diff --git a/.changeset/docs-gen-description-line-layout-and-nested-links.md b/.changeset/docs-gen-description-line-layout-and-nested-links.md new file mode 100644 index 0000000000..38525043a5 --- /dev/null +++ b/.changeset/docs-gen-description-line-layout-and-nested-links.md @@ -0,0 +1,58 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): docs-gen renders a module description as the markdown it was written as (#5553, #6136) + +Two independent defects in `scripts/lib/file-description.ts`, both from a +transform applied at the wrong granularity. The block SELECTION rule #5059 added +is untouched: all 185 sources that carried a module header still render one, and +no page gained or lost an opening paragraph. + +**#5553 — line layout is content, not decoration.** The renderer dropped every +blank line and joined what survived with `\n\n`, making each SOURCE LINE its own +paragraph. Anything that legitimately wraps across lines was then cut in half by +a paragraph boundary, and an inline code span cannot cross one, so both of its +backticks fell out as literal text — `` `explain(principal, object, `` / +`` operation)` `` on `security/explain`, and three more like it. The same pass +escaped `{` and `}` everywhere including inside code, where a backslash is not +an escape character but a character the reader sees, so pages published +`` `\{ dialect, source \}` ``. + +The fix is to stop rewriting the layout: strip the ` * ` gutter and keep the +lines as authored. Markdown's own rules then do what the issue asked for — +consecutive lines are one paragraph, a blank line opens the next — and lists, +headings, tables and code blocks keep working, which the literal space-join the +issue floated would have broken on the 85 sources that write a list. Escaping and +link resolution are now scoped to prose: fenced and indented code blocks are +copied verbatim, and within prose a tokenizer keeps inline code spans out of +reach. + +One construct is deliberately NOT reproduced as authored: an indented (4-space) +code block is re-emitted as a fenced one. MDX dropped CommonMark's indented code +blocks so that indentation could lay out JSX, so such a block reaches the MDX +compiler as ordinary prose — and unescaped braces in prose are an expression. +`data/date-macros` and `data/context-tokens` write their placeholder examples +that way and are almost entirely braces; left indented they fail to compile +("Could not parse expression with acorn"), and escaped instead they show `\{` in +what is meant to be code. The target dialect has one spelling for a code block. + +Measured over the 185 rendered descriptions: paragraphs with unpaired backticks +8 → 0 (`automation/flow-function`, `security/explain`, `shared/expression`, +`system/settings-client`), and backslash-brace residue inside code 296 → 0 across +33 pages. 32 pages get their fenced `@example` sample back as a real code block +instead of one escaped paragraph per line, and 47 regain the indentation that +made a nested list nested. The issue named five victim pages; `system/doc` is not +among them because #5059 has since found its header documents `DocSchema` and +stopped publishing it. + +**#6136 — a rewriter that ran over its own output.** The untitled +`{@link }` branch emits `[]()`, whose link TEXT is the path +itself. The bare-source-path rewriter ran next over the whole string and matched +that text, wrapping it a second time into a link nested in a link. Lookaround +cannot express "not nested inside a link", so the rewriter is now applied per +prose token with formed links excluded. `automation/etl` and +`integration/connector` each get their "See also" back as one clickable link. + +169 reference pages are regenerated. No runtime, package export or protocol +semantics change — this is the docs generator only. diff --git a/content/docs/references/ai/conversation.mdx b/content/docs/references/ai/conversation.mdx index 07ca32e7dc..aa4694814c 100644 --- a/content/docs/references/ai/conversation.mdx +++ b/content/docs/references/ai/conversation.mdx @@ -8,7 +8,6 @@ description: Conversation protocol schemas AI Conversation Memory Protocol Multi-turn AI conversations with token budget management. - Enables context preservation, conversation history, and token optimization. diff --git a/content/docs/references/ai/embedding.mdx b/content/docs/references/ai/embedding.mdx index 83bcb8f744..70c0d15773 100644 --- a/content/docs/references/ai/embedding.mdx +++ b/content/docs/references/ai/embedding.mdx @@ -10,25 +10,17 @@ Embedding & Vector Store Primitives Platform contract for configuring embedding models and vector stores. Scope (intentionally minimal): - - How to reference an embedding model (provider + model name + secret). - - How to reference a vector store (provider + connection). NOT in scope (these belong to application code, not the platform): - - Chunking strategies (fixed/semantic/recursive/markdown). - - Retrieval pipelines (rerankers, multi-stage retrieval, filters). - - Document loaders / ingestion DSLs. - - End-to-end RAG pipeline orchestration. These were removed in v1 because they describe one specific way to - build a RAG application; the platform's job is to expose the embed + - vector primitives so any RAG strategy can be built on top. diff --git a/content/docs/references/ai/knowledge-document.mdx b/content/docs/references/ai/knowledge-document.mdx index 1d9d862826..63dbc202f6 100644 --- a/content/docs/references/ai/knowledge-document.mdx +++ b/content/docs/references/ai/knowledge-document.mdx @@ -6,15 +6,11 @@ description: Knowledge Document protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Knowledge Document / Chunk / Hit — canonical shapes shared by every - `IKnowledgeAdapter` implementation. The framework does **not** prescribe chunk strategy or vector - format. Adapters are free to chunk however they like; the framework - only requires they round-trip these shapes when talking to the - `IKnowledgeService`. See `content/docs/protocol/knowledge.mdx` for the full design. diff --git a/content/docs/references/ai/knowledge-source.mdx b/content/docs/references/ai/knowledge-source.mdx index 9f5e305cf1..cc2592099e 100644 --- a/content/docs/references/ai/knowledge-source.mdx +++ b/content/docs/references/ai/knowledge-source.mdx @@ -6,17 +6,12 @@ description: Knowledge Source protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Knowledge Source — declarative metadata describing what to index and - which adapter to use. A KnowledgeSource is the metadata-level equivalent of an - `IDataEngine` driver binding: it pairs a logical source description - (object/file/http) with the *id* of an `IKnowledgeAdapter` plugin - that will actually do the work. The adapter resolves the id at - runtime via `IKnowledgeService.registerAdapter`. See `content/docs/protocol/knowledge.mdx` for the full design. diff --git a/content/docs/references/ai/mcp.mdx b/content/docs/references/ai/mcp.mdx index c8b707e632..f1aa69c8b8 100644 --- a/content/docs/references/ai/mcp.mdx +++ b/content/docs/references/ai/mcp.mdx @@ -8,29 +8,20 @@ description: Mcp protocol schemas Model Context Protocol (MCP) — Reference & Binding Primitives MCP itself is an external protocol defined by Anthropic - (https://modelcontextprotocol.io). The platform does NOT re-define - MCP's wire format, transport, or message shapes — that is the job - of the `@modelcontextprotocol/sdk` consumed by `@objectstack/mcp`. This file defines only the two things the *platform* needs: 1. **MCPServerRef** — how a project references an external MCP - -server (so an agent can mount its tools). - + server (so an agent can mount its tools). 2. **MCPToolBinding** — how an MCP tool from a referenced server - -is exposed as an ObjectStack `AIToolDefinition` (alias, - -visibility, approval policy). + is exposed as an ObjectStack `AIToolDefinition` (alias, + visibility, approval policy). Everything else (transport details, capability negotiation, - resource/prompt shapes, streaming, sampling) is handled by the SDK - at runtime and does not need a metadata representation. diff --git a/content/docs/references/ai/model-registry.mdx b/content/docs/references/ai/model-registry.mdx index c6f335101d..69c1ae3703 100644 --- a/content/docs/references/ai/model-registry.mdx +++ b/content/docs/references/ai/model-registry.mdx @@ -8,7 +8,6 @@ description: Model Registry protocol schemas AI Model Registry Protocol Centralized registry for managing AI models, prompt templates, and model versioning. - Enables AI-powered ObjectStack applications to discover and use LLMs consistently. diff --git a/content/docs/references/ai/skill.mdx b/content/docs/references/ai/skill.mdx index daf20e3eb7..04c9fb714f 100644 --- a/content/docs/references/ai/skill.mdx +++ b/content/docs/references/ai/skill.mdx @@ -8,7 +8,6 @@ description: Skill protocol schemas Skill Trigger Condition Schema Defines programmatic conditions under which a skill becomes active. - Allows context-aware activation based on object type, user role, etc. diff --git a/content/docs/references/ai/usage.mdx b/content/docs/references/ai/usage.mdx index f7e0cfa80a..5010f06e7a 100644 --- a/content/docs/references/ai/usage.mdx +++ b/content/docs/references/ai/usage.mdx @@ -10,25 +10,17 @@ AI Usage Primitives Platform contract for measuring AI consumption. Scope (intentionally minimal): - - Token usage per call. - - Per-call cost (computed from a model's unit price). - - Model unit pricing. NOT in scope (deferred to FinOps / product layer): - - Budget definitions, enforcement, alerts. - - Cost allocation / chargeback / reports. - - Optimization recommendations. Rationale: the platform must record *what was used*; deciding what - to do about it (block calls, send alerts, allocate to cost centers) - is product policy that varies wildly between tenants. diff --git a/content/docs/references/api/analytics.mdx b/content/docs/references/api/analytics.mdx index 14f01fa2dc..145d2080bc 100644 --- a/content/docs/references/api/analytics.mdx +++ b/content/docs/references/api/analytics.mdx @@ -8,7 +8,6 @@ description: Analytics protocol schemas Analytics API Protocol Defines the HTTP interface for the Semantic Layer. - Provides endpoints for executing analytical queries and discovering metadata. diff --git a/content/docs/references/api/auth-endpoints.mdx b/content/docs/references/api/auth-endpoints.mdx index a95d20ac65..7fc3dd9902 100644 --- a/content/docs/references/api/auth-endpoints.mdx +++ b/content/docs/references/api/auth-endpoints.mdx @@ -8,13 +8,10 @@ description: Auth Endpoints protocol schemas Authentication Endpoint Specification Defines the canonical HTTP endpoints for the authentication service. - Based on better-auth v1.4.18 endpoint conventions. NOTE: ObjectStack's auth implementation uses better-auth library which has - established endpoint conventions. This spec documents those conventions as - the canonical API contract. diff --git a/content/docs/references/api/auth.mdx b/content/docs/references/api/auth.mdx index ded8f69d12..6198f257c2 100644 --- a/content/docs/references/api/auth.mdx +++ b/content/docs/references/api/auth.mdx @@ -8,7 +8,6 @@ description: Auth protocol schemas Authentication Service Protocol Defines the standard API contracts for Identity, Session Management, - and Access Control. diff --git a/content/docs/references/api/automation-api.mdx b/content/docs/references/api/automation-api.mdx index 77c6fb2353..b16dc05463 100644 --- a/content/docs/references/api/automation-api.mdx +++ b/content/docs/references/api/automation-api.mdx @@ -8,29 +8,19 @@ description: Automation Api protocol schemas Automation API Protocol Defines REST CRUD endpoint schemas for managing automation flows, - triggering executions, and querying execution history. Base path: /api/automation @example Endpoints - GET /api/automation — List flows - GET /api/automation/:name — Get flow - POST /api/automation — Create flow - PUT /api/automation/:name — Update flow - DELETE /api/automation/:name — Delete flow - POST /api/automation/:name/trigger — Trigger flow execution - POST /api/automation/:name/toggle — Enable/disable flow - GET /api/automation/:name/runs — List execution runs - GET /api/automation/:name/runs/:runId — Get single execution run diff --git a/content/docs/references/api/batch.mdx b/content/docs/references/api/batch.mdx index 10c2de1c95..63e45d3ae3 100644 --- a/content/docs/references/api/batch.mdx +++ b/content/docs/references/api/batch.mdx @@ -8,17 +8,12 @@ description: Batch protocol schemas Batch Operations API Provides efficient bulk data operations with transaction support. - Implements P0/P1 requirements for ObjectStack kernel. Features: - - Batch create/update/delete operations - - Atomic transaction support (all-or-none) - - Partial success handling - - Detailed error reporting per record Industry alignment: Salesforce Bulk API, Microsoft Dynamics Bulk Operations diff --git a/content/docs/references/api/discovery.mdx b/content/docs/references/api/discovery.mdx index badd228346..fbb731f391 100644 --- a/content/docs/references/api/discovery.mdx +++ b/content/docs/references/api/discovery.mdx @@ -6,19 +6,13 @@ description: Discovery protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Service Status Enum - Describes the operational state of a service in the discovery response. - `available` – Fully operational: service is registered AND HTTP handler is verified. - - `registered` – Route is declared in the dispatcher table but the HTTP handler has - -not been verified (may 501 at runtime). - + not been verified (may 501 at runtime). - `unavailable` – Service is not installed / not registered in the kernel. - - `degraded` – Partially working (e.g., in-memory fallback, missing persistence). - - `stub` – Placeholder handler that always returns 501 Not Implemented. diff --git a/content/docs/references/api/dispatcher.mdx b/content/docs/references/api/dispatcher.mdx index a30e67c98b..f6ba6ff817 100644 --- a/content/docs/references/api/dispatcher.mdx +++ b/content/docs/references/api/dispatcher.mdx @@ -8,25 +8,17 @@ description: Dispatcher protocol schemas # HttpDispatcher Protocol Defines how the ObjectStack HttpDispatcher routes incoming API requests - to the correct kernel service based on URL prefix matching. The dispatcher is the central routing component that: - 1. Matches incoming request URLs against registered route prefixes - 2. Delegates to the corresponding CoreService implementation - 3. Returns 503 Service Unavailable when a service is not registered - 4. Supports dynamic route registration from plugins via contributes.routes Architecture alignment: - - Kubernetes: API server aggregation layer - - Eclipse: Extension registry routing - - VS Code: Command palette routing diff --git a/content/docs/references/api/documentation.mdx b/content/docs/references/api/documentation.mdx index a2dac688dd..e16fcb07ad 100644 --- a/content/docs/references/api/documentation.mdx +++ b/content/docs/references/api/documentation.mdx @@ -8,55 +8,33 @@ description: Documentation protocol schemas API Documentation & Testing Interface Protocol Provides schemas for generating interactive API documentation and testing - interfaces similar to Swagger UI, Postman, etc. Features: - - OpenAPI/Swagger specification generation - - Interactive API testing playground - - API versioning and changelog - - Code generation templates - - Mock server configuration Architecture Alignment: - - Swagger UI: Interactive API documentation - - Postman: API testing collections - - Redoc: Documentation rendering @example Documentation Config - ```typescript - -const docConfig: ApiDocumentationConfig = \{ - -enabled: true, - -title: 'ObjectStack API', - -version: '1.0.0', - -servers: [\{ url: 'https://api.example.com', description: 'Production' \}], - -ui: \{ - -type: 'swagger-ui', - -theme: 'light', - -enableTryItOut: true - -\} - -\} - +const docConfig: ApiDocumentationConfig = { + enabled: true, + title: 'ObjectStack API', + version: '1.0.0', + servers: [{ url: 'https://api.example.com', description: 'Production' }], + ui: { + type: 'swagger-ui', + theme: 'light', + enableTryItOut: true + } +} ``` diff --git a/content/docs/references/api/endpoint.mdx b/content/docs/references/api/endpoint.mdx index 327498102b..7718002bc9 100644 --- a/content/docs/references/api/endpoint.mdx +++ b/content/docs/references/api/endpoint.mdx @@ -6,7 +6,6 @@ description: Endpoint protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} API Mapping Schema - Transform input/output data. diff --git a/content/docs/references/api/errors.mdx b/content/docs/references/api/errors.mdx index ce3d2729ed..57bcbba85b 100644 --- a/content/docs/references/api/errors.mdx +++ b/content/docs/references/api/errors.mdx @@ -8,17 +8,12 @@ description: Errors protocol schemas Standardized Error Codes Protocol Implements P0 requirement for ObjectStack kernel. - Provides consistent, machine-readable error codes across the platform. Features: - - Categorized error codes (validation, authentication, authorization, etc.) - - HTTP status code mapping - - Localization support - - Retry guidance Industry alignment: Google Cloud Errors, AWS Error Codes, Stripe API Errors diff --git a/content/docs/references/api/events.mdx b/content/docs/references/api/events.mdx index c758b72374..b0197a8b26 100644 --- a/content/docs/references/api/events.mdx +++ b/content/docs/references/api/events.mdx @@ -8,15 +8,11 @@ description: Events protocol schemas Metadata Event Types Triggered when metadata items are created, updated, or deleted. - -Follows the pattern: `metadata.\{type\}.\{action\}` +Follows the pattern: `metadata.{type}.{action}` Examples: - - `metadata.object.created` - A new object was created - - `metadata.view.updated` - A view was updated - - `metadata.agent.deleted` - An agent was deleted diff --git a/content/docs/references/api/export.mdx b/content/docs/references/api/export.mdx index 574e105cf7..9b17f0ea26 100644 --- a/content/docs/references/api/export.mdx +++ b/content/docs/references/api/export.mdx @@ -8,11 +8,9 @@ description: Export protocol schemas Data Export & Import Protocol Defines schemas for streaming data export, import validation, - template-based field mapping, and scheduled export jobs. Industry alignment: Salesforce Data Export, Airtable CSV Export, - Dynamics 365 Data Management. Base path: /api/v1/data/\{object\}/export diff --git a/content/docs/references/api/http-cache.mdx b/content/docs/references/api/http-cache.mdx index 270dcd4905..7121c2ee97 100644 --- a/content/docs/references/api/http-cache.mdx +++ b/content/docs/references/api/http-cache.mdx @@ -8,43 +8,28 @@ description: Http Cache protocol schemas HTTP Metadata Cache Protocol Implements efficient HTTP-level metadata caching with ETag support. - Implements P0 requirement for ObjectStack kernel. ## Caching in ObjectStack -**HTTP Cache (`[api/http-cache.zod.ts](/docs/references/api/http-cache)`) - This File** - +**HTTP Cache (`api/http-cache.zod.ts`) - This File** - **Purpose**: Cache API responses at HTTP protocol level - - **Technologies**: HTTP headers (ETag, Last-Modified, Cache-Control), CDN - - **Configuration**: Cache-Control headers, validation tokens - - **Use case**: Reduce API response time for repeated metadata requests - - **Scope**: HTTP layer, client-server communication -**Application Cache (`[system/cache.zod.ts](/docs/references/system/cache)`)** - +**Application Cache (`system/cache.zod.ts`)** - **Purpose**: Cache computed data, query results, aggregations - - **Technologies**: Redis, Memcached, in-memory LRU - - **Configuration**: TTL, eviction policies, cache warming - - **Use case**: Cache expensive database queries, computed values - - **Scope**: Application layer, server-side data storage ## Features - - ETag-based conditional requests (HTTP 304 Not Modified) - - Cache-Control directives - - Metadata versioning - - Selective cache invalidation Industry alignment: HTTP Caching (RFC 7234), Salesforce Metadata API diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index e5b1811856..51a1662cd9 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -8,39 +8,24 @@ description: Metadata protocol schemas Metadata Service Protocol Defines the standard API contracts for the **@objectstack/metadata** package. - This is the single authority for ALL metadata-related services and APIs across - the entire platform, including Hono, Next.js, and NestJS adapters. ## Architecture - ``` - ┌──────────────────────────────────────────────────────────────────┐ - │ @objectstack/metadata — API Contracts │ - │ │ - │ CRUD │ Query/Search │ Bulk Ops │ Overlay │ Watch │ - │ Import/Export│ Validation │ Type Reg │ Deps │ │ - ├──────────────────────────────────────────────────────────────────┤ - │ Hono Adapter │ Next.js Adapter │ NestJS Adapter │ CLI │ - └──────────────────────────────────────────────────────────────────┘ - ``` ## Alignment - - **Salesforce**: Metadata API (deploy, retrieve, describe) - - **ServiceNow**: System Dictionary + Metadata API - - **Kubernetes**: API Server + CRD Registry diff --git a/content/docs/references/api/odata.mdx b/content/docs/references/api/odata.mdx index f8dd6c2f4b..6ec9d90812 100644 --- a/content/docs/references/api/odata.mdx +++ b/content/docs/references/api/odata.mdx @@ -8,101 +8,63 @@ description: Odata protocol schemas OData v4 Protocol Support Open Data Protocol (OData) v4 is an industry-standard protocol for building - and consuming RESTful APIs. It provides a uniform way to expose, structure, - query, and manipulate data. ## Overview OData v4 provides standardized URL conventions for querying data including: - - $select: Choose which fields to return - - $filter: Filter results with complex expressions - - $orderby: Sort results - - $top/$skip: Pagination - - $expand: Include related entities - - $count: Get total count ## Use Cases 1. **Enterprise Integration** - -- Integrate with Microsoft Dynamics 365 - -- Connect to SharePoint Online - -- SAP OData services + - Integrate with Microsoft Dynamics 365 + - Connect to SharePoint Online + - SAP OData services 2. **API Standardization** - -- Provide consistent query interface - -- Standard pagination and filtering - -- Industry-recognized protocol + - Provide consistent query interface + - Standard pagination and filtering + - Industry-recognized protocol 3. **External Data Sources** - -- Connect to OData-compliant systems - -- Federated queries - -- Data virtualization + - Connect to OData-compliant systems + - Federated queries + - Data virtualization See also: https://www.odata.org/documentation/ See also: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html @example OData Query - ``` - GET /api/odata/customers? - -$select=name,email& - -$filter=country eq 'US' and revenue gt 100000& - -$orderby=revenue desc& - -$top=10& - -$skip=20& - -$expand=orders& - -$count=true - + $select=name,email& + $filter=country eq 'US' and revenue gt 100000& + $orderby=revenue desc& + $top=10& + $skip=20& + $expand=orders& + $count=true ``` @example Programmatic Use - ```typescript - -const query: ODataQuery = \{ - -select: ['name', 'email'], - -filter: "country eq 'US' and revenue gt 100000", - -orderby: 'revenue desc', - -top: 10, - -skip: 20, - -expand: ['orders'], - -count: true - -\} - +const query: ODataQuery = { + select: ['name', 'email'], + filter: "country eq 'US' and revenue gt 100000", + orderby: 'revenue desc', + top: 10, + skip: 20, + expand: ['orders'], + count: true +} ``` diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index b3c5a50751..1401053cde 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -12,21 +12,13 @@ REST API endpoint schemas for package lifecycle management. Base path: /api/v1/packages @example Endpoints - POST /api/v1/packages/install — Install a package - POST /api/v1/packages/upgrade — Upgrade a package - POST /api/v1/packages/resolve-dependencies — Resolve dependencies - POST /api/v1/packages/upload — Upload an artifact - GET /api/v1/packages — List installed packages - GET /api/v1/packages/:packageId — Get package details - POST /api/v1/packages/:packageId/rollback — Rollback a package - DELETE /api/v1/packages/:packageId — Uninstall a package diff --git a/content/docs/references/api/plugin-rest-api.mdx b/content/docs/references/api/plugin-rest-api.mdx index ea98b9d612..0dfd3f1765 100644 --- a/content/docs/references/api/plugin-rest-api.mdx +++ b/content/docs/references/api/plugin-rest-api.mdx @@ -8,105 +8,59 @@ description: Plugin Rest Api protocol schemas REST API Plugin Protocol Defines the schema for REST API plugins that register Discovery, Metadata, - Data CRUD, Batch, and Permission routes with the HTTP Dispatcher. This plugin type implements Phase 2 of the API Protocol implementation plan, - providing standardized REST endpoints with: - - Request validation middleware using Zod schemas - - Response envelope wrapping with BaseResponseSchema - - Error handling using ApiErrorSchema - - OpenAPI documentation auto-generation Features: - - Route registration for core API endpoints - - Automatic schema-based validation - - Standardized request/response envelopes - - OpenAPI/Swagger documentation generation Architecture Alignment: - - Salesforce: REST API with metadata and data CRUD - - Microsoft Dynamics: Web API with entity operations - - Strapi: Auto-generated REST endpoints from schemas @example Plugin Manifest - ```typescript - -\{ - -"name": "rest_api", - -"version": "1.0.0", - -"type": "server", - -"contributes": \{ - -"routes": [ - -\{ - -"prefix": "/api/v1/discovery", - -"service": "metadata", - -"methods": ["getDiscovery"], - -"middleware": [ - -\{ "name": "response_envelope", "type": "transformation", "enabled": true \} - -] - -\}, - -\{ - -"prefix": "/api/v1/meta", - -"service": "metadata", - -"methods": ["getMetaTypes", "getMetaItems", "getMetaItem", "saveMetaItem"], - -"middleware": [ - -\{ "name": "auth", "type": "authentication", "enabled": true \}, - -\{ "name": "request_validation", "type": "validation", "enabled": true \} - -] - -\}, - -\{ - -"prefix": "/api/v1/data", - -"service": "data", - -"methods": ["findData", "getData", "createData", "updateData", "deleteData"] - -\} - -] - -\} - -\} - +{ + "name": "rest_api", + "version": "1.0.0", + "type": "server", + "contributes": { + "routes": [ + { + "prefix": "/api/v1/discovery", + "service": "metadata", + "methods": ["getDiscovery"], + "middleware": [ + { "name": "response_envelope", "type": "transformation", "enabled": true } + ] + }, + { + "prefix": "/api/v1/meta", + "service": "metadata", + "methods": ["getMetaTypes", "getMetaItems", "getMetaItem", "saveMetaItem"], + "middleware": [ + { "name": "auth", "type": "authentication", "enabled": true }, + { "name": "request_validation", "type": "validation", "enabled": true } + ] + }, + { + "prefix": "/api/v1/data", + "service": "data", + "methods": ["findData", "getData", "createData", "updateData", "deleteData"] + } + ] + } +} ``` diff --git a/content/docs/references/api/query-adapter.mdx b/content/docs/references/api/query-adapter.mdx index aa61332c3b..3281c36acc 100644 --- a/content/docs/references/api/query-adapter.mdx +++ b/content/docs/references/api/query-adapter.mdx @@ -8,13 +8,10 @@ description: Query Adapter protocol schemas API Query DSL Adapter Protocol Defines mapping rules between the internal unified query DSL - -(defined in `[data/query.zod.ts](/docs/references/data/query)`) and external API protocol formats: - +(defined in `data/query.zod.ts`) and external API protocol formats: REST and OData. This enables ObjectStack to expose a single internal query representation - while supporting multiple API standards for external consumers. See also: [data/query.zod.ts](/docs/references/data/query) - Unified internal query DSL diff --git a/content/docs/references/api/realtime-shared.mdx b/content/docs/references/api/realtime-shared.mdx index 74533569c4..c62e523242 100644 --- a/content/docs/references/api/realtime-shared.mdx +++ b/content/docs/references/api/realtime-shared.mdx @@ -8,17 +8,12 @@ description: Realtime Shared protocol schemas Realtime Shared Protocol Shared schemas and types for real-time communication protocols. - This module consolidates overlapping definitions between the transport-level - realtime protocol (SSE/Polling/WebSocket) and the WebSocket collaboration protocol. **Architecture:** - - `realtime-shared.zod.ts` — Shared base schemas (Presence, Event types) - - `realtime.zod.ts` — Transport-layer protocol (Channel, Subscription, Transport selection) - - `websocket.zod.ts` — Collaboration protocol (Cursor, OT editing, Advanced presence) See also: realtime.zod.ts for transport-layer configuration diff --git a/content/docs/references/api/rest-server.mdx b/content/docs/references/api/rest-server.mdx index 20d52779cb..159e03e37a 100644 --- a/content/docs/references/api/rest-server.mdx +++ b/content/docs/references/api/rest-server.mdx @@ -8,27 +8,18 @@ description: Rest Server protocol schemas REST API Server Protocol Defines the REST API server configuration for automatically generating - RESTful CRUD endpoints, metadata endpoints, and batch operations. Features: - - Automatic CRUD endpoint generation from Object definitions - - Standard REST conventions (GET, POST, PUT, PATCH, DELETE) - - Metadata API endpoints - - Batch operation endpoints - - OpenAPI/Swagger documentation generation Architecture alignment: - - Salesforce: REST API with Object CRUD - - Microsoft Dynamics: Web API with entity operations - - Strapi: Auto-generated REST endpoints diff --git a/content/docs/references/api/storage.mdx b/content/docs/references/api/storage.mdx index 7618c0bb5a..18e178dd01 100644 --- a/content/docs/references/api/storage.mdx +++ b/content/docs/references/api/storage.mdx @@ -8,9 +8,7 @@ description: Storage protocol schemas Storage Service Protocol Defines the API contract for client-side file operations. - Focuses on secure, direct-to-cloud uploads (Presigned URLs) - rather than proxying bytes through the API server. diff --git a/content/docs/references/api/versioning.mdx b/content/docs/references/api/versioning.mdx index f03d080ec2..52a1e75715 100644 --- a/content/docs/references/api/versioning.mdx +++ b/content/docs/references/api/versioning.mdx @@ -8,19 +8,13 @@ description: Versioning protocol schemas # API Versioning Protocol Defines how API versions are negotiated between client and server. - Supports multiple versioning strategies and deprecation lifecycle management. Architecture Alignment: - - Salesforce: URL path versioning (v57.0, v58.0) - - Stripe: Date-based versioning (2024-01-01) - - Kubernetes: API group versioning (v1, v1beta1) - - GitHub: Accept header versioning (application/vnd.github.v3+json) - - Microsoft Graph: URL path versioning (v1.0, beta) diff --git a/content/docs/references/api/websocket.mdx b/content/docs/references/api/websocket.mdx index 2b59093556..eb3d1172be 100644 --- a/content/docs/references/api/websocket.mdx +++ b/content/docs/references/api/websocket.mdx @@ -8,19 +8,14 @@ description: Websocket protocol schemas WebSocket Event Protocol Defines the schema for WebSocket-based real-time communication in ObjectStack. - Supports event subscriptions, filtering, presence tracking, and collaborative editing. Industry alignment: Firebase Realtime Database, Socket.IO, Pusher ⚠️ NOT YET SERVED — this protocol is declared but no WebSocket server is - mounted anywhere in the runtime (#2462, #3197): `IRealtimeService.handleUpgrade` - is deliberately unimplemented and discovery advertises `websockets: false`. - These schemas define the future wire contract; nothing consumes them at - runtime today. diff --git a/content/docs/references/automation/bpmn-interop.mdx b/content/docs/references/automation/bpmn-interop.mdx index f45ff6c252..e995fcab23 100644 --- a/content/docs/references/automation/bpmn-interop.mdx +++ b/content/docs/references/automation/bpmn-interop.mdx @@ -10,9 +10,7 @@ description: Bpmn Interop protocol schemas BPMN XML Interoperability Protocol Defines the specification for importing and exporting BPMN 2.0 XML - process definitions. This enables interoperability with external BPM - tools (Camunda, Activiti, jBPM, etc.) via a plugin-based approach. **Priority:** Low — long-term planning, not a core requirement. diff --git a/content/docs/references/automation/builtin-node-config.mdx b/content/docs/references/automation/builtin-node-config.mdx index 1792b6e071..f78db97789 100644 --- a/content/docs/references/automation/builtin-node-config.mdx +++ b/content/docs/references/automation/builtin-node-config.mdx @@ -8,118 +8,71 @@ description: Builtin Node Config protocol schemas @module automation/builtin-node-config Config contracts for the remaining flat builtins — the CRUD quartet - (`get_record` / `create_record` / `update_record` / `delete_record`), - `screen`, and `map` (#4045). Sibling of `io-node-config.zod.ts` - (notify / http) and `control-flow.zod.ts` (loop / parallel / try_catch). ## Provenance — written from the executors, not from the forms Each schema was derived by reading what the executor actually does with - `node.config` (`service-automation/builtin/crud-nodes.ts`, - `screen-nodes.ts`, `map-node.ts`), **not** by transcribing the hand-written - `configSchema` literal on the node's descriptor. The two artifacts are - reconciled bidirectionally by `builtin-node-form-zod-ledger.test.ts` in - `service-automation`; a Zod copied from the form would make that - reconciliation a tautology (#4045). Writing these against the executors is what surfaced the drift the - reconciliation exists to catch — keys the executors read that no form - offered anywhere (`get_record.fields`, `screen.recordId`, the screen - field-item keys `options`/`defaultValue`/`placeholder`, `map.indexVariable`, - `map.input`), and the undeclared `map.flow` alias, which graduated into the - ADR-0087 D2 conversion layer like `notify.source` before it. ## What these schemas are wired to (#4277) Live execute-time contracts: each executor `parse()`s its config against - its schema before running (`service-automation`'s `parse-config.ts`), so - type and `required` violations refuse the node as a guard. All of these - parse the RAW stored config — their typed slots are strings (or `unknown` - -where values interpolate), so `\{token\}` templates pass and resolve at the - +where values interpolate), so `{token}` templates pass and resolve at the executor's existing interpolation points. ## Unknown keys — closed here too, as of #4001 批 9 These contracts used to say "unknown keys are rejected earlier, at - `registerFlow()` (the tightened #4059 check); the parse here strips them." - The registration walk is still the first and more informative door — it - descends NESTED config against the descriptor's JSON Schema, which is how it - catches `fields[0].visibleIf` (#3528) and not just top-level typos — but - "some other door is closed" is the exact reasoning #4001 exists to retire: - the sibling of every guard in this campaign turned out to leave the other - doors open, because its author was fixing one bug rather than auditing a - surface. A config reaching `parse()` without passing registration (tooling - that parses a contract directly, a host composing the engine itself) is no - longer silently trimmed. The two doors are kept in agreement by `builtin-node-form-zod-ledger.test.ts`, - which reconciles these key sets against the descriptors' in both directions. - The per-key prescriptions below are the same curation the registration - rejection carries in `FLOW_NODE_UNKNOWN_KEY_GUIDANCE` — the campaign's - finding is that a bespoke guard's detection generalizes for free the moment - a default flips, while its PROSE does not, so the prose is copied to the new - door rather than left behind at the old one. Deliberately absent: - -- `assignment` — its config cannot be described by a fixed key set: with no - -`assignments` wrapper the TOP-LEVEL config keys ARE the author's variable - -names (logic-nodes.ts). The ledger test pins that exemption with its - -reason instead of pretending a shape. - -- `decision` / `script` / `subflow` / `wait` / `connector_action` — the - -descriptor-schemaless class (config-schemas.test.ts). `wait` and - -`connector_action` keep their contracts in FlowNodeSchema's sibling - -blocks (`waitEventConfig` / `connectorConfig`); the other three publish - -executor-derived config contracts in `schemaless-node-config.zod.ts` - -(#4278) — separate from this module because they must NOT grow into - -descriptor `configSchema`s (the forms they describe stay hand-written in - -objectui, reconciled by a test there). + - `assignment` — its config cannot be described by a fixed key set: with no + `assignments` wrapper the TOP-LEVEL config keys ARE the author's variable + names (logic-nodes.ts). The ledger test pins that exemption with its + reason instead of pretending a shape. + - `decision` / `script` / `subflow` / `wait` / `connector_action` — the + descriptor-schemaless class (config-schemas.test.ts). `wait` and + `connector_action` keep their contracts in FlowNodeSchema's sibling + blocks (`waitEventConfig` / `connectorConfig`); the other three publish + executor-derived config contracts in `schemaless-node-config.zod.ts` + (#4278) — separate from this module because they must NOT grow into + descriptor `configSchema`s (the forms they describe stay hand-written in + objectui, reconciled by a test there). **Source:** `packages/spec/src/automation/builtin-node-config.zod.ts` diff --git a/content/docs/references/automation/control-flow.mdx b/content/docs/references/automation/control-flow.mdx index ed44f396c8..7a839bca46 100644 --- a/content/docs/references/automation/control-flow.mdx +++ b/content/docs/references/automation/control-flow.mdx @@ -8,107 +8,67 @@ description: Control Flow protocol schemas @module automation/control-flow Structured control-flow constructs (ADR-0031) — the **native + AI-authored** - flow model: a `loop` **container**, a `parallel` **block**, and structured - `try/catch/retry`. Unlike BPMN's gateway/boundary/token graph (kept in the - protocol for *interop* only), these constructs are **well-formed by - construction**, locally composable, and statically analyzable — the right - substrate for LLM authoring (ADR-0010/0011). ## Representation — decision: **(B) nested sub-structure** ADR-0031 flagged two ways to carry structured containers in the flat - `nodes[]`+`edges[]` model: -- **(A)** marker-delimited scoped regions (a container node + a scope-end - -marker; the body is the edges *between* them in the main graph), or - -- **(B)** the container node carries a **nested mini-flow** in its `config`. + - **(A)** marker-delimited scoped regions (a container node + a scope-end + marker; the body is the edges *between* them in the main graph), or + - **(B)** the container node carries a **nested mini-flow** in its `config`. We adopt **(B)**. Each container holds its body as a self-contained - `FlowRegionSchema` (`config.body` for `loop`, `config.branches[]` for - `parallel`, `config.try`/`config.catch` for `try_catch`). The reasons: -1. **Well-formed by construction** — a nested region is its *own* graph, so - -single-entry is intrinsic; there are no scope markers to balance and no - -way to "leak" an edge across a boundary. Validation is local. - -2. **The shared engine traversal stays untouched** — the container executor - -runs its own body via a scoped helper; the main DAG `traverseNext` never - -learns about scope markers (important under the multi-agent discipline - -around `engine.ts`). The container's *ordinary* out-edges remain the - -"after-loop / after-block" continuation. - -3. **Cleaner AST for AI** — ADR-0031 calls (B) "the cleaner long-term AST," - -and AI authoring is the design center. + 1. **Well-formed by construction** — a nested region is its *own* graph, so + single-entry is intrinsic; there are no scope markers to balance and no + way to "leak" an edge across a boundary. Validation is local. + 2. **The shared engine traversal stays untouched** — the container executor + runs its own body via a scoped helper; the main DAG `traverseNext` never + learns about scope markers (important under the multi-agent discipline + around `engine.ts`). The container's *ordinary* out-edges remain the + "after-loop / after-block" continuation. + 3. **Cleaner AST for AI** — ADR-0031 calls (B) "the cleaner long-term AST," + and AI authoring is the design center. Existing flat-graph loops (a `loop` node with no `config.body`) keep their - legacy behavior — the constructs are **additive**, activated only when the - nested structure is present. The canonical construct type ids are `LOOP_NODE_TYPE` (`loop`, - pre-existing), `PARALLEL_NODE_TYPE` (`parallel`), and - `TRY_CATCH_NODE_TYPE` (`try_catch`). These are distinct from the BPMN - interop node types (`parallel_gateway` / `join_gateway` / `boundary_event`), - which remain author-invisible interchange representations. ## Unknown keys are rejected (#4001 / ADR-0078) Every shape below is `strictObject`. Before that they were plain `z.object`, - so zod's default `.strip` applied and a key this file does not declare was - **discarded in silence** — the container still parsed, still registered, and - still ran, with the author's configuration simply absent. On these five - shapes that silence is unusually expensive, because each one carries - *control* rather than data: a swallowed `maxIterations` is an uncapped loop, - a swallowed branch key is a branch that runs without what it was given. ### How this relates to `validateControlFlow` `validateControlFlow` is a **sibling guard, not a key gate** — it answers - "is this region single-entry / single-exit / acyclic", which no amount of - key strictness can answer. The two do not overlap and cannot fight: the - schema rejects undeclared KEYS, the analysis rejects malformed STRUCTURE. - They do now meet at one seam, deliberately — `validateControlFlow` - `safeParse`s each region slot before analyzing it, so from #4001 that parse - is also where a region's undeclared key surfaces, reported as - `: invalid region — `. Nothing was - duplicated and nothing was removed; the structural prose this guard exists - for is untouched, and it simply stopped silently repairing its own input. diff --git a/content/docs/references/automation/etl.mdx b/content/docs/references/automation/etl.mdx index 89b7d09794..4bfd3a8429 100644 --- a/content/docs/references/automation/etl.mdx +++ b/content/docs/references/automation/etl.mdx @@ -10,74 +10,51 @@ ETL (Extract, Transform, Load) Pipeline Protocol - LEVEL 2: Data Engineering Inspired by modern data integration platforms like Airbyte, Fivetran, and Apache NiFi. **Positioning in the sync/integration layering** (L1 "Simple Sync" was - retired in #4738 — narrative-only, zero consumers; see - `packages/spec/docs/SYNC_ARCHITECTURE.md`): - - **ETL Pipeline** (THIS FILE) - Data engineers - Aggregate 10 sources to warehouse - - **Enterprise Connector** (integration/connector.zod.ts) - System integrators - Full SAP integration; connector-attached sync via `syncConfig` ETL pipelines enable automated data synchronization between systems, transforming - data as it moves from source to destination. **SCOPE: Advanced multi-source, multi-stage transformations.** - Supports complex operations: joins, aggregations, filtering, custom SQL. ## When to Use This Layer **Use ETL Pipeline when:** - - Combining data from multiple sources - - Need aggregations, joins, transformations - - Building data warehouses or analytics platforms - - Complex data transformations required **Examples:** - - Sales data from Salesforce + Marketing from HubSpot → Data Warehouse - - Multi-region databases → Consolidated reporting - - Legacy system migration with transformation **When to upgrade:** - - Need full connector lifecycle (auth, webhooks, rate limits) → Use [Enterprise Connector](/docs/references/integration/connector) -See also: [../[integration/connector.zod.ts](/docs/references/integration/connector)](/docs/references/integration/connector) for the Enterprise Connector layer +See also: [../integration/connector.zod.ts](/docs/references/integration/connector) for the Enterprise Connector layer ## Use Cases 1. **Data Warehouse Population** - -- Extract from multiple operational systems - -- Transform to analytical schema - -- Load into data warehouse + - Extract from multiple operational systems + - Transform to analytical schema + - Load into data warehouse 2. **System Integration** - -- Sync data between CRM and Marketing Automation - -- Keep product catalog synchronized across e-commerce platforms - -- Replicate data for backup/disaster recovery + - Sync data between CRM and Marketing Automation + - Keep product catalog synchronized across e-commerce platforms + - Replicate data for backup/disaster recovery 3. **Data Migration** - -- Move data from legacy systems to modern platforms - -- Consolidate data from multiple sources - -- Split monolithic databases into microservices + - Move data from legacy systems to modern platforms + - Consolidate data from multiple sources + - Split monolithic databases into microservices See also: https://airbyte.com/ @@ -86,45 +63,25 @@ See also: https://docs.fivetran.com/ See also: https://nifi.apache.org/ @example - ```typescript - -const salesforceToDB: ETLPipeline = \{ - -name: 'salesforce_to_postgres', - -label: 'Salesforce Accounts to PostgreSQL', - -source: \{ - -type: 'api', - -connector: 'salesforce', - -config: \{ object: 'Account' \} - -\}, - -destination: \{ - -type: 'database', - -connector: 'postgres', - -config: \{ table: 'accounts' \} - -\}, - -transformations: [ - -\{ type: 'map', config: \{ 'Name': 'account_name' \} \} - -], - -schedule: '0 2 * * *' // Daily at 2 AM - -\} - +const salesforceToDB: ETLPipeline = { + name: 'salesforce_to_postgres', + label: 'Salesforce Accounts to PostgreSQL', + source: { + type: 'api', + connector: 'salesforce', + config: { object: 'Account' } + }, + destination: { + type: 'database', + connector: 'postgres', + config: { table: 'accounts' } + }, + transformations: [ + { type: 'map', config: { 'Name': 'account_name' } } + ], + schedule: '0 2 * * *' // Daily at 2 AM +} ``` diff --git a/content/docs/references/automation/execution.mdx b/content/docs/references/automation/execution.mdx index 9ff847d3f9..a5ddf17b05 100644 --- a/content/docs/references/automation/execution.mdx +++ b/content/docs/references/automation/execution.mdx @@ -8,11 +8,9 @@ description: Execution protocol schemas Automation Execution Protocol Defines schemas for execution logging, error tracking, checkpointing, - concurrency control, and scheduled execution persistence. Industry alignment: Salesforce Flow Interviews, Temporal Workflow History, - AWS Step Functions execution logs. diff --git a/content/docs/references/automation/flow-function.mdx b/content/docs/references/automation/flow-function.mdx index 3c72fdcfe7..a85bf0ddc4 100644 --- a/content/docs/references/automation/flow-function.mdx +++ b/content/docs/references/automation/flow-function.mdx @@ -8,75 +8,48 @@ description: Flow Function protocol schemas @module automation/flow-function The contract for a **named handler function a `script` node invokes** — - -contributed by `defineStack(\{ functions \})` and resolved by name at execute - +contributed by `defineStack({ functions })` and resolved by name at execute time (#1870). ## The rule, and why it lives here instead of in a comment A flow function is a PURE compute step: it receives its mapped `input`, - RETURNS a value, and the node's `outputVariable` exposes that value as a flow - -variable so a later DECLARATIVE node persists it (`update_record fields: \{ - -ai_category: '\{aiResult.ai_category\}' \}`). Data I/O stays on the flow graph. +variable so a later DECLARATIVE node persists it (`update_record fields: { +ai_category: '{aiResult.ai_category}' }`). Data I/O stays on the flow graph. That is not style advice. #4354's per-run summary reports what a run did to - the data, and the `script` node reports NO record metrics *because* of this - rule: every write a script causes is a downstream `create_record` / - `update_record` that counts itself, so "this node touched no records" is the - accurate answer rather than a guess. A function that writes anyway makes its - run under-report — `selected: 30, acted: 0` on a run that wrote 30 invoices, - which reads exactly like the broken sweep #4354 exists to detect, and the - durable `sys_automation_run` row says so permanently. Until #4396 that rule lived ONLY in a comment inside the executor, so neither - an author, a lint, nor the runtime could see the contract the summary was - relying on. It is now declared in two halves: -1. `ActionDescriptor.handlerContract` — `script` publishes `'pure'`, so the - -action catalog and the designer palette carry the rule an author reads. - -2. `FlowFunctionEffectSchema` — a function that legitimately writes - -DECLARES it, and its step then reports `unmeasuredEffect`, so the run - -says "cannot count" instead of claiming it wrote nothing. + 1. `ActionDescriptor.handlerContract` — `script` publishes `'pure'`, so the + action catalog and the designer palette carry the rule an author reads. + 2. `FlowFunctionEffectSchema` — a function that legitimately writes + DECLARES it, and its step then reports `unmeasuredEffect`, so the run + says "cannot count" instead of claiming it wrote nothing. ## What is deliberately not here A blanket `unmeasuredEffect` on every `script` step (the escape hatch #4354 - gave `connector_action`) was rejected: it would suppress the broken-sweep - signal on every flow that calls any function, in order to accommodate the - flows that break the rule — paying for a rule-breaker with everyone else's - signal, and fossilizing the violation as supported behaviour. Nor is this *enforcement*. The runtime hands a function no data reach — - `FlowFunctionContext` in `@objectstack/service-automation` carries - `input` / `variables` / `automation` / `logger` and no engine handle — but a - function is ordinary host code and can close over a client at module scope. - What the declaration buys is that the honest case is now *expressible*, and - the platform's own counters stop being wrong for it. diff --git a/content/docs/references/automation/flow.mdx b/content/docs/references/automation/flow.mdx index 47adce029f..840591bc44 100644 --- a/content/docs/references/automation/flow.mdx +++ b/content/docs/references/automation/flow.mdx @@ -8,19 +8,13 @@ description: Flow protocol schemas Flow Node Types — **built-in seed set** (ADR-0018). Historically this `z.enum` *gated* `FlowNodeSchema.type`, which made the - closed protocol reject any plugin-registered node type — defeating the open - runtime registry (`registerNodeExecutor(type: string)`). Per ADR-0018 the - gate is removed: `FlowNodeSchema.type` is now a validated `string`, checked - against the live action registry at `registerFlow()` time, not frozen here. `FlowNodeAction` is **retained** as the canonical list of built-in type ids - (documentation + the seed descriptor set the engine registers at boot). It - no longer constrains authored flows — plugins extend the vocabulary. diff --git a/content/docs/references/automation/io-node-config.mdx b/content/docs/references/automation/io-node-config.mdx index cfef1d6551..213dc5acf3 100644 --- a/content/docs/references/automation/io-node-config.mdx +++ b/content/docs/references/automation/io-node-config.mdx @@ -12,79 +12,48 @@ Config contracts for the flat IO builtins — `notify` and `http` (#4045). ## Provenance — written from the executors, not from the forms Each schema here was derived by reading what the executor actually does with - `node.config` (`service-automation/builtin/notify-node.ts`, `http-nodes.ts`), - **not** by transcribing the hand-written `configSchema` literal on the node's - descriptor. That independence is the point: the two artifacts are reconciled - bidirectionally by `io-node-form-zod-ledger.test.ts` in `service-automation`, - and a Zod copied from the form would make that reconciliation a tautology — - it would pass by construction and prove nothing (#4045). ## What these schemas are wired to (#4277) Like `LoopConfigSchema` / `ParallelConfigSchema` / `TryCatchConfigSchema`, - these are **live execute-time contracts**: each executor `parse()`s its - config against its schema before running (`service-automation`'s - `parse-config.ts`), so type and `required` violations refuse the node as a - guard (not routable via `fault` edges). `notify` parses the RAW stored - -config — its slots are string-typed, so `\{token\}` templates pass and the - +config — its slots are string-typed, so `{token}` templates pass and the post-interpolation guards still own "resolved to nothing". `http` parses - the INTERPOLATED config, because that is the shape its executor reads — - -a `\{token\}` in a typed slot (`timeoutMs`, `durable`) resolves to its real - +a `{token}` in a typed slot (`timeoutMs`, `durable`) resolves to its real type first. ## Unknown keys — closed here too, as of #4001 批 9 These contracts used to say "unknown keys are the registration layer's job": - `registerFlow()` rejects keys the descriptor `configSchema` does not declare - (the tightened #4059 check), and this parse merely stripped them. That is one - door, and the #4001 campaign's second recurring finding is that a schema - which strips by default leaves every OTHER door open — whoever writes the - guard is fixing the bug in front of them, not auditing the surface. The registration check remains the first door a stored flow meets and the - more informative one (it walks NESTED config against the descriptor's JSON - Schema and prints the declared set per path, which a flat key list cannot). - What changes is that a config reaching `parse()` by any OTHER route — a - direct `NotifyConfigSchema.parse()` in tooling, a host that composes the - engine without `registerFlow`, a future executor seam — no longer has its - undeclared keys silently deleted. The two doors are kept in agreement by - `io-node-form-zod-ledger.test.ts`, which reconciles this key set against the - descriptor's in both directions. `connector_action` has no schema here on purpose: its config contract is - empty. The executor reads only the declared `FlowNodeSchema.connectorConfig` - sibling block — see the descriptor note in - `service-automation/builtin/connector-nodes.ts`. diff --git a/content/docs/references/automation/node-executor.mdx b/content/docs/references/automation/node-executor.mdx index 8a40d02db7..2227fc6de4 100644 --- a/content/docs/references/automation/node-executor.mdx +++ b/content/docs/references/automation/node-executor.mdx @@ -10,17 +10,12 @@ description: Node Executor protocol schemas Node Executor Plugin Protocol — Wait Node Pause/Resume Defines the specification for node executor plugins, with a focus on - the `wait` node executor that supports flow pause and external-event - resume (signal, manual, webhook, condition). The protocol covers: - - **WaitResumePayload**: The payload delivered when a paused flow is resumed - - **WaitExecutorConfig**: Configuration for the wait executor plugin - - **NodeExecutorDescriptor**: Generic node executor plugin descriptor diff --git a/content/docs/references/automation/schemaless-node-config.mdx b/content/docs/references/automation/schemaless-node-config.mdx index 357e866ad7..f294928397 100644 --- a/content/docs/references/automation/schemaless-node-config.mdx +++ b/content/docs/references/automation/schemaless-node-config.mdx @@ -8,159 +8,98 @@ description: Schemaless Node Config protocol schemas @module automation/schemaless-node-config Config contracts for the **descriptor-schemaless** builtins whose designer - form lives ONLY in objectui's hand-written `FLOW_NODE_CONFIG` table — - `script`, `subflow` and `decision` (#4278). ## Why these nodes publish no descriptor `configSchema` — and still need this `config-schemas.test.ts` in `service-automation` pins the schemaless class - with each member's reason: `decision`'s virtual Target column is derived from - the out-edges, `subflow` carries a top-level `timeoutMs` — a published - partial schema would DROP those editors (the #4210 `connector_action` - incident). So the Studio form for these types is objectui's hand-written - group, and until #4278 **nothing reconciled that hand-written table against - the executors**: `script`'s form offered an `outputVariables` key nothing - reads, two `actionType` options that fail every run, a no-op default — and - could not author the `function`/`inputs`/`outputVariable` path that works. `script`'s own reason for staying schemaless was that its form switched on - `actionType`. #4343 retired that switch, so the node is now three flat keys - and could graduate to a published descriptor `configSchema` the way `map` - did — a follow-up, deliberately not folded into the retirement. These schemas are the machine-readable half of that reconciliation. They are - **written from the executors** (`service-automation/builtin/screen-nodes.ts` - for `script`, `subflow-node.ts`, `logic-nodes.ts` for `decision`), not from - any form, and objectui's `flow-node-config` reconciliation test compares its - hand-written key sets against them — the same bidirectional ledger the - descriptor-schema'd builtins get from `builtin-node-form-zod-ledger.test.ts`, - carried across the repo seam by the `@objectstack/spec` dependency objectui - already has. `wait` and `connector_action` — the other two schemaless members — need no - entry here: their contracts are the spec-structured sibling blocks on - `FlowNodeSchema` (`waitEventConfig` / `connectorConfig`), which the - same objectui test reconciles directly. ## What these schemas are wired to `script` and `subflow` are **parsed at execute time** since #4343, through - the same `parseNodeConfig()` seam #4277 gave the flat builtins - (`service-automation`'s `parse-config.ts`): a config that fails its contract - refuses the node as a GUARD — wrong metadata, so a rerun cannot help and no - `fault` edge may route it (#3863). `script` could not be parsed while its legal key set depended on - `actionType`; #4343 removed that dependence instead of modelling it. - Converging the node to its one real path — call a registered function — left - a flat three-key contract a flat parse fits exactly, and the five keys the - other branches read became `retiredKey` tombstones. The two halves reach different audiences, which is why they shipped together: -- the **tombstones** teach whoever authors the key — `tsc` types it `never`, - -and a direct parse raises the prescription. They do NOT reach a stored - -flow: `FlowNodeSchema.config` is `z.record(z.unknown())`, so no load-path - -parse ever descends into a node's config; - -- the **execute-time parse** is what a stored flow meets. `registerFlow` - -canonicalizes data at rest through the retired conversion too (#3903), so - -a stored `actionType: 'email'` node arrives here stripped of the keys - -nothing read — and then refuses, naming the `function` it does not have, - -instead of logging a line and reporting success as it used to. + - the **tombstones** teach whoever authors the key — `tsc` types it `never`, + and a direct parse raises the prescription. They do NOT reach a stored + flow: `FlowNodeSchema.config` is `z.record(z.unknown())`, so no load-path + parse ever descends into a node's config; + - the **execute-time parse** is what a stored flow meets. `registerFlow` + canonicalizes data at rest through the retired conversion too (#3903), so + a stored `actionType: 'email'` node arrives here stripped of the keys + nothing read — and then refuses, naming the `function` it does not have, + instead of logging a line and reporting success as it used to. `decision` stays export-only, deliberately: it may carry no `conditions` at - all when it branches purely on edge predicates (a plain BPMN exclusive - gateway), and `conditions` is its only key — so a parse would have nothing - left to check. Its enforcement remains the objectui reconciliation test, - which is what #4278 was actually about (a form authoring keys nothing reads). Undeclared aliases are NOT part of these contracts: `subflow`'s historical - `flow` spelling graduated into the ADR-0087 D2 conversion - `flow-node-subflow-flow-alias` (the `map.flow` path), so the executor only - ever sees `flowName`. ## Unknown keys — closed as of #4001 批 9, and this class had NO other door The descriptor-schema'd builtins have a registration-time key gate: - `registerFlow()` walks each node's `config` against the descriptor's - `configSchema` and hard-rejects what it does not declare (#4277). **These - three node types are exempt from that walk** — by construction, since it - derives the declared set from a `configSchema` they publish none of - (`validateNodeConfigKeys`' schemaless exemption). So until now the entire - `script` / `subflow` / `decision` config surface had exactly zero unknown-key - enforcement at any layer: the execute-time parse #4343 added checks types and - requiredness, and Zod's default `.strip` deleted everything else in silence. That is the #4001 asymmetry in its purest form — a guard was written for the - door in front of its author, and the class it structurally could not cover is - precisely the class with no second door. Closing these shapes is therefore - not a duplicate check for `script` and `subflow`; it is their first one. `decision` is still export-only, so its strictness binds at authoring - (`tsc`), in the published JSON Schema, and in objectui's reconciliation — - not at run time. It is closed anyway, because the campaign's whole finding - is that a shape left open accretes a test, a form and a fixture that assert - the openness, and then closing it is a migration instead of an edit. diff --git a/content/docs/references/automation/state-machine.mdx b/content/docs/references/automation/state-machine.mdx index 35cb6a12c9..4b248424b6 100644 --- a/content/docs/references/automation/state-machine.mdx +++ b/content/docs/references/automation/state-machine.mdx @@ -8,69 +8,42 @@ description: State Machine protocol schemas @module automation/state-machine XState-inspired State Machine Protocol — hierarchical states, guarded - transitions, entry/exit actions. Used to declare strict business-logic - constraints and lifecycle management, so an AI author cannot "hallucinate" a - transition the machine never declared. ## Where this is authored — the question #4001 had to answer first The ledger carried these shapes as `authorable (p)` — provisional, because - nobody had checked. Checking matters here more than usual, because - [ADR-0020](../../../docs/adr/0020-state-machine-converge-and-enforce.md) - **retired this shape as a record-lifecycle declaration**: the top-level - `workflow` metadata type and `object.stateMachines` are both gone, and a - record's legal transitions are declared as a `state_machine` **validation - -rule** (`[data/validation.zod.ts](/docs/references/data/validation)`, a flat `\{ from: [to] \}` table — closed - +rule** (`data/validation.zod.ts`, a flat `{ from: [to] }` table — closed since #4001 batch 3b). A schema whose only doors were those two would be - dead surface, and the campaign's own rule is that dead surface gets its - ledger class corrected, not tightened. -One door survives, and it is an authoring door: **`[ai/agent.zod.ts](/docs/references/ai/agent)`'s - +One door survives, and it is an authoring door: **`ai/agent.zod.ts`'s `lifecycle`** is `StateMachineSchema`, and `agent` is a registered metadata - -type — so `defineStack(\{ agents \})`, `POST /api/v1/meta/types/agent` and the - +type — so `defineStack({ agents })`, `POST /api/v1/meta/types/agent` and the Studio agent form all reach this file through `AgentSchema.parse()`. Verified - by parse, not by reading: before this change, ```ts - -AgentSchema.parse(\{ …, lifecycle: \{ - -id: 'probe_machine', initial: 'draft', stats: \{ runs: 3 \}, - -states: \{ draft: \{ onn: \{ APPROVE: 'done' \}, meta: \{ labell: 'Draft', owner: 'ops' \} \}, - -done: \{ type: 'final' \} \}, - -\} \}) - +AgentSchema.parse({ …, lifecycle: { + id: 'probe_machine', initial: 'draft', stats: { runs: 3 }, + states: { draft: { onn: { APPROVE: 'done' }, meta: { labell: 'Draft', owner: 'ops' } }, + done: { type: 'final' } }, +} }) ``` **succeeded**, returning - -`\{ id, initial, states: \{ draft: \{ type: 'atomic', meta: \{\} \}, done: … \} \}` — - +`{ id, initial, states: { draft: { type: 'atomic', meta: {} }, done: … } }` — `stats` gone, `meta`'s two keys gone, and `onn` (one keystroke from `on`) - gone with every transition the author declared. A state machine whose whole - purpose is to *deny* undeclared transitions had silently become one with no - transitions at all, and reported success. So: `authorable`, and every shape below is `strictObject`. @@ -78,23 +51,14 @@ So: `authorable`, and every shape below is `strictObject`. ## `meta` is closed, deliberately XState treats `meta` as an open bag, so leaving it open was the plausible - call and it was checked rather than assumed (the #4909 precedent: a slot - whose openness is real should say `.passthrough()`, not strip). Three facts - say closed here: the hand-written `StateNodeConfig` type beside this - schema declares exactly four `meta` keys, so `passthrough` would open the - Zod while `tsc` stayed shut — a new declared-≠-enforced split; nothing in - this repo reads any `meta` key (`aiInstructions` has no consumer outside - this file's own test); and the current behaviour is not openness but - -*strip* — the probe above shows an author's `meta` arriving as `\{\}`. There - +*strip* — the probe above shows an author's `meta` arriving as `{}`. There is no openness here to preserve, only a silence to end. diff --git a/content/docs/references/automation/time-relative-trigger.mdx b/content/docs/references/automation/time-relative-trigger.mdx index 7bc41c62c7..48fb2cdb71 100644 --- a/content/docs/references/automation/time-relative-trigger.mdx +++ b/content/docs/references/automation/time-relative-trigger.mdx @@ -8,91 +8,55 @@ description: Time Relative Trigger protocol schemas Time-Relative Trigger Protocol A **declarative** trigger for time-relative business rules — "act on records - whose date field is coming up (or overdue) relative to today" — without the - author hand-writing a cron job + range query, and without the fragile - date-equality-on-record-change anti-pattern (#1874). ## The anti-pattern it replaces Authors used to express "alert 60 days before `end_date`" as a `record_change` - flow gated on `end_date == daysFromNow(60)`. That predicate is only evaluated - when the record *happens to change*, so it fires only if the record is edited - on exactly that day — i.e. almost never, unattended. The robust alternative - was a hand-written `schedule` flow that queries a date range every day, which - every author re-implemented (contracts `renewal_alert`, hr - `document_expiring_soon`, procurement `po_overdue`, …). ## What this declares instead A `time_relative` trigger sweeps an object on a schedule (daily by default) - and launches the flow **once per matching record**, with that record in the - -automation context (so `\{record.\}` interpolation and the start-node - +automation context (so `{record.}` interpolation and the start-node `condition` gate work exactly as they do for record-change flows). The - descriptor is carried on the flow's start node as `config.timeRelative`. @example T-minus renewal reminders (fires on the day a contract is 60/30/7 days out) - ```ts - // flow start node - -config: \{ - -timeRelative: \{ - -object: 'contracts', - -dateField: 'end_date', - -offsetDays: [60, 30, 7], - -filter: \{ status: 'active' \}, - -\}, - -// optional sweep cadence — defaults to daily at 08:00 UTC - -schedule: \{ type: 'cron', expression: '0 8 * * *' \}, - -\} - +config: { + timeRelative: { + object: 'contracts', + dateField: 'end_date', + offsetDays: [60, 30, 7], + filter: { status: 'active' }, + }, + // optional sweep cadence — defaults to daily at 08:00 UTC + schedule: { type: 'cron', expression: '0 8 * * *' }, +} ``` @example "Expiring soon" range (fires every day a document is within 30 days of expiry) - ```ts - -config: \{ - -timeRelative: \{ object: 'hr_document', dateField: 'expires_on', withinDays: 30 \}, - -\} - +config: { + timeRelative: { object: 'hr_document', dateField: 'expires_on', withinDays: 30 }, +} ``` @example Overdue sweep (fires for POs up to 14 days past due) - ```ts - -config: \{ - -timeRelative: \{ object: 'purchase_order', dateField: 'due_date', withinDays: -14, filter: \{ status: 'open' \} \}, - -\} - +config: { + timeRelative: { object: 'purchase_order', dateField: 'due_date', withinDays: -14, filter: { status: 'open' } }, +} ``` diff --git a/content/docs/references/automation/webhook.mdx b/content/docs/references/automation/webhook.mdx index bdd87562a3..4612018d36 100644 --- a/content/docs/references/automation/webhook.mdx +++ b/content/docs/references/automation/webhook.mdx @@ -6,58 +6,35 @@ description: Webhook protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Webhook Trigger Event - When should this webhook fire? These mirror the record events the engine actually emits - (`data.record.created` / `updated` / `deleted`), which the webhook - auto-enqueuer maps to `create` / `update` / `delete`. Only events with a real - producer are declared here — an author can't subscribe to something that - never fires. **Bulk triggers (#4639).** `bulk_update` / `bulk_delete` map to the engine's - aggregate `data.records.updated` / `data.records.deleted`, emitted when a - predicate write (`multi: true` → `IDataDriver.updateMany`/`deleteMany`) - affects a set of rows the driver reports only as a count. They are separate - trigger values, not extra sources for `update` / `delete`, because their - delivery has a different SHAPE: no `recordId`, no record body, just - `object` + `matched`. Folding them into the per-record triggers would send - every existing subscriber a body missing the fields it reads — the same - class of breakage as the pre-#4626 `recordId: ''` fabrication, arriving from - the other direction. A webhook that wants both subscribes to both. Deliberately NOT triggers (#3196): - - `undelete` — there is no soft-delete / restore capability in the engine - -(`delete` is a hard delete; no `deleted_at` convention, no restore - -operation, no `data.record.undeleted` emit), so it had no event source. - -Reintroduce it only alongside a real restore subsystem that emits an - -undelete event. - + (`delete` is a hard delete; no `deleted_at` convention, no restore + operation, no `data.record.undeleted` emit), so it had no event source. + Reintroduce it only alongside a real restore subsystem that emits an + undelete event. - `api` (manual/programmatic fire) — no manual fire path exists (the only - -webhook HTTP surface re-queues already-failed deliveries). Reintroduce it - -with a real "fire this webhook now" endpoint/service, not as a bare enum - -value that silently never fires. + webhook HTTP surface re-queues already-failed deliveries). Reintroduce it + with a real "fire this webhook now" endpoint/service, not as a bare enum + value that silently never fires. **Source:** `packages/spec/src/automation/webhook.zod.ts` diff --git a/content/docs/references/cloud/app-store.mdx b/content/docs/references/cloud/app-store.mdx index 7fe48e831a..c8fb6280f2 100644 --- a/content/docs/references/cloud/app-store.mdx +++ b/content/docs/references/cloud/app-store.mdx @@ -8,33 +8,22 @@ description: App Store protocol schemas # App Store Protocol (Customer Experience) Defines schemas for the end-customer experience when browsing, evaluating, - installing, and managing marketplace apps from within ObjectOS. ## Architecture Alignment - - **Salesforce AppExchange (Customer)**: Browse apps, read reviews, 1-click install - - **Shopify App Store (Merchant)**: App evaluation, trial, install, manage subscriptions - - **Apple App Store (User)**: Ratings, reviews, featured collections, personalized recs ## Customer Journey - ``` - Discover → Evaluate → Install → Configure → Use → Rate/Review → Manage - ``` ## Key Concepts - - **Reviews & Ratings**: User-submitted ratings and reviews with moderation - - **Collections & Recommendations**: Personalized discovery and curated picks - - **Subscription Management**: Manage licenses, billing, and renewals - - **Installed App Management**: Enable, disable, configure, upgrade, uninstall diff --git a/content/docs/references/cloud/developer-portal.mdx b/content/docs/references/cloud/developer-portal.mdx index d9bfc8ee91..4d83d77698 100644 --- a/content/docs/references/cloud/developer-portal.mdx +++ b/content/docs/references/cloud/developer-portal.mdx @@ -8,47 +8,31 @@ description: Developer Portal protocol schemas # Developer Portal Protocol Defines schemas for the developer-facing side of the marketplace ecosystem. - Covers the complete developer journey: ``` - Register → Create App → Develop → Validate → Build → Submit → Monitor → Iterate - ``` ## Architecture Alignment - - **Salesforce Partner Portal**: ISV registration, AppExchange publishing, Trialforce - - **Shopify Partner Dashboard**: App management, analytics, billing - - **VS Code Marketplace Management**: Extension publishing, statistics, tokens ## Identity Integration (better-auth) - Authentication, organization management, and API keys are handled by the - Identity module (`@objectstack/spec` Identity namespace), which follows the - better-auth specification. This module only defines marketplace-specific - extensions on top of the shared identity layer: - **User & Session** → `Identity.UserSchema`, `Identity.SessionSchema` - - **Organization & Members** → `Identity.OrganizationSchema`, `Identity.MemberSchema` - - **API Keys** → `Identity.ApiKeySchema` (with marketplace scopes) ## Key Concepts - - **Publisher Profile**: Links an Identity Organization to a marketplace publisher - - **App Listing Management**: CRUD for marketplace listings (draft → published) - - **Version Channels**: alpha / beta / rc / stable release channels - - **Publishing Analytics**: Install trends, revenue, ratings over time diff --git a/content/docs/references/cloud/environment-artifact.mdx b/content/docs/references/cloud/environment-artifact.mdx index 1980ed85bd..65b3d69dce 100644 --- a/content/docs/references/cloud/environment-artifact.mdx +++ b/content/docs/references/cloud/environment-artifact.mdx @@ -8,21 +8,14 @@ description: Environment Artifact protocol schemas # Environment Artifact Envelope — re-export (#4740, #4535 C10) The envelope has exactly ONE declaration: - `../system/environment-artifact.zod` (maintainer route A′ on #4740 — - `./system` holds the live wire shape, `./cloud` re-exports it). Importing - from `@objectstack/spec/cloud` and `@objectstack/spec/system` yields the - SAME symbols, so the import path can never change the shape a consumer - gets (the #4411 dual-source trap, closed for this name). Do NOT re-declare the envelope here. A second declaration under this name - is exactly what `check:dual-source-exports` and the symbol-identity pin in - `../system/environment-artifact.test.ts` exist to reject. diff --git a/content/docs/references/cloud/environment-package.mdx b/content/docs/references/cloud/environment-package.mdx index 8ac67049ca..7dc40a05f9 100644 --- a/content/docs/references/cloud/environment-package.mdx +++ b/content/docs/references/cloud/environment-package.mdx @@ -8,22 +8,15 @@ description: Environment Package protocol schemas # Environment Package Installation Protocol Models `sys_package_installation` — the pairing between an Environment and - a specific, immutable `sys_package_version` snapshot. Key invariants (per ADR-0003): - - One active version per package per environment at any time - -(UNIQUE `(environment_id, package_id)`). - + (UNIQUE `(environment_id, package_id)`). - **Upgrade** = atomic `UPDATE package_version_id` to a newer version UUID. - - **Rollback** = atomic `UPDATE package_version_id` to an older version UUID. - - Only `status = 'published'` versions may be installed in production - -environments (draft/pre-release allowed in dev/sandbox with `allowDraft`). + environments (draft/pre-release allowed in dev/sandbox with `allowDraft`). Stored in the **Control Plane DB** (not in environment data-plane DBs). diff --git a/content/docs/references/cloud/environment.mdx b/content/docs/references/cloud/environment.mdx index 9ede6109ec..663d2c76d8 100644 --- a/content/docs/references/cloud/environment.mdx +++ b/content/docs/references/cloud/environment.mdx @@ -8,42 +8,27 @@ description: Environment protocol schemas # Environment Protocol (runtime container) An **Environment** is the runtime container of an organization's data. - It owns a physically isolated database, a canonical hostname, a plan/quota - tier, and per-environment RBAC. An organization may own many environments - (dev/test/prod/sandbox/preview/…) — exactly one is marked `is_default`. This file defines the Control-Plane schemas for the `sys_environment`, - `sys_environment_credential` and `sys_environment_member` tables. Business - data lives in each environment's own database — those data-plane DBs hold - no system tables. See ADR-0006 v4: `sys_environment` was renamed to `sys_environment`; the - separate dev-workspace `Project` concept introduced in v3 has been - dropped — user code is now modelled as an implicit `sys_package` with - a per-org `manifest_id` (see ADR-0003), so a single package + version - + installation model serves both Marketplace apps and user projects. Split of concerns: - - **Control Plane**: `sys_environment` (includes physical DB addressing), - -`sys_package_installation` (with `environment_id`), `sys_environment_credential`, - -`sys_environment_member`, `sys_metadata` (with `environment_id`). - + `sys_package_installation` (with `environment_id`), `sys_environment_credential`, + `sys_environment_member`, `sys_metadata` (with `environment_id`). - **Data Plane**: each environment DB contains only business objects - -(account, task, …). No system tables, no `environment_id` columns. + (account, task, …). No system tables, no `environment_id` columns. **Source:** `packages/spec/src/cloud/environment.zod.ts` diff --git a/content/docs/references/cloud/marketplace-admin.mdx b/content/docs/references/cloud/marketplace-admin.mdx index 9c4d7bc39b..06d066e294 100644 --- a/content/docs/references/cloud/marketplace-admin.mdx +++ b/content/docs/references/cloud/marketplace-admin.mdx @@ -8,25 +8,17 @@ description: Marketplace Admin protocol schemas # Marketplace Administration Protocol Defines schemas for the platform (Cloud) side of marketplace operations. - Covers the administrative workflows for managing and governing the marketplace. ## Architecture Alignment - - **Salesforce AppExchange Admin**: Security review, ISV monitoring, partner management - - **Apple App Store Connect Review**: Human review process, guidelines, rejection reasons - - **Google Play Console**: Policy enforcement, quality gates, content moderation ## Key Concepts - - **Review Process**: Structured workflow for submission review (automated + manual) - - **Curation**: Featured apps, curated collections, editorial picks - - **Governance**: Policy enforcement, takedown, compliance - - **Platform Analytics**: Marketplace health, trending, abuse detection diff --git a/content/docs/references/cloud/marketplace.mdx b/content/docs/references/cloud/marketplace.mdx index da0a6073d4..f0a006ea65 100644 --- a/content/docs/references/cloud/marketplace.mdx +++ b/content/docs/references/cloud/marketplace.mdx @@ -8,57 +8,35 @@ description: Marketplace protocol schemas # Marketplace Protocol Defines the core schemas for the plugin marketplace ecosystem, covering: - - **Developer Side**: Package publishing, submission, and version releases - - **Platform Side**: Marketplace listing, review, approval, and discovery This protocol defines the contract between plugin developers, the marketplace - platform, and customers who install plugins. ## Architecture Alignment - - **Salesforce AppExchange**: Security review, managed packages, listing profiles - - **VS Code Marketplace**: Extension publishing, ratings, verified publishers - - **npm Registry**: Package publishing, versioning, scoped packages - - **Shopify App Store**: App review process, billing integration, merchant installs ## Developer Publishing Flow - ``` - 1. Develop → Build the project locally using ObjectStack CLI - 2. Validate → Run `os validate` (schema + security checks) - 3. Build → Run `os build` (compile to dist/objectstack.json) - 4. Submit → Run `os projects bind --artifact dist/objectstack.json` - 5. Review → Platform conducts automated + manual review - 6. Publish → Approved listing goes live on marketplace - ``` ## Platform Management Flow - ``` - 1. Receive → Accept submissions from verified publishers - 2. Scan → Automated security scan and compatibility check - 3. Review → Human review for quality and policy compliance - 4. Catalog → Index in marketplace search catalog - 5. Monitor → Track installs, ratings, issues, and enforce SLAs - ``` diff --git a/content/docs/references/cloud/package-version.mdx b/content/docs/references/cloud/package-version.mdx index 989c8b6583..f8e976d92a 100644 --- a/content/docs/references/cloud/package-version.mdx +++ b/content/docs/references/cloud/package-version.mdx @@ -8,17 +8,13 @@ description: Package Version protocol schemas Package Version Protocol A **package version** is an **immutable** release snapshot of a package. - Once published (`status = 'published'`), its `manifestJson` and `checksum` - fields are frozen — publishing is the act of sealing the snapshot. Lifecycle: - -draft → published → deprecated + draft → published → deprecated Installing a package means pointing a `sys_package_installation` row at a - specific `sys_package_version` UUID. Upgrading swaps that pointer atomically. See `docs/adr/0003-package-as-first-class-citizen.md` for the full rationale. diff --git a/content/docs/references/cloud/package.mdx b/content/docs/references/cloud/package.mdx index e81ac47a3c..0d850429b6 100644 --- a/content/docs/references/cloud/package.mdx +++ b/content/docs/references/cloud/package.mdx @@ -8,19 +8,13 @@ description: Package protocol schemas Package Identity Protocol A **package** (also called a Solution in Power Platform, an Unlocked Package - in Salesforce, or an Application in ServiceNow) is the first-class unit of - distribution in ObjectStack. It groups related metadata — objects, views, - flows, translations, agents — into a named, versioned artifact. Architecture: - - `sys_package` — identity (one row per logical package) - - `sys_package_version` — immutable release snapshots (see package-version.zod.ts) - - `sys_package_installation` — env ↔ version pairing (see environment-package.zod.ts) See `docs/adr/0003-package-as-first-class-citizen.md` for the full rationale. diff --git a/content/docs/references/cloud/tenant.mdx b/content/docs/references/cloud/tenant.mdx index 0525c031a6..c3d4769962 100644 --- a/content/docs/references/cloud/tenant.mdx +++ b/content/docs/references/cloud/tenant.mdx @@ -8,17 +8,12 @@ description: Tenant protocol schemas Multi-Tenant Architecture Schema Defines the schema for managing multi-tenant architecture with: - - Global control plane: Single database for auth, org management, tenant registry - - Tenant data plane: Isolated databases per organization (UUID-based naming) Design decisions: - - Database naming: \{uuid\}.turso.io (not org-slug, since slugs can be modified) - - Each tenant has its own Turso database for complete data isolation - - Global database stores user auth, organizations, and tenant metadata diff --git a/content/docs/references/data/analytics.mdx b/content/docs/references/data/analytics.mdx index c447961502..17a6492658 100644 --- a/content/docs/references/data/analytics.mdx +++ b/content/docs/references/data/analytics.mdx @@ -8,11 +8,9 @@ description: Analytics protocol schemas Analytics/Semantic Layer Protocol Defines the "Business Logic" for data analysis. - Inspired by Cube.dev, LookML, and dbt MetricFlow. This layer decouples the "Physical Data" (Tables/Columns) from the - "Business Data" (Metrics/Dimensions). diff --git a/content/docs/references/data/context-tokens.mdx b/content/docs/references/data/context-tokens.mdx index 31b335ca9c..55de25325b 100644 --- a/content/docs/references/data/context-tokens.mdx +++ b/content/docs/references/data/context-tokens.mdx @@ -6,110 +6,75 @@ description: Context Tokens protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Context Tokens — the declarative placeholders that resolve against the - **caller's session** (who am I, which org am I in) rather than the clock. # Why this lives in `spec` -These are the sibling vocabulary to `\{date-macros\}`. Filter values in - +These are the sibling vocabulary to `{date-macros}`. Filter values in dashboards, views, reports and pages travel as JSON, so a user-scoped - slice cannot call `currentUser().id` inline — it writes a placeholder: -\{ owner_id: '\{current_user_id\}' \} - -[\{ field: 'owner', operator: 'equals', value: '\{current_user_id\}' \}] +``` +{ owner_id: '{current_user_id}' } +[{ field: 'owner', operator: 'equals', value: '{current_user_id}' }] +``` Like date macros, the placeholders are expanded on **both** sides of - the wire (framework#3582): `resolveContextTokens()` in - `@object-ui/core` before the filter leaves the browser, and - `resolveFilterTokens()` in `@objectstack/core` on the ObjectQL read - AND write paths and the analytics dataset executor, for filters that - reach the database without passing through a renderer. The DRIVER - -only ever sees concrete ids, never `\{tokens\}`. +only ever sees concrete ids, never `{tokens}`. The write verbs matter as much as the read ones (#3810): a filter has - to select the same rows whether `find`, `update` or `delete` consumes - it, or a flow that previews with one and acts with the other operates - on two different row sets. -The server resolver reads `ExecutionContext` — `\{current_user_id\}` is - -`userId`, `\{current_org_id\}` is `tenantId`. A request that carries - +The server resolver reads `ExecutionContext` — `{current_user_id}` is +`userId`, `{current_org_id}` is `tenantId`. A request that carries neither is an ERROR, not a null comparand: resolving to `null` - degrades to `IS NULL` on most drivers and would hand back the rows - the filter was written to exclude. # Presentation scope, NOT a security boundary This is the single most important thing to understand about these - -tokens. `\{current_user_id\}` scopes what a surface *shows*; it does not - +tokens. `{current_user_id}` scopes what a surface *shows*; it does not decide what a caller is *allowed* to read. Enforcement is RLS, which - uses a different and genuinely server-side vocabulary rooted at - `current_user` (`owner_id = current_user.id`, compiled by - `@objectstack/plugin-security`'s RLS compiler). The two look alike and are easy to confuse, so keep them straight: -| | `\{current_user_id\}` | `current_user.id` | - +| | `{current_user_id}` | `current_user.id` | |---|---|---| - | Where | filter values (JSON) | RLS `using` expressions | - | Resolved | client-side, before the query | server-side, during the query | - | Purpose | presentation scope | access enforcement | - | Bypassable | yes — it's just a filter | no | Never reach for a context token to keep a user away from data. Removing - -a `\{current_user_id\}` filter widens a *view*; it must never widen - +a `{current_user_id}` filter widens a *view*; it must never widen *access*. # Where the tokens are honoured Filter values on every surface that resolves placeholders — object list - views, dashboard widgets, reports, SDUI page components. Navigation - (`recordId` / `params`) additionally resolves `AppContextSelector` ids - -such as `\{active_package\}`; those are nav-only and are NOT valid inside - +such as `{active_package}`; those are nav-only and are NOT valid inside filter values, because filters are not evaluated with the sidebar's - selector state. # Out of scope - `current_user.*` RLS expressions — see `@objectstack/plugin-security`. - -- `\{date-macros\}` — the clock-based sibling; see `./date-macros.zod.ts`. - -- `titleFormat` field interpolation (`\{user_id\}` etc.) — that substitutes - -*record fields*, an unrelated mechanism that happens to share braces. +- `{date-macros}` — the clock-based sibling; see `./date-macros.zod.ts`. +- `titleFormat` field interpolation (`{user_id}` etc.) — that substitutes + *record fields*, an unrelated mechanism that happens to share braces. **Source:** `packages/spec/src/data/context-tokens.zod.ts` diff --git a/content/docs/references/data/data-engine.mdx b/content/docs/references/data/data-engine.mdx index 8b1706a18e..5b36e14c68 100644 --- a/content/docs/references/data/data-engine.mdx +++ b/content/docs/references/data/data-engine.mdx @@ -8,11 +8,8 @@ description: Data Engine protocol schemas Data Engine Protocol Defines the standard interface for data persistence engines in ObjectStack. - This protocol abstracts the underlying storage mechanism (SQL, NoSQL, API, Memory), - allowing the ObjectQL engine to execute standardized CRUD and Aggregation operations - regardless of where the data resides. The Data Engine acts as the "Driver" layer in the Hexagonal Architecture. diff --git a/content/docs/references/data/datasource.mdx b/content/docs/references/data/datasource.mdx index a46965385d..7b697265c5 100644 --- a/content/docs/references/data/datasource.mdx +++ b/content/docs/references/data/datasource.mdx @@ -6,7 +6,6 @@ description: Datasource protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Driver Identifier - Can be a built-in driver or a plugin-contributed driver (e.g., "com.vendor.snowflake"). diff --git a/content/docs/references/data/date-macros.mdx b/content/docs/references/data/date-macros.mdx index e0ab02d15f..0799e0f1fc 100644 --- a/content/docs/references/data/date-macros.mdx +++ b/content/docs/references/data/date-macros.mdx @@ -6,114 +6,75 @@ description: Date Macros protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Date Macro Tokens — the declarative placeholders the UI substitutes - into filter values before sending a query to the data engine. # Why this lives in `spec` Filter values in dashboards, views, reports and pages travel as JSON. - Because JSON cannot evaluate code, callers cannot write `daysAgo(30)` - inline; they use a tiny placeholder grammar instead: -\{ published_at: \{ $gte: '\{last_quarter_start\}' \} \} - -\{ signal_at: \{ $gte: '\{30_days_ago\}' \} \} +``` +{ published_at: { $gte: '{last_quarter_start}' } } +{ signal_at: { $gte: '{30_days_ago}' } } +``` The placeholders are expanded on **both** sides of the wire, so a - filter behaves the same wherever it is executed (framework#3582): - **Client** — `resolveDateMacros()` in `@object-ui/core`, just - -before the filter is handed to the data source. - + before the filter is handed to the data source. - **Server** — `resolveFilterTokens()` in `@objectstack/core`, wired - -into the ObjectQL read AND write paths (`find`/`findOne`/`count`/ - -`aggregate`/`update`/`delete`) and the analytics dataset executor. - -Filters that reach the database WITHOUT passing through a renderer — - -dashboard widgets, dataset definitions, REST query params, flow node - -filters — need this: before it, the token compared as a literal - -string and matched nothing. The write verbs are covered for the same - -reason (#3810): one filter must select one row set regardless of - -which verb consumes it, or a flow's `find` preview and its - -`update` act on different rows. + into the ObjectQL read AND write paths (`find`/`findOne`/`count`/ + `aggregate`/`update`/`delete`) and the analytics dataset executor. + Filters that reach the database WITHOUT passing through a renderer — + dashboard widgets, dataset definitions, REST query params, flow node + filters — need this: before it, the token compared as a literal + string and matched nothing. The write verbs are covered for the same + reason (#3810): one filter must select one row set regardless of + which verb consumes it, or a flow's `find` preview and its + `update` act on different rows. Either way the DRIVER only ever sees ISO date / timestamp strings, - -never `\{tokens\}`. Translating an ISO comparand into a column's on-disk - +never `{tokens}`. Translating an ISO comparand into a column's on-disk form — canonical UTC text on SQLite, a native `timestamptz` on - Postgres, a `DATETIME(3)` literal on MySQL, and `YYYY-MM-DD` text for - a calendar day on every dialect — is the driver's job; see - `SqlDriver.temporalFilterValue`. A token OUTSIDE this vocabulary is rejected rather than passed - through: `@objectstack/lint`'s `validate-filter-tokens` fails the - build, and the runtime resolver throws. Silently matching nothing is - the failure mode the vocabulary exists to prevent. AI agents and template authors author these placeholders directly, - so the **set of recognised tokens is part of the platform contract** - and must live here next to the rest of the JSON-DSL schemas, not - inside any single UI implementation. # Two flavours of token -1. **Fixed tokens** — small, finite list (`\{today\}`, - -`\{current_quarter_start\}`, `\{last_year_end\}`, …). Enumerated by - -`DATE_MACRO_TOKENS` below. - -2. **Parameterised tokens** — `\{N_days_ago\}`, `\{N_weeks_from_now\}`, - -etc., where `N` is any non-negative integer. Matched by +1. **Fixed tokens** — small, finite list (`{today}`, + `{current_quarter_start}`, `{last_year_end}`, …). Enumerated by + `DATE_MACRO_TOKENS` below. -`DATE_MACRO_PARAM_RE`. Units: `minute(s)`, `hour(s)`, `day(s)`, - -`week(s)`, `month(s)`, `year(s)`. Directions: `ago`, `from_now`. +2. **Parameterised tokens** — `{N_days_ago}`, `{N_weeks_from_now}`, + etc., where `N` is any non-negative integer. Matched by + `DATE_MACRO_PARAM_RE`. Units: `minute(s)`, `hour(s)`, `day(s)`, + `week(s)`, `month(s)`, `year(s)`. Directions: `ago`, `from_now`. # Out of scope - CEL expressions (`cel\`daysAgo(30)\``) run **server-side** in the - -formula engine. They are unrelated to these placeholders; see - -`@objectstack/formula`. - + formula engine. They are unrelated to these placeholders; see + `@objectstack/formula`. - Token resolution semantics (week-start day, timezone, fiscal - -calendars) are defined by the resolver implementation; spec only - -freezes the **vocabulary**. One property is worth stating here - -because it is authored against: a `*_end` token is the period's - -last calendar DAY (`\{current_year_end\}` → `2026-12-31`), so on a - -`datetime` column `<= \{current_year_end\}` stops at midnight on the - -31st. Filter a timestamp with the half-open `< \{next_year_start\}`. + calendars) are defined by the resolver implementation; spec only + freezes the **vocabulary**. One property is worth stating here + because it is authored against: a `*_end` token is the period's + last calendar DAY (`{current_year_end}` → `2026-12-31`), so on a + `datetime` column `<= {current_year_end}` stops at midnight on the + 31st. Filter a timestamp with the half-open `< {next_year_start}`. **Source:** `packages/spec/src/data/date-macros.zod.ts` diff --git a/content/docs/references/data/document.mdx b/content/docs/references/data/document.mdx index 7b9d6e56a6..d78f2561c2 100644 --- a/content/docs/references/data/document.mdx +++ b/content/docs/references/data/document.mdx @@ -8,31 +8,19 @@ description: Document protocol schemas Document Version Schema Represents a single version of a document in a version-controlled system. - Each version is immutable and maintains its own metadata and download URL. @example - ```json - -\{ - -"versionNumber": 2, - -"createdAt": 1704067200000, - -"createdBy": "user_123", - -"size": 2048576, - -"checksum": "a1b2c3d4e5f6", - -"downloadUrl": "https://storage.example.com/docs/v2/file.pdf", - -"isLatest": true - -\} - +{ + "versionNumber": 2, + "createdAt": 1704067200000, + "createdBy": "user_123", + "size": 2048576, + "checksum": "a1b2c3d4e5f6", + "downloadUrl": "https://storage.example.com/docs/v2/file.pdf", + "isLatest": true +} ``` diff --git a/content/docs/references/data/driver-common.mdx b/content/docs/references/data/driver-common.mdx index fd62c6d23f..209cc61ec1 100644 --- a/content/docs/references/data/driver-common.mdx +++ b/content/docs/references/data/driver-common.mdx @@ -8,25 +8,16 @@ description: Driver Common protocol schemas Shared building blocks for the per-driver `datasource.config` shapes (#4410). Every schema under `data/driver/` describes ONE driver's `config` slot — the - keys an author may write and the platform actually reads. They are the - enforcement half of the `config` escape hatch `datasource.zod.ts` opens: the - slot stays `z.record` at the top of `DatasourceSchema` because a sqlite - `filename` and a postgres `host` share no shape, and `DatasourceSchema`'s - refinement then parses it against the schema for the declared driver. The rule these files are written to: **a key is declared here only if some - code path reads it.** A config key that no driver and no factory consumes is - the same silent-strip defect one level down (#4001, ADR-0078), so an unread - key is either wired or rejected with a prescription — never left in the - contract to look supported. diff --git a/content/docs/references/data/driver-memory.mdx b/content/docs/references/data/driver-memory.mdx index 659fe5db67..e6f250807a 100644 --- a/content/docs/references/data/driver-memory.mdx +++ b/content/docs/references/data/driver-memory.mdx @@ -8,21 +8,14 @@ description: Driver Memory protocol schemas Memory Driver Configuration Schema Defines the configuration options for the in-memory driver. - Reference: objectql/packages/drivers/memory (Mingo-powered production-ready driver) The memory driver is ideal for: - - Unit testing (no database setup required) - - Development & prototyping - - Edge/Worker environments (Cloudflare Workers, Deno Deploy) - - Client-side state management - - Temporary data caching - - CI/CD pipelines diff --git a/content/docs/references/data/driver-mongo.mdx b/content/docs/references/data/driver-mongo.mdx index e5edbd3a6c..9da1944082 100644 --- a/content/docs/references/data/driver-mongo.mdx +++ b/content/docs/references/data/driver-mongo.mdx @@ -10,17 +10,11 @@ MongoDB Standard Driver Protocol Describes the MongoDB connection settings and capabilities. ENFORCED as of #4410. This block used to claim it was "used by the Platform - to validate `datasource.config` when `driver: 'mongo'`", which was false: the - config slot was a bare `z.record` and this schema had no consumer at all — - not even an export, since `data/driver/` was reachable only from its own - tests. It is now what `DatasourceSchema` parses `config` against for a mongo - datasource, and the same schema is projected onto - `MongoDriverSpec`.configSchema for the connection form. diff --git a/content/docs/references/data/driver-nosql.mdx b/content/docs/references/data/driver-nosql.mdx index 5760f0a603..ee0c69cccb 100644 --- a/content/docs/references/data/driver-nosql.mdx +++ b/content/docs/references/data/driver-nosql.mdx @@ -6,7 +6,6 @@ description: Driver Nosql protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} NoSQL Database Type Enumeration - Supported NoSQL database types diff --git a/content/docs/references/data/driver-sql.mdx b/content/docs/references/data/driver-sql.mdx index 65547ac09c..d6c488951a 100644 --- a/content/docs/references/data/driver-sql.mdx +++ b/content/docs/references/data/driver-sql.mdx @@ -6,7 +6,6 @@ description: Driver Sql protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} SQL Dialect Enumeration - Supported SQL database dialects diff --git a/content/docs/references/data/driver.mdx b/content/docs/references/data/driver.mdx index 9b88c26b23..97102538dc 100644 --- a/content/docs/references/data/driver.mdx +++ b/content/docs/references/data/driver.mdx @@ -6,7 +6,6 @@ description: Driver protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Common Driver Options - Passed to most driver methods to control behavior (transactions, timeouts, etc.) diff --git a/content/docs/references/data/external-catalog.mdx b/content/docs/references/data/external-catalog.mdx index 3be0c03cce..72017be763 100644 --- a/content/docs/references/data/external-catalog.mdx +++ b/content/docs/references/data/external-catalog.mdx @@ -6,17 +6,12 @@ description: External Catalog protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} ExternalCatalog — cached remote-schema snapshot for a federated datasource - (ADR-0015 §4.3). Introspecting a mature warehouse on every boot is expensive, so the - `IExternalDatasourceService.refreshCatalog` persists a snapshot of the - remote tables/columns as an `external_catalog` metadata record. The - boot-validation gate (Gate 2) and Studio's schema browser read from it; - drift is detected by diffing a fresh introspection against the snapshot. diff --git a/content/docs/references/data/external-lookup.mdx b/content/docs/references/data/external-lookup.mdx index 60bdca78b0..e875d4dd80 100644 --- a/content/docs/references/data/external-lookup.mdx +++ b/content/docs/references/data/external-lookup.mdx @@ -8,41 +8,24 @@ description: External Lookup protocol schemas External Data Source Schema Configuration for connecting to external data systems. - Similar to Salesforce External Objects for real-time data integration. @example - ```json - -\{ - -"id": "salesforce-accounts", - -"name": "Salesforce Account Data", - -"type": "rest-api", - -"endpoint": "https://api.salesforce.com/services/data/v58.0", - -"authentication": \{ - -"type": "oauth2", - -"config": \{ - -"clientId": "...", - -"clientSecret": "...", - -"tokenUrl": "https://login.salesforce.com/services/oauth2/token" - -\} - -\} - -\} - +{ + "id": "salesforce-accounts", + "name": "Salesforce Account Data", + "type": "rest-api", + "endpoint": "https://api.salesforce.com/services/data/v58.0", + "authentication": { + "type": "oauth2", + "config": { + "clientId": "...", + "clientSecret": "...", + "tokenUrl": "https://login.salesforce.com/services/oauth2/token" + } + } +} ``` diff --git a/content/docs/references/data/feed.mdx b/content/docs/references/data/feed.mdx index 43161ef518..1f31c924d8 100644 --- a/content/docs/references/data/feed.mdx +++ b/content/docs/references/data/feed.mdx @@ -8,13 +8,9 @@ description: Feed protocol schemas Activity-timeline UI config enums. The `service-feed` backend was retired (ADR-0052 §5 / #1955); `sys_comment` / - `sys_activity` are the canonical record-collaboration/timeline backend. Only these - two enums remain here — they are pure UI configuration for the record activity - -component (`RecordActivityProps` in `../[ui/component.zod.ts](/docs/references/ui/component)`), with no backend - +component (`RecordActivityProps` in `../ui/component.zod.ts`), with no backend dependency. (A later `feed` → `activity` rename is tracked separately.) diff --git a/content/docs/references/data/field-value.mdx b/content/docs/references/data/field-value.mdx index 7f412f320b..233e9c63bc 100644 --- a/content/docs/references/data/field-value.mdx +++ b/content/docs/references/data/field-value.mdx @@ -8,47 +8,29 @@ description: Field Value protocol schemas Field runtime VALUE-shape contract (ADR-0104 D1). `FieldSchema` owns what a field *definition* looks like; this module owns - what a field's runtime *value* looks like — the shape the write path - accepts, drivers persist, and an unexpanded API read returns. Before this - module the knowledge lived as private, hand-copied type sets in objectql's - record-validator, rest's import-coerce, driver-sql, and verify; adding one - multi-capable or JSON-shaped type meant updating four lists or silently - corrupting data. Those consumers now derive from the classes below. Two canonical forms exist per field (ADR-0104 D1): - -- `stored` — the storage/wire form (e.g. lookup ⇒ record-id string, - -`date` ⇒ `YYYY-MM-DD`, select ⇒ option code). - -- `expanded` — the enriched `$expand` read form (lookup ⇒ the related - -record object). For types without an expansion, - -expanded ≡ stored. + - `stored` — the storage/wire form (e.g. lookup ⇒ record-id string, + `date` ⇒ `YYYY-MM-DD`, select ⇒ option code). + - `expanded` — the enriched `$expand` read form (lookup ⇒ the related + record object). For types without an expansion, + expanded ≡ stored. "Reality wins": where the deployed stored shape is coherent, the contract - adopts it — deployed data is a wire contract we don't get to rewrite by - editing Zod. This is why `currency` is a bare number (not the never-consumed - -`CurrencyValueSchema` object) and `location` is `\{lat, lng\}` (what field-zoo - -stores), not the never-consumed `\{latitude, longitude\}` shape. +`CurrencyValueSchema` object) and `location` is `{lat, lng}` (what field-zoo +stores), not the never-consumed `{latitude, longitude}` shape. Purity: schemas/constants/derivation only — no runtime logic, no caching - (Prime Directive #2). Consumers cache `valueSchemaFor` results per field - definition; building a Zod schema per write is the one performance trap - this contract has (ADR-0104 performance budget). diff --git a/content/docs/references/data/filter.mdx b/content/docs/references/data/filter.mdx index 03fe6cd302..b30acdd524 100644 --- a/content/docs/references/data/filter.mdx +++ b/content/docs/references/data/filter.mdx @@ -8,31 +8,21 @@ description: Filter protocol schemas Unified Query DSL Specification Based on industry best practices from: - - Prisma ORM - - Strapi CMS - - TypeORM - - LoopBack Framework Version: 1.0.0 - Status: Draft Objective: Define a JSON-based, database-agnostic query syntax standard - for data filtering interactions between frontend and backend APIs. Design Principles: - 1. Declarative: Frontend describes "what data to get", not "how to query" - 2. Database Agnostic: Syntax contains no database-specific directives - 3. Type Safe: Structure can be statically inferred by TypeScript - 4. Convention over Configuration: Implicit syntax for common queries diff --git a/content/docs/references/data/hook.mdx b/content/docs/references/data/hook.mdx index 8c51a66ab9..aa4789829c 100644 --- a/content/docs/references/data/hook.mdx +++ b/content/docs/references/data/hook.mdx @@ -6,7 +6,6 @@ description: Hook protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Hook Lifecycle Events - Defines the interception points in the ObjectQL execution pipeline. diff --git a/content/docs/references/data/query.mdx b/content/docs/references/data/query.mdx index 4604f30937..e4504f147e 100644 --- a/content/docs/references/data/query.mdx +++ b/content/docs/references/data/query.mdx @@ -6,9 +6,7 @@ description: Query protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Sort Node - -Represents "Order By" — one `\{ field, order \}` pair. Unknown keys are - +Represents "Order By" — one `{ field, order }` pair. Unknown keys are REJECTED (#4721); spell the direction `order`, never `direction`. diff --git a/content/docs/references/data/seed-loader.mdx b/content/docs/references/data/seed-loader.mdx index 02e535d36a..0189cf7d33 100644 --- a/content/docs/references/data/seed-loader.mdx +++ b/content/docs/references/data/seed-loader.mdx @@ -8,33 +8,21 @@ description: Seed Loader protocol schemas # Seed Loader Protocol Defines the schemas for metadata-driven seed data loading with automatic - relationship resolution, dependency ordering, and multi-pass insertion. ## Architecture Alignment - - **Salesforce Data Loader**: External ID-based upsert with relationship resolution - - **ServiceNow**: Sys ID and display value mapping during import - - **Airtable**: Linked record resolution via display names ## Loading Flow - ``` - 1. Build object dependency graph from field metadata (lookup/master_detail) - 2. Topological sort → determine insert order (parents before children) - 3. Pass 1: Insert/upsert records, resolve references via externalId - 4. Pass 2: Fill deferred references (circular/delayed dependencies) - 5. Validate & report unresolved references - 6. Return structured result with per-object stats - ``` diff --git a/content/docs/references/data/seed.mdx b/content/docs/references/data/seed.mdx index ca481c3410..fcb75eeba1 100644 --- a/content/docs/references/data/seed.mdx +++ b/content/docs/references/data/seed.mdx @@ -6,7 +6,6 @@ description: Seed protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Seed Import Strategy - Defines how the engine handles existing records when a seed is applied. diff --git a/content/docs/references/data/validation.mdx b/content/docs/references/data/validation.mdx index feae326b0e..1a499d4e15 100644 --- a/content/docs/references/data/validation.mdx +++ b/content/docs/references/data/validation.mdx @@ -8,115 +8,71 @@ description: Validation protocol schemas # ObjectStack Validation Protocol This module defines the validation schema protocol for ObjectStack, providing a comprehensive - type-safe validation system similar to Salesforce's validation rules but with enhanced capabilities. ## Overview Validation rules are applied at the data layer to ensure data integrity and enforce business logic. - A validation rule is a **deterministic, synchronous, side-effect-free predicate over a single - record** — it must be decidable from the incoming write (and, on update, the prior record) with - no I/O. Everything advertised here runs on the write path (see - `objectql/src/validation/rule-validator.ts`) — insert, single-id update, and multi-row - (`multi: true`) update, where the evaluator runs once per matched row (#3106); nothing is a - silent no-op. The `events` enum admits only `insert`/`update` for this reason — see the - `delete` note under "Deliberately NOT validation rules" below. The system supports these validation types: 1. **Script Validation**: Formula-based validation using a CEL predicate - 2. **State Machine Validation**: Control allowed state transitions - 3. **Format Validation**: Validate a field's value (email, URL, phone, JSON, regex) - 4. **Cross-Field Validation**: Validate relationships between multiple fields - 5. **JSON Schema Validation**: Validate a JSON field against a JSON Schema - 6. **Conditional Validation**: Apply a nested rule based on a CEL condition ## Deliberately NOT validation rules These were once declared here but never enforced. Because the contract above rules them out - (they need I/O or are client-side concerns), they were removed rather than left as silent - no-ops. Use the layer that already does each one correctly: - **Uniqueness** → a unique **index** whose scope is stated (`ObjectSchema.indexes`, with - -`unique: 'organization'` for one holder per organization or `unique: 'global'` for one - -across the whole installation — ADR-0120; `partial` for a scoped/conditional constraint), - -or field-level `unique`. A SELECT-then-INSERT "rule" is inherently racy (TOCTOU); a DB - -unique constraint is not. - + `unique: 'organization'` for one holder per organization or `unique: 'global'` for one + across the whole installation — ADR-0120; `partial` for a scoped/conditional constraint), + or field-level `unique`. A SELECT-then-INSERT "rule" is inherently racy (TOCTOU); a DB + unique constraint is not. - **Async / remote validation** → a client-form concern (`debounce`/`validatorUrl` only mean - -anything against keystrokes) and an SSRF/latency hazard on the server write path. Keep it in - -the form layer, or enforce the underlying invariant with a `unique` index / lifecycle hook. - + anything against keystrokes) and an SSRF/latency hazard on the server write path. Keep it in + the form layer, or enforce the underlying invariant with a `unique` index / lifecycle hook. - **Custom handler** → a `beforeInsert` / `beforeUpdate` lifecycle hook, the typed, supported - -extension point for arbitrary validation code. - + extension point for arbitrary validation code. - **Delete-time guards** (`events: ['delete']`) → a `beforeDelete` lifecycle hook. The evaluator - -only runs on the insert/update write path (a delete carries no record payload to validate), so - -a `delete` event was a proven silent no-op — the enum value was removed rather than left - -advertised-but-unenforced (#3184; see docs/audits/2026-06-validationschema-property-liveness.md). + only runs on the insert/update write path (a delete carries no record payload to validate), so + a `delete` event was a proven silent no-op — the enum value was removed rather than left + advertised-but-unenforced (#3184; see docs/audits/2026-06-validationschema-property-liveness.md). ## Salesforce Comparison ObjectStack validation rules are inspired by Salesforce validation rules but enhanced: - - Salesforce: Formula-based validation with `Error Condition Formula` - - ObjectStack: Multiple validation types with composable rules Example Salesforce validation rule: - ``` - Rule Name: Discount_Cannot_Exceed_40_Percent - Error Condition Formula: Discount_Percent__c > 0.40 - Error Message: Discount cannot exceed 40%. - ``` Equivalent ObjectStack rule: - ```typescript - -\{ - -type: 'script', - -name: 'discount_cannot_exceed_40_percent', - -condition: 'discount_percent > 0.40', - -message: 'Discount cannot exceed 40%', - -severity: 'error' - -\} - +{ + type: 'script', + name: 'discount_cannot_exceed_40_percent', + condition: 'discount_percent > 0.40', + message: 'Discount cannot exceed 40%', + severity: 'error' +} ``` diff --git a/content/docs/references/identity/eval-user.mdx b/content/docs/references/identity/eval-user.mdx index 1efde14608..49dd9b6661 100644 --- a/content/docs/references/identity/eval-user.mdx +++ b/content/docs/references/identity/eval-user.mdx @@ -8,21 +8,14 @@ description: Eval User protocol schemas EvalUser — the one user-context contract (ADR-0068 D1). The signed-in user exposed to every predicate surface (server formula, server - RLS, client UI gates) under the canonical variable name `current_user` - (aliases `user`, `ctx.user`) with an **identical shape**. A predicate such as - `current_user.positions.exists(p, p == 'org_admin')` (or - `'org_admin' in current_user.positions`) therefore evaluates identically wherever - it is written. `positions: string[]` is the **only canonical** membership field (renamed from - `roles`, ADR-0090 D3). A singular field is NOT part of this contract — its legacy "overwritten to 'admin' on promotion" - behavior is the footgun this eliminates. See also: docs/adr/0068-unified-user-context-and-built-in-identity-roles.md diff --git a/content/docs/references/identity/identity.mdx b/content/docs/references/identity/identity.mdx index 989338fe24..7340c50ee9 100644 --- a/content/docs/references/identity/identity.mdx +++ b/content/docs/references/identity/identity.mdx @@ -8,11 +8,9 @@ description: Identity protocol schemas Identity & User Model Specification Defines the standard user, account, and session data models for ObjectStack. - These schemas represent "who is logged in" and their associated data. This is separate from authentication configuration (auth.zod.ts) which - defines "how to login". diff --git a/content/docs/references/identity/organization.mdx b/content/docs/references/identity/organization.mdx index 8226df8ab7..14ab8ffb93 100644 --- a/content/docs/references/identity/organization.mdx +++ b/content/docs/references/identity/organization.mdx @@ -8,7 +8,6 @@ description: Organization protocol schemas Organization Schema (Multi-Tenant Architecture) Defines the standard organization/workspace model for ObjectStack. - Supports B2B SaaS scenarios where users belong to multiple teams/workspaces. This aligns with better-auth's organization plugin capabilities. diff --git a/content/docs/references/identity/position.mdx b/content/docs/references/identity/position.mdx index 75ad7faf4c..173fc671be 100644 --- a/content/docs/references/identity/position.mdx +++ b/content/docs/references/identity/position.mdx @@ -8,55 +8,35 @@ description: Position protocol schemas Position Schema — the flat capability-distribution group (ADR-0090 D3). A position (岗位, "job role" in NetSuite/Workday terms) is a **named, - assignable bundle of permission sets**: users hold positions - (`sys_user_position`), positions bind permission sets - (`sys_position_permission_set`), and a user's capability is the union of - every set reached that way plus direct grants. Positions are deliberately **flat** — no `parent`, no hierarchy. The - visibility hierarchy lives on the business-unit tree (`sys_business_unit`, - ADR-0057 D2) and the manager chain (`sys_user.manager_id`); re-adding a - second tree here is the mistake ADR-0057 D5 retired and ADR-0090 D3 - finalizes. VOCABULARY (ADR-0090 D3): the word "role" is reserved-forbidden across the - platform — capability = permission_set, distribution = position, - hierarchy = business_unit. The sole exception is better-auth's internal - `sys_member.role` (org-membership tier), projected as - `org_membership_level`. **NAMING CONVENTION:** - Position names MUST be lowercase snake_case to prevent security issues. @example Good position names - - 'sales_manager' - - 'ceo' - - 'region_east_vp' - - 'engineering_lead' @example Bad position names (will be rejected) - - 'SalesManager' (camelCase) - - 'CEO' (uppercase) - - 'Region East VP' (spaces and uppercase) diff --git a/content/docs/references/identity/scim.mdx b/content/docs/references/identity/scim.mdx index bd7689499a..3ad4a416de 100644 --- a/content/docs/references/identity/scim.mdx +++ b/content/docs/references/identity/scim.mdx @@ -8,67 +8,47 @@ description: Scim protocol schemas # SCIM 2.0 Protocol Implementation System for Cross-domain Identity Management (SCIM) 2.0 specification - implementation for ObjectStack. ## Overview SCIM 2.0 is an HTTP-based protocol for managing user and group identities - across domains. It provides a standardized REST API for user provisioning, - de-provisioning, and synchronization. ## Use Cases 1. **Enterprise SSO Integration** - -- Integrate with Okta, Azure AD, OneLogin - -- Automatic user provisioning from corporate directory - -- Just-in-Time (JIT) user creation on first login + - Integrate with Okta, Azure AD, OneLogin + - Automatic user provisioning from corporate directory + - Just-in-Time (JIT) user creation on first login 2. **User Lifecycle Management** - -- Automatically create users when they join organization - -- Update user attributes when they change roles - -- Deactivate users when they leave organization + - Automatically create users when they join organization + - Update user attributes when they change roles + - Deactivate users when they leave organization 3. **Group/Department Synchronization** - -- Sync organizational structure from AD/LDAP - -- Maintain group memberships automatically - -- Map corporate roles to application permissions + - Sync organizational structure from AD/LDAP + - Maintain group memberships automatically + - Map corporate roles to application permissions 4. **Compliance & Audit** - -- Maintain accurate user directory - -- Track all identity changes - -- Meet SOX/HIPAA requirements for user management + - Maintain accurate user directory + - Track all identity changes + - Meet SOX/HIPAA requirements for user management ## Specification References - **RFC 7643**: SCIM Core Schema - - **RFC 7644**: SCIM Protocol - - **RFC 7642**: SCIM Requirements ## Industry Implementations - **Okta**: Leading SCIM provider - - **Azure AD**: Microsoft's identity platform - - **OneLogin**: Enterprise SSO provider - - **Google Workspace**: Google's identity management See also: https://datatracker.ietf.org/doc/html/rfc7643 diff --git a/content/docs/references/integration/connector.mdx b/content/docs/references/integration/connector.mdx index 22c32c1b7f..665856b7f7 100644 --- a/content/docs/references/integration/connector.mdx +++ b/content/docs/references/integration/connector.mdx @@ -8,29 +8,20 @@ description: Connector protocol schemas Connector Protocol - LEVEL 3: Enterprise Connector Defines the standard connector specification for external system integration. - Connectors enable ObjectStack to sync data with SaaS apps, databases, file storage, - and message queues through a unified protocol. **Positioning in the sync/integration layering** (L1 "Simple Sync" was - retired in #4738 — narrative-only, zero consumers; see - `packages/spec/docs/SYNC_ARCHITECTURE.md`): - - **ETL Pipeline** (automation/etl.zod.ts) - Data engineers - Aggregate 10 sources to warehouse - - **Enterprise Connector** (THIS FILE) - System integrators - Full SAP integration; connector-attached sync via `syncConfig` **SCOPE: Most comprehensive integration layer.** - Includes authentication, webhooks, rate limiting, field mapping, bidirectional sync, - retry policies, and complete lifecycle management. This protocol supports multiple authentication strategies, bidirectional sync, - field mapping, webhooks, and comprehensive rate limiting. ## Runtime contract — descriptor vs. registered connector (#2612) @@ -38,93 +29,57 @@ field mapping, webhooks, and comprehensive rate limiting. This schema serves TWO distinct consumers; do not conflate them: 1. **Runtime registration (plugin-only).** The automation engine's connector - -registry — what `GET /connectors` lists and the `connector_action` flow - -node dispatches — is populated exclusively by plugins calling - -`engine.registerConnector(def, handlers)` with a handler per declared - -action (ADR-0018 §Addendum). The definition is validated against this - -schema at registration. - + registry — what `GET /connectors` lists and the `connector_action` flow + node dispatches — is populated exclusively by plugins calling + `engine.registerConnector(def, handlers)` with a handler per declared + action (ADR-0018 §Addendum). The definition is validated against this + schema at registration. 2. **Declarative `connectors:` stack entries (catalog descriptors).** Stack - -metadata validated against this schema is registered as kind 'connector' - -for discovery/documentation/marketplace purposes only — it never reaches - -the runtime registry, because an action here carries no execution binding - -(deliberately: ADR-0023 rejected re-inventing OpenAPI inside this schema). - -The automation service warns at boot about declared entries with `actions` - -that lack a same-name runtime registration; mark deliberate catalog-only - -entries with `enabled: false`. Provider-bound declarative instances that - -a generic executor (connector-openapi / connector-mcp) materializes at - -boot are tracked in #2977 (ADR-0097). + metadata validated against this schema is registered as kind 'connector' + for discovery/documentation/marketplace purposes only — it never reaches + the runtime registry, because an action here carries no execution binding + (deliberately: ADR-0023 rejected re-inventing OpenAPI inside this schema). + The automation service warns at boot about declared entries with `actions` + that lack a same-name runtime registration; mark deliberate catalog-only + entries with `enabled: false`. Provider-bound declarative instances that + a generic executor (connector-openapi / connector-mcp) materializes at + boot are tracked in #2977 (ADR-0097). Authentication is now imported from the canonical `auth/config.zod.ts`. ## When to Use This Layer **Use Enterprise Connector when:** - - Building enterprise-grade connectors (e.g., Salesforce, SAP, Oracle) - - Complex OAuth2/SAML authentication required - - Bidirectional sync with field mapping and transformations - - Webhook management and rate limiting required - - Full CRUD operations and data synchronization - - Need comprehensive retry strategies and error handling **Examples:** - - Full Salesforce integration with webhooks - - SAP ERP connector with CDC (Change Data Capture) - - Microsoft Dynamics 365 connector **When to downgrade:** - - Data transformation only → Use [ETL Pipeline](/docs/references/automation/etl) -See also: [../[automation/etl.zod.ts](/docs/references/automation/etl)](/docs/references/automation/etl) for the ETL Pipeline layer (data engineering) +See also: [../automation/etl.zod.ts](/docs/references/automation/etl) for the ETL Pipeline layer (data engineering) ## There is no "Trigger Registry" alternative This header used to carry a "When to use Integration Connector vs. Trigger - Registry?" comparison, steering "lightweight" cases to - -`[automation/trigger-registry.zod.ts](/docs/references/automation/trigger-registry)`. That file was a third declaration of - +`automation/trigger-registry.zod.ts`. That file was a third declaration of the connector vocabulary with zero consumers — nothing registered, validated - or executed against it — so the guidance pointed authors, with the - platform's authority, at a dead end (#4499; removed alongside the #4480 - per-provider template cluster). The same defect class as the - `capabilities.readOnly` prescription #4487 corrected: a signpost must land - somewhere enforced. Lightweight cases are served HERE — a connector instance - -with simple `auth` — or by `[automation/etl.zod.ts](/docs/references/automation/etl)` for transformation - +with simple `auth` — or by `automation/etl.zod.ts` for transformation pipelines. (The automation-side L1 "Simple Sync" file was itself retired as - a dead end of the same class in #4738.) diff --git a/content/docs/references/kernel/cli-extension.mdx b/content/docs/references/kernel/cli-extension.mdx index 50ac0d8191..7d1657c0c5 100644 --- a/content/docs/references/kernel/cli-extension.mdx +++ b/content/docs/references/kernel/cli-extension.mdx @@ -8,95 +8,60 @@ description: Cli Extension protocol schemas # CLI Extension Protocol Defines the contract for plugins that extend the ObjectStack CLI with - custom commands. This enables third-party packages (e.g., marketplace, - cloud deployment tools) to register new CLI commands via oclif's - built-in plugin system. ## How It Works (oclif Plugin Model) 1. **Declare** — Plugin's `package.json` includes an `oclif` config section - -declaring its commands directory and any topics. - + declaring its commands directory and any topics. 2. **Discover** — The main CLI (`@objectstack/cli`) lists the plugin in its - -`oclif.plugins` array, or users install it via `os plugins install `. - + `oclif.plugins` array, or users install it via `os plugins install `. 3. **Load** — oclif automatically discovers and registers all Command classes - -exported from the plugin's commands directory. + exported from the plugin's commands directory. ## Plugin Package Contract The plugin must be a valid oclif plugin: ```json - // package.json of the plugin - -\{ - -"name": "@acme/plugin-marketplace", - -"oclif": \{ - -"commands": \{ - -"strategy": "pattern", - -"target": "./dist/commands", - -"glob": "**\/*.js" - -\} - -\} - -\} - +{ + "name": "@acme/plugin-marketplace", + "oclif": { + "commands": { + "strategy": "pattern", + "target": "./dist/commands", + "glob": "**\/*.js" + } + } +} ``` Commands are standard oclif Command classes: ```typescript - // src/commands/marketplace/search.ts - -import \{ Args, Command, Flags \} from '@oclif/core'; - -export default class MarketplaceSearch extends Command \{ - -static override description = 'Search marketplace apps'; - -static override args = \{ - -query: Args.string(\{ description: 'Search query', required: true \}), - -\}; - -async run() \{ - -const \{ args \} = await this.parse(MarketplaceSearch); - -// ... - -\} - -\} - +import { Args, Command, Flags } from '@oclif/core'; + +export default class MarketplaceSearch extends Command { + static override description = 'Search marketplace apps'; + static override args = { + query: Args.string({ description: 'Search query', required: true }), + }; + async run() { + const { args } = await this.parse(MarketplaceSearch); + // ... + } +} ``` ## Migration from Commander.js The previous plugin model required `contributes.commands` in the manifest - and exported Commander.js `Command` instances. The new model uses oclif's - native plugin system for automatic command discovery and registration. - The `objectstack.config.ts` plugins array no longer determines CLI commands. diff --git a/content/docs/references/kernel/cluster.mdx b/content/docs/references/kernel/cluster.mdx index 9e228764eb..4a516421f2 100644 --- a/content/docs/references/kernel/cluster.mdx +++ b/content/docs/references/kernel/cluster.mdx @@ -8,19 +8,13 @@ description: Cluster protocol schemas # Cluster Protocol Defines the runtime semantics required for ObjectStack to behave correctly - when more than one Node.js process is involved. The protocol layer codifies - **intent** (scope, delivery, leadership); concrete implementations - (`memory`, `redis`, `postgres`, `nats`) live in `@objectstack/service-cluster`. The full design rationale is in - `content/docs/kernel/cluster.mdx`. Read it before changing - any of the enums here — every value has a precise contract that other - subsystems depend on. diff --git a/content/docs/references/kernel/context.mdx b/content/docs/references/kernel/context.mdx index 32198a0ece..3c76320f29 100644 --- a/content/docs/references/kernel/context.mdx +++ b/content/docs/references/kernel/context.mdx @@ -6,7 +6,6 @@ description: Context protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Runtime Mode Enum - Defines the operating mode of the kernel diff --git a/content/docs/references/kernel/dependency-resolution.mdx b/content/docs/references/kernel/dependency-resolution.mdx index aeb4d2824d..c670ff4ac9 100644 --- a/content/docs/references/kernel/dependency-resolution.mdx +++ b/content/docs/references/kernel/dependency-resolution.mdx @@ -8,35 +8,22 @@ description: Dependency Resolution protocol schemas # Dependency Resolution Protocol Defines schemas for runtime dependency resolution when installing, - upgrading, or managing packages. Provides a standardized way to - express dependency conflicts, resolution results, and installation order. ## Architecture Alignment - - **npm**: Dependency tree resolution with conflict detection - - **Helm**: Dependency management with version constraints - - **Salesforce**: Package dependency validation at install time ## Resolution Flow - ``` - 1. Parse manifest.dependencies (SemVer ranges) - 2. Check installed packages registry - 3. Resolve each dependency → satisfied | needs_install | needs_upgrade | conflict - 4. Detect circular dependencies - 5. Compute topological install order - 6. Return resolution result with required actions - ``` diff --git a/content/docs/references/kernel/events-bus.mdx b/content/docs/references/kernel/events-bus.mdx index 89ab8fae6d..8f3e0ff9ca 100644 --- a/content/docs/references/kernel/events-bus.mdx +++ b/content/docs/references/kernel/events-bus.mdx @@ -6,25 +6,16 @@ description: Events Bus protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Event Bus Configuration Schema - Complete configuration for the event bus system @example - \{ - -"persistence": \{ "enabled": true, "retention": 365 \}, - -"queue": \{ "concurrency": 20 \}, - -"eventSourcing": \{ "enabled": true \}, - -"webhooks": [], - -"messageQueue": \{ "provider": "kafka", "topic": "events" \}, - -"realtime": \{ "enabled": true, "protocol": "websocket" \} - + "persistence": \{ "enabled": true, "retention": 365 \}, + "queue": \{ "concurrency": 20 \}, + "eventSourcing": \{ "enabled": true \}, + "webhooks": [], + "messageQueue": \{ "provider": "kafka", "topic": "events" \}, + "realtime": \{ "enabled": true, "protocol": "websocket" \} \} diff --git a/content/docs/references/kernel/events-core.mdx b/content/docs/references/kernel/events-core.mdx index 8e3ba20093..ab2e47ec4d 100644 --- a/content/docs/references/kernel/events-core.mdx +++ b/content/docs/references/kernel/events-core.mdx @@ -6,9 +6,7 @@ description: Events Core protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Event Priority Enum - Priority levels for event processing - Lower numbers = higher priority diff --git a/content/docs/references/kernel/events-dlq.mdx b/content/docs/references/kernel/events-dlq.mdx index 1468994caf..ae57986e14 100644 --- a/content/docs/references/kernel/events-dlq.mdx +++ b/content/docs/references/kernel/events-dlq.mdx @@ -6,7 +6,6 @@ description: Events Dlq protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Dead Letter Queue Entry Schema - Represents a failed event in the dead letter queue diff --git a/content/docs/references/kernel/events-handlers.mdx b/content/docs/references/kernel/events-handlers.mdx index e45a9bdf5e..894db76704 100644 --- a/content/docs/references/kernel/events-handlers.mdx +++ b/content/docs/references/kernel/events-handlers.mdx @@ -6,7 +6,6 @@ description: Events Handlers protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Event Handler Schema - Defines how to handle a specific event diff --git a/content/docs/references/kernel/events-integrations.mdx b/content/docs/references/kernel/events-integrations.mdx index 8d09afaa6f..7cf25bc0c1 100644 --- a/content/docs/references/kernel/events-integrations.mdx +++ b/content/docs/references/kernel/events-integrations.mdx @@ -6,21 +6,14 @@ description: Events Integrations protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Event Webhook Configuration Schema - Configuration for sending events to webhooks @example - \{ - -"eventPattern": "order.*", - -"url": "https://api.example.com/webhooks/orders", - -"method": "POST", - -"headers": \{ "Authorization": "Bearer token" \} - + "eventPattern": "order.*", + "url": "https://api.example.com/webhooks/orders", + "method": "POST", + "headers": \{ "Authorization": "Bearer token" \} \} diff --git a/content/docs/references/kernel/events-queue.mdx b/content/docs/references/kernel/events-queue.mdx index a99044ddee..87e9fe19e1 100644 --- a/content/docs/references/kernel/events-queue.mdx +++ b/content/docs/references/kernel/events-queue.mdx @@ -6,25 +6,16 @@ description: Events Queue protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Event Queue Configuration Schema - Configuration for async event processing queue @example - \{ - -"name": "event_queue", - -"concurrency": 10, - -"retryPolicy": \{ - -"maxRetries": 3, - -"backoffStrategy": "exponential" - -\} - + "name": "event_queue", + "concurrency": 10, + "retryPolicy": \{ + "maxRetries": 3, + "backoffStrategy": "exponential" + \} \} diff --git a/content/docs/references/kernel/execution-context.mdx b/content/docs/references/kernel/execution-context.mdx index c6485511ab..2d22668691 100644 --- a/content/docs/references/kernel/execution-context.mdx +++ b/content/docs/references/kernel/execution-context.mdx @@ -8,22 +8,16 @@ description: Execution Context protocol schemas Execution Context Schema Defines the runtime context that flows from HTTP request → data operations. - This is the "identity + environment" envelope that every data operation can carry. Design: - - All fields are optional for backward compatibility - - `isSystem` bypasses permission checks (for internal/migration operations) - - `transaction` carries the database transaction handle for atomicity - - `traceId` enables distributed tracing across microservices Usage: - -engine.find('account', \{ context: \{ userId: '...', tenantId: '...' \} \}) + engine.find('account', \{ context: \{ userId: '...', tenantId: '...' \} \}) **Source:** `packages/spec/src/kernel/execution-context.zod.ts` diff --git a/content/docs/references/kernel/metadata-customization.mdx b/content/docs/references/kernel/metadata-customization.mdx index 9a1eb046e2..533705c088 100644 --- a/content/docs/references/kernel/metadata-customization.mdx +++ b/content/docs/references/kernel/metadata-customization.mdx @@ -8,47 +8,29 @@ description: Metadata Customization protocol schemas # Metadata Customization Layer Protocol Defines the overlay system for managing user customizations on top of - package-delivered metadata. This protocol solves the critical challenge - of separating "vendor-managed" metadata from "customer-customized" metadata, - enabling safe package upgrades without losing user changes. ## Architecture Alignment - - **Salesforce**: Managed vs Unmanaged metadata components - - **ServiceNow**: Update Sets with collision detection - - **WordPress**: Parent/child theme overlay model - - **Kubernetes**: Strategic merge patch for resource customization ## Three-Layer Model - ``` - ┌─────────────────────────────────┐ - │ User Layer (scope: user) │ ← Personal overrides (per-user) - ├─────────────────────────────────┤ - │ Platform Layer (scope: platform)│ ← Admin customizations (per-tenant) - ├─────────────────────────────────┤ - │ System Layer (scope: system) │ ← Package-delivered metadata (read-only) - └─────────────────────────────────┘ - ``` ## Merge Resolution Order - Effective metadata = System ← merge(Platform) ← merge(User) - Each layer only stores the delta (changed fields), not the full definition. diff --git a/content/docs/references/kernel/metadata-plugin.mdx b/content/docs/references/kernel/metadata-plugin.mdx index 5b279c3ed5..511412bbdd 100644 --- a/content/docs/references/kernel/metadata-plugin.mdx +++ b/content/docs/references/kernel/metadata-plugin.mdx @@ -8,61 +8,37 @@ description: Metadata Plugin protocol schemas # Metadata Plugin Protocol Defines the specification for the **Metadata Plugin** — the central authority - responsible for managing ALL metadata across the ObjectStack platform. ## Architecture - The Metadata Plugin consolidates all scattered metadata operations into a single, - cohesive plugin that "takes over" the entire platform's metadata management: ``` - ┌──────────────────────────────────────────────────────────────────┐ - │ Metadata Plugin │ - │ │ - │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ - │ │ Type Registry │ │ Loader │ │ Customization Layer │ │ - │ │ (all types) │ │ (file/db/s3)│ │ (overlay / merge) │ │ - │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ - │ │ - │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ - │ │ Persistence │ │ Query │ │ Lifecycle │ │ - │ │ (db records) │ │ (search) │ │ (validate/deploy) │ │ - │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ - └──────────────────────────────────────────────────────────────────┘ - ``` ## Alignment - - **Salesforce**: Metadata API (deploy, retrieve, describe) - - **ServiceNow**: System Dictionary + Metadata API - - **Kubernetes**: API Server + CRD Registry ## References - - [kernel/metadata-loader.zod.ts](/docs/references/kernel/metadata-loader) — MetadataManager wiring (datasource, cache, write gates) - - [kernel/metadata-customization.zod.ts](/docs/references/kernel/metadata-customization) — Overlay/merge protocol - - [system/metadata-persistence.zod.ts](/docs/references/system/metadata-persistence) — Database record format + loader/watch envelope types - - contracts/metadata-service.ts — Service interface diff --git a/content/docs/references/kernel/metadata-protection.mdx b/content/docs/references/kernel/metadata-protection.mdx index 5992215f6b..2cc4aa1077 100644 --- a/content/docs/references/kernel/metadata-protection.mdx +++ b/content/docs/references/kernel/metadata-protection.mdx @@ -8,43 +8,26 @@ description: Metadata Protection protocol schemas # Metadata Protection Model — Phase 1 (ADR-0010) Phase 1 introduces the **item-level lock** (`_lock`) and the - provenance / package tags that drive it. Later phases extend this - file with the path-level (`_frozenPaths`) and package-level - (`metadataDefaults`) layers; the wire shapes here are forward- - compatible with those additions. Wire / runtime contract: - -- `_lock` — 4-state enum, controls overlay / delete actions. - -- `_lockReason` — short, user-visible explanation surfaced in - -`403 ITEM_LOCKED` errors and on Studio tooltips. - -- `_lockSource` — which layer set the lock (Phase 1 only emits - -`'artifact'`; `'package'` and `'env-forced'` - -are reserved for Phase 3/2 respectively). - -- `_provenance` — `'package'` for loader-introduced items, - -`'org'` for tenant-authored, `'env-forced'` - -reserved for emergency overrides. - -- `_packageId` / `_packageVersion` — denormalised from the - -registry tag so consumers don't need a second - -round-trip to inspect provenance. + - `_lock` — 4-state enum, controls overlay / delete actions. + - `_lockReason` — short, user-visible explanation surfaced in + `403 ITEM_LOCKED` errors and on Studio tooltips. + - `_lockSource` — which layer set the lock (Phase 1 only emits + `'artifact'`; `'package'` and `'env-forced'` + are reserved for Phase 3/2 respectively). + - `_provenance` — `'package'` for loader-introduced items, + `'org'` for tenant-authored, `'env-forced'` + reserved for emergency overrides. + - `_packageId` / `_packageVersion` — denormalised from the + registry tag so consumers don't need a second + round-trip to inspect provenance. See `docs/adr/0010-metadata-protection-model.md` for the full - design (industry references, 4-layer model, audit trail). diff --git a/content/docs/references/kernel/package-artifact.mdx b/content/docs/references/kernel/package-artifact.mdx index bf1885ea1c..c43158ee0a 100644 --- a/content/docs/references/kernel/package-artifact.mdx +++ b/content/docs/references/kernel/package-artifact.mdx @@ -8,59 +8,34 @@ description: Package Artifact protocol schemas # Package Artifact Format Protocol Defines the standard structure of a package artifact (.tgz) produced by - the build pipeline. The marketplace uses these schemas to validate, store, - and distribute package artifacts. ## Artifact Internal Structure - ``` - ├── manifest.json ← ManifestSchema serialized - ├── metadata/ ← 30+ metadata types (JSON) - │ ├── objects/ ← *.object.json - │ ├── views/ ← *.view.json - │ ├── pages/ ← *.page.json - │ ├── flows/ ← *.flow.json - │ ├── dashboards/ ← *.dashboard.json - │ ├── permissions/ ← *.permission.json - │ ├── agents/ ← *.agent.json - │ └── ... ← Other metadata types - ├── assets/ ← Static resources - │ ├── icon.svg - │ └── screenshots/ - ├── data/ ← Seed data (SeedSchema serialized) - ├── locales/ ← i18n translation files - ├── checksums.json ← SHA256 checksum per file - └── signature.sig ← RSA-SHA256 package signature - ``` ## Architecture Alignment - - **Salesforce**: Managed Package .zip with metadata components - - **npm**: .tgz with package.json + contents - - **Helm**: Chart .tgz with Chart.yaml + templates - - **VS Code**: .vsix (zip) with extension manifest + assets diff --git a/content/docs/references/kernel/package-registry.mdx b/content/docs/references/kernel/package-registry.mdx index b263c5d43e..ec093169db 100644 --- a/content/docs/references/kernel/package-registry.mdx +++ b/content/docs/references/kernel/package-registry.mdx @@ -10,35 +10,21 @@ description: Package Registry protocol schemas Defines the runtime state and lifecycle operations for installed packages. ## Key Distinction: App vs Package (ADR-0019) - - **App (AppSchema)**: the one consumer-facing unit — what a tenant downloads, - -opens, and uninstalls. Only `type: app` packages are consumer-installable - -(see `isConsumerInstallable`), and a consumer package defines **at most one - -app** — there is no "suite contains apps" aggregator. - + opens, and uninstalls. Only `type: app` packages are consumer-installable + (see `isConsumerInstallable`), and a consumer package defines **at most one + app** — there is no "suite contains apps" aggregator. - **Package (Manifest)**: the internal / control-plane artifact term (the - -"row" in the installed-packages table). Never surfaced to consumers as a - -separate noun. - + "row" in the installed-packages table). Never surfaced to consumers as a + separate noun. - **Internal contributions** (plugin/driver/server/…): the "frameworks inside - -the .app bundle" — bundled within an App or operator-provisioned; a consumer - -never installs them directly. + the .app bundle" — bundled within an App or operator-provisioned; a consumer + never installs them directly. ## Architecture Alignment - - **Salesforce**: Managed Packages with install/uninstall lifecycle - - **VS Code**: Extension marketplace with enable/disable per-workspace - - **Kubernetes**: Helm charts with release state tracking - - **npm**: Package registry with install/uninstall/version management diff --git a/content/docs/references/kernel/package-upgrade.mdx b/content/docs/references/kernel/package-upgrade.mdx index 68ab224864..d27e637538 100644 --- a/content/docs/references/kernel/package-upgrade.mdx +++ b/content/docs/references/kernel/package-upgrade.mdx @@ -8,37 +8,23 @@ description: Package Upgrade protocol schemas # Package Upgrade Protocol Defines the complete lifecycle for upgrading installed packages, - including pre-upgrade analysis, snapshot/backup, execution, validation, - and rollback capabilities. ## Architecture Alignment - - **Salesforce**: Managed Package upgrade with push upgrades and subscriber control - - **ServiceNow**: Update Sets with preview, commit, and back-out support - - **Helm**: Helm upgrade with rollback to previous release - - **Kubernetes**: Rolling update with readiness probes and automatic rollback ## Upgrade Flow - ``` - 1. PreCheck → Validate compatibility, check dependencies - 2. Plan → Generate upgrade plan with metadata diff - 3. Snapshot → Backup current state (metadata + customizations) - 4. Execute → Apply new package metadata with 3-way merge - 5. Validate → Run post-upgrade health checks - 6. Commit → Finalize upgrade (or Rollback on failure) - ``` diff --git a/content/docs/references/kernel/plugin-capability.mdx b/content/docs/references/kernel/plugin-capability.mdx index c41ac10c5a..76f5477750 100644 --- a/content/docs/references/kernel/plugin-capability.mdx +++ b/content/docs/references/kernel/plugin-capability.mdx @@ -8,15 +8,11 @@ description: Plugin Capability protocol schemas # Plugin Capability Protocol Defines the standard way plugins declare their capabilities, implementations, - and conformance levels to ensure interoperability across vendors. Based on the Protocol-Oriented Architecture pattern similar to: - - Kubernetes CRDs (Custom Resource Definitions) - - OSGi Service Registry - - Eclipse Extension Points diff --git a/content/docs/references/kernel/plugin-lifecycle-advanced.mdx b/content/docs/references/kernel/plugin-lifecycle-advanced.mdx index fd2a907ff7..0077885352 100644 --- a/content/docs/references/kernel/plugin-lifecycle-advanced.mdx +++ b/content/docs/references/kernel/plugin-lifecycle-advanced.mdx @@ -8,17 +8,12 @@ description: Plugin Lifecycle Advanced protocol schemas # Advanced Plugin Lifecycle Protocol Defines advanced lifecycle management capabilities including: - - Hot reload and live updates - - Graceful degradation and fallback mechanisms - - Health monitoring and auto-recovery - - State preservation during updates This protocol extends the basic plugin lifecycle with enterprise-grade - features for production environments. diff --git a/content/docs/references/kernel/plugin-loading.mdx b/content/docs/references/kernel/plugin-loading.mdx index 86c6054767..20bdcf5a6d 100644 --- a/content/docs/references/kernel/plugin-loading.mdx +++ b/content/docs/references/kernel/plugin-loading.mdx @@ -8,27 +8,17 @@ description: Plugin Loading protocol schemas # Plugin Loading Protocol Defines the enhanced plugin loading mechanism for the microkernel architecture. - Inspired by industry best practices from: - - Kubernetes CRDs and Operators - - OSGi Dynamic Module System - - Eclipse Plugin Framework - - Webpack Module Federation This protocol enables: - - Lazy loading and code splitting - - Dynamic imports and parallel initialization - - Capability-based discovery - - Hot reload in development - - Advanced caching strategies diff --git a/content/docs/references/kernel/plugin-registry.mdx b/content/docs/references/kernel/plugin-registry.mdx index 6fe97464a0..3a43b1b1c5 100644 --- a/content/docs/references/kernel/plugin-registry.mdx +++ b/content/docs/references/kernel/plugin-registry.mdx @@ -8,9 +8,7 @@ description: Plugin Registry protocol schemas # Plugin Registry Protocol Defines the schema for the plugin discovery and registry system. - This enables plugins from different vendors to be discovered, validated, - and composed together in the ObjectStack ecosystem. diff --git a/content/docs/references/kernel/plugin-security-advanced.mdx b/content/docs/references/kernel/plugin-security-advanced.mdx index 620fc1e8ca..239276a2e6 100644 --- a/content/docs/references/kernel/plugin-security-advanced.mdx +++ b/content/docs/references/kernel/plugin-security-advanced.mdx @@ -8,19 +8,13 @@ description: Plugin Security Advanced protocol schemas # Plugin Security and Sandboxing Protocol Defines comprehensive security mechanisms for plugin isolation, permission - management, and threat protection in the ObjectStack ecosystem. Features: - - Fine-grained permission system - - Resource access control - - Sandboxing and isolation - - Security scanning and verification - - Runtime security monitoring diff --git a/content/docs/references/kernel/plugin-security.mdx b/content/docs/references/kernel/plugin-security.mdx index 9bab36561d..273d787132 100644 --- a/content/docs/references/kernel/plugin-security.mdx +++ b/content/docs/references/kernel/plugin-security.mdx @@ -8,21 +8,14 @@ description: Plugin Security protocol schemas # Plugin Security & Dependency Resolution Protocol Provides comprehensive security scanning, vulnerability management, - and dependency resolution for the ObjectStack plugin ecosystem. Features: - - CVE/vulnerability scanning - - Dependency graph resolution - - Semantic version conflict detection - - Supply chain security - - Plugin sandboxing policies - - Trust and verification workflows diff --git a/content/docs/references/kernel/plugin-structure.mdx b/content/docs/references/kernel/plugin-structure.mdx index 8b0fc75a27..baddbb0bae 100644 --- a/content/docs/references/kernel/plugin-structure.mdx +++ b/content/docs/references/kernel/plugin-structure.mdx @@ -8,7 +8,6 @@ description: Plugin Structure protocol schemas ObjectStack Plugin Structure Standards (OPS) Formal Zod definitions for the Plugin Directory Structure and File Naming conventions. - This can be used by the CLI or IDE extensions to lint project structure. See also: PLUGIN_STANDARDS.md diff --git a/content/docs/references/kernel/plugin-validator.mdx b/content/docs/references/kernel/plugin-validator.mdx index 7fa3673c60..d68b23aa2d 100644 --- a/content/docs/references/kernel/plugin-validator.mdx +++ b/content/docs/references/kernel/plugin-validator.mdx @@ -8,11 +8,9 @@ description: Plugin Validator protocol schemas Plugin Validator Protocol Zod schemas for plugin validation data structures. - These schemas align with the IPluginValidator contract interface. Following ObjectStack "Zod First" principle - all data structures - must have Zod schemas for runtime validation and JSON Schema generation. diff --git a/content/docs/references/kernel/plugin-versioning.mdx b/content/docs/references/kernel/plugin-versioning.mdx index 207f08c4c1..0f4d31be83 100644 --- a/content/docs/references/kernel/plugin-versioning.mdx +++ b/content/docs/references/kernel/plugin-versioning.mdx @@ -8,17 +8,12 @@ description: Plugin Versioning protocol schemas # Plugin Versioning and Compatibility Protocol Defines comprehensive versioning, compatibility checking, and dependency - resolution mechanisms for the plugin ecosystem. Based on semantic versioning (SemVer) with extensions for: - - Compatibility matrices - - Breaking change detection - - Migration paths - - Multi-version support diff --git a/content/docs/references/kernel/service-registry.mdx b/content/docs/references/kernel/service-registry.mdx index 751eb97401..ede8e23f49 100644 --- a/content/docs/references/kernel/service-registry.mdx +++ b/content/docs/references/kernel/service-registry.mdx @@ -8,17 +8,13 @@ description: Service Registry protocol schemas Service Registry Protocol Zod schemas for service registry data structures. - These schemas align with the IServiceRegistry contract interface. Following ObjectStack "Zod First" principle - all data structures - must have Zod schemas for runtime validation and JSON Schema generation. Note: IServiceRegistry itself is a runtime interface (methods only), - so it correctly remains a TypeScript interface. This file contains - schemas for configuration and metadata related to service registry. diff --git a/content/docs/references/kernel/startup-orchestrator.mdx b/content/docs/references/kernel/startup-orchestrator.mdx index 7e9c832aa5..1fee839c3a 100644 --- a/content/docs/references/kernel/startup-orchestrator.mdx +++ b/content/docs/references/kernel/startup-orchestrator.mdx @@ -8,11 +8,9 @@ description: Startup Orchestrator protocol schemas Startup Orchestrator Protocol Zod schemas for plugin startup orchestration data structures. - These schemas align with the IStartupOrchestrator contract interface. Following ObjectStack "Zod First" principle - all data structures - must have Zod schemas for runtime validation and JSON Schema generation. diff --git a/content/docs/references/security/explain.mdx b/content/docs/references/security/explain.mdx index ef86e20869..cf20bd3926 100644 --- a/content/docs/references/security/explain.mdx +++ b/content/docs/references/security/explain.mdx @@ -6,47 +6,30 @@ description: Explain protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} [ADR-0090 D6] Access-explanation contract — `explain(principal, object, - operation)` as a first-class API. The explain engine (`@objectstack/plugin-security`) walks the SAME code - paths as the enforcement middleware — the same permission-set resolution, - the same evaluator, the same RLS compiler — and reports what each layer of - the evaluation pipeline contributed to the final decision. "Explained by - construction": the report can never drift from enforcement because it IS - enforcement, minus the throw. Layer order mirrors the runtime pipeline: - tenant_isolation → principal → required_permissions → object_crud → fls → - owd_baseline → depth → sharing → vama_bypass → rls. [C2 / ADR-0095] Record-grained explanation. The contract carries an optional - `recordId` on the request and, when present, a per-layer `record` attribution - plus a top-level `record` verdict on the response — so the sharing / rls / owd - layers can report the ROW-LEVEL story for one concrete record (which share - admitted it, which filter excluded it, whether the effective row filter - matches). Object-level requests (no `recordId`) stay byte-compatible. [ADR-0095 D1/D2] The contract also reserves the kernel-chain vocabulary the - β engine + UI will fill: the always-first tenant wall as `tenant_isolation` - (Layer 0), a per-layer `kernelTier` marking Layer 0 vs. business RLS - (Layer 1), and the monotonic posture ladder - (PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL) on the resolved principal. diff --git a/content/docs/references/security/permission.mdx b/content/docs/references/security/permission.mdx index 1b76886353..6c52130a20 100644 --- a/content/docs/references/security/permission.mdx +++ b/content/docs/references/security/permission.mdx @@ -6,15 +6,11 @@ description: Permission protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Entity (Object) Level Permissions - Defines CRUD + VAMA (View All / Modify All) + Lifecycle access. Refined with enterprise data lifecycle controls: - - Transfer (Ownership change) - - Restore (Soft delete recovery) - - Purge (Hard delete / Compliance) diff --git a/content/docs/references/security/rls.mdx b/content/docs/references/security/rls.mdx index f30f1f2c37..21f2aa58c1 100644 --- a/content/docs/references/security/rls.mdx +++ b/content/docs/references/security/rls.mdx @@ -8,137 +8,91 @@ description: Rls protocol schemas # Row-Level Security (RLS) Protocol Implements fine-grained record-level access control inspired by PostgreSQL RLS - and Salesforce Criteria-Based Sharing Rules. ## Overview Row-Level Security (RLS) allows you to control which rows users can access - in database tables based on their identity and positions. Unlike - object-level permissions (CRUD), RLS provides record-level filtering. ## Use Cases 1. **Multi-Tenant Data Isolation** - -- Users only see records from their organization - -- `using: "organization_id == current_user.organization_id"` + - Users only see records from their organization + - `using: "organization_id == current_user.organization_id"` 2. **Ownership-Based Access** - -- Users only see records they own - -- `using: "owner_id == current_user.id"` + - Users only see records they own + - `using: "owner_id == current_user.id"` 3. **Organization Member Visibility** - -- Users see fellow members of their active organization - -- `using: "id in current_user.org_user_ids"` - -(`org_user_ids` is pre-resolved by the runtime) + - Users see fellow members of their active organization + - `using: "id in current_user.org_user_ids"` + (`org_user_ids` is pre-resolved by the runtime) 4. **Territory / Regional Access (§7.3.1 dynamic membership)** - -- Sales reps only see accounts in their assigned territories - -- `using: "account_id in current_user.territory_account_ids"` - -(the runtime stages `territory_account_ids` in `ExecutionContext.rlsMembership`) + - Sales reps only see accounts in their assigned territories + - `using: "account_id in current_user.territory_account_ids"` + (the runtime stages `territory_account_ids` in `ExecutionContext.rlsMembership`) 5. **Manager / Hierarchy Access (§7.3.1 dynamic membership)** - -- Managers see records assigned to anyone they manage - -- `using: "assigned_to_id in current_user.team_member_ids"` - -(the runtime pre-resolves `team_member_ids`, no subquery needed) + - Managers see records assigned to anyone they manage + - `using: "assigned_to_id in current_user.team_member_ids"` + (the runtime pre-resolves `team_member_ids`, no subquery needed) ## PostgreSQL RLS Comparison PostgreSQL RLS Example: - ```sql - CREATE POLICY tenant_isolation ON accounts - -FOR SELECT - -USING (tenant_id = current_setting('app.current_tenant_id')::uuid); + FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id')::uuid); CREATE POLICY account_insert ON accounts - -FOR INSERT - -WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid); - + FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid); ``` ObjectStack RLS Equivalent: - ```typescript - -\{ - -name: 'tenant_isolation', - -object: 'account', - -operation: 'select', - -using: 'organization_id == current_user.organization_id' - -\} - +{ + name: 'tenant_isolation', + object: 'account', + operation: 'select', + using: 'organization_id == current_user.organization_id' +} ``` ## Salesforce Sharing Rules Comparison Salesforce uses "Sharing Rules" and a visibility hierarchy for record-level - access (our equivalent hierarchy is the business-unit tree, ADR-0090 D3). - ObjectStack RLS provides similar functionality with more flexibility. Salesforce: - - Criteria-Based Sharing: Share records matching criteria with users/groups - - Owner-Based Sharing: Share records based on who owns them - - Manual Sharing: Individual record sharing ObjectStack RLS: - - A small, fixed expression grammar (equality, set-membership, always-true) - - Subquery-shaped needs are pre-resolved by the runtime (§7.3.1) - - Multiple policies OR-combine for union (any-match-allows) semantics ## Best Practices 1. **Always Define SELECT Policy**: Control what users can view - 2. **Define INSERT/UPDATE CHECK Policies**: Prevent data leakage - 3. **Use Position-Scoped Policies**: Apply different rules to different positions - 4. **Test Thoroughly**: RLS can have complex interactions - 5. **Monitor Performance**: Complex RLS policies can impact query performance ## Security Considerations 1. **Defense in Depth**: RLS is one layer; use with object permissions - 2. **Default Deny**: If no policy matches, access is denied - 3. **Policy Precedence**: More permissive policy wins (OR logic) - 4. **Context Variables**: Ensure current_user context is always set See also: https://www.postgresql.org/docs/current/ddl-rowsecurity.html diff --git a/content/docs/references/security/sharing.mdx b/content/docs/references/security/sharing.mdx index 65cd287edc..5e8de7166b 100644 --- a/content/docs/references/security/sharing.mdx +++ b/content/docs/references/security/sharing.mdx @@ -6,7 +6,6 @@ description: Sharing protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Organization-Wide Defaults (OWD) - The baseline security posture for an object. diff --git a/content/docs/references/shared/branded-types.mdx b/content/docs/references/shared/branded-types.mdx index b26e54f44f..536cf256b9 100644 --- a/content/docs/references/shared/branded-types.mdx +++ b/content/docs/references/shared/branded-types.mdx @@ -8,25 +8,18 @@ description: Branded Types protocol schemas Branded Types for ObjectStack Identifiers Branded types provide compile-time safety by preventing accidental mixing - of different identifier kinds. For example, you cannot pass an ObjectName - where a FieldName is expected, even though both are strings at runtime. @example - ```ts - -import \{ ObjectNameSchema, FieldNameSchema \} from '@objectstack/spec'; +import { ObjectNameSchema, FieldNameSchema } from '@objectstack/spec'; const objName = ObjectNameSchema.parse('project_task'); // ObjectName - const fieldName = FieldNameSchema.parse('task_name'); // FieldName // TypeScript will catch this at compile time: - // const fn: FieldName = objName; // Error! - ``` diff --git a/content/docs/references/shared/expression.mdx b/content/docs/references/shared/expression.mdx index 9783d681e7..2cd762d0d7 100644 --- a/content/docs/references/shared/expression.mdx +++ b/content/docs/references/shared/expression.mdx @@ -8,35 +8,24 @@ description: Expression protocol schemas # Expression Protocol Canonical wire format for all "expression"-shaped metadata across ObjectStack - (formula fields, predicates, conditions, criteria, visibility rules, seed - dynamic values, …). -The persisted form is `\{ dialect, source \}` (and, after `objectstack - -compile` normalization, `\{ dialect, ast \}`). String-only shorthand is - +The persisted form is `{ dialect, source }` (and, after `objectstack +compile` normalization, `{ dialect, ast }`). String-only shorthand is accepted at *input* time for developer ergonomics; build emits the canonical - envelope. ## Dialects | dialect | engine | use | - |:---|:---|:---| - | `cel` | `@objectstack/formula` (cel-js + ObjectStack stdlib) | formulas, predicates, seed dynamic values | - | `js` | sandboxed L2 hook bodies (`isolated-vm` / `quickjs`) | mapping, hook bodies | - | `cron` | `cron-parser` | job schedules | SQL fragments (analytics joins, partial indexes) are intentionally **not** - routed through this schema — they stay driver-native because their security - posture and portability story differ. See also: content/docs/concepts/north-star.mdx §8 "No private expression DSL" diff --git a/content/docs/references/shared/http.mdx b/content/docs/references/shared/http.mdx index 2c73380148..218fc2934f 100644 --- a/content/docs/references/shared/http.mdx +++ b/content/docs/references/shared/http.mdx @@ -8,7 +8,6 @@ description: Http protocol schemas Shared HTTP Schemas Common HTTP-related schemas used across API and System protocols. - These schemas ensure consistency across different parts of the stack. diff --git a/content/docs/references/shared/identifiers.mdx b/content/docs/references/shared/identifiers.mdx index f0f9d3dda5..57d23c55cb 100644 --- a/content/docs/references/shared/identifiers.mdx +++ b/content/docs/references/shared/identifiers.mdx @@ -8,73 +8,43 @@ description: Identifiers protocol schemas System Identifier Schema Universal naming convention for all machine identifiers (API Names) in ObjectStack. - Enforces lowercase with underscores or dots to ensure: - - Cross-platform compatibility (case-insensitive filesystems) - - URL-friendliness (no encoding needed) - - Database consistency (no collation issues) - - Security (no case-sensitivity bugs in permission checks) **Applies to all metadata that acts as a machine identifier:** - - Object names (tables/collections) - - Field names - - Role names - - Permission set names - - Action/trigger names - - Event keys - - App IDs - - Menu/page IDs - - Select option values - - Workflow names - - Webhook names **Naming Convention Summary:** - | Type | Pattern | Example | - |------|---------|---------| - | Machine ID | snake_case | `crm_account`, `btn_submit`, `role_admin` | - | Event keys | dot.notation | `user.login`, `order.created` | - | Labels | Any case | `Client Account`, `Submit Form` | @example Valid identifiers - - 'account' - - 'crm_account' - - 'user_profile' - - 'order.created' (for events) - - 'api_v2_endpoint' @example Invalid identifiers (will be rejected) - - 'Account' (uppercase) - - 'CrmAccount' (camelCase) - - 'crm-account' (kebab-case - use underscore instead) - - 'user profile' (spaces) diff --git a/content/docs/references/shared/mapping.mdx b/content/docs/references/shared/mapping.mdx index d5ef0ceeed..88fb9ca0bf 100644 --- a/content/docs/references/shared/mapping.mdx +++ b/content/docs/references/shared/mapping.mdx @@ -10,69 +10,43 @@ Base Field Mapping Protocol Shared by: Connector, External Lookup This module provides the canonical field mapping schema used across - ObjectStack for data synchronization. **Use Cases:** - - Integration connectors (integration/connector.zod.ts) - - External lookups (data/external-lookup.zod.ts) @example Basic field mapping - ```typescript - -const mapping: FieldMapping = \{ - -source: 'external_user_id', - -target: 'user_id', - -\}; - +const mapping: FieldMapping = { + source: 'external_user_id', + target: 'user_id', +}; ``` @example With a fallback for missing source values - ```typescript - -const mapping: FieldMapping = \{ - -source: 'user_name', - -target: 'name', - -defaultValue: 'Unknown' - -\}; - +const mapping: FieldMapping = { + source: 'user_name', + target: 'name', + defaultValue: 'Unknown' +}; ``` ## What is NOT here any more: `transform` (#5552, protocol 17) This schema used to carry a `transform` key typed by a five-member - discriminated union — `constant` / `cast` / `lookup` / `javascript` / `map`. - No runtime ever executed one of the five, so the whole union was retired - under ADR-0049 enforce-or-remove; the tombstone below carries the - prescription, and the measurement behind it is written up on the - `field-mapping-transform-removed` conversion in `src/conversions/registry.ts`. -**Where transforms actually run:** `[data/mapping.zod.ts](/docs/references/data/mapping)`'s - +**Where transforms actually run:** `data/mapping.zod.ts`'s `ImportFieldMappingSchema.transform` — a flat string enum steering a `params` - bag, applied row by row by the REST import path and recorded live, key by - key, in `packages/spec/liveness/mapping.json`. Same word, opposite - disposition: that one runs, and rejects its own `javascript` value with a 400 - rather than pretending to. diff --git a/content/docs/references/shared/protection.mdx b/content/docs/references/shared/protection.mdx index d7faa7c9f1..2afa9f16df 100644 --- a/content/docs/references/shared/protection.mdx +++ b/content/docs/references/shared/protection.mdx @@ -8,74 +8,44 @@ description: Protection protocol schemas # Package-level metadata protection (ADR-0010 §3.7 — Phase 4.3) Public, type-safe author surface for package authors to declare - how much of one of their metadata items the runtime (and the - tenant's Studio) is allowed to mutate. Internally this is what - gets translated into the `_lock` / `_lockReason` / `_lockDocsUrl` - -private envelope (`[kernel/metadata-protection.zod.ts](/docs/references/kernel/metadata-protection)`) that the - +private envelope (`kernel/metadata-protection.zod.ts`) that the protocol layer enforces. Why two layers? - -- **`protection`** is the *author DX* surface — typed, validated, - -and discoverable via IntelliSense on every `*.app.ts` / - -`*.object.ts` / `*.view.ts` etc. - -- **`_lock` envelope** is the *runtime* surface — strips off the - -protection block on load and stamps the private fields so the - -persistence and overlay layers don't drag the author-facing - -block through every `sys_metadata` overlay diff. + - **`protection`** is the *author DX* surface — typed, validated, + and discoverable via IntelliSense on every `*.app.ts` / + `*.object.ts` / `*.view.ts` etc. + - **`_lock` envelope** is the *runtime* surface — strips off the + protection block on load and stamps the private fields so the + persistence and overlay layers don't drag the author-facing + block through every `sys_metadata` overlay diff. Example: - ```ts - -export const SETUP_APP: App = \{ - -name: 'setup', - -label: 'Setup', - -protection: \{ - -lock: 'full', - -reason: 'Core admin UI shipped by @objectstack/platform-objects.', - -docsUrl: 'https://docs.objectstack.ai/adr/0010-metadata-protection', - -\}, - -// ... - -\}; - +export const SETUP_APP: App = { + name: 'setup', + label: 'Setup', + protection: { + lock: 'full', + reason: 'Core admin UI shipped by @objectstack/platform-objects.', + docsUrl: 'https://docs.objectstack.ai/adr/0010-metadata-protection', + }, + // ... +}; ``` The loader (`metadata/plugin.ts` + `objectql/registry.ts`) calls - `applyProtection` to translate this block into the private - `_lock` envelope at registration time. Authors should NEVER set - the underscored fields directly — they are an implementation - detail. See also: - -- ADR-0010 §3.7 — Future work → now implemented. - -- `[kernel/metadata-protection.zod.ts](/docs/references/kernel/metadata-protection)` — the runtime envelope. + - ADR-0010 §3.7 — Future work → now implemented. + - `kernel/metadata-protection.zod.ts` — the runtime envelope. **Source:** `packages/spec/src/shared/protection.zod.ts` diff --git a/content/docs/references/studio/flow-builder.mdx b/content/docs/references/studio/flow-builder.mdx index 68921e1d23..2e2a9f75c9 100644 --- a/content/docs/references/studio/flow-builder.mdx +++ b/content/docs/references/studio/flow-builder.mdx @@ -10,47 +10,28 @@ description: Flow Builder protocol schemas Studio Flow Builder Protocol Defines the specification for the visual Flow Builder (automation canvas) - within ObjectStack Studio. Covers: - - **Node Shape Registry**: Shape and visual style per FlowNodeAction type - - **Canvas Node**: Position, size, and rendering hints for each node on canvas - - **Canvas Edge**: Visual properties for sequence flows (normal, default, fault, back-edge) - - **Flow Builder Config**: Canvas settings, palette, minimap, and toolbar ## Architecture ``` - ┌──────────────────────────────────────────────────────────────┐ - │ Toolbar (run / save / undo / zoom / layout) │ - ├──────────┬───────────────────────────────────┬───────────────┤ - │ Node │ Canvas │ Property │ - │ Palette │ ┌─────┐ ┌──────────┐ │ Panel │ - │ │ │start│───▶│ decision │──▶ ... │ (node-aware) │ - │ ─ BPMN │ └─────┘ └──────────┘ │ │ - │ ─ CRUD │ ┌──────────┐ │ ─ config │ - │ ─ Logic │ │parallel │ │ ─ edges │ - │ ─ HTTP │ │ gateway │ │ ─ validation │ - ├──────────┴───────────────────────────────────┴───────────────┤ - │ Minimap / Zoom Controls │ - └──────────────────────────────────────────────────────────────┘ - ``` diff --git a/content/docs/references/studio/object-designer.mdx b/content/docs/references/studio/object-designer.mdx index ecc8cdfc4f..e99b9c8b05 100644 --- a/content/docs/references/studio/object-designer.mdx +++ b/content/docs/references/studio/object-designer.mdx @@ -10,15 +10,10 @@ description: Object Designer protocol schemas Object Designer Protocol — Visual Field Editor, Relationship Mapper & ER Diagram Defines the specification for the Object Designer experience within ObjectStack Studio, - including: - - **Field Editor**: Visual field creation/editing with type-aware property panels - - **Relationship Mapper**: Visual lookup/master-detail relationship configuration - - **ER Diagram**: Entity-Relationship diagram rendering and interaction - - **Object Manager**: Unified object list with search, filtering, and bulk operations ## Architecture @@ -26,81 +21,45 @@ including: The Object Designer is composed of four interconnected panels: ``` - ┌─────────────────────────────────────────────────────────────────┐ - │ Object Manager (list / search) │ - ├──────────────┬──────────────────────────┬──────────────────────┤ - │ Object List │ Field Editor │ Property Panel │ - │ (sidebar) │ (table + inline edit) │ (type-specific) │ - │ │ │ │ - │ ─ search │ ─ drag-to-reorder │ ─ constraints │ - │ ─ filter │ ─ inline type picker │ ─ validation │ - │ ─ group │ ─ batch add/remove │ ─ security │ - │ ─ create │ ─ field groups │ ─ relationships │ - ├──────────────┴──────────────────────────┴──────────────────────┤ - │ ER Diagram (toggle panel) │ - │ ─ auto-layout (force / hierarchy / grid) │ - │ ─ interactive: click node → navigate to object │ - │ ─ hover: highlight connected relationships │ - │ ─ zoom/pan/minimap │ - └─────────────────────────────────────────────────────────────────┘ - ``` @example - ```typescript - -import \{ - -ObjectDesignerConfigSchema, - -ERDiagramConfigSchema, - -\} from '@objectstack/spec/studio'; - -const config = ObjectDesignerConfigSchema.parse(\{ - -defaultView: 'field-editor', - -fieldEditor: \{ - -inlineEditing: true, - -dragReorder: true, - -showFieldGroups: true, - -\}, - -erDiagram: \{ - -enabled: true, - -layout: 'force', - -showFieldDetails: true, - -\}, - -\}); - +import { + ObjectDesignerConfigSchema, + ERDiagramConfigSchema, +} from '@objectstack/spec/studio'; + +const config = ObjectDesignerConfigSchema.parse({ + defaultView: 'field-editor', + fieldEditor: { + inlineEditing: true, + dragReorder: true, + showFieldGroups: true, + }, + erDiagram: { + enabled: true, + layout: 'force', + showFieldDetails: true, + }, +}); ``` diff --git a/content/docs/references/studio/plugin.mdx b/content/docs/references/studio/plugin.mdx index 68fe8238c0..2e05ce00bc 100644 --- a/content/docs/references/studio/plugin.mdx +++ b/content/docs/references/studio/plugin.mdx @@ -10,87 +10,51 @@ description: Plugin protocol schemas Studio Plugin Protocol Defines the specification for Studio plugins — a VS Code-like extension model - that allows each metadata type to contribute custom viewers, designers, - sidebar groups, actions, and commands. ## Architecture Like VS Code extensions, Studio plugins have two layers: - 1. **Manifest (Declarative)** — JSON-serializable contribution points - 2. **Activation (Imperative)** — Runtime registration of React components & handlers ``` - ┌─────────────────────────────────────────────────────────┐ - │ Studio Host │ - │ ┌───────────────────────────────────────────────────┐ │ - │ │ Plugin Registry │ │ - │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ - │ │ │ Object │ │ Flow │ │ Agent │ ... │ │ - │ │ │ Plugin │ │ Plugin │ │ Plugin │ │ │ - │ │ └──────────┘ └──────────┘ └──────────┘ │ │ - │ └───────────────────────────────────────────────────┘ │ - │ │ - │ ┌─── Sidebar ───┐ ┌──── Main Panel ────────────────┐ │ - │ │ [plugin icons] │ │ PluginHost renders viewer │ │ - │ │ [plugin groups]│ │ from highest-priority plugin │ │ - │ └────────────────┘ └────────────────────────────────┘ │ - └─────────────────────────────────────────────────────────┘ - ``` @example - ```typescript - -import \{ StudioPluginManifestSchema \} from '@objectstack/spec/studio'; - -const manifest = StudioPluginManifestSchema.parse(\{ - -id: 'objectstack.object-designer', - -name: 'Object Designer', - -version: '1.0.0', - -contributes: \{ - -metadataViewers: [\{ - -id: 'object-explorer', - -metadataTypes: ['object', 'objects'], - -label: 'Object Explorer', - -priority: 100, - -modes: ['preview', 'design', 'data'], - -\}], - -\}, - -\}); - +import { StudioPluginManifestSchema } from '@objectstack/spec/studio'; + +const manifest = StudioPluginManifestSchema.parse({ + id: 'objectstack.object-designer', + name: 'Object Designer', + version: '1.0.0', + contributes: { + metadataViewers: [{ + id: 'object-explorer', + metadataTypes: ['object', 'objects'], + label: 'Object Explorer', + priority: 100, + modes: ['preview', 'design', 'data'], + }], + }, +}); ``` diff --git a/content/docs/references/system/app-install.mdx b/content/docs/references/system/app-install.mdx index 012dc01d01..d85ad69cb4 100644 --- a/content/docs/references/system/app-install.mdx +++ b/content/docs/references/system/app-install.mdx @@ -8,21 +8,14 @@ description: App Install protocol schemas App Installation Protocol Defines the schemas for installing marketplace apps into tenant databases. - An "app install" injects metadata (objects, views, flows) + schema sync - into a tenant's isolated database. Install pipeline: - 1. Check compatibility (kernel version, existing objects, conflicts) - 2. Validate app manifest - 3. Apply schema changes (via deploy pipeline) - 4. Seed initial data - 5. Register app in tenant's metadata registry diff --git a/content/docs/references/system/auth-config.mdx b/content/docs/references/system/auth-config.mdx index 02ad984df3..ea40d47098 100644 --- a/content/docs/references/system/auth-config.mdx +++ b/content/docs/references/system/auth-config.mdx @@ -8,7 +8,6 @@ description: Auth Config protocol schemas Better-Auth Configuration Protocol Defines the configuration required to initialize the Better-Auth kernel. - Used in server-side configuration injection. diff --git a/content/docs/references/system/book.mdx b/content/docs/references/system/book.mdx index c7f45aacd1..93b96c83e1 100644 --- a/content/docs/references/system/book.mdx +++ b/content/docs/references/system/book.mdx @@ -8,31 +8,19 @@ description: Book protocol schemas Package Documentation Navigation — the `book` element (ADR-0046 §6). A `book` is the **spine** of a table of contents: an ordered set of groups - (sections) plus identity and access. It deliberately does NOT store its - members. Membership — which doc sits in which group — is **derived** from a - rule on each group (`include` glob/tag) plus an optional per-doc - `order`/`group`, never held in a central array. Why a spine and not a container (ADR-0046 §6.2.1): storing the whole tree in - one array conflates low-cardinality group definitions (curated by a human, - rarely changed) with high-cardinality membership (churned by the AI on every - new doc). A central array forces a read-modify-write on every doc the AI - adds — stale/concurrent edits silently drop or reorder siblings — and breaks - overlay (RFC 7396 replaces arrays atomically, shadowing docs a later package - version adds). A derived spine removes the write entirely: the AI creates a - doc named to match a rule and it files itself (create-and-forget), and the - only per-doc storage is the scalar `doc.order`, which merges cleanly. diff --git a/content/docs/references/system/cache.mdx b/content/docs/references/system/cache.mdx index e876b57daa..b24ad44f74 100644 --- a/content/docs/references/system/cache.mdx +++ b/content/docs/references/system/cache.mdx @@ -8,33 +8,22 @@ description: Cache protocol schemas Application-Level Cache Protocol Multi-tier caching strategy for application data. - Supports Memory, Redis, Memcached, and CDN. ## Caching in ObjectStack -**Application Cache (`[system/cache.zod.ts](/docs/references/system/cache)`) - This File** - +**Application Cache (`system/cache.zod.ts`) - This File** - **Purpose**: Cache computed data, query results, aggregations - - **Technologies**: Redis, Memcached, in-memory LRU - - **Configuration**: TTL, eviction policies, cache warming - - **Use case**: Cache expensive database queries, computed values - - **Scope**: Application layer, server-side data storage -**HTTP Cache (`[api/http-cache.zod.ts](/docs/references/api/http-cache)`)** - +**HTTP Cache (`api/http-cache.zod.ts`)** - **Purpose**: Cache API responses at HTTP protocol level - - **Technologies**: HTTP headers (ETag, Last-Modified, Cache-Control), CDN - - **Configuration**: Cache-Control headers, validation tokens - - **Use case**: Reduce API response time for repeated metadata requests - - **Scope**: HTTP layer, client-server communication See also: ../../[api/http-cache.zod.ts](/docs/references/api/http-cache) for HTTP-level caching diff --git a/content/docs/references/system/change-management.mdx b/content/docs/references/system/change-management.mdx index 297e0e0f33..bb764ce3e3 100644 --- a/content/docs/references/system/change-management.mdx +++ b/content/docs/references/system/change-management.mdx @@ -8,7 +8,6 @@ description: Change Management protocol schemas Change Type Enum Classification of change requests based on risk and approval requirements. - Follows ITIL change management best practices. diff --git a/content/docs/references/system/collaboration.mdx b/content/docs/references/system/collaboration.mdx index 43a1857310..1899e48547 100644 --- a/content/docs/references/system/collaboration.mdx +++ b/content/docs/references/system/collaboration.mdx @@ -8,9 +8,7 @@ description: Collaboration protocol schemas Real-Time Collaboration Protocol Defines schemas for real-time collaborative editing in ObjectStack. - Supports Operational Transformation (OT), CRDT (Conflict-free Replicated Data Types), - cursor sharing, and awareness state for collaborative applications. Industry alignment: Google Docs, Figma, VSCode Live Share, Yjs diff --git a/content/docs/references/system/core-services.mdx b/content/docs/references/system/core-services.mdx index 005a38c0dd..be32f42a98 100644 --- a/content/docs/references/system/core-services.mdx +++ b/content/docs/references/system/core-services.mdx @@ -8,13 +8,9 @@ description: Core Services protocol schemas # Service Registry Protocol Defines the standard built-in services that constitute the ObjectStack Kernel. - This registry is used by the `ObjectKernel` and `HttpDispatcher` to: - 1. Verify service availability. - 2. Route requests to the correct service handler. - 3. Type-check service interactions. diff --git a/content/docs/references/system/deploy-bundle.mdx b/content/docs/references/system/deploy-bundle.mdx index 09c7b3cc33..5a31a2c3f4 100644 --- a/content/docs/references/system/deploy-bundle.mdx +++ b/content/docs/references/system/deploy-bundle.mdx @@ -8,15 +8,11 @@ description: Deploy Bundle protocol schemas Deploy Bundle Protocol Defines the schemas for metadata-driven deployment: - Schema Push → Zod Validate → Diff → DDL Sync → Register This eliminates traditional CI/CD pipelines for schema changes. - A "deploy" is a bundle of metadata (objects, views, flows, permissions) - that is validated, diffed against the current state, and applied - as DDL migrations directly to the tenant database. Target: 2-5 second deploys vs. 2-15 minute traditional Docker/CI/CD. diff --git a/content/docs/references/system/disaster-recovery.mdx b/content/docs/references/system/disaster-recovery.mdx index 6761f46b5a..1144635220 100644 --- a/content/docs/references/system/disaster-recovery.mdx +++ b/content/docs/references/system/disaster-recovery.mdx @@ -10,27 +10,17 @@ Backup Strategy Schema Defines backup methods for disaster recovery. - **full**: Complete snapshot of all data - - **incremental**: Only changes since last backup - - **differential**: All changes since last full backup @example - ```typescript - -const backup: BackupConfig = \{ - -strategy: 'incremental', - -schedule: '0 2 * * *', - -retention: \{ days: 30, minCopies: 3 \}, - -encryption: \{ enabled: true, algorithm: 'AES-256-GCM' \}, - -\}; - +const backup: BackupConfig = { + strategy: 'incremental', + schedule: '0 2 * * *', + retention: { days: 30, minCopies: 3 }, + encryption: { enabled: true, algorithm: 'AES-256-GCM' }, +}; ``` diff --git a/content/docs/references/system/email-config.mdx b/content/docs/references/system/email-config.mdx index e7456e97af..38e92c8b33 100644 --- a/content/docs/references/system/email-config.mdx +++ b/content/docs/references/system/email-config.mdx @@ -8,45 +8,29 @@ description: Email Config protocol schemas Email Service Configuration Protocol Operator-facing configuration that selects the outbound email - transport for the EmailServicePlugin. Provider is a `provider` tag - + provider-specific settings; concrete `IEmailTransport` - implementations live in `@objectstack/plugin-email/transports/*`. Resolution order in `serve.ts`: - -1. `config.email.*` from objectstack.config.ts - -2. `OS_EMAIL_*` environment variables (override per setting) - -3. Default → provider='log' (LogTransport, no real send) + 1. `config.email.*` from objectstack.config.ts + 2. `OS_EMAIL_*` environment variables (override per setting) + 3. Default → provider='log' (LogTransport, no real send) `appName` is the one key whose env layer is not `OS_EMAIL_*` — it is - `OS_APP_NAME`, because the same product name names the whole deployment, - not just its mail. Every key here is one `resolveEmailCapabilityArg` reads (the single reader - of `config.email`); the schema is the operator-facing contract for that - function, so a key the runtime honours and this object omits is a type - error on a config that boots fine — the declared ≠ implemented gap of - #5104 (provider='smtp') and #5307 (queueDelivery / appName / - defaultTemplateContext), both times with the spec on the lagging side. SMTP delivery is built in (ADR-0012): select it with provider='smtp' - and supply the connection through `options` (host / port / secure / - user / password) or the matching OS_EMAIL_SMTP_HOST / _PORT / - _SECURE / _USER / _PASSWORD environment variables. diff --git a/content/docs/references/system/email-template.mdx b/content/docs/references/system/email-template.mdx index 4d51837059..bc1943d1d6 100644 --- a/content/docs/references/system/email-template.mdx +++ b/content/docs/references/system/email-template.mdx @@ -8,23 +8,15 @@ description: Email Template protocol schemas Email Template Metadata Protocol Declarative template definition consumed by `IEmailService.sendTemplate()` - to render outbound mail. Persisted as rows of `sys_email_template` so - administrators can author/edit/translate templates in Studio without - shipping code, and tenants can override the built-in defaults - (`allowOrgOverride: true` in the metadata registry). Aligned with Salesforce `EmailTemplate` and ServiceNow - `sysevent_email_action` conventions: a single named template is - resolved by `(name, locale)`; subject/body strings carry simple - -`\{\{path.to.value\}\}` placeholders rendered against a per-send - +`{{path.to.value}}` placeholders rendered against a per-send `data` payload. diff --git a/content/docs/references/system/encryption.mdx b/content/docs/references/system/encryption.mdx index b44540427a..2ff91f82d8 100644 --- a/content/docs/references/system/encryption.mdx +++ b/content/docs/references/system/encryption.mdx @@ -6,7 +6,6 @@ description: Encryption protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Field-level encryption protocol - GDPR/HIPAA/PCI-DSS compliant diff --git a/content/docs/references/system/environment-artifact.mdx b/content/docs/references/system/environment-artifact.mdx index 3866d1de13..007e251a84 100644 --- a/content/docs/references/system/environment-artifact.mdx +++ b/content/docs/references/system/environment-artifact.mdx @@ -8,69 +8,44 @@ description: Environment Artifact protocol schemas # Environment Artifact Envelope THE single declaration of the environment artifact envelope (#4740, - #4535 C10 — maintainer route A′). `@objectstack/spec/cloud` re-exports - this file; both entry points resolve to these exact symbols, so the - chosen entry point can never change the shape a consumer gets (the - #4411 dual-source trap, closed for this name). Describes the response shape of - `GET /api/v1/cloud/environments/:environmentId/artifact` — the assembled - artifact ObjectOS pulls from the control plane, and the shape the runtime - metadata loader parses at boot (`packages/metadata/src/plugin.ts`, - `_parseAndRegisterArtifact` — the one runtime Zod parse of this envelope). Distinct from the marketplace `PackageArtifactSchema` (a .tgz file - listing). This envelope wraps the compiled `ObjectStackDefinitionSchema` - produced by `objectstack compile` together with control-plane assigned - identity (`commitId`, `checksum`). ## Boundary - **Artifact (this schema):** compiled environment metadata plus - -provenance. Immutable, content-addressable via `commitId` and - -`checksum`. - + provenance. Immutable, content-addressable via `commitId` and + `checksum`. - **Deployment Config (NOT in this schema):** business DB coordinates, - -credentials, environment identity, secrets. Injected at runtime. + credentials, environment identity, secrets. Injected at runtime. See `content/docs/concepts/north-star.mdx` §6.3 for the - runtime-inputs boundary. ## History (#4740) This file previously documented a richer "v0" envelope — a - -`\{ algorithm, value \}` checksum object, a category-bag `metadata`, inlined - +`{ algorithm, value }` checksum object, a category-bag `metadata`, inlined `functions[]`, a required plugin/driver `manifest`, and a reserved - `payloadRef` indirection — that NO producer or consumer ever implemented: - `objectstack compile` ships function code as standalone runtime modules - referenced from the compiled definition, and the control plane has always - served the wire shape below (string SHA-256 checksum, `metadata` = the - compiled definition). The declaration converged to the live wire shape; - the never-implemented keys are tombstoned below (ADR-0049 - enforce-or-remove: declared = enforced, or absent). diff --git a/content/docs/references/system/http-server.mdx b/content/docs/references/system/http-server.mdx index 6b3e15e956..839e78dcce 100644 --- a/content/docs/references/system/http-server.mdx +++ b/content/docs/references/system/http-server.mdx @@ -10,11 +10,8 @@ HTTP Server Protocol Route-registration metadata, middleware declaration and the server-side lifecycle/status vocabulary for HTTP server implementations (Express, Fastify, Hono, etc.) Architecture alignment: - - Kubernetes: Service and Ingress resources - - AWS: API Gateway configuration - - Spring Boot: Application properties diff --git a/content/docs/references/system/incident-response.mdx b/content/docs/references/system/incident-response.mdx index 7acef04b21..fdb134cb32 100644 --- a/content/docs/references/system/incident-response.mdx +++ b/content/docs/references/system/incident-response.mdx @@ -8,9 +8,7 @@ description: Incident Response protocol schemas Incident Response Protocol — ISO 27001:2022 (A.5.24–A.5.28) Defines schemas for information security event management including - incident classification, severity grading, response procedures, - and notification matrices. See also: https://www.iso.org/standard/27001 diff --git a/content/docs/references/system/job.mdx b/content/docs/references/system/job.mdx index 3131b4335e..99afb5395d 100644 --- a/content/docs/references/system/job.mdx +++ b/content/docs/references/system/job.mdx @@ -6,7 +6,6 @@ description: Job protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Cron Schedule Schema - Schedule jobs using cron expressions diff --git a/content/docs/references/system/logging.mdx b/content/docs/references/system/logging.mdx index 996638575c..2db2223967 100644 --- a/content/docs/references/system/logging.mdx +++ b/content/docs/references/system/logging.mdx @@ -8,17 +8,11 @@ description: Logging protocol schemas Logging Protocol - Comprehensive Observability Logging Unified logging protocol that combines: - - Basic kernel logging (LoggerConfig) - - Enterprise-grade features (LoggingConfig) - - Multiple log destinations (file, console, external services) - - Structured logging with enrichment - - Log aggregation and forwarding - - Integration with external log management systems diff --git a/content/docs/references/system/message-queue.mdx b/content/docs/references/system/message-queue.mdx index 7af9060a4f..c43c627abb 100644 --- a/content/docs/references/system/message-queue.mdx +++ b/content/docs/references/system/message-queue.mdx @@ -6,7 +6,6 @@ description: Message Queue protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Message queue protocol for async communication - Supports Kafka, RabbitMQ, AWS SQS, Redis Pub/Sub diff --git a/content/docs/references/system/metadata-persistence.mdx b/content/docs/references/system/metadata-persistence.mdx index 331b5fe235..498080f54b 100644 --- a/content/docs/references/system/metadata-persistence.mdx +++ b/content/docs/references/system/metadata-persistence.mdx @@ -6,7 +6,6 @@ description: Metadata Persistence protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Metadata Scope Enum - Defines the lifecycle and mutability of a metadata item. diff --git a/content/docs/references/system/metrics.mdx b/content/docs/references/system/metrics.mdx index aefcea7974..536c01cf7c 100644 --- a/content/docs/references/system/metrics.mdx +++ b/content/docs/references/system/metrics.mdx @@ -8,15 +8,10 @@ description: Metrics protocol schemas Metrics Protocol - Performance and Operational Metrics Comprehensive metrics collection and monitoring: - - Counter, Gauge, Histogram, Summary metric types - - Time-series data collection - - SLI/SLO definitions - - Metric aggregation and export - - Integration with monitoring systems (Prometheus, etc.) diff --git a/content/docs/references/system/migration.mdx b/content/docs/references/system/migration.mdx index d27cb46ab3..23b19d3b7a 100644 --- a/content/docs/references/system/migration.mdx +++ b/content/docs/references/system/migration.mdx @@ -8,23 +8,15 @@ description: Migration protocol schemas Migration protocol — the two kinds of migration, kept apart on purpose. A **schema migration** (`ChangeSet` + its atomic operations) reshapes the - physical database to match metadata: add a field, change a type, create an - object, run SQL. It is derivable from the metadata and applied by - `os migrate plan` / `os migrate apply`. A **data migration** rewrites the rows themselves, and whether it is done is - a property of ONE DEPLOYMENT's database rather than of the installed code - version — so it cannot be expressed as a ChangeSet, and its completion cannot - be inferred from a release. `DataMigrationFlag` is the per-deployment record - that one ran here and its self-check passed; consumers that would act - irreversibly on migrated data gate on the flag instead of the version. diff --git a/content/docs/references/system/object-storage.mdx b/content/docs/references/system/object-storage.mdx index 8093e01d42..09598c58f5 100644 --- a/content/docs/references/system/object-storage.mdx +++ b/content/docs/references/system/object-storage.mdx @@ -8,21 +8,13 @@ description: Object Storage protocol schemas Object Storage Protocol Unified storage protocol that combines: - - Object storage systems (S3, Azure Blob, GCS, MinIO) - - Scoped storage configuration (temp, cache, data, logs, config, public) - - Multi-cloud storage providers - - Bucket/container configuration - - Access control and permissions - - Lifecycle policies for data retention - - Presigned URLs for secure direct access - - Multipart uploads for large files diff --git a/content/docs/references/system/registry-config.mdx b/content/docs/references/system/registry-config.mdx index 8b66bacd3a..c889110d44 100644 --- a/content/docs/references/system/registry-config.mdx +++ b/content/docs/references/system/registry-config.mdx @@ -8,7 +8,6 @@ description: Registry Config protocol schemas # Registry Configuration Protocol Defines the configuration for the ObjectStack Registry Service. - Includes federation, synchronization, and storage settings. diff --git a/content/docs/references/system/search-engine.mdx b/content/docs/references/system/search-engine.mdx index 05d9b17d55..feaba3219d 100644 --- a/content/docs/references/system/search-engine.mdx +++ b/content/docs/references/system/search-engine.mdx @@ -6,7 +6,6 @@ description: Search Engine protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Full-text search protocol - Supports Elasticsearch, Algolia, Meilisearch, Typesense diff --git a/content/docs/references/system/security-context.mdx b/content/docs/references/system/security-context.mdx index c68d888666..6465bcdd61 100644 --- a/content/docs/references/system/security-context.mdx +++ b/content/docs/references/system/security-context.mdx @@ -8,29 +8,18 @@ description: Security Context protocol schemas Unified Security Context Protocol Provides a central governance layer that correlates and unifies - the four independent security subsystems it was designed against. Three of - the four have since been REMOVED per ADR-0056 D8 (declared-but-never-enforced; - see system/index.ts notes) — only encryption survives, marked experimental: - - **Audit** (audit.zod.ts — REMOVED): the live audit path is plugin-audit's - -always-on capture + object/field `trackHistory` + lifecycle `audit` retention - + always-on capture + object/field `trackHistory` + lifecycle `audit` retention - **Encryption** (encryption.zod.ts): Field-level encryption and key management - - **Compliance** (compliance.zod.ts — REMOVED): GDPR/HIPAA/SOX/PCI-DSS configs - - **Masking** (masking.zod.ts — REMOVED): PII data masking and tokenization This schema enforces cross-cutting security policies, ensuring compliance - frameworks drive encryption requirements, masking rules respect role-based - audit visibility, and all security operations are correlated in a single - governance context. See also: https://www.iso.org/standard/27001 diff --git a/content/docs/references/system/settings-client.mdx b/content/docs/references/system/settings-client.mdx index 85c7188622..b3b589a8fe 100644 --- a/content/docs/references/system/settings-client.mdx +++ b/content/docs/references/system/settings-client.mdx @@ -8,52 +8,36 @@ description: Settings Client protocol schemas SettingsClient — reactive consumer contract for runtime settings. Background. Phase 0 introduced the cascade scope (`env > global > - tenant > user > default`) and made `SettingsService.get()` resolve - across the chain. Consumers (e.g. EmailServicePlugin, BrandingPlugin) - still pull values once at boot, which means saved changes never take - effect without a process restart. This module defines the contract that fixes that: -const mail = ctx.settings.bind('mail', MailSettingsSchema); + const mail = ctx.settings.bind('mail', MailSettingsSchema); + mail.current.smtp_host; // current effective value + const off = mail.onChange(() => rebuild()); -mail.current.smtp_host; // current effective value - -const off = mail.onChange(() => rebuild()); - -service.set('mail', 'smtp_host', '…') // ↳ fires settings:changed - -// → handler runs → transport rebuilt + service.set('mail', 'smtp_host', '…') // ↳ fires settings:changed + // → handler runs → transport rebuilt Design rules: -1. **Spec-only.** This package emits types and Zod shapes. No runtime - -wiring (event bus, in-memory cache) lives here — that is - -`@objectstack/service-settings`'s job. Keeping the contract pure - -lets non-Node consumers (RN, edge workers) re-implement it. - -2. **Snapshot semantics.** `current` is an immutable snapshot of the - -namespace at the moment of the last refresh. After a change event - -fires, the next read of `current` returns the new snapshot — old - -references stay stable (good for React useSyncExternalStore). - -3. **No fetching here.** A `SettingsClient` does not know how to fetch - -itself; it is constructed by an authority (the service) that owns + 1. **Spec-only.** This package emits types and Zod shapes. No runtime + wiring (event bus, in-memory cache) lives here — that is + `@objectstack/service-settings`'s job. Keeping the contract pure + lets non-Node consumers (RN, edge workers) re-implement it. -the persistence layer. That keeps cycles out of the dependency + 2. **Snapshot semantics.** `current` is an immutable snapshot of the + namespace at the moment of the last refresh. After a change event + fires, the next read of `current` returns the new snapshot — old + references stay stable (good for React useSyncExternalStore). -graph and lets us mock the client trivially in tests. + 3. **No fetching here.** A `SettingsClient` does not know how to fetch + itself; it is constructed by an authority (the service) that owns + the persistence layer. That keeps cycles out of the dependency + graph and lets us mock the client trivially in tests. **Source:** `packages/spec/src/system/settings-client.zod.ts` diff --git a/content/docs/references/system/settings-manifest.mdx b/content/docs/references/system/settings-manifest.mdx index f0d1b1d380..e4c9f5005b 100644 --- a/content/docs/references/system/settings-manifest.mdx +++ b/content/docs/references/system/settings-manifest.mdx @@ -8,30 +8,21 @@ description: Settings Manifest protocol schemas Settings Manifest Protocol Declarative description of a single namespace of platform settings - (e.g. `mail`, `branding`, `feature_flags`). Modelled on Apple's - `Settings.bundle/Root.plist` PreferenceSpecifiers — a small, closed - set of specifier types that the system-owned renderer turns into a - uniform Settings page. Storage for values is the generic `sys_setting` K/V table; manifests - themselves are NEVER persisted — they ship with plugin code. See ADR-0007 (Settings Manifest + K/V Store + Resolver). Resolution order (handled by `SettingsService.get`): - -1. process.env override (source='env', locked=true) - -2. sys_setting scope=tenant - -3. sys_setting scope=user - -4. manifest specifier.default + 1. process.env override (source='env', locked=true) + 2. sys_setting scope=tenant + 3. sys_setting scope=user + 4. manifest specifier.default **Source:** `packages/spec/src/system/settings-manifest.zod.ts` diff --git a/content/docs/references/system/stack-server.mdx b/content/docs/references/system/stack-server.mdx index 2d8582e960..541939bb0e 100644 --- a/content/docs/references/system/stack-server.mdx +++ b/content/docs/references/system/stack-server.mdx @@ -5,102 +5,63 @@ description: Stack Server protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} -`defineStack(\{ server \})` — the authorable server-facing configuration. +`defineStack({ server })` — the authorable server-facing configuration. ## Why this is NOT `HttpServerConfigSchema` -`[system/http-server.zod.ts](/docs/references/system/http-server)` used to declare nine keys (`port`, `host`, - +`system/http-server.zod.ts` used to declare nine keys (`port`, `host`, `cors`, `requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, - `trustProxy`). #4938 measured them: **none had a runtime reader and none was - reachable from any authoring surface** — `stack.zod.ts` had no `server:` key, - so the whole shape was unwritable as well as unread. Mounting it wholesale - here would have made eight dead keys authorable in one move, which is the - declared-≠-enforced defect (Prime Directive #10) manufactured on purpose. So this schema is deliberately NARROW: it carries only keys an executor - actually consumes, and it grows one key at a time, each arriving with its - consumer. Today that is exactly two: | key | consumed by | - |---|---| - | `security.rateLimit` | `createDispatcherPlugin` → the inbound token bucket (`@objectstack/runtime` `security/inbound-rate-limit.ts`) — an over-budget caller gets `429` + `Retry-After` | - | `trustProxy` | the same limiter's IP resolution — see below | The other seven `HttpServerConfigSchema` keys were RETIRED with the shape - that carried them (#4938, ADR-0049 enforce-or-remove): unreachable *and* - unread, they were the cleanest remove candidate in the ledger, and their - prescriptions now live in the `guidance` maps below — the only place an - author can write a server key is also the only place that has to answer for - one. Adding one here without an executor re-opens the hole this narrowness - exists to close. `cors` is the registered exception-in-waiting: the 2026-08-04 ruling named it - the FIRST per-key admission candidate for this shape, because embedding - (`example-embed-objectql`) is a real scenario with real pull. When that work - is scheduled it arrives the #4910 way — key and executor in one change — not - by un-retiring a declaration. ## What `server:` is NOT for **Deployment knobs stay on the CLI.** There is no `server.port` / `server.host` - on purpose: the listening socket is a property of *where* a stack runs, not of - the stack itself, and it is already owned by `objectstack serve -p ` / - `PORT`. Two authorities for one number is how a config becomes advisory. If a - future need does add `server.port`, the precedence is settled in advance and - recorded here so it cannot be re-litigated per-caller: **the CLI flag wins over - `server:`, and `server:` wins over the built-in default** — an operator - overriding a port at the command line must not be silently overruled by a file - baked into the artifact. Related: #4910 (this seam), #4937 (the limiter that documented an execution - chain it never had), #4936 (the declarative `apis:` surface as it stood while - nothing executed it: vocabulary kept, a non-empty array rejected outright) and - #5040 — the executor that ended that state. From protocol 17 a declared - endpoint is LIVE behind five per-endpoint publish gates, and its own - `rateLimit` is enforced by the endpoint policy chain against a bucket keyed in - a separate namespace, so an endpoint budget and the server-level budget - declared here meter INDEPENDENTLY rather than sharing a counter. The upgrade - checklist for that flip is the `declarative-apis-endpoints-live` entry of the - protocol upgrade guide. ADR-0069 D2 (shared counters), ADR-0049 (enforce or - remove). diff --git a/content/docs/references/system/supplier-security.mdx b/content/docs/references/system/supplier-security.mdx index 65b45f1ab4..e3d8aec3ea 100644 --- a/content/docs/references/system/supplier-security.mdx +++ b/content/docs/references/system/supplier-security.mdx @@ -8,7 +8,6 @@ description: Supplier Security protocol schemas Supplier Security Protocol — ISO 27001:2022 (A.5.19–A.5.22) Defines schemas for supplier information security management including - risk assessment, security requirements, monitoring, and change control. See also: https://www.iso.org/standard/27001 diff --git a/content/docs/references/system/tenant.mdx b/content/docs/references/system/tenant.mdx index 8c94ea9aac..f4595d5c80 100644 --- a/content/docs/references/system/tenant.mdx +++ b/content/docs/references/system/tenant.mdx @@ -8,17 +8,12 @@ description: Tenant protocol schemas Tenant Schema (Multi-Tenant Architecture) Defines the tenant/tenancy model for ObjectStack SaaS deployments. - Supports different levels of data isolation to meet varying security, - performance, and compliance requirements. Isolation Levels: - - shared_schema: All tenants share the same database and schema (row-level isolation) - - isolated_schema: Tenants have separate schemas within a shared database - - isolated_db: Each tenant has a completely separate database diff --git a/content/docs/references/system/tracing.mdx b/content/docs/references/system/tracing.mdx index 70467cbade..06ea56e116 100644 --- a/content/docs/references/system/tracing.mdx +++ b/content/docs/references/system/tracing.mdx @@ -8,15 +8,10 @@ description: Tracing protocol schemas Tracing Protocol - Distributed Tracing & Observability Comprehensive distributed tracing based on OpenTelemetry standards: - - Trace context propagation - - Span creation and management - - Sampling strategies - - Integration with tracing backends (Jaeger, Zipkin, etc.) - - W3C Trace Context standard compliance diff --git a/content/docs/references/system/training.mdx b/content/docs/references/system/training.mdx index 8a7cee4650..67298b818a 100644 --- a/content/docs/references/system/training.mdx +++ b/content/docs/references/system/training.mdx @@ -8,7 +8,6 @@ description: Training protocol schemas Information Security Training Protocol — ISO 27001:2022 (A.6.3) Defines schemas for security awareness and training management including - course definitions, completion tracking, and organizational training plans. See also: https://www.iso.org/standard/27001 diff --git a/content/docs/references/system/worker.mdx b/content/docs/references/system/worker.mdx index e0a27182a8..ded20a5c4d 100644 --- a/content/docs/references/system/worker.mdx +++ b/content/docs/references/system/worker.mdx @@ -8,47 +8,28 @@ description: Worker protocol schemas Worker System Protocol Background task processing system with queues, priorities, and retry logic. - Provides a robust foundation for async task execution similar to: - - Sidekiq (Ruby) - - Celery (Python) - - Bull/BullMQ (Node.js) - - AWS SQS/Lambda Features: - - Task queues with priorities - - Task scheduling and retry logic - - Batch processing - - Dead letter queues - - Task monitoring and logging @example Basic task - ```typescript - -const task: Task = \{ - -id: 'task-123', - -type: 'send_email', - -payload: \{ to: 'user@example.com', subject: 'Welcome' \}, - -queue: 'notifications', - -priority: 5 - -\}; - +const task: Task = { + id: 'task-123', + type: 'send_email', + payload: { to: 'user@example.com', subject: 'Welcome' }, + queue: 'notifications', + priority: 5 +}; ``` diff --git a/content/docs/references/ui/action-params.mdx b/content/docs/references/ui/action-params.mdx index 724d27fc4d..56d4f28a30 100644 --- a/content/docs/references/ui/action-params.mdx +++ b/content/docs/references/ui/action-params.mdx @@ -6,41 +6,27 @@ description: Action Params protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} The action DISPATCH contract: what the platform validates on the way in, and - what it hands the handler on the way out. Two halves, one surface. **Inbound** — action-param VALUE validation - (ADR-0104 D2), below. **Outbound** — the runtime context an action body / - handler receives: `ActionSessionSchema` (the `ctx.session` contract, - #5697), `ActionEngineFacade`, `ActionHandlerContext` and - `ActionHandler`. ## Inbound — action-param VALUE validation (ADR-0104 D2) An action's declared `params[]` is a complete value contract — `type`, - `required`, `multiple`, `options`, `reference` — but before this it only - informed the client dialog: the server passed `reqBody.params` straight to - the handler, unvalidated (`http-dispatcher.ts`). This module is the pure - contract that lets the REST and MCP dispatch paths enforce that declaration - BEFORE the handler runs, reusing the D1 field value-shape contract - (`valueSchemaFor`). Purity: schema derivation only (Prime Directive #2). Field-backed params are - resolved to their effective value-shape inputs by the CALLER (the runtime, - which holds the object metadata registry); this module validates the already - resolved descriptors. diff --git a/content/docs/references/ui/action.mdx b/content/docs/references/ui/action.mdx index 89934e7481..d7967f37c8 100644 --- a/content/docs/references/ui/action.mdx +++ b/content/docs/references/ui/action.mdx @@ -12,47 +12,30 @@ Defines inputs required before executing an action. Two declaration modes: 1. **Field-backed** (preferred) — reference an existing object field; the + runtime resolves the field's label (i18n), type, validation rules, + options, placeholder, help text, and widget mapping from object + metadata. Cross-object references use `objectOverride`. -runtime resolves the field's label (i18n), type, validation rules, - -options, placeholder, help text, and widget mapping from object - -metadata. Cross-object references use `objectOverride`. - -```ts - -params: [ - -\{ field: 'email' \}, // same object - -\{ field: 'role', objectOverride: 'sys_member' \}, // different object - -] - -``` + ```ts + params: [ + { field: 'email' }, // same object + { field: 'role', objectOverride: 'sys_member' }, // different object + ] + ``` 2. **Inline** (legacy / bespoke) — declare `name`, `label`, `type` etc. + inline when no matching object field exists. Inline values may also be + used alongside `field` to override individual properties. A `lookup` / + `master_detail` param declared this way MUST name its target object via + `reference` — there is no field to inherit it from: -inline when no matching object field exists. Inline values may also be - -used alongside `field` to override individual properties. A `lookup` / - -`master_detail` param declared this way MUST name its target object via - -`reference` — there is no field to inherit it from: - -```ts - -params: [ - -\{ name: 'inspector', label: 'Inspector', type: 'lookup', reference: 'sys_user' \}, - -] - -``` + ```ts + params: [ + { name: 'inspector', label: 'Inspector', type: 'lookup', reference: 'sys_user' }, + ] + ``` `name` is required unless `field` is provided (in which case it defaults - to the field name and is used as the request-body key). diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index e92d451e38..74a4873ef2 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -6,25 +6,18 @@ description: App protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Base Navigation Item Schema - Shared properties for all navigation types. **NAMING CONVENTION:** - Navigation item IDs are used in URLs and configuration and must be lowercase snake_case. @example Good IDs - - 'menu_accounts' - - 'page_dashboard' - - 'nav_settings' @example Bad IDs (will be rejected) - - 'MenuAccounts' (PascalCase) - - 'Page Dashboard' (spaces) diff --git a/content/docs/references/ui/bulk-action.mdx b/content/docs/references/ui/bulk-action.mdx index 5b150d454e..d2c43db4d5 100644 --- a/content/docs/references/ui/bulk-action.mdx +++ b/content/docs/references/ui/bulk-action.mdx @@ -8,19 +8,13 @@ description: Bulk Action protocol schemas Bulk Action Schemas The vocabulary of a list view's `bulkActionDefs` — one entry per button in - the multi-select toolbar. Use a def for a mass data-plane mutation that no - action expresses (`operation: 'update'` with a patch, or `'delete'`), or for - an `operation: 'custom'` + `execution: 'aggregate'` entry that dispatches the - action it NAMES once for the whole selection. For the per-record dispatch, name the action in the view's - `bulkActions: ['']` instead — the bare-string form, promoted with the - action's own label, params and `visible`. diff --git a/content/docs/references/ui/chart.mdx b/content/docs/references/ui/chart.mdx index ef18f9b7e4..2f70dedb7e 100644 --- a/content/docs/references/ui/chart.mdx +++ b/content/docs/references/ui/chart.mdx @@ -8,7 +8,6 @@ description: Chart protocol schemas Unified Chart Type Taxonomy Shared by Dashboard and Report widgets. - Provides a comprehensive set of chart types for data visualization. diff --git a/content/docs/references/ui/dataset.mdx b/content/docs/references/ui/dataset.mdx index a082e3f302..265449a2de 100644 --- a/content/docs/references/ui/dataset.mdx +++ b/content/docs/references/ui/dataset.mdx @@ -8,35 +8,22 @@ description: Dataset protocol schemas Analytics Dataset — the one semantic layer (ADR-0021). A `dataset` is a named, reusable analytical definition: a base object, the - relationships to include (joins are *derived* from the object graph — the - author never writes an `ON` clause), and the declared **dimensions** - (groupable axes) and **measures** (aggregatable values). It is deliberately - SMALLER than `QuerySchema`: no raw SQL, no hand-authored join predicates, - no window/having grammar in the author surface. Presentations (`report` / `dashboard`) bind to a dataset by reference and - pick dimensions/measures *by name*. The dataset compiles to the existing - Cube analytics runtime (ADR-0021 D-A=(c)); RLS / tenant scoping is enforced - by the runtime per joined object (D-C), never declared here. Naming: this module owns the high-prior `dataset` / `dimension` / `measure` - vocabulary (LookML / dbt / Cube / PowerBI). The Zod export identifiers are - `Dataset`-prefixed (`DatasetDimensionSchema`, `DatasetMeasureSchema`) so they - do not clash with the Cube layer's `DimensionSchema` / `MetricSchema` in - -`[data/analytics.zod.ts](/docs/references/data/analytics)` while the two layers coexist (Phase 1). The Cube - +`data/analytics.zod.ts` while the two layers coexist (Phase 1). The Cube layer is absorbed/retired in a later phase (D-A). diff --git a/content/docs/references/ui/i18n.mdx b/content/docs/references/ui/i18n.mdx index 3e2e1b6813..3ddf259d97 100644 --- a/content/docs/references/ui/i18n.mdx +++ b/content/docs/references/ui/i18n.mdx @@ -6,23 +6,15 @@ description: I18n protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} I18n Object Schema - Structured internationalization label with translation key and parameters. @example - ```typescript - -const label: I18nObject = \{ - -key: 'views.task_list.label', - -defaultValue: 'Task List', - -params: \{ count: 5 \}, - -\}; - +const label: I18nObject = { + key: 'views.task_list.label', + defaultValue: 'Task List', + params: { count: 5 }, +}; ``` diff --git a/content/docs/references/ui/notification.mdx b/content/docs/references/ui/notification.mdx index 155317e62e..c1cebd582d 100644 --- a/content/docs/references/ui/notification.mdx +++ b/content/docs/references/ui/notification.mdx @@ -6,7 +6,6 @@ description: Notification protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Notification Type Schema - Defines the visual presentation style of the notification. diff --git a/content/docs/references/ui/page.mdx b/content/docs/references/ui/page.mdx index c4f58ec0ea..d055e8f864 100644 --- a/content/docs/references/ui/page.mdx +++ b/content/docs/references/ui/page.mdx @@ -6,7 +6,6 @@ description: Page protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Page Region Schema - A named region in the template where components are dropped. diff --git a/content/docs/references/ui/sharing.mdx b/content/docs/references/ui/sharing.mdx index 6717d81456..5564774d53 100644 --- a/content/docs/references/ui/sharing.mdx +++ b/content/docs/references/ui/sharing.mdx @@ -10,39 +10,24 @@ description: Sharing protocol schemas Sharing & Embedding Protocol Public-link sharing of a form view. The module name is plural for historical - reasons: it once held two shapes in **opposite** postures, and #4001 批 14 - measured them per SCHEMA rather than assuming a file-level verdict — - `SharingConfigSchema` a live authoring door, `EmbedConfigSchema` no door at - all. That measurement is what let the two be disposed of separately, and the - asymmetry survives as the reason this file reads the way it does: - `SharingConfigSchema` has a **live authoring door**. `FormViewSchema.sharing` - -carries it (`view.zod.ts`), `view` is a metadata-type root, and the runtime - -really reads it: `rest-server.ts` mounts the anonymous form endpoints only - -when `sharing.allowAnonymous === true` and a `sharing.publicLink` slug - -matches. Both example apps author it (`app-showcase` `inquiry.view.ts`, - -`app-crm` `lead.view.ts`). It is `strictObject` as of #4001 批 14. - + carries it (`view.zod.ts`), `view` is a metadata-type root, and the runtime + really reads it: `rest-server.ts` mounts the anonymous form endpoints only + when `sharing.allowAnonymous === true` and a `sharing.publicLink` slug + matches. Both example apps author it (`app-showcase` `inquiry.view.ts`, + `app-crm` `lead.view.ts`). It is `strictObject` as of #4001 批 14. - `EmbedConfigSchema` was **REMOVED** at #5015 (ADR-0049 enforce-or-remove) — - -see the block below where it stood. + see the block below where it stood. The ledger's classification question is *"who writes this schema's input?"*, - and it is answered per SCHEMA, not per file; before 批 14 this file's row - carried one verdict for both, and a file-level verdict would have been wrong - in one direction or the other whichever way it fell. diff --git a/content/docs/references/ui/theme.mdx b/content/docs/references/ui/theme.mdx index c2963ede7f..be94799318 100644 --- a/content/docs/references/ui/theme.mdx +++ b/content/docs/references/ui/theme.mdx @@ -6,7 +6,6 @@ description: Theme protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} Color Palette Schema - Defines brand colors and their variants. diff --git a/content/docs/references/ui/view.mdx b/content/docs/references/ui/view.mdx index 1a80aa763f..103ebb5534 100644 --- a/content/docs/references/ui/view.mdx +++ b/content/docs/references/ui/view.mdx @@ -6,7 +6,6 @@ description: View protocol schemas {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} HTTP Method Enum & HTTP Request Schema - Migrated to [shared/http.zod.ts](/docs/references/shared/http). Re-exported here for backward compatibility. diff --git a/content/docs/references/ui/widget.mdx b/content/docs/references/ui/widget.mdx index 329aca26a6..043b236c19 100644 --- a/content/docs/references/ui/widget.mdx +++ b/content/docs/references/ui/widget.mdx @@ -8,7 +8,6 @@ description: Widget protocol schemas Widget Lifecycle Hooks Schema Defines lifecycle callbacks for custom widgets inspired by Web Components and React. - These hooks allow widgets to perform initialization, cleanup, and respond to changes. See also: https://developer.mozilla.org/en-US/docs/Web/API/Web_components @@ -16,25 +15,15 @@ See also: https://developer.mozilla.org/en-US/docs/Web/API/Web_components See also: https://react.dev/reference/react/Component#component-lifecycle @example - ```typescript - -const widget = \{ - -lifecycle: \{ - -onMount: "console.log('Widget mounted')", - -onUpdate: "if (prevProps.value !== props.value) \{ updateUI() \}", - -onUnmount: "cleanup()", - -onValidate: "return value.length > 0 ? null : 'Required field'" - -\} - -\} - +const widget = { + lifecycle: { + onMount: "console.log('Widget mounted')", + onUpdate: "if (prevProps.value !== props.value) { updateUI() }", + onUnmount: "cleanup()", + onValidate: "return value.length > 0 ? null : 'Required field'" + } +} ``` diff --git a/packages/spec/scripts/escape-mdx.test.ts b/packages/spec/scripts/escape-mdx.test.ts index 3510e652ef..5427e48435 100644 --- a/packages/spec/scripts/escape-mdx.test.ts +++ b/packages/spec/scripts/escape-mdx.test.ts @@ -117,8 +117,20 @@ describe('escapeMdxDescription — shapes the fix must not disturb', () => { * carry an unbalanced one inside a code span (`record.amount < 0`, * `>=1.2.3`) — 11 such spans, all correct. Nesting for angles is pinned by * the positive unit case above instead. Backslash-escaped delimiters are not - * delimiters: the module-JSDoc path escapes braces as `\{`, so they are - * dropped before counting. + * delimiters and are dropped before counting; that strip no longer has + * anything to do in a span (#5553 stopped the module-JSDoc path escaping + * braces inside code, where the backslash was content rather than an escape) + * but it still guards the invariant against a future path that does. + * + * A span is extracted per PARAGRAPH, not per line. It used to be per line, + * which was only ever right by accident: the module-JSDoc path made every + * source line its own paragraph, so no span could span lines. #5553 restored + * that layout, and a span may now wrap — `automation/flow-function` opens one + * on one line and closes it on the next. Splitting per line saw the opening + * half alone (`` `update_record fields: {` ``) and read a wrapped span as an + * unbalanced one. A blank line is still a hard boundary, since a code span + * cannot cross one, so the paragraph is the correct unit — and #5452's own + * defect, a pair cut in half WITHIN one line, is reported exactly as before. */ describe('published reference pages keep inline-code braces balanced (#5452)', () => { const pages: string[] = []; @@ -139,18 +151,34 @@ describe('published reference pages keep inline-code braces balanced (#5452)', ( const offenders: string[] = []; for (const file of pages) { const rel = path.relative(REPO, file); - fs.readFileSync(file, 'utf-8') - .split('\n') - .forEach((line, index) => { - // Odd segments of a backtick split are the inline-code spans. - const segments = line.split('`'); - for (let i = 1; i < segments.length; i += 2) { - const span = segments[i].replace(/\\[{}]/g, ''); - const opens = span.split('{').length - 1; - const closes = span.split('}').length - 1; - if (opens !== closes) offenders.push(`${rel}:${index + 1} \`${segments[i]}\``); - } - }); + // Fenced blocks are not inline code and their braces are not ours to + // balance, so blank them before the paragraphs are cut. + let fenced = false; + const prose = fs.readFileSync(file, 'utf-8').split('\n').map(line => { + if (/^\s*(?:`{3,}|~{3,})/.test(line)) { fenced = !fenced; return ''; } + return fenced ? '' : line; + }); + + let buffer: string[] = []; + let start = 0; + const check = () => { + if (!buffer.length) return; + // Odd segments of a backtick split are the inline-code spans. + const segments = buffer.join('\n').split('`'); + for (let i = 1; i < segments.length; i += 2) { + const span = segments[i].replace(/\\[{}]/g, ''); + const opens = span.split('{').length - 1; + const closes = span.split('}').length - 1; + if (opens !== closes) offenders.push(`${rel}:${start + 1} \`${segments[i]}\``); + } + buffer = []; + }; + prose.forEach((line, index) => { + if (line.trim() === '') { check(); return; } + if (!buffer.length) start = index; + buffer.push(line); + }); + check(); } expect(offenders).toEqual([]); }); diff --git a/packages/spec/scripts/file-description.test.ts b/packages/spec/scripts/file-description.test.ts index 52e60e65bc..e1ce35b777 100644 --- a/packages/spec/scripts/file-description.test.ts +++ b/packages/spec/scripts/file-description.test.ts @@ -221,13 +221,9 @@ describe('renderFileDescription', () => { // Rendering is unchanged by #5059 — only the block SELECTION moved. These // two assertions exist so the extraction is provably behaviour-preserving. // - // The `{@link }` (untitled) form is deliberately NOT asserted here: - // the untitled branch emits `[path](route)` and the bare-source-path - // rewriter two lines below then matches the path INSIDE the link text and - // wraps it again, so the published output is a link nested in a link. That - // is a pre-existing defect of the rendering chain, live on `main` in - // `automation/etl.mdx:54` and `integration/connector.mdx:102`; filed - // separately rather than pinned here, because pinning it would ratify it. + // #6136 has since fixed the untitled `{@link }` form this case used + // to steer around; it is pinned on its own below rather than folded in + // here, so this case keeps testing exactly what it was written to test. const source = [ '/**', ' * Header referencing {@link ../automation/sync.zod.ts | the sync protocol}', @@ -243,6 +239,230 @@ describe('renderFileDescription', () => { }); }); +/** + * #5553 — the block is rendered as the markdown it was written as. + * + * The renderer used to drop blank lines and join what was left with `\n\n`, + * making every SOURCE LINE its own paragraph. Anything that legitimately wraps + * across lines was cut in half by a paragraph boundary, and an inline code span + * cannot cross one, so both backticks fell out as literal text on five + * published pages. + */ +describe('renderFileDescription — #5553: line layout is content, not decoration', () => { + const ctx = { sourcePathToDocsRoute: () => null }; + + it('keeps an inline code span that wraps across two source lines', () => { + // `automation/flow-function.zod.ts:13-15`, reduced — the example the issue + // opened with. Published as two paragraphs, one starting with a stray + // backtick and the next ending with one. + const source = [ + '/**', + ' * A later node persists it (`update_record fields: {', + " * ai_category: '{aiResult.ai_category}' }`). Done.", + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + "A later node persists it (`update_record fields: {\n ai_category: '{aiResult.ai_category}' }`). Done.", + ); + }); + + it('does not escape braces inside an inline code span, and still does outside one', () => { + // A code span renders its content literally, so a backslash there is not an + // escape character — it is a backslash the reader sees. `shared/expression` + // published `` `\{ dialect, source \}` `` for exactly this reason. + const source = [ + '/**', + ' * The persisted form is `{ dialect, source }`, written {inline} in prose.', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + 'The persisted form is `{ dialect, source }`, written \\{inline\\} in prose.', + ); + }); + + it('leaves a fenced code block alone — no paragraph splitting, no escaping', () => { + const source = [ + '/**', + ' * Example:', + ' *', + ' * ```ts', + ' * const a = { b: 1 };', + ' *', + ' * const c = 2;', + ' * ```', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + ['Example:', '', '```ts', 'const a = { b: 1 };', '', 'const c = 2;', '```'].join('\n'), + ); + }); + + it('re-emits an indented (4-space) code block as a fenced one', () => { + // `data/date-macros.zod.ts` and `data/context-tokens.zod.ts` write their + // placeholder examples this way, and they are almost entirely braces. + // + // The fence is not cosmetic. MDX dropped CommonMark's indented code blocks + // so indentation could lay out JSX, so an indented block reaches MDX as + // ordinary prose — and unescaped braces in prose are an expression. Left + // indented, both pages fail to compile with "Could not parse expression + // with acorn"; escaped instead, the reader gets `\{` in what is meant to be + // code, which is the very defect #5553 is about. + const source = [ + '/**', + ' * They use a placeholder grammar:', + ' *', + " * { published_at: { $gte: '{last_quarter_start}' } }", + ' *', + ' * Expanded on both sides.', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + [ + 'They use a placeholder grammar:', + '', + '```', + "{ published_at: { $gte: '{last_quarter_start}' } }", + '```', + '', + 'Expanded on both sides.', + ].join('\n'), + ); + }); + + it('keeps a list a list, and its nesting nested', () => { + // 85 of the 185 described sources write a list. Splitting per line made + // every item its own paragraph and `.trim()` flattened the nesting, so a + // literal space-join — the other reading of "merge the lines" — would have + // been just as wrong in the other direction. + const source = [ + '/**', + ' * ## Layers', + ' *', + ' * 1. **Warehouse**', + ' * - Extract from systems', + ' * - Load into the warehouse', + ' * 2. **Integration**', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + [ + '## Layers', + '', + '1. **Warehouse**', + ' - Extract from systems', + ' - Load into the warehouse', + '2. **Integration**', + ].join('\n'), + ); + }); + + it('keeps consecutive `@see` tags as separate blocks', () => { + // JSDoc block tags are block-level, and the sources write runs of them with + // no blank line between (`automation/etl` ends on three). Preserving the + // source layout alone would have merged them into one run-on paragraph — + // the one place the renderer must ADD a blank line rather than keep one. + const source = [ + '/**', + ' * ETL pipelines.', + ' * @see https://airbyte.com/', + ' * @see https://nifi.apache.org/', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + ['ETL pipelines.', '', 'See also: https://airbyte.com/', '', 'See also: https://nifi.apache.org/'].join('\n'), + ); + }); +}); + +/** + * #6136 — a rewriter that runs over its own output nests a link in a link. + * + * The untitled `{@link }` branch emits `[]()`, whose link + * TEXT is the path itself. The bare-source-path rewriter that runs next only + * excluded "preceded by `(`" and "followed by `)`", so it matched that text and + * wrapped it a second time. Lookaround cannot express "not nested inside a + * link"; the fix is to stop showing it the links at all. + */ +describe('renderFileDescription — #6136: the bare-path rewriter skips formed links', () => { + const ctx = { + sourcePathToDocsRoute: (t: string) => + /integration\/connector\.zod\.ts$/.test(t) ? '/docs/references/integration/connector' : null, + }; + + it('renders an untitled `{@link }` as ONE link', () => { + const source = [ + '/**', + ' * See {@link ../integration/connector.zod.ts} for the connector layer.', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + 'See [../integration/connector.zod.ts](/docs/references/integration/connector) for the connector layer.', + ); + }); + + it('renders the published `@see {@link file://…}` shape as ONE link', () => { + // `automation/etl.zod.ts:42` verbatim — the exact input behind + // `content/docs/references/automation/etl.mdx:54`. + const source = [ + '/**', + ' * @see {@link file://../integration/connector.zod.ts} for the Enterprise Connector layer', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + 'See also: [../integration/connector.zod.ts](/docs/references/integration/connector) for the Enterprise Connector layer', + ); + }); + + it('still linkifies a path left bare in prose', () => { + // The half that must NOT regress: skipping formed links is not the same as + // skipping paths, and a rewriter that stopped doing its job would pass the + // two cases above for the wrong reason. + // + // Spelled WITHOUT a `../` prefix on purpose. A bare path that carries one + // is mis-linked by a defect this PR does not touch — the rewriter's leading + // `\b` cannot match at the `.` of `../`, so the prefix is left outside the + // link (`../../[system/cache.zod.ts](route)`, live on `api/http-cache` and + // `system/cache`). That is a different input shape from #6136 (no `{@link}` + // is involved) and it is filed separately; asserting the broken spelling + // here would ratify it, so this case steers around it the way #5059's did. + const source = [ + '/**', + ' * The connector lives in integration/connector.zod.ts today.', + ' */', + '', + "import { z } from 'zod';", + '', + ].join('\n'); + expect(renderFileDescription(source, ctx)).toBe( + 'The connector lives in [integration/connector.zod.ts](/docs/references/integration/connector) today.', + ); + }); +}); + /** * The corpus half: re-derive the verdict from the real sources, so the six * pages the issue measured cannot silently re-acquire a wrong opening, and so a @@ -307,3 +527,137 @@ describe('corpus — no reference source donates a symbol comment to its page', expect(openingOf('ui/sharing.zod.ts')).toBe('@module ui/sharing'); }); }); + +/** + * The corpus half of #5553 / #6136: re-derive both verdicts from the real + * sources, so a future header cannot quietly re-acquire either defect. + * + * These assert on the RENDERED fragment rather than on the emitted `.mdx`, for + * the same reason the selection half does — running the whole generator and + * grepping its artifact is how both defects survived on `main` in the first + * place. + */ +describe('corpus — every rendered description is well-formed markdown', () => { + // `build-docs.ts` derives its category map from exactly this directory + // listing (`CATEGORIES`, build-docs.ts:129), so this is the real mapping and + // not a stand-in that could disagree with the generator. + const categories = new Set( + fs.readdirSync(SRC_DIR, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name), + ); + const ctx = { + sourcePathToDocsRoute: (target: string) => { + const m = target.match(/(?:^|\/)([\w-]+)\/([\w.-]+)\.zod\.ts$/); + return m && categories.has(m[1]) ? `/docs/references/${m[1]}/${m[2]}` : null; + }, + }; + + const zodFiles: string[] = []; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const p = path.join(dir, entry.name); + if (entry.isDirectory()) walk(p); + else if (entry.name.endsWith('.zod.ts')) zodFiles.push(p); + } + }; + walk(SRC_DIR); + + const described = zodFiles + .map(file => ({ rel: path.relative(SRC_DIR, file), out: renderFileDescription(fs.readFileSync(file, 'utf-8'), ctx) })) + .filter(d => d.out !== ''); + + /** The description with fenced code blocks removed. */ + const withoutFences = (out: string) => { + let fenced = false; + return out + .split('\n') + .map(line => { + if (/^\s*(?:`{3,}|~{3,})/.test(line)) { fenced = !fenced; return ''; } + return fenced ? '' : line; + }) + .join('\n'); + }; + + it('finds descriptions to check', () => { + expect(described.length).toBeGreaterThan(150); + }); + + it('never cuts an inline code span in half (#5553)', () => { + // A code span cannot cross a blank line, so within one PARAGRAPH the + // backticks have to pair. Counting per LINE — the scan the issue proposed — + // stopped being the right question once spans were allowed to wrap again: + // it now flags the four spans that correctly span lines. + const offenders: string[] = []; + for (const { rel, out } of described) { + for (const para of withoutFences(out).split(/\n\s*\n/)) { + if ((para.match(/`/g) ?? []).length % 2 === 1) offenders.push(`${rel}: ${para.trim().slice(0, 60)}`); + } + } + expect(offenders).toEqual([]); + }); + + it('never prints a backslash escape inside code, where it is content (#5553)', () => { + const offenders: string[] = []; + for (const { rel, out } of described) { + let fenced = false; + for (const line of out.split('\n')) { + if (/^\s*(?:`{3,}|~{3,})/.test(line)) { fenced = !fenced; continue; } + const code = fenced || /^ {4,}\S/.test(line) + ? [line] + : line.match(/`[^`]*`/g) ?? []; + if (code.some(c => /\\[{}]/.test(c))) offenders.push(`${rel}: ${line.trim().slice(0, 60)}`); + } + } + expect(offenders).toEqual([]); + }); + + it('never leaves a brace where MDX would parse it as an expression', () => { + // The other half of "escape only in prose": every `{` that is NOT inside a + // code span or a fenced block has to arrive escaped, or the docs build dies + // with "Could not parse expression with acorn". This is the assertion that + // catches an indented code block left indented — MDX has no such construct, + // so its braces reach the compiler as prose. + const offenders: string[] = []; + for (const { rel, out } of described) { + const bare = withoutFences(out) + .replace(/`[^`]*`/g, '') // inline code spans are literal in MDX + .replace(/\\[{}]/g, ''); // already escaped + if (/[{}]/.test(bare)) offenders.push(`${rel}: ${bare.match(/.{0,40}[{}].{0,20}/)?.[0].trim()}`); + } + expect(offenders).toEqual([]); + }); + + it('never emits an indented code block — MDX has no such construct', () => { + const offenders: string[] = []; + for (const { rel, out } of described) { + let fenced = false; + let prevBlank = true; + for (const line of out.split('\n')) { + if (/^\s*(?:`{3,}|~{3,})/.test(line)) { fenced = !fenced; prevBlank = false; continue; } + if (!fenced && prevBlank && /^ {4,}\S/.test(line)) offenders.push(`${rel}: ${line.slice(0, 60)}`); + prevBlank = line.trim() === ''; + } + } + expect(offenders).toEqual([]); + }); + + it('never nests a markdown link inside a markdown link (#6136)', () => { + const offenders: string[] = []; + for (const { rel, out } of described) { + // A link whose TEXT still contains link syntax — the published shape was + // `[../[path](route)](route)`. + if (/\[[^\]]*\]\([^)]*\)\]\(/.test(out) || /\[[^\][]*\[[^\]]*\]\(/.test(out)) { + offenders.push(rel); + } + } + expect(offenders).toEqual([]); + }); + + it('keeps a description for every source that had one — #6134 selection is untouched', () => { + // The rendering fix must not remove a page's opening paragraph; that is + // #5059's acceptance criterion and it still binds. 185 sources carry a + // module header, and all 185 still render one. + expect(described.length).toBe( + zodFiles.filter(f => findModuleDocBlock(fs.readFileSync(f, 'utf-8')) !== null).length, + ); + }); +}); diff --git a/packages/spec/scripts/lib/file-description.ts b/packages/spec/scripts/lib/file-description.ts index 6b97e092f9..4dc919f31d 100644 --- a/packages/spec/scripts/lib/file-description.ts +++ b/packages/spec/scripts/lib/file-description.ts @@ -58,6 +58,46 @@ * that cannot pick a symbol's comment in the first place makes the whole class * impossible. Its enforcement is `file-description.test.ts`, which pins the * selection on the real shapes instead of on the emitted `.mdx`. + * + * ## Rendering the selected block (#5553, #6136) + * + * Selection says WHICH block; the rest of this file says how it becomes MDX. + * Two defects lived here, both from transforms applied at the wrong GRANULARITY: + * + * - **Per line instead of per block (#5553).** The renderer dropped every blank + * line and joined the surviving lines with `\n\n`, i.e. it declared each + * SOURCE LINE its own paragraph. Markdown constructs that legitimately wrap + * across lines were then cut in half by a paragraph boundary: an inline code + * span cannot cross a blank line, so both of its backticks rendered as + * literal text (`explain(principal, object,` / `operation)` on + * `security/explain`). It also flattened every list, heading, table and code + * block the sources had written, because their structure IS their line layout. + * The fix is to stop rewriting that layout: strip the ` * ` gutter and keep + * the lines as authored. Markdown's own rules then do what the issue asked + * for — consecutive lines are one paragraph, a blank line opens the next — + * while lists and fences keep working, which a literal space-join would have + * broken on 85 of the 185 sources that have a description. + * + * - **Everywhere instead of only in prose (#5553, #6136).** Brace escaping ran + * over the whole string, so `{` inside an inline code span became a visible + * `\{` — the backslash is not an escape character there, it is content. The + * bare-source-path rewriter had the same shape of bug one level up: it ran + * over text that already contained the markdown links the `{@link}` step had + * just produced, matched the path inside a link's TEXT, and wrapped it again + * into a link nested in a link (`automation/etl:54`, `integration/connector:102`). + * + * So every transform below is scoped to the runs it is actually about: code + * lines (fenced or indented) are copied verbatim, and within prose the + * tokenizer keeps inline code spans and already-formed links out of reach. + * Widening the rewriter's lookaround instead would not have worked — lookaround + * cannot express "not nested inside a link". + * + * The one place the output is NOT the source's own layout is an indented + * (4-space) code block, which is re-emitted as a fenced one. MDX dropped + * CommonMark's indented code blocks so that indentation could lay out JSX, so + * keeping them would hand `{ $gte: '{last_quarter_start}' }` to MDX as an + * expression and fail the docs build — the target dialect has one spelling for + * a code block and this is it. */ /** @@ -160,32 +200,226 @@ export function findModuleDocBlock(source: string): string | null { } /** - * The module's doc block, rendered as the MDX fragment a reference page opens - * with. Empty string when the module has no description — callers print nothing - * rather than a placeholder. + * The doc block's lines with the ` * ` gutter removed and nothing else changed. + * + * Indentation AFTER the gutter is content, not decoration — it is what makes a + * nested list nested and an indented code block code — so only the gutter and + * trailing whitespace come off. The first and last lines of a block carry no + * gutter (they are the fragments beside the opening and closing delimiters), + * so they are trimmed outright. */ -export function renderFileDescription(source: string, ctx: FileDescriptionContext): string { - const block = findModuleDocBlock(source); - if (block === null) return ''; +function stripDocGutter(block: string): string[] { + return block.split('\n').map(line => { + const gutter = /^\s*\*\s?/.exec(line); + return gutter ? line.slice(gutter[0].length).trimEnd() : line.trim(); + }); +} + +/** What a line is, which decides which transforms may touch it. */ +type LineKind = 'prose' | 'fenced' | 'indented'; + +/** + * Classify every line as prose, fenced code, or an indented (4-space) code + * block. Code of either kind is content the reader is meant to read literally, + * so link resolution and brace escaping would be printing their own syntax into + * it. + * + * Indented blocks have to be recognised separately rather than folded in with + * fenced ones, because MDX does not have them: it dropped CommonMark's indented + * code blocks precisely so indentation could be used to lay out JSX. Two sources + * write their placeholder examples that way (`data/date-macros`, + * `data/context-tokens`) and both are almost entirely braces, so leaving them + * indented would hand `{ $gte: '{last_quarter_start}' }` to MDX as an expression + * and fail the docs build. They are re-emitted as fenced blocks instead. + */ +function classifyLines(lines: readonly string[]): LineKind[] { + const kind: LineKind[] = lines.map(() => 'prose'); + let fence: string | null = null; + let indented = false; + let prevBlank = true; // the start of the block is a block boundary + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const blank = line.trim() === ''; + + if (fence !== null) { + kind[i] = 'fenced'; + if (line.trimStart().startsWith(fence)) fence = null; + prevBlank = false; + continue; + } + if (indented) { + if (blank) { prevBlank = true; continue; } // a blank line does not close it + if (/^ {4,}\S/.test(line)) { kind[i] = 'indented'; prevBlank = false; continue; } + indented = false; // dedented — this line is prose again + } + const opener = /^\s*(`{3,}|~{3,})/.exec(line); + if (opener) { kind[i] = 'fenced'; fence = opener[1]; prevBlank = false; continue; } + if (prevBlank && /^ {4,}\S/.test(line)) { kind[i] = 'indented'; indented = true; prevBlank = false; continue; } + + prevBlank = blank; + } + + // A blank line BETWEEN two indented lines belongs to the block; one after the + // last belongs to the prose that follows. + for (let i = 0; i < kind.length; i++) { + if (kind[i] !== 'indented') continue; + let j = i + 1; + while (j < kind.length && lines[j].trim() === '') j++; + if (j < kind.length && kind[j] === 'indented') for (let k = i + 1; k < j; k++) kind[k] = 'indented'; + i = j - 1; + } + return kind; +} + +/** + * A blank line before every JSDoc block tag that does not already have one. + * + * Keeping the source's own blank lines is not sufficient on its own, because + * the sources write RUNS of tags with no blank line between them — + * `automation/etl` ends on three consecutive `@see` links. Those are three + * blocks in JSDoc, so they must stay three blocks in markdown; joined into one + * paragraph they would read as a single run-on sentence. + */ +function withTagBlocksSeparated(lines: readonly string[]): string[] { + const kind = classifyLines(lines); + const out: string[] = []; + lines.forEach((line, i) => { + if (kind[i] === 'prose' && /^@\w+/.test(line) && out.length > 0 && out[out.length - 1].trim() !== '') out.push(''); + out.push(line); + }); + return out; +} + +/** A run of prose the transforms may rewrite, or one they must leave alone. */ +interface ProseRun { + kind: 'text' | 'code' | 'link'; + text: string; +} + +/** + * Split prose into rewritable text, inline code spans, and markdown links. + * + * The two protected kinds are protected for different reasons. A code span is + * content the reader sees literally, so escaping inside it leaks the escape + * character onto the page. A link is a construct a previous transform BUILT, + * and re-running a rewriter over its text is how a link ends up inside a link + * (#6136) — the reason this is a tokenizer and not a longer lookaround. + */ +function tokenizeProse(text: string): ProseRun[] { + const runs: ProseRun[] = []; + let buffer = ''; + const flush = () => { if (buffer) { runs.push({ kind: 'text', text: buffer }); buffer = ''; } }; + + for (let i = 0; i < text.length;) { + if (text[i] === '`') { + let open = i; + while (open < text.length && text[open] === '`') open++; + const width = open - i; + // CommonMark: the closing run has to be exactly as wide as the opener. + let end = -1; + for (let j = open; j < text.length;) { + if (text[j] !== '`') { j++; continue; } + let k = j; + while (k < text.length && text[k] === '`') k++; + if (k - j === width) { end = j; break; } + j = k; + } + if (end !== -1) { + flush(); + runs.push({ kind: 'code', text: text.slice(i, end + width) }); + i = end + width; + continue; + } + buffer += text.slice(i, open); // unmatched backticks are ordinary text + i = open; + continue; + } + if (text[i] === '[') { + const link = /^\[[^\]]*\]\([^)\s]*\)/.exec(text.slice(i)); + if (link) { + flush(); + runs.push({ kind: 'link', text: link[0] }); + i += link[0].length; + continue; + } + } + buffer += text[i]; + i++; + } + flush(); + return runs; +} + +/** Apply `fn` to prose outside inline code spans, and outside links when asked. */ +function mapProse(text: string, kinds: ProseRun['kind'][], fn: (plain: string) => string): string { + return tokenizeProse(text).map(run => (kinds.includes(run.kind) ? fn(run.text) : run.text)).join(''); +} + +/** One run of consecutive prose lines, rendered to MDX. */ +function renderProse(text: string, ctx: FileDescriptionContext): string { const { sourcePathToDocsRoute } = ctx; - return block - .split('\n') - .map(line => line.replace(/^\s*\*\s?/, '').trim()) - .filter(line => line) - // A bare `@see ` tag renders as noise — turn it into prose. - .map(line => line.replace(/^@see\s+/, 'See also: ')) - .join('\n\n') - .replace(/\{@link\s+([^|]+?)\s*\|\s*([^}]+?)\s*\}/g, (_m, target: string, text: string) => - `[${text.trim()}](${sourcePathToDocsRoute(target.trim()) ?? target.trim()})`) + + // A bare `@see ` tag renders as noise — turn it into prose. + let out = text.replace(/^@see[ \t]+/gm, 'See also: '); + + // `{@link}` first, because this is the step that PRODUCES markdown links. + out = mapProse(out, ['text'], s => s + .replace(/\{@link\s+([^|]+?)\s*\|\s*([^}]+?)\s*\}/g, (_m, target: string, label: string) => + `[${label.trim()}](${sourcePathToDocsRoute(target.trim()) ?? target.trim()})`) .replace(/\{@link\s+([^}]+?)\s*\}/g, (_m, target: string) => { const route = sourcePathToDocsRoute(target.trim()); return route ? `[${target.trim()}](${route})` : `\`${target.trim()}\``; - }) - // Same for a bare source path left in prose by `See also:` above. - .replace(/(? { + })); + + // …and only then paths left BARE in prose. Re-tokenizing between the two is + // the whole of #6136's fix: the untitled `{@link}` branch above emits + // `[]()`, whose link text is the path itself, so a rewriter run + // over the raw string matched it a second time and nested a link in a link. + out = mapProse(out, ['text'], s => + s.replace(/(? { const route = sourcePathToDocsRoute(p); return route ? `[${p}](${route})` : `\`${p}\``; - }) - .replace(/file:\/\//g, '') // Remove file:// protocol - .replace(/\{/g, '\\{').replace(/\}/g, '\\}'); // Escape { } for MDX + })); + + out = out.replace(/file:\/\//g, ''); // Remove file:// protocol + + // Escape `{ }` for MDX, but only where MDX would read them as an expression. + // Inside a code span they are already literal, so escaping there printed the + // backslash itself onto five published pages (#5553). + return mapProse(out, ['text', 'link'], s => s.replace(/\{/g, '\\{').replace(/\}/g, '\\}')); +} + +/** + * The module's doc block, rendered as the MDX fragment a reference page opens + * with. Empty string when the module has no description — callers print nothing + * rather than a placeholder. + */ +export function renderFileDescription(source: string, ctx: FileDescriptionContext): string { + const block = findModuleDocBlock(source); + if (block === null) return ''; + + const lines = withTagBlocksSeparated(stripDocGutter(block)); + const kind = classifyLines(lines); + + // Prose is rendered a RUN of lines at a time, never line by line: a sentence, + // an inline code span and a `{@link}` tag may each wrap across source lines, + // and a transform applied per line cuts them in half (#5553). + const out: string[] = []; + for (let i = 0; i < lines.length;) { + if (kind[i] === 'fenced') { out.push(lines[i]); i++; continue; } + + if (kind[i] === 'indented') { + const start = i; + while (i < lines.length && kind[i] === 'indented') i++; + // Re-emitted as a fence: MDX has no indented code blocks, so left as it + // was authored this would be parsed as prose containing JSX expressions. + out.push('```', ...lines.slice(start, i).map(line => line.replace(/^ {4}/, '')), '```'); + continue; + } + + const start = i; + while (i < lines.length && kind[i] === 'prose') i++; + out.push(renderProse(lines.slice(start, i).join('\n'), ctx)); + } + return out.join('\n').trim(); }