From d6f93172ef007abde054694ac9e71f844c5c46b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 13:38:29 +0000 Subject: [PATCH 1/2] docs(kernel): list services.sms in both runtime-service index pages The runtime-services chapter list and the kernel module table both enumerated seven slots and omitted services.sms, while sms-service.mdx exists, meta.json lists it in pages, the chapter's own Source-of-Truth list names its contract, and service-sms really registers the slot (sms-plugin.ts:181). Both index pages are fixed in one change so they cannot newly disagree. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- content/docs/kernel/index.mdx | 1 + content/docs/kernel/runtime-services/index.mdx | 1 + 2 files changed, 2 insertions(+) diff --git a/content/docs/kernel/index.mdx b/content/docs/kernel/index.mdx index f58127586e..9141f5d6b7 100644 --- a/content/docs/kernel/index.mdx +++ b/content/docs/kernel/index.mdx @@ -18,6 +18,7 @@ The kernel is ObjectStack's runtime: it loads your metadata artifact, hosts plug | [`services.data`](/docs/kernel/runtime-services/data-service) | stable | CRUD and queries with the caller's permission context | | [`services.sharing`](/docs/kernel/runtime-services/sharing-service) | stable | `buildReadFilter`, `canEdit`, `canDelete`, `canManageShares`, `grant`/`revoke`, `listShares` | | [`services.email`](/docs/kernel/runtime-services/email-service) | stable | `send`, `sendTemplate` | +| [`services.sms`](/docs/kernel/runtime-services/sms-service) | stable | Outbound SMS delivery through pluggable providers (Aliyun SMS, Twilio) | | [`services.queue`](/docs/kernel/runtime-services/queue-service) | stable | Background work and queues | | [`services.settings`](/docs/kernel/runtime-services/settings-service) | stable | App/environment settings | | [`services.storage`](/docs/kernel/runtime-services/storage-service) | stable | File storage | diff --git a/content/docs/kernel/runtime-services/index.mdx b/content/docs/kernel/runtime-services/index.mdx index f070f8ca23..df2b76e449 100644 --- a/content/docs/kernel/runtime-services/index.mdx +++ b/content/docs/kernel/runtime-services/index.mdx @@ -21,6 +21,7 @@ This chapter documents the runtime `services.*` APIs used in hook/action/flow/pl - `services.audit` - `services.queue` - `services.email` +- `services.sms` - `services.settings` - `services.storage` From 88a3e36596c5177379e4bf2254290bed32fae8db Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 13:47:33 +0000 Subject: [PATCH 2/2] ci(docs): gate the runtime-services chapter's index lists against the pages on disk The chapter's pages are enumerated in three hand-written places -- meta.json "pages", the chapter list in runtime-services/index.mdx, and the services.* table in kernel/index.mdx -- and nothing read any of them. check:docs-audit-scope derives WHICH pages the accuracy audit covers, never whether an index enumerates them, so services.sms could ship with a page, a meta.json entry, a registered slot and a canonical-source row while missing from both index lists. The pages on disk are the source of truth. The gate holds all three enumerations and the chapter list's order to them, after first checking each page declares the accessor its filename claims. The Source-of-Truth canonical-source list is deliberately out of scope: it is a superset by exactly one row (Security) whose resolution is an open maintainer question, and encoding any answer -- allowlist included -- would pre-judge it. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja --- .github/workflows/lint.yml | 15 ++ package.json | 1 + scripts/check-runtime-services-index.mjs | 261 +++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 scripts/check-runtime-services-index.mjs diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2911a4e8be..8ee62d1ede 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -455,6 +455,21 @@ jobs: - name: Quick-reference section counts match their tables run: pnpm check:quick-reference-counts + # #9604: the runtime-services chapter's pages are enumerated in three + # hand-written places (meta.json "pages", the chapter list in + # runtime-services/index.mdx, the services.* table in kernel/index.mdx) and + # nothing read any of them -- check:docs-audit-scope derives WHICH pages the + # accuracy audit covers, never whether an index enumerates them. #9604 found + # services.sms with a page, a meta.json entry, a registered slot and a + # canonical-source row, missing from BOTH index lists; #9588 was the same + # page drifting on a different line. The pages on disk are the source of + # truth and the gate holds all three enumerations (and the chapter list's + # order) to them. It lives in this job with the other docs guards: the edit + # that breaks it is a docs edit, so a packages/** paths filter would blind + # it to its own failure mode. + - name: Runtime-services indexes enumerate the chapter's real pages + run: pnpm check:runtime-services-index + # #3723 ADR anchors: code an accepted ADR governs must keep naming it. # That incident reversed three accepted ADRs with a patch-level changeset, # and the mechanism was simply that the edited file never mentioned them — diff --git a/package.json b/package.json index 9d1d378aac..7b4ce6120a 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "check:docs-image-tag-sync": "node scripts/sync-docs-image-tags.mjs --self-test", "check:role-word": "node scripts/check-role-word.mjs --self-test && node scripts/check-role-word.mjs", "check:quick-reference-counts": "node scripts/check-quick-reference-counts.mjs --self-test && node scripts/check-quick-reference-counts.mjs", + "check:runtime-services-index": "node scripts/check-runtime-services-index.mjs --self-test && node scripts/check-runtime-services-index.mjs", "check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs", "check:skill-frame-freshness": "node scripts/check-skill-frame-freshness.mjs --self-test && node scripts/check-skill-frame-freshness.mjs", "check:skill-compatibility": "node scripts/check-skill-compatibility-version.mjs --self-test && node scripts/check-skill-compatibility-version.mjs", diff --git a/scripts/check-runtime-services-index.mjs b/scripts/check-runtime-services-index.mjs new file mode 100644 index 0000000000..689dc761b7 --- /dev/null +++ b/scripts/check-runtime-services-index.mjs @@ -0,0 +1,261 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// check-runtime-services-index (#9604) -- hold the runtime-services chapter's +// two INDEX lists to the pages that actually exist. +// +// node scripts/check-runtime-services-index.mjs +// node scripts/check-runtime-services-index.mjs --self-test # verify the checker itself +// +// ## What it guards +// +// `content/docs/kernel/runtime-services/` publishes one `-service.mdx` +// page per documented `services.` accessor, and THREE hand-written places +// claim to enumerate them: +// +// 1. `runtime-services/meta.json` -> `pages` (chapter nav order) +// 2. `runtime-services/index.mdx` -> "This chapter documents ..." bullets +// 3. `kernel/index.mdx` -> the `services.*` table +// +// None of the three is generated, so each drifts from the tree one edit at a +// time, and nothing reads them: `check:docs-audit-scope` derives WHICH pages the +// docs-accuracy audit covers, never whether an index enumerates them. #9604 +// measured the result -- `services.sms` had a page, a `meta.json` entry, a +// registered slot (`sms-plugin.ts:181`) and a canonical-source row, and was +// still missing from BOTH index lists. It shipped that way and every gate was +// green. #9588 was the same page drifting on a different line. +// +// Nothing breaks at runtime; the cost is that an index page's whole job is to be +// a trustworthy map. A reader who does not find SMS in the list concludes the +// chapter has no SMS page. `content/docs/` is also the corpus humans and AIs +// copy from, so a short list is read as a fact about the platform's surface. +// Declared = enforced. +// +// ## What "derived" means here +// +// The pages on disk are the source of truth -- they are the thing a reader can +// actually open. Each page also has to declare its own accessor +// (`title: services.` matching its filename), which is checked first: it +// is the premise the other three comparisons rest on, so a page that lies about +// its own name must go red here rather than silently redefine the expected set. +// +// Order is enforced too, not just membership. The chapter list currently +// follows `meta.json`'s `pages` order exactly, and that convention is the only +// thing that tells the next author WHERE a new bullet goes. Membership-only +// checking would have accepted `services.sms` appended at the end, next to a +// list whose order encodes the nav -- so the gate keeps the answer mechanical. +// +// ## Deliberately NOT checked: the "Source of Truth" list +// +// `index.mdx` carries a fourth list -- `-