docs(plugin-email): re-anchor the dead tracker citations to the commits that decided them - #20757
Conversation
…ts that decided them Sixteen comment and docblock sites under packages/plugins/plugin-email/src cited tracker numbers that no longer resolve. Each now cites the commit in this repository's history that decided what the line describes: - #8675 -> c9f5950 (sys_account OAuth tokens internal; the key-absence trap on optional columns and the absenceProvesStrip discriminator) - #11741 -> b706af9 (SendEmailInput.organizationId, stamped pass-through onto sys_email by its producers) - #13189 -> 33fbd35 (the SMTP port guard tests integrality, and the generated refusal sentence says so) - #13190 -> 56c5b1d (a present-but-unreadable smtp_port reaches the guard instead of silently falling back to 587) Comments only: 16 lines out, 16 in, every file keeps its line count, and no code token moves. Test titles carrying these numbers are string tokens and are left. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
The rewritten docblocks and inline comments ship: two anchors reach the package's JavaScript entries and two its declaration files, so the change publishes bytes and takes a patch changeset. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 5 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f45ff2b360bae3f620678bb768c8c0cd8cb4b35f && git checkout f45ff2b360bae3f620678bb768c8c0cd8cb4b35f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 33e4a5609c4d6cc012279f08e24e111c3370d14f 23283d394b2182a846066d4bd6fd1b7aa2d170ac && git checkout -B drift-repro 33e4a5609c4d6cc012279f08e24e111c3370d14f && git merge --no-ff 23283d394b2182a846066d4bd6fd1b7aa2d170ac
node scripts/docs-audit/affected-docs.mjs --json 33e4a5609c4d6cc012279f08e24e111c3370d14f
|
Contract reviewServed-tier: ① Derived judgmentsRead against
② Semver level
③ Boundary flagsThe dev report (
One advisory read, not a flag: the Docs Drift Check comment on the PR lists three pages via Nothing is escalated against this PR. One reading for the seat, not a flag: Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #20596
Clause-②: no
What changed
This is the eleventh stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/plugins/plugin-email/src/**and nothing else. By the seat's census at the claim (5902547086), it is the largest package in the lane that no in-flight work holds. Later stages cover the other packages, so this PR saysPart ofand the card stays open.Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123), by the method of stages 1 to 10 (PR #20609 as
422db788a, PR #20626 asb80ab579d, PR #20634 as4d04b6be3, PR #20658 as9a4b2bb38, PR #20693 as0e9ad74fb, PR #20708 as9b384f63a, PR #20717 ascbaf04c1f, PR #20729 asd2820876f, PR #20737 as4dfff176b, PR #20742 as697845d19). That is 16 sites on 16 lines in 8 files, covering 4 numbers:#13190, a dead number that stands only in test files here, so the census never judged it; it was read on its own (404);Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and says in its own words what was decided: 4 distinct shas. No number in this package has an ADR or ruling record of its own (a grep ofdocs/adr/andscripts/adr-anchors/finds only ADR-0131 naming#11741, as evidence in its D7, not as the record of that decision; nothing else underdocs/names the four), so every anchor is a commit, per ruling C's order. No number was dropped.Only comments changed. Every touched source file keeps its line count (16 lines out, 16 in, over 8 files), so no line citation into these files moves. Every one of the 16 changed lines carried a dead citation; there is no reflow line. No code token moves (see the guard below).
No citation number is added. The added lines carry no tracker number at all. Over the whole diff, added minus removed is negative for the four dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line: the two
PR #8675spellings became that pull request's squash commit.10 dead sites are left on purpose, all of them
describe/ittitles (see the list below).One more file: a
patchchangeset for@objectstack/plugin-email, because the rewritten prose ships (see Changeset below).Census:
plugin-email, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is itsallocated-but-absentfindings underpackages/plugins/plugin-email/. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run.allocated-but-absent97005aed0, run 2026-09-30T02:00:45Z to 02:04:02Z15a7d69a7, run 02:11:19Z to 02:14:30ZThe before count matches the seat's census and A1 (7 sites:
#13189×4,#11741×2,#8675×1). The before run's board moved during the run; its frontier equals the newest number at the run's end, which is A1's criterion (stage 7's precedent). The whole-repo drop is 7, exactly this diff's census sites. Theresolvestally is 33,029 in both runs, andresolves-as-pull-request(1,984) andcross-repo-unjudged(995) did not move either. The after run was taken on15a7d69a7; the head23283d394adds only the changeset. No run was truncated or discarded: both enumerations read 186 pages at the newest frontier.Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported
extractCitations(whole-file and comment-prose projections) andnamesThisRepositoryover every.tsfile underplugin-email/src(50 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported itallocated-but-absent, and alive when that census judged it on this board anywhere (its--listextraction, 37,072 rows) and did not report it. The eleven numbers the census never saw, because they stand only in test files or as the second half of a slash pair here, were read one by one on the issues endpoint:#13190answers 404;#5169,#5286,#10619,#16506,#20374,#5197answer 200 as issues, and#8348,#5191,#5211,#5232as pull requests.97005aed015a7d69a7Its src-comment column equals the census's 7, which is the control on the second instrument. The 323 live citations are the same in both readings, and the drop of 16 citations is exactly the rewritten sites. 11 extracted tokens are not tracker references at all and are not judged: the HTML entity
'(6 sites in the template engine and its tests) and the fixture subjectsInvoice #42toInvoice #45(5 sites). A third, raw reading (every#followed by 2 to 6 digits, whatever surrounds it) finds 371 occurrences and 26 dead before, 355 and 10 after. Beyond the gate's grammar it sees 11 tokens, none dead: the nine second numbers of the#A/#Blines (all live), the excusedPrime Directive #12, and the CSS colour#2563eb.Per-number table
Sites and files count every dead occurrence in scope at the base (comments and strings, tests included).
rewritten / leftcounts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject, andgit blameat the base puts every rewritten line in its anchor commit or in a later commit that descends from it (merge-base --is-ancestorexit 0 for all 16 line and anchor pairs).#1318933fbd3566(PR #13375): the SMTP port guard tests integrality (Number.isInteger), so a fractional port such as587.5is refused at construction, and the generated refusal sentence reads(expected an integer 1-65535), the range still rendered from the constants. Its changeset headline names#13189; its diff writes the integrality docblocks the rewritten lines sit in. New to the sweep#1319056c5b1dbe(PR #13316):smtpOptionsFromMailSettingspasses a present-but-unreadablesmtp_portthrough to the guard instead of omitting it (which had silently fallen back to 587); absent and''still mean "not set", and no second refusal was added. Its changeset headline names#13190; its diff writes the#13190comment block itself. New to the sweep#11741b706af987(PR #11839):SendEmailInput/SendTemplateInputgain an optionalorganizationId, whichplugin-email's writer stamps verbatim ontosys_email.organization_id(pass-through only, no resolution or fabrication), andsendTemplateforwards it as a producer ofsend(). Its message names#11741as the card that commit closed;git blameputs all three rewritten lines in it. Theplugin-authstage's anchor for the same number#8675c9f595083: the squash commit of the pull request that was#8675(its subject ends(#7987) (#8675)):sys_account's OAuth token columns are declaredinternal: true. Its diff records the trap both lines describe: those columns arerequired: false, so inferring "key missing, therefore the strip ran" broke ordinary sign-in (16 red tests), which is why the readback carries theabsenceProvesStripdiscriminator. New to the sweepEvery cited sha matches exactly one commit (
git rev-parse --disambiguate, count 1 for each of the 4), and every one is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 4; control leg: stage 1's landing422db788aexit 0; the history is complete,--is-shallow-repositoryfalse, 15,155 commits). Each of the 4 numbers answers 404 on the issues endpoint, which serves pull requests too. Independently, the package's own shippedCHANGELOG.mdpairsb706af9,33fbd35and56c5b1dwith the same three decisions.Wordings to check
transports/smtp-port-contract.ts:87(a section heading),:134andtransports/smtp.ts:68.SendEmailInputwithorganizationIdsosys_emailcan be stamped at its producers (Decision 2 of #11303) #11741 —」 became 「Commit b706af9 —」 atemail-service.ts:742and:1439; 「A non-numeric stored smtp_port is silently dropped and mail goes out on 587 instead — the setting is ignored without a word #13190 —」 became 「Commit 56c5b1d —」 attransports/smtp.test.ts:221; 「## SmtpTransport accepts a non-integer port its own message promises to reject, then fails at connect time under an internal name #13189 —」 became 「## Commit 33fbd35 —」 attransports/smtp-port-contract.test.ts:34.email-service.test.ts:342, a section rule: 「── WidenSendEmailInputwithorganizationIdsosys_emailcan be stamped at its producers (Decision 2 of #11303) #11741 —」 became 「── Commit b706af9 —」, and its trailing rule was shortened by 10 characters so the line keeps its width exactly.internal-header-readback.ts:37. 「(PR fix(security): sys_account OAuth access/refresh/id tokens stop serializing on the data API (#7987) #8675 hit exactly this onsys_account's optional」 became 「(Commit c9f5950 records exactly this onsys_account's optional」: a commit does not "hit" a trap, it records one, and that commit's own diff is where the 16 red tests are recorded.email-headers-internal.integration.test.ts:251. 「The regression PR fix(security): sys_account OAuth access/refresh/id tokens stop serializing on the data API (#7987) #8675 measured on a sibling card」 became 「The regression commit c9f5950 records from a sibling card」, the same reading.transports/smtp-port-contract.test.ts:228. 「SmtpTransport accepts a non-integer port its own message promises to reject, then fails at connect time under an internal name #13189 is the card that SPENDS that」 became 「Commit 33fbd35 is the change that SPENDS that」, so the noun matches the anchor.transports/smtp.ts:127,transports/smtp.test.ts:272,:276,:281,:283. The number became 「commit SHA」 in place (「until commit 33fbd35:」, 「The bucket commit 56c5b1d never had to name」, 「Commit 33fbd35 made the guard test」, 「Commit 56c5b1d's rule is that」, 「commit 33fbd35 changed which numbers」).The 10 sites left
describe/ittitles, left as stages 1 to 10 left theirs:email-service.test.ts:349andsend-template.test.ts:63,:88(#11741);transports/smtp-port-contract.test.ts:225,:309,:340(#13189);transports/smtp.test.ts:230(#13190),:271(#13189),:293(#13190and#13189).src, the package'sCHANGELOG.mdnames three of these numbers on 5 lines. It is release-owned and deliberately not edited here (see Acceptance notes).Mechanical guard: no code token moves
The guard compares the TypeScript parser's leaf nodes (a
forEachChildwalk, so comments are trivia and JSDoc nodes are never visited), base97005aed0against head. String and template literals are therefore read in full. It ran over all 8 touched.tsfiles.email-service.ts(「no resolution, no default, no fabrication」 to 「… no default and no fabrication」): 0 files changed, as expected (exit 0).transports/smtp.ts(isValidSmtpPort(port)givenas number): DIFFER, 587 to 588 leaf tokens (exit 1).transports/smtp.test.ts:293,#13189to#13188): DIFFER (exit 1).Every mutation went through
scripts/ablation-replace.mjs(wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (1e99bd5e2bcb,46c13267611b,da5314910bc4), withgit diff HEADempty and a clean tree afterwards.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/plugin-email(.changeset/20596-plugin-email-provenance-anchors.md) is included. Its body is stage 10's, word for word, with the package name changed.Measured on the built package (A3):
files[]isdist,README.mdandCHANGELOG.md, and the package is not private. After the build,b706af987appears twice in each ofdist/index.jsanddist/index.mjs(the two inline comments inemail-service.ts, which the bundle keeps).c9f595083appears once in each ofdist/index.d.tsanddist/index.d.mts(theinternal-header-readback.tsdocblock), and so does33fbd3566(the docblock onSmtpTransportOptions.port).56c5b1dbereaches nothing (test files only). Positive controls, one unchanged line beside each shipped rewrite, land exactly where their neighbours do: 「context, so the input's organization is the one fact it may stamp:」 and 「caller's organization so the sys_email row it persists is stamped.」 once in each JS file; 「token columns: inheriting」 and the unchanged line just above the rewritten one in theportdocblock once in each declaration file. A never-written negative phrase appears nowhere indist. None of the 4 dead numbers is left indist.Gates (head
23283d394)pnpm check:issue-citationsexits 0.node scripts/check-issue-citations.mjsexits 0: the diff-scoped run found no citation added against97005aed0(4 files read; test files are a deferred surface).pnpm check:doc-authoringexits 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat23283d394derived 61 commands: all 55 derived at dispatch, pluscheck:engine-double-contract,check:objectql-double-limit,check:query-options-erasure,check:type-check-coverage,check:type-check-debtandcheck:where-matcher. Each ran with its exit code captured before any pipe, and all 61 exit 0.--ran, fed each command with its exit code, reports 61 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A fullturbo run buildof./packages/*and./packages/*/*ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity, each exit 0.pnpm --filter @objectstack/plugin-email test: 31 files pass and 510 tests pass.vitest list --filesOnlynames 31 files, all the tracked test files, the 4 touched ones included.pnpm --filter @objectstack/plugin-email typecheckexits 0 (tscontsconfig.json, thencheck:test-typecheckontsconfig.test.json: 0 files and 0 errors in its debt ledger).tsc --listFilesholds all 8 touched files in both programs, and the test program holds all 50 files undersrc/..tsfiles, gives 8 files, 0 errors and 0 warnings. All 8 are in eslint's own population (isPathIgnoredis false for each; adistfile, as the control, is ignored).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.pnpm check:nul-bytesexits 0, and a raw scan of the 9 changed files for control bytes finds none.Acceptance notes
CITATION_RErefuses a hyphen after the digits and a/before the#(check-issue-citations closeout (extractor spellings):CITATION_RErefuses a hyphen after the digits, so a dead#N-wordcitation (#13398-class) is invisible to the diff gate and to the census #20636), andNON_CITATION_HEADSexcuses a number after the word 「option」. In this package:#N-wordnone,#A/#B9 lines,option #Nnone, at the base and at the head, which is the claim's 0 / 9 / 0. Every second number on the 9 slash lines answers 200 (#5197×2,#5191,#5211,#5232×2,#5177,#4251,#5094), so nothing there needed rewriting.#11741. Its D7 cites#11741as the writer fact that keepssys_emailtenant data. That is evidence inside a later record, not the record of what#11741decided, so it is not this stage's anchor, anddocs/adr/**is a governed Tier H surface outside this card's stages. It joins the ADR-tree residue the seat already carries (ADR-0131's#14484, stage 2).CHANGELOG.mdis left.packages/plugins/plugin-email/CHANGELOG.mdnames#11741,#13189,#13190and#8675on 5 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage.#13189test block, they still have the kept(#13189)title as their referent; the one rewritten line that said 「the card」 now says 「the change」 (above). The rest are unchanged, as in stages 8 to 10.#13189→33fbd3566;#13190→56c5b1dbe;#8675→c9f595083.#11741→b706af987reuses theplugin-authstage's anchor.mainat97005aed0.mainhas since moved two commits (9c8f113c6,a6866da0c). Their 14 files touch nothing underplugin-email, norscripts/check-issue-citations.mjs,.changeset/config.jsonor thedoc-authoring-prose-idbaseline, and the three console-injection scripts they change are not among this diff's 61 derived families. So no merge was taken; the merge queue rebuilds on the merged generation.Generated by Claude Code