Skip to content

docs(kernel): drop the Security row from the runtime-services Source-of-Truth list, and gate that list - #9861

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9629-security-source-row
Aug 19, 2026
Merged

docs(kernel): drop the Security row from the runtime-services Source-of-Truth list, and gate that list#9861
os-steve merged 1 commit into
mainfrom
claude/issue-9629-security-source-row

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#9629

Implements the 2026-08-18 maintainer ruling: drop the row.services.security is an
internal accessor, not a publicly documented runtime accessor — so the Security row
leaves the runtime-services Source-of-Truth list, no security-service.mdx page is
created, and the accessor is not relocated to another chapter. The ruling's in-card
follow-up ships with it: check:runtime-services-index now holds that list.

The drop

content/docs/kernel/runtime-services/index.mdx carried
Security: packages/spec/src/contracts/security-service.ts under a list whose own
sentence is "Each page links the canonical TypeScript source used to derive signatures".
There is no page, so that sentence was false for exactly one row and a reader following it
landed on a contract file with nothing explaining the accessor.

Every enumeration of this chapter was swept before editing, because dropping one row
while another list still names the service would leave the page contradicting itself:

enumerationnamed security?
runtime-services/meta.jsonpagesno
chapter list ("This chapter documents ...")no
kernel/index.mdxservices.* tableno
runtime-services/versioning.mdx "Current Matrix"no
Stability Legend / "Stability Labels"no — they enumerate levels, not services
Source-of-Truth listyes — the only one

Repo-wide confirmation that nothing else advertises the accessor:
grep -rn 'services\.security' content/docs/ returns 0 hits, and after this change
grep -rn 'contracts/security-service' content/docs/ returns 0 as well (it was 1: the
row itself). So the row was the whole surface, and this is the whole edit.

What replaces it

Dropping a pointer with no replacement can strand a reader, so the row's place carries one
short paragraph saying where security really lives — the permissions guide for access
decisions, permissions/explain for the one in-process call, and ISecurityService in
kernel/contracts/ for the interface an implementation must provide. It records that
services.security is deliberately absent, which is the opposite of advertising it as a
documented runtime accessor, and it is prose rather than a list row so no enumeration
grows back. Trim it if you would rather the chapter say nothing at all.

The gate — the sixth claim

That list was left unheld on purpose: PR #9634 kept it out of its new gate because
encoding a Security allowlist entry would have been one of the three candidate answers
in gate form, quietly deciding an open product question; PR #9753 then pinned the
restraint with two self-test assertions proving its parser could not reach the row.

The ruling closes the question, so "one row per page" is decidable and the limb is built:

  • membership both ways — every page has a canonical-source row, and every row names a
    page (the row's label is the accessor, matched case-insensitively, so SMS answers for
    sms-service.mdx);
  • no duplicates — two canonical sources for one surface is a contradiction membership
    checking passes silently;
  • every path exists — a canonical-source pointer to a file that moved is the same
    broken map as a row with no page.

Deliberately still not held, each for a measured reason. Row order: checks 3 and 6
hold the chapter list and the stability matrix to meta.json nav order because both
demonstrably follow it, which makes that convention the only thing telling the next author
where a new entry goes. This list does not follow it (Data, Sharing, Queue, Email, SMS, Storage, Settings, Audit against nav data, sharing, audit, queue, email, sms, settings, storage) and never claimed to — enforcing it would re-sort a published list to a
convention it never had. And whether a path is the right canonical source: three of
the eight rows point outside packages/spec/src/contracts/ on purpose (Data at
packages/client, Settings at service-settings, Audit at plugin-audit — the last
by #9605), so "canonical sources live under contracts/" would have been false the day it
landed. That the file exists is checkable; which file is canonical is not.

The existing six checks are untouched — this is additive, matching #9753's restraint.
The two #9629 self-test assertions are the one exception, and they are re-aimed rather
than removed: they pinned "the row reaches no limb" while the question was open, and now
pin the answer — the page-less row is caught, and caught as a canonical-source row
against index.mdx, never misread as a stability claim against versioning.mdx or the
kernel table. The half with lasting value (prose is never read as a stability row) is
still pinned directly on the parser.

Verification — at 7a08cbfe2 (final commit)

Reverse-verified on the real tree, not only the fixture. Putting the row back:

✗ check-runtime-services-index -- 1 drift(s) ...
• content/docs/kernel/runtime-services/index.mdx: "Source of Truth" row "Security"
names no page in this chapter -- label each row with the accessor it documents,
one of `audit`, `data`, `email`, `queue`, `settings`, `sharing`, `sms`, `storage`

...and main's version of the script, run against that same tree, is green — which is
the drift itself, measured in one pair:

✓ check-runtime-services-index: ... (Source-of-Truth list not in scope). exit=0

Restored to a byte-identical tree (git status --porcelain empty; the row was restored
with git checkout HEAD -- ..., no stash). Then, green at 7a08cbfe2:

✓ check-runtime-services-index: 8 chapter page(s) vs meta.json "pages", 8 chapter-list
bullet(s) and 8 kernel/index.mdx table row(s), 8 stability-matrix row(s), 8
canonical-source row(s), and 8 declared registry slot(s) vs 55 registered key(s)
✓ check-runtime-services-index --self-test: 36 assertions (was 24)

Gate family derived from the changed paths with scripts/pm/dispatch-gates.mjs, all green
at 7a08cbfe2: check:runtime-services-index, check:doc-anchors (251 fragment links),
check:docs-audit-scope, check:docs-redirects, check:published-readme-links,
check:role-word, check:cross-package-test-inputs, check:node-version,
check:required-contexts, check:shard-attestation, check:workflow-status-functions,
check:type-check-coverage, check:nul-bytes, spec liveness
(check:variant-docs, check:empty-state, check:liveness, check:strictness-ledger),
and eslint scripts/check-runtime-services-index.mjs.

One derived gate was not run locally: check:type-check-debt. It refuses to
--re-measure without a full built package closure (55 workspace dependencies), and this
diff contains no TypeScript — it reached the derived list only because a comment-only
edit to lint.yml matches that gate's source glob. check:type-check-coverage, the same
script without --re-measure, is green. CI runs the re-measure after building the closure.

No changeset: this touches content/docs/, scripts/, and a workflow comment — nothing
publishes. Labelled skip-changeset.


Generated by Claude Code

…of-Truth list, and gate that list
`services.security` is ruled an internal accessor, not a publicly documented
runtime accessor (maintainer ruling, 2026-08-18). The chapter's Source-of-Truth
list carried `Security: packages/spec/src/contracts/security-service.ts` while
`services.security` was documented nowhere under `content/docs/`, so the list's
own sentence -- "Each page links the canonical TypeScript source used to derive
signatures" -- was false for exactly that row: a reader following it landed on a
contract file with nothing explaining the accessor.
The row is dropped. No page is created and the accessor is not relocated. A short
note in its place says where security really lives for a reader who needs it: the
permissions chapter for access decisions, `permissions/explain` for the one
in-process call, and `ISecurityService` in kernel contracts for the interface an
implementation must provide. Five of the chapter's six enumerations already
omitted Security (meta.json "pages", the chapter list, the kernel/index.mdx
table, versioning.mdx's Current Matrix, the Stability Legend), so this is the
whole edit -- nothing else in the chapter names it.
check-runtime-services-index gains a sixth claim rather than losing one. That
list was left unheld on purpose while this was an open product question: any rule
written over it -- an allowlist entry included -- would have answered the question
in a gate, and #9684 pinned that restraint with two self-test assertions. Now
that it is ruled, "one row per page" is decidable, so the limb is built and those
two pins are re-aimed at the answer: the page-less row is CAUGHT, and caught as a
canonical-source row rather than misread as a stability claim. Membership runs
both ways, duplicates are rejected, and each row's path must be a file that
exists. Row ORDER is deliberately not held -- unlike the chapter list and the
matrix, this list has never followed meta.json nav order, and enforcing it would
re-sort a published list to a convention it never had.
The existing six checks are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@github-actionsgithub-actionsBot added size/m documentation Improvements or additions to documentation ci/cd labels Aug 19, 2026
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 19, 2026
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT. Both open questions ruled: A, and keep both omissions.

Reviewed at 7a08cbfe2. GOVERNED_HITS=NONE (surfaces re-derived live from scripts/pm/check-governed-merges.mjs on origin/main). Arming now.

The reverse verification is the reason this lands without argument

You did not tell me the gate works. You ran the pair:

  • Security row restored + your script → RED, exactly one finding, exit=1
  • Security row restored + main's script, same tree → GREEN, exit=0, "(Source-of-Truth list not in scope)"

Same tree, two scripts, opposite verdicts. That difference is the drift, and it is the only form of evidence that distinguishes "I added a gate" from "I added a gate that catches the thing it was written for." Restoring via git checkout HEAD -- rather than the shared stash, and confirming the tree byte-identical afterwards, is the right instinct on a repo where the stash stack is shared across worktrees.

Self-test 24 → 36 assertions.

Q1 — ship A, the prose paragraph

Your reasoning is the deciding one and I'll restate it because it generalises: the row was the chapter's only pointer to security, so dropping it silently converts "documented in the wrong place" into "undiscoverable." The ruling closed the question of whether services.security is a publicly documented accessor; it did not ask for the capability to become unfindable.

Prose rather than a list row is what makes A safe — no enumeration regrows, and your own new gate cannot see it, so the two changes don't fight. And a paragraph that says the accessor is deliberately absent is the opposite of the advertisement the card complained about. It is also the only thing that stops a fourth pass re-adding the row that already survived two deliberate ones.

Q2 — keep both omissions, and keep them documented where you put them

Both are the same correct call: build the checkable half, refuse the judgement half.

  • Row order — you measured it: the published list (Data, Sharing, Queue, Email, SMS, Storage, Settings, Audit) has never matched nav order (data, sharing, audit, queue, email, sms, settings, storage). Encoding order would not have enforced an existing convention, it would have imposed a new one by silently re-sorting a published list. That is a docs decision, not a gate's.
  • Which path is canonical — 3 of 8 rows point outside packages/spec/src/contracts/ on purpose (packages/client, service-settings, plugin-audit per docs(kernel): point the runtime-services index at the audit slot's real registrant #9605). A contracts/-only rule would have been false on the day it shipped, which is the worst kind of gate: red on correct code.

The distinction you drew — the file exists is checkable, the file is the right one is a judgement — is the line I want every gate in this repo drawn on.

On not repeating #9634's and #9753's restraint

Worth naming explicitly: two devs previously declined to gate this list, and #9753 went as far as proving by two self-test assertions that its parser could not reach the Security row. That restraint was correct while the question was open — encoding an allowlist entry then would have been "answer C in gate form", deciding the question by making the drift permanently legal. The ruling closed it, so building the gate now is not a reversal of their judgement; it is the step their judgement was waiting for. You read that correctly.

The one gate not run — accepted, with the finding recorded

check:type-check-debt refuses --re-measure without a full built package closure (55 workspace deps), and this diff contains zero TypeScript. It was derived only because a comment-onlylint.yml edit matches its source glob. check:type-check-coverage — same script, no --re-measure — is green.

That over-derivation is itself a finding: a comment-only workflow edit pulling in a gate that needs a full build closure is the deriver reasoning from file identity rather than change content. It belongs to the #9747 family (a recognizer coarser than the shapes it judges). Not a blocker here; I'm attaching the measurement to #9747 rather than opening a fifth card on the same axis.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 19, 2026 05:47
@os-steve
os-steve added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 4f994deAug 19, 2026
25 checks passed
@os-steve
os-steve deleted the claude/issue-9629-security-source-row branch August 19, 2026 06:08
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddocumentationImprovements or additions to documentationsize/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

services.security is documented nowhere under content/docs/, yet the runtime-services chapter advertises a canonical source for it

2 participants

@os-steve@claude