Skip to content

test(sharing): hold the built-in rules table to all three locales (#809) - #1240

Merged
os-sam merged 2 commits into
mainfrom
claude/issue-809-sharing-rules-table-all-locales
Aug 23, 2026
Merged

test(sharing): hold the built-in rules table to all three locales (#809)#1240
os-sam merged 2 commits into
mainfrom
claude/issue-809-sharing-rules-table-all-locales

Conversation

@claude

@claude claude Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Fixes #809

Description

test/sharing-coverage.test.ts had three doc-reading rules over the sharing-and-security pages. #790 gave the third one a per-locale sibling and widened the Org-Wide Defaults guard to all three locales, but the two that read the Built-in sharing rules table — the roster of what the app widens out of the box, and the object / access / position each row claims — still read SHARING_DOC, the English page, alone. This widens them over the existing PAGES ledger so all three pages are held to the same truth.

⚠️ This PR is intentionally RED, and that red is the deliverable

The card filed this as a dormant coverage gap, on a 2026-08-05 row-by-row check that found both Chinese tables clean. It is no longer dormant. Re-measured on main @ 966cc23:

[en]      sharing-and-security.mdx          rows=10  missing=[]                            ghost=[]
[zh-Hans] sharing-and-security.zh-Hans.mdx  rows=9   missing=["Unassigned Cases — Triage"]  ghost=[]
[zh-Hant] sharing-and-security.zh-Hant.mdx  rows=9   missing=["Unassigned Cases — Triage"]  ghost=[]

4a0e1de (2026-08-12, PR #1134 for #1096) added case_unassigned_triage_sharing and its English row in one commit and never touched the two Chinese pages — the exact mechanism this card predicted one week earlier. For ten days both Chinese pages have shown nine of the app's ten sharing rules, and the missing one is the app's only grant over records with no owner at all: every holder of the Service Agent position has edit access to open, unowned cases, on the app's own security page.

The guard therefore fails on main with no mutation at all, naming both pages and the rule. The two doc rows are #1239, deliberately not in this PR — the dispatch on #809 scoped this to test/sharing-coverage.test.ts only, because a docs correction and a guard widening in one PR make each other unreviewable. This PR is Blocked by #1239 and cannot go green until it lands; #1239 remains open until then.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • CI/CD update

Related Issues

Fixes #809
Related to #790, #791, #725, #592, #1096
Blocked by #1239 (the two missing doc rows this guard reports)

Changes Made

  • Retired the two English-only rules (the built-in rules table lists exactly the shipped sharing rules, every documented rule states the object, access level and position it really grants) and replaced them with one per-locale block over PAGES. Nothing was dropped — the English page is that ledger's en entry, proven by reverse-verification leg D3 below.
  • LocalePage gains rulesHeading (### Built-in sharing rules / ### 内置共享规则 / ### 內建共用規則).
  • New ACCESS_WORD map — three locales × two access levels (Read/Edit, 读取/编辑, 讀取/編輯). This is the one genuinely new thing the card predicted. An unmapped access level throws by design, the convention owdCell and COUNT_WORD already use.
  • Two anti-vacuum rules in the file's existing style: a floor on sharingRules.length, and a per-page "the table still parses" check.

Built so a future classification change is one edit

Per the dispatch's warning about #549 (undecided, proposing crm_contractcontrolled_by_parent): no per-object, per-locale fact about sharing classification is copied into this block. Every fact a row states is derived from the compiled stack — the roster is sharingRules itself, and the object, access level and position must be the ones the rule really grants. Adding or removing a sharing rule costs zero ledger edits here. Only the language is authored, once per word: the object column reuses ROW_LABEL (the ledger the OWD table already reads) on the Chinese pages and the stack's own label on the English one, which is a derivation and stronger than a ledger. A reclassification moves ACCOUNT_CHILD_COVERAGE — one entry, one place — and nothing here moves with it.

Testing

  • Unit tests pass (npm test) — see the exact status below
  • Linting passes (npm run lint)
  • Build succeeds (npm run build)
  • Manual testing completed — n/a, this is a test-side guard
  • New tests added (if applicable)

Gates, at commit 74c2460 (the pushed head; the run below is that tree)

pnpm verify chains eight. Exit codes captured by redirect, never through a pipe.

gate result
pnpm validate
pnpm typecheck
pnpm lint
pnpm lint:i18n-gate
pnpm hygiene
pnpm hygiene:tokens ✓ source token ratchet clean
pnpm build
pnpm test 2 failed | 2807 passed | 1 skipped (2810), 1 failed | 116 passed (117) files

Both failures are this PR's new guard reporting #1239, and nothing else in the repo moved:

FAIL test/sharing-coverage.test.ts > … > sharing-and-security.zh-Hans.mdx > lists exactly the sharing rules this app ships
FAIL test/sharing-coverage.test.ts > … > sharing-and-security.zh-Hant.mdx > lists exactly the sharing rules this app ships

AssertionError: content/docs/administration/sharing-and-security.zh-Hans.mdx (zh-Hans): the built-in
sharing-rules table has drifted from src/sharing/.
  shipped, but this page lists no row for it: Unassigned Cases — Triage
  row naming a rule this app does not ship:   (none)

Also run: node scripts/check-stackblitz-lock.mjs → exit 0, "package-lock.json is in sync with package.json (v3)". That is CI's first step after install and is not part of pnpm verify.

Token ratchet — before == after, by construction

This branch changes test/sharing-coverage.test.ts and one .changeset/ file; the ratchet measures src/**/*.ts only, and git diff --name-only origin/main..HEAD | grep -c '^src/' is 0. Measured at 74c2460, unchanged from main:

scope ~tokens ceiling headroom
business semantics 81,904 85,000 3,096
interaction layer 38,826 42,000 3,174
authored total 134,657 140,000 5,343

No ceiling raised.

Reverse verification — five directions, each predicted before it was run

Every mutation was applied by an anchored replace that asserts it matched exactly once (a zero-hit sed exits 0 and reads like a successful ablation), proven on disk with a grep -c of both the injected and the removed text plus git diff --numstat, and restored under a trap … EXIT INT TERM so a foreground-cap SIGTERM could not leave a mutated tree behind for the next measurement. Restores proven by an empty git status --porcelain.

# direction predicted measured
D0 none — the drift already on main RED on the roster rule for both Chinese pages, naming the rule 2 failed | 45 passed, both naming Unassigned Cases — Triage
D1 add a fabricated Retired Partner Portal Sharing row to zh-Hant RED on zh-Hant's roster rule, ghost half naming it, missing half still naming the triage rule; the other two pages unmoved 2 failed | 45 passed; zh-Hant now reports row naming a rule this app does not ship: Retired Partner Portal Sharing
D2 zh-Hans Grants cell 编辑读取 on one row RED on zh-Hans's row-facts rule only, naming the rule and both words — proves ACCESS_WORD is really consulted per locale 3 failed | 44 passed: Account Team Sharing: row grants "读取", the rule grants "编辑"
D3 English object cell AccountOpportunity RED on the en row-facts rule — proves retiring the two English-only rules did not lose English coverage 3 failed | 44 passed: sharing-and-security.mdx (en) … row says object "Opportunity", the rule targets "Account"
D4 zh-Hant object cell 案件客戶 RED on zh-Hant's row-facts rule expecting 案件 — a word only ROW_LABEL supplies, so it proves the Chinese object column is answered by the ledger and not by the page against itself 3 failed | 44 passed: Escalated Cases Sharing: row says object "客戶", the rule targets "案件"
D5 restore everything, re-run identical to D0 2 failed | 45 passed, git status --porcelain empty

Every failure names the file, the locale and the row, which was the requirement — a guard that fires for real has to be actionable without re-deriving what drifted.

Checklist

  • I have added a changeset.changeset/sharing-rules-table-guard-every-locale.md
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation — deliberately not: the docs correction is Both Chinese sharing pages list 9 of the app's 10 sharing rules — the Unassigned Cases — Triage row never followed #1096 #1239, kept out of this PR by the dispatch so the guard and the correction stay separately reviewable
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes — 2 fail on purpose, see above
  • Any dependent changes have been merged and published

Additional Notes

The three PM mechanism assumptions were measured rather than trusted:

  1. "The gap is still exactly this" — half true. The two rules did still read SHARING_DOC alone, exactly as described. But the Chinese tables have drifted since 2026-08-05, so this stopped being a dormant coverage gap and became a live defect. That is the loud part of this PR.
  2. "PAGES is the right ledger and ROW_LABEL covers what you need" — confirmed, no second ledger needed. ROW_LABEL carries 客户/客戶, 商机/商機, 工单/案件, 营销活动/行銷活動 — the four objects the table names — and they match the pages exactly. Position names are language-invariant inside backticks and needed no ledger at all. Only the Grants column needed something new, and it is the three-line map the card predicted.
  3. "The nine rules are stable across locales" — confirmed for rule names (all rows in both Chinese tables carry the English label verbatim, Account Team SharingLive Campaigns — Marketing Director). The count in that assumption is what moved: it is ten rules now, not nine, which is the drift itself.

Sanitizer note on the card: #809's body is truncated where it proposes the map — it reads ACCESS_WORD: Record>, the <…> type parameters having been eaten after the comment was stored. The intent was recoverable from context.


Generated by Claude Code

The two rules over the Built-in sharing rules table read the English page
alone, while #790 had already given the related-list rule a per-locale
sibling and widened the OWD guard to all three pages. They are now one
per-locale block over the existing PAGES ledger.

Every fact a row states stays derived from the compiled stack: the roster
is `sharingRules` itself, and the object, access level and position cells
must be the ones the rule really grants. Only the language is authored --
`ROW_LABEL` for the object column on the Chinese pages (the stack's own
label on the English one, which is stronger), and a new ACCESS_WORD map
for the Grants column. No per-object, per-locale fact about sharing
classification is copied in, so a reclassification stays one edit in one
place (#549 is undecided and must not cost three).

This is red on main, on purpose: #1096 added `case_unassigned_triage_sharing`
and its English row on 2026-08-12 and never touched the Chinese pages, so
both list nine of the app's ten rules. The two missing rows are a separate
docs correction -- #809 remains open there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzS6NUXo5A5KEwj1KjvPHk
@vercel

vercel Bot commented Aug 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 23, 2026 2:43am

Request Review

@github-actions github-actions Bot added the ci/cd CI plumbing and the verification pipeline label Aug 22, 2026
os-sam pushed a commit that referenced this pull request Aug 23, 2026
…e pages (#1239)

The app ships ten built-in sharing rules; both Chinese Sharing & Security
pages listed nine. `4a0e1de` (#1096) added `case_unassigned_triage_sharing`
and its English table row in one commit and never touched
`sharing-and-security.zh-Hans.mdx` or `.zh-Hant.mdx`.

The undocumented rule is the app's only grant over records with no owner at
all: `service_agent` gets EDIT on open, unowned cases
(`record.owner_id == null && record.is_closed == false`). A Chinese-reading
admin auditing unassigned case intake was shown a complete-looking nine-row
table that omitted a real access grant.

Every cell is taken from `src/sharing/case.sharing.ts`: object `crm_case`
(工单 / 案件, the word each page already uses), access level `edit`
(编辑 / 編輯), position `service_agent`, and the criteria stated in each
page's own vocabulary — 负责人 / 負責人 is the Owner term those pages define,
未关闭 / 未關閉 the word their sibling escalation rows already use. The rule
name stays English, as every other row does. The rows are appended after
`Escalated Cases — Service Director` so the order matches the English page.

No sharing rule changed. This unblocks the all-locales guard in PR #1240
(#809), which is red on `main` on exactly this drift and needs no edit.

Claude-Session: https://claude.ai/code/session_01WzS6NUXo5A5KEwj1KjvPHk

os-sam commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

PM note — the blocker landed. Base refreshed; this PR's red should now clear with zero edits to the guard, which is the whole point.

#1239 merged as 48970f7 (PR #1241). I have refreshed this branch onto the new main so the guard runs against the corrected pages rather than the drift it was written to report.

Verified on main after the merge, by me, not taken from #1241's report:

sharing-and-security.mdx          Unassigned Cases ×1
sharing-and-security.zh-Hans.mdx  Unassigned Cases ×1
sharing-and-security.zh-Hant.mdx  Unassigned Cases ×1

against 10 shipped rules in src/sharing/*.sharing.ts. Previously 1 / 0 / 0. The undocumented grant over ownerless records — every service_agent with EDIT on open unowned cases — is now on the security page in all three languages.

What this PR proved, and why the red mattered

This PR was dispatched on a card that described a dormant coverage gap. It was not dormant: the guard fired on its first run against unmutated main and named both pages and the exact rule. 4a0e1de (2026-08-12, PR #1134 for #1096) had added case_unassigned_triage_sharing and its English row in one commit and never touched the Chinese pages — ten days before this guard existed to catch it.

The red was never something to clear by weakening the guard, narrowing it to English, or exempting a locale. It was the deliverable. Two PRs were kept deliberately separate so that this stayed reviewable: the guard here, the two doc rows in #1241, and the red-to-green transition run in both directions on #1241 against a byte-identical copy of this file (test/sharing-coverage.test.ts is unmodified by that PR — git diff --name-only there is the two pages plus one changeset).

Recorded because it is the reusable part: a coverage card whose premise is "this gap is dormant" is worth re-measuring before it is treated as low-value work. This one had already fired.

Once CI settles I will confirm Build and Test (22.x) by name — ⛔ not the aggregate check_suite conclusion, which on every PR landed this round reported success while that job was still in_progress — and land this.


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review August 23, 2026 02:45
@os-sam
os-sam added this pull request to the merge queue Aug 23, 2026
Merged via the queue into main with commit 6a912b7 Aug 23, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

守卫单语缺口:「内置共享规则表」口径规则只读英文页,三语的另两份表无人检查

2 participants