test(qa): census the authz-conformance probe table's reach across all 11 probe files - #13369
Conversation
📓 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. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
PM 复核:ACCEPT-on-green。已 undraft,绿了就 arm。
⛔ 先纠正我自己:派发令写错了一处,dev 是对的我的派发令说卡里那组正控「都在
⇒ 卡的那组读数(0/0/0/0 对 2/20/78)在 matrix 文件里逐个复现,而 无修复围栏:本席独立验证,成立从 diff 自己读(不是从报告读):change set 恰好 2 个文件,均为 added,都在 我额外查了一条 dev 没被要求证明的:跨包输入面census 从 6 个其它包读源码,而 diff 没有新增任何声明文件 —— 这本该是 ⭐ 本 PR 最有价值的贡献不是那个数字,是三种探针不能用一个数去读
⇒ 如果按一个口径统计,6 条正常工作的绊线会被读成 6 个洞。⭐ 这个区分决定了修复的范围,比 80 这个总数更重要。 消融把缺陷演示出来了,而不只是主张Leg A 是本卡的核心证据:往 三条 NOT MEASURED 记法正确
关于
|
…thz-probe-blind-spot-census
⛔ 不 arm —— CI 是红的,根因已定位,修复已派回
失败点
两条都是普查行的 ⛔ 不许加 baseline 条目 —— 门明写这是 MAINTAINER-ONLY,且不是一个对等选项(那个文件是 shrink-only 的棘轮)。已作为围栏写进派回的指令。 ⭐ 为什么本地没测出来 —— 而这不是粗心
同一轮里,#12573 的 dev 在 #13380 上撞到了同一件事的另一半: ⇒ ⭐ 同一天、同一个新家族、两个独立的 dev。差别不是能力,是其中一个恰好读了 stderr 的横幅。 而那条横幅伴随 exit 0 和一份看起来完整的 stdout。 ⇒ 已立卡 #13392(p1, 已派回的修复要求:把两个 id 从 绿了就 arm。 Generated by Claude Code |
CI fix pushed —
|
Part of #13260 — the MEASUREMENT half only. This PR adds no repair; it sizes the gap so the repair can be scoped from a reading rather than an extrapolation. The card's own sequencing, adopted by the PM, is that only 1 of the 11 probe files had been measured, and repairing from one file's reading would be extrapolation.
What is measured
packages/qa/dogfood/test/authz-conformance.matrix.ts's header states that the companion test "ratchets completeness over a CURATED table of HTTP/transport entry points" and that a new ungated route there "is UNCLASSIFIED ... and breaks CI". This PR measures, for every one of the 11 files thePROBEStable names, how far that reach actually extends.The instrument was first pointed back at the file the card had already measured. It reproduces the card exactly: 16 probe entries over 11 files minting 9 keys repo-wide; 17 registrars in
rest-server.ts; 19 routes under the one probe key; 85 routes mounted, 66 outside every mintable key.The census — all 11 probe files
packages/rest/src/rest-server.tsrouteManager.register(call sites over 17 registrarspackages/runtime/src/http-dispatcher.tsasync handle...(methodspackages/runtime/src/domains/actions.tspackages/runtime/src/domains/automation.tspackages/runtime/src/domains/packages.tspackages/runtime/src/domains/mcp.tspackages/plugins/plugin-hono-server/src/hono-plugin.tsrawAppmount sites, 0 of them data routespackages/services/service-realtime/src/in-memory-realtime-adapter.tspackages/services/service-realtime/src/realtime-service-plugin.tspackages/client/src/realtime-api.tspackages/mcp/src/plugin.tsRepo-wide blind spot: 80 route/handler entry points inside the probe table's own files that no mintable key can ever name (66 + 13 + 1, runtime reading; 75 with the static reading of
rest-server.ts).Row 7's six mounts are deliberately excluded from that total and reported beside it: they are middleware, static-asset and SPA-fallback routes, and folding them in would overstate the data surface. Its real finding is a different one, below.
Reading one number is the mistake this census exists to prevent
The
PROBEStable mixes three instruments with three different promises, and only the first makes "entry points outside every mintable key" a defect:shouldDenyAnonymous(,buildMcpBridge(deps, context),resolveStdioExecutionContext(). It mints exactly one key by construction; its promise is anti-regression on that gate, never route completeness. A 0 there means "this file funnels through the pinned gate", not "every route here is enumerated".Two findings beyond the card's number
1.
registerUiEndpointsis not special — confirmed, and now demonstrated. 16 of the 17 registrars can never mint a key:registerCrudEndpoints,registerApprovalsEndpoints(12 routes),registerDataActionEndpoints(9),registerReportsEndpoints(8),registerSharingRuleEndpoints(5),registerUiEndpoints(1) and the rest. It is simply the registrar a census happened to walk past.2. Row 7 is a DEAD PROBE, and it is dated. Its comment claims live discovery of the plugin's data routes. That spelling occurs zero times in the file. Commit
e5a4d26901(2026-07-31) deleted the plugin CRUD/discovery surface — 3 matching mounts before, 0 after — and the probe stayed behind. It has minted nothing since, in total silence, because STALE only fires for a key some matrix row covers, and no row ever covered adata:hono-plugin.tskey. The spelling it watches is alive one file away, incurrent-user-endpoints.ts(3 mounts, none of them data routes), which the table does not name. This is a second, independent blind-spot mechanism: not "the probe was too narrow from the start" but "the probe's population was deleted and nothing said so".Method, and the positive control behind every zero
registerMetadataEndpoints2 ·anonymous-deny20 ·enforced78, againstregisterUi0 ·/ui0 ·getUiView0 ·ui-view0 — measured againstauthz-conformance.matrix.ts, which is the file those zeros belong to. (Measured for the record: inrest-server.tsthose same terms readregisterUi2 ·/ui4 ·getUiView2, so that file could not have produced the card's zeros.)rest-server.tsprivate register...Endpoints(17 ·this.routeManager.register(80 ·enforceAuth61http-dispatcher.tsasync handleMetadata(1 ·async handleMcp(1 ·HttpDispatcherResult18domains/actions.tsshouldDenyAnonymous(1 ·handleActionsRequest2domains/automation.tsshouldDenyAnonymous(1 ·handleAutomationRequest2domains/packages.tsshouldDenyAnonymous(1 ·handlePackagesRequest2domains/mcp.tsbuildMcpBridge(deps, context)1 ·prefix: '/4hono-plugin.tsrawApp11 ·rawApp.get(3 ·serveStatic3in-memory-realtime-adapter.tsasync publish(1 ·subscriptions12realtime-service-plugin.tsRealtimeService10 ·async init(1client/realtime-api.tssubscriptions14 ·WebSocket7mcp/plugin.tsresolveStdioExecutionContext(3 ·async start(1The last row of that table is the sharpest one:
client/realtime-api.tsmentionsWebSocket7 times while the tripwire's construction spelling (new WebSocket/new EventSource) occurs 0 times — so that zero is unambiguously a reading about the file, not about a missing file.Two readings for
rest-server.ts, both correct. Static: 80routeManagercall sites, 19 in the mintable registrar, 61 outside. Runtime (constructRestServeragainst a recordingRouteManagerand a protocol implementing every optional capability, then call each registrar): 85 / 19 / 66 — the card's figure. The delta is fully explained:registerApprovalsEndpointsbuilds 12 routes from 7 call sites through three route factories (3 + 2 + 4 mounts), andregisterBatchEndpointsmounts all 5 of its routes only when the protocol implementsbatchData/createManyData/updateManyData/deleteManyData(1 of 5 against a bare protocol). The static reading is the one pinned in-tree, because it is the onepackages/qa/dogfoodcan re-derive without depending on@objectstack/rest.Where the census is recorded, and how it is re-derived
packages/qa/dogfood/test/authz-probe-blind-spot.census.ts— the record, the three probe kinds, the derivation rules, andderiveProbeFileCensus(), which re-measures every number from the same source files the probes read.packages/qa/dogfood/test/authz-probe-blind-spot.test.ts— asserts derived equals recorded, asserts every control is still non-zero in its own file, and asserts thePROBEStable still has the shape the census was measured against.It is not a second authorization gate and it classifies nothing. It is a measurement pin: it goes red when the reach changes in either direction — a probe added, a registrar added, a dispatcher handler added, a dead probe revived — so the census is re-read by whoever moved it.
Also pinned, deliberately not repaired here: the matrix header says "
discover(): 15 probes over 11 named source files". The table has held 16 entries since a 16th probe landed on 2026-08-18 without the prose moving; the "15" was accurate when written two days earlier. Correcting that sentence belongs with the repair, not with the measurement.Reverse verification — the defect demonstrated, not just asserted
Both instruments are source-text scanners (
readFileSyncover the probe files), so nodist/resolution and no rebuild leg is involved on either side. Mutation and restore were both proven on disk, by blob hash, never by an exit code; the script carried atrap ... EXIT INT TERMrestore with absolute paths.Leg A — prediction: an ungated route added to a registrar the ratchet cannot reach goes UNNOTICED. One route injected into
registerUiEndpoints(mutation proven on disk: marker present,routeManager.register(sites 80 to 81).authz-conformance.test.ts— 27 tests, 0 failed. GREEN, with an ungated route on disk.authz-probe-blind-spot.test.ts— RED:AssertionError: expected 81 to be 80.Leg B — prediction: the instrument is alive where it can see. The one mintable registrar renamed (mutation proven:
registerMetadataEndpoints7 occurrences to 0, replacement present).authz-conformance.test.ts— RED, 5 failures:AssertionError: STALE covers — surface no longer in source: meta:rest-server.ts:registerMetadataEndpoints.Restore proven both legs:
git diff HEADempty and blobb9809d0e52cd7157f7262ac805f46d9e5e384765, identical to the HEAD blob for that path.An already-complete route enumeration exists one package away
Recorded because the repair should start from it rather than from a wider regex:
packages/rest/src/rest-route-ledger.tsholds 94 audited rows over every route@objectstack/restmounts, enumerated throughRestServer.getRoutes()and guarded byrest-route-ledger.conformance.test.ts— measured green here, 7/7.packages/runtime/src/route-ledger.tsdoes the same for the dispatcher with 80 rows. The authz ratchet's route population is a regex table reaching 1 of 17 registrars, while a complete, runtime-derived, already-guarded enumeration of the same surface sits next to it. Which to use is the follow-up card's decision.What is NOT claimed
⛔ Nothing here asserts that any route is unguarded, unauthenticated or exploitable. Route-level enforcement is mostly in-handler (
enforceAuth,shouldDenyAnonymous, per-object permission checks) and is not measured by this PR at all. What is measured is what the ratchet can see: an entry point outside every mintable key can gain or lose a guard without this gate changing colour. That is a defect in a guarantee, not a breach. Exactly one route in this population has ever been measured unguarded, and that measurement belongs to its own card, which stays open and is neither re-graded nor re-litigated here.⛔ The
securitylabel on the card is a topic marker, not a verdict, and this PR keeps it that way.⛔ The matrix's #8711 caveat is not cited as cover. It disclaims primitive discovery while explicitly claiming route completeness over the curated table, and this gap is on the route side, in files the table already names.
⛔ No repair, and the ratchet is untouched. The suggested registrar-level widening was not applied, nothing was weakened, skipped, quarantined or baselined, and
authz-conformance.test.tsandauthz-conformance.matrix.tsare both byte-identical toorigin/mainin this diff.Verification
Union run on
387711583e, the branch head.Green:
pnpm lint(repo-wide,--no-inline-config, 85s, exit 0) ·pnpm --filter @objectstack/dogfood typecheck(exit 0;tsc --listFilesconfirms both new files are in the program — 2 hits, so this is not a green over source nothing read) ·pnpm --filter @objectstack/dogfood exec vitest run test/authz-conformance.test.ts test/authz-probe-blind-spot.test.ts(54/54) ·pnpm --filter @objectstack/rest exec vitest run src/rest-route-ledger.conformance.test.ts(7/7) · gate families derived withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackand run:check:cross-package-test-inputs,check:test-source-alias,check:nul-bytes,check:engine-double-contract,check:where-matcher,check:query-options-erasure,check:type-check-coverage,check:published-files,check:type-source-resolution,check:slot-lookup,check:page-declaration-shape,check:logger-receiver-detach,check:objectql-double-limit,check-comment-mask-adoption,check-keyed-text-bounds,check-plugin-teardown-shape,check-undeclared-dep-imports,check-ci-filter-parity,check-shard-attestation,check-affected-docs, and speccheck:empty-state/check:liveness/check:strictness-ledger/check:variant-docs.NOT MEASURED (each self-declared by the gate as a prerequisite failure, none of them a red):
check:test-completeness— grades a savedturbo run testlog; there is none locally, and the script's own text says the local reading is NOT MEASURED.check:dual-build-cjs-loads— reads built output; 8 packages have nodist/in this worktree. Its own words: "This is NOT a pass: nothing was measured."check:type-check-debt— refuses--re-measurewhile@objectstack/service-knowledgehas no built type entry point. Its structural halfcheck:type-check-coverageis green, and@objectstack/dogfoodcarries no DEBT or TEST_DEBT entry at all (notest-typecheck-debt.json, no mention in the ledger script), so the ratchet has nothing to re-measure for this package.No changeset: the diff is two test files in
@objectstack/dogfood, aprivate: truepackage that publishes nothing —skip-changeset.Generated by Claude Code
Generated by Claude Code