Skip to content

[finding] TemplateLoader 的 docblock 开头说「best-matching row」,而 spec 契约逐字写着「There is no "best match"」—— 同一段注释两行后又说 EXACT match #19507

Closed

Description

@huangyiirene

Path: none | 契约文本与实现注释不一致(plugin-email) | 北极星「优先级」4

由 domain:services 席(session_01AhQASwqJr2Z7XfGWUdvnbF)在 #18499 的交付中作顺路发现立卡。证据为本席独立现验,⛔ 非转述。⛔ 未分诊、⛔ 无 domain:*、⛔ 无定级。

class: b(违背已声明契约)

缺陷

packages/plugins/plugin-email/src/email-service.ts 的 TemplateLoader 接口 docblock 开头第一句:

 * Loader for sys_email_template rows. Injected by EmailServicePlugin
 * on `kernel:ready`. Returns the best-matching row for `(name, locale)`
 * or `null` when none exists / inactive.
 *
 * `locale` set → an EXACT match for that locale, or `null`; the en-US fallback

⇒ 同一段注释里自相矛盾:开头说「best-matching row」,两行后又正确地说「an EXACT match」。

它违背的已声明契约(逐字)

packages/spec/src/contracts/email-service.ts,SendTemplateInput.template 的 describe:

Template identifier (matches sys_email_template.name) … There is no "best match" and no language-subtag folding: the locale row is resolved by the exact ladder documented on locale below.

为什么它不在 #18499 里一并修

#18499 的载体集是用探针 best-matching locale|picks the best 枚举出来的,而这句写的是「best-matching row」—— 不在那个探针的命中集内。⇒ 它是同类的第四处,但不是那张卡点名的载体,顺手改会把那张卡的范围撑开。

⭐ 这也是一条关于探针的读数:一个按字面串枚举载体的集合,对同义改写是盲的。修本卡时值得连带问一句:还有没有第五种拼法。

为什么它咬人

该 docblock 随 dist 出货(plugin-email/dist/index.d.ts),⇒ 集成方在 IDE 里读到的就是它。一个在 IDE 里说「best match」、在契约里说「no best match」的接口,会让调用方按不存在的回退行为写代码。

Seam: spec:SendTemplateInput.template → runtime:EmailService.resolveAndRenderTemplate | renderer: none

查重词

TemplateLoader best-matching row · email-service.ts loader docblock best match · plugin-email interface docblock locale · best-matching row (name, locale) · email loader docblock contradicts contract

出处:#18499 / PR #19504 的交付。


Generated by Claude Code

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 21, 2026
  2. self-assigned this
    on Sep 21, 2026
  3. huangyiirene commented on Sep 21, 2026

    @huangyiirene
    CollaboratorAuthor

    Claim: session_01AhQASwqJr2Z7XfGWUdvnbF
    Branch: claude/issue-19507-template-loader-docblock-best-match
    Session: session_01AhQASwqJr2Z7XfGWUdvnbF
    Clause-②: no

    认领 —— domain:services 席

    写于 2026-09-21T11:19Z。⛔ 与卡面不一致处以本评论为现值。

    取卡依据:在飞 0;队列 5 张,本卡是 p3 且带 bug ⇒ 同级先 Bug,全序第一(次位 #18923 为 p3 无 bug)。p0 实读 4 —— 维护者裁决「没有卡的时候可以处理 p3」,⇒ 队列无更高级别可派时解禁。队列读数与本认领同一次调用取得。

    前提过时检查(跑了):email-service.ts:467 今日 main 仍为 Returns the best-matching row for (name, locale);packages/spec/src/contracts/email-service.ts:153 仍为 There is no "best match" and no language-subtag folding ⇒ 缺陷活着。

    条款②逐肢:接受集肢 —— 交付物是注释散文,⛔ 不碰解析/准入;公开面肢 —— ⛔ 无新导出符号、⛔ 无载荷新键、⛔ 零 packages/spec。⇒ no。

    ⚠️ 本卡由本席所立。立卡与派卡是两件事,定级与路由是分诊的产出(已给 p3·bug·domain:services),本席只是按全序取它。


    Generated by Claude Code

  4. huangyiirene commented on Sep 21, 2026

    @huangyiirene
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 19507,
      "status": "done",
      "branch": "claude/issue-19507-template-loader-docblock-best-match",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19591",
      "session": "session_01AhQASwqJr2Z7XfGWUdvnbF",
      "premise_still_valid": true,
      "summary": "Verified all three readings on today's head (base 31184e5daf) before editing: email-service.ts:467 still opened 'Returns the best-matching row for (name, locale)', :470 of the same paragraph still said 'locale set -> an EXACT match for that locale, or null', and packages/spec/src/contracts/email-service.ts:153 still declared 'There is no \"best match\" and no language-subtag folding'. Read template-loader.ts before touching the prose: load() at :120 puts the named locale into the where clause as a literal (no folding), :121 pins DEFAULT_TEMPLATE_LOCALE for a call naming none, and :124 falls to BY_LOCALE, the bundle's lowest locale tag ordered — a deterministic tie-break, not a match quality. So the prose was wrong and the behaviour was right. Rewrote only the opening sentence to 'Returns the row (name, locale) resolves to under the exact rule below — there is no \"best match\" and no language-subtag folding — or null when none exists / inactive', matching the spelling sys_email_template's object doc already carries. Zero behaviour change, zero packages/spec, zero content/docs/releases. Patch changeset for @objectstack/plugin-email because the docblock provably ships in dist/index.d.ts.",
      "tests": "pnpm --filter '@objectstack/plugin-email^...' build :: exit 0 (under scripts/pm/os-verify-lock.sh, VERDICT command-exit 0). pnpm --filter @objectstack/plugin-email build && typecheck && test :: exit 0 (VERDICT command-exit 0) — 'Test Files 30 passed (30) / Tests 468 passed (468)'. Publish-surface measurement (the changeset's basis): grep -c 'there is no \"best match\"' packages/plugins/plugin-email/dist/index.d.ts = 1, positive control grep -c 'an EXACT match for that locale' = 1, grep -c 'best-matching row' = 0. Gate families derived mechanically, not hand-listed: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 54 families; all 54 run with exit codes captured before any pipe, then reconciled with --ran: '54 derived families accounted for — 53 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (wants a whole-repo pnpm build); declared to CI, read as neither green nor red. pnpm lint (eslint . --no-inline-config) run WHOLE, not narrowed :: exit 0 at final commit 2ebc3f9ef2. The eight changeset-reading gates plus check:nul-bytes and check:doc-authoring were re-run at 2ebc3f9ef2 after the changeset moved from untracked to committed — all exit 0. Control leg 2 of the dispatch (does anything pin this text?): NO. After the edit git grep -nI 'Returns the best-matching row|best-matching row for' over the tree excluding dist/ returns zero hits; no script under scripts/** and no *.test.ts asserts on this docblock's text — the six files naming TemplateLoader use it as a TYPE. No verification surface was created for a prose fix. No ablation/reverse-verification run and none owed: the diff adds no guard and changes no behaviour, so there is nothing that could be made to fail.",
      "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl.",
      "api_writes": "2 REST writes — POST /repos/objectstack-ai/objectstack/pulls (draft PR 19591), POST /repos/objectstack-ai/objectstack/issues/19507/comments (this report). Zero label writes: the dispatch named no labels and the diff carries a changeset, so the permitted set is empty. Separately, 3 git pushes on claude/issue-19507-template-loader-docblock-best-match (empty-branch route probe, the edit, the changeset) — git, not REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: b · packages/metadata-core/src/item-key-discriminators.ts:7-10 quotes EmailTemplateDefinitionSchema as saying 'the service picks the best match for the recipient's locale, falling back to en-US'. Two defects in one: (i) the claim is what packages/spec/src/contracts/email-service.ts:153 denies in as many words, and template-loader.ts:24-30 already enumerates that exact sentence as a FALSE declaration; (ii) it is a STALE quotation — that sentence has zero occurrences anywhere in packages/spec today, because #18499 corrected the schema it cites but never reached this package. This is the fifth carrier and the direct payoff of the card's wider-probe instruction: #18499's literal probe was 'best-matching locale|picks the best', and this spells it 'picks the best match for the recipient's locale'. Seam: spec:SendTemplateInput.template -> runtime:EmailService.resolveAndRenderTemplate | renderer: none. Dedupe words: item-key-discriminators best match recipient locale · metadata-core stale EmailTemplateDefinitionSchema quotation · picks the best match for the recipient's locale · i18n bundle discriminator docblock best match · fifth carrier email locale best-matching",
        "carrier: this PR's own file, packages/plugins/plugin-email/src/email-service.ts:474 — noted, not filed. Same docblock, the 'locale undefined' branch, reads 'the loader's own deterministic best answer for the name'. Deliberately NOT changed: widening the card to it was the one thing the dispatch forbade, and it is the weakest member of the family — it defers to createSysEmailTemplateLoader and qualifies itself as deterministic, so unlike the other four it asserts no match quality and contradicts no declared contract. It reads as polish rather than a contract violation, which is why it goes to Acceptance notes rather than a card; a seat verdict on whether 'best answer' is in the same family as 'best match' would settle it either way.",
        "reading, not a finding: packages/apps/setup|studio/CHANGELOG.md and packages/cli/CHANGELOG.md carry 'collapses a doc to the best-matching locale (exact -> primary subtag ...)'. That is the DOCS i18n resolver, which really does fold a subtag, and CHANGELOG.md is release-owned. Correct as written; recorded so the next probe does not re-open it."
      ]
    }

    Generated by Claude Code

  5. added a commit that references this issue on Sep 28, 2026
    8dba7aa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions