Repository navigation
The receipt stops guessing who wrote the target text - #206
Merged
SearheiParkhamchuk merged 1 commit intoOct 22, 2026
Merged
SearheiParkhamchuk merged 1 commit into
SearheiParkhamchuk merged 1 commit into
Conversation
A provenance receipt recorded, per leaf, a source hash, UNANSWERED, or
DECLINED. DECLINED was written for every leaf a run saw but did not send, and
both readers took it as "claim nothing": the leaf never read as out of date,
and skip_existing never selected it again however far its source drifted.
That made the plugin protect one kind of hand-written text and destroy
another, with nothing an editor could see to tell them apart. A target a
person filled before the plugin ever ran was kept for good; a target the
plugin wrote and a person then corrected was overwritten the moment the
source moved.
The receipt now records where each leaf's source stood when the run ended,
whether the run translated that leaf or left it alone. Two states remain, a
hash and UNANSWERED, and automatic translation takes every leaf whose source
moved without asking who last touched the target. Where a field must be left
alone, withFieldTranslation(field, { exclude: true }) is the explicit mark;
per-document control arrives with the field picker.
A null in a receipt written by 1.0.0-next.3 or earlier is read at the parse
boundary as UNANSWERED, so the leaf is revisited once and re-based. Refusing
the value instead would fail the whole map and leave the record answering
nothing about any leaf.
This reverses a documented promise. README said skip_existing "fills only
empty target fields"; that line, the strategy tables, the AutoTranslateStrategy
docblock and the admin tooltip are corrected, and DEPRECATIONS carries the
entry. The refresh needs provenance on and a receipt already written for the
locale, which every corrected statement now says.
Part of #198
SearheiParkhamchuk
requested review from
ChiefCreator and
dogfrogfog
as code owners
October 22, 2026 14:01
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🎉 This PR is included in version 1.0.0-next.4 🎉 The release is available on npm package (@next dist-tag) Your semantic-release bot 📦🚀 |
SearheiParkhamchuk
deleted the
fix/translator-the-receipt-stops-guessing
branch
October 22, 2026 14:24
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
A provenance receipt recorded, per leaf, one of three claims: the source hash,
UNANSWERED, orDECLINED(null).DECLINEDwas written for every leaf a run saw but did not send, and bothreaders took it as claim nothing — so that leaf never read as out of date, and
skip_existingnever selected it again, however far its source drifted.
The result was a plugin that protected one kind of hand-written text and destroyed another, with
nothing an editor could see to tell them apart:
What this changes
The receipt becomes a watermark of the source. Per leaf it records where that leaf's source
stood when the run ended, whether the run translated it or left it alone. Two states remain — a hash
and
UNANSWERED— and automatic translation takes every leaf whose source moved, without asking wholast touched the target.
Where a field must be left alone, that is an explicit mark, not a guess the plugin makes from
its own records:
withFieldTranslation(field, { exclude: true }). Per-document, per-field controlarrives with the picker in #48.
Old receipts self-heal. A
nullwritten by1.0.0-next.3or earlier is read at the parseboundary as
UNANSWERED, so the leaf is revisited once and re-based. Refusing the value instead wastried and rejected: one out-of-schema value fails the whole map, and a degraded record answers
nothing about any leaf — so under
skip_existingwith no empty target the run selects nothing,writes nothing, and the document stays degraded indefinitely. That was verified by running the
schema, not reasoned about.
This reverses a documented promise, on purpose
README.mdsaidskip_existing"fills only empty target fields". That line, both strategytables, the
AutoTranslateStrategydocblock and the admin tooltip are corrected, anddocs/DEPRECATIONS.mdcarries the entry. Every corrected statement now also says what review foundmissing: the refresh needs
provenanceon and a receipt already written for that locale —without either,
changedLeavesreturns{}and behaviour is exactly as before.Affordable because the package is at
1.0.0-next.3under thenextdist-tag and the major does notship until epic #192 is complete, so the picker that replaces the lost protection lands in the same
release.
What the review passes found
Six passes ran over this change: three review angles, a stub-and-mutation audit of the suite, an
abstraction audit, two complexity audits and two comment audits. The four that changed the outcome:
provenancedefaults tofalse; the first draft read as though therefresh were unconditional. Five statements corrected.
isRecordStaleis true infour cases and in two of them the source did not change, so the label would have been false on
screen and in the accessible name.
leafChangedtestedrecorded === UNANSWERED || recorded !== current;the first term can never decide the result, because the marker can never equal a hash. Keeping it
suggested a danger that does not exist. The invariant it leaned on is pinned by its own check.
staleness.tscame out at 70 lines from 99, having also lostmatchesClaim— a verbatim negationof the function beside it — and an unreachable
undefinedstate.Not solved here
design; the surface belongs with the picker in [translator] Choose fields, not a strategy — rebuild how a run picks what it translates #48.
recordhas alwaysiterated
currentFields, and this diff does not touch that line — but it is the one place where"translate everything whose source moved" still does not hold.
{"title":null}receipt and runs the real handler. Themigration is covered by six unit checks.
Checks
180 files / 2405 checks · integration 58 files / 235 checks ·
check-types0 across all nineworkspaces ·
oxlint44 warnings, 0 errors ·turbo run buildgreen, which is the realtscdeclaration emit rather than
tsgo·sp-diff-checksclean ·sp-lint-deltaintroduced nothing.Decisions, rejected alternatives and the full review log are in
packages/payload-plugin-translator/docs/plans/2026-10-22-the-receipt-stops-guessing.task.md.Part of #198