Skip to content

docs(content): apply the approved search-intent title rule to 169 authored pages, short nav labels kept via navTitle - #20170

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12237-title-rule-rollout
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12237-title-rule-rollout

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #12237
Clause-②: no

Summary

Applies the maintainer-approved page-title rule to the authored docs pages that still carried their pre-rule title, and keeps every navigation label exactly as it was through navTitle.

  • 175 approved rows applied verbatim from the table in PR docs(content): propose a search-intent title rule, land it on the four pages with no sidebar cost #12312, each with a navTitle: line holding the page's previous title.
  • 1 approved row re-worded because a required gate rejects the approved text (check:role-word), and 1 authored page the table never listed authored per the rule. Both are listed under Needs maintainer voice review below.
  • One gate premise moved, not loosened: check:runtime-services-index used to hold each kernel/runtime-services/NAME-service.mdx to title: services.NAME. It now reads that accessor premise from the page's navTitle, so the chapter's 8 approved titles land verbatim with navTitle: services.NAME. See The runtime-services gate below. (PM answer to the round-1 open question, review comment 5852831074 on docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237.)
  • 177 docs files, frontmatter only: every one is +2/-1 (title: replaced, navTitle: added). No description:, no page body, nothing under references/** or releases/**, nothing in apps/docs/** or any meta.json. Plus one gate script, scripts/check-runtime-services-index.mjs (file surface amended by the seat for this round).

⛔ Draft, and it stays open for maintainer review — do not self-merge (card body). The landing path after review is the seat's call.

The rule

PRIMARY-KEYWORD — QUALIFIER

What changes on the site, and what does not

Measured in apps/docs at this head. A page's title is read at 12 read points on 5 faces, plus the site search index. All of these take the longer title:

face read points
SERP / Open Graph / Twitter metadata app/[lang]/docs/[[...slug]]/page.tsx generateMetadata: title (232), openGraph.title (237), OG image alt (248), twitter.title (254)
on-page h1 page.tsx:171 DocsTitle
JSON-LD TechArticle headline and name (page.tsx:144,145); the BreadcrumbList leaf crumb (page.tsx:116)
llms.txt / llms-full.txt / /llms.mdx/docs/… app/llms.txt/route.ts:10; lib/source.ts:66 getLLMText
Open Graph card image app/og/docs/[...slug]/route.tsx:18
site search results app/api/search/route.ts, createFromSource(source) (fumadocs indexes page.data.title)

Unchanged, by construction: the page tree, i.e. the sidebar entries and the footer previous/next links. Every page this PR lengthens is a page-tree node, and each one declares navTitle: with its previous title verbatim (same YAML quoting), so navLabel() returns the old string.

How "page-tree node" was measured, not guessed: replaying fumadocs-core 16.14.4 buildFolder() over every meta.json (all 19 authored meta.json list pages explicitly, none uses a rest entry, none lists index) gives 180 of 181 authored pages in the tree: 162 sidebar leaves and 18 folder index pages. A folder index page is not a sidebar row (its folder label comes from meta.json title), but flattenTree() puts folder.index into the footer previous/next list, so it needs navTitle too. The one authored page outside the tree is content/docs/index.mdx, the global root (landed by #12312, not touched here).

The JSON-LD breadcrumb does not pick up the short label: docsTrail() calls getBreadcrumbItems(…, { includePage: false }) and pushes page.data.title itself, pinned by leg D of scripts/check-docs-nav-label.mjs (run green below).

Statistics, re-derived

Population: every content/docs/**/*.mdx outside references/** and releases/**, 181 pages. Rendered length = title + 14. Base 84880f9, head c69d5c7.

pages median min max in 50–60 over 60 under 50 duplicate titles navTitle declared
before 181 32 19 56 6 0 175 0 0
after 181 54 50 60 181 0 0 0 177

Duplicates were also checked against the 225 generated references/** + releases/** titles: no new title collides with any page.

Premise check on the approved table (PR #12312 body, section 6): it parses to 180 rows — 4 landed with #12312 and 176 not landed. On base 84880f9, 176 of 176 pages still carry exactly the table's before, 0 carry its after, 0 paths are missing. The generated-page test reads the file head for a marker, never prose: no authored page carries one (the nine prose hits of the word "generated" in a page head, e.g. api/index.mdx, ui/index.mdx, are body text). One authored page is absent from the table: permissions/tenant-audit-census.mdx (added after #12312; its frontmatter is authored, only a body region is generated by scripts/tenant-audit-census.mjs).

Needs maintainer voice review — not covered by the approved table

Every row here is applied in this diff.

page before approved after applied rendered why
permissions/administrator-guide.mdx Administrator Guide Administrator guide to permissions and roles Administrator guide to permissions and access 59 the approved text fails the required check:role-word gate: "role" is a reserved word (ADR-0090 D3). Smallest change that clears it.
permissions/tenant-audit-census.mdx Tenant-Audit Census — (page not in the table) Tenant-audit census — tenancy write call sites 60 authored page the table never listed; written per the rule. navTitle: Tenant-Audit Census.

The runtime-services gate

scripts/check-runtime-services-index.mjs checks first that each chapter page declares the accessor its filename claims; the other five enumerations rest on that premise. It used to read title. The title rule lengthens title for the search-facing surfaces, so the premise now reads navTitle, which is the page-tree label and, on these 8 pages, the bare accessor.

  • Read from the page's leading frontmatter block only; one layer of matching YAML quotes is syntax, not value. The title is not read at all.
  • Still red: navTitle absent, blank, present only in the body, or not services.NAME. A page that still says title: services.NAME with no navTitle is red too. Each finding names the fix (add \navTitle: services.sms` to the page's leading frontmatter block`).
  • The header prose that stated the title: premise is rewritten to say what the gate now does and why.
  • --self-test: new floored battery The accessor premise lives in navTitle, 7 cases (absent, wrong, old title-only spelling, body-only line and blank are red; a long title and a quoted value are green). Roster floor 8 to 9. The fixture's default title is now a long search-intent string, so every existing green case is also a green with a long title. 59 assertions.
  • Ablation, trapped: scripts/ablation-replace.mjs set navTitle: services.sms to navTitle: services.text in sms-service.mdx (anchor 1 to 0, blob 2001694b2859 to 67f9af393d4d). The gate exited 1: frontmatter navTitle is "services.text", expected "services.sms" to match the filename -- set \navTitle: services.sms`, or rename the page if the accessor really changed. Restored under an EXIT/INT/TERM trap to blob 2001694b2859, which equals HEAD; git diff HEAD` empty.
  • The live tree before the 8 pages were edited: the new gate went red with exactly 8 declares no navTitle findings.

Other readers of these pages' title (git grep over scripts/, packages/, apps/docs, .github/, content/docs): no other gate or script parses it. apps/docs reads it generically through page.data.title, which is the intended change. content/docs/kernel/index.mdx names the accessors in its services table and does not read the page titles. One piece of stale prose: the header of scripts/check-docs-single-h1.mjs (lines 72-73) uses audit-service.mdx title: services.audit as a worked example. Its logic is unaffected and it is green, but the example no longer matches the page. That file is outside this PR's surface, so it is noted here and not edited.

The complete table

All 177 authored pages this PR changes. Rendered = title + | ObjectStack. navTitle = the page's previous title, declared verbatim. ✳️ = in the review section above.

page before after rendered navTitle
ai/actions-as-tools.mdx Actions as Tools MCP tools — expose actions to AI agents 53 yes
ai/agents.mdx AI Agents AI agents — declare tools, model and prompt 57 yes
ai/connect-mcp.mdx Connect an MCP Client MCP client setup — Claude, Cursor and IDEs 56 yes
ai/index.mdx AI Overview AI features — MCP tools, agents and RAG 53 yes
ai/knowledge-rag.mdx Knowledge & RAG RAG — embeddings and knowledge retrieval 54 yes
ai/natural-language-queries.mdx Natural Language Queries Natural language queries over your data 53 yes
ai/skills-reference.mdx AI Skills Reference AI skill reference — every field explained 56 yes
ai/skills.mdx AI Skills System AI skills — reusable instructions for agents 58 yes
ai/tools.mdx Tool Records Tool records — govern what an agent may call 58 yes
api/client-sdk.mdx Client SDK Client SDK — typed JavaScript data access 55 yes
api/data-api.mdx Data API REST data API — CRUD over every object 52 yes
api/data-flow.mdx Data Flow Diagrams Request data flow — from HTTP to driver 53 yes
api/declarative-endpoints.mdx Declarative Endpoints Custom endpoints declared as metadata 51 yes
api/environment-routing.mdx Environment-Scoped Routing Environment routing — dev, preview, prod 54 yes
api/error-catalog.mdx Error Code Catalog API error codes — the complete catalog 52 yes
api/error-handling-client.mdx Client-Side Error Handling Client error handling — retries and codes 55 yes
api/error-handling-server.mdx Server-Side Error Handling Server error handling — throw the envelope 56 yes
api/index.mdx API Overview REST and GraphQL APIs — generated per object 58 yes
api/metadata-api.mdx Metadata & Package API Metadata API — read and publish packages 54 yes
api/plugin-endpoints.mdx Plugin Endpoints Plugin endpoints — add routes from a plugin 57 yes
api/wire-format.mdx Wire Format & JSON Examples API wire format — request and response JSON 57 yes
automation/approvals.mdx Approval workflow Approval chains — multi-step sign-off rules 57 yes
automation/connectors.mdx Connectors Connectors — call external systems safely 55 yes
automation/email-templates.mdx Email Templates Email templates — merge fields and layouts 56 yes
automation/flows.mdx Flow Metadata Flows — DAG automation as typed metadata 54 yes
automation/hook-bodies.mdx Hook & Action Bodies (L1 / L2) Hook and action bodies — the L1/L2 rules 54 yes
automation/hooks.mdx Hooks Record hooks — beforeInsert to afterDelete 56 yes
automation/index.mdx Automation Automation — flows, triggers and schedules 56 yes
automation/jobs.mdx Scheduled Jobs Scheduled jobs — cron automation metadata 55 yes
automation/webhooks.mdx Webhook Delivery Webhooks — outbound delivery and retries 54 yes
automation/workflows.mdx Workflow Metadata Workflow rules — declarative record logic 55 yes
build-without-code.mdx Build Without Code Build business apps without writing code 54 yes
capabilities/ai.mdx AI Under Governance AI under governance — permissioned agents 55 yes
capabilities/analytics.mdx Analytics & Dashboards Analytics — dashboards, reports and charts 56 yes
capabilities/approvals.mdx Approvals Approvals — routing, queues and audit trail 57 yes
capabilities/automation.mdx Automation — Processes That Run Themselves Automation — processes that run themselves 56 yes
capabilities/data.mdx Manage Business Data Business data — objects, fields and records 57 yes
capabilities/forms.mdx Forms & Data Quality Forms and data quality — validation rules 55 yes
capabilities/index.mdx What Can It Do? What can it do? — the capability overview 55 yes
capabilities/integrations.mdx Integrations & Everyday Work Integrations — email, files and everyday work 59 yes
capabilities/permissions.mdx Permissions — Who Sees What Permissions — who sees what, enforced 51 yes
capabilities/request-template.mdx How to Request Features Request a feature — how to describe it 52 yes
capabilities/views.mdx Views — See Data Your Way Views — lists, kanban, calendar and gantt 55 yes
concepts/architecture.mdx Protocol Architecture Protocol architecture — the four layers 53 yes
concepts/design-principles.mdx Design Principles Design principles behind the metadata spec 56 yes
concepts/index.mdx Core Concepts Core concepts — metadata, runtime, protocol 57 yes
concepts/metadata-driven.mdx Metadata-Driven Development Metadata-driven development explained 51 yes
concepts/metadata-lifecycle.mdx Metadata Lifecycle & HMR Metadata lifecycle — load, publish and HMR 56 yes
concepts/north-star.mdx North Star North star — why this framework exists 52 yes
data-modeling/analytics.mdx Analytics Datasets Analytics datasets — modelling for reports 56 yes
data-modeling/drivers.mdx Database Drivers Database drivers — Postgres, MySQL, Mongo 55 yes
data-modeling/external-datasources.mdx External Datasources (Federation) External datasources — query without ETL 54 yes
data-modeling/field-type-decision-tree.mdx Field Type Decision Tree Which field type should I use? A decision tree 60 yes
data-modeling/field-types.mdx Field Type Gallery Field types — the complete visual gallery 55 yes
data-modeling/fields.mdx Field Metadata Field metadata — every option explained 53 yes
data-modeling/formulas.mdx Expressions (CEL) Formulas — CEL expressions on records 51 yes
data-modeling/import-mappings.mdx Import Mappings Import mappings — load external data files 56 yes
data-modeling/index.mdx Data Modeling Data modelling — objects, fields, relations 57 yes
data-modeling/indexing.mdx Database Indexing Database indexes — declare and tune them 54 yes
data-modeling/object-extensions.mdx Object Extensions Object extensions — extend without forking 56 yes
data-modeling/objects.mdx Object Metadata Object metadata — define your data schema 55 yes
data-modeling/queries.mdx Query Syntax Cheat Sheet ObjectQL query syntax — a cheat sheet 51 yes
data-modeling/relationships.mdx Relationships & Lookups Relationships — lookups, master-detail 52 yes
data-modeling/schema-design.mdx Schema Design Schema design — model a business domain 53 yes
data-modeling/seed-data.mdx Seed Data & Fixtures Seed data — fixtures and demo datasets 52 yes
data-modeling/validation-rules.mdx Field Validation Rules Validation rules — reject bad records 51 yes
data-modeling/validation.mdx Validation Metadata Validation metadata — declare the checks 54 yes
deployment/backup-restore.mdx Backup & Restore Backup and restore — protect tenant data 54 yes
deployment/cli.mdx Command Line Interface Command line interface — the os CLI reference 59 yes
deployment/environment-variables.mdx Environment Variables Environment variables — the full list 51 yes
deployment/index.mdx Deployment Overview Deployment — ship a runtime to production 55 yes
deployment/production-readiness.mdx Production Readiness Production readiness — the go-live list 53 yes
deployment/publish-and-preview.mdx Publish, Versioning & Preview Publish, versioning and preview builds 52 yes
deployment/seed-tenancy-repair.mdx Seed Tenancy Repair Repair seed tenancy after a bad import 52 yes
deployment/self-hosting.mdx Self-Hosted Deployment Self-hosting — run your own deployment 52 yes
deployment/single-project-mode.mdx Single-Environment Mode Single-environment mode — the simple setup 56 yes
deployment/tenancy-modes.mdx Tenancy Postures & Membership Tenancy postures and membership models 52 yes
deployment/troubleshooting.mdx Troubleshooting & FAQ Troubleshooting — common errors and fixes 55 yes
deployment/validating-metadata.mdx Validating Metadata Validate metadata before you deploy it 52 yes
getting-started/build-with-claude-code.mdx Build with Claude Code Build an app with Claude Code, step by step 57 yes
getting-started/common-patterns.mdx Common Patterns Common patterns — proven metadata recipes 55 yes
getting-started/examples.mdx Example Apps Example apps — CRM, showcase and more 51 yes
getting-started/glossary.mdx Glossary Glossary — every metadata term defined 52 yes
getting-started/how-ai-development-works.mdx How AI Development Works How AI-written app development works 50 yes
getting-started/index.mdx What is ObjectStack? What is ObjectStack? — a 5-minute intro 53 yes
getting-started/quick-reference.mdx Quick Reference Guide Quick reference — every metadata type 51 yes
getting-started/quick-start.mdx Anatomy of an ObjectStack App Anatomy of an app — your first metadata 53 yes
getting-started/your-first-project.mdx Your First Project Your first project — from zero to running 55 yes
kernel/architecture.mdx Architecture Core architecture — kernel and services 53 yes
kernel/cluster.mdx Cluster Semantics Cluster semantics — multi-node runtimes 53 yes
kernel/contracts/auth-service.mdx IAuthService Contract IAuthService — the authentication contract 56 yes
kernel/contracts/cache-service.mdx ICacheService Contract ICacheService — the cache service contract 56 yes
kernel/contracts/data-engine.mdx IDataEngine Contract IDataEngine — the storage driver contract 55 yes
kernel/contracts/index.mdx Service Contracts Overview Service contracts — the kernel interfaces 55 yes
kernel/contracts/metadata-service.mdx IMetadataService Contract IMetadataService — the metadata contract 54 yes
kernel/contracts/storage-service.mdx IStorageService Contract IStorageService — the file storage contract 57 yes
kernel/events.mdx Events & Hooks Kernel events and hooks — the full list 53 yes
kernel/index.mdx Kernel & Services Kernel and services — the runtime core 52 yes
kernel/runtime-services/audit-service.mdx services.audit services.audit — the audit log service API 56 yes
kernel/runtime-services/data-service.mdx services.data services.data — the record CRUD service API 57 yes
kernel/runtime-services/email-service.mdx services.email services.email — the outbound mail API 52 yes
kernel/runtime-services/examples.mdx Runtime Service Examples Runtime service examples — copyable code 54 yes
kernel/runtime-services/index.mdx Runtime Service APIs Runtime service APIs — the services object 56 yes
kernel/runtime-services/queue-service.mdx services.queue services.queue — the job queue service API 56 yes
kernel/runtime-services/settings-service.mdx services.settings services.settings — the settings API 50 yes
kernel/runtime-services/sharing-service.mdx services.sharing services.sharing — the record share API 53 yes
kernel/runtime-services/sms-service.mdx services.sms services.sms — the text message service API 57 yes
kernel/runtime-services/storage-service.mdx services.storage services.storage — the file store API 51 yes
kernel/runtime-services/versioning.mdx Runtime Service API Versioning Runtime service API versioning policy 51 yes
kernel/services-checklist.mdx Kernel Services Checklist Kernel services checklist for reviewers 53 yes
kernel/services.mdx Service Registry Service registry — resolve and override 53 yes
permissions/access-matrix.mdx Access-Matrix Snapshot Gate Access matrix snapshot gate explained 51 yes
permissions/access-recipes.mdx Who can see data / automation / interface Access recipes — data, automation and UI 54 yes
permissions/administrator-guide.mdx ✳️ Administrator Guide Administrator guide to permissions and access 59 yes
permissions/attachments-access.mdx Attachments Access Attachment access — who can read a file 53 yes
permissions/authentication.mdx Authentication Authentication — sessions, tokens, SSO 52 yes
permissions/authorization.mdx Authorization Architecture Authorization architecture — how it decides 57 yes
permissions/capabilities.mdx Declaring Capabilities Declaring capabilities in a permission set 56 yes
permissions/delegated-administration.mdx Delegated Administration Delegated administration — scoped admin rights 60 yes
permissions/explain.mdx Explain Engine Explain engine — why access was denied 52 yes
permissions/field-level-security.mdx Field-Level Security Field-level security — hide and mask fields 57 yes
permissions/index.mdx Permissions & Identity Permissions and identity — the overview 53 yes
permissions/permission-metadata.mdx Permission Metadata Permission metadata — every option explained 58 yes
permissions/permission-sets.mdx Permission Sets Permission sets — grant access in bundles 55 yes
permissions/permissions-matrix.mdx Security Permissions Matrix Security permissions matrix reference 51 yes
permissions/positions.mdx Positions Positions — org hierarchy for sharing 51 yes
permissions/profiles.mdx Profiles (removed) Profiles (removed) — use permission sets 54 yes
permissions/record-view-auditing.mdx Record-View Auditing Record view auditing — who read what 50 yes
permissions/rls.mdx Row-Level Security (RLS) Row-level security (RLS) — filter by rule 55 yes
permissions/sharing-rules.mdx Sharing Rules Sharing rules and organization-wide defaults 58 yes
permissions/sso.mdx Social & Enterprise SSO SSO — social and enterprise sign-in setup 55 yes
permissions/system-context.mdx System Context (isSystem) System context (isSystem) — bypass rules 54 yes
permissions/tenant-audit-census.mdx ✳️ Tenant-Audit Census Tenant-audit census — tenancy write call sites 60 yes
plugins/adding-a-metadata-type.mdx Adding a Metadata Type Add a custom metadata type from a plugin 54 yes
plugins/anatomy.mdx Plugin Anatomy Plugin anatomy — files, hooks and exports 55 yes
plugins/development.mdx Plugin Development Plugin development — build and test one 53 yes
plugins/index.mdx Plugin System Plugin system — extend the runtime safely 55 yes
plugins/packages.mdx Package Overview Package overview — what each one does 51 yes
protocol/backward-compatibility.mdx Backward Compatibility Policy Backward compatibility policy for the spec 56 yes
protocol/diagram.mdx Protocol Relationship Diagram Protocol relationship diagram explained 53 yes
protocol/index.mdx Protocol Specification Protocol specification — the open format 54 yes
protocol/kernel/config-resolution.mdx Configuration Resolution Configuration resolution order and layers 55 yes
protocol/kernel/error-handling.mdx Error Handling Error handling — the response envelope 52 yes
protocol/kernel/http-protocol.mdx HTTP API HTTP API protocol — routes and verbs 50 yes
protocol/kernel/i18n-standard.mdx Internationalization Standard Internationalization standard for metadata 56 yes
protocol/kernel/index.mdx Kernel: The System Protocol Kernel — the system protocol specification 56 yes
protocol/kernel/lifecycle.mdx System Lifecycle System lifecycle — from boot to ready state 57 yes
protocol/kernel/metadata-service.mdx Metadata Service Metadata service protocol specification 53 yes
protocol/kernel/plugin-spec.mdx Plugin Package Specification Plugin package specification and manifest 55 yes
protocol/kernel/realtime-protocol.mdx Real-Time Protocols Real-time protocols — websockets, SSE 51 yes
protocol/knowledge.mdx Knowledge Protocol Knowledge protocol — RAG as metadata 50 yes
protocol/objectql/query-syntax.mdx Query Syntax ObjectQL query syntax — the full specification 60 yes
protocol/objectql/schema.mdx Schema Definition ObjectQL schema definition specification 54 yes
protocol/objectql/security.mdx Security & Access Control ObjectQL security and access control 50 yes
protocol/objectql/state-machine.mdx State Machine (Lifecycle) State machine — record lifecycle spec 51 yes
protocol/objectql/types.mdx Type System ObjectQL type system — the specification 54 yes
protocol/objectui/actions.mdx Action Protocol Action protocol — buttons as metadata 51 yes
protocol/objectui/concept.mdx UI as Data Concept UI as data — the core ObjectUI concept 52 yes
protocol/objectui/layout-dsl.mdx Layout DSL Layout DSL — arrange fields declaratively 55 yes
protocol/objectui/widget-contract.mdx Widget Contract Widget contract — build a custom widget 53 yes
ui/actions.mdx Actions Actions — permission-checked UI buttons 53 yes
ui/apps.mdx App Metadata App metadata — navigation and branding 52 yes
ui/audience-based-interfaces.mdx Audience-based interfaces Audience-based interfaces — one app, many 55 yes
ui/create-vs-edit-form.mdx Create form ≠ edit form Create form vs edit form — the differences 56 yes
ui/dashboards.mdx Dashboard Metadata Dashboard metadata — charts and KPIs 50 yes
ui/doc-pages.mdx Doc Metadata Doc metadata — in-app documentation pages 55 yes
ui/field-grouping-and-order.mdx Field grouping & order Field grouping and order in generated forms 57 yes
ui/forms.mdx Forms (Public + Internal) Forms — public and internal data entry 52 yes
ui/index.mdx UI Engine UI engine — render views from metadata 52 yes
ui/pages.mdx Page Metadata Page metadata — build custom app screens 54 yes
ui/public-data-collection.mdx Collect data from the public Collect data from the public with web forms 57 yes
ui/react-pages.mdx React Pages React pages — escape hatch for custom UI 54 yes
ui/reports.mdx Report Metadata Report metadata — grouped, filtered data 54 yes
ui/setup-app.mdx Setup App Setup app — administer a running runtime 54 yes
ui/translations.mdx Translations Translations — labels and UI text per locale 58 yes
ui/views.mdx View Metadata View metadata — list, kanban, calendar 52 yes
upgrading.mdx Upgrading Upgrade guide — move between major versions 57 yes

Verification

All at head c69d5c7, git status --porcelain empty. origin/main was merged in twice during the rework round (now at merge base e2c4e12); neither merge touched this PR's files.

Gate list derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (178 paths vs merge base e2c4e12, three-dot; 640 changed lines, under the 5,000 human-merge threshold), then reconciled: --ran answered 74 derived famil(ies) accounted for — 74 run, 0 NOT-MEASURED (a DERIVED zero — all 74 recorded an exit code and none of them is 3). Each command's exit code was captured before any pipe:

exit 0  node scripts/check-ci-filter-parity.mjs
exit 0  node scripts/check-closing-keyword-parity.mjs
exit 0  node scripts/check-closing-keyword-parity.mjs --self-test
exit 0  node scripts/check-comment-mask-corpus.mjs
exit 0  node scripts/check-declaration-mirrors.mjs
exit 0  node scripts/check-declaration-mirrors.mjs --self-test
exit 0  node scripts/check-doc-frontmatter.mjs
exit 0  node scripts/check-doc-frontmatter.mjs --self-test
exit 0  node scripts/check-doc-route-spelling.mjs --advisory
exit 0  node scripts/check-doc-route-spelling.mjs --self-test
exit 0  node scripts/check-docs-section-name.mjs
exit 0  node scripts/check-docs-section-name.mjs --self-test
exit 0  node scripts/check-scripts-symbol-anchors.mjs
exit 0  node scripts/check-scripts-symbol-anchors.mjs --self-test
exit 0  node scripts/check-section-landing-index.mjs
exit 0  node scripts/check-section-landing-index.mjs --self-test
exit 0  node scripts/check-self-test-wired.mjs
exit 0  node scripts/check-self-test-wired.mjs --self-test
exit 0  node scripts/check-self-test-workflow-commands.mjs
exit 0  node scripts/check-self-test-workflow-commands.mjs --self-test
exit 0  node scripts/check-system-context-census.mjs
exit 0  node scripts/check-system-context-census.mjs --self-test
exit 0  node scripts/check-tenant-audit-census.mjs
exit 0  node scripts/check-tenant-audit-census.mjs --self-test
exit 0  node scripts/check-whole-set-label-write.mjs
exit 0  node scripts/check-whole-set-label-write.mjs --self-test
exit 0  node scripts/docs-audit/check-drift-comment.mjs
exit 0  node scripts/pm/bare-root-worklist.mjs --self-test
exit 0  pnpm --filter @objectstack/lint run check:doc-formula-expressions
exit 0  pnpm --filter @objectstack/lint run check:doc-security-posture
exit 0  pnpm --filter @objectstack/spec run check:docs
exit 0  pnpm --filter @objectstack/spec run check:empty-state
exit 0  pnpm --filter @objectstack/spec run check:liveness
exit 0  pnpm --filter @objectstack/spec run check:skill-docs
exit 0  pnpm --filter @objectstack/spec run check:skill-examples
exit 0  pnpm --filter @objectstack/spec run check:strictness-ledger
exit 0  pnpm --filter @objectstack/spec run check:variant-docs
exit 0  pnpm --filter @objectstack/spec run check:yaml-examples
exit 0  pnpm check:agent-test-spelling
exit 0  pnpm check:bash32-floor
exit 0  pnpm check:cli-command-ids
exit 0  pnpm check:cli-examples-parity
exit 0  pnpm check:corpus-claim-drift
exit 0  pnpm check:cross-package-test-inputs
exit 0  pnpm check:doc-anchors
exit 0  pnpm check:doc-authoring
exit 0  pnpm check:docs-audit-scope
exit 0  pnpm check:docs-image-tag
exit 0  pnpm check:docs-redirects
exit 0  pnpm check:docs-single-h1
exit 0  pnpm check:docs-spec-enumerations
exit 0  pnpm check:docs-transcript-drift
exit 0  pnpm check:driver-memory-census
exit 0  pnpm check:entry-guard
exit 0  pnpm check:error-status-conformance
exit 0  pnpm check:gitlink-declared
exit 0  pnpm check:lockstep-package-count
exit 0  pnpm check:merge-driver
exit 0  pnpm check:nul-bytes
exit 0  pnpm check:overlay-whitelist-table
exit 0  pnpm check:parse-guard
exit 0  pnpm check:pm-dispatch-gates
exit 0  pnpm check:pm-widening-tells
exit 0  pnpm check:pnpm-filter-targets
exit 0  pnpm check:published-readme-links
exit 0  pnpm check:quick-reference-counts
exit 0  pnpm check:ratchet-remedy-authority
exit 0  pnpm check:react-page-adapter-contract
exit 0  pnpm check:refd-timer-probe
exit 0  pnpm check:role-word
exit 0  pnpm check:runtime-services-index
exit 0  pnpm check:skill-identifier-liveness
exit 0  pnpm check:vendor-version-stamps
exit 0  pnpm check:watch-hint-literal
exit 0  node scripts/check-docs-nav-label.mjs
exit 0  node scripts/check-docs-nav-label.mjs --self-test

Verdict lines:

  • check-doc-anchors: 376 internal #fragment link(s) across 411 source file(s) all resolve to a real heading (the card's acceptance gate)
  • check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 406, content/blog 3.
  • check-docs-nav-label: navTitle named in code by 2 file(s) only; resolver executed; plugin wired into the docs loader; JSON-LD breadcrumb leg green. It reads apps/docs/** only, so a navTitle: frontmatter line is not judged by it; run as a guard that the mechanism this PR relies on is intact.
  • check-runtime-services-index --self-test: 59 assertions … and check-runtime-services-index: 8 chapter page(s) vs meta.json "pages" … both green, with the 8 approved titles applied.

For the record, round 1: on the verbatim table check:role-word and check:runtime-services-index exited 1. Both were real findings: the first is handled by the re-worded row, the second by the gate change above. check:skill-examples exited 3 (PREREQUISITE NOT MET, stale client-react declarations, nothing measured) until @objectstack/client-react was built. All green at this head.

NOT MEASURED locally: the card's "link checker" is .github/workflows/check-links.yml (lychee, advisory lane); lychee is not installed in this container. It runs on this PR in CI. This diff changes no link, link text or file path, only two frontmatter keys. A full next build of apps/docs was not run locally; fumadocs-mdx regenerated the source index at exit 0, and check-doc-frontmatter parses every page with the yaml version the build resolves.

pnpm lint is not owed: .mdx is outside the ESLint population and no other file type is touched.

No changeset: docs content only, publishes nothing. skip-changeset applied.

Acceptance notes


Generated by Claude Code

…eeping short nav labels via navTitle

Every authored page still carrying its pre-rule title now takes the approved
search-intent title (`<primary keyword> — <qualifier>`, 36-46 characters so
the rendered title with the ` | ObjectStack` suffix lands in 50-60), and
declares `navTitle:` with its previous short title so the page tree -- the
sidebar and the footer previous/next links -- reads exactly as before.

176 rows are applied verbatim from the approved table; one authored page the
table never listed (permissions/tenant-audit-census.mdx) is authored per the
rule and flagged for maintainer voice review.

Claude-Session: https://claude.ai/code/session_018bR89KaSkoZVqgBnUYXtyD
Co-authored-by: Claude <noreply@anthropic.com>
…le" from the administrator guide title

check:runtime-services-index holds each runtime-services page's title to
its `services.<name>` accessor, so the eight approved rows for that chapter
cannot land from frontmatter alone; they are restored to base and left for a
maintainer ruling. check:role-word rejects the reserved word in the approved
administrator-guide title, so that row takes "permissions and access".

Claude-Session: https://claude.ai/code/session_018bR89KaSkoZVqgBnUYXtyD
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 27, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 27, 2026
…, so the chapter takes its approved titles

check:runtime-services-index held each kernel/runtime-services/NAME-service.mdx
to `title: services.NAME`, the premise its five enumerations rest on. The
site-wide title rule lengthens `title` for the search-facing surfaces, so the
premise moves to the page's `navTitle` -- the page-tree label, which on these
pages is the bare accessor. It does not loosen: an absent, blank, body-only or
mismatched navTitle is red, the old title-only spelling is red, and each
finding names the fix. The eight pages take their approved titles verbatim
and declare `navTitle: services.NAME`.

Self-test: new floored battery 'The accessor premise lives in navTitle'
(7 cases), roster floor 8 -> 9.

Claude-Session: https://claude.ai/code/session_018bR89KaSkoZVqgBnUYXtyD
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读 · 需要你过目两行标题 · 2026-09-27T05:53Z

本 PR 交付 #12237(p1)。席位复核结论 ACCEPT(#12237 评论 5853145345),CI 全绿。按卡面要求,它等你审过才落地。

改了什么

  • 177 个作者文档页换了标题。其中 175 行逐字取自你 8/26 批准的规则和对照表(「其他同意」)。
  • 每页都加了 navTitle,保留原来的短标签,所以侧栏和上一页/下一页链接一字不变。
  • 改了一个门禁 check-runtime-services-index:它原先要求这 8 页的标题等于 services.xxx,现在改从 navTitle 读这个访问器名。严格度不变,缺失或写错照样报红。

为什么改

风险与代价(含回滚)

  • 只动了 frontmatter,页面正文零改动。
  • 搜索结果标题、页内 h1、OG 分享卡、llms.txt 会跟着变长,侧栏不变。
  • 回滚是 revert 一个 squash 提交。

席位意见:下面两行不在你批准的表里,席位建议都通过。

页面 你批准的文字 实际用的 为什么
permissions/administrator-guide.mdx Administrator guide to permissions and roles Administrator guide to permissions and access 门禁 check:role-word(ADR-0090 D3)不允许在这里用 "role" 一词,这是改动最小的写法
permissions/tenant-audit-census.mdx (表里没有这一页) Tenant-audit census — tenancy write call sites 新页面,按同一规则写

四棱:

  • 实际需求:两行都是真实页面,标题都在 50–60 字符带内。
  • 长远:第一行是被门禁强制的;如果回到 "roles",就要先改门禁的词表。
  • 防 AI 犯错:门禁照旧响亮拒收。
  • 不扩散:零新增能力。

你要做的(一个动作):在本 PR 回复「同意」,或者点名要改哪一行、改成什么。收到后,席位把 PR 转为 ready,经合并队列落地。


Generated by Claude Code

This was referenced Sep 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Maintainer ruling recorded: both rows approved, land it · 2026-09-27T14:42Z

domain:devx seat 2, session_018mA64scZ8fmpiPkrHVAwXj. This answers the quick-read 5853150565 above.

谁的指令: the maintainer
原话:

同意,两行都通过,落地吧
在哪说: the chat of session session_018mA64scZ8fmpiPkrHVAwXj, answering the quick-read's single ask.

  • Both rows are approved as applied: permissions/administrator-guide.mdx → "Administrator guide to permissions and access", and permissions/tenant-audit-census.mdx → "Tenant-audit census — tenancy write call sites".
  • Landing pre-checks at head c69d5c7:
    • Not governed: check-governed-merges.mjs --branch finds 0 of 178 paths on the register, and the size is 640 lines, under 5,000.
    • Internal head repo.
    • mergeable_state is clean.
    • check-expected-skips.mjs --pr 20170: OK, all 17 skipped runs are on the roster, and every other run is success.
    • Clause-② is no, and nothing is under packages/spec/src/**.
    • content/docs/permissions/system-context.mdx is an os-regen mixed file (mixed: 'line-anchors'). This PR touches only its hand-written frontmatter, not the generated half. main has not touched the file since merge base e2c4e12, and check:system-context-census was green in the dev's union. So no section-A regeneration is owed; the queue re-runs the census on the merged tree.
  • Next: needs-user-decision comes off. Ready → auto-merge through the merge queue. Then verify the squash on main and close out docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20 #12237.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 14:43
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 5983d11 Sep 27, 2026
55 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12237-title-rule-rollout branch September 27, 2026 15:13
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Maintainer's word recorded — this PR lands. Director seat (summon #30 续) · session_01AsCNgFBs8HCjwhyHQsFbx3 · 2026-09-27T15:15Z.

  • The seat's 速读 5853150565 asked the maintainer to pass the two title rows outside the approved table (permissions/administrator-guide.mdx → 「Administrator guide to permissions and access」, forced by the check-role-word ratchet, ADR-0090 D3; permissions/tenant-audit-census.mdx → 「Tenant-audit census — tenancy write call sites」, a page the table never listed). Both were presented again in the director seat's batch 🔗 Broken links detected in documentation #228 with the full reading; the maintainer answered, verbatim: 「20170 之前已批复」 — approved earlier, standing. Recorded here per the charter (回批原话逐 PR 记到 PR 上).
  • Landing pre-checks on head c69d5c7aa3, re-read by the director seat: the seat ACCEPT 5853145345 at this head; 54 check runs, 34 success, 20 path-skipped (a docs-only diff), 0 red; git merge-tree --write-tree origin/main refs/pull/20170/head at a91d12af2c exits 0 with no conflict; 178 files, none under a governed path (content/docs/** and one gate script). Not Tier H.
  • Act: needs-user-decision off, pr_ready + automerge_enable through the relay; the merge queue lands it, and Fixes #12237 closes the card. docs(spec): carry the page-title rule into the docs GENERATOR — 225 of 405 pages are emitted, so a hand edit is reverted (split (b) of #12237) #15403 (the generator-emitted pages) unlocks on merge.

Generated by Claude Code

akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…on the dry run, the commit and validate alike (objectstack-ai#20208)

Fixes objectstack-ai#20150

Clause-②: yes (narrowing)

## What this does

`ImportFieldMappingSchema.target` is declared as "Target object
field(s)". A mapping whose target named no field of its `targetObject`
passed `objectstack validate`, answered `ok` on every row of the dry
run, and then failed every row on the commit with `INVALID_FIELD`. The
dry run promised what the commit refused (objectstack-ai#4633's contract is that the
dry run predicts the write).

One verdict now decides what a target may name, and both doors call it:

- **`@objectstack/spec`**
(`packages/spec/src/data/import-mapping-target.ts`, re-exported from
`mapping.zod.ts` beside the schema it judges):
`unknownImportMappingTargets(fieldMapping, objectDef)`, built on
`indexImportMappingTargets` + `judgeImportMappingTarget` +
`importMappingEntryTargets`. It lives in the spec because
`@objectstack/rest` and `@objectstack/lint` both depend on the spec and
neither depends on the other (triage `5853335141`).
- **The import door** (`packages/rest/src/import-prepare.ts`,
`import-mapping.ts`): `prepareImportRequest` resolves the object
definition first and refuses a named mapping with a target that names no
field BEFORE any row, with `400 INVALID_FIELD`. Both the dry run and the
commit arrive through that function, so they give the same answer (the
async import-job route shares it too). `rest-server.ts` is untouched.
- **The author-time check**
(`packages/lint/src/validate-mapping-target-fields.ts`): a new
reference-integrity suite member, `validateMappingTargetFields`, rule
`mapping-target-field-unknown`, severity `error`, located at
`mappings[i].fieldMapping[j].target` and naming the mapping and the
object. The suite is the one table `os validate`, `os lint` and `os
build` share, so there is no validate-only copy in `packages/cli`.

### What a target may name (and the false-refusal trap)

A target resolves when it names (1) a declared field, (2) a column the
platform provisions on THAT object, from `resolveInjectedSystemColumns`
(`owner_id` only where the object carries it, and so on), or (3) `id` /
`created_at` / `updated_at`, which the engine's write door
(`PLATFORM_PROVISIONED_COLUMNS` in `undeclaredWriteFieldErrors`) admits
on every object, `systemFields: false` included. Row 3 is what keeps the
new refusal from being narrower than the write: measured below, a
`created_at` target on a `systemFields: false` object is written by the
commit. An object with no readable, non-empty field map is not judged at
all (no opinion, never a refusal), the engine's own position on the same
input.

Measured at the door (probe against the real engine + protocol + sqlite,
HEAD `eab27ef780`): the object definition `getMetaItem` serves carries
the injected columns (`created_at, created_by, organization_id,
owner_id, owning_business_unit_id, updated_at, updated_by`) and the
fields an `objectExtensions` entry merges in. The lint side reads the
authored stack, so it folds `objectExtensions` fields (from the stack
and from `packages[].manifest`) into the object before judging, and it
skips a mapping whose `targetObject` this stack does not define, the
same skip its field-existence siblings take.

### Where objectstack-ai#20149's arm goes

objectstack-ai#20149 is not addressed here (ruled A, `Blocked-by` this card). Its
compound-part capability extends this verdict by ONE arm in
`judgeImportMappingTarget`, at the marked comment, returning a new
`kind: 'part'` member of `ImportMappingTargetVerdict`; it reads the head
field's definition from `ImportMappingTargetIndex.fields`, which carries
the declared definitions for exactly that purpose. It never becomes a
second predicate. Until it lands, `mailing_address.street` names no
field and is refused, which is this card's pin.

## Before and after (measured)

Before, with the door's refusal ablated at HEAD (the same door
origin/main runs), mapping target `mailing_address.street` on object
`task`:

- dry run: `200`, `ok: 2, errors: 0`
- commit: `200`, `ok: 0, errors: 2`, each row `INVALID_FIELD` "Unknown
field 'mailing_address.street' on object 'task'"

After: both answer `400` `{ code: 'INVALID_FIELD' }`, byte-identical
bodies, nothing written. The refusal reuses the code the commit already
carried, so no error-code ledger entry is added.

## Tests

The card's pin is in `packages/rest/src/import-integration.test.ts`
(real engine, real protocol, sqlite): a target that names no field is
refused on the dry run and on the commit alike; each element of a
`split` target is judged; a valid mapping is the control (dry run and
commit agree on `ok`); and a platform column the door accepts
(`created_at` / `updated_at` on a `systemFields: false` object,
`owner_id` on a default one) stays green at both ends. The lint rule is
red on the bad mapping and green on the control
(`validate-mapping-target-fields.test.ts`), and the suite membership pin
gains the member with a live finding
(`reference-integrity-suite.test.ts`). The verdict itself is pinned in
`packages/spec/src/data/import-mapping-target.test.ts`.

Local results at `f35d38b16d` (all through `os-verify-lock.sh`, shared
box):

- `@objectstack/spec`: `vitest run --project local` 541 files / 15849
tests pass; `typecheck` (tsc, scripts, test layer) exit 0;
`check:generated` all 15 artifacts up to date after `gen:api-surface` +
`gen:export-origins` (committed).
- `@objectstack/lint`: `vitest run` 109 files / 4183 tests pass;
`typecheck` exit 0.
- `@objectstack/rest`: `vitest run --project local` 197 files / 3347
pass, 1 skipped; `typecheck` exit 0.
- `@objectstack/cli` (unit): `test/validate-build-gate-parity.test.ts`
22 pass. The `integration` layer (the spawned-CLI
`authoring-rule-command-parity`) is declared to CI.
- Gates: `dispatch-gates.mjs --commands` derived 85 families; `--ran`
reconciles 83 run green, 2 NOT MEASURED, 0 unrun. NOT MEASURED:
`check:dual-build-cjs-loads` and `check:type-check-debt`, both
PREREQUISITE NOT MET (exit 3, they read a whole-repo build this worktree
does not have).
- ESLint over the 12 changed `.ts` files (`--no-inline-config`): 0
errors, 0 warnings. The repo-wide `pnpm lint` is CI's.

### Ablation (one-shot, restored and proven byte-identical to HEAD)

Each leg mutated through `scripts/ablation-replace.mjs` (anchor hit 1,
blob changed, restored by `git checkout HEAD`, hash equal to the HEAD
blob after):

- A, the door stops refusing (`import-prepare.ts`): the two refusal
tests in `import-integration.test.ts` go red; the controls stay green.
- B, the lint rule stops reporting: 3 rule tests and the suite's "every
member runs" test go red.
- C, the spec verdict admits every name: 3 verdict tests go red.

## Acceptance notes

- `content/docs/data-modeling/import-mappings.mdx` is not edited: its
"Rejections before any row is read" table does not list the new
`INVALID_FIELD` row and its build-validation list does not mention the
new lint rule. No sentence in it turns false, so it is outside this
card's surface (its frontmatter is held by PR objectstack-ai#20170). Carrier: none.
- The engine keeps its own `PLATFORM_PROVISIONED_COLUMNS` literal in
`@objectstack/objectql`; the spec constant
`IMPORT_TARGET_ALWAYS_ADDRESSABLE_COLUMNS` mirrors it. Converging the
engine onto the spec constant is outside this surface. Carrier: none.
- The runtime publish gate does not judge `mapping` writes: the new
member keeps the frozen `flow` default, and a flow snapshot carries no
`mappings`. The import door refuses a bad mapping at use either way.
- `upsertKey` naming no field is not judged here (the card is about
`target`).

---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…gent chat route (objectstack-ai#20220)

Fixes objectstack-ai#19958
Clause-②: no

Docs only. One section of
`content/docs/protocol/kernel/error-handling.mdx` is rewritten: the
`QUOTA_EXCEEDED` entry. No runtime, spec or error-code-ledger change.
`QUOTA_EXCEEDED` stays registered, as the narrowed ruling on objectstack-ai#17707
(`5807013768`) keeps it.

## What the page said, and what it says now

Removed (it was false):

> **Envelope:** none — no producer emits it
>
> **Not emitted today.** `QUOTA_EXCEEDED` is a registered member of the
standard error-code catalog (…), but no ObjectStack producer emits it.
No response carries this code, and none carries a quota `details` bag —
do not write a client branch against it.

Added (each sentence rests on a measurement below):

> **Envelope:** nested — emitted by one route only, the ObjectOS agent
chat route, and only to a JSON-mode request
>
> **One producer.** `POST /api/v1/ai/agents/:agentName/chat` answers
`QUOTA_EXCEEDED` when the deployment has switched on its per-user daily
chat-turn cap and the calling user has spent that cap. […] No other
route emits this code.
>
> **JSON mode only.** The route streams by default. A request whose
`stream` flag is absent or `true` gets the refusal as an ordinary
assistant text message on HTTP 200, and no error code reaches it. Only a
request with `stream: false` gets the 429 body below.
>
> `error.details.resetAt` is an ISO-8601 timestamp: the moment the cap
lifts. It is the only recovery time the response carries. The route sets
no `Retry-After` header and sends no `retryAfterSeconds`.

Kept as it was: the SMS sentence (`the SMS daily quota answers
TOO_MANY_REQUESTS`) and the pointer to `RATE_LIMIT_EXCEEDED` for request
pacing. The SMS sentence still holds on `main`:
`packages/services/service-sms/src/sms-daily-quota.ts:85` is
`SMS_QUOTA_EXCEEDED_CODE = 'TOO_MANY_REQUESTS'`. The only change there
is its first word, "Quota enforcement that does exist" → "Other quota
enforcement".

## The producer, measured (cloud `main` `48d70663`)

The producer and reader are cited here, not in the doc, following the
dispatch's route.

- `packages/service-ai/src/routes/agent-routes.ts:572-575`: `return
sendError(429, 'QUOTA_EXCEEDED', message, { details: { resetAt:
decision.resetAt }, category: 'rate_limit' })`.
- `agent-routes.ts:527`: `const wantStreamMode = body.stream !==
false;`. At `:552-565` a streaming request gets `status: 200` with the
copy as one `text-delta` part, so there is no code on that path.
- `packages/service-ai/src/routes/envelope.ts:188-198`: `sendError`
writes `{ success: false, error: { code, message, httpStatus: status,
...extra } }`.
- `packages/service-ai/src/plugin.ts:1154-1159`: the gate exists only
when the deployment sets its daily-turn knob to a positive number, and
the only implementation wired is `DailyMessageQuota`. Its refusal always
sets `resetAt` (`quota/agent-chat-quota.ts:92-96`).
- No `Retry-After` on this path. `sendError` returns status and body
only, and no cloud writer adds that header to this route.
- Pinned in cloud by
`packages/service-ai/src/__tests__/agent-error-envelope.conformance.test.ts:300-315`
(status 429, code, `details` equal to `{ resetAt }`).
- Control: objectstack `main` has no producer of this code. `git grep
QUOTA_EXCEEDED` at `e7f69dbb` gives 40 lines: docs, generated
references, the registration in
`packages/spec/src/api/errors.zod.ts:106`, a ledger test, a status
baseline, and the SMS `SMS_QUOTA_EXCEEDED_*` constants whose value is
`TOO_MANY_REQUESTS`.

## The readers, measured

objectui, pin `f8a9d0fb` (current `.objectui-sha`) and `main`
`25c7d584`. `tool-display.ts`, `tool-display.test.ts` and
`useObjectChat.ts` are byte-identical between the two.

- `packages/plugin-chatbot/src/tool-display.ts:314-326` and `:336`:
`parseAiQuotaError` deliberately does NOT recognize `QUOTA_EXCEEDED`.
- The 429 is routed by `isUnsentSendError` (`:404`) and
`isRateLimitError` (`:423`). They key on the HTTP status tagged by
`sendAwareFetch`, or on a raw-text regex.
- objectui reads no field of this body: not `code`, not
`details.resetAt`, not `category`. The card's "objectui branches on it
(`error.details.resetAt`, `category`)" comes from a comment at `:317`
that describes the producer. No read in objectui matches it.
- objectui's chat defaults to `streamingEnabled = true`
(`useObjectChat.ts:768`, sent as `stream` at `:931`). Its default path
therefore receives the in-band text on HTTP 200. The 429 branch is
reached only when a chatbot schema sets `streamingEnabled: false`, and
it is pinned by fixture (`tool-display.test.ts:365-390`).

`@objectstack/client`, this repo at `e7f69dbb`:

- `client.ai.agents.chat()` sends `stream: false`
(`packages/client/src/index.ts:6648-6653`). It throws an error carrying
`code`, `category`, `details` and `httpStatus` (from `res.status`)
(`:7376-7405`).
- `chatStream()` sends `stream: true` (`:6662-6667`).

Field mismatches (dispatch Zone 2 item 2):

- **Sent by the producer, ignored by the reader:**
- objectui ignores all of `code`, `details.resetAt`, `category` and
`httpStatus`, and reads `message` only through the regex probe.
  - The SDK reads every sent field.
- **Read by a reader, not sent:** the SDK's `error.retryable`, which is
`undefined` here.

## Choices this PR settled

- **"ObjectOS", not "hosted".** Triage's wording was 托管的 AI agent 对话路由.
The docs' own term for where this route lives is the callout in
`content/docs/ai/index.mdx`: the in-product chat runtime "ships in
**ObjectOS**". The section links there.
- **The example shows `httpStatus: 429`.** Triage ruled 「⛔ 不要自行补充字段」.
`httpStatus` is not an added field: the producer's `sendError` writes it
on every body (`envelope.ts:198`), and leaving it out would misdescribe
the wire. The field list under the example names only what a client acts
on: `code`, `details.resetAt`, `category` and `message`.
`retryAfterSeconds` and `Retry-After` are named only as absent.
- **The example `message` is illustrative.** It is the English half of
`DailyMessageQuota`'s copy with an example limit of 50. The real copy is
bilingual, Chinese first. The doc says to display `message`, never to
parse it.
- **The "JSON mode only" paragraph was not in triage's text.** It is
measured and it changes what a client can rely on: a streaming client
never sees the code. Dispatch Zone 2 item 3 asked for the one emitting
route, not a platform-wide promise. This paragraph narrows that route to
its one mode.

## Acceptance notes

- **`content/docs/api/error-catalog.mdx` disagrees with the rewritten
section. It is not edited here (claim file surface).**
- `:507-510`: the entry sends readers to "check `retryAfterSeconds`",
which this producer never sends; `details.resetAt` is the field. Its
cause line says "API usage quota for the current period", while the only
producer is a per-user daily chat-turn cap.
  - The 429 row at `:913` (`rate_limit`) agrees.
- Open PR objectstack-ai#19957 has that entry only as unchanged context, and PR objectstack-ai#20170
does not touch it. Both leave the disagreement in place.
- **The page's general "Nested envelope" field list** says no
route-module body carries `httpStatus`, and it does not list `category`.
That is true of the writer it names,
`packages/types/src/response-envelope.ts`. The ObjectOS route writes
through its own `sendError`, which carries both. Not edited.
- **The `fetchWithRetry` example under `RATE_LIMIT_EXCEEDED` retries any
429 by `Retry-After`.** Against this 429 it would wait zero seconds and
retry. The new section warns against that. The loop under "Implement
Retry Logic" keys on the code and lets `QUOTA_EXCEEDED` throw, which is
right. Its comment "it is present on every 429" is inaccurate for this
code's 429, but the comment sits inside the `RATE_LIMIT_EXCEEDED`
branch, so behaviour is unaffected. Carrier: none.
- **Card pin moved.** The card cited objectui `62597c5880`;
`.objectui-sha` is now `f8a9d0fb`. The cited lines are unchanged at
both.

## Verification (head `6e0bb38e`)

- **Derived gate set.** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` gives 41 commands on the actual
changed paths, identical to the dispatch lead.
- **Readings.** All 41 ran, every exit code was recorded to disk before
it was read, and all 41 read exit 0.
- **Prerequisite refusals.** Four lines first refused with PREREQUISITE
NOT MET (exit 3): `check:doc-formula-expressions`,
`check:doc-security-posture`, `check:skill-examples` and
`check:docs-transcript-drift`. `check:docs` was held back until spec was
built. None of those refusals counted as a reading. The lines were
re-run after builds under `scripts/pm/os-verify-lock.sh`:
- `turbo run build` over `@objectstack/lint...`, `@objectstack/formula`
and `@objectstack/spec`: VERDICT command-exit 0.
- The same over `@objectstack/client-react...` and
`@objectstack/client...`: VERDICT command-exit 0. `check:skill-examples`
needed this second build because it refused again, on missing client
declarations.
- `check:skill-examples` then read "259 prose examples type-check across
3 surface(s)".
- **Reconciliation.** `dispatch-gates.mjs --ran ran.list` prints: "✓
dispatch-gates --ran: 41 derived famil(ies) accounted for — 41 run, 0
NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of
them is 3)."
- **Control bytes.** `pnpm check:nul-bytes` exits 0, and the
control-byte self-scan of the edited file has no hits.
- **Tests.** No test and no pin was added or changed: the change is
prose only, and nothing is accepted or refused differently.
- **Changeset.** `skip-changeset`: 0 of 69 non-private workspace
packages list a `files[]` entry reaching `content/docs`.
- **Newer `main`.** `origin/main` moved to `7e7fab73`, and no commit
since BASE `e7f69dbb` touches either doc.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…rt of a compound field (mailing_address.street) (objectstack-ai#20246)

Fixes objectstack-ai#20149

Clause-②: yes

## What this does

The maintainer's ruling on objectstack-ai#20149 (comment 5852138019, 「同意」):

> **Ruled: A — a mapping target may name a declared part of a compound
field (`mailing_address.street`); the importer assembles the parts into
one value; the part names are the closed set the value schema
declares.**

An import mapping could write each source column to one flat field only,
so nothing could build an `address` value from the street / city / state
/ postal code / country columns a spreadsheet carries. Now a
`fieldMapping[].target` may name `field.part`, and the import door
assembles every part one row maps into ONE value under the field's key,
before the engine sees the row.

It extends the ONE verdict objectstack-ai#20150 built, at the arm that PR marked.
There is no second predicate.

- **`@objectstack/spec`**
(`packages/spec/src/data/import-mapping-target.ts`):
- `judgeImportMappingTarget` gains the `{ kind: 'part', target, field,
part }` arm.
- `indexImportMappingTargets` carries each compound field's parts on a
new `parts` map, read from the field's stored value schema
(`valueSchemaFor`). The part names are never listed by hand.
- A dotted target that stays refused answers `unknown` with a `head`
(what the text before the dot names, and its parts when it is compound),
so every door can name the legal parts.
- `unknownImportMappingTargets` gives each refused target a `reason`:
`unknown`, or `collides` for a part of a field the same mapping also
writes whole.
- `ImportFieldMappingSchema.target` declares the part path in its
`.describe()` and docblock. The generated reference page, `api-surface/`
and `export-origins/` are regenerated.
- **`@objectstack/rest`** (`import-mapping.ts`, `import-prepare.ts`):
- `applyMappingToRows` takes the object definition, `trimWhitespace` and
`nullValues`, and assembles parts from every transform: `none`, `map`,
`constant`, `join`, and each element of a `split`.
- `refuseUnknownMappingTargets` names the legal parts in its refusal. It
still answers `400 INVALID_FIELD`, before any row.
- `isBlank` is exported from `import-coerce.ts`, so a blank part is
judged by the same rule as a blank cell.
- **`@objectstack/lint`** (`validate-mapping-target-fields.ts`): the
message "a dotted path into a field's value is not a target" is gone,
because it is false for a declared part. The rule now reports the three
refused cases, each with the legal parts.
- **Docs**: in `content/docs/data-modeling/import-mappings.mdx` the
`target` row said "Target field name(s)", which is now false. It is
rewritten. Only the body is edited; the frontmatter is held by PR
objectstack-ai#20170.

## Decisions, each from a measurement

**H1: which fields are compound.** I checked every one of the 49
`FieldType` values through `valueSchemaFor`. Two stored value schemas
are closed objects (`catchall: never`):

- `address`: seven parts (`street`, `city`, `state`, `postalCode`,
`country`, `countryCode`, `formatted`). Every part is an optional
string.
- `location`: `lat` and `lng` are required numbers; `altitude` and
`accuracy` are optional numbers.

A field counts as compound when its value schema is a closed object
whose every part is an optional string. That selects `address` only.
**`location` is excluded, as the ruling asked me to decide and record:**

- `LocationValueSchema` refuses `{ lat: '37.7', lng: '-122.4' }`
(strings) and `{ lat: 37.7 }` (no `lng`).
- On the ADR-0104 warn-first write path, the engine admits a value that
fails its value schema and only warns. I measured this below with an
address part holding a number.
- So a location assembled from text cells would be stored with the wrong
type, and nothing would refuse it. With optional-string parts, every
subset of text cells is a valid value by construction.

The census test pins the whole set against `FieldType.options`.

**H2: what one row assembles.** Measured through the real engine
(sqlite, JSON rows, no mapping) at `055d4b66e9`, with the same result on
the dry run and the commit:

| value sent | dry run | commit | read back |
|:---|:---|:---|:---|
| `{ street, city }` (partial) | ok | ok | `{ street, city }` |
| `{ street: '', city }` (empty part) | ok | ok | `{ street: '', city }`
|
| `{ postalCode: 12345 }` (wrong type) | ok, with warning `invalid_type`
| ok | `{ postalCode: 12345 }` |
| update `{ city }` over `{ street, city }` | | ok | `{ city }` (the
stored value is replaced whole) |

The assembly rule, stated in `applyMappingToRows`' docblock and the docs
row:

- A blank part cell contributes nothing. Blank means empty, whitespace,
or a `nullValues` token.
- The schema would accept `street: ''`, but a blank flat cell leaves its
field unset, and a blank part does the same one level down.
- A string part is trimmed under `trimWhitespace`, as a flat text cell
is. Coercion never reaches inside a compound value, so the trim happens
in the assembly.
- If every part a row maps is blank, the field is left unset.
- Otherwise the assembled object is the field's whole new value. On an
update it replaces the stored one.

**Whole field and part together.** When a mapping writes
`mailing_address` and also `mailing_address.street`, both write the same
key of one row. They are refused as `reason: 'collides'` at all three
doors, naming where the whole field is written. A repeated part target
follows the same last-write rule as a repeated flat target.

**One door.** The dry run and the commit both reach the assembly through
`prepareImportRequest`, so they judge the same assembled row.
`objectstack validate` asks the same spec verdict.

**H5: census of dotted targets** at base `3875ae6773`:

- The repo's one import mapping, `examples/app-showcase`
`showcase_inquiry_feed` (5 entries), has no dotted target.
- No `skills/` or docs page carries a dotted `fieldMapping` target.
- Every dotted `target:` hit in the tree belongs to another schema:
action or form targets, API endpoint `inputMapping` / `outputMapping`,
and the seed loader.
- hotcrm (the card's origin, hotcrm#1836) is NOT MEASURED from here.

## Pins (the pin sweep)

- **Flipped:**
- objectstack-ai#20150 pinned `mailing_address.street` as `unknown` in
`import-mapping-target.test.ts`, and as a finding in
`validate-mapping-target-fields.test.ts`, on an object that declares
that address field.
- Both now assert the new meaning: all seven parts answer `part`, and
the card's full address template is green at `validate`.
- The objectstack-ai#20150 integration pin keeps refusing `mailing_address.street` on
`task`, which declares no such field. Its comment now says why.
- **New, real engine** (`import-integration.test.ts`, sqlite):
- The customer template's five columns over three rows (full, partial,
all blank).
  - The dry run and the commit agree row for row: `ok 3` each.
- Read back: one address object, a two-part address, and an unset field.
- An unknown part and a dotted path on a text field are refused on both
paths with identical bodies and the part list named, and nothing is
written.
  - The whole-plus-part collision is refused on both paths.
  - The control mapping with no dotted target passes on both.
- **Unit:** the assembly rule in `import-mapping.test.ts` (blank, trim,
`trimWhitespace: false`, every transform, two compound fields kept
apart, no object definition means no part reading). The door's three
refusal texts. The lint rule's three refusals.

## Tests (all through `os-verify-lock.sh`, shared box; tree
`f21f41dbea`)

- `@objectstack/spec`: `vitest run --project local`: 542 files, 15945
passed, 2 todo. `typecheck` (tsc, scripts, test layer): exit 0.
- `@objectstack/rest`: `vitest run --project local`: 199 files, 3569
passed, 1 skipped. `typecheck` (tsc and test layer): exit 0. The `repo`
project is declared to CI.
- `@objectstack/lint`: `vitest run`: 109 files, 4236 passed.
`typecheck`: exit 0.
- `@objectstack/cli` (unit): `test/validate-build-gate-parity.test.ts`:
22 passed. The `integration` layer is declared to CI.
- ESLint over the 10 changed `.ts` files (`--no-inline-config --format
json`): 10 files, 0 errors, 0 warnings.
- The population is the config's `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
minus the build directories, which all 10 files fall under.
- `eslint.config.mjs` enables no type-aware linting (no
`parserOptions.project`), so this diff cannot move the verdict on any
file it does not touch.
  - The repo-wide `pnpm lint` is CI's.

### Ablations (one-shot; each through `scripts/ablation-replace.mjs`,
anchor hit 1, blob changed, restored to the HEAD blob with `git diff
HEAD` empty)

- **A.** The spec part arm answers nothing (`import-mapping-target.ts`):
3 red, 18 green in `import-mapping-target.test.ts`. The red tests are
the part arm, the address-by-parts mapping and the collision.
- **B.** The rest assembly is off (`import-mapping.ts`): 5 red, 57
green.
  - 4 red are the assembly unit tests; the control stays green.
- The fifth is the integration test. With assembly off, the dry run
still answered `ok 3` while the commit answered `ok 1, errors 2`. That
is the card's own dry-run-versus-commit defect, reproduced.
- **C.** The lint dotted reason is dropped
(`validate-mapping-target-fields.ts`): 2 red, 11 green (the unknown-part
and no-parts refusals).

## Gates

`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 108 families at `f21f41dbea`, and `--ran` reconciles
them: 105 run green, 3 NOT MEASURED, 0 unrun.

- NOT MEASURED, each exit 3 (PREREQUISITE NOT MET):
- `check:skill-examples` needs a `client-react` build. The shared lock
never granted one inside its budget.
- `check:dual-build-cjs-loads` and `check:type-check-debt` read a
whole-repo build that this worktree does not have.
- `check:generated`: all 15 artifacts are up to date after
`gen:api-surface`, `gen:export-origins` and `gen:docs`, which are
committed.
- `check:adr-0087-registration`: green. The changeset declares a
widening only (`Clause-②: yes`), so no disposition marker applies.
- A mergeability probe against `origin/main` `ab820016b3`, from a bare
clone with no regen driver: clean.

## Acceptance notes

- Only `address` is compound for import. A `location` field still
imports whole, as one JSON object. Adding it would need a per-part
number coercion and a required-part check. Carrier: none.
- A JSON-format row can put a non-string value in a part, such as a
number for `postalCode`. That value passes through to the engine's
value-shape check, which admits it with a warning on a warn-first
deployment (measured above). This is the posture every structured value
on the import path already has. Carrier: none.
- On an update, the assembled value replaces the stored address whole
(measured). Merging parts into the stored value would be a new decision.
It is documented in the docs row and the changeset. Carrier: none.
- The collision refusal travels under the existing rule id
`mapping-target-field-unknown` and the existing door code
`INVALID_FIELD`. No new rule id or error code was minted.
- The spec verdict is shared. The door and the lint rule each phrase
their own sentence from it (`head.parts`, `index.parts`), as objectstack-ai#20150 left
them.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…s validate` and the published per-type schemas (objectstack-ai#19098) (objectstack-ai#20266)

Fixes objectstack-ai#19098
Fixes objectstack-ai#20270
Clause-②: no (narrowing)

Executes maintainer ruling `5856790152` on objectstack-ai#19098 (batch objectstack-ai#227 item 5,
**letter C**, maintainer 「同意」, confirmed `5856865990`): `os generate
schema` is retired, not repaired. No file is generated in its place, and
nothing in `packages/spec` moves (ruling item 4).

## What changed

**`packages/cli/src/commands/generate.ts`**

- `RETIRED_GENERATORS` gains `schema` in the `os g agent` shape. The
refusal reads "`os g schema` was retired" and cites the maintainer
ruling. It names `os validate` (the real parse:
`ObjectStackDefinitionSchema.safeParse`), and names the per-type schemas
`@objectstack/spec` publishes at
`node_modules/@objectstack/spec/json-schema/CATEGORY/TYPE.json`, which
carry the published projection plus `x-dropped-refinements`. It exits 1.
- The ruling's id lives in the ledger's code comment, not in the
refusal. Text an author is shown carries no tracker number (AGENTS.md;
`check:doc-authoring`'s cross-package prose-id leg ratchets `#NNN`
tokens in `packages/**` strings, and `generate.ts` has no ledger row).
- **Deleted, not left dead:** `runSchemaGeneration`, with its three
`z.toJSONSchema(ObjectStackDefinitionSchema…)` rungs and both ladder
notices; `KNOWN_UNSUPPORTED_JSON_SCHEMA_PATTERNS`;
`isKnownUnsupportedJsonSchema`; and the `case 'schema'` route.
- Nothing else was schema-only. `-o`, `--dry-run` and `--format` serve
`types`, `client` and `migration`. `z` and `ObjectStackDefinitionSchema`
were dynamic imports inside the deleted function. `zod` stays a
dependency, used by `compile`, `validate` and `format`. The `type`
argument's help text derives from `GENERATORS` and never listed
`schema`.
- **The ledger door moved (PM mechanism assumption 2, measured and
falsified).** The lookup sat inside `runMetadataGeneration`, which runs
only after two earlier steps: the sub-command `switch` (where `schema`
returned first) and the NAME requirement (where a nameless call
stopped). So a `schema` entry alone could never be reached from `os
generate schema`.
- BEFORE, at BASE `0d3ec4713` on the dev runner, `os g agent` with no
name printed `Missing required argument` and exited 1.
- The lookup now runs first in `Generate.run`, through
`refuseRetiredGenerator`, as an own-key read (the package's
`Object.prototype.hasOwnProperty.call` idiom).
- Consequences, both pinned. `os g agent` with no name now prints the
agent retirement. `os g constructor NAME` used to print "`os g
constructor` was retired — undefined" and crash with `TypeError:
retired.detail is not iterable`. It now falls through to the ordinary
checks: exit 1, "No file naming convention is declared for type:
constructor".

**Tests**

- `packages/cli/test/generate-schema-retired.e2e.test.ts` (new) is the
retirement pin, in the `agent` precedent's shape: a real child process
through `bin/run-dev.js` plus tsx, with assertions on stdout content and
the exit status. It covers:
- `os generate schema` (no name) and `os g schema -o custom.schema.json`
both exit 1 and write nothing (directory listing `[]`);
- "was retired", with neither "Unknown type:" nor "Missing required
argument";
- "maintainer ruling", `os validate`, and
`@objectstack/spec/json-schema/`;
  - the alias output is byte-equal to the documented spelling's;
  - `os g agent` (no name) answers the agent retirement;
  - `os g constructor thing` is not taken for a retired type;
  - control: `os g object customer --dry-run` still previews.
- `packages/cli/test/generate-schema-writes-json-schema.e2e.test.ts`
(the objectstack-ai#17873 pin, groups (a) to (e)) is **deleted**. Every assertion in
it was about the document the ruling withdrew: the file exists, parses,
declares its draft, the four members' fragments, and the input
direction. Nothing in it survives the retirement.
- Other tests that exercised the command: none. `git grep` for `generate
schema`, `runSchemaGeneration`, `objectstack.schema.json` and
`isKnownUnsupportedJsonSchema` over `packages/cli/src` and
`packages/cli/test` finds only the deleted file. The ladder PR (objectstack-ai#17903)
added no pin of its own beyond that file.
- `packages/cli/test/generate-refuses-retired-generator.test.ts` (new,
patch round 1) is the per-PR guard for the door. It spawns the CLI and
is not named `.e2e`, so it runs in the queue's `integration` project. It
pins:
- `os generate schema` and `os g schema -o custom.schema.json`: exit 1,
nothing written, the retirement answered (neither "Missing required
argument" nor "Unknown type:"), naming the ruling, `os validate` and
`@objectstack/spec/json-schema/`;
- `os g constructor thing`: an own-key miss (exit 1, not "was retired",
no `TypeError`, nothing written);
- control: `os g object customer --dry-run` exits 0 and previews exactly
what the exported object template emits.
- `packages/cli/test/generate-agent-retired.e2e.test.ts` (objectstack-ai#20270, its
own commit `a0cec66e9`): the live-generator control asserted `import *
as Data from '@objectstack/spec/data'`, which the object template
stopped emitting in `0bd11261e`, so this nightly case was red on main.
It now asserts the template's current shape, `ObjectSchema` among the
named imports from `@objectstack/spec/data` plus the
`ObjectSchema.create({` call, never one exact import line. The case is
neither skipped nor quarantined.

**Docs (ruling item 2).** The sweep is `git grep -n "generate schema" --
content/docs`, run at BASE:

| hit | disposition |
|:--|:--|
| `content/docs/api/data-flow.mdx:200`, the JSON Schema row
("Autocomplete and validation for `objectstack.config.ts` (via `os
generate schema`)") | **removed** |
| `content/docs/references/api/plugin-rest-api.mdx:107` | kept: a
substring of "Auto-generate schemas", the REST plugin's
`generateSchemas` option, in an auto-generated reference; it is not the
command |
| `content/docs/references/api/plugin-rest-api.mdx:282` | kept: same |

- One docs edit goes beyond the grep-found set, declared here. The
Mermaid node `D[JSON Schema]` and its two edges, in the same section
directly above the row, pictured the removed row's output ("Build Time:
generate, then JSON Schema, then feed IDE Autocomplete"). They are
removed, and `TypeScript Types` now feeds `IDE Autocomplete` alone.
- The page's front matter is untouched. objectstack-ai#20170 landed its title-rule
hunk on it, and the merge was clean.
- `content/docs/deployment/cli.mdx` (patch round 1) gains a warn-type
Callout, "`os generate schema` is retired", in the `os generate` section
beside the `os g agent` one, the same shape as the two prior CLI
retirements. It points at `os validate` (linked to the page's own `os
validate` heading) and at the per-type schemas `@objectstack/spec`
publishes. The refusal's `Docs:` link lands on this page. The page never
listed a `schema` sub-type, so nothing else on it changes.
- Also swept, with nothing to change:
  - `packages/cli/README.md` carries only the generic `os generate` row;
  - `skills/` has 0 hits;
  - `content/docs/protocol/diagram.mdx:243-255` (see Acceptance notes).

**Changeset** `.changeset/19098-retire-generate-schema.md`:

- `@objectstack/cli: minor`, with a **BREAKING** banner and `Clause-②:
no (narrowing)`;
- the ADR-0087 marker in the `os g agent` wording (`not-required
(no-migration-prescription)`);
- reach outside the repository stated as NOT MEASURED (no telemetry);
- the refusal's pointer given as the migration: delete the call and any
editor mapping of `objectstack.schema.json`, run `os validate`, and
point editors at the per-type schemas.

## Verification, patch round 1 (at HEAD `2cb9e9613` unless stated)

- **Merge.** `origin/main` `17bd31877` merged by merge as `9f702ee94`
(`scripts/pm/os-regen-merge.sh`). No incoming file is one of this PR's.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 93 commands: round 0's 92
plus `pnpm check:cli-examples-parity`, from the `cli.mdx` edit. All 93
exited 0 at `2cb9e9613`. `--ran` with the recorded exit codes reads "93
derived, 93 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero).
- **Named gates:**
  - `pnpm lint`: exit 0 (118 s);
- `node scripts/check-issue-citations.mjs --base origin/main`
(`ae8e3ca01`): exit 0;
- `pnpm check:docs-audit-scope` and `node
scripts/docs-audit/check-affected-docs.mjs`: exit 0 each;
- `pnpm check:docs-transcript-drift` and `pnpm
check:cli-examples-parity`: exit 0 each;
- `pnpm --filter @objectstack/cli typecheck`: `VERDICT command-exit 0`,
with the new pin inside the `tsconfig.test.json` program
(`--listFilesOnly`: 1 hit).
- **Per-PR pin.** `pnpm --filter @objectstack/cli exec vitest run
--project integration --maxWorkers=2
test/generate-refuses-retired-generator.test.ts`: 7/7. It sits in the
queue's `integration` population (54 to 55 files) and is absent from the
nightly population. The partition pin
`test/vitest-tiers-partition.test.ts` passes 22/22.
- **Nightly.** `OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli
exec vitest run --project integration --maxWorkers=2
test/generate-schema-retired.e2e.test.ts
test/generate-agent-retired.e2e.test.ts`: 2 files, 19/19. That includes
"the generators that were not retired still work", the objectstack-ai#20270 case.
- **Shape proof for objectstack-ai#20270.** The two new assertions accept the current
emission: through the exported template, the `ObjectSchema` import
matches and the `create` call matches. They reject the pre-objectstack-ai#20195
emission taken from `0bd11261e^`: neither matches.
- **Ablations on the per-PR file.** These ran on committed state
`a2dda36c2`. `generate.ts` and the per-PR file are byte-identical at
`2cb9e9613`.

| leg | mutation | on-disk proof | per-PR pin result | restore |
|:--|:--|:--|:--|:--|
| A | `schema` ledger key renamed to `schema_ablated` | anchor 1 to 0;
blob `62cb7ec8b8ed` to `7a1f27dd8cfb`; grep `schema-entry=0
ablated-entry=1` | **2 failed**, 5 passed: "is the retirement, not a
missing name and not an unknown type"; "names the ruling, `os validate`
and the per-type schemas" | blob equals HEAD `62cb7ec8b8ed`, `git diff
HEAD` empty |
| B | own-key read reverted to `RETIRED_GENERATORS[args.type]` | anchor
1 to 0; blob to `26ec87322f84`; grep `own-key=0 bracket=1` | **1
failed**, 6 passed: the `os g constructor thing` case | same |

After both legs, the restored per-PR run passed 7/7.

- **History.** The objectstack-ai#20270 fix is its own commit, `a0cec66e9`. A first
push, `a2dda36c2`, had carried a first version of it together with the
per-PR pin and the callout. Rewriting that pushed commit was refused by
the session's permission classifier, so the split line was merged with
the pushed head instead (`2cb9e9613`, whose tree is identical to
`a0cec66e9`'s). Nothing pushed was discarded.

## Verification, round 0 (at HEAD `c1036ebb9` unless stated)

- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 92 commands, and all 92
exited 0 at `c1036ebb9`. `--ran` with the recorded exit codes reads "92
derived, 92 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero).
- `check-adr-0087-registration --base origin/main` reads
`[BREAKING+clause-②-narrowing] not-required
(no-migration-prescription)`.
- `check-changeset-no-major` reads "no `major` bump". Its level axis
reads the PR body, so it is judged in CI.
- **`pnpm lint`** (repo-wide, `eslint . --no-inline-config`): exit 0,
130 s.
- **`pnpm --filter @objectstack/cli typecheck`**: `VERDICT command-exit
0`. That is `tsc --noEmit` plus `check:test-typecheck`, and the new pin
is inside the `tsconfig.test.json` program (`--listFilesOnly`: 1 hit).
- **Board check.** `node scripts/check-issue-citations.mjs --base
origin/main` (`a9fb83ef0`) exits 0: 73 citations judged, 71 resolve, 2
cross-repo.
- **`unit` layer**, run at merge commit `5c7c5bee3`:
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` ran 229 files: 227 passed and 2 failed as prerequisite
refusals ("packages/cli is not built").
  - After the CLI build, those 2 files passed (29 tests).
- `c1036ebb9` differs from `5c7c5bee3` only in the new pin, which is not
in the unit population.
- **Integration tier.** The retirement pin is `*.e2e.test.ts` like the
`agent` precedent, so it is **nightly-tier**: the per-PR queue run does
not select it.
- Run locally as `OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli
exec vitest run --project integration --maxWorkers=2` over the new pin
plus `generate-agent-retired`, `generate-skill` and
`generate-object-namespace-prefix`.
- Result: 38 passed and 1 failed. The failure is the pre-existing stale
control in `generate-agent-retired.e2e.test.ts` (see Acceptance notes);
the new pin is 10/10.
- **Reach checks** on the dev runner, BEFORE at BASE and AFTER at HEAD:
- `os generate schema -o out.json` went from exit 0 and a 3,335,734-byte
file written to exit 1, the refusal, and nothing written;
- `os g agent` went from "Missing required argument" to the agent
retirement;
- `os g constructor foo` went from a `TypeError` crash to the ordinary
refusal.

**Ablation.** This is a one-shot proof, run against the committed state
`c1036ebb9` through `scripts/ablation-replace.mjs` under a script-level
`trap` restore. The subject resolves from `src/` via tsx, so no dist is
involved.

| leg | mutation | on-disk proof | pin result | restore |
|:--|:--|:--|:--|:--|
| A | `schema` ledger key renamed to `schema_ablated` (the lookup misses
it) | anchor 1 to 0; blob `62cb7ec8b8ed` to `7a1f27dd8cfb`; grep
`schema-entry=0 ablated-entry=1` | **4 failed**, 6 passed: "was
RETIRED", "names the ruling", `os validate`, per-type schemas | blob
equals HEAD `62cb7ec8b8ed`; `git diff HEAD` empty |
| B | own-key read reverted to `RETIRED_GENERATORS[args.type]` | anchor
1 to 0; blob to `26ec87322f84`; grep `own-key=0 bracket=1` | **1
failed**, 9 passed: the `constructor` own-keys assertion | same |

In leg A the exit-1 and writes-nothing assertions stayed green, because
the missing-name path also exits 1 without writing. That is exactly why
the pin asserts the refusal's content, as the `agent` precedent does.

## Acceptance notes

1. Now repaired here (objectstack-ai#20270, commit `a0cec66e9`): the live-generator
control in `packages/cli/test/generate-agent-retired.e2e.test.ts`
asserted `import * as Data from '@objectstack/spec/data'`, which the
object template stopped emitting in `0bd11261e`.
- Triage's note-2 sweep covered all 73 nightly-tier files under
`packages/cli` for pre-objectstack-ai#20195 template text (`import * as Data from
'@objectstack/spec/data'`, `Data.ServiceObject`). That line was the only
nightly hit; the control, the same sweep at `9f702ee94`, finds it.
- Two per-PR files carry the old shape on purpose and are not changed:
`scaffold-emission-typechecks.test.ts` (a tsc canary fixture) and
`scaffold-object-declaration-shape.test.ts` (it refuses the pre-ruling
literal).
2. `.changeset/sour-moons-smile.md`, pending and unreleased, records the
objectstack-ai#17903 ladder repair this PR deletes. It is left unchanged.
- Rewriting it here is the case `check-empty-changeset.mjs` classes as a
DELIBERATE CORRECTION (objectstack-ai#18160, ruling D on objectstack-ai#17712). The gate goes red on
any modification or deletion of a changeset from the merge base, and its
remedy is "do NOT restore it -- say so on the PR and get it confirmed".
- The seat answered the open question with A: leave the file. The new
changeset tells the reader that this retirement supersedes that repair,
and the release owner may drop the file at compilation.
3. The docblock at `packages/spec/scripts/lib/refinement-projection.ts`
(the "Producers outside `packages/spec`'s own artifacts" bullet) still
names the CLI's `os generate` as a direct `z.toJSONSchema` producer. It
goes stale once this lands, but `packages/spec` is untouched by ruling
item 4.
4. The `GENERATORS[type]` lookup in `runMetadataGeneration` is still an
inherited-key read. So `os g constructor NAME` answers "No file naming
convention is declared for type: constructor" rather than "Unknown
type". The answer is misleading but does not crash, and it is not
changed here.
5. Reach outside this repository is NOT MEASURED (no telemetry). Inside
it there is no reader and no editor mapping of
`objectstack.schema.json`.
6. `content/docs/protocol/diagram.mdx:243-255` carries a `D[JSON
Schema]` node feeding IDE autocomplete, and a row "Generated JSON Schema
files for IDE validation". It does not name the command, and the plural
"files" reads as the per-type schemas `@objectstack/spec` still
publishes, so it is left unchanged.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…ebar labels kept via navTitle (objectstack-ai#20401)

Fixes objectstack-ai#15403
Clause-②: no

## Summary

The generated reference pages under `content/docs/references/**` now
carry the docs page-title rule, emitted by the generator itself, and
every one keeps its sidebar label byte-identical through `navTitle`.

- **One pure function holds the rule:**
`packages/spec/scripts/lib/page-title.ts`. It builds each title from
data the generator already holds (the module display name and the
category's declared title) plus fixed words, picks the longest candidate
inside the band, and refuses (never truncates) when no candidate fits.
- **Three emission sites use it:** module pages and category index pages
in `packages/spec/scripts/build-docs.ts`, and the root index in
`packages/spec/scripts/lib/root-index.ts`.
- **`navTitle` = the page's previous `title`, verbatim**, so
`navLabel()` in `apps/docs/lib/nav-title.ts` returns the same string as
before. The page tree is unchanged (replayed below, with a control).
- **211 generated pages regenerated**, frontmatter only: each is `+2/-1`
(the `title:` line replaced, a `navTitle:` line added). No
`description:`, no body, no `meta.json`.

## The rule, and how the emitter expresses it

The rule is the maintainer's, recorded on objectstack-ai#12237 in comment
`5419555720`, verbatim 「其他同意」. Its text as landed is the `## The rule`
section of PR objectstack-ai#20170:

- shape `PRIMARY-KEYWORD — QUALIFIER`, separator ` — `;
- frontmatter `title` is 36–46 characters, so the rendered title with
the 14-character ` | ObjectStack` suffix is 50–60, none over 60;
- `ObjectStack` never inside a title;
- short navigation labels go in `navTitle`, whose fallback to `title`
lives only in `navLabel()`.

The generator holds only the module display name (3–29 characters on
this tree), the category's declared title (11–20 for categories with
pages) and fixed words. No single template lands every page inside an
11-character band, so each page kind has a short ladder, longest first,
and the title is the first candidate inside the band:

| page kind | candidate (longest first) | length |
|---|---|--:|
| module page | `NAME schema — CATEGORY property reference`, offered
only when the page renders a `### Properties` table | name + category +
29 |
| | `NAME schema — CATEGORY reference` | name + category + 20 |
| | `NAME — CATEGORY reference` | name + category + 13 |
| | `NAME — CATEGORY` | name + category + 3 |
| category index | `CATEGORY — complete schema reference` | category +
28 |
| | `CATEGORY — schema reference` | category + 19 |
| root index | `Protocol reference — every schema by module` | 43 |

The module rungs overlap end to end and cover every name + category
length from 7 to 43; the longest pair on the tree is 41 (`Schemaless
Node Config` in `Automation Protocol`). The first module rung is
conditional: `property reference` is offered only when at least one of
the page's schemas renders a `### Properties` table
(`rendersPropertiesTable` in `lib/schema-section.ts`, built on
`declaresProperties`, the one expression the section renderer itself
branches on). A page without one, such as the enum-only `data/feed`,
starts at the second rung; without the first rung the ladder covers name
+ category lengths 16 to 43, and the shortest property-less pair on the
tree is 17 (`Feed` in `Data Protocol`). The category rungs cover
category titles of 8 to 27 characters; the longest declared title is 25.
The category in every module title keeps same-named modules apart
(`Plugin` is both a `kernel` and a `studio` page). A page no rung fits
stops `gen:docs` with a message naming the page and every candidate with
its length.

Rung usage on this tree: module pages 18 / 103 / 60 / 15 (rungs 1–4),
category index pages 11 / 3, root index 1. All 18 rung-1 pages render a
`### Properties` table; the 14 module pages that render none are all on
rungs 2–4.

## Measured, before and after

Population re-derived at base `862b6ce8` and at head:
`content/docs/**/*.mdx` = **406**; pages carrying the generator's
`AUTO-GENERATED — DO NOT EDIT` banner = **211**, all under
`content/docs/references/**` (196 module pages, 14 category index pages,
1 root index). Rendered = `title` + 14.

| | pages | min | median | max | in 36–46 | outside | rendered over 60 |
with ` — ` | with `ObjectStack` | duplicate titles | `navTitle` |
|---|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
| before (`origin/main`) | 211 | 3 | 11 | 29 | 0 | 211 | 0 | 0 | 0 | 6 |
0 |
| after (this head) | 211 | 37 | 43 | 46 | 211 | 0 | 0 | 211 | 0 | 0 |
211 |

After: rendered 51–60. Duplicate titles across the whole corpus (406
pages, authored and generated): **0**.

**The card body's 225 of 405 does not reproduce, and the difference is
not generator output.** The generated set is 211; the 14 others in that
count are `content/docs/releases/**` pages, which carry no generator
banner and are release-owned (compiled by hand at release time). No
other emitter writes a `title:` into `content/docs/**`:
`build-skill-docs.ts` rewrites only the region between its markers in
`content/docs/ai/skills-reference.mdx`, whose authored title already
follows the rule (PR objectstack-ai#20170). The only `title:` emissions in the repo's
generators are the three this PR changes (`git grep` over `scripts/`,
`packages/*/scripts/`, `apps/docs/scripts/`).

## The page tree is unchanged

Replayed with the site's own machinery: `fumadocs-core` 16.14.4
`loader()` (the `apps/docs` dependency) with the site's `i18n` settings
and the site's `navTitlePlugin()`, over every `.mdx` frontmatter and
`meta.json` under `content/docs`, once from `origin/main` and once from
this head. Serialized page tree (folders, separators, pages, folder
index pages): **449 nodes before, 449 after, `diff` exit 0.**

Control, so the replay can see a title change at all: the same replay of
this head WITHOUT `navTitlePlugin()` differs from `origin/main` in
**388** nodes, exactly the 211 generated pages plus the 177 authored
pages PR objectstack-ai#20170 gave a `navTitle`.

Folder labels come from each category's `meta.json` `title`, which this
PR does not touch. The category and root `index.mdx` pages carry
`navTitle` anyway because the footer previous/next list walks folder
index pages.

## Examples

| page | before | after | chars / rendered |
|---|---|---|--:|
| `references/ai/mcp.mdx` | Mcp | Mcp schema — AI Protocol property
reference | 43 / 57 |
| `references/ai/agent.mdx` | Agent | Agent schema — AI Protocol
property reference | 45 / 59 |
| `references/data/feed.mdx` | Feed | Feed schema — Data Protocol
reference | 37 / 51 |
| `references/data/object.mdx` | Object | Object schema — Data Protocol
reference | 39 / 53 |
| `references/automation/flow.mdx` | Flow | Flow schema — Automation
Protocol reference | 43 / 57 |
| `references/kernel/plugin.mdx` | Plugin | Plugin schema — Kernel
Protocol reference | 41 / 55 |
| `references/studio/plugin.mdx` | Plugin | Plugin schema — Studio
Protocol reference | 41 / 55 |
| `references/kernel/plugin-registry.mdx` | Plugin Registry | Plugin
Registry — Kernel Protocol reference | 43 / 57 |
| `references/kernel/metadata-protection.mdx` | Metadata Protection |
Metadata Protection — Kernel Protocol | 37 / 51 |
| `references/ui/expression-bindable-text-keys.mdx` | Expression
Bindable Text Keys | Expression Bindable Text Keys — UI Protocol | 43 /
57 |
| `references/automation/schemaless-node-config.mdx` | Schemaless Node
Config | Schemaless Node Config — Automation Protocol | 44 / 58 |
| `references/ai/index.mdx` | AI Protocol | AI Protocol — complete
schema reference | 39 / 53 |
| `references/automation/index.mdx` | Automation Protocol | Automation
Protocol — schema reference | 38 / 52 |
| `references/identity/index.mdx` | Identity Protocol | Identity
Protocol — complete schema reference | 45 / 59 |
| `references/index.mdx` | Protocol Reference | Protocol reference —
every schema by module | 43 / 57 |

The complete table is at the end.

## Wording for maintainer voice review

The rule is approved; the fixed words the generator adds are new public
text and are not in the approved table of PR objectstack-ai#12312: `schema`, `property
reference`, `reference`, `complete schema reference`, `schema
reference`, and the root title `Protocol reference — every schema by
module`. Rewording any of them is a one-line change in
`lib/page-title.ts` plus `gen:docs`; the band, the unit test and
`check:docs` hold the result either way. Non-blocking: the seat answered
land-as-shipped in comment 5865474678 on objectstack-ai#15403, and `property
reference` is now claimed only by pages that render a property table.

## File surface

- `packages/spec/scripts/lib/page-title.ts` (new): the rule, the
ladders, the refusal, the frontmatter lines.
- `packages/spec/scripts/page-title.test.ts` (new): the pin (`local`
vitest project; reads nothing outside the package).
- `packages/spec/scripts/build-docs.ts`: the two emission sites (module
pages, category index pages), located by symbol.
- `packages/spec/scripts/lib/schema-section.ts`: `declaresProperties()`
(an object that declares properties, the one spelling of that condition,
now also used by the renderer's root and union-arm branches) and
`rendersPropertiesTable()` (whether a schema renders at least one `###
Properties` table), read by the module-page title. Rendered output
unchanged: regenerating moved exactly one page, `data/feed.mdx`, and
only its `title:` line.
- `packages/spec/scripts/lib/root-index.ts`: **one site beyond the two
the dispatch named.** It is the generator's third `title:` emission (the
root `references/index.mdx`, previously `title: Protocol Reference`, 18
characters). Leaving it would have left one generated page outside the
rule.
- `content/docs/references/**`: 211 pages, regenerated by `gen:docs`,
never hand-edited.
- Not touched: authored pages, the `description:` emission,
`check-generated.ts` (it regenerates and compares; nothing in it reads
the title line), `build-skill-docs.ts` (emits no title).

## Changeset: `skip-changeset`

Rule applied: a changeset is owed when a package's published `files[]`
content moves. `@objectstack/spec` publishes `dist`, `json-schema`,
`liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`,
`CHANGELOG.md`, `api-surface`, `spec-changes.json`; `scripts/` is not
among them, and `apps/docs` (which renders `content/docs`) is private.
Measured after the build: the new symbols (`titleFrontmatter`,
`navTitle`, `property reference`) have **0** hits across all ten of
those paths, while the positive control `ObjectSchema` hits in 9 of the
10 (all but `json-schema`).

## Verification

Head `1ba9d84c`, REWORK round 1: `41b0f683` (the conditional rung),
`0ea42f45` (`data/feed.mdx` regenerated), then a merge of `origin/main`
at `df3ba164` whose two deferred pages, `security/{permission,rls}.mdx`,
were regenerated on the merged tree in `1ba9d84c`. Round 0 was head
`a61335f9`.

- `pnpm --filter @objectstack/spec test`: 561 files, 16546 passed, 1
todo.
- `pnpm --filter @objectstack/spec test:repo`: 35 files, 634 passed.
- Type check: `check:scripts-typecheck` (`tsc -p tsconfig.scripts.json`,
the program holding every file this diff edits, `page-title.test.ts`
included) exit 0 at `1ba9d84c`. The full spec `typecheck` was green
locally at round 0's pre-merge head `0a241893`, and `Type Check ·
workspace` ran it green in CI on `a61335f9`.
- The new files are in the scripts tsc program (`tsc -p
tsconfig.scripts.json --listFiles` lists `lib/page-title.ts` and
`page-title.test.ts`).
- Gates derived by `node scripts/pm/dispatch-gates.mjs --commands` over
this diff at `1ba9d84c` (216 paths, the same 89 families as round 0),
each exit code recorded, then `--ran`: **89 derived, 85 run (all exit
0), 4 NOT MEASURED, 0 UNRUN.** Among the 85: `check:docs`,
`check:generated` (all 15 generated artifacts up to date),
`check:doc-frontmatter`, `check:docs-single-h1`, `check:doc-anchors`,
`check:nul-bytes`, `check:quick-reference-counts`.
- NOT MEASURED, prerequisite refused (exit 3): `check:skill-examples`
(needs the 36-package `@objectstack/client-react` closure built),
`check:dual-build-cjs-loads` (needs every package built),
`check:type-check-debt` (needs a 30-package closure built).
- NOT MEASURED, timed out: `check:pm-dispatch-gates`. Its self-test of
`scripts/pm/dispatch-gates.mjs`, which this diff does not edit, did not
finish in a 420 s and then a 560 s window on the shared box. Not re-run
in round 1; `Lint & Repo Gates` ran it green in CI on `a61335f9`.
- **Ablation 1, the unit test can fail:** `scripts/ablation-replace.mjs`
changed `TITLE_MAX = 46` to `60` in `lib/page-title.ts` (anchor 1 to 0,
blob `3fe85286f82d` to `ee55509927c8`). `page-title.test.ts` went **12
failed / 13 passed**. Restored under an EXIT/INT/TERM trap to
`3fe85286f82d` = HEAD blob, `git diff HEAD` empty.
- **Ablation 2, `check:docs` sees the emission:** the first module
rung's `property reference` changed to `field reference` (blob
`3fe85286f82d` to `b8f4c6d26269`). `check:docs` exited **1** with **49**
pages out of date: the 19 rung-1 pages, plus the pages the shorter word
lets onto rung 1. Restored the same way.
- **Ablation 3, the rung condition is pinned:** `...(documentsProperties
? [` changed to `...(true ? [` in `lib/page-title.ts` (anchor 1 to 0,
blob `38277b23f728` to `90e0cbe18d3b`). `page-title.test.ts` went **3
failed / 38 passed**: the `data/feed` row, the condition pin, and the
short property-less refusal. Restored to `38277b23f728` = HEAD blob,
`git diff HEAD` empty.
- **The predicate agrees with the renderer on every published schema:**
over all 1523 JSON Schemas under `packages/spec/json-schema/`,
`rendersPropertiesTable(name, schema)` equals whether
`renderSchemaSection(name, schema)` emits a `### Properties` heading:
1219 true, **0 disagreements**. The unit pin holds the same agreement
shape by shape (11 JSON Schema shapes covering every renderer branch).

## Acceptance notes

- **Module display names are title-cased from the file slug, so
abbreviations come out wrong:** `Mcp`, `Rls`, `Scim`, `Odata`, `Http`,
`I18n`, `Driver Sql`, `Cli Extension`, `Io Node Config`, `Bpmn Interop`,
`Package Api`, `Rest Server`, `Events Dlq`, and others. This is the `Qa
Protocol` defect class that `lib/category-title.ts` fixed for category
titles, one level down, and it predates this PR (these strings were the
whole title before; they are the `navTitle` now, and the primary keyword
of the new title). Out of scope here: fixing it changes nav labels,
which this card keeps byte-identical. Noted, not filed.
- The card body's 225 / 405 reading included 14 `releases/**` pages (see
*Measured*); nothing here depends on them.
- `api/protocol.mdx` renders 6 `### Properties` tables with no rows
(object schemas declaring an empty `properties`). Pre-existing renderer
output; that page is on rung 2 and carries non-empty tables as well.
Noted, not filed.
- The optional routing of `rootIndexTitle()` through `pageTitleOrExit`
was not taken: `pageTitleOrExit` lives in `build-docs.ts`, so routing it
means passing the title into `renderRootIndex` (its input type and the
`root-index.test.ts` fixtures), more than one line. The root title is a
fixed 43-character string pinned by the unit test.

## The complete table

All 211 generated pages. `navTitle` = before, verbatim, on every row.

| page | before | after | chars / rendered | `navTitle` |
|---|---|---|--:|---|
| `references/index.mdx` | Protocol Reference | Protocol reference —
every schema by module | 43 / 57 | = before |
| `references/ai/index.mdx` | AI Protocol | AI Protocol — complete
schema reference | 39 / 53 | = before |
| `references/ai/agent.mdx` | Agent | Agent schema — AI Protocol
property reference | 45 / 59 | = before |
| `references/ai/build-progress.mdx` | Build Progress | Build Progress
schema — AI Protocol reference | 45 / 59 | = before |
| `references/ai/conversation.mdx` | Conversation | Conversation schema
— AI Protocol reference | 43 / 57 | = before |
| `references/ai/embedding.mdx` | Embedding | Embedding schema — AI
Protocol reference | 40 / 54 | = before |
| `references/ai/knowledge-document.mdx` | Knowledge Document |
Knowledge Document — AI Protocol reference | 42 / 56 | = before |
| `references/ai/knowledge-source.mdx` | Knowledge Source | Knowledge
Source — AI Protocol reference | 40 / 54 | = before |
| `references/ai/mcp.mdx` | Mcp | Mcp schema — AI Protocol property
reference | 43 / 57 | = before |
| `references/ai/model-registry.mdx` | Model Registry | Model Registry
schema — AI Protocol reference | 45 / 59 | = before |
| `references/ai/skill.mdx` | Skill | Skill schema — AI Protocol
property reference | 45 / 59 | = before |
| `references/ai/solution-blueprint.mdx` | Solution Blueprint | Solution
Blueprint — AI Protocol reference | 42 / 56 | = before |
| `references/ai/tool.mdx` | Tool | Tool schema — AI Protocol property
reference | 44 / 58 | = before |
| `references/ai/usage.mdx` | Usage | Usage schema — AI Protocol
property reference | 45 / 59 | = before |
| `references/api/index.mdx` | API Protocol | API Protocol — complete
schema reference | 40 / 54 | = before |
| `references/api/analytics.mdx` | Analytics | Analytics schema — API
Protocol reference | 41 / 55 | = before |
| `references/api/auth-endpoints.mdx` | Auth Endpoints | Auth Endpoints
schema — API Protocol reference | 46 / 60 | = before |
| `references/api/auth.mdx` | Auth | Auth schema — API Protocol property
reference | 45 / 59 | = before |
| `references/api/automation-api.mdx` | Automation Api | Automation Api
schema — API Protocol reference | 46 / 60 | = before |
| `references/api/batch.mdx` | Batch | Batch schema — API Protocol
property reference | 46 / 60 | = before |
| `references/api/contract.mdx` | Contract | Contract schema — API
Protocol reference | 40 / 54 | = before |
| `references/api/discovery.mdx` | Discovery | Discovery schema — API
Protocol reference | 41 / 55 | = before |
| `references/api/dispatcher.mdx` | Dispatcher | Dispatcher schema — API
Protocol reference | 42 / 56 | = before |
| `references/api/documentation.mdx` | Documentation | Documentation
schema — API Protocol reference | 45 / 59 | = before |
| `references/api/endpoint.mdx` | Endpoint | Endpoint schema — API
Protocol reference | 40 / 54 | = before |
| `references/api/error-code-ledger.mdx` | Error Code Ledger | Error
Code Ledger — API Protocol reference | 42 / 56 | = before |
| `references/api/errors.mdx` | Errors | Errors schema — API Protocol
reference | 38 / 52 | = before |
| `references/api/events.mdx` | Events | Events schema — API Protocol
reference | 38 / 52 | = before |
| `references/api/export.mdx` | Export | Export schema — API Protocol
reference | 38 / 52 | = before |
| `references/api/http-cache.mdx` | Http Cache | Http Cache schema — API
Protocol reference | 42 / 56 | = before |
| `references/api/metadata.mdx` | Metadata | Metadata schema — API
Protocol reference | 40 / 54 | = before |
| `references/api/misc.mdx` | Misc | Misc schema — API Protocol property
reference | 45 / 59 | = before |
| `references/api/odata.mdx` | Odata | Odata schema — API Protocol
property reference | 46 / 60 | = before |
| `references/api/package-api-assembled.mdx` | Package Api Assembled |
Package Api Assembled — API Protocol reference | 46 / 60 | = before |
| `references/api/package-api.mdx` | Package Api | Package Api schema —
API Protocol reference | 43 / 57 | = before |
| `references/api/package-lifecycle.mdx` | Package Lifecycle | Package
Lifecycle — API Protocol reference | 42 / 56 | = before |
| `references/api/plugin-rest-api.mdx` | Plugin Rest Api | Plugin Rest
Api — API Protocol reference | 40 / 54 | = before |
| `references/api/protocol.mdx` | Protocol | Protocol schema — API
Protocol reference | 40 / 54 | = before |
| `references/api/query-adapter.mdx` | Query Adapter | Query Adapter
schema — API Protocol reference | 45 / 59 | = before |
| `references/api/realtime-shared.mdx` | Realtime Shared | Realtime
Shared — API Protocol reference | 40 / 54 | = before |
| `references/api/realtime.mdx` | Realtime | Realtime schema — API
Protocol reference | 40 / 54 | = before |
| `references/api/rest-server.mdx` | Rest Server | Rest Server schema —
API Protocol reference | 43 / 57 | = before |
| `references/api/router.mdx` | Router | Router schema — API Protocol
reference | 38 / 52 | = before |
| `references/api/sortability.mdx` | Sortability | Sortability schema —
API Protocol reference | 43 / 57 | = before |
| `references/api/storage.mdx` | Storage | Storage schema — API Protocol
reference | 39 / 53 | = before |
| `references/api/versioning.mdx` | Versioning | Versioning schema — API
Protocol reference | 42 / 56 | = before |
| `references/api/websocket.mdx` | Websocket | Websocket schema — API
Protocol reference | 41 / 55 | = before |
| `references/automation/index.mdx` | Automation Protocol | Automation
Protocol — schema reference | 38 / 52 | = before |
| `references/automation/approval.mdx` | Approval | Approval —
Automation Protocol reference | 40 / 54 | = before |
| `references/automation/bpmn-interop.mdx` | Bpmn Interop | Bpmn Interop
— Automation Protocol reference | 44 / 58 | = before |
| `references/automation/builtin-node-config.mdx` | Builtin Node Config
| Builtin Node Config — Automation Protocol | 41 / 55 | = before |
| `references/automation/control-flow.mdx` | Control Flow | Control Flow
— Automation Protocol reference | 44 / 58 | = before |
| `references/automation/execution.mdx` | Execution | Execution —
Automation Protocol reference | 41 / 55 | = before |
| `references/automation/flow-function.mdx` | Flow Function | Flow
Function — Automation Protocol reference | 45 / 59 | = before |
| `references/automation/flow.mdx` | Flow | Flow schema — Automation
Protocol reference | 43 / 57 | = before |
| `references/automation/io-node-config.mdx` | Io Node Config | Io Node
Config — Automation Protocol reference | 46 / 60 | = before |
| `references/automation/node-executor.mdx` | Node Executor | Node
Executor — Automation Protocol reference | 45 / 59 | = before |
| `references/automation/schedule-organization.mdx` | Schedule
Organization | Schedule Organization — Automation Protocol | 43 / 57 | =
before |
| `references/automation/schemaless-node-config.mdx` | Schemaless Node
Config | Schemaless Node Config — Automation Protocol | 44 / 58 | =
before |
| `references/automation/state-machine.mdx` | State Machine | State
Machine — Automation Protocol reference | 45 / 59 | = before |
| `references/automation/time-relative-trigger.mdx` | Time Relative
Trigger | Time Relative Trigger — Automation Protocol | 43 / 57 | =
before |
| `references/automation/webhook.mdx` | Webhook | Webhook schema —
Automation Protocol reference | 46 / 60 | = before |
| `references/data/index.mdx` | Data Protocol | Data Protocol — complete
schema reference | 41 / 55 | = before |
| `references/data/analytics.mdx` | Analytics | Analytics schema — Data
Protocol reference | 42 / 56 | = before |
| `references/data/context-tokens.mdx` | Context Tokens | Context Tokens
— Data Protocol reference | 40 / 54 | = before |
| `references/data/data-engine.mdx` | Data Engine | Data Engine schema —
Data Protocol reference | 44 / 58 | = before |
| `references/data/datasource.mdx` | Datasource | Datasource schema —
Data Protocol reference | 43 / 57 | = before |
| `references/data/date-macros.mdx` | Date Macros | Date Macros schema —
Data Protocol reference | 44 / 58 | = before |
| `references/data/document.mdx` | Document | Document schema — Data
Protocol reference | 41 / 55 | = before |
| `references/data/driver-common.mdx` | Driver Common | Driver Common
schema — Data Protocol reference | 46 / 60 | = before |
| `references/data/driver-memory.mdx` | Driver Memory | Driver Memory
schema — Data Protocol reference | 46 / 60 | = before |
| `references/data/driver-mongo.mdx` | Driver Mongo | Driver Mongo
schema — Data Protocol reference | 45 / 59 | = before |
| `references/data/driver-mysql.mdx` | Driver Mysql | Driver Mysql
schema — Data Protocol reference | 45 / 59 | = before |
| `references/data/driver-nosql.mdx` | Driver Nosql | Driver Nosql
schema — Data Protocol reference | 45 / 59 | = before |
| `references/data/driver-postgres.mdx` | Driver Postgres | Driver
Postgres — Data Protocol reference | 41 / 55 | = before |
| `references/data/driver-sql.mdx` | Driver Sql | Driver Sql schema —
Data Protocol reference | 43 / 57 | = before |
| `references/data/driver-sqlite.mdx` | Driver Sqlite | Driver Sqlite
schema — Data Protocol reference | 46 / 60 | = before |
| `references/data/driver-turso.mdx` | Driver Turso | Driver Turso
schema — Data Protocol reference | 45 / 59 | = before |
| `references/data/driver.mdx` | Driver | Driver schema — Data Protocol
reference | 39 / 53 | = before |
| `references/data/external-catalog.mdx` | External Catalog | External
Catalog — Data Protocol reference | 42 / 56 | = before |
| `references/data/feed.mdx` | Feed | Feed schema — Data Protocol
reference | 37 / 51 | = before |
| `references/data/field-value.mdx` | Field Value | Field Value schema —
Data Protocol reference | 44 / 58 | = before |
| `references/data/field.mdx` | Field | Field schema — Data Protocol
reference | 38 / 52 | = before |
| `references/data/filter.mdx` | Filter | Filter schema — Data Protocol
reference | 39 / 53 | = before |
| `references/data/hook-body.mdx` | Hook Body | Hook Body schema — Data
Protocol reference | 42 / 56 | = before |
| `references/data/hook.mdx` | Hook | Hook schema — Data Protocol
property reference | 46 / 60 | = before |
| `references/data/mapping.mdx` | Mapping | Mapping schema — Data
Protocol reference | 40 / 54 | = before |
| `references/data/object.mdx` | Object | Object schema — Data Protocol
reference | 39 / 53 | = before |
| `references/data/query.mdx` | Query | Query schema — Data Protocol
reference | 38 / 52 | = before |
| `references/data/seed-loader.mdx` | Seed Loader | Seed Loader schema —
Data Protocol reference | 44 / 58 | = before |
| `references/data/seed.mdx` | Seed | Seed schema — Data Protocol
property reference | 46 / 60 | = before |
| `references/data/validation.mdx` | Validation | Validation schema —
Data Protocol reference | 43 / 57 | = before |
| `references/identity/index.mdx` | Identity Protocol | Identity
Protocol — complete schema reference | 45 / 59 | = before |
| `references/identity/eval-user.mdx` | Eval User | Eval User schema —
Identity Protocol reference | 46 / 60 | = before |
| `references/identity/identity.mdx` | Identity | Identity schema —
Identity Protocol reference | 45 / 59 | = before |
| `references/identity/organization.mdx` | Organization | Organization —
Identity Protocol reference | 42 / 56 | = before |
| `references/identity/position.mdx` | Position | Position schema —
Identity Protocol reference | 45 / 59 | = before |
| `references/identity/scim.mdx` | Scim | Scim schema — Identity
Protocol reference | 41 / 55 | = before |
| `references/integration/index.mdx` | Integration Protocol |
Integration Protocol — schema reference | 39 / 53 | = before |
| `references/integration/connector.mdx` | Connector | Connector —
Integration Protocol reference | 42 / 56 | = before |
| `references/kernel/index.mdx` | Kernel Protocol | Kernel Protocol —
complete schema reference | 43 / 57 | = before |
| `references/kernel/cli-extension.mdx` | Cli Extension | Cli Extension
— Kernel Protocol reference | 41 / 55 | = before |
| `references/kernel/cluster.mdx` | Cluster | Cluster schema — Kernel
Protocol reference | 42 / 56 | = before |
| `references/kernel/context.mdx` | Context | Context schema — Kernel
Protocol reference | 42 / 56 | = before |
| `references/kernel/dependency-resolution.mdx` | Dependency Resolution
| Dependency Resolution — Kernel Protocol | 39 / 53 | = before |
| `references/kernel/events-bus.mdx` | Events Bus | Events Bus schema —
Kernel Protocol reference | 45 / 59 | = before |
| `references/kernel/events-core.mdx` | Events Core | Events Core schema
— Kernel Protocol reference | 46 / 60 | = before |
| `references/kernel/events-dlq.mdx` | Events Dlq | Events Dlq schema —
Kernel Protocol reference | 45 / 59 | = before |
| `references/kernel/events-handlers.mdx` | Events Handlers | Events
Handlers — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/events-integrations.mdx` | Events Integrations |
Events Integrations — Kernel Protocol | 37 / 51 | = before |
| `references/kernel/events-queue.mdx` | Events Queue | Events Queue —
Kernel Protocol reference | 40 / 54 | = before |
| `references/kernel/execution-context.mdx` | Execution Context |
Execution Context — Kernel Protocol reference | 45 / 59 | = before |
| `references/kernel/manifest.mdx` | Manifest | Manifest schema — Kernel
Protocol reference | 43 / 57 | = before |
| `references/kernel/metadata-loader.mdx` | Metadata Loader | Metadata
Loader — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/metadata-plugin.mdx` | Metadata Plugin | Metadata
Plugin — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/metadata-protection.mdx` | Metadata Protection |
Metadata Protection — Kernel Protocol | 37 / 51 | = before |
| `references/kernel/package-artifact.mdx` | Package Artifact | Package
Artifact — Kernel Protocol reference | 44 / 58 | = before |
| `references/kernel/package-registry.mdx` | Package Registry | Package
Registry — Kernel Protocol reference | 44 / 58 | = before |
| `references/kernel/package-upgrade.mdx` | Package Upgrade | Package
Upgrade — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/plugin-capability.mdx` | Plugin Capability | Plugin
Capability — Kernel Protocol reference | 45 / 59 | = before |
| `references/kernel/plugin-lifecycle-advanced.mdx` | Plugin Lifecycle
Advanced | Plugin Lifecycle Advanced — Kernel Protocol | 43 / 57 | =
before |
| `references/kernel/plugin-loading.mdx` | Plugin Loading | Plugin
Loading — Kernel Protocol reference | 42 / 56 | = before |
| `references/kernel/plugin-registry.mdx` | Plugin Registry | Plugin
Registry — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/plugin-security-advanced.mdx` | Plugin Security
Advanced | Plugin Security Advanced — Kernel Protocol | 42 / 56 | =
before |
| `references/kernel/plugin-security.mdx` | Plugin Security | Plugin
Security — Kernel Protocol reference | 43 / 57 | = before |
| `references/kernel/plugin-structure.mdx` | Plugin Structure | Plugin
Structure — Kernel Protocol reference | 44 / 58 | = before |
| `references/kernel/plugin-validator.mdx` | Plugin Validator | Plugin
Validator — Kernel Protocol reference | 44 / 58 | = before |
| `references/kernel/plugin-versioning.mdx` | Plugin Versioning | Plugin
Versioning — Kernel Protocol reference | 45 / 59 | = before |
| `references/kernel/plugin.mdx` | Plugin | Plugin schema — Kernel
Protocol reference | 41 / 55 | = before |
| `references/kernel/service-registry.mdx` | Service Registry | Service
Registry — Kernel Protocol reference | 44 / 58 | = before |
| `references/kernel/startup-orchestrator.mdx` | Startup Orchestrator |
Startup Orchestrator — Kernel Protocol | 38 / 52 | = before |
| `references/marketplace/index.mdx` | Marketplace Protocol |
Marketplace Protocol — schema reference | 39 / 53 | = before |
| `references/marketplace/marketplace.mdx` | Marketplace | Marketplace —
Marketplace Protocol reference | 44 / 58 | = before |
| `references/marketplace/package-version.mdx` | Package Version |
Package Version — Marketplace Protocol | 38 / 52 | = before |
| `references/marketplace/package.mdx` | Package | Package — Marketplace
Protocol reference | 40 / 54 | = before |
| `references/marketplace/template-manifest.mdx` | Template Manifest |
Template Manifest — Marketplace Protocol | 40 / 54 | = before |
| `references/qa/index.mdx` | QA Protocol | QA Protocol — complete
schema reference | 39 / 53 | = before |
| `references/qa/testing.mdx` | Testing | Testing schema — QA Protocol
reference | 38 / 52 | = before |
| `references/security/index.mdx` | Security Protocol | Security
Protocol — complete schema reference | 45 / 59 | = before |
| `references/security/explain.mdx` | Explain | Explain schema —
Security Protocol reference | 44 / 58 | = before |
| `references/security/misc.mdx` | Misc | Misc schema — Security
Protocol reference | 41 / 55 | = before |
| `references/security/permission.mdx` | Permission | Permission —
Security Protocol reference | 40 / 54 | = before |
| `references/security/rls.mdx` | Rls | Rls schema — Security Protocol
reference | 40 / 54 | = before |
| `references/security/sharing.mdx` | Sharing | Sharing schema —
Security Protocol reference | 44 / 58 | = before |
| `references/shared/index.mdx` | Shared Protocol | Shared Protocol —
complete schema reference | 43 / 57 | = before |
| `references/shared/duration.mdx` | Duration | Duration schema — Shared
Protocol reference | 43 / 57 | = before |
| `references/shared/enums.mdx` | Enums | Enums schema — Shared Protocol
reference | 40 / 54 | = before |
| `references/shared/epoch.mdx` | Epoch | Epoch schema — Shared Protocol
reference | 40 / 54 | = before |
| `references/shared/expression.mdx` | Expression | Expression schema —
Shared Protocol reference | 45 / 59 | = before |
| `references/shared/http.mdx` | Http | Http schema — Shared Protocol
reference | 39 / 53 | = before |
| `references/shared/identifiers.mdx` | Identifiers | Identifiers schema
— Shared Protocol reference | 46 / 60 | = before |
| `references/shared/mapping.mdx` | Mapping | Mapping schema — Shared
Protocol reference | 42 / 56 | = before |
| `references/shared/metadata-types.mdx` | Metadata Types | Metadata
Types — Shared Protocol reference | 42 / 56 | = before |
| `references/shared/protection.mdx` | Protection | Protection schema —
Shared Protocol reference | 45 / 59 | = before |
| `references/shared/value-domain.mdx` | Value Domain | Value Domain —
Shared Protocol reference | 40 / 54 | = before |
| `references/studio/index.mdx` | Studio Protocol | Studio Protocol —
complete schema reference | 43 / 57 | = before |
| `references/studio/flow-builder.mdx` | Flow Builder | Flow Builder —
Studio Protocol reference | 40 / 54 | = before |
| `references/studio/object-designer.mdx` | Object Designer | Object
Designer — Studio Protocol reference | 43 / 57 | = before |
| `references/studio/plugin.mdx` | Plugin | Plugin schema — Studio
Protocol reference | 41 / 55 | = before |
| `references/system/index.mdx` | System Protocol | System Protocol —
complete schema reference | 43 / 57 | = before |
| `references/system/app-install.mdx` | App Install | App Install schema
— System Protocol reference | 46 / 60 | = before |
| `references/system/auth-config.mdx` | Auth Config | Auth Config schema
— System Protocol reference | 46 / 60 | = before |
| `references/system/book.mdx` | Book | Book schema — System Protocol
reference | 39 / 53 | = before |
| `references/system/cache.mdx` | Cache | Cache schema — System Protocol
reference | 40 / 54 | = before |
| `references/system/collaboration.mdx` | Collaboration | Collaboration
— System Protocol reference | 41 / 55 | = before |
| `references/system/core-services.mdx` | Core Services | Core Services
— System Protocol reference | 41 / 55 | = before |
| `references/system/deploy-bundle.mdx` | Deploy Bundle | Deploy Bundle
— System Protocol reference | 41 / 55 | = before |
| `references/system/dev-login.mdx` | Dev Login | Dev Login schema —
System Protocol reference | 44 / 58 | = before |
| `references/system/disaster-recovery.mdx` | Disaster Recovery |
Disaster Recovery — System Protocol reference | 45 / 59 | = before |
| `references/system/doc.mdx` | Doc | Doc schema — System Protocol
reference | 38 / 52 | = before |
| `references/system/email-config.mdx` | Email Config | Email Config —
System Protocol reference | 40 / 54 | = before |
| `references/system/email-template.mdx` | Email Template | Email
Template — System Protocol reference | 42 / 56 | = before |
| `references/system/encryption.mdx` | Encryption | Encryption schema —
System Protocol reference | 45 / 59 | = before |
| `references/system/environment-artifact.mdx` | Environment Artifact |
Environment Artifact — System Protocol | 38 / 52 | = before |
| `references/system/http-server.mdx` | Http Server | Http Server schema
— System Protocol reference | 46 / 60 | = before |
| `references/system/job.mdx` | Job | Job schema — System Protocol
reference | 38 / 52 | = before |
| `references/system/license.mdx` | License | License schema — System
Protocol reference | 42 / 56 | = before |
| `references/system/logging.mdx` | Logging | Logging schema — System
Protocol reference | 42 / 56 | = before |
| `references/system/metadata-persistence.mdx` | Metadata Persistence |
Metadata Persistence — System Protocol | 38 / 52 | = before |
| `references/system/metrics.mdx` | Metrics | Metrics schema — System
Protocol reference | 42 / 56 | = before |
| `references/system/migration.mdx` | Migration | Migration schema —
System Protocol reference | 44 / 58 | = before |
| `references/system/notification.mdx` | Notification | Notification —
System Protocol reference | 40 / 54 | = before |
| `references/system/object-storage.mdx` | Object Storage | Object
Storage — System Protocol reference | 42 / 56 | = before |
| `references/system/registry-config.mdx` | Registry Config | Registry
Config — System Protocol reference | 43 / 57 | = before |
| `references/system/search-engine.mdx` | Search Engine | Search Engine
— System Protocol reference | 41 / 55 | = before |
| `references/system/security-context.mdx` | Security Context | Security
Context — System Protocol reference | 44 / 58 | = before |
| `references/system/settings-client.mdx` | Settings Client | Settings
Client — System Protocol reference | 43 / 57 | = before |
| `references/system/settings-manifest.mdx` | Settings Manifest |
Settings Manifest — System Protocol reference | 45 / 59 | = before |
| `references/system/stack-server.mdx` | Stack Server | Stack Server —
System Protocol reference | 40 / 54 | = before |
| `references/system/supplier-security.mdx` | Supplier Security |
Supplier Security — System Protocol reference | 45 / 59 | = before |
| `references/system/tenant.mdx` | Tenant | Tenant schema — System
Protocol reference | 41 / 55 | = before |
| `references/system/tracing.mdx` | Tracing | Tracing schema — System
Protocol reference | 42 / 56 | = before |
| `references/system/translation.mdx` | Translation | Translation schema
— System Protocol reference | 46 / 60 | = before |
| `references/system/worker.mdx` | Worker | Worker schema — System
Protocol reference | 41 / 55 | = before |
| `references/ui/index.mdx` | UI Protocol | UI Protocol — complete
schema reference | 39 / 53 | = before |
| `references/ui/action-params.mdx` | Action Params | Action Params
schema — UI Protocol reference | 44 / 58 | = before |
| `references/ui/action.mdx` | Action | Action schema — UI Protocol
property reference | 46 / 60 | = before |
| `references/ui/app.mdx` | App | App schema — UI Protocol property
reference | 43 / 57 | = before |
| `references/ui/bulk-action.mdx` | Bulk Action | Bulk Action schema —
UI Protocol reference | 42 / 56 | = before |
| `references/ui/chart.mdx` | Chart | Chart schema — UI Protocol
property reference | 45 / 59 | = before |
| `references/ui/component.mdx` | Component | Component schema — UI
Protocol reference | 40 / 54 | = before |
| `references/ui/dashboard.mdx` | Dashboard | Dashboard schema — UI
Protocol reference | 40 / 54 | = before |
| `references/ui/dataset.mdx` | Dataset | Dataset schema — UI Protocol
reference | 38 / 52 | = before |
| `references/ui/expression-bindable-text-keys.mdx` | Expression
Bindable Text Keys | Expression Bindable Text Keys — UI Protocol | 43 /
57 | = before |
| `references/ui/i18n.mdx` | I18n | I18n schema — UI Protocol property
reference | 44 / 58 | = before |
| `references/ui/notification.mdx` | Notification | Notification schema
— UI Protocol reference | 43 / 57 | = before |
| `references/ui/page.mdx` | Page | Page schema — UI Protocol property
reference | 44 / 58 | = before |
| `references/ui/report.mdx` | Report | Report schema — UI Protocol
property reference | 46 / 60 | = before |
| `references/ui/responsive.mdx` | Responsive | Responsive schema — UI
Protocol reference | 41 / 55 | = before |
| `references/ui/sharing.mdx` | Sharing | Sharing schema — UI Protocol
reference | 38 / 52 | = before |
| `references/ui/view.mdx` | View | View schema — UI Protocol property
reference | 44 / 58 | = before |

---
_Generated by [Claude
Code](https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs content: page titles carry no search intent — median 14 characters, 325 of 403 under 20

2 participants