Uh oh!
There was an error while loading. Please reload this page.
Measure /meta/* READ principal-filtering on a stub-auth boot, and record marketplace preview as unbuildable from the framework - #11845
Conversation
…n a stub-auth boot Part of #11806. A measurement, not a behaviour change — one new test file, no source touched. On a boot whose `auth` slot self-declares `{ status: 'stub', handlerReady: false }` with no `security` service registered, `/meta/*` reads are either refused outright or served unfiltered, never a permission-filtered artifact: anonymous GET /meta/object/:name → 401 UNAUTHENTICATED, read never runs anonymous GET /meta/types → 401 UNAUTHENTICATED, listing never runs two distinct principals, item → served, bodies byte-identical, no mask ran two distinct principals, types → served, identical; resolved from an argument object carrying no principal `shouldDenyAnonymous` inspects only the resolved execution context, never the auth service or its `handlerReady`, and its `requireAuth` opt-out was retired in #3963. With no `security` service the ADR-0106 metadata-plane mask resolves to `passthrough` / `no-service` (D6 tier 1), so no per-caller projection runs. The positive control is a live case, not a comment: the same two principals go down the same channel with a `security` service answering per caller, the two bodies then differ, and the identities the mask was handed are asserted BY NAME — a two-principal scenario whose principals collapse onto one identity measures nothing, which is how a sibling objectui pin came to be unable to detect the loss of what it pinned. Marketplace preview is deliberately absent: `RuntimeMode`'s 'preview' member and the whole `KernelContext.previewMode` block are declared in packages/spec and read by nothing, so that boot is produced by the deployment layer and its reading has to be taken there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-sam
commented
Aug 24, 2026
PM review — PASS. Landing.
Diff verified independently: exactly 1 file, 275 insertions, 0 deletions, and it is a ⭐ Clause-② — NO, and I am supplying the assessment because the PR body omits itThe dispatch brief asked for an explicit assessment against the tree, and this body carries none. That is a process gap worth naming rather than waving through, even though the answer is unambiguous: the diff touches no source at all. Nothing accepts or rejects differently, no exported symbol exists to move, no request that was refused now succeeds. A test-only diff cannot move a contract. ⇒ NO, judged here from the diff rather than inherited. What makes this measurement worth landing rather than just trueThe reading is a zero-hit — "the two principals got the same bytes" — and a zero-hit is worth nothing until the apparatus is shown to see the thing whose absence it reports. Case 5 is that control, and it is a live assertion rather than a comment: same two principals, same dispatcher, same route, same assertions, changing exactly one thing — a ⭐ And the control is hardened against the specific way its sibling failed. objectui PR #6103's two-principal pin could not detect the loss of the protection it claimed to pin, because both principals degenerated to the same identity. Here the control records the The half that was refused, and why the refusal is the right answerMarketplace-preview was reported not constructible from the framework, with the reason measured: One check the suite-count claim earned
Landing: Clause-② NO ⇒ no review gate. Flipping ready and arming auto-merge; it holds for green. This PR is Generated by Claude Code |
os-sam
commented
Aug 24, 2026
CI red — root-caused, fix dispatched. And the miss is mine, not the implementer's.
The new test file adds 2 raw tsc errors to ⛔ Where this actually went wrong, stated plainlyThe implementer declared The defect is my review. I accepted that narrowing without checking the one thing that decides whether it holds. On PR #11840 I checked exactly that — I measured that ⇒ The rule I already had, applied on one PR and skipped on the next: a declared narrowing on The trap underneath, worth carrying beyond this PR
Not folded in, deliberatelyThe same run reports Nothing about the measurement itself is in question: the stub-auth reading, the positive control asserting both principals by name, and the refusal to manufacture a marketplace-preview boot the framework cannot build all stand. Generated by Claude Code |
…orded-argument pin typechecks Part of #11806. Repairs the two raw tsc errors this file added to `@objectstack/runtime`'s TEST_DEBT pile — 227 → 229 — which is what turned `Type Check · debt ledger` red on PR #11845. The two diagnostics, both in this file and nothing else: meta-read-principal-filtering.measurement.test.ts(246,53): error TS2493: Tuple type '[]' of length '0' has no element at index '0'. meta-read-principal-filtering.measurement.test.ts(247,53): error TS2493: Tuple type '[]' of length '0' has no element at index '0'. `getMetaTypes` was declared `vi.fn(async () => …)` — zero parameters — so `mock.calls` types as an array of EMPTY tuples and reading `[0][0]` off it is a type error, even though the domain really does call it as `protocol.getMetaTypes({})`. The double now declares the parameter it is actually invoked with. ⛔ The TEST_DEBT entry is NOT raised: that ledger is a shrink-only ratchet (#5278) and raising it is a maintainer's call, not a way to get CI green. The errors are fixed instead, and the entry stays at 227. ⛔ Neither pin is weakened — that was the constraint on this fix. The recorded-argument assertions (`getMetaTypes.mock.calls[n][0]` deep-equals `{}`) are untouched and are the reason the parameter is declared rather than cast away with `as any`, which would have erased the very thing being pinned. The positive control still asserts the two principals BY NAME (`['u_alice','u_bob']`, not a count of two). Measured with the exclusion lifted, the same way the gate measures it: the package now reports 227 raw errors, back at its ledger number, with none naming this file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4
os-sam
commented
Aug 24, 2026
TEST_DEBT red fixed at the source, not with a cast — head |
Part of #11806
A measurement, not a behaviour change. One new test file; no source is touched.
The question
This is the READ half. The adjacent WRITE-door measurement (#11373 / PR #11472) drove the six mutating doors and is sound; it is a different question and a different boot class, and it is not re-run here.
Answer, stub-auth half — measured
On a boot whose
authslot is occupied by a service self-declaring{ status: 'stub', handlerReady: false }, with nosecurityservice registered:GET /meta/object/:name401 UNAUTHENTICATED; the metadata read never runsGET /meta/types401 UNAUTHENTICATED; the listing never runsGET /meta/object/:nameGET /meta/typessecurityservice answering per principalSo on this boot class
/meta/*reads are either refused outright or served unfiltered — never a permission-filtered artifact.Two in-repo facts carry it, and both are independent of the auth slot's health:
shouldDenyAnonymous(packages/core/src/security/anonymous-deny.ts) inspects only the already-resolved execution context —userId/isSystem, plus theOPTIONSand control-plane-path exemptions. It never consults the auth service or itshandlerReady, and itsrequireAuthopt-out was retired in 把 public 从"全局开关的副产品"升级为声明式能力,然后删掉 api.requireAuth 开关 #3963, so no deployment posture re-opens the door.securityservice the ADR-0106 metadata-plane mask resolves topassthroughwith reasonno-service(D6 tier 1,packages/metadata-core/src/object-schema-fls.ts), so no per-caller projection runs.Answer, marketplace-preview half — not constructible here
RuntimeMode's'preview'member and the wholeKernelContext.previewModeblock (autoLogin,simulatedRole,simulatedUserName,readOnly,expiresInSeconds,bannerMessage) are declared inpackages/spec/src/kernel/context.zod.tsand read by nothing: no runtime branches onmode === 'preview', and the only consumer ofOS_PREVIEW_MODEwidens the better-auth trusted-origin list inpackages/cli/src/commands/serve.ts. A preview boot is produced by the deployment layer, so its reading must be taken there rather than manufactured here. That is stated in the new file's header instead of being approximated with a framework test named after a boot the framework cannot build.Why the positive control is in the file rather than in a comment
A zero-hit reading — "the two principals got the same bytes" — is worth nothing unless the apparatus can be shown to see filtering where filtering exists. The control re-runs the same two principals down the same channel and changes exactly one thing: a
securityservice that answers a different readable-field set per caller. It then asserts the two bodies differ and records theuserIdthe mask was handed for each request.That recording is deliberate. A sibling pin in objectui (PR #6103) was found unable to detect the loss of the protection it claimed to pin, because its two-principal scenario had both principals degenerate to the same identity. The control here asserts the two resolved principals by name, not that there were two of them, so a boot that collapsed both callers onto one identity fails this file instead of passing quietly.
The type-listing case is asserted the same way, on the recorded call argument rather than on two equal bodies: equal bodies alone would also hold if identity were passed and merely ignored today.
Verification
Run at
8d030e26e6.@objectstack/runtimewhole suite with the new file in it: 187 files / 2756 tests passed — 187 is the package's complete test-file count, so the file is inside that run rather than filtered out of it.@objectstack/runtimetypecheck: green.check:nul-bytes,check:route-envelope,check:test-source-alias,check:engine-double-contract,check:where-matcher,check:cross-package-test-inputs,check:slot-lookup,check:published-files,check:type-source-resolution,check:query-options-erasure,check:type-check-coverage.check:type-check-debtwas run, after CI caught what the declared narrowing had left uncovered. First pass at8d030e26e6added 2 raw tsc errors to@objectstack/runtime's TEST_DEBT pile (227 → 229), bothTS2493in this file:getMetaTypeswas declared with zero parameters, somock.callstyped as an array of empty tuples and the recorded-argument assertion could not index it. Repaired atf0eb4bfc6cby declaring the parameter the domain actually calls it with — ⛔ not by raising the shrink-only ledger entry ([finding] DEBT ledger counts in check-type-check-coverage.mjs drift silently — @objectstack/metadata-protocol records 28, actually reports 63 #5278), and ⛔ not by casting the argument away, which would have erased the very thing the case pins. Both pins are byte-for-byte intact. Re-measured with the full closure built:check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 339.8s, 1898 raw tsc error(s) total, none above its recorded number, with@objectstack/runtimeback at its recorded 227.pnpm lintsweep remains a repo-scale run, declared not run locally and left to CI. That is a declared narrowing, not a silent one.skip-changeset: test-only, nothing user-visible is published by this PR.Adjacent and deliberately not addressed here: #11373, whose write-door reading stands unchanged.
Generated by Claude Code
Generated by Claude Code