diff --git a/.changeset/hip-otters-smash.md b/.changeset/hip-otters-smash.md new file mode 100644 index 0000000000..5d9230b083 --- /dev/null +++ b/.changeset/hip-otters-smash.md @@ -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. diff --git a/content/docs/references/api/error-code-ledger.mdx b/content/docs/references/api/error-code-ledger.mdx index 555ffbcf82..da6acfdc1c 100644 --- a/content/docs/references/api/error-code-ledger.mdx +++ b/content/docs/references/api/error-code-ledger.mdx @@ -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). + **Source:** `packages/spec/src/api/error-code-ledger.zod.ts` diff --git a/content/docs/references/cloud/template-manifest.mdx b/content/docs/references/cloud/template-manifest.mdx index 5dc2c1d546..9ba4a1a3e2 100644 --- a/content/docs/references/cloud/template-manifest.mdx +++ b/content/docs/references/cloud/template-manifest.mdx @@ -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). + **Source:** `packages/spec/src/cloud/template-manifest.zod.ts` diff --git a/content/docs/references/data/driver-mysql.mdx b/content/docs/references/data/driver-mysql.mdx index ee2fb95c38..d8d819492a 100644 --- a/content/docs/references/data/driver-mysql.mdx +++ b/content/docs/references/data/driver-mysql.mdx @@ -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. + **Source:** `packages/spec/src/data/driver/mysql.zod.ts` diff --git a/content/docs/references/data/driver-postgres.mdx b/content/docs/references/data/driver-postgres.mdx index 5a258b5961..7fe5756f48 100644 --- a/content/docs/references/data/driver-postgres.mdx +++ b/content/docs/references/data/driver-postgres.mdx @@ -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. + **Source:** `packages/spec/src/data/driver/postgres.zod.ts` diff --git a/content/docs/references/data/driver-sqlite.mdx b/content/docs/references/data/driver-sqlite.mdx index 564044e63e..ab088e25ad 100644 --- a/content/docs/references/data/driver-sqlite.mdx +++ b/content/docs/references/data/driver-sqlite.mdx @@ -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. + **Source:** `packages/spec/src/data/driver/sqlite.zod.ts` diff --git a/content/docs/references/system/doc.mdx b/content/docs/references/system/doc.mdx index 18c1cec781..71a8089705 100644 --- a/content/docs/references/system/doc.mdx +++ b/content/docs/references/system/doc.mdx @@ -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. + **Source:** `packages/spec/src/system/doc.zod.ts` diff --git a/packages/spec/scripts/file-description.test.ts b/packages/spec/scripts/file-description.test.ts index c56fe3a2e1..561da8e47c 100644 --- a/packages/spec/scripts/file-description.test.ts +++ b/packages/spec/scripts/file-description.test.ts @@ -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).'); + }); }); /** diff --git a/packages/spec/src/api/error-code-ledger.zod.ts b/packages/spec/src/api/error-code-ledger.zod.ts index 15555db908..da81a2205d 100644 --- a/packages/spec/src/api/error-code-ledger.zod.ts +++ b/packages/spec/src/api/error-code-ledger.zod.ts @@ -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). * @@ -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', diff --git a/packages/spec/src/cloud/template-manifest.zod.ts b/packages/spec/src/cloud/template-manifest.zod.ts index 34f41956fc..c91d053e40 100644 --- a/packages/spec/src/cloud/template-manifest.zod.ts +++ b/packages/spec/src/cloud/template-manifest.zod.ts @@ -1,7 +1,4 @@ // 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 @@ -9,6 +6,11 @@ import { CreatePackageRequestSchema } from './package.zod'; * 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 }) diff --git a/packages/spec/src/data/driver/mysql.zod.ts b/packages/spec/src/data/driver/mysql.zod.ts index f707f2d3bb..356614ba2e 100644 --- a/packages/spec/src/data/driver/mysql.zod.ts +++ b/packages/spec/src/data/driver/mysql.zod.ts @@ -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`). @@ -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; diff --git a/packages/spec/src/data/driver/postgres.zod.ts b/packages/spec/src/data/driver/postgres.zod.ts index a7fda99659..119402d8d6 100644 --- a/packages/spec/src/data/driver/postgres.zod.ts +++ b/packages/spec/src/data/driver/postgres.zod.ts @@ -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'; @@ -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', diff --git a/packages/spec/src/data/driver/sqlite.zod.ts b/packages/spec/src/data/driver/sqlite.zod.ts index 66fc6f4d1b..e680eb1895 100644 --- a/packages/spec/src/data/driver/sqlite.zod.ts +++ b/packages/spec/src/data/driver/sqlite.zod.ts @@ -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 @@ -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 = { diff --git a/packages/spec/src/system/doc.zod.ts b/packages/spec/src/system/doc.zod.ts index 484e558948..d56d0021d0 100644 --- a/packages/spec/src/system/doc.zod.ts +++ b/packages/spec/src/system/doc.zod.ts @@ -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) * @@ -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: