Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/hip-otters-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@objectstack/spec': patch
---

Give six `*.zod.ts` modules a true module-header doc block, so their reference pages open with their introduction again

`postgres` / `mysql` / `sqlite` driver config, `cloud/template-manifest`, `system/doc` and
`api/error-code-ledger` each already carried a real module introduction — but written glued
to the module's first declaration, which under #5059's strict selection rule is that
symbol's TSDoc and therefore not the module's description. The prose was never the problem;
its attachment was. Each block is promoted verbatim to a top-level header that documents no
symbol, the shape 183 of the reference sources already use, and
`content/docs/references/**` is regenerated: six pages gain their opening paragraphs, no
other page changes and no schema byte moves.
45 changes: 45 additions & 0 deletions content/docs/references/api/error-code-ledger.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,51 @@ description: Error Code Ledger protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

Error-Code Ledger (ADR-0112 D3).

The top-level `error.code` vocabulary is two-tier:

1. **Standard catalog** — `StandardErrorCode` (`errors.zod.ts`): a small,
closed set with platform-wide HTTP semantics. It does NOT grow when a
service invents a code.
2. **Registered extension codes** — THIS ledger: every service-specific code
a route may put in `error.code`, registered under its owning package.

`ErrorCode` (exported below) is the union, and is what `ApiErrorSchema.code`
validates against. An unregistered code fails schema parse — which fails the
envelope conformance suites — which fails CI. That friction is the point
(ADR-0112: "no silent fourth state" for error codes, per ADR-0049/0078).

## Registering a new code

Add it to your package's entry (create the entry if your package has none),
SCREAMING_SNAKE (`^[A-Z][A-Z0-9_]*$` — lint-enforced by
`error-code-ledger.test.ts`), with a trailing `//` comment when the name
alone doesn't carry the meaning. Prefer a domain prefix for anything not
self-evidently global (`ATTACHMENT_*`, `REPORT_*`, `SETTINGS_*`). If the
condition is generic (not found / permission / validation / rate limit),
use the standard catalog instead of registering a synonym.

A code emitted by several packages is listed once per emitting package —
the union dedupes; the per-package rows are provenance, not identity.

## Retiring a code

A row whose last EMITTER is deleted comes out with it. The admission rules
below check casing, duplication and shadowing — never whether anyone still
throws the code — so a registered-but-unemittable row stays green forever
while promising a client a code no response can carry. That is ADR-0112's
"no silent fourth state" read backwards, and it is not hypothetical:
`OVERLAY_PERSISTENCE_FAILED` outlived its only producer by one PR (#5264
deleted `saveMetaItem`'s legacy raw-engine branch; #5783 unregistered the
code). Before deleting a row, check that no producer remains repo-wide AND
that no consumer — including `objectui` and `cloud` — reads the literal;
tests that merely CONSTRUCT the code are not producers, and a test pinned to
a producerless code is pinning nothing (#4984's phantom-check family).

Field-level codes (`FieldErrorSchema.code`, the `fields[]` array) are a
SEPARATE vocabulary and do not belong here — see #3977 (ADR-0112 D6).

<Callout type="info">
**Source:** `packages/spec/src/api/error-code-ledger.zod.ts`
</Callout>
Expand Down
5 changes: 5 additions & 0 deletions content/docs/references/cloud/template-manifest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ description: Template Manifest protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

`objectstack.manifest.json` — on-disk descriptor for a template / package
source tree. Strict projection of `CreatePackageRequestSchema` (server-
managed fields excluded) plus scaffold-time extras (name slug,
specVersion, skills, preview, scaffold, readmePath).

<Callout type="info">
**Source:** `packages/spec/src/cloud/template-manifest.zod.ts`
</Callout>
Expand Down
13 changes: 13 additions & 0 deletions content/docs/references/data/driver-mysql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ description: Driver Mysql protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

MySQL / MariaDB driver configuration — the `config` slot of a `datasource`
whose `driver` resolves to `mysql` (`mysql2`).

The driver id was offered by the connection form and buildable by the shared
factory long before #4410, but had no config shape at all in `packages/spec`
— postgres, mongo and memory each had one and mysql did not, so its `config`
was the one slot with neither a gate nor a documented shape.

Every key here is read by `createDefaultDatasourceDriverFactory`
(→ `SqlDriver`, knex `mysql2`). Postgres-only knobs are deliberately absent:
`mysql2` has no `application_name` and no `statement_timeout`, so declaring
them would advertise settings the client drops.

<Callout type="info">
**Source:** `packages/spec/src/data/driver/mysql.zod.ts`
</Callout>
Expand Down
11 changes: 11 additions & 0 deletions content/docs/references/data/driver-postgres.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ description: Driver Postgres protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

PostgreSQL driver configuration — the `config` slot of a `datasource` whose
`driver` resolves to `postgres` (`pg` / `postgresql`).

ENFORCED as of #4410: `DatasourceSchema` parses `config` against this schema,
so a misspelled connection key fails at authoring time instead of leaving the
datasource on the client's localhost defaults. Every key here is read by
`createDefaultDatasourceDriverFactory` (→ `SqlDriver`, knex `pg`).

Pool sizing is NOT here: it lives in the driver-agnostic `datasource.pool`
block, which the factory now honours for every SQL driver.

<Callout type="info">
**Source:** `packages/spec/src/data/driver/postgres.zod.ts`
</Callout>
Expand Down
15 changes: 15 additions & 0 deletions content/docs/references/data/driver-sqlite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ description: Driver Sqlite protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

SQLite driver configuration — the `config` slot of a `datasource` whose
`driver` resolves to `sqlite` (native `better-sqlite3`, with the dev-only
step-down to wasm then in-memory, #2229) or to `sqlite-wasm` (pure-JS).

The one key that matters is `filename`, and it is exactly the key the silent
strip used to hide: an author who wrote `path:` got no error, the connection
fell back to `:memory:`, and their data vanished on restart with every signal
saying the datasource was configured.

`file` and `database` once also worked, purely because the factory read them
as undeclared `??` fallbacks. That tolerance has graduated into the declared
ADR-0087 conversion `datasource-config-driver-key-aliases` (#4456): stored
rows are rewritten to `filename` at load, the factory reads one spelling,
and authoring rejects both with the rename hint below.

<Callout type="info">
**Source:** `packages/spec/src/data/driver/sqlite.zod.ts`
</Callout>
Expand Down
21 changes: 21 additions & 0 deletions content/docs/references/system/doc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ description: Doc protocol schemas

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}

Package Documentation Metadata Protocol (ADR-0046)

One `doc` item per Markdown file under the package's flat `src/docs/`
directory (no subdirectories — flatness is the contract that keeps
cross-references stable). The CLI compiles each file into this shape at
build time; TS-first stacks may also declare items inline via
`defineStack({ docs: [...] })`.

Identity model: `name` = filename stem (lowercase snake_case). A namespace
prefix (`crm_lead_guide`) is a *recommended convention*, no longer required:
per ADR-0048, single-doc resolution is package-scoped (`getItem('doc', name,
packageId)` via `?package=` on the detail route), so two packages may ship a
doc with the same bare name and each resolves within its own package — just
like `page`/`dashboard`/`report`. The prefix stays useful for readable,
globally-unique filenames but is not load-bearing for uniqueness.

Docs are inert data: the kernel registers them without parsing
`content`, and they participate in no runtime behavior. Renderers
resolve relative links between docs (`[guide](./crm_lead_guide.md)`)
by stripping `./` and `.md` to obtain the target doc name.

<Callout type="info">
**Source:** `packages/spec/src/system/doc.zod.ts`
</Callout>
Expand Down
31 changes: 31 additions & 0 deletions packages/spec/scripts/file-description.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,37 @@ describe('corpus — no reference source donates a symbol comment to its page',
expect(openingOf('api/websocket.zod.ts')).toBe('WebSocket Event Protocol');
expect(openingOf('ui/sharing.zod.ts')).toBe('@module ui/sharing');
});

/**
* #6145 — the other side of #5059's ledger. Six more modules had written a
* real module introduction and then glued it to their first declaration, so
* the strict rule (correctly) read it as that symbol's TSDoc and their pages
* went on opening with nothing. The prose was never the problem; its
* attachment was. Each block was promoted VERBATIM to a true module header,
* and this pins the result per file.
*
* Deliberately an assertion about the six SOURCES, not about the emitted
* `.mdx`: `check:docs` compares the artifact to the source, so it stays green
* while a re-glued header quietly empties the page — which is exactly how the
* original six survived two rounds on `main`.
*/
it('opens each of the six #6145 modules with its own module header', () => {
const openingOf = (rel: string) =>
opening(findModuleDocBlock(fs.readFileSync(path.join(SRC_DIR, rel), 'utf-8')));

expect(openingOf('data/driver/postgres.zod.ts'))
.toBe('PostgreSQL driver configuration — the `config` slot of a `datasource` whose');
expect(openingOf('data/driver/mysql.zod.ts'))
.toBe('MySQL / MariaDB driver configuration — the `config` slot of a `datasource`');
expect(openingOf('data/driver/sqlite.zod.ts'))
.toBe('SQLite driver configuration — the `config` slot of a `datasource` whose');
expect(openingOf('cloud/template-manifest.zod.ts'))
.toBe('`objectstack.manifest.json` — on-disk descriptor for a template / package');
expect(openingOf('system/doc.zod.ts'))
.toBe('Package Documentation Metadata Protocol (ADR-0046)');
expect(openingOf('api/error-code-ledger.zod.ts'))
.toBe('Error-Code Ledger (ADR-0112 D3).');
});
});

/**
Expand Down
7 changes: 4 additions & 3 deletions packages/spec/src/api/error-code-ledger.zod.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { z } from 'zod';
import { StandardErrorCode } from './errors.zod';

/**
* Error-Code Ledger (ADR-0112 D3).
*
Expand Down Expand Up @@ -49,6 +46,10 @@ import { StandardErrorCode } from './errors.zod';
* Field-level codes (`FieldErrorSchema.code`, the `fields[]` array) are a
* SEPARATE vocabulary and do not belong here — see #3977 (ADR-0112 D6).
*/

import { z } from 'zod';
import { StandardErrorCode } from './errors.zod';

export const ERROR_CODE_LEDGER = {
'@objectstack/rest': [
'ALREADY_REVERTED',
Expand Down
8 changes: 5 additions & 3 deletions packages/spec/src/cloud/template-manifest.zod.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { z } from 'zod';
import { lazySchema } from '../shared/lazy-schema';
import { CreatePackageRequestSchema } from './package.zod';

/**
* `objectstack.manifest.json` — on-disk descriptor for a template / package
* source tree. Strict projection of `CreatePackageRequestSchema` (server-
* managed fields excluded) plus scaffold-time extras (name slug,
* specVersion, skills, preview, scaffold, readmePath).
*/

import { z } from 'zod';
import { lazySchema } from '../shared/lazy-schema';
import { CreatePackageRequestSchema } from './package.zod';

export const TemplateManifestSchema = lazySchema(() =>
CreatePackageRequestSchema
.omit({ ownerOrgId: true, createdBy: true })
Expand Down
27 changes: 14 additions & 13 deletions packages/spec/src/data/driver/mysql.zod.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { z } from 'zod';

import { lazySchema } from '../../shared/lazy-schema';
import { strictUnknownKeyError } from '../../shared/suggestions.zod';
import {
driverConfigJsonSchema,
DriverSslToggleSchema,
READ_ONLY_BELONGS_ON_DATASOURCE,
SCHEMA_MODE_BELONGS_ON_DATASOURCE,
SqlAutoMigrateSchema,
SSL_DETAIL_BELONGS_ON_DATASOURCE,
} from './common.zod';

/**
* MySQL / MariaDB driver configuration — the `config` slot of a `datasource`
* whose `driver` resolves to `mysql` (`mysql2`).
Expand All @@ -27,6 +14,20 @@ import {
* `mysql2` has no `application_name` and no `statement_timeout`, so declaring
* them would advertise settings the client drops.
*/

import { z } from 'zod';

import { lazySchema } from '../../shared/lazy-schema';
import { strictUnknownKeyError } from '../../shared/suggestions.zod';
import {
driverConfigJsonSchema,
DriverSslToggleSchema,
READ_ONLY_BELONGS_ON_DATASOURCE,
SCHEMA_MODE_BELONGS_ON_DATASOURCE,
SqlAutoMigrateSchema,
SSL_DETAIL_BELONGS_ON_DATASOURCE,
} from './common.zod';

const MYSQL_CONFIG_KEYS = [
'url', 'host', 'port', 'database', 'username', 'password', 'ssl', 'autoMigrate',
] as const;
Expand Down
25 changes: 13 additions & 12 deletions packages/spec/src/data/driver/postgres.zod.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

/**
* PostgreSQL driver configuration — the `config` slot of a `datasource` whose
* `driver` resolves to `postgres` (`pg` / `postgresql`).
*
* ENFORCED as of #4410: `DatasourceSchema` parses `config` against this schema,
* so a misspelled connection key fails at authoring time instead of leaving the
* datasource on the client's localhost defaults. Every key here is read by
* `createDefaultDatasourceDriverFactory` (→ `SqlDriver`, knex `pg`).
*
* Pool sizing is NOT here: it lives in the driver-agnostic `datasource.pool`
* block, which the factory now honours for every SQL driver.
*/

import { z } from 'zod';

import { lazySchema } from '../../shared/lazy-schema';
Expand All @@ -13,18 +26,6 @@ import {
SSL_DETAIL_BELONGS_ON_DATASOURCE,
} from './common.zod';

/**
* PostgreSQL driver configuration — the `config` slot of a `datasource` whose
* `driver` resolves to `postgres` (`pg` / `postgresql`).
*
* ENFORCED as of #4410: `DatasourceSchema` parses `config` against this schema,
* so a misspelled connection key fails at authoring time instead of leaving the
* datasource on the client's localhost defaults. Every key here is read by
* `createDefaultDatasourceDriverFactory` (→ `SqlDriver`, knex `pg`).
*
* Pool sizing is NOT here: it lives in the driver-agnostic `datasource.pool`
* block, which the factory now honours for every SQL driver.
*/
const POSTGRES_CONFIG_KEYS = [
'url', 'host', 'port', 'database', 'username', 'password', 'ssl',
'schema', 'applicationName', 'statementTimeout', 'autoMigrate',
Expand Down
23 changes: 12 additions & 11 deletions packages/spec/src/data/driver/sqlite.zod.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { z } from 'zod';

import { lazySchema } from '../../shared/lazy-schema';
import { strictUnknownKeyError } from '../../shared/suggestions.zod';
import {
driverConfigJsonSchema,
READ_ONLY_BELONGS_ON_DATASOURCE,
SCHEMA_MODE_BELONGS_ON_DATASOURCE,
SqlAutoMigrateSchema,
} from './common.zod';

/**
* SQLite driver configuration — the `config` slot of a `datasource` whose
* `driver` resolves to `sqlite` (native `better-sqlite3`, with the dev-only
Expand All @@ -27,6 +16,18 @@ import {
* rows are rewritten to `filename` at load, the factory reads one spelling,
* and authoring rejects both with the rename hint below.
*/

import { z } from 'zod';

import { lazySchema } from '../../shared/lazy-schema';
import { strictUnknownKeyError } from '../../shared/suggestions.zod';
import {
driverConfigJsonSchema,
READ_ONLY_BELONGS_ON_DATASOURCE,
SCHEMA_MODE_BELONGS_ON_DATASOURCE,
SqlAutoMigrateSchema,
} from './common.zod';

const SQLITE_CONFIG_KEYS = ['filename', 'autoMigrate'] as const;

const FILENAME_ALIASES = {
Expand Down
11 changes: 6 additions & 5 deletions packages/spec/src/system/doc.zod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { z } from 'zod';
import { lazySchema } from '../shared/lazy-schema';
import { strictObject } from '../shared/strict-object';
import { MetadataProtectionFields } from '../kernel/metadata-protection.zod';

/**
* Package Documentation Metadata Protocol (ADR-0046)
*
Expand All @@ -27,6 +22,12 @@ import { MetadataProtectionFields } from '../kernel/metadata-protection.zod';
* resolve relative links between docs (`[guide](./crm_lead_guide.md)`)
* by stripping `./` and `.md` to obtain the target doc name.
*/

import { z } from 'zod';
import { lazySchema } from '../shared/lazy-schema';
import { strictObject } from '../shared/strict-object';
import { MetadataProtectionFields } from '../kernel/metadata-protection.zod';

export const DocSchema = lazySchema(() => strictObject({
surface: 'this doc',
history:
Expand Down
Loading