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: