Skip to content

fix(spec): punctuate the zh-CN Operation Message Catalog with the full-width comma - #12904

Merged
os-trump merged 8 commits into
mainfrom
claude/issue-12717-zhcn-punctuation
Aug 28, 2026
Merged

fix(spec): punctuate the zh-CN Operation Message Catalog with the full-width comma#12904
os-trump merged 8 commits into
mainfrom
claude/issue-12717-zhcn-punctuation

Conversation

@os-trump

@os-trumpos-trump commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes#12717

The catalog literal and every pin asserting it move together in this one PR, which is what the
triage seat's ordering ruling requires.

Authored in Claude Code session session_01LpRNHxWZgSUgVnFT9mQQo4.

What moves

Every zh-CN entry in BUILTIN_OPERATION_MESSAGES separated its clauses with U+002C (the ASCII
comma) while ending the same sentence with U+3002 (the full-width period) — two punctuation
systems inside one sentence, in strings a business user reads in a toast.

Twelve U+002C across the nine zh-CN entries become U+FF0C: the five situation keys
(permission_denied, record_access_denied, record_change_not_allowed, record_write_denied,
approval_recall_not_submitter) and the four delete_restricted* variants.

Measured by codepoint before and after, not by eye:

U+002CU+FF0CU+3001U+3002
before12029
after01229

Nothing else moves. The U+3002 sentence finals were already correct. The two U+3001 (、) in the
_required variants are the ideographic ENUMERATION comma in 必填、无法清空 — correct Chinese,
and deliberately left alone; the card is about the clause separator only. The en, ja-JP and
es-ES rows are byte-identical (the catalog diff is 9 insertions / 9 deletions, all inside the
zh-CN block).

Text-face only: no key, placeholder, wire code, status or accept/reject behaviour changes. A
deployment that overrides errors.MESSAGEKEY through a translation is unaffected; only the
built-in default moves.

The pin census

Five test files pin these literals by asserting catalog-rendered output, so all five move with
the literal. Each classification was confirmed by RUNNING the file, not by reading it:

FileSiteMeasured
packages/spec/src/system/operation-message.test.ts20, 118, 208, 212, 325, 329green (440 files / 11650 tests)
packages/plugins/plugin-approvals/src/recall-refusal-user-copy.test.ts147 ZH_SENTENCE11/11
packages/plugins/plugin-security/src/permission-denied-user-copy.test.ts25712/12
packages/objectql/src/engine-delete-restricted-locale.test.ts17112/12
packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts249 ZH_SENTENCE, feeding 270/273/281/305/35716/16 (whole package 673)

Each was measured RED against the catalog change before being moved — that is this PR's reverse
verification. Representative output:

FAIL src/permission-denied-user-copy.test.ts:257
expected '您没有执行此操作的权限,如需访问请联系管理员。'
to be '您没有执行此操作的权限,如需访问请联系管理员。'
FAIL src/write-denial-user-copy.test.ts (4 failed | 12 passed)
· the report, reproduced: a zh-CN user PATCHing a row they do not own reads Chinese
· the DELETE verb reads the same sentence — one key serves both write verbs
· the `FORBIDDEN:` prefix survives the conversion and still strips clean
· a misbehaving i18n service degrades to the built-in copy, never to a 500

Exactly one literal changed per pin file; no assertion was weakened — every site keeps its
exact-equality toBe, so each suite still pins the ruled spelling end-to-end. In both
plugin-security and plugin-sharing the neighbouring assertions that read
BUILTIN_OPERATION_MESSAGES directly stayed green throughout, which localises the defect: only
the hand-transcribed literals were ever stale.

⛔ Editing a pin's expectation to match new output is only legitimate when done BY the change that
moved the literal. This PR is that change.

Two further files quote a catalog sentence but are NOT pins — they construct their own Error
and assert the transport echoes it back (grep -c for BUILTIN_OPERATION_MESSAGES|renderOperationMessage
returns 0 in each), so they are green for any string. Run green and left byte-identical:

FileSiteMeasured
packages/rest/src/rest.test.ts2552, 2564, 2595, 2614228 passed, untouched
packages/runtime/src/domains/data-permission-denied-envelope.test.ts42 USER_MESSAGE5 passed, untouched

Sweep method

Population: 5145 tracked .ts files (2883 *.test.ts), git grep -F at the head being
pushed. Per catalog sentence, a distinctive comma-free fragment is the anchor — so a stale pin
cannot hide behind the very character under test — and both spellings of its clause are probed
separately. Each fragment carries a positive control: it must hit the catalog source itself or
the reading is void. All 8 controls passed. The sweep was re-run after the final main merge, on
the exact tree pushed.

Verification

All readings from 9bade031d, this branch's final commit (current origin/maina77e46010
merged in; everything re-run after that merge).

Build: pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*
— 70/70 successful, exactly as lint.yml does it.

Dist reachability proved before trusting any consumer test: the emitted CJS, ESM and browser
bundles of @objectstack/spec were unescaped and searched — the new U+FF0C sentence present
(1 occurrence each), the old U+002C form absent (0 occurrences). Consumers resolve
@objectstack/spec/system to dist, so without this every consumer reading would have been of a
stale bundle.

Tests: the five pin files as tabled above, plus whole-package runs for @objectstack/spec (440
files / 11650 tests) and @objectstack/plugin-sharing (29 files / 673 tests); typecheck green
for spec, plugin-security, plugin-approvals, objectql and plugin-sharing; both fixture-local
controls green untouched.

Gates: 44 families run locally, 43 green — including check:adr-anchors, the spec families,
the changeset family, check:query-options-erasure, check:type-check-coverage,
check:type-check-debt (re-measured 31 ledger entries, none above its recorded number),
check:engine-double-contract, check:where-matcher, check:cross-package-test-inputs,
check:i18n, check:i18n-stale-fill, check:nul-bytes, check:durability-log-level,
check-engine-split-ratio and check-undeclared-dep-imports. Per-gate exit codes were captured
individually before any pipe.

The one non-green is scripts/pm/check-half-states.mjs, which printed PREREQUISITE NOT MET (no
valid GitHub credential in this container) and swept nothing — NOT MEASURED, not a failure; the
real pnpm check:pm-half-states script passed.

Lint: eslint over all 6 changed source files — 0 errors, 0 warnings. The narrowing is measured,
not asserted: population from eslint's own config, count from --format json, and the config
declares no parserOptions.project and no typed @typescript-eslint rules, so this diff cannot
move the verdict of any file it did not touch. CI runs the full farm regardless.

Scope notes

Two zh sentences keep their ASCII comma deliberately, because both are deployment-override
fixtures — the value a fake translator or bundle returns, asserted to WIN over the built-in
catalog. Neither is a catalog literal and no user reads either:

  • 本单只能由发起人撤回,请联系发起人。 — approvals test, lines 250 and 259.
  • 此操作已被安全策略阻止,请联系系统管理员。 — plugin-security test, lines 352 and 354.

Sibling repo objectui carries its own separate zh bundle (packages/i18n/src/locales/zh.ts)
with different sentences; no coupling to these nine literals.

#11993's pin landed on main first, per the triage seat's ordering constraint.

…l-width comma
Every zh-CN entry in BUILTIN_OPERATION_MESSAGES separated its clauses with
U+002C while ending the same sentence with U+3002 — two punctuation systems
inside one sentence, in strings a business user reads in a toast.
Twelve U+002C across the nine zh-CN entries become U+FF0C. The U+3002 sentence
finals were already correct, the two U+3001 enumeration commas are correct
Chinese and stay, and the en / ja-JP / es-ES rows are untouched.
The pins that quote the moved literals move in the same commit, per the triage
ordering ruling: the spec catalog's own test and plugin-approvals'
recall-refusal operator-visible pin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@github-actions

github-actionsBot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

9 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: 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 d41d166b857e5ff188ba79bcf756c8b110a48cb0packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4120e1a719632c0053abbff4494b2a72c698b013 — the merge of head 9bade031daae4895f401f7203c719541a366d311 into base d41d166b857e5ff188ba79bcf756c8b110a48cb0, 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 4120e1a719632c0053abbff4494b2a72c698b013 && git checkout 4120e1a719632c0053abbff4494b2a72c698b013
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d41d166b857e5ff188ba79bcf756c8b110a48cb0 9bade031daae4895f401f7203c719541a366d311 && git checkout -B drift-repro d41d166b857e5ff188ba79bcf756c8b110a48cb0 && git merge --no-ff 9bade031daae4895f401f7203c719541a366d311
node scripts/docs-audit/affected-docs.mjs --json d41d166b857e5ff188ba79bcf756c8b110a48cb0

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

… the full-width comma
The whole-repo pin census for the catalog punctuation pass found seven files
quoting these literals, not the two anticipated at grading time. Four are real
pins that assert catalog-rendered output; two of them sat outside the file
surface originally authorized for this card:
- plugin-security permission-denied-user-copy.test.ts:257 — the hand
transcribed literal beside line 256's catalog read, which already passes.
- objectql engine-delete-restricted-locale.test.ts:171 — the sentence
rendered through the real engine with translated labels.
Both are forced by the catalog edit in the previous commit; moving them here
keeps the literal and every pin asserting it in ONE PR, per the triage
ordering ruling. Surface extension authorized by the dispatching seat.
The fixture-local quotes in rest.test.ts and data-permission-denied-envelope
.test.ts construct their own Error and were measured green unchanged, so they
are deliberately untouched, as are the deployment-override fixtures in both
edited files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
…h comma
packages/plugins/plugin-sharing/src/write-denial-user-copy.test.ts pins the
record_write_denied sentence as a hand-transcribed literal (ZH_SENTENCE, line
249) feeding five assertion sites. It renders through the operation catalog
end-to-end, so the zh-CN punctuation pass this card owns moved the value out
from under it.
Only the literal moves; every assertion keeps its exact-equality toBe against
it, so the suite still pins the ruled spelling end-to-end. The catalog-reading
assertions in the same file (record_write_denied read straight off
BUILTIN_OPERATION_MESSAGES) were green throughout -- as in plugin-security,
only the hand-copied literal was stale.
Why it was missed: the original pin sweep ran at the claim-time base, where
this file did not yet exist; it arrived in a later main merge. The sweep is now
re-run at the head being pushed, over both spellings of all nine sentences
across all tracked TS, with a positive control per fragment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4
@os-trump
os-trump marked this pull request as ready for review August 28, 2026 15:06
@os-trump
os-trump enabled auto-merge August 28, 2026 15:06
@os-trump
os-trump added this pull request to the merge queueAug 28, 2026
Merged via the queue into main with commit be93457Aug 28, 2026
41 checks passed
@os-trump
os-trump deleted the claude/issue-12717-zhcn-punctuation branch August 28, 2026 15:33
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:systemsize/steststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] the zh-CN Operation Message Catalog punctuates with an ASCII comma, while the platform's other shipped zh-CN copy uses the full-width one

2 participants

@os-trump@claude