Skip to content

auth 邮件(验证/重置)不按用户语言选模板:中文界面注册收到英文主题与正文 #14319

Description

@hotlong

现象

中文界面注册(浏览器 Accept-Language: zh-CN,UI 全中文),收到的验证邮件主题是英文 "Verify your ObjectOS Cloud email address",正文同样英文。EmailServicePlugin 启动时 seeded 24 template row(s),但 sendVerificationEmail 没有按用户语言选模板。

修复要求

验证/重置密码等 auth 邮件按用户语言(注册请求的 locale 或 sys_user 的 language)选择模板,缺省回落英文;补中文模板并加一条测试。

Activity

  1. self-assigned this
    on Sep 2, 2026
  2. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: PM loop round 3 (epic objectstack-ai/cloud#1841)
    Session: session_c5c0ce54-bb9c-478c-9e5b-cf44b80d4569
    Branch: claude/issue-14319-auth-email-locale
    Worktree: objectstack-issue-14319
    Domain: domain:services
    File surface: packages/plugins/plugin-auth/** (verification/reset email template selection), packages/plugins/plugin-email/** templates only if the locale-keyed template lookup lives there (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; PM judgement)
    Clause-②: no
    Serial constraints cleared: #14318 (PR #14371) MERGED — base on current main; no other open PR touches these packages beyond dependabot

  3. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: dev
    Session: session_c5c0ce54-bb9c-478c-9e5b-cf44b80d4569
    Branch: claude/issue-14319-auth-email-locale
    Worktree: objectstack-issue-14319
    Base: 5563bfb32 (origin/main at claim time)
    File surface: packages/plugins/plugin-auth/src/auth-plugin.ts, packages/plugins/plugin-auth/src/auth-manager.ts, packages/plugins/plugin-auth/src/auth-plugin.test.ts, .changeset/, plus content/docs/permissions/system-context.mdx (one census anchor re-pointed by the gate's own --fix after this PR moved a line). No plugin-email file is touched: the locale-keyed template lookup is already correct there.
    Verification lock: UNLOCKED (declared) — no usable flock on this macOS host; os-verify-lock.sh ran every heavy command in its declared unlocked mode. Concurrency held at 2.

    Generated by Claude Code

  4. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 14319,
      "status": "done",
      "branch": "claude/issue-14319-auth-email-locale",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14591",
      "premise_still_valid": false,
      "summary": "Two of the card's three claims are falsified on current main, and the third is barred by a standing ruling. (1) The zh-CN templates already ship: auth-templates.ts carries 6 auth templates x 4 locales = 24 rows, which is exactly the 'seeded 24 template row(s)' the card quotes as evidence they are MISSING. (2) sendVerificationEmail has selected by locale since #8195; all five auth sends spread a locale and auth-email-locale.test.ts pins it. (3) The requested key is barred: the 2026-08-13 maintainer ruling quoted in auth-manager.ts makes the locale the deployment default, REJECTS Accept-Language, and defers a per-user column; verified, not assumed, that sys_user.object.ts has no locale and no language field. What IS broken, and what this PR fixes: 'the deployment default' has two producers and auth email read the weaker one. II18nService.getDefaultLocale() is the app artifact's BUILD-TIME i18n.defaultLocale ('en' if undeclared); localization.locale (ADR-0053, Setup Localization, tenant-scoped) is the workspace's RUNTIME language, and is already the authority for auth SMS (#2815) and audit activity summaries (framework#3039). Its four options are exactly AUTH_EMAIL_TEMPLATE_LOCALES. So a workspace that declared Chinese got Chinese OTP texts and English verification mail, from one plugin, on one kernel:ready pass, a dozen lines apart. AuthPlugin now prefers localization.locale when the operator explicitly set it (ResolvedSettingValue.source is not 'default' -- the same precedence the sibling branding.workspace_name binding already uses, so a manifest default does not demote a deployment that declared i18n.defaultLocale), keeps the build-time default underneath, names nothing when neither speaks (EmailService's documented en-US fallback), and live-rebinds on settings changes. The ruling is untouched. PR says 'Part of', NOT a closing keyword: the half the card actually asks for is in the maintainer's decision box and the card must stay open for it. PM mechanism question answered: template rows carry a locale COLUMN and selection is per-locale ROWS (sys_email_template keyed by name plus locale; createSysEmailTemplateLoader pins en-US in the WHERE clause, #7731), not multi-language blocks -- so the fix is entirely in plugin-auth and no plugin-email file is touched.",
      "tests": "All at HEAD 40dc0aff1, base 5563bfb32. pnpm --filter '@objectstack/plugin-auth^...' build -- VERDICT command-exit 0. pnpm --filter @objectstack/plugin-auth test -- 'Test Files 90 passed (90) / Tests 1838 passed (1838)'. pnpm --filter @objectstack/plugin-auth typecheck -- exit 0, 'check:test-typecheck: OK ... 10 file(s) / 94 error(s) / 23 pinned signature(s)'; the ledger held after one repair (my new getService double inferred a union return and made it 12 vs a recorded 11 TS2322, so the double states ': any' on its return rather than ratcheting the ledger up). Confirmed the test layer IS measured: it compiles under tsconfig.test.json, which is how that error was seen at all -- the package's main tsconfig reaches 0 test files (tsc --listFiles). node scripts/check-nul-bytes.mjs -- 'OK (scanned 7963 text file(s) ... no raw ASCII control bytes)'. dispatch-gates --repo objectstack-ai/objectstack, no paths passed: first derivation 38 families, all run; ONE real red -- check-system-context-census, pure line rot (this PR moved an elevation read in auth-plugin.ts from 1301 to 1345), repaired by the script's own --fix which rewrote exactly one anchor, then 'OK -- 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read'. That repair added content/docs to the diff, so the family was RE-DERIVED: 62 families, and the 24 new doc families were run too -- all green. Three families refuse on an unbuilt tree rather than answering; turbo run build --filter='./packages/*' --filter='./packages/*/*' ran 70/70 successful and all three then measured green: check-type-check-coverage --re-measure 'OK -- 22 ledger entr(ies) re-measured in 53.8s, 755 raw tsc error(s) total, none above its recorded number'; check:skill-examples '262 prose examples type-check across 3 surface(s)'; check:dual-build-cjs-loads exit 0, floors held. TWO families NOT MEASURED by their own verdict text, neither a red: check-test-completeness exits 3 asking for a saved turbo test log and instructs a local family run to record NOT MEASURED, and check-half-states exits 3 as an unread instrument on this host. REVERSE VERIFICATION, direction RED as predicted: fix committed at fceba76c0, then auth-plugin.ts restored to merge base 5563bfb32 and the suite re-run -- 5 of the 7 new cases failed with the exact expected shape, 'expected last setDefaultEmailLocale call to have been called with [ zh-CN ]' vs 'Received [ en ]', while the two not depending on the settings read stayed green. Mutation confirmed ON DISK before the run by blob hash AND anchor count, never by an editor exit code: HEAD blob 7826ea6f, mutated blob 8d8137058, applySmsLocale count 3 in the mutated file and 0 in the fixed one. Restore confirmed after: worktree blob back to 7826ea6f, git diff HEAD empty, git status --porcelain empty; the restore leg ran from a trap on EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. NO REBUILD was needed for either leg and none is claimed: both suites import ./auth-plugin as source, so no dist sits between the mutation and the assertion. DECLARED NARROWING: os-verify-lock.sh found no usable flock on this macOS host and ran every heavy command in its own declared UNLOCKED mode -- nothing was serialized against sibling agents. Repo-wide pnpm lint was NOT run and no narrowing of it is claimed; it is CI's.",
      "mcp_calls": "0 -- every GitHub read and write went through the gh CLI; no MCP GitHub call was made. The PM's dedup reading was taken as given and not re-run.",
      "open_questions": [
        {
          "question": "This PR implements the 2026-08-13 ruling's INTENT (the deployment default) from localization.locale, but the ruling as recorded in auth-manager.ts names II18nService.getDefaultLocale() as the source by name. Is promoting the explicit workspace setting above that named source within the ruling, or does it need re-adjudication?",
          "options": [
            "A. Accept as shipped -- an explicit localization.locale wins, the named build-time source stands underneath it. Both are 'the deployment default'; the settings key is the one an operator can actually reach, and it is already the authority for auth SMS in the same hook and for audit summaries.",
            "B. Revert to getDefaultLocale() alone and file the divergence as its own card for the maintainer.",
            "C. Re-adjudicate the whole question, including whether signup-time browser language should select auth mail after all."
          ],
          "recommendation": "A. Long-term soundness (weighted highest): one fact -- what language does this deployment speak -- must have ONE authority, and the platform already elected localization.locale in two other consumers, so leaving email on a second key IS the contract-first defect rather than the fix for it. Real business need is measured, not speculative: a workspace CAN set Chinese today and does get Chinese SMS, so the English mail is a live, reachable inconsistency. AI-authoring safety: no new key, no new spelling, no tolerant consumer-side fallback -- the precedence is the one already written a few lines above for branding.workspace_name, so the next author reads one idiom instead of two. Startup scope: zero new surface, no schema change, no new setting. Option C is the maintainer's to call and is exactly why the PR says 'Part of'."
        },
        {
          "question": "The reported repro is ObjectOS Cloud signup, where the UI language comes from the browser and the workspace setting is still at its manifest default -- so this fix does NOT turn that specific repro Chinese, and no fix in this repo can while Accept-Language stays rejected. Cloud's per-environment AuthProxyPlugin lives in the cloud repo and was not inspected here. How should the remaining half be routed?",
          "options": [
            "A. PM files a cloud-repo card to check whether AuthProxyPlugin performs the same localization.locale binding, and a separate objectstack card carrying option C above to the maintainer.",
            "B. Leave #14319 open as the single carrier for both and let the maintainer decide before any further work.",
            "C. Re-open the Accept-Language question directly in this PR."
          ],
          "recommendation": "A. It is the only option that keeps the two halves separable: the cloud-side binding is a mechanical parity question answerable without a ruling, while signup-time language selection is a ruling and nothing else. Bundling them (B) blocks the cheap half on the expensive one; C would have me re-adjudicate a standing ruling from inside a PR. I did NOT file either card myself -- both are cross-repo and PM owns epic cloud#1841, so routing them is PM's call, not a finding I should place blind."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    ACCEPT (partial — PR says Part of; card stays OPEN for the maintainer's half) — reviewer of record: PM (epic objectstack-ai/cloud#1841).

    • PR fix(plugin-auth): bind the auth email locale to the workspace language, not the build-time default #14591: draft → main, Part of #14319, plugin-auth only (no plugin-email file) + the census anchor repair + changeset. premise_still_valid: false on two of three claims, with evidence — a good run: zh-CN auth templates already ship (6 × 4 locales = the 24 seeded rows the card misread as missing) and sendVerificationEmail has selected by locale since Auth emails are always en-US: no send names a locale and sys_user has no locale column, so localized template rows can never be selected #8195.
    • What shipped: auth email's "deployment default" now prefers the operator-set localization.locale (ADR-0053, the same authority auth SMS and audit summaries already use) over the build-time i18n.defaultLocale, with the build-time default underneath and live rebinding — one fact, one authority. Ruling on open question 1 (PM discretion): A, accept as shipped — it implements the 2026-08-13 ruling's intent (the deployment default) and does not touch its rejection of Accept-Language.
    • Evidence: plugin-auth 90 files / 1838 tests; typecheck with the test-typecheck ledger held; check-nul-bytes OK; 62 gate families derived (one census anchor line-rot repaired with the script's own --fix), all green or NOT MEASURED by their own text; reverse verification 5/7 red as predicted with on-disk proof and byte-identical restore.
    • Routing of the remaining half (open question 2): A. (i) cloud card to check the per-environment AuthProxyPlugin binds localization.locale the same way — filed by PM below; (ii) whether SIGNUP-time language (browser or an explicit picker) should select auth mail is a maintainer decision — being put to the maintainer in-session; auth 邮件(验证/重置)不按用户语言选模板:中文界面注册收到英文主题与正文 #14319 stays open as its carrier.
    • CI in progress at review time; flipping ready, enqueuing when every check is green.

    Generated by Claude Code

  6. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    裁决(维护者 2026-09-02 晚,在场裁定)— 接受浏览器 Accept-Language 选 auth 邮件语言

    • 场景:cloud 自助注册时还没有工作区,没有任何「部署默认语言」能代表这个用户;中文用户收到英文验证邮件。
    • 裁决:注册 / 登录 / 重置密码等由请求触发的 auth 邮件,语言优先取请求的 Accept-Language(命中 AUTH_EMAIL_TEMPLATE_LOCALES 才生效),其次才是 PR fix(plugin-auth): bind the auth email locale to the workspace language, not the build-time default #14591 确立的部署默认(localization.locale → i18n.defaultLocale)。这翻转了 2026-08-13「拒绝 Accept-Language」的裁决:把 auth-manager.ts 里记录的旧裁决改写为本裁决(保留日期与原话,注明被 2026-09-02 裁决取代),⛔ 不留两套说法。
    • 用户级语言列(per-user language)仍暂缓,不在本卡范围。
    • 执行:原认领 dev 续做为第二个 PR(Fixes #14319);cloud 侧对账见 objectstack-ai/cloud#1857。

    Generated by Claude Code

  7. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 14319,
      "status": "done",
      "branch": "claude/issue-14319-accept-language",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14600",
      "premise_still_valid": true,
      "summary": "Second PR for the card, on the maintainer's 2026-09-02 ruling. Premise verified before writing anything: the ruling was read from the card itself (hotlong, 2026-09-02T13:56:36Z), not taken on the dispatch's word, and it does flip 2026-08-13 explicitly. Auth email locale is now a two-rung ladder resolved at send time in AuthManager: the caller's own Accept-Language first (only when it names a locale in AUTH_EMAIL_TEMPLATE_LOCALES), then the deployment default (#14591's localization.locale, then i18n.defaultLocale), then nothing named, which is EmailService's documented en-US. The header goes through the platform's existing preferredLocaleFromHeader -- the same function REST uses for metadata translation and the runtime dispatcher for ExecutionContext.requestLocale -- so no second parser was invented and the mail cannot disagree with the screen that triggered it. The hit requirement is load-bearing: fr-FR falls through rather than naming a row that does not exist, which is deliberately narrower than normalizeAuthEmailLocale (that one passes unshipped regional tags through because a tenant may overlay en-GB rows); the asymmetry is the ruling's own, a per-request header being a weaker claim than a deployment's declaration. Per-user language stays deferred, no sys_user column added. The 2026-08-13 ruling is rewritten wherever recorded and marked superseded, with its date and reasoning preserved: AuthManager.setDefaultEmailLocale holds the single history record and auth-plugin.ts plus auth-email-locale.test.ts point at it rather than restating it -- no two stories left. VENDOR SEAM MEASURED, NOT ASSUMED, against the installed better-auth 1.7.x: sendResetPassword / sendVerificationEmail / sendInvitationEmail get ctx.request (a Web Request), sendMagicLink gets the endpoint ctx itself, and the change-email notice fires from the global after hook's ctx -- so one defensive reader covers all three shapes and never throws, because a vendor reshaping a callback must degrade to the deployment default, not fail a send.",
      "tests": "All at HEAD cdf2c3c3d, base 7085f9053 (main WITHOUT #14591, which was still OPEN at push time). 13 new cases in auth-email-locale.test.ts: the three the dispatch named (zh-CN caller gets zh-CN; absent header falls back to the deployment default; a locale we ship no row for -- fr-FR, de, pt-BR, '*' -- falls back), plus ja-JP/es-ES/en-US callers, both rungs silent leaving the key ABSENT rather than 'en-US' (what the ladder contract is written against), the invitation abstention, payload non-disturbance, and unit cases for the reader across a Request, an endpoint ctx, both header-bag spellings, bare-language promotion, and a header bag that throws. pnpm --filter '@objectstack/plugin-auth^...' build -- VERDICT command-exit 0. pnpm --filter @objectstack/plugin-auth test -- 'Test Files 90 passed (90) / Tests 1849 passed (1849)'. typecheck exit 0 with 'check:test-typecheck: OK ... 10 file(s) / 94 error(s) / 23 pinned signature(s)' -- ledger unchanged, no new test-layer debt. check-nul-bytes 'OK (scanned 7977 text file(s) ... no raw ASCII control bytes)'. dispatch-gates --repo objectstack-ai/objectstack with no paths passed: 38 families first, ONE real red -- check-system-context-census, pure line rot (comments moved an elevation read in auth-plugin.ts from 1301 to 1310), repaired by the script's own --fix, one anchor rewritten, then 'OK -- 109 elevation read sites in 20 packages across 45 files, all anchored'. That repair added content/docs to the diff, so the family was RE-DERIVED to 62 and the 24 doc families were run too -- all green. Three families refuse on an unbuilt tree; turbo run build --filter='./packages/*' --filter='./packages/*/*' ran 71/71 successful and all three then measured green: check-type-check-coverage --re-measure 'OK -- 22 ledger entr(ies) re-measured in 137.6s, 755 raw tsc error(s) total, none above its recorded number'; check:skill-examples '261 prose examples type-check across 3 surface(s)'; check:dual-build-cjs-loads exit 0, floors held. TWO NOT MEASURED by their own verdict text, neither a red: check-test-completeness (exit 3, wants a saved turbo test log, instructs a local family run to record NOT MEASURED) and check-half-states (exit 3, unread instrument on this host). REVERSE VERIFICATION, deliberately surgical: reverting the whole file would have broken the import and produced a red that proves nothing, so only the load-bearing line was mutated -- 'authEmailLocaleFromRequest(requestSource) ?? this.emailLocale' replaced by 'this.emailLocale' alone, keeping the exported helper so the red stayed behavioural. Direction RED and DISCRIMINATING: '4 failed | 26 passed', the four being exactly the request-precedence cases ('expected zh-CN to be en-US'), while every fallback and unit case stayed green -- a blanket red would have meant the tests were not isolating the rung. Mutation confirmed on disk by blob hash AND anchor counts, never by an editor exit code: HEAD blob d0d15b92, mutated blob 4d642984, injected anchor 1x and deleted anchor 0x. Restore confirmed byte-identical: worktree blob back to d0d15b92, git diff HEAD empty, git status --porcelain empty, restore leg under a trap on EXIT INT TERM with absolute paths from git rev-parse --show-toplevel. NO REBUILD needed for either leg and none claimed: the suite imports ./auth-manager as source, so no dist sits between mutation and assertion. DECLARED NARROWING: os-verify-lock.sh found no usable flock on this macOS host and ran every heavy command in its declared UNLOCKED mode; nothing was serialized against sibling agents. Repo-wide pnpm lint NOT run, no narrowing of it claimed -- it is CI's.",
      "mcp_calls": "0 -- every GitHub read and write went through the gh CLI; no MCP GitHub call was made in this round either.",
      "open_questions": [
        {
          "question": "INVITATIONS: I excluded the invitation send from the request rung. better-auth hands sendInvitationEmail a ctx.request like the others, so wiring it was available and I declined -- that request is the INVITER's, and stamping their browser language onto the invitee's mail reproduces this very card one seat over. The ruling enumerates 注册 / 登录 / 重置密码 (requester IS recipient) and closes with 等, and the superseded 2026-08-13 ruling named invitations as its own counterexample. Is that the intended reading of 等?",
          "options": [
            "A. As shipped -- the request rung covers only sends where the requester is the recipient (verify, reset, magic link, change-email notice); invitees keep the deployment default until a per-user language exists.",
            "B. Extend the request rung to invitations too, taking the inviter's Accept-Language as a proxy for the invitee's.",
            "C. Give invitations their own rung later, from the invitee's stored language, once the deferred per-user column lands."
          ],
          "recommendation": "A now, C eventually. Long-term soundness (weighted highest): the invitee's language is genuinely unknown at invite time, and B answers an unknown with a confidently wrong value -- an English-speaking admin silently forcing English on a Chinese workspace's new hires -- which is the same defect class #14319 exists to close, just relocated. Real business need is measured against the ruling's own text: every scenario it enumerates has requester == recipient, and the reasoning it supersedes named invitations as the counterexample, so B would be extending the ruling rather than applying it. AI-authoring safety: the abstention is commented AT the call site with its reason, so the next author meets a decision rather than an omission they might 'fix'. Startup scope: A is zero new surface; C costs nothing now because it layers onto the deferred column when that arrives. If the maintainer reads 等 as covering invitations, B is a one-argument change at a single site -- cheap to reverse, which is why I shipped the conservative reading rather than blocking on it."
        },
        {
          "question": "PR #14600 will conflict with PR #14591 (still OPEN at push time, so this branch is cut from main without it). Two overlapping regions, both mechanical. How should they be resolved?",
          "options": [
            "A. auth-plugin.ts #8195 comment block: KEEP BOTH -- #14591's localization.locale precedence text, plus this PR's 'this block binds the SECOND rung' framing and its instruction not to restore the superseded reading.",
            "B. content/docs/permissions/system-context.mdx line 100: take neither side's number (1345 from #14591, 1310 here) and instead re-run node scripts/check-system-context-census.mjs --fix on the merged tree, taking what it writes.",
            "C. Rebase this branch onto #14591 pre-emptively before it merges."
          ],
          "recommendation": "A and B together -- they address different files and are both in the PR body already. C is wrong while #14591 is unmerged: rebasing onto an open PR's branch would make this PR depend on a commit that may still change under review. The docs number in particular must NOT be hand-picked (B): it is a derived line number, so choosing a side is guesswork that the gate will simply contradict on the merged tree. I will rebase on request once #14591 lands."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  8. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    ACCEPT (second half; this PR closes the card) — reviewer of record: PM (epic objectstack-ai/cloud#1841), verified against GitHub.

    Generated by Claude Code

  9. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 14319,
      "status": "done",
      "branch": "claude/issue-14319-accept-language",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14600",
      "premise_still_valid": true,
      "summary": "Merge-into-main patch round for PR #14600 after #14591 landed as a5b95ee6e. THREE conflicts, not the two I predicted, and the third is the interesting one. (1) auth-plugin.ts was NOT a text conflict of the kind I forecast: #14591 RELOCATED the #8195 email-locale block earlier in the kernel:ready hook, so git saw delete-vs-modify. Resolved by taking main's side (the block moved) and re-applying this branch's framing onto the relocated copy -- it binds the SECOND rung, the request rung lives in AuthManager, and the superseded 2026-08-13 reading must not be restored. #14591's two-producer content and its applyEmailLocale helper are kept verbatim. (2) auth-manager.ts conflicted too, which I had NOT predicted: #14591 also rewrote the setDefaultEmailLocale docblock. Kept this branch's two-rung ladder framing AND #14591's fact that the deployment rung itself has two producers (localization.locale over i18n.defaultLocale); dropped only main's restatement of 2026-08-13 as operative, because the history paragraph below it already records that ruling as superseded and two live statements of one ruling is exactly what that paragraph exists to prevent. (3) content/docs/permissions/system-context.mdx is a merge=os-regen path: it merged with exit 0 and ZERO conflict markers by silently keeping one side -- the documented silent-drop shape -- so the merged number was untrustworthy in both directions. Committed the merge FIRST (never regenerate in MERGE state), then ran the repo's own generator, which is literally `check-system-context-census.mjs --fix`. It wrote 1353: NEITHER this branch's 1310 NOR #14591's 1345. Both sides were wrong once the two edits stacked, which is precisely why the PR body said not to hand-pick it. The pre-commit hook then confirmed the artifact current and cleared its deferred marker. Pushed as a fast-forward (cdf2c3c3d..9161836b0), no force. PR is now mergeable=true (was dirty) and CI is running on the new head.",
      "tests": "All at merged HEAD 9161836b0 (merge commit 4423fdf6e; main side a5b95ee6e). BOTH PRs' suites now coexist and pass -- the real proof the resolution is behaviourally correct, not merely marker-free: pnpm --filter @objectstack/plugin-auth test -- 'Test Files 90 passed (90) / Tests 1857 passed (1857)', up from this branch's 1849 and #14591's own additions. pnpm --filter '@objectstack/plugin-auth^...' build -- VERDICT command-exit 0; package build exit 0. typecheck exit 0 with 'check:test-typecheck: OK ... 10 file(s) / 94 error(s) / 23 pinned signature(s)' -- ledger unchanged by the merge, no new test-layer debt. node scripts/check-nul-bytes.mjs -- 'OK (scanned 7980 text file(s) -- 7980 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes)'. dispatch-gates --repo objectstack-ai/objectstack with NO paths passed, re-derived on the merged tree AFTER the census regen: 62 families, all 62 run. Final tally 60 green, 2 NOT MEASURED, 0 red. The census family is GREEN this round rather than repaired-after-red, because the regen ran before the derivation: 'OK -- 109 elevation read sites in 20 packages across 45 files, all anchored; 145 anchors resolve, 27 declared non-read'. Four families refuse to answer on an unbuilt tree rather than green-lighting one; turbo run build --filter='./packages/*' --filter='./packages/*/*' ran 71/71 successful and three then measured green: check-type-check-coverage --re-measure 'OK -- 22 ledger entr(ies) re-measured in 81.1s, 755 raw tsc error(s) total, none above its recorded number'; check:skill-examples '261 prose examples type-check across 3 surface(s)'; check:dual-build-cjs-loads exit 0, floors held. The remaining TWO are NOT MEASURED by their own verdict text and neither is a red, unchanged from prior rounds: check-test-completeness exits 3 wanting a saved turbo run test log and instructs a local family run to record NOT MEASURED, and check-half-states exits 3 as an unread instrument on this host. Both are CI's. No ablation this round -- nothing new was implemented, so there was no new assertion to prove could fail; the behavioural evidence for the resolution is the two suites passing together. CI on 9161836b0 via REST at report time: 31 check runs, 12 success / 2 skipped / 17 in_progress, ZERO failure|timed_out|cancelled. DECLARED NARROWING: os-verify-lock.sh found no usable flock on this macOS host and ran every heavy command in its declared UNLOCKED mode; nothing was serialized against sibling agents. Repo-wide pnpm lint NOT run and no narrowing of it claimed -- it is CI's.",
      "mcp_calls": "0 -- every GitHub read and write went through the gh CLI against REST endpoints (repos/.../pulls/14600, repos/.../commits/9161836b0/check-runs, issues/14319/comments). No GraphQL and no MCP GitHub call in any of the three rounds.",
      "open_questions": [
        {
          "question": "PR #14600 now reads draft=false. I opened it as a draft and did not flip it; per the standing rule that PR state I did not set belongs to another actor, I left it alone rather than reverting. Confirming this was PM's deliberate flip and not an accident?",
          "options": [
            "A. PM flipped it intentionally after #14591 landed -- nothing to do.",
            "B. It was unintended and should go back to draft.",
            "C. I should have reverted it on sight."
          ],
          "recommendation": "A, and C is wrong regardless of the answer to A -- reverting another actor's ready-flip is measured to break in both directions and is never mine to do. Flagging rather than acting is the whole of my obligation here. If B, the flip back is PM's, and it should happen before the queue picks the PR up, since mergeable=true now and only the pending checks are holding it."
        },
        {
          "question": "The two open questions from my previous report -- the INVITATION abstention from the request rung, and whether 等 in the 2026-09-02 ruling covers invitations -- are still unanswered and are now sitting in a PR that is ready and mergeable. Should the queue wait on that reading?",
          "options": [
            "A. Land as shipped; invitations keep the deployment default, and extending them later is a one-argument change at a single commented site.",
            "B. Hold the PR until the maintainer rules on 等.",
            "C. Split the invitation question into its own card and land this now."
          ],
          "recommendation": "A, or C if PM wants a tracking card. The conservative reading is the reversible one: shipping it cannot produce a wrong-language invitation, whereas the alternative can -- an English-speaking admin silently forcing English on a Chinese workspace's new hires is the same defect class this card exists to close, relocated one seat. B trades a live, measured fix for signup mail against a question that costs one argument to answer later, which is the wrong side of that trade while the fix is already green."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  10. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    PM answers to the merge-round questions: (1) A — the ready flip was mine (ACCEPT comment above), deliberate, after #14591 landed. (2) A — land as shipped; the invitation abstention stands (A now / C later, as ruled in the ACCEPT), and the tracking card for C is #14641. Auto-merge is armed on this PR; it lands when every check on 9161836 is green. Good catch on the third, silent-drop conflict in the generated census doc — regenerating instead of hand-picking was right.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions