Repository navigation
fix(service-analytics): declare the measured deny on a kernel with no security service, pin it on both in-repo kernels, give the reconcile runner an explicit security double - #22276
Conversation
…eny declaration; pin a never-registered security service on both in-repo kernels Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
…d deny on a kernel with no security service Comments only: the ABSENT branch is reached only by a context that answers a miss with nothing; the in-repo kernels throw on a never-registered name, so the bridges take UNUSABLE and refuse, fail-closed. No line that decides an answer moves (comment-stripped transpile identical to the base). Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
…so the dataset path answers Claude-Session: https://claude.ai/code/session_01WkL6Eijt432S1Y7ekb6ovQ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ea934414a3c1a9ecd6776ff9af408ada03609cdf && git checkout ea934414a3c1a9ecd6776ff9af408ada03609cdf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8cbe255ef6cadf6a38bcf68a49afa43591f88543 183709e70ca3c45a2916ee4e738820d10e558b9f && git checkout -B drift-repro 8cbe255ef6cadf6a38bcf68a49afa43591f88543 && git merge --no-ff 183709e70ca3c45a2916ee4e738820d10e558b9f
node scripts/docs-audit/affected-docs.mjs --json 8cbe255ef6cadf6a38bcf68a49afa43591f88543
|
…reads" sentences to the measured deny (objectstack-ai#22296) Part of objectstack-ai#22279 Clause-②: no This is the docs-only half of objectstack-ai#22279. It amends release-owned text and nothing else. The four `service-analytics` source comments are in a separate PR, on branch `claude/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.md` row: "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". The `content/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, objectstack-ai#22279.)*`. That line quotes the wording that shipped. Earlier in-place amendments in these files use the same shape, for example 5b481e2 and 576afc1 in `packages/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 objectstack-ai#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 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." The sentences were false when they were published, not only now. Measured on the published tarballs (`npm pack`, unpacked into a scratch directory): - `@objectstack/core` 17.5.0 and 17.6.0: the context `getService` of both `ObjectKernel` and `LiteKernel` throws `[Kernel] Service 'NAME' not found` for a name that was never registered. - `@objectstack/service-analytics` 17.5.0 and 17.6.0: the object-level bridge catches that throw as `unusable` and denies. Its `if (resolved.kind === "absent") return true;` line is reached only when the lookup returns nothing. The row-scope bridge does the same and throws `readScopeUnresolvedError`. In 17.6.0 the field-level bridge throws as well. In both releases `assertReadAdmitted` runs before `resolveReadScopes`. 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 objectstack-ai#22276 landed on `main` (`58707166f`). ## What changed Line positions are at base `58707166f`. These files have not changed since `3513ac77`, where the card read its positions. | File | Entry | Passage | Listed on the card? | |---|---|---|---| | `service-analytics/CHANGELOG.md` | 17.6.0 `1571aed` | `:583-584` "applies no field-level check, as on the data API" | yes | | same | 17.6.0 `5f6b63a` | `:645-647` "applies no object-level check, as on the data API" | yes | | same | 17.6.0 `ce4e205` | `:821` "the field-level read gate, which stands down with no security service" | **added** | | same | 17.6.0 `ae1e950` | `:1211-1213` "A deployment with no security service, and an object …, apply no field-level check" | yes | | same, and the three lockstep copies | 17.5.0 `041d9fd` | `:1917-1919` BREAKING banner, "Nothing that was already admitted becomes refused **except** the requests GET /data/OBJECT refuses" | **added** | | same, and the three lockstep copies | 17.5.0 `041d9fd` | `:1926` "keeps its previous analytics behaviour by design" | yes | | same, and the three lockstep copies | 17.5.0 `041d9fd` | `:1930` "an ABSENT `security` service admits (… keeps a deployment shipping no `plugin-security` working as before)" | yes | | same | 17.5.0 `5d12b16` | `:1936` "ABSENT admits, THROWING and METHOD-LESS deny" | yes | | same | 17.5.0 `5d12b16` | `:1952` ABSENT row, "a legitimate configuration … ⛔ Deliberately not tightened" | yes | | same | 17.5.0 `5d12b16` | `:1955` "and no deployment with none, changes behaviour by so much as a byte" | **added** | | same | 17.5.0 `54b3d1d` | `:2773` "a deployment with NO security service still runs unscoped exactly as before" | yes | | `content/docs/releases/v17/17-5.mdx` | object read grant bullet | `:482-486` "keeps its old behaviour and warns at init" | yes (`:482-484`) | The lockstep copies are `plugin-security/CHANGELOG.md` (`:1666-1668`, `:1675`, `:1679`), `spec/CHANGELOG.md` (`:13365-13367`, `:13374`, `:13378`) and `verify/CHANGELOG.md` (`:562-564`, `:571`, `:575`). After the edit the `041d9fd` entry is still byte-identical in all four files: 27 lines each, sha256 prefix `a7ff1b99f2de9992`. 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 supply `admitObjectRead`. ### 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 `/data` still serves them. Left alone, the banner would contradict the amended `:1926` three paragraphs below it in the same entry. One clause is added. - `:1955`: the same entry as `:1936` and `:1952`, with the same claim. - `:821`: the claim `:1211` makes, 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 - The ADR-0087 disposition marker of the `041d9fd` entry (`: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. - `:1930` still 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. - `:1244` says the 17.6.0 member gate refuses "in every tier — including a deployment with no security service". That is true, so it stays. `:1948` describes the value the pre-fix code produced for an absent service. That is history, so it stays too. - Not here, per the card: the runtime UNUSABLE log text in `plugin.ts` and `read-admission.ts`, and any change in behaviour. ## Changeset: none, labelled `skip-changeset` This follows 5b481e2 and 576afc1. `CHANGELOG.md` is in each package's `files[]`, 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 version` prepends to these files and keeps the amended entries. All four packages are in the single `fixed` group of `.changeset/config.json`, and 101 changesets are pending on `main`, so the next release carries the corrected entries. ## Verification (head `2bd53e98a`) - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derives **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/client` and `@objectstack/client-react` closure. - `pnpm check:i18n`: needs the CLI closure, 61 packages. - `pnpm check:dts-closure`, `pnpm check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure` and `pnpm check:sourcemap-no-sources-content`: need a full workspace build. - This narrowing is declared. The diff changes no source byte. A build compiles `src/` and never reads `CHANGELOG.md` or `.mdx`. CI's `Lint & Repo Gates` runs these gates after its own closure build. - `node scripts/pm/dispatch-gates.mjs --ran` gives: "85 derived, 79 run, 6 NOT-MEASURED, 0 UNRUN." - The `@objectstack/lint` closure (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-posture` and `check:docs-transcript-drift`, all exit 0. The build left the working tree clean. - Named results: `check:nul-bytes` scanned 10245 tracked files and found no raw control bytes. `check:release-notes`, `check:release-page-status`, `check:doc-authoring` and `check:issue-citations` all exit 0. A separate control-byte grep over the five files matches nothing. - Lint: `eslint --no-inline-config --format json` over the 5 files reports 5 files, each "File ignored because no matching configuration was supplied". No config object matches `.md` or `.mdx`, so `pnpm lint` has nothing to judge here. ## Acceptance notes - Two test titles say "no security service" where they mean a test double that answers the lookup with nothing. `admission-absence-report.test.ts:199` names its describe "no security service ever registered (the ABSENT resolution)". `caller-member-column-reference-gate.test.ts:109` labels 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](https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… 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>
Fixes #22235
Clause-②: no
Executes the maintainer's ruling B (
6056824332): deny, as measured, becomes the declaration. On a kernel that registers nosecurityservice, the analytics read bridges deny, fail-closed, as they already did. The comments and the test header now say so, a pin holds it on both in-repo kernels, and the one in-repo producer registers an explicit security double. No line that decides an answer moves, on any context.What changed
packages/services/service-analytics/src/plugin.ts: comments only. The resolution blocks of the row-scope bridge (near:556), the object-level admission bridge (near:700) and the field-level and query-side bridges now give the measured classification. ABSENT is reached only by a context that answers a miss with nothing: the package's test doubles do, and no in-repo kernel does.ObjectKernelandLiteKernelthrow on a never-registered name, so the bridges take UNUSABLE and refuse with a locatederror. The same change is made in the twocatchcomments, the two ABSENT-branch comments, the per-query report comment (the block PR fix(service-analytics): log the admitObjectRead bridge's init-time security absence at info; WARN once at the first query that finds none #22234 last changed) and the two init-time comments. Proof that only comments moved: a comment-stripped transpile (ts.transpileModule,removeComments) of the base file and of this head is byte-identical (sha256 prefixc1a4301c498f8087both sides, 30086 bytes).packages/services/service-analytics/src/__tests__/admission-bridge-resolution.test.ts:describe.eachoverObjectKernelandLiteKernel), each booted with nosecurityservice ever registered. It asserts that the kernel's own lookup throws, and that the dataset query is refused withcode: PERMISSION_DENIED,status: 403andobject: employer_member. It also asserts that nothing was read, and that exactly oneerrorline from the object-level bridge names the object and the failedsecuritylookup. Finally, it asserts that the ABSENT branch's WARN did not fire.scripts/analytics-reconcile/boot.tsregisters an explicit, OPENsecuritytest double as a named plugin. It answerscanReadObjecttrue,getReadFilterundefined (no row restriction) andgetReadableFieldsundefined (no answer). The runner compares numbers, not access: its legacyengine.aggregate()path runs with no caller context on an engine with no security middleware, so the dataset path gets the same posture, declared by name.Hypotheses, measured on
origin/main3513ac77plugin.ts:556and:691stated "admits" and called a kernel shipping no plugin-security a legitimate configuration. The same claim repeated at the twocatchcomments, the two ABSENT-branch comments, the report block, the field-level and query-side rows, and both init-time comments. All are rewritten, comments only.boot.ts's ownreconcileApp()with one dual-form widget (todo_taskbystatus,task_metrics/task_count), the composition is denied before this change. The dataset query is refused withAccess deniedand 0 rows, and the bridge logs the UNUSABLEerror. After the change, the dataset query answers 4 groups whose counts equal the legacy path's (2, 1, 4, 1). Refinement: on3513ac77none of the three shipping runners reachesqueryDataset.app-todo.tsboots and skips all 13 widgets and reports as not dual-form, and it exits 0 before and after this change.app-crm.tsandapp-showcase.tsfailMODULE_NOT_FOUNDon example files that no longer exist (noted below).Pin and ablation (head
183709e70c)The pin's subject is imported from source (
../plugin.js), so nodistleg applies.vitest runover the two admission files gives 2 files and 13 tests passed. The new pin is 2 cases.catchclassify a throw as ABSENT. It went throughscripts/ablation-replace.mjs: anchor x1 to x0, replacement x0 to x1, blobccecf120ec1dto1d21d50d6cea, with an outertraprestore.ObjectKernel,LiteKernel) are red, together with the double-based throw case here andadmission-absence-report.test.ts's real-kernel case.READ_SCOPE_COMPILE_FAILED/ 500, so the declaredPERMISSION_DENIED/ 403 envelope naming the object was lost.ccecf120ec1dequalsHEAD:plugin.ts, andgit diff HEADis empty.Local verification (head
183709e70c)@objectstack/service-analytics:typecheck(tsc --noEmit) exits 0. The program includes the test file:--listFileslistsadmission-bridge-resolution.test.ts.vitest run --maxWorkers=2: Test Files 180 passed (180). Tests 4443 passed, 262 skipped (4705).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 66 families for this change set. 65 exit 0.pnpm check:dual-build-cjs-loadsexits 3,PREREQUISITE NOT MET, because 37 workspace packages have nodist/in this worktree. NOT MEASURED. A targeted substitute reading, arequire()of this package'sdist/index.cjs, loads.dispatch-gates --ran: 66 derived, 65 run, 1 NOT-MEASURED, 0 UNRUN.check:type-check-debt: 26 raw tsc errors, none above the recorded number.boot.tscarries the same 8TS2307it carried before this change, and no other diagnostic.eslint --no-inline-config --format jsonover the 3 touched files reports 3 files, 0 errors and 0 warnings. The config lints every TS/JS file outside its never-linted list, so all 3 are in scope. It never enables type-aware linting (noparserOptions.project), so this diff cannot move the verdict on a file it does not touch. The repo-widepnpm lintis left to CI.Changeset: none,
skip-changesetNothing this package publishes changes behaviour. Its
filesaredist,README.mdandCHANGELOG.md. After a rebuild, three of the new comment phrases occur in 0 files underdist/, while the positive control (a runtime string from the same file) occurs in 2. The test file andscripts/are not published.Released sentences that state "absent admits": for the docs card, not edited here
packages/services/service-analytics/CHANGELOG.md::583-584: "A deployment with no security service applies no field-level check, as on the data API.":645-647: "A deployment with no security service applies no object-level check, as on the data API.":1211-1213: "A deployment with no security service, and an object the security service gives no field answer for, apply no field-level check, as before.":1926: "a deployment with nosecurityservice registered keeps its previous analytics behaviour by design, because on that deployment/datacarries no object-level gate either and the equivalence is what is being defended.":1930: "an ABSENTsecurityservice admits (that deployment has no object-level gate on/dataeither, so the two doors still agree, and this is what keeps a deployment shipping noplugin-securityworking as before)":1936: "The object-level bridge tells three resolutions apart — ABSENT admits, THROWING and METHOD-LESS deny aterror.":1952: "ABSENT — nosecurityservice resolved: unchanged. No row-scope provider on this deployment, which is a legitimate configuration (a single-tenant kernel that ships noplugin-security...) ... ⛔ Deliberately not tightened: refusing here would break every such deployment.":2773: "a deployment with NO security service still runs unscoped exactly as before."The same lockstep entry repeats, with the
:1926and:1930sentences, in:packages/plugins/plugin-security/CHANGELOG.md:1675and:1679packages/spec/CHANGELOG.md:13374and:13378packages/verify/CHANGELOG.md:571and:575content/docs/releases/v17/17-5.mdx:482-484: "A deployment with nosecurityservice keeps its old behaviour and warns at init; a registered one that cannot be used denies."Acceptance notes
The same declaration repeats outside this card's file surface, in
service-analytics:read-admission.tsmodule header (:59-65);admitObjectReadandgetReadableFieldsdocs onAnalyticsServiceConfig(analytics-service.tsnear:842and:862);assertReadAdmitteddoc (near:1897);field-read-admission.ts:339-341.Each equates "no provider wired" with "a deployment with no security service". Through the plugin a provider is always wired. Reported to the seat with the docs card's list, not edited here.
On a kernel with no security service, the UNUSABLE
errorline reads "A security service is wired on this deployment". TheassertObjectsReadabledenial line that follows reads "the same verdict GET /data/OBJECT reaches". Neither holds on that deployment (measured throughboot.tsbefore this change). These are runtime strings, outside this card's comments-only fence, and are reported to the seat.scripts/analytics-reconcile/app-crm.tsandapp-showcase.tsimport example files that no longer exist (MODULE_NOT_FOUND), andapp-todo.tshas no dual-form widget. So no shipping runner exercises the dataset path. Driving one synthetic dual-form widget showed that the reconcile tool keys a select dimension by stored value on the legacy path and by option label on the dataset path. Counts agree, keys differ. Noted, not filed: no runner reaches it.Generated by Claude Code