fix(ops): refuse an account whose rows are not the thousandfold class

The provenance criterion proves where a row came from, not which build
imported it. An account re-imported on the fixed build carries the same
`EXPORT_XML_SOURCE_MAX` stamp and the same `stats:` external id with the
right numbers and no audit row, so it is selected exactly like a broken
one — and its rest days, under 200 m because the phone stayed at home,
survive the plausibility check after another thousandfold multiply.

So one candidate row that would leave the range now refuses the whole
account: nothing written, no audit row, the offending rows and a printed
reason instead. The idempotency guard only ever held against the
script's own prior run; this holds against the case it could not see.

The runbook says the same in words — a re-imported account must not be
repaired, the re-import already did it — and says what the dry run is
for: it is where the decision is made, not a formality in front of one
already taken.

Integration coverage for the re-imported account: correct rows, an
`--apply` run, nothing changes and no audit row is left behind.

Review finding H1.
This commit is contained in:
Marc-André Bombeck committed 2026-09-10 00:27:43 +02:00
1 parent 65e9ecbde6
commit 361e577864
4 files changed
+189 -18

No files matched your search

+20 -5
View File
@@ -24,8 +24,10 @@
* - `--apply` writes, one transaction per account, and records an audit row
* in that same transaction. An account carrying that row is skipped, so a
* second run finds nothing.
* - A row whose repaired value would leave the plausibility range is
* reported and never written.
* - A row whose repaired value would leave the plausibility range refuses
* the WHOLE account: one such row is evidence the rows are not the 1000x
* class (an account re-imported on the fixed build looks exactly like
* this), and its rest days would otherwise be multiplied again.
* - `--unit=mi` for an imperial archive (default `km`). The stored row keeps
* no record of the archive's unit — that is the defect — so the operator
* names it; a mismatched unit is why the flag exists rather than a guess.
@@ -76,6 +78,7 @@ async function main(): Promise<void> {
let totalRepairable = 0;
let totalUpdated = 0;
let totalSkipped = 0;
let refused = 0;
for (const plan of plans) {
if (plan.alreadyRepairedAt) {
@@ -107,7 +110,12 @@ async function main(): Promise<void> {
for (const row of plan.outOfRange) {
console.log(
` ! ${row.id} (${row.measuredAt.toISOString().slice(0, 10)}): ` +
`${row.value} m x ${factor} leaves the plausible range — not written`,
`${row.value} m x ${factor} leaves the plausible range`,
);
}
if (plan.outOfRange.length > 0) {
console.log(
" ! this account will be REFUSED — see the reason under --apply",
);
}
@@ -117,6 +125,11 @@ async function main(): Promise<void> {
});
totalUpdated += outcome.updated;
totalSkipped += outcome.skipped;
if (outcome.refusedReason) {
refused += 1;
console.log(` ✗ refused: ${outcome.refusedReason}`);
continue;
}
console.log(
` ✓ repaired ${outcome.updated} row(s), skipped ${outcome.skipped}`,
);
@@ -124,9 +137,11 @@ async function main(): Promise<void> {
console.log(
apply
? `\nDone. ${totalUpdated} row(s) repaired, ${totalSkipped} skipped.`
? `\nDone. ${totalUpdated} row(s) repaired, ${totalSkipped} skipped, ` +
`${refused} account(s) refused.`
: `\nDry run: ${totalRepairable} row(s) would be repaired. ` +
"Re-run with --apply to write.",
"Read the worked examples above and decide before re-running with " +
"--apply.",
);
}