Repository navigation
feat!: provenance becomes an ordinary module on a run seam - #207
Merged
SearheiParkhamchuk merged 1 commit intoOct 23, 2026
Merged
Conversation
The translation handler wired provenance in by name: four blocks guarded with `provenance?.` resolved the service, built its key, captured fingerprints, computed drift and wrote the receipt. Nothing else could join a translation run without editing that handler again. A run now has two positions any module can occupy — before the translation, and after the document is saved. Provenance is the first to take them, with no privileges. A module returns only its contribution rather than the whole run state; the chain merges `moduleState` by key, so a module cannot drop a slot it does not own, and a module that fails contributes nothing instead of ending the run. Module names are refused at boot when two collide, because a name keys the module's slot in that state. The config-time half follows. `levels` is replaced by `modules` and `components`, separating what wires server routes from what attaches an admin control. The document and collection levels registered the same route bundle — the only reason endpoints were deduplicated at all — and once the controls moved out they were indistinguishable and collapsed into one module. Two further changes ride along: - An address absent from a stored receipt now means "translate" rather than "cannot tell". A field whose source was emptied and later refilled is re-translated instead of being left alone forever. - Route factories and the shared config context no longer accept the absence of an access guard. The guarantee is pinned with `@ts-expect-error`, so a route that forgets one fails the type-check rather than serving unguarded. Closes #198 BREAKING CHANGE: the `levels` plugin option is removed, along with `documentLevel()`, `collectionLevel()`, `fieldLevel()` and the `TranslationLevel` type. Replace `levels: [documentLevel(), collectionLevel()]` with `modules: [documentTranslation()]` and `components: [translateDocumentControl(), bulkTranslationDashboard()]`; `levels: [fieldLevel()]` becomes `modules: [fieldTranslation()]`. Both lists are required and either may be empty — an omitted option used to mean two surfaces silently, and now fails at boot with the replacement in the message. See docs/DEPRECATIONS.md for the full table.
SearheiParkhamchuk
requested review from
ChiefCreator and
dogfrogfog
as code owners
October 23, 2026 13:09
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
🎉 This PR is included in version 1.0.0-next.5 🎉 The release is available on npm package (@next dist-tag) Your semantic-release bot 📦🚀 |
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.
Closes #198
The problem
The translation handler knew provenance by name. Four blocks guarded with
provenance?.resolved the service, built its key, captured fingerprints, computed drift and wrote the receipt. Anything else that wanted to join a translation run — a glossary that must read from the CMS before the request, an audit entry after the save — had nowhere to go without editing that handler again.What changes
A run has two positions any module can occupy: before the translation, and after the document is saved. Provenance is the first to take them, with no privileges.
moduleStateby key, so a module cannot drop a slot it does not own.The config-time half follows.
levelsis replaced bymodulesandcomponents, which separates what wires server routes from what attaches an admin control. The document and collection levels registered the same route bundle — the only reason endpoints were deduplicated at all — and once the controls moved out, the two were indistinguishable and collapsed into one module.Breaking
The
levelsoption is removed, withdocumentLevel(),collectionLevel(),fieldLevel()and theTranslationLeveltype.levels: [documentLevel()]modules: [documentTranslation()]·components: [translateDocumentControl()]levels: [collectionLevel()]modules: [documentTranslation()]·components: [bulkTranslationDashboard()]levels: [fieldLevel()]modules: [fieldTranslation()]modules: [documentTranslation()]·components: [translateDocumentControl(), bulkTranslationDashboard()]Both lists are required and either may be
[]. An omitted option used to mean two surfaces silently; it now fails at boot with the table above in the message. Full entry indocs/DEPRECATIONS.md.One behaviour change, worth seeing before it ships
An address absent from a stored receipt now means "translate" rather than "cannot tell". A field whose source was emptied and later refilled is re-translated instead of being left alone forever.
The consequence: a receipt that is a whole-document digest, or no receipt at all, reads as "every field changed". A document translated before provenance was switched on therefore gets every field re-translated on its first drift-only run — including text a person typed.
Also here
Route factories and the shared config context no longer accept the absence of an access guard. The guarantee is pinned with
@ts-expect-error, so a route that forgets one fails the type-check rather than quietly serving unguarded.Verification
apps/cmsandapps/devtype-check cleantsgoalone does not catch what it catchesThree defects were found by review and confirmed by mutation before being fixed — in each case the whole suite stayed green over broken code:
@ts-expect-errorwas satisfied by "cannot find module"Each now has a check that reddens on that mutation alone.
Not solved here
translatedAt.