Repository navigation
test(plugin-security): the delegated-organization-membership parity test boots once per posture and asks only the probes it asserts - #26872
Merged
objectstack-fleet[bot] merged 2 commits intoOct 27, 2026
Conversation
…he asserted probes The delegated-organization-membership parity describe ran one `it` per posture that booted a stack and then asked all nine probes on every cell, though it asserts four of them. Under CI shard load it crossed vitest's 5 s default and evicted merge-queue builds. Measured alone, the boot and seed were about a tenth of that `it` and the five unasserted probes (the four field projections and the update probe, pinned per context elsewhere in the file) about a third. Now one stack per posture is booted in `beforeAll` and shared by three `it`s (read, export, insert), each asking only the probes it pins, with the one writing operation last. Every parity assertion is kept verbatim; the non-vacuity pin is repeated in the export `it` so it does not pass vacuously alone. Claude-Session: https://claude.ai/code/session_01QSeKYmQXM3rHJcPnrEPk4B Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 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): |
Contributor
Author
席位核对 · ACCEPT ·
|
objectstack-fleet
Bot
deleted the
claude/issue-26754-parity-test-budget
branch
October 27, 2026 20:18
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #26754
Clause-②: no
Metadata-protocol: none
维护者速读
delegated-organization-membership的 parity 测试,由「每个 posture 一个大it」改为「每个 posture 只启动一次栈,三个小it(读、导出、插入)共用」,每个it只调用它真正断言的探针。产品代码零改动,不发版。it里多放一份);对三个探针各做一次变异,都只让对应的那个it变红。回滚就是 revert 本 PR。packages/plugins/plugin-security/src/delegated-organization-membership.test.ts里 paritydescribe的beforeAll/afterAll共享栈与三个it;其次是boot()新增的lifetime选项(不加它,共享的引擎会在第一个it后被文件级afterEach销毁)。pnpm --filter @objectstack/plugin-security exec vitest run src/delegated-organization-membership.test.ts -t 'parity: each probe agrees',预期Tests 6 passed | 64 skipped (70),空闲机器上每个 parityit约 0.1–0.3 秒。What changes
One file:
packages/plugins/plugin-security/src/delegated-organization-membership.test.ts. No product source, no timeout raised, nothing skipped or quarantined.The
[#25107 · ADR-0090 D10] paritydescribe. Per posture (group,isolated), one stack is booted and seeded inbeforeAlland destroyed inafterAll. Threeits share it, so a failure names the operation:canReadObject, find ⇔ the read explanation;canExport;canWriteObjectinsert. It runs last, being the only one that writes.Each
itasks only the probes it asserts. The old singleitcalledprobeAnswers(nine probes per cell), asserted four of them, and threw the other five away: the four field projections and the update probe. Those five stay pinned, per context and per object, in the two probe describes above it.boot()gainslifetime: 'describe'. It hands the engine to the caller'safterAllinstead of the file's per-testafterEach, which would otherwise destroy a shared engine after the firstit. Every other caller is unchanged (default'test').Where the time went (measured before choosing the cut)
This is the old
it, instrumented in a throwaway copy, alone and warm, at--maxWorkers=1, two rounds per posture, on base0ea90c7a18:it(411–510 ms)seedForReads)So the boot was not the cost, which falsifies the dispatch's mechanism assumption. The parity
italready booted only once per posture, so sharing a stack alone would save nothing there. The cut that pays is to stop asking for answers the test never reads. Sharing the stack is what lets the three operations become separateits without paying three boots.Why one stack per posture is sound
I measured it directly. On one stack per posture, every read-side outcome (find ok,
canReadObject,canExport, explainallowed,canWriteObjectinsert) was read for all nine cells, then all nine parity inserts were run, then every outcome was read again. Result:outcomes-equal=trueon both postures. The rows a find returns did change (rows-equal=false, for exampleparity_4appears fordelegated in org_a · qa_account), but no parity assertion reads rows, only whether an operation is refused. The olditalready ran later cells' finds after earlier cells' inserts. The insertitnow runs last, so the read and exportits see no parity row at all.Assertions, before and after
expect(lines in the parity describe (textual)its in the fileAll five original assertion lines are present byte-for-byte (compared modulo indentation, by script; none missing, none reworded). The one added line is the original non-vacuity pin (
expect(refused).toEqual(...)), repeated in the exportit, because thatitruns its own find and would otherwise pass vacuously when run alone.Tests
Timings: the slowest parity
it, before and afterThe leg. The card's CI leg was a Test Core shard: turbo
--concurrency=4×VITEST_MAX_WORKERS=3on 4 vCPU, so up to 12 runnable test threads. I approximated it on this 4-vCPU container as the file at--maxWorkers=3(VITEST_MAX_WORKERS=3) beside 11 pure-CPUnodebusy loops: 12 runnable threads. Other agents' unlocked work on the box (builds, a dogfood run) came on top, uncontrolled; load average per run is in the logs below. This is a CPU-share approximation, not a real shard.Paired: the fair comparison. The original file (a byte-identical untracked copy of base
0ea90c7a18's version, blob356eb401) and the new file ran in the same vitest invocation, under the same burners, on heade92c25a1b4. The two files' unchanged controlits ("what the rule leaves alone in the probes") agree within each run, so both saw comparable load.ititit, original / newSequential, same leg. Before on base
0ea90c7a18, after on heade92c25a1b4:groupisolateditsBefore, 3 of 3 sequential runs were red at vitest's 5,000 ms default. Each failing record's stack anchors at the parity
it's definition (old line 648), which is how vitest 4's JSON reporter records a timeout. After, 6 of 6 loaded runs (3 sequential, 3 paired) are green, and the slowest parityitin any of them is 1,885 ms. Caveat: the sequential after-runs met a lighter box than the before-runs (their controlits ran about 1.5× faster), which is why the paired table above is the comparison to quote.Alone (
--maxWorkers=3, no burners): beforegroup897 ms andisolated823 ms; after,groupread / export / insert 315 / 153 / 167 ms andisolated286 / 116 / 115 ms.The reshaped
its still bite: one-shot mutation check (nothing committed)The diff changes no product behaviour, so no ablation was owed. To show that none of the three new
its is vacuous, I mutated each probe on heade92c25a1b4in turn throughscripts/ablation-replace.mjs(wrap mode: anchor must hit once, write verified on disk, restore verified). The mutation made the probe admit exactly the refused context:if (context?.onBehalfOf && context?.tenantId === 'org_d') return Promise.resolve(true);at the top ofSecurityPlugin.canReadObject,canExportandcanWriteObject, insecurity-plugin.ts. The engine's find and insert do not route through these three entry points.its (both postures)itscanReadObjectdelegated in org_d · qa_account: canReadObject ⇔ find: expected true to be falsecanExportdelegated in org_d · qa_account: canExport ⇔ find (an export reads as a find): expected true to be falsecanWriteObjectdelegated in org_d · qa_account: canWriteObject insert ⇔ insert: expected true to be falseAfter each leg:
ok restored: blob == HEAD (fe67e27b8705) and git diff HEAD is empty, and the marker count went back to 0. A first attempt was a no-op: its replacement re-contained the anchor, the tool refused before measuring ("the anchor count moved 1 -> 1"), and it was re-anchored and re-run as above.Package suite and typecheck (head
e92c25a1b4)vitest run --maxWorkers=3over the whole package:Test Files 231 passed (231),Tests 5036 passed | 45 skipped (5081).pnpm --filter @objectstack/plugin-security typecheck: exit 0, endingcheck:test-typecheck: OK — @objectstack/plugin-security's test layer compiles under packages/plugins/plugin-security/tsconfig.test.json. That program lists this file (tsc -p tsconfig.test.json --listFiles: 1 hit, 231 test files).pnpm exec eslint packages/plugins/plugin-security/src/delegated-organization-membership.test.ts: exit 0, no output.test:repoproject.Acceptance notes
it; the probes are about 72%, and a third of theitwas probes it never asserted. (2) "Sharing is safe only if the insert does not change what the read and the export see": the insert changes the rows a later find returns, but no asserted outcome. The isolation here is by order (the one writingitruns last), not by distinct rows: the parity cells must cover the same three objects.its fixed, the slowestits left in this file are the three "what the rule leaves alone in the probes" admitted-probeits, each runningprobeAnswersover three objects. On the same leg they reached 1.9–3.9 s before and 1.5–3.1 s after, under 5 s in every run. They are outside this card, and nothing observed here failed.files[]isdist,README.mdandCHANGELOG.md. Afterpnpm --filter @objectstack/plugin-security build, this diff's symbols (parityContexts,lifetime: 'describe', the newittitles) have 0 hits in those paths, while the positive controls (canExport(,delegatedOrganizationRefusal) hit 4 files each. Nothing publishes. Per the dispatch, theskip-changesetlabel is the seat's to apply; I did not apply it.origin/main(then8eef9a8db4) was merged in ate92c25a1b4before every reading above. Upstream moved code on the measured path: the objectql validate split, a driver-sql move, and fix(plugin-security)!: security explain refuses a userId that no sys_user record has with 404 RECORD_NOT_FOUND #26801's explain-another-user existence check. The last runs only whenrequest.userIdnames another user, and the parity read passes none. The 9 commits that landed since touch nothing underpackages/plugins/plugin-security, and neither in-flight plugin-security PR the claim named had landed (git log 0ea90c7a18..origin/main, with a control commit found in the same range).Verification
The gate union ran on head
e92c25a1b4(git rev-parse --short HEAD), the head of this PR. It used the list re-derived on that head with no paths,node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 52 commands, identical to the pre-merge derivation. Each exit code was captured before any pipe.PREREQUISITE NOT MET: NOT MEASURED locally, not a pass and not a fail.check:dual-build-cjs-loadsneeds every package'sdist/(56 absent in this worktree).check:i18nneeds the CLI's compiledi18n extractand its config closure. Both run in CI'sLint & Repo Gates, and neither reads a test file.dispatch-gates --ranover the recordedCOMMAND :: exit CODElines:52 derived famil(ies) accounted for — 50 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).Gate verdict lines (head e92c25a)
Run logs: the load leg and the box per run
Every run went through
scripts/pm/os-verify-lock.sh, which excludes other locked runs but not unlocked sibling work, so these are shared-box figures. The burners were started and killed by PID inside the run script; nothing was killed by name.Generated by Claude Code