test(plugin-email): assert the over-limit attachment by identity, not deep equality - #16521
test(plugin-email): assert the over-limit attachment by identity, not deep equality#16521claude[bot] wants to merge 1 commit into
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. Part of #16506 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 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): |
|
| run | inputs | exit |
|---|---|---|
| actual | real body + real commit list | 1 — one finding, the commit trailer |
| control | same body, same commit, trailer line deleted | 0 — green |
The control is what makes this precise: the trailer line is the sole cause. The body passes
RULE 1 and RULE 3 as posted, so the prose mentions of other card numbers in it are not implicated,
and no body edit can clear this red.
What the merger needs to do
Take the squash message from this body, which declares the relation once as Part of #16506.
The trailer in the commit message must not reach the landed message.
If a green check is required rather than a documented red, the only route that does not rewrite
pushed history is a replacement branch and a replacement PR carrying the identical tree with a
clean commit message. That trades this PR's identity for a green, so it is the PM's call and not
mine — say the word and I will open it.
Generated by Claude Code
|
Superseded by #16522, which carries the Why this PR could not simply be repaired: its single commit message carried the card relation as a Measured, so the diagnosis is not a guess: the gate run against this PR's real body and real commit This PR's branch is left in place, untouched. Generated by Claude Code |
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
7d8103600, the head this PR points at.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.
Generated by Claude Code