test(plugin-email): assert the over-limit attachment by identity, not deep equality - #16522
Merged
Merged
Conversation
… deep equality The over-limit test's stated subject is the comment above it -- "delivered inline and delivered WHOLE". Deep equality is the WEAKER reading of that: it passes for a copy too, so it cannot tell an untouched buffer from one the service re-encoded and rebuilt to the same bytes. `toBe` on the captured call argument proves the exact instance the caller allocated travelled through the over-limit path untouched, which is what "whole" means. Removing the O(n) walk over the 256 KiB + 1 fixture is a consequence of that, not the reason for it. Measured in this container, under the shared verify lock, `--reporter=verbose`: target test 674 ms -> 1 ms its file, tests total 800 ms -> 125 ms (21 siblings total 126 ms) whole package 2287 ms -> 1537 ms across the same 468 tests The fixture size is unchanged (the over-limit boundary IS the subject), no timeout was raised, and nothing was skipped. The transport fake in this one test now declares its parameter so the captured call is typed rather than cast -- `vi.fn(async () => ...)` has an empty parameter tuple, which makes `mock.calls[0][0]` a type error under the package's test-layer typecheck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
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): |
zhuangjianguo
marked this pull request as ready for review
September 7, 2026 07:20
zhuangjianguo
enabled auto-merge
September 7, 2026 07:20
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.
Part of #16506
Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
What changed
One assertion, in
packages/plugins/plugin-email/src/email-service.queue-delivery.test.ts, in thetest
still refuses the queue for attachments OVER the limit, and stores nothing (#5177):(The committed file inlines the array instead of binding
files. It is bound here only to keepthe non-null operator away from a following bracket: GitHub's renderer treats the byte pair
!+[as the start of image syntax and eats the!— measured on the first version of thisdescription, INSIDE a fenced code block. Read the file, not this excerpt, for the exact text.)
Why — identity is the STRONGER assertion. Speed is the consequence, not the reason.
The comment directly above the assertion states its subject: "Pre-#5177 behaviour, unchanged:
delivered inline and delivered WHOLE." Deep equality is the weaker reading of "whole" — it
passes for a copy too, so it cannot tell an untouched buffer from one the service re-encoded,
sliced and rebuilt to the same bytes.
toBeproves the exact instance the caller allocatedtravelled through the over-limit path untouched.
This is measured, not argued. A reverse mutation made
normalizeMessagehand the transport abyte-identical copy (
content: Buffer.from(a.content)), and the two assertions were run againstthat same mutated tree:
toHaveBeenCalledWith(objectContaining(...))toBe(huge)expected Buffer[...] to be Buffer[...] // Object.is equality/Received: serializes to the same stringThat last line is vitest saying the two buffers are deep-equal. The old assertion could not have
seen the defect; the new one does. Four further mutations at the delivery seam, each with its
mutation proven on disk and its restore proven byte-exact against the HEAD blob:
Object.is equalityoncontenttransport.sendTarget cannot be null or undefinedontoHaveLength(1)transport.send(subarray(0, 10))Object.isoncontenttransport.send(same length,0x42)Object.isoncontent, and it is the ONLY failurenormalizeMessageres.status, because truncating under the limit changes the ROUTINGWhat it cost the repo
That single deep-equality walk over a 256 KiB + 1 buffer ejected two independent PRs from the
merge queue inside 24 hours (#16369 and #16442), and the queue rebuilds everything behind an ejected
PR. The failure reason on both was
Error: Test timed out in 5000ms.— a timeout, not anAssertionError. At ~670 ms on an idle machine the test needs only a ~7.5x slowdown to cross the5000 ms default, which is ordinary contention for one of six parallel shards.
Timings — re-derived in this container, under the shared verify lock,
--reporter=verboseplugin-emailpackage, tests total (468 tests)Its 21 siblings total 126 ms between them. 22 passed on both legs; 468 passed on both package legs.
Absolutes here are shared-box seconds, so the ratio is the durable claim.
What was deliberately NOT done
testTimeoutraised and no per-test timeout added — the point is that the cost isremovable; raising the ceiling would convert a visible ejection into a slow suite nobody looks at;
.todo;plugin-emailwidened.One incidental change inside the same test: its transport fake now declares its parameter
(
vi.fn(async (_message: NormalizedEmailMessage) => ...)).vi.fn(async () => ...)has an emptyparameter tuple, so reading
mock.calls[0][0]is a type error under this package's test-layertypecheck (TS2493 + three TS18048). Declaring the parameter is what makes the captured call typed
rather than cast.
Census — is anything else this shape?
Two instruments, both with their own positive control:
plugin-email/src/*.test.ts: exactly one hit, the one this PR changes (so the census fires onits own target).
change the top entries are 172 ms (
smtp.wire), 163 ms (sys-email-payload.wire), 122 ms and101 ms. The former runner-up at 317 ms is
produces a byte-identical MIME message, inline vs row round trip, whose cost is a real MIME round trip throughonTheWire, not an assertion — adifferent shape, and it already asserts with
toBe.Nothing else in
plugin-emailcarries the defect. Nothing else was touched.Clause-② —
no, measured rather than predictedThe diff is confined to one
*.test.ts. Measured on@objectstack/plugin-email, whosefiles[]isdist:c383352cb, rebuilt. All 6 published files byte-identical (index.d.ts,index.d.mts,index.js,index.mjs, and both.map). The rebuild is proven live bydist/index.jsmtimemoving 1788762853 to 1788762860. Restore proven: blob back to the HEAD blob and
git diff HEADempty.
index.js.maplists 18 sources andindex.mjs.map17; 0 are.test.ts. Firing control:src/email-service.tsIS among the sources of both.0 of the 6 published files; so does this diff's own new text. Firing controls from a non-test
source appear in 4 and 6 of them.
Changeset — the
skip-changesetlabel, derived.github/workflows/pr-automation.ymlroutes a diff that "releases nothing (... tests-only, and thelike)" to the label, which that file marks as the PREFERRED route, and reserves the WHICH LEVEL question for route 1, a PR
that releases something. The measurement above is exactly the evidence for route 2: no published
byte moves.
scripts/check-changeset-no-major.mjsadds the cost of getting this wrong — everypublishable package is in the Changesets
fixedgroup, so apatchhere would version the wholelockstep group for a diff no consumer can observe. The label is applied to this PR.
Verification
91092fbb3, the head this PR points at. Its tree is BYTE-IDENTICAL to the tree verifiedbelow (both trees hash to
fb2e339a1904da01469ce1981e1a6303e82a64ad), so every reading in thissection is a reading about this head.
pnpm --filter '@objectstack/plugin-email^...' build— dependency closure, green.pnpm --filter @objectstack/plugin-email run typecheck— green:tsc --noEmitpluscheck:test-typecheck, the latter reporting0 file(s) / 0 error(s). The edited test file isproven inside the swept population (
tsc --listFileson bothtsconfig.jsonandtsconfig.test.jsonlists it).pnpm --filter @objectstack/plugin-email exec vitest run— 30 files, 468 passed.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackfrom the real change set (the script reads the merge base itself). 53 exit 0. The other three
are NOT MEASURED rather than red, and two say so in their own words:
check:dual-build-cjs-loadsandcheck:published-readme-exportsboth exit 3,PREREQUISITE NOT MET -- this gate reads built output, and some package has no dist/, naming 51 packages thisround never built (none of them touched here);
check:react-declaration-parityneeds abrowser-produced SDUI manifest. All three read built output or a browser artefact, and the
ablation above shows this diff's built output is byte-identical — so their verdicts are invariant
under it. Declared narrowing; CI runs the farm.
git grepfinds zero references to
email-service.queue-deliveryoutside its own package; the same grepshape on
registry.ts:returns hits in 5+ files, so the zero is a reading. The onlyplugin-email/src/*.ts:NNNanchors in the repo nameemail-plugin.ts:117andemail-template-provenance.ts:54— neither is the edited file.pnpm check:nul-bytesgreen, plus a direct control-character scan of the edited file: no hits.Notes for triage
Part of, not a closing keyword: this PR removes the measured cause of the two recorded ejections,but #16506 is a standing anchor that the merge-queue-triage workflow refreshes on every further
ejection, and its own text asks a human to decide whether it closes. That decision is triage's, not
this PR's.
#16434 is a separate card about a different cause — packages running expensive suites under
vitest's default 5000 ms budget — and it stays open on its own terms. The two are not the same
defect: that one is a budget question, this one was a single accidental O(n) assertion.
This PR supersedes #16521
#16521 carried this identical tree. Its single commit message also carried the card relation as a
trailer, which
check:partof-closing-keywordRULE 2 refuses: this repo squash-merges, so everycommit message on a PR is concatenated into the message that lands, and the relation must be
declared once, in the body, only. That gate's own output and its docblock both forbid repairing it
by amend, rebase or force-push, and so do AGENTS.md and the os-dev agent rules -- so the trailer
could not be removed from an already-pushed commit. This branch is the same tree with a commit
message that carries no card-relation trailer. The measurement behind that: the gate run against
#16521's real body and real commit list exits 1 naming only the trailer, and the same body with
only that trailer line deleted exits 0, so the trailer was the sole cause.
Generated by Claude Code