Repository navigation
docs(releases): amend the released "absent security admits analytics reads" sentences to the measured deny - #22296
Conversation
…reads" sentences to the measured deny Amends, in place, the released CHANGELOG entries (service-analytics 17.5.0 and 17.6.0, and the 041d9fd lockstep entry in plugin-security, spec and verify) and the v17.5 release page. Each said a deployment with no security service keeps its analytics behaviour, applies no object- or field-level check, or runs unscoped. On the published 17.5.0 and 17.6.0 tarballs, ObjectKernel and LiteKernel throw on a never-registered `security` service, so the analytics bridges take the UNUSABLE branch and refuse the query, fail-closed. Docs-only. No code, changeset, or other entry moves. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude <noreply@anthropic.com>
… in-place amendment shape Each amended CHANGELOG entry, and the v17.5 release-page bullet, gains one dated erratum line at its own end. It quotes what the entry said and says the sentence was false when published. This is the shape the earlier in-place amendments in these files use, so the correction is not a silent rewrite and stays inside the entry it corrects. Claude-Session: https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37787288895 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
… a deployment with no security service (objectstack-ai#22299) Part of objectstack-ai#22279 Clause-②: no This is the source-comment half of objectstack-ai#22279: comments only, plus the patch changeset their published text needs. The release-owned text is in the docs-only PR objectstack-ai#22296. The card stays open until both PRs have merged. ## Why Maintainer ruling B on objectstack-ai#22235 (`6056824332`, 「同意」, 2026-10-08), verbatim: "**B — deny, as measured, becomes the declaration.** On a deployment with no security service the analytics read bridges deny, fail-closed, as they do today; the bridge comments and the header of `admission-bridge-resolution.test.ts` say so; the "absent admits" sentence in the released text is corrected by a docs card, since that text is release-owned." PR objectstack-ai#22276 (`58707166f`) rewrote the bridge comments in `plugin.ts` and the test header to that ruling. The comments here are the ones its acceptance notes listed outside its fence. Each one equated "no provider wired" with "a deployment with no security service" and said such a deployment keeps its analytics behaviour. Through `AnalyticsServicePlugin` a provider is always wired, either the host's own or the bridge. On `ObjectKernel` and `LiteKernel` the lookup of a `security` service that was never registered throws, so the bridges take UNUSABLE and refuse the query, fail-closed. The new text follows the `plugin.ts` wording on `main`. ⛔ It does not say the ABSENT branch denies. ABSENT is described as reached only by a context that answers the lookup with nothing (the package's test doubles do, and no in-repo kernel does). Whether ABSENT itself should deny is not ruled. ## What changed (comments only) Line positions are at base `58707166f`. | File | Site | Listed on the card? | |---|---|---| | `read-admission.ts` | module header, *Fail direction*, `:51-65`. The ABSENT bullet is replaced by three bullets: the declared deny on a deployment with no security service, the ABSENT context, and a host that constructs `AnalyticsService` with no provider. The lead-in "but closed is a claim about a WIRED provider" goes. | yes (`:59-65`) | | `analytics-service.ts` | `AnalyticsServiceConfig.admitObjectRead` doc, `:842-845` | yes | | `analytics-service.ts` | `AnalyticsServiceConfig.getReadableFields` doc, `:862-866` | yes | | `analytics-service.ts` | `assertReadAdmitted` doc, `:1899-1901` (the card's `:1897-1899`) | yes | | `field-read-admission.ts` | `assertCallerMembersJudgeable` header, `:338-341` | yes (`:339-341`) | | `analytics-service.ts` | the `[objectstack-ai#20917]` field-level read gate doc, `:1922` "A no-op when no provider is wired (no security service)". It documents `assertFieldsReadable` but sits detached, directly above the member-shape gate's doc. | **added** | | `analytics-service.ts` | `assertCallerMembersResolvable` doc, `:1935` "that gate is a no-op with no security service" | **added** | | `analytics-service.ts` | `assertDatasetFieldsJudgeable` doc, `:2061` "which stands down with no security service" | **added** | | `read-scope-refusal.ts` | `readScopeUnresolvedError` doc, `:96-99`: "a deployment with no security service … is reported loudly at init, and it must keep running unscoped exactly as before" | **added** | ### The four added sites (bounded in-place fix, declared here) The card listed four sites, taken from PR objectstack-ai#22276's acceptance notes. A census of the package's `src/` for the same claim, `git grep -n -i 'no .?security.? service'` outside `__tests__`, found four more. Three are in `analytics-service.ts`, a file this card already holds. The fourth is the row-scope sibling of the read-admission header. All four meet the four conditions: the same defect class as the card, a mechanical rewording to a shape the ruling already fixed, the same gates, and no other claim on the file (`read-scope-refusal.ts` is in none of the 12 open PRs' file lists, which were read during this run before the edit). Leaving them would keep the claim alive twenty lines below the doc this PR corrects. Reviewer: if you want any of them out, say which and it comes back in a patch round. ## Proof that only comments moved For each of the four files, `ts.transpileModule` with `removeComments: true` gives byte-identical output for the base file and for this head: ``` read-admission.ts base=8a4829c31e94d58c/1351 head=8a4829c31e94d58c/1351 analytics-service.ts base=8dd74af6788b7660/65291 head=8dd74af6788b7660/65291 field-read-admission.ts base=9be45d2722f380cd/5145 head=9be45d2722f380cd/5145 read-scope-refusal.ts base=badef6b1c177459d/233 head=badef6b1c177459d/233 ``` (sha256 prefix / bytes.) Positive control: the same comparison over `read-scope-refusal.ts` with `err.status = 500` changed in memory to `501` reports the stripped outputs as different. ## Changeset: `patch` for `@objectstack/service-analytics`, not `skip-changeset` AGENTS.md: published means what `files[]` ships, and this package ships `["dist","README.md","CHANGELOG.md"]`. After a rebuild of the package (under the verify lock), the edited JSDoc is in the published output: - "That is not a deployment with no security service" (the two `AnalyticsServiceConfig` docs) is in `dist/index.d.ts` and `dist/index.d.cts`. - "without `admitObjectRead`. `AnalyticsServicePlugin` always wires one" and "no-op with no field reader wired" (class-member docs) are in all four of `dist/index.{js,cjs,d.ts,d.cts}`. - "a deployment that registers NO security service denies" (the module header) is in `dist/index.d.ts` and `dist/index.d.cts`. - Control for that reading: the `read-scope-refusal.ts` phrase "their lookup throws on the never-registered name" is in 0 files, because a non-exported function's doc is not emitted. A runtime string from `plugin.ts` is in 2 files. The published `@objectstack/service-analytics@17.6.0` tarball carries the old sentences in the same files. "the deployment has no security service, which" and "keeps its pre-existing analytics behaviour" are in its `index.d.ts` and `index.d.cts`. "A no-op when no provider is wired: that is a deployment with no security" is in all four. So this diff changes published bytes and takes a `patch`: `.changeset/22279-analytics-absent-security-comments.md`. This differs from PR objectstack-ai#22276, which carried `skip-changeset`. Its `plugin.ts` comments sit inside a function body, measured at 0 files under `dist/`. Here the comments are on an exported interface and on class members, which the build keeps. ## Verification (head `7fb9cee98`) - `@objectstack/service-analytics` was built under the verify lock: its dependency closure, then the package itself. `check-dts-emitted` found "2/2 declared declaration file(s) present". - `pnpm --filter @objectstack/service-analytics typecheck` exits 0. - `pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2`: "Test Files 180 passed (180)", "Tests 4443 passed | 262 skipped (4705)". These are the same counts PR objectstack-ai#22276 recorded. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives **58** commands. Each was run with its exit code captured before any pipe. **57 exit 0.** **1 is NOT MEASURED**: `pnpm check:dual-build-cjs-loads` exited 3, `PREREQUISITE NOT MET`, because 65 workspace packages have no `dist/` in this worktree. A targeted substitute reading: `require('./dist/index.cjs')` of this package loads, with 17 exports and `AnalyticsServicePlugin` a function. `dispatch-gates --ran` gives: "58 derived, 57 run, 1 NOT-MEASURED, 0 UNRUN." - Named results: `check:dts-closure` reports 75/75 declaration files across 15 built packages. `check:sourcemap-no-sources-content` reports 105 maps across 15 packages, none embedding source. `check:published-files`, `check:nul-bytes` (10246 tracked files, no raw control bytes), `check:doc-authoring`, `check-empty-changeset` ("1 declaring changeset(s) added") and `check:lean-entry-closure` all exit 0. Locally, `check-changeset-no-major` reads its clause-② level axis as not applicable, because there is no `pull_request` payload. CI reads this body's `Clause-②: no`. - Lint, narrowed: `eslint --no-inline-config --format json` over the 4 touched source files reports 4 files, 0 errors and 0 warnings. The config's only global ignores are `node_modules`, `dist`, `build`, `.next` and `.turbo`, so all 4 files are in scope. The config never enables type-aware linting (no `parserOptions.project` in `eslint.config.mjs`), so this diff cannot change the verdict on a file it does not touch. The repo-wide `pnpm lint` is left to CI. ## Acceptance notes - Not here, per the card: the runtime UNUSABLE log text ("A security service is wired on this deployment") in `plugin.ts` and `read-admission.ts`, and any change in behaviour. - Two test titles still say "no security service" for a double that answers the lookup with nothing. One is the describe at `admission-absence-report.test.ts:199`, the other the tier label at `caller-member-column-reference-gate.test.ts:109`. Test names are outside this comments-only fence. Noted, not filed. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #22279
Clause-②: no
This is the docs-only half of #22279. It amends release-owned text and nothing else. The four
service-analyticssource comments are in a separate PR, on branchclaude/issue-22279-analytics-source-comments. The card stays open until both PRs have merged.Route: amend each entry in place and add a dated erratum inside it
AGENTS.md, Documentation Guardrails, the
packages/*/CHANGELOG.mdrow: "Factual error in a released entry → amend that entry in a dedicated docs-only PR, ⛔ never an erratum in a later entry and never a rider on code changes". Thecontent/docs/releases/row: "Factual error on a releases page → dedicated docs-only PR or an issue, never a rider on code changes."Triage asked this PR to say which route it took: "⛔ No silent rewrite of a published CHANGELOG line, if that process forbids one; the PR states which route it took."
The route: each false passage is corrected inside the entry that published it. Each amended entry also gets one dated line at its own end, in the form
*Erratum, 2026-10-08 — this entry said "…". … (Corrected after publication, #22279.)*. That line quotes the wording that shipped. Earlier in-place amendments in these files use the same shape, for example 5b481e2 and 576afc1 inpackages/spec/CHANGELOG.md, and the bulk-callers bullet on the 17.0 release page. So nothing is rewritten silently. The shipped wording is quoted in the entry, kept in the published tarballs and kept in git history. No later entry is written, and no changeset is added (see below).Why: maintainer ruling B on #22235
Ruling
6056824332(「同意」, 2026-10-08), verbatim: "B — deny, as measured, becomes the declaration. On a deployment with no security service the analytics read bridges deny, fail-closed, as they do today; the bridge comments and the header ofadmission-bridge-resolution.test.tssay so; the "absent admits" sentence in the released text is corrected by a docs card, since that text is release-owned."The sentences were false when they were published, not only now. Measured on the published tarballs (
npm pack, unpacked into a scratch directory):@objectstack/core17.5.0 and 17.6.0: the contextgetServiceof bothObjectKernelandLiteKernelthrows[Kernel] Service 'NAME' not foundfor a name that was never registered.@objectstack/service-analytics17.5.0 and 17.6.0: the object-level bridge catches that throw asunusableand denies. Itsif (resolved.kind === "absent") return true;line is reached only when the lookup returns nothing. The row-scope bridge does the same and throwsreadScopeUnresolvedError. In 17.6.0 the field-level bridge throws as well. In both releasesassertReadAdmittedruns beforeresolveReadScopes.The new text states what was ruled: on a deployment with no security service the bridges deny, fail-closed. ⛔ It does not say that the ABSENT branch denies. ABSENT is described as a state reached only by a context that answers the lookup with nothing, and no in-repo kernel does that. This matches the bridge comments that PR #22276 landed on
main(58707166f).What changed
Line positions are at base
58707166f. These files have not changed since3513ac77, where the card read its positions.service-analytics/CHANGELOG.md1571aed:583-584"applies no field-level check, as on the data API"5f6b63a:645-647"applies no object-level check, as on the data API"ce4e205:821"the field-level read gate, which stands down with no security service"ae1e950:1211-1213"A deployment with no security service, and an object …, apply no field-level check"041d9fd:1917-1919BREAKING banner, "Nothing that was already admitted becomes refused except the requests GET /data/OBJECT refuses"041d9fd:1926"keeps its previous analytics behaviour by design"041d9fd:1930"an ABSENTsecurityservice admits (… keeps a deployment shipping noplugin-securityworking as before)"5d12b16:1936"ABSENT admits, THROWING and METHOD-LESS deny"5d12b16:1952ABSENT row, "a legitimate configuration … ⛔ Deliberately not tightened"5d12b16:1955"and no deployment with none, changes behaviour by so much as a byte"54b3d1d:2773"a deployment with NO security service still runs unscoped exactly as before"content/docs/releases/v17/17-5.mdx:482-486"keeps its old behaviour and warns at init":482-484)The lockstep copies are
plugin-security/CHANGELOG.md(:1666-1668,:1675,:1679),spec/CHANGELOG.md(:13365-13367,:13374,:13378) andverify/CHANGELOG.md(:562-564,:571,:575). After the edit the041d9fdentry is still byte-identical in all four files: 27 lines each, sha256 prefixa7ff1b99f2de9992. The four copies were also identical before the edit.Each amended entry gets one erratum line: seven in
service-analytics/CHANGELOG.md, one in each lockstep copy and one in the release-page bullet. On the release page, the migration line also gains the remedy for a deployment with no security service: register one, or supplyadmitObjectRead.The three added passages (bounded in-place fix, declared here)
The card did not list these. Each makes the same claim and sits inside an entry this PR amends, or in the same file:
:1917-1919(all four copies): on a kernel with no security service, analytics reads that 17.4 served became refused, while/datastill serves them. Left alone, the banner would contradict the amended:1926three paragraphs below it in the same entry. One clause is added.:1955: the same entry as:1936and:1952, with the same claim.:821: the claim:1211makes, in another 17.6.0 entry of the same file.All three meet the four conditions: the same defect class as the card, a mechanical rewording to a shape the ruling already fixed, a file this card already holds, and the same gates. Reviewer: if you want any of them out, say which and it comes back in a patch round.
Not changed, on purpose
041d9fdentry (:1912) is an HTML-comment line that does not render. Its prose also says the narrowing is "the same verdict GET /data/OBJECT already returns". It is the machine-read disposition record, its category (not-required) does not change, and it is left as published.:1930still says the plugin "warns loudly at init when no security service is registered". That stays because 17.5.0 and 17.6.0 did print that warning at init (measured in both tarballs). Only what the bridge then does is corrected.:1244says the 17.6.0 member gate refuses "in every tier — including a deployment with no security service". That is true, so it stays.:1948describes the value the pre-fix code produced for an absent service. That is history, so it stays too.plugin.tsandread-admission.ts, and any change in behaviour.Changeset: none, labelled
skip-changesetThis follows 5b481e2 and 576afc1.
CHANGELOG.mdis in each package'sfiles[], so the next tarball ships the amended text. A changeset is still the wrong tool here. It would compile the correction into a new entry under a later version. That is the "erratum in a later entry" the guardrail rules out, and this PR changes no package code.changeset versionprepends to these files and keeps the amended entries. All four packages are in the singlefixedgroup of.changeset/config.json, and 101 changesets are pending onmain, so the next release carries the corrected entries.Verification (head
2bd53e98a)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 85 commands for this diff. Each was run with its exit code captured before any pipe. 79 exit 0. 6 are NOT MEASURED: each exited 3,PREREQUISITE NOT MET, because it reads build output this docs-only worktree does not hold.pnpm --filter @objectstack/spec run check:skill-examples: its client-SDK half needs a 36-package build of the@objectstack/clientand@objectstack/client-reactclosure.pnpm check:i18n: needs the CLI closure, 61 packages.pnpm check:dts-closure,pnpm check:dual-build-cjs-loads,pnpm check:lean-entry-closureandpnpm check:sourcemap-no-sources-content: need a full workspace build.src/and never readsCHANGELOG.mdor.mdx. CI'sLint & Repo Gatesruns these gates after its own closure build.node scripts/pm/dispatch-gates.mjs --rangives: "85 derived, 79 run, 6 NOT-MEASURED, 0 UNRUN."@objectstack/lintclosure (lint, formula, spec, sdui-parser) was built under the verify lock first, so these gates were measured rather than refused:check:docs("225 generated files in sync"),check:doc-formula-expressions,check:doc-security-postureandcheck:docs-transcript-drift, all exit 0. The build left the working tree clean.check:nul-bytesscanned 10245 tracked files and found no raw control bytes.check:release-notes,check:release-page-status,check:doc-authoringandcheck:issue-citationsall exit 0. A separate control-byte grep over the five files matches nothing.eslint --no-inline-config --format jsonover the 5 files reports 5 files, each "File ignored because no matching configuration was supplied". No config object matches.mdor.mdx, sopnpm linthas nothing to judge here.Acceptance notes
admission-absence-report.test.ts:199names its describe "no security service ever registered (the ABSENT resolution)".caller-member-column-reference-gate.test.ts:109labels a tier "no security service (no field reader wired)". Test names are outside a docs-only PR and outside a comments-only PR. Noted, not filed. Carrier: none.Generated by Claude Code