Skip to content

docs(spec): name the real policy-gate runtime symbols in the publish-gate consumer table - #13046

Merged
os-trump merged 1 commit into
mainfrom
claude/issue-13040-publish-gate-docblock
Aug 29, 2026
Merged

docs(spec): name the real policy-gate runtime symbols in the publish-gate consumer table#13046
os-trump merged 1 commit into
mainfrom
claude/issue-13040-publish-gate-docblock

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

Fixes#13040

The consumer table in packages/spec/src/api/endpoint-publish-gate.ts maps each publish gate to the runtime that enforces the same refusal. Its policy row named two symbols that do not exist: both were renamed in packages/runtime/src/endpoint-policy.ts and the table was never updated. The cost is a reader (human or AI) grepping either name, finding nothing, and concluding the enforcement is missing — exactly backwards, since the enforcement is real and green.

The two corrections

named in the tableactually, in packages/runtime/src/endpoint-policy.ts
cacheControlHeadercomputeCacheControlexport function, line 252
endpointRateLimiterRegistrycreateEndpointRateLimiterRegistryexport function, line 130

Both real symbols were re-verified by reading the file at this PR's base commit 8cb96ec41 before anything was written.

Repo-wide occurrence counts for the two phantom spellings, measured on that base and again on this branch:

spellingbefore, under packages/after
cacheControlHeader21
endpointRateLimiterRegistry21

The card measured these at 9ee2dcfbd as 2 and 1; the second number moved because PR #13039 landed in between. The one remaining hit in each case is now the same line — packages/spec/liveness/api.json:139 — where that PR rewrote the ledger note to narrate this exact rename, and so quotes both old spellings as history. Those are prose about a rename, not pointers to a symbol. With this change the docblock row is no longer among them, and the two phantom names have no surviving live pointer anywhere in the tree.

The rest of the table was swept and is accurate

Every other symbol and path the table names was checked against this tree rather than assumed. No third stale name was found, so this PR corrects two names and no more.

rowreferencefound at
unsupported targetplanEndpointTargetpackages/runtime/src/endpoint-executor.tsexport function, line 221
mappingmappingDeclarationRejectionpackages/runtime/src/api-mapping.tsexport function, line 250
namespace + uniquenessnormalizeEndpointPathpackages/metadata/src/endpoint-matcher.tsre-exported there, line 148
namespace + uniquenessADR-0121 D1/D2docs/adr/0121-declarative-endpoint-routing-namespace-and-channel-split.md, both sections present

The normalizeEndpointPath row is the one that could have been misread as a fourth defect, so it is worth stating why it is not. The symbol is declared in packages/spec/src/api/endpoint.zod.ts and only re-exported by the file the row names. But the column is "runtime counterpart", endpoint-matcher.ts carries export { normalizeEndpointPath } on line 148, and a reader grepping that name in that file finds it. The defect class this card is about is a grep that comes back empty; this one does not, so the row stays as written. Structural changes to the table were out of scope either way.

Verifying mechanically that the diff is comment-only

Two independent methods, following the batch-1 precedent in #12955.

Method 1 — every changed line is a block-comment continuation.git diff -U0 reports one hunk, @@ -31 +31 @@, and the changed-line census is two lines (one removed, one added). Both match ^[+-] \* , and the count of changed lines that do not match is zero:

$ git diff -U0 -- packages/spec/src/api/endpoint-publish-gate.ts \
| grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' > changed-lines.txt
$ wc -l < changed-lines.txt
2
$ grep -vcE '^[+-] \* ' changed-lines.txt
0

Method 2 — the compiler agrees. Method 1 is textual: a line that starts with an asterisk is not proof it is a comment. So both versions of the file were transpiled with the workspace TypeScript under removeComments: true and the emitted code compared byte for byte. The compiler, not a regex, decides what a comment is:

raw source sha256 BASE: 24f0b0721074dd165e60e927d8fd70d5aa29ac8c329a8273f38cbea97985aab1
raw source sha256 HEAD: d341ab8b66555d073e34abe7e8c50be534a56b84243319dfd9dc27debb2ac99b
raw sources differ : true
emit (no comments) BASE : d539f12ec9984abddd409f8e9270d675de225a1834997ebf5429915fe04e7f33 16200 bytes
emit (no comments) HEAD : d539f12ec9984abddd409f8e9270d675de225a1834997ebf5429915fe04e7f33 16200 bytes
VERDICT: COMMENT-ONLY -- sources differ, comment-stripped emit is byte-identical.

The check guards against its own vacuous case: identical inputs would emit identically too, so it exits non-zero with INCONCLUSIVE when the two raw sources match. Here they differ and the stripped emits do not, which is the whole claim.

Changeset: measured against the gate's own convention

The Check Changeset gate in .github/workflows/pr-automation.yml has no path-based skip list. Its job-level condition declares exactly two exemptions, and both mean "this PR declares no release of its own": the skip-changeset label, and the Changesets release PR (changeset-release/main pushed by the bot). So a comment-only edit under packages/spec/src owes the gate something — it is not silently exempt because of where the file lives.

Which of the two routes applies is settled by the gate family's own prose rather than by preference. lint.yml states the prescription directly — a PR that "releases nothing" takes the label — and scripts/check-empty-changeset.mjs shuts the third route by rejecting any empty-frontmatter changeset a PR newly introduces (#5471), with the measurement that an empty changeset buys nothing the label does not and uniquely risks stalling a release.

This PR releases nothing, and that is measured rather than argued: the comment-stripped emit above is byte-identical, so no consumer-observable behaviour moves; and no generated artifact carries this docblock (zero hits for its prose across packages/spec/api-surface/, authorable-surface* and content/docs/). check:docs confirms it from the other side, reporting 231 generated files in sync with packages/spec after the edit. So the route is the skip-changeset label, applied to this PR, and no .changeset/*.md file — leaving the declared file surface at exactly one file.

Gates

Family derived from the real change set, not from a hand-written list: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (exit 0; repo assertion holds against this checkout's origin; change set 1 path — packages/spec/src/api/endpoint-publish-gate.ts). It named 26 families, and all 26 were run at final commit 94ee55c85. Exit codes were captured before any pipe.

All 26 green. Representative verdict lines, quoted from the gates themselves:

  • check:docs231 generated files in sync with packages/spec
  • check:authorable-surfaceauthorable-defaults/ verified against upstream 8cb96ec41b34 — 1246 default(s) unchanged
  • check:livenessevery governed-type property at the walk's one-level granularity is classified, every registered type is governed or explicitly pending, no ledger row outlives its property
  • check:nul-bytesOK (scanned 7245 text file(s) -- 7245 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)
  • check:doc-formula-expressions22 record-scoped formula example(s) across 425 files / 1453 TS blocks judged clean

Two of the 26 first came back as unmet preconditions rather than findings, and are reported honestly as such:

  • check:doc-formula-expressions exited 1 with PREREQUISITE NOT MET — @objectstack/formula is not built, then again for @objectstack/lint. Both packages were built and the gate then ran for real and passed. The reading above is from that real run.
  • check-dev-prereqs.mjs exits 1 with The workspace is not built — 1 unmet precondition, not a list of problems (66 of 67 packages have no dist/ in this fresh worktree; only the @objectstack/spec closure, formula and lint were built here). Its own text says nothing was measured, so this is recorded as NOT MEASURED, not as a red gate. A full 67-package build was deliberately not run for it: the box is shared and the turbo cache is cold here, and CI checks out fresh and builds anyway. The one leg of that gate which reads this PR's package — the packages/spec/dist content hash — is satisfied, the spec build having printed packages/spec/dist/.build-input-hash.

Also run: pnpm --filter @objectstack/spec typecheck (exit 0, tsc --noEmit plus the scripts and test-layer legs). Its green is not taken on faith — tsc --noEmit --listFiles proves the edited file is in that program, packages/spec/src/api/endpoint-publish-gate.ts, 1 match among 906 files.

No ablation is owed here and none was invented. Ablation proves a guard can fail; this PR adds no guard and moves no code, and the comment-only proof above is the evidence that fits the change.

Contract

Clause ② — PATH leg is hit: the file is under packages/spec/src/**, the contract surface, so this PR carries needs:contract-review and the seat reviews it at PASS.

CONTENT is comment-only with zero behaviour movement. That is not a description of intent, it is the measurement in the section above: the comment-stripped emit is byte-identical between base and head. No schema, no export, no accept/reject behaviour, and no authorable surface moves — check:authorable-surface reports 1246 defaults unchanged against upstream, and check:docs reports all 231 generated files in sync.

On the disposition

The card left two shapes open and the dispatch ruled option 1 — correct the two names. Option 2, a doc-comment path#symbol convention plus a resolver lint that would cover the whole class, is not attempted here, on the card's own #11457 caution: it is a new gate with an unmeasured false-positive population, and that measurement is its own piece of work rather than a rider on a two-word correction. The class-level question stays with the board's finding stream; this PR restores the table's truth and claims nothing about the next rename.


Generated by Claude Code

…gate table
The consumer table in endpoint-publish-gate.ts named cacheControlHeader and
endpointRateLimiterRegistry. Neither is a symbol: endpoint-policy.ts declares
computeCacheControl and createEndpointRateLimiterRegistry. Comment-only.
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️1 changed file(s) yielded no anchor (packages/spec/src/api/endpoint-publish-gate.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/api/endpoint-publish-gate.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cf71d73f8428424244b15674942e04ea34feb0a9packageMentionDocs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xsskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-trump@claude