|
| 1 | +# ADR-0046: Package documentation as metadata — `src/docs/` compiled into the manifest, derived content rendered not written |
| 2 | + |
| 3 | +**Status**: Proposed (2026-06-12) |
| 4 | +**Deciders**: ObjectStack Protocol Architects |
| 5 | +**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package + versioned releases), [ADR-0016](./0016-studio-package-authoring-and-publish.md) (publish pipeline, `manifest_json` snapshot), [ADR-0019](./0019-app-as-consumer-unit.md) (App is the only consumer-facing unit), [ADR-0025](./0025-plugin-package-distribution.md) (artifact distribution, trust boundary), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (AI as primary author) |
| 6 | +**Consumers**: `@objectstack/spec` (manifest `docs` element), `@objectstack/cli` (collection, publish lint, `os docs lint|verify`), `@objectstack/console` (help center, contextual help, metadata views), cloud control plane (registry rendering, grants — implementation design lives in the cloud repo), `@objectstack/core` (kernel skips `docs` at load) |
| 7 | +**Pilot**: `os-tianshun-mtc` (delivery project; repo layout + authoring conventions validated there first) |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## TL;DR |
| 12 | + |
| 13 | +A package today carries every behavioral fact about itself — objects, |
| 14 | +fields, validations, flows, permissions — but not one sentence of *intent*: |
| 15 | +what it is, how its users operate it, how its admins run it. That prose |
| 16 | +lives in ad-hoc repo trees, wikis, and static sites that rot the moment |
| 17 | +metadata changes, and reaches customers through channels (file shares, |
| 18 | +hosted sites) that duplicate the registry's versioning and access control. |
| 19 | + |
| 20 | +This ADR makes documentation a **metadata element**. Markdown under |
| 21 | +`src/docs/` compiles into the manifest exactly like objects and views do; |
| 22 | +the registry, console, and in-app AI assistant render it from there. Two |
| 23 | +disciplines keep it honest: |
| 24 | + |
| 25 | +1. **Derived content is rendered, never written.** Field dictionaries, |
| 26 | + permission matrices, validation lists, state-machine tables are *views |
| 27 | + of metadata* — the platform renders them live; they must not exist as |
| 28 | + authored or generated files. The only legitimate generation is a |
| 29 | + **frozen export** (e.g. a confirmation PDF) bound to a released version. |
| 30 | +2. **Docs bind to what they change with.** Repo layout separates four |
| 31 | + lifecycles: `src/` (ships with the package), `delivery/` (bound to one |
| 32 | + customer's contract, never ships), `internal/` (engineering notes, |
| 33 | + never ships), plus root `CHANGELOG.md` (collected at publish). |
| 34 | + |
| 35 | +``` |
| 36 | +src/docs/ compiled into manifest.docs |
| 37 | +├── meta.json {"pages": ["index", "user", "admin"]} |
| 38 | +├── index.md what this package is (floor: required) |
| 39 | +├── user/ |
| 40 | +│ ├── meta.json {"title": "...", "pages": ["lead", ...]} |
| 41 | +│ └── lead.md … how each role operates it |
| 42 | +└── admin/ … how to run and configure it |
| 43 | +CHANGELOG.md what changed per version (floor: required) |
| 44 | +``` |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## 1. Context |
| 49 | + |
| 50 | +- The manifest schema (`packages/spec/src/kernel/manifest.zod.ts`) has a |
| 51 | + `description` string and nothing else documentation-shaped. ADR-0025 |
| 52 | + defines how code and dependencies travel; nothing defines how *intent* |
| 53 | + travels. |
| 54 | +- Pure-element packages publish as a single self-contained JSON |
| 55 | + (`dist/objectstack.json` → `sys_package_version.manifest_json`, |
| 56 | + ADR-0016). Anything added to the manifest therefore reaches the registry |
| 57 | + **with zero registry schema changes** — the cheapest possible v1. |
| 58 | +- Delivery teams hand customers docs via static sites or files, rebuilding |
| 59 | + versioning (directories), identity (logins), and authorization (per-site |
| 60 | + auth) that the registry/grant system already provides. The cloud repo's |
| 61 | + publisher-catalog design (grants, share tokens) makes the duplication |
| 62 | + explicit: granting a package *should* grant its docs. |
| 63 | +- Field-reference documents generated into git rot by construction: they |
| 64 | + are caches of a render. The pilot project shipped a 26-page generated |
| 65 | + dictionary and deleted it within a day once this principle was named. |
| 66 | +- Going forward AI writes most documentation (ADR-0033 extends to prose). |
| 67 | + The scarce resource is no longer writing effort but a machine-checkable |
| 68 | + definition of "complete" and "too much". |
| 69 | + |
| 70 | +## 2. Goals & Non-goals |
| 71 | + |
| 72 | +### Goals |
| 73 | + |
| 74 | +- A spec'd `docs` manifest element: collection rules, structure, syntax |
| 75 | + boundary, minimal set per package type. |
| 76 | +- Repo layout convention separating the four documentation lifecycles. |
| 77 | +- Platform rendering surfaces (registry page, console help center, |
| 78 | + contextual help, AI-assistant grounding) and their phasing. |
| 79 | +- Quality gates that make AI authorship safe: coverage floor, bloat |
| 80 | + ceiling, fact-checking, usefulness evals. |
| 81 | +- i18n model compatible with AI-derived translations. |
| 82 | + |
| 83 | +### Non-goals |
| 84 | + |
| 85 | +- **Delivery documents** (requirement traceability, solution confirmation, |
| 86 | + acceptance records). They bind to one contract, freeze at milestones, |
| 87 | + and must *not* auto-update with code. Convention: `delivery/`, outside |
| 88 | + the package. Their confirmation workflow is a cloud/process concern. |
| 89 | +- **Standalone product documentation sites** (e.g. docs.objectstack.ai, |
| 90 | + docs.objectos.app). Those are websites; `content/docs/` remains their |
| 91 | + convention. Package docs are package source, not a website. |
| 92 | +- **Internal engineering docs** (`internal/`, formerly `docs/` — renaming |
| 93 | + removes the perpetual "docs vs content/docs" ambiguity; the word *docs* |
| 94 | + appears in exactly one shippable path). |
| 95 | +- Rich media. v1 is text-only by design (§5). |
| 96 | + |
| 97 | +## 3. Design |
| 98 | + |
| 99 | +### 3.1 Collection (one rule) |
| 100 | + |
| 101 | +`src/docs/**/*.md` plus per-directory `meta.json` compile into |
| 102 | +`manifest.docs`. Root `CHANGELOG.md` is collected at publish into the |
| 103 | +version record. `src/` already means "everything here ships"; docs simply |
| 104 | +join objects, views, and dashboards under that single boundary. The |
| 105 | +kernel/plugin-loader **skips or lazy-loads** the `docs` section — manuals |
| 106 | +must not occupy runtime memory. |
| 107 | + |
| 108 | +### 3.2 Structure (closed set + explicit order) |
| 109 | + |
| 110 | +- **First-level directory = docSet**, a *closed* enum: `index.md` |
| 111 | + (overview), `user/`, `admin/`. Closed means renderers know canonical |
| 112 | + order ("what it is → how to use → how to run") and display names without |
| 113 | + configuration, and AI cannot invent new trees. |
| 114 | +- **`meta.json`** (subset of the Fumadocs convention — exactly two fields, |
| 115 | + `title` and `pages`) fixes ordering and directory titles. Absent → |
| 116 | + alphabetical. Page title = first `#` heading; optional frontmatter may |
| 117 | + override. |
| 118 | +- Ordering deliberately lives in a dedicated, schema-validatable file. The |
| 119 | + alternatives each failed a long-term test: single-file-per-docSet mixes |
| 120 | + chapters and destroys diff locality; numeric filename prefixes break |
| 121 | + links on reorder; deriving order from `index.md` link order welds a |
| 122 | + machine contract onto human prose, so every copy-edit becomes a |
| 123 | + structure edit. **Structure and content stay physically separate** for |
| 124 | + the same reason `delivery/` is a directory and not a flag: separation |
| 125 | + beats convention, and a 3-line JSON the AI edits to reorder is a smaller |
| 126 | + blast radius than a prose file it must not accidentally reflow. |
| 127 | + |
| 128 | +### 3.3 Syntax boundary (the two day-one prohibitions) |
| 129 | + |
| 130 | +1. **Pure Markdown** (CommonMark + GFM; a small directive whitelist such |
| 131 | + as admonitions/mermaid may be added later). **MDX is forbidden**: MDX |
| 132 | + is code, and rendering publisher-supplied code inside the platform |
| 133 | + origin crosses the ADR-0025 trust boundary. Markdown is data; a |
| 134 | + sanitizing pipeline renders third-party packages safely. |
| 135 | +2. **No image references in v1** (publish lint rejects ``). |
| 136 | + Binaries in the artifact bloat installs; arbitrary external URLs break |
| 137 | + version immutability (a confirmed v1.3 whose screenshots silently |
| 138 | + change is not confirmed) and leak customer UI data to unmanaged hosts. |
| 139 | + v2 introduces a platform **asset service**: content-addressed, |
| 140 | + immutable, unguessable URLs (`…/blob/sha256-…`); the CLI uploads local |
| 141 | + images at publish and rewrites references; packages keep containing |
| 142 | + text only. Enforcing the ban from day one means zero legacy cleanup |
| 143 | + when assets arrive. |
| 144 | + |
| 145 | +Everything else — docSet declarations beyond the closed set, audience |
| 146 | +config, binds — is **additive** and deliberately absent from v1. Only |
| 147 | +rules that are expensive to retrofit (syntax, images) are mandatory now. |
| 148 | + |
| 149 | +### 3.4 Derived content is rendered, never written |
| 150 | + |
| 151 | +Anything reconstructible from the manifest — field dictionaries, option |
| 152 | +lists, validation conditions, state-machine transitions, permission |
| 153 | +matrices — is a **metadata view**: the console renders it live |
| 154 | +(customer-readable schema browser, permission matrix view); the registry |
| 155 | +renders what package pages need. It must not exist as hand-written prose |
| 156 | +*or* as generated markdown committed to the repo (a cache in git, with a |
| 157 | +cache's lifecycle). Handwritten docs reference metadata via semantic links |
| 158 | +(`object://mtc_lead`) that renderers resolve; publish lint heuristically |
| 159 | +flags field-table blocks in prose. |
| 160 | + |
| 161 | +**The frozen-export exception**: at confirmation milestones a tool may |
| 162 | +render metadata + docs into a versioned artifact (PDF) for signature. |
| 163 | +Freezing is the requirement there; the export is produced once and never |
| 164 | +maintained, so it cannot rot. |
| 165 | + |
| 166 | +### 3.5 Minimal set per package type ("intent four-piece") |
| 167 | + |
| 168 | +| Package type | Required | Rationale | |
| 169 | +|:--|:--|:--| |
| 170 | +| App (consumer-facing, ADR-0019) | `index.md`, `user/`, `admin/`, `CHANGELOG.md` | what it is / how to use / how to run / what changed | |
| 171 | +| plugin / driver / connector | `index.md`, `CHANGELOG.md` | the "user" is a developer; `configuration` schema is already metadata and is rendered, not documented | |
| 172 | + |
| 173 | +Enforced by `os package publish` lint once available (warn → block). Until |
| 174 | +then it is the documented floor. |
| 175 | + |
| 176 | +### 3.6 Distribution & rendering |
| 177 | + |
| 178 | +- **v1 needs no registry change**: docs ride `manifest_json`; package |
| 179 | + visibility and `sys_package_grant` authorization apply to docs |
| 180 | + automatically — granting a package grants its documentation. |
| 181 | +- Rendering surfaces, in expected delivery order: |
| 182 | + 1. **Registry package page** renders `index.md` (the npm-README model) |
| 183 | + and CHANGELOG per version. |
| 184 | + 2. **Console help center**: sidebar tree from directories + `meta.json`, |
| 185 | + markdown body, search. docSet is *not* a UI concept — users see |
| 186 | + "Help", one tree; the closed set only fixes order and (later) |
| 187 | + default visibility (`admin/` → org admins). |
| 188 | + 3. **Contextual help (v2)**: pages may declare `binds: |
| 189 | + [object/view/flow]`; object pages get a "?" opening the bound |
| 190 | + chapter. `binds` also powers coverage lint and staleness nudges |
| 191 | + ("`mtc_lead` changed; its bound page didn't"). |
| 192 | + 4. **AI assistant grounding**: in-app agents answer "how do I…" from the |
| 193 | + package's own manual — same JSON the kernel loads. No UI required; |
| 194 | + likely the highest-frequency consumer of docs and a direct payoff of |
| 195 | + docs-in-manifest. |
| 196 | + |
| 197 | +### 3.7 i18n: translations are derived-but-reviewed |
| 198 | + |
| 199 | +- Single canonical source language per package. Translations are sibling |
| 200 | + files `<page>.<locale>.md`; one tree, one `meta.json`; renderers fall |
| 201 | + back to the source language. |
| 202 | +- In the AI era translations are *derived content with a review gate* |
| 203 | + (lockfile model): generated by AI when the source changes, committed via |
| 204 | + reviewed PR. Each translation records the source content hash; |
| 205 | + `os docs lint` reports stale translations. Locale-directory mirrors are |
| 206 | + rejected — two trees and N copies of `meta.json` drift by construction. |
| 207 | + |
| 208 | +### 3.8 Quality gates for AI authorship |
| 209 | + |
| 210 | +The floor and ceiling are computable *because the software is metadata*: |
| 211 | + |
| 212 | +| Gate | Mechanism | Tool | |
| 213 | +|:--|:--|:--| |
| 214 | +| Completeness (floor) | minimal-set check; `binds` coverage (every object/flow bound by ≥1 page); manifest **version-diff → docs impact checklist** the author must address item-by-item | publish lint / `os docs lint` | |
| 215 | +| Bloat (ceiling) | derived-content ban (§3.4); **token budget** per package tracked like bundle size — docs share the agent context window, so size is a functional constraint, not style | `os docs lint` | |
| 216 | +| Correctness | fact-check agent: extract verifiable claims ("approver is the sales director", "status X can only reach Y") and check them against the manifest; contradictions fail CI | `os docs verify` | |
| 217 | +| Usefulness | Q&A eval: an agent that can see *only the docs* answers real user questions; failures are documentation gaps | `os docs verify` | |
| 218 | + |
| 219 | +Humans review one thing only: **intent** — is the business context right. |
| 220 | +Everything mechanical is gated by machines, which is the only arrangement |
| 221 | +that scales when AI is the author (ADR-0033). |
| 222 | + |
| 223 | +## 4. Alternatives considered |
| 224 | + |
| 225 | +| Alternative | Verdict | |
| 226 | +|:--|:--| |
| 227 | +| Static doc sites per project (Pages/R2 + auth) | Right answer *without* a platform; duplicates registry versioning, identity, and grants when you have one. Acceptable only as a pre-§3.6 transition channel, retired afterwards. | |
| 228 | +| Docs as separate artifact beside the code artifact | Justified only by size; text-only packages make it moot. Revisit with the asset service if ever needed (ADR-0025 already provides the multi-artifact path). | |
| 229 | +| Generated reference markdown in-repo | A cache in git; rots by construction. Replaced by §3.4. Validated empirically on the pilot. | |
| 230 | +| MDX / React components in docs | Code crosses the trust boundary; interactivity comes from renderer-side directives and metadata views instead. | |
| 231 | +| `content/docs/` as the package-docs home | `content/` is the standalone-website convention (framework/cloud doc sites) and is role-empty for packages; package docs are package source → `src/docs/`. | |
| 232 | +| Ordering via filename prefixes / single file / index-link order | See §3.2 — each fails reorder cost, diff locality, or prose/contract separation. | |
| 233 | + |
| 234 | +## 5. Phasing |
| 235 | + |
| 236 | +| Phase | Scope | Depends on | |
| 237 | +|:--|:--|:--| |
| 238 | +| **P0** (no platform change) | Repo conventions (`src/docs`, `delivery/`, `internal/`, CLAUDE.md authoring checklists) on pilot projects; optional transition static site | this ADR | |
| 239 | +| **P1** (framework) | spec `docs` schema; `os build` collection; publish lint (minimal set, MDX/image ban); kernel skip | P0 | |
| 240 | +| **P2** (cloud/console) | registry page rendering → help center → customer-readable metadata views (dictionary, permission matrix) → contextual help via `binds` | P1 | |
| 241 | +| **P3** (quality) | coverage/budget lint, version-diff checklist, fact-check + Q&A eval (`os docs verify`) | P1, AI service | |
| 242 | +| **P4** (enrichment) | asset service + images, audience filtering, frozen PDF export, i18n staleness tooling | P2 | |
| 243 | + |
| 244 | +Project-side conventions (P0) are isomorphic to the P1 schema by |
| 245 | +construction: when the compiler lands, pilot content migrates with zero |
| 246 | +edits. |
0 commit comments