Skip to content

feat(spec): retire SendTemplateInput.org — the declared org-overlay tenant id nothing ever read (ADR-0049 enforce-or-remove) - #12114

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-11832-retire-sendtemplate-org
Aug 25, 2026
Merged

os-litant merged 2 commits into
mainfrom
claude/issue-11832-retire-sendtemplate-org

Conversation

@os-litant

Copy link
Copy Markdown
Contributor

Fixes #11832

What

Removes SendTemplateInput.org from packages/spec/src/contracts/email-service.ts under ADR-0049 enforce-or-remove. The member was declared as "Tenant id for org-overlay resolution (when supported)" and had zero readers: @objectstack/plugin-email — the only IEmailService implementation — resolves templates on (name, locale) only, so a caller passing org got no org-overlay resolution and no error. The "(when supported)" hedge was the declaration admitting the gap. After #11741 landed organizationId beside it, the input carried two org-shaped keys of which one did nothing — exactly the shape that invites an AI author to pick the wrong one.

org is NOT replaced by organizationId: that member is the delivery row's tenant stamp (sys_email.organization_id pass-through, #11741 / Decision 2 of #11303) and opts into no overlay resolution. #11741's members remain open and untouched semantically; #11741 is not affected by this PR.

Premise re-verification (at claim, base 8d60dcd)

  • packages/spec/src/contracts/email-service.ts:185 still declared the member (PM-checked at 0b04839; re-verified).
  • Zero .org reads in packages/plugins/plugin-email/src (the only prose mention is a comment at template-loader.ts:69).
  • Zero org: writers at any sendTemplate() call site: service-messaging email channel, qa/dogfood, plugin-auth, examples/app-showcase, docs (content/docs/kernel/runtime-services/email-service.mdx has no bare org).
  • RenderTemplateInput (the render-only subset) never carried org.
  • Sibling objectui: zero SendTemplateInput references.
  • The member shipped in published @objectstack/spec@17.2.0 (dist/contracts/index.d.ts:6318 in the npm tarball), so this is a published-contract-member removal, not an unpublished trim.

Route

Contracts interface member (programmatic contract; nothing parses it at runtime) — so the retirement kit is the lighter half of the spec-property-retirement playbook:

  • Tombstone prose in the interface where the member sat (why removed, date, the organizationId semantic boundary). No retiredKey() tombstone and no guidance-map entry: there is no Zod surface to carry a parse-time prescription; the enforcement channel is the compiler.
  • Compile-time pin in email-service.test.ts: authoring org is an excess-property error via @ts-expect-error, bidirectional under check:test-typecheck (re-adding the member turns the directive into TS2578).
  • D3 SemanticMigration send-template-input-org-retired (major 18) as a one-file shard entries/semantic/18.send-template-input-org-retired.ts + gen:migration-registry — conflict-free by construction with the parallel registry cards this batch (ActionSchema documents "newTabUrl is only valid together with opensInNewTab" but no refine enforces the pair (observation) #11842 / spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039); semantic shards carry no sequence number to claim. No D2 conversion: the key only ever appeared in a call-time input bag — no metadata seam ever runs on it (the data.engine.update options.upsert precedent). spec-changes.json / docs/protocol-upgrade-guide.md are byte-unchanged by construction (projections stop at PROTOCOL_MAJOR 17; verified by running both generators — zero diff), matching every other staged major-18 entry.
  • Changeset @objectstack/spec: minor with BREAKING prose per the lockstep launch-window convention (the PR feat(spec): retire the nine dead contributes members and kinds[].globs (family: #10724 + #11169) #12001 house style), FROM → TO, and adr-0087: registered send-template-input-org-retired. runtime-interface-only is structurally refused for symbols under packages/spec/src/contracts/ and the body carries a prescription, so registered is the only honest disposition — gate verdict: "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition … registered send-template-input-org-retired (new here)".
  • No consumer cleanup needed: zero writers measured (above), so no call site changes anywhere.
  • Liveness ledger untouched: the ledger walks authorable metadata schema shapes; a contracts interface member has no ledger surface (check:liveness green confirms).

Declared deviation from the claim's file surface

The claim said organizationId untouched. SendEmailInput.organizationId is untouched byte-for-byte. SendTemplateInput.organizationId's TSDoc, however, ended with "Distinct from {@link SendTemplateInput.org} …" — a link left dangling by this removal that would keep describing the retired member as existing. That one clause is rewritten to reference the retired member in past tense; the member's semantics and every other TSDoc sentence are unchanged. Flagged here rather than left as a dangling reference.

Verification — union re-run at final head 6a8a947 (post origin/main merge; delta vs main is exactly the 5 intended files, +156/−5)

Tests (all via the shared verify lock):

  • @objectstack/spec vitest src/contracts/email-service.test.ts src/migrations/migrations.test.ts: 2 files / 109 tests passed; full pnpm --filter @objectstack/spec typecheck green incl. check:test-typecheck: OK (test layer compiles; 55/263 ledger-held, unchanged).
  • @objectstack/plugin-email (downstream consumer of the rebuilt narrowed dist): vitest 27 files / 429 tests passed; tsc --noEmit clean.
  • @objectstack/example-showcase test/email-template-locale.test.ts: 1 file / 6 tests passed.

Reverse verification (committed-state, mutation proven on disk by anchored grep both legs; tsc reads src via tsconfig.test.json, no dist leg involved): re-adding org?: string moves the test program from 263 ledger-held diagnostics to 264, the +1 being exactly src/contracts/email-service.test.ts(71,7): error TS2578: Unused '@ts-expect-error' directive; restore leg back to check:test-typecheck: OK at 263.

Gates at 6a8a947, each read from its own verdict line (exit captured before any pipe): check:generated "All 14 generated artifacts are up to date" (api-surface / export-origins / authorable-surface / docs unchanged — an interface-member removal is invisible to the existence-level snapshots, the expected reading for this route) · adr-0087 registration (verdict quoted above) · check:nul-bytes · check:merge-driver · check:published-files · check:slot-lookup · check:spec-parsed-alias · check:cross-package-test-inputs · check:changeset-gate-self-tests · check:objectui-changeset · check:query-options-erasure · check:test-source-alias · check:type-source-resolution · check:engine-double-contract · check:where-matcher · check:type-check-coverage · check-changeset-no-major ("no major bump") · check-ci-filter-parity · check-empty-changeset · check-plugin-teardown-shape · spec check:liveness / check:empty-state / check:variant-docs / check:strictness-ledger · lint check:doc-formula-expressions ("9 @example(s) judged clean across 1017 files") — all green.

Declared narrowings (accepted deviations; CI runs the farm):

  • check-dev-prereqs reds locally with "The workspace is not built — 1 unmet precondition, not a list of problems" (fresh targeted worktree; its own text says it is not a finding). CI builds first.
  • check:type-check-debt --re-measure not run repo-wide: its population is the DEBT/TEST_DEBT ledgers in scripts/check-type-check-coverage.mjs — @objectstack/spec graduated (@ts-expect-error 退役 pin 在 packages/spec 里是幽灵检查:tsconfig 把 **/*.test.ts 排除出唯一的 tsc --noEmit #5286) and the @objectstack/spec-monorepo root program excludes packages/, so none of this PR's 5 paths is an input to any ledgered tsc program; the only programs reading them are spec's own three, run in full above.
  • Repo-wide pnpm lint / remaining farm: CI-owned.

Generated by Claude Code

claude added 2 commits August 25, 2026 08:16
…t id nothing ever read (#11832, ADR-0049 enforce-or-remove)

The member declared 'Tenant id for org-overlay resolution (when supported)'
and no implementation ever read it: plugin-email resolves templates on
(name, locale) only. Removal is a compile-time accept-set narrowing; runtime
behaviour is unchanged. organizationId (#11741) is untouched and is NOT a
replacement (delivery stamp, not overlay resolution).

- contracts/email-service.ts: member removed; tombstone prose in place;
  stale cross-reference in SendTemplateInput.organizationId TSDoc updated
- email-service.test.ts: bidirectional compile pin (@ts-expect-error)
- D3 semantic entry send-template-input-org-retired (major 18, one-file
  shard + gen:migration-registry; no D2 — call-time input bag, the
  options.upsert precedent); spec-changes.json / upgrade guide unchanged
  by construction (projections stop at PROTOCOL_MAJOR 17)
- changeset: @objectstack/spec minor (lockstep launch-window convention),
  adr-0087: registered

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/kernel/runtime-services/email-service.mdx (via SendTemplateInput (symbol))
What this run could not see
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 2ba4329e441a5ea66656efb41c1782de9380f0e3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d11173b35468c9a1efb279fb87dbcb2b587ab4ae — the merge of head 6a8a947b6727eadacc5000a9306518cb7a49e4f5 into base 2ba4329e441a5ea66656efb41c1782de9380f0e3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin d11173b35468c9a1efb279fb87dbcb2b587ab4ae && git checkout d11173b35468c9a1efb279fb87dbcb2b587ab4ae
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2ba4329e441a5ea66656efb41c1782de9380f0e3 6a8a947b6727eadacc5000a9306518cb7a49e4f5 && git checkout -B drift-repro 2ba4329e441a5ea66656efb41c1782de9380f0e3 && git merge --no-ff 6a8a947b6727eadacc5000a9306518cb7a49e4f5

node scripts/docs-audit/affected-docs.mjs --json 2ba4329e441a5ea66656efb41c1782de9380f0e3

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 2ba4329e441a5ea66656efb41c1782de9380f0e3 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-litant
os-litant marked this pull request as ready for review August 25, 2026 09:57
@os-litant
os-litant added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit 8619f95 Aug 25, 2026
39 checks passed
@os-litant
os-litant deleted the claude/issue-11832-retire-sendtemplate-org branch August 25, 2026 10:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] SendTemplateInput.org is declared ("Tenant id for org-overlay resolution (when supported)") but no implementation reads it

2 participants