Repository navigation
fix(service-analytics)!: the analytics admission bridge answers 503 SERVICE_UNAVAILABLE, not a denial's 403, when the security service is missing or unusable - #25673
Conversation
…VAILABLE when the security service is missing or unusable The auto-bridged admitObjectRead's UNUSABLE branch (resolving the "security" service threw, or it exposes neither canReadObject nor explain; on the in-repo kernels a never-registered service lands here) now throws readAdmissionUnresolvedError instead of returning false, so the gate answers 503 SERVICE_UNAVAILABLE naming the object rather than a denial's 403. Fail-closed is unchanged. The untrue error log sentence "A security service is wired on this deployment" is corrected in both security bridges. Claude-Session: https://claude.ai/code/session_01JrXUZYyTV4i588KLZRxSvJ Co-authored-by: Claude <noreply@anthropic.com>
… pin answers 503 SERVICE_UNAVAILABLE Claude-Session: https://claude.ai/code/session_01JrXUZYyTV4i588KLZRxSvJ Co-authored-by: Claude <noreply@anthropic.com>
…idge-503 Claude-Session: https://claude.ai/code/session_01JrXUZYyTV4i588KLZRxSvJ 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:
⛔ 3 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 311036dd17d1f5b556dd53f703fdf8a001159a11 && git checkout 311036dd17d1f5b556dd53f703fdf8a001159a11
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f4860da151df014ef093c87d6517d78d92b009d9 032ae182250030691f03bb83915c07d899473359 && git checkout -B drift-repro f4860da151df014ef093c87d6517d78d92b009d9 && git merge --no-ff 032ae182250030691f03bb83915c07d899473359
node scripts/docs-audit/affected-docs.mjs --json f4860da151df014ef093c87d6517d78d92b009d9
|
席位核对 · ACCEPT · 2026-10-24T15:15Z
Generated by Claude Code |
Requested by Sales · project thread
Refs #25561
Clause-②: no
Metadata-protocol: none
This is the ruling-A follow-up on the closed card: ruling record 6384696061 (letter A, maintainer 「25561 同意」). Claim 6384855135, premise amendment 6384944476. PR #25595 landed the card itself; this PR does not close it.
What was wrong
AnalyticsServicePluginbridges the object-level read admission (admitObjectRead) to thesecurityservice. Its UNUSABLE branch covers three cases: resolving the service threw, the service exposes neithercanReadObjectnorexplain, or (onObjectKernelandLiteKernel, whose synchronousgetServicethrows on a miss) nosecurityservice was ever registered. That branch answeredreturn false. So the caller got403 PERMISSION_DENIED, the same bytes as a real denial, although no verdict was reached. After PR #25595 it was the last analytics read gate where a fault was answered as a denial. Itserrorlog line also said "A security service is wired on this deployment". That sentence is false when no security service was ever registered.What changed
plugin.ts, the object-level bridge. The UNUSABLE branch now throwsreadAdmissionUnresolvedError(object), the shared constructor PR fix(service-analytics)!: an analytics read gate that could not decide answers 503 SERVICE_UNAVAILABLE, not the denial's 403 #25595 added inread-admission.ts.assertObjectsReadablerelays it asSERVICE_UNAVAILABLE/503withobjectnaming the object. The message names only the object; the cause stays in the server log. Fail-closed is unchanged: the query is refused before any read.canReadObjectorexplainanswering false still returnsfalse, and the caller still gets403 PERMISSION_DENIED. The ABSENT branch (a context that answers the lookup with nothing admits and warns once) is untouched, as the ruling leaves it unruled.errorline now says thesecurityservice is missing or cannot be used, so no verdict was reached, and analytics refuses rather than fall open. That is true for both the broken and the never-registered case. The row-level read scope bridge in the same file printed the same false sentence for the same two cases. It now says that analytics does not serve rows with no row-level policy when thesecurityservice is missing or cannot be used. That bridge's answer (READ_SCOPE_COMPILE_FAILED) does not change.SERVICE_UNAVAILABLE/ 503: the bridge block and the [finding(service-analytics, core): with no security service ever registered, every analytics query is refused 403 on the in-repo kernels, while the declared contract and the released text say an absent security service admits #22235] declaration inplugin.ts, the fail-direction list inread-admission.ts,AnalyticsServiceConfig.admitObjectReadandassertReadAdmittedinanalytics-service.ts, andscripts/analytics-reconcile/boot.ts. No ADR text states the status, so nodocs/adr/**edit is needed.@objectstack/service-analyticspatch, with a BREAKING banner (a status on the wire moves), the migration, both declaration lines and the ADR-0087 disposition, in the same form as PR fix(service-analytics)!: an analytics read gate that could not decide answers 503 SERVICE_UNAVAILABLE, not the denial's 403 #25595.The pins that moved (403 to 503
SERVICE_UNAVAILABLEwithobject)admission-bridge-resolution.test.tssecuritythrowsadmission-bridge-resolution.test.tscanReadObjectnorexplainadmission-bridge-resolution.test.tsObjectKernel/LiteKernelthat never registerssecurity(two runs)admission-absence-report.test.tsLiteKernelwith no security serviceread-scope-bridge-resolution.test.tsThe real-verdict cases (
canReadObjectfalse,explainallowed false, a later-registered service answering false) stay at403 PERMISSION_DENIED. The log regexes still match the corrected text and were kept.packages/rest: no seam pin of this branch.analytics-read-gate-fault-envelope.test.tsinjects its ownadmitObjectReadand never reaches the bridge. The rest door tests that boot the plugin either wire a security service or assert no status on this path. Nopackages/restsource change: the dataset door already relays a declared 5xx (PR #25595).Tests
Final head
032ae18225. It differs from24895888only by a merge oforigin/main(#25658, which touches.claude/skills/pm-dispatch/references/**andscripts/pm/check-half-states.mjs). Theservice-analyticstree is byte-identical between the two.pnpm --filter @objectstack/service-analytics testat24895888: VERDICT command-exit 0, Test Files 185 passed (185), Tests 4517 passed, 262 skipped (4779). The first run, before the row-scope pin moved, was red on exactly that pin: 1 failed, 184 passed.pnpm --filter @objectstack/service-analytics typecheck: VERDICT command-exit 0.eslint --no-inline-configon the 7 changed source files: exit 0.!title) at032ae18225, closure built under the lock (command-exit 0):pnpm --filter @objectstack/dogfood test, VERDICT command-exit 0, Test Files 293 passed, 3 skipped (296), Tests 2509 passed, 18 skipped (2527).Ablation (one leg per changed negative pin)
Done with
scripts/ablation-replace.mjson the committed fix. The anchorthrow readAdmissionUnresolvedError(object);was replaced byreturn false;: anchor x1 to x0, blob83715b24c049to0e7c50c8c5b1. The tests import../plugin.jsfrom source, so no dist was involved. The three files were then run:Restore: blob after restore
83715b24c049== HEAD blob,git diff HEADempty. The same three files then ran green: Test Files 3 passed (3), Tests 18 passed (18). The 12 that stayed green under the mutation include every real-denial 403 pin, so those pins did not move.Gates
node scripts/pm/dispatch-gates.mjs --commandsat032ae18225derived 82 commands. All 82 were run; each record iscmd :: exit N, and every one exited 0.--ranverdict: "82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero — all 82 recorded an exit code and none of them is 3)". On the earlier run at24895888,check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET, nodist/) andcheck-plugin-teardown-shape --self-testcould not read its pinned fixture on the shallow clone. Both exited 0 at the final head, after the dogfood closure build and a fetch of the fixture commit. CI runs the full farm.Acceptance notes
errorlines per refused query on this path: its own line, carrying the cause, andassertObjectsReadable's catch line ("read-admission resolution failed …"). That second line is the same one every throwing provider already gets. Not changed here.🤖 Generated with Claude Code
https://claude.ai/code/session_01JrXUZYyTV4i588KLZRxSvJ