没有不值得去解决的问题,也没有不值得去学习的技术!

Which States Should a Single Content Change Invalidate? Dependency and Invalidation Propagation Design in v2

Which States Should a Single Content Change Invalidate? Dependency and Invalidation Propagation Design in v2

作者:

在

I previously wrote another post:

The glossary changed, so why shouldn’t all pages be retranslated?

That post mainly discussed a rather specific issue:

When the unified glossary changes, how to identify the pages actually affected and reprocess only the translation units that truly need modification, rather than retranslating and reviewing the entire language version.

However, as Go Learning & Documentation v2 becomes more complete, I realize this problem isn’t actually confined to the glossary.

Similar issues exist throughout the entire workflow:

After an upstream state changes, which downstream results should actually be invalidated?

For example:

A page is revised.

Does its previous quality review still count?

Can the original sealed result of the content package still be used?

Does the integrated site review need to be redone?

Should other completely unchanged pages be reviewed again along with it?

If only a single input file changes, should the entire language version go through the workflow again?

On the other hand, if invalidation criteria are too loose, an even more dangerous problem arises:

Downstream continues to use an old result that no longer corresponds to the current content.

So what v2 ultimately needed to solve was no longer just:

Plaintext
Did a change occur?

But rather:

Plaintext
What exactly changed?
↓ Which states truly depend on this change?
↓ Which existing results are still valid?
↓ Which states must be invalidated?

This post aims to organize this problem.


The simplest approach: clear everything downstream whenever upstream changes

If we only consider safety, the easiest rule to implement is actually very straightforward.

Assume the overall workflow is roughly:

Plaintext
Source content
↓ Translation units
↓ Quality review
↓ Content package
↓ Integrated site
↓ Production environment

Then, as long as the source content changes, you can:

Plaintext
Source content changes
↓ All downstream states are invalidated

Regenerate all translations.

Redo all quality reviews.

Reseal the content package.

Review the integrated site again.

Re-verify the production environment.

This approach is easy to understand.

And it is very conservative.

Because it leaves almost no expired old results behind.

But the problem is also very obvious:

The workload is massively amplified.

A page with only a few lines changed could force the entire language version back into the workflow.

Adjusting a single term in the glossary that affects only two pages might also wipe out hundreds of already stable translation results.

From a safety perspective, it’s very simple.

From a long-term maintenance perspective, however, it’s hard to sustain.


The other extreme is also dangerous: keep using old results as long as they exist

Since the cost of invalidating everything is too high, the other extreme is to reuse as much as possible.

For example:

Plaintext
Old quality review record exists → reuse directly
Old integrated site PASS record exists → reuse directly
Old content package seal record exists → reuse directly

This is certainly fast.

But it ignores a core question:

Are these old results actually reviewing the current content?

If the candidate translation has been modified, the continued existence of an old quality review doesn’t mean it can still prove the new translation is an A.

If the content package has changed, the continued existence of an old integrated site PASS record doesn’t mean it reviewed the current set of pages.

Therefore:

“An old result exists” and “an old result is still valid” are two different things.

This is why v2 requires extensive identity tags, SHAs, scope checks, and currency checks.

The truly difficult part is finding the balance between these two extremes:

We can’t just clear everything at the slightest change, nor can we continue trusting old evidence just because it’s still around.


To determine which states should be invalidated, you must first understand dependencies

As I worked on this, I felt that invalidation logic is essentially maintaining a dependency graph.

The actual system is of course much more complex, but it can be initially understood simply as:

Plaintext
Source content
↓ Translation units
↓ Candidate translations
↓ Quality review
↓ All-A finalization
↓ Content package sealing
↓ Integrated site review
↓ Activation
↓ Preview and production environment

At the same time, the unified glossary also affects translation units.

Changes to the parser and content representation can also affect certain pages.

So after something changes, the real question isn’t:

“How many things are below it?”

But rather:

“Which downstream conclusions truly depend on this change?”

That is the scope that needs to be re-evaluated.


An identity change doesn’t necessarily mean semantic invalidation

This point is actually consistent with the conclusion in the previous glossary post.

SHA is well-suited for proving:

Whether two inputs are exactly the same.

But it cannot answer this on its own:

Whether this change actually invalidates existing conclusions.

For example, the glossary’s SHA changes from A to B.

This change might affect only two pages.

It might also affect a hundred pages.

It might even require no modifications to existing translations, because they already conform to the new terminology rules.

Similarly, a change in the parser’s implementation doesn’t mean all Markdown pages must be retranslated.

So in v2, it’s necessary to explicitly distinguish between two questions:

Plaintext
Has the input identity changed?

And:

Plaintext
Are the original linguistic or workflow conclusions actually affected?

The first question is suited for precise machine evaluation.

The second question determines which results need to be invalidated.


The translation unit is the first formal linguistic boundary

For Learn / Docs, I’ve written before:

An actual existing page is a formal TranslationUnit.

So when an issue occurs somewhere within a page, the machine can analyze down to a very fine scope.

For example:

Plaintext
Page
├── Context A → still compatible
├── Context B → still compatible
├── Context C → affected
└── Context D → still compatible

The final formal state is still:

Plaintext
Entire page → needs reprocessing

But this doesn’t mean:

The entire content package is affected.

And it certainly doesn’t mean:

The entire language version is affected.

This is the first step in layered scoping.

Internally, the machine can analyze the cause very finely.

The formal workflow then elevates the impact to the correct governance unit.


Revising one page shouldn’t invalidate other A grades

Suppose a content package has 49 pages.

48 of them have passed independent quality reviews and are all A.

Another page received a B.

Later, precise revisions are made to address the issues found in the review.

The simplest approach at this point is to rebuild the entire batch of candidate states and review all 49 pages again.

But this wastes the 48 previously trusted results.

A more reasonable approach is:

Plaintext
48 unchanged A's → continue to inherit
1 revised page → machine validation → independent re-review → A

Then establish a new all-A finalization.

Here, a precise invalidation has actually occurred:

Only the old linguistic judgment of the translation unit that was actually revised needs to be invalidated.

The other unchanged A’s should remain valid.

This is actually the other side of the same coin as the “reuse historical achievements” principle written about earlier.


The prerequisite for inheriting old review results isn’t “no one touched this file”

Of course, you also can’t simply assume:

I didn’t manually modify these 48 files, so they keep their A.

What actually needs to be proven is:

Plaintext
Candidate translation unchanged
+ Relevant inputs unchanged, or still compatible
+ Previous A still corresponds to this exact current translation

Only then can the old review result be inherited.

So “continued reuse” and “invalidation logic” are actually a pair of relationships:

Can prove still valid → continue to inherit
Cannot prove still valid → re-evaluate or treat as invalidated

This is why the system must adhere to:

When in doubt, treat as failure.

You can’t guess that an old result is still valid just to reduce workload.


Content package sealing is another dependency boundary

Once all translation units meet the formal requirements, they enter content package sealing.

Assume a content package has already been sealed.

Later, one of its pages is reopened and modified because an issue was found during the integrated site review.

Then:

Can the original content package seal still represent the current content package?

Obviously not.

Because it sealed the exact state prior to the modification.

So:

Plaintext
Translation unit changes
↓ Old content package seal is no longer the current valid state

After the revision, machine validation, independent re-review, and all-A finalization are all complete, a new successor content package seal is established.

There is an important distinction here:

The old seal record is not deleted.

It remains part of history.

It’s just that:

It no longer represents the current content package.

This is entirely consistent with the earlier notion that “history should be preserved, but it shouldn’t tie the hands of the current workflow.”


Integrated site review evaluates the “combined” result

Every single translation unit getting an A doesn’t guarantee the entire site will be correct once assembled.

That’s why v2 also includes an integrated site review.

It focuses on a higher level of issues, such as:

  • Whether multiple content packages are consistent when combined;
  • Whether navigation is logical;
  • Whether local content coverage is correct;
  • Whether links between pages are correct;
  • Whether the site shell and body text are coordinated;
  • Whether the complete site the user ultimately sees is sound.

Therefore, it depends on:

The exact site scope formed by a set of currently valid content package seals.

If this scope actually changes, the old integrated site review conclusion may no longer hold.

But here, you still can’t simply assume:

Plaintext
Any change anywhere
↓ Redo all integrated site reviews

What really needs to be determined is:

Whether this change falls within the scope covered by that review.


Finding a linguistic issue during integrated site review can’t bypass translation unit review

This is another very clear boundary established later in v2.

Suppose the integrated site reviewer discovers:

A page’s translation itself has an issue.

At this point, you can’t just:

Plaintext
Integrated site review finds issue
↓ Directly modify page
↓ Integrated site PASS

Because the integrated site review cannot replace the translation unit’s independent quality review.

What should happen is a return to the corresponding linguistic governance layer:

Plaintext
Integrated site finds linguistic issue
↓ Reopen affected translation unit
↓ Precise revision
↓ Machine validation
↓ Independent re-review
↓ All-A finalization
↓ Establish new content package seal
↓ Re-perform subsequent integrated site review

This keeps the dependency chain complete.

Although the issue was discovered during the integrated site phase.

The layer that truly holds linguistic authority is still the translation unit.

Therefore:

Whichever layer an issue belongs to, return to the layer with formal authority to fix it.


This prevents “higher-level reviews from secretly modifying lower-level facts”

Without this layering, a very dangerous situation can arise.

An integrated site reviewer finds a translation issue on a page.

Someone directly modifies the page.

Then the integrated site review PASSes.

On the surface, the page is fixed.

But internally, the following might have occurred:

  • The translation unit’s formal quality review still corresponds to the old translation;
  • The content package seal still corresponds to the old state;
  • But the integrated site record is already based on the new state.

The entire evidence chain begins to fracture.

In other words:

The user sees the new text, but the linguistic quality record is still attesting to the old text.

This kind of problem is very hard to detect on the surface.

So invalidation propagation isn’t just about:

“Making things expired.”

It is also responsible for ensuring:

After content is modified, all formal conclusions that depended on the old state are rebinded to the new successor state.


Not all integrated site failures are linguistic failures

On the other hand, you can’t treat all failures during the integrated site phase as translation quality failures.

For example:

Archive transfer errors.

Incomplete input files.

Currency check failures.

Data structure specification mismatches.

These issues are essentially:

Input gate failures.

The reviewer hasn’t even received a current site that can be formally reviewed yet.

So at this point, it shouldn’t be recorded as:

Plaintext
Linguistic review FAILED

Because the actual linguistic evaluation hasn’t even started yet.

A more reasonable approach is:

Plaintext
Input gate failure
↓ No state change produced
↓ Fix transfer or input
↓ Use new review-id
↓ Re-enter review

A very important point here is:

System failures and linguistic quality failures must be separated.

Otherwise, the history will be left with many “linguistic review failures” that never actually happened.


The activation phase also has its own currency requirements

Moving further into the activation phase, the dependencies change again.

Activation isn’t simply:

Turning on a switch after the integrated site PASSes.

It also needs to bind:

  • The current content package seal;
  • The current integrated site result;
  • The current formal authority state;
  • The exact prior_sha256.

One purpose of the activation pre-check is to reconfirm:

Whether what we are about to activate is still that exact set of states that was just reviewed.

If the prior_sha256 obtained during the pre-check no longer matches, it means a new state change occurred in the meantime.

The correct approach here is not:

“Everything passed before, so let’s just proceed.”

But rather:

Plaintext
prior SHA mismatch
↓ STOP
↓ Read current formal state
↓ Re-run pre-check

Because the old pre-check conclusion no longer corresponds to the current state.

This is also a form of invalidation logic.


CAS ensures the “state seen at decision time” hasn’t secretly changed

The prior SHA used in the activation phase is essentially similar to compare-and-swap, or CAS.

You can understand it as:

Plaintext
I saw state A during the pre-check
Only if the current state is still A when the change is actually executed
am I allowed to proceed with changing it to B

If by the time of actual execution it has become:

Plaintext
A'

Then the previous change authorization can no longer be used.

It solves a very practical problem:

Is the state I saw when making the decision still the same as the state when the change is actually executed?

If it isn’t, you should re-evaluate.

Rather than assuming no other operations changed it in between.


Preview and production environments prove a different kind of fact

Even after successful activation, it doesn’t mean the production environment is necessarily correct.

The final website also involves:

  • Page rendering;
  • Navigation;
  • URLs;
  • canonical;
  • Local content coverage;
  • 404s;
  • sitemap;
  • Actual browser behavior.

So preview validation and production environment validation remain independent stages.

The linguistic chain might be completely correct.

The integrated site might have already PASSed.

But the final page rendering could still go wrong.

Therefore:

Plaintext
Translation unit review PASS ≠ Integrated site review PASS
Integrated site review PASS ≠ Safe to enter production environment

Each layer proves different things.

So the invalidation conditions for each layer cannot be mixed together.


An upstream change doesn’t mean invalidation must propagate all the way down

A common misunderstanding arises here:

Since the system has dependencies, does an upstream change inevitably invalidate all downstream results all the way down?

No.

Dependencies primarily indicate:

Re-evaluation is needed.

Whether it actually invalidates anything depends on the change’s real impact.

For example, the glossary changes.

The system first recalculates compatibility.

But a certain page might find:

Plaintext
Still conforms to new terminology rules

Then its candidate translation and quality review don’t need to be invalidated.

Since this page’s formal result hasn’t changed, the actual content of the content package might not have changed either.

If the content package identity hasn’t changed either, there’s no reason for the integrated site to review everything again just because the glossary file was once modified.

The ideal propagation method should be:

Plaintext
Change occurs
↓ Evaluate real impact
↓ If formal conclusions haven't changed
↓ Stop further propagation

Rather than:

Plaintext
Change occurs
↓ All downstream states invalidated

This is actually a lot like an incremental build system

As I worked on this, I felt v2 was becoming more and more like an incremental build system.

Compilers usually don’t recompile everything just because an arbitrary file in the repository changed.

A mature build system determines:

Which targets actually depend on this input.

And then rebuilds only the affected parts.

A translation system is very similar.

It also has:

Plaintext
Glossary
Source content
Parser rules
Candidate translations
Quality review
Content packages
Integrated site
Activation

Each layer has its own dependencies.

Each layer has its own identity.

Each layer can also be invalidated by upstream changes.

The only difference is:

With natural language, it’s harder to determine whether “results are still equivalent” compared to standard build artifacts.

So the system needs more evidence to safely prove that old results can still be used.


The goal isn’t “to have as few invalidations as possible”

Here we also need to avoid going to the other extreme.

If you overly pursue:

The fewer states invalidated, the better

You’ll start trying to judge all old results as “still valid”.

This is very dangerous.

The real goal isn’t:

To minimize invalidations as much as possible.

But rather:

To determine invalidations as accurately as possible.

If it is truly affected, it should be invalidated.

If you can’t reliably prove it’s unaffected, it should also be treated as a failure.

Only when you can truly prove the old result still holds should it be inherited.

So:

Plaintext
Reducing duplicate work → ranks after ensuring correctness

“Don’t know if there’s an impact” is itself a state that requires stopping

This is particularly important in engineering.

Sometimes the system cannot confirm:

Whether a certain state change actually occurred.

Or:

Whether a piece of evidence still corresponds to the current state.

The most dangerous thing to do here is guess:

Probably hasn’t changed.

And then continue.

A safer rule should be:

Plaintext
State cannot be confirmed
↓ STOP
↓ Diagnose first

Especially when the machine explicitly outputs:

Plaintext
mutation=unknown

In this situation, you absolutely cannot blindly re-execute.

Because you don’t even know if the previous round already produced a partial state change.

Executing again might create a second change.

So:

Stopping when uncertain is more reliable than guessing and continuing.


Determining if old results are invalidated is also part of failure recovery capability

I previously understood invalidation logic more as:

Preventing the system from using old results.

Later, I increasingly felt that it’s actually part of the failure recovery architecture.

Because after a workflow interruption, what the system really needs to answer is:

Plaintext
Which results have already been reliably completed?
Which are just missing subsequent records?
Which results are truly invalidated?
Which content is actually affected by this change?

If it can’t accurately answer these questions, the simplest recovery method is reduced to:

Redo everything.

The more accurate the invalidation and inheritance logic, the more the recovery process can achieve:

Plaintext
Retain reliably completed results
↓ Only continue processing missing, invalidated, and truly affected parts

So precise invalidation and fast recovery are actually interdependent.


Good invalidation logic makes the entire system increasingly incremental

In the long run, I hope the entire workflow increasingly approaches:

Plaintext
New work
+ Real changes
+ Real failures

Rather than:

Plaintext
Reprocessing all historical work every time

For example:

Adding a new content package:

Primarily process the new content.

Glossary adjustment:

Primarily process the pages actually affected.

Integrated site finds a linguistic issue:

Primarily reopen the corresponding translation unit.

A content package seal changes:

Re-evaluate the integrated site results that depend on it.

Activation phase detects concurrent state changes:

Re-run the pre-check, rather than continuing to use the old prior_sha256.

This is my understanding of an incremental workflow.


Historical reuse, precise invalidation, and fast recovery are actually the same thing

Previous posts covered separately:

  • Why history is worth preserving;
  • Why history can’t be endlessly rebuilt;
  • Why glossary changes can’t be entirely redone.

By this post, I feel these issues have gradually converged into the same goal:

Letting the system know what is already done, what is still valid, and what has actually changed.

If we can reliably answer:

Plaintext
Is it already done?
Is it still valid?
Is it actually affected?

Then:

History can be reused.

Failures can be recovered incrementally.

Revisions can remain precise.

The integrated site can review only the truly current valid scope.

The production environment can also avoid relying on invalidated old states.

So dependency and invalidation propagation isn’t an isolated minor feature.

It is actually one of the foundations that allows the entire long-term workflow to scale stably.


The resulting principles

Looking back now, the design of dependencies and invalidation propagation in v2 can be distilled into a few rules.

First:

An identity change doesn’t automatically mean semantic conclusions are invalidated.

Second:

Only formal states that truly depend on this change re-enter the workflow.

Third:

Trusted results that can be proven still valid continue to be inherited.

Fourth:

After a modification, formal conclusions that originally depended on the old state must be rebound to the new successor state.

Fifth:

Whichever layer an issue belongs to, return to the layer with formal authority to fix it.

Sixth:

When you can’t prove it’s unaffected, treat it as a failure. Don’t guess.

Seventh:

Failure recovery prioritizes processing missing, invalidated, and truly affected parts, rather than defaulting to redoing everything.

The ultimate goal isn’t:

To have as few invalidations as possible.

Nor is it:

To make the workflow safer by making it more complex.

But rather:

To ensure every change incurs only the engineering cost it truly should.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系