|
142 | 142 | // addressing only `fix` would have left it splitting; readings belong in |
143 | 143 | // `evidence`, which is not keyed. The classified branches (`WORKSPACE_BUILD_FIX` / |
144 | 144 | // `INSTALL_THEN_BUILD_FIX`) group byte-for-byte as they did before. |
| 145 | +// |
| 146 | +// #14729 named a class the ratchet itself produces, not a defect in it: PR #14651 |
| 147 | +// (#14376) taught this gate to walk three new families, one of them |
| 148 | +// `objects.OBJECT._validations.RULE.message`, and its committed baseline therefore |
| 149 | +// held counts only that branch could compute — on `main` the walk did not exist, so |
| 150 | +// other PRs moved those counts without being able to see that they did. Two PRs did |
| 151 | +// exactly that while it waited to land, each costing a patch round: merge `main`, |
| 152 | +// re-derive, rebuild every number in the PR body. Stated generally, because it is |
| 153 | +// not i18n-specific — type-check debt counts, token ratchets and liveness state |
| 154 | +// counts have the same shape: a PR that widens what a committed ratchet MEASURES |
| 155 | +// races every PR that changes what is MEASURED, for its whole review-and-queue |
| 156 | +// latency — and the race is invisible on `main`, because there the instrument does |
| 157 | +// not exist. The two collisions were not independent draws, either: both were steps |
| 158 | +// of one in-flight campaign moving the examples' authored `validations[].message` |
| 159 | +// onto a shared translation channel, and a campaign that systematically touches |
| 160 | +// exactly the family a PR makes measurable collides on every step it lands. As of |
| 161 | +// `224f8ea`, `app-showcase` has zero untranslated rule messages left — that leg is |
| 162 | +// finished — but the exposure is forward-looking, not closed: `app-crm` (5) and |
| 163 | +// `app-todo` (4) rule messages, plus `bulkActions` (18) and `datasets` (62), are |
| 164 | +// untranslated populations nobody is currently working, and a PR that starts |
| 165 | +// walking any of them will race this gate the same way. It fails safe either |
| 166 | +// direction — the DOWN-direction remedy below is what caught both collisions — so |
| 167 | +// the cost is latency and re-derive rounds, not correctness; that is why the fix |
| 168 | +// here is one sentence in that remedy, not a mechanism. |
145 | 169 | import { execFileSync } from 'node:child_process'; |
146 | 170 | import { readdirSync, readFileSync, writeFileSync, existsSync, openSync, closeSync, unlinkSync } from 'node:fs'; |
147 | 171 | import { dirname, join, resolve } from 'node:path'; |
@@ -1338,7 +1362,8 @@ for (const [file, allowed] of Object.entries(baseline)) { |
1338 | 1362 | } else if (now < allowed) { |
1339 | 1363 | errors.push( |
1340 | 1364 | `${file}: untranslated declared strings improved ${allowed} → ${now} — ratchet DOWN: ` + |
1341 | | - `run \`node scripts/check-i18n-coverage.mjs --update\` and commit the baseline.`, |
| 1365 | + `run \`node scripts/check-i18n-coverage.mjs --update\` and commit the baseline. ` + |
| 1366 | + `If you did not touch this population, merge \`origin/main\` first — the movement is probably not yours.`, |
1342 | 1367 | ); |
1343 | 1368 | } |
1344 | 1369 | } |
|
0 commit comments