This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-website.git


The following commit(s) were added to refs/heads/master by this push:
     new e142b9d4dc4a [i18n](docs) Maintain only future release notes (#4527)
e142b9d4dc4a is described below

commit e142b9d4dc4a94fab0a8dc4d7a3494ce0eab8755
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Tue Oct 27 02:21:42 2026 +0000

    [i18n](docs) Maintain only future release notes (#4527)
    
    Scheduled release-note maintenance currently lacks a fixed historical
    boundary. Enable RELEASES for Japanese, Korean and Russian only for
    genuinely new native sources after commit `b207a93551a3`, while
    preserving the 83 historical notes, existing targets and their
    publication/fallback behavior.
    
    The shared policy follows Git lineage, excludes historical identities
    and paths from required/pending work, and sends ambiguous delete/add
    histories to manual reconciliation. Inventory, audit, planning and the
    generator enforce the same policy; forced historical tasks fail before
    model calls. The existing ownership checks, active-PR reservations,
    whole-run lock, batch limits and trusted-master checks remain in place.
    Audit workflows fetch history for cutoff and lineage checks.
    
    Add a localized Release Notes search context for current real
    translations. Add a small five-language native fixture covering
    historical/current/missing notes, policy selection, reserved PRs, mock
    generation, fallback SEO and actual search workers. CI keeps
    representative compilation and validates the fixed policy offline.
    
    Validation so far:
    - i18n tests: 320 passed, one skipped.
    - Workflow security tests: 22 passed.
    - Real release-note fixture: 15 pages across five languages, three
    managed-language search workers, correct fallback exclusion and
    mock-only generation.
    - Configuration validation passes for ja/ko/ru; `git diff --check`
    passes.
    - Three-language full native audits are running and will be recorded
    before this draft is made ready.
    
    No historical release-note prose, translated target, translation state,
    blog body or Chinese automation is changed. No real future release note
    was manufactured and no real model call was needed for this
    implementation. The first real future note's
    generation/merge/publication remains an explicit maintenance
    observation.
    
    ---------
    
    Co-authored-by: morningman <[email protected]>
---
 .github/workflows/build-check.yml                  |   6 +-
 .github/workflows/japanese-coverage-audit.yml      |   5 +-
 .github/workflows/korean-coverage-audit.yml        |   5 +-
 .github/workflows/russian-coverage-audit.yml       |   4 +-
 docusaurus.config.js                               |   4 +-
 scripts/i18n/__tests__/expanded-scope.test.cjs     |   9 +-
 scripts/i18n/__tests__/multi-locale.test.cjs       |   2 +-
 scripts/i18n/__tests__/release-policy.test.cjs     | 154 ++++++
 scripts/i18n/__tests__/search-contexts.test.cjs    |   2 +-
 .../i18n/__tests__/search-verification.test.cjs    |   6 +-
 scripts/i18n/audit-coverage.cjs                    |  50 +-
 scripts/i18n/build-scope.cjs                       |   2 +-
 scripts/i18n/check-configuration.cjs               |  17 +-
 scripts/i18n/ci-plan.cjs                           |   2 +-
 scripts/i18n/inventory.cjs                         |  19 +-
 scripts/i18n/master-drift.cjs                      |   2 +-
 scripts/i18n/plan-translations.cjs                 |   9 +-
 scripts/i18n/release-policy.cjs                    | 131 +++++
 scripts/i18n/search-plugin.cjs                     |   7 +-
 scripts/i18n/translation-engine.cjs                |   9 +
 scripts/i18n/verify-release-policy-fixture.cjs     | 160 ++++++
 scripts/i18n/workflow-scan.cjs                     |   3 +-
 translations/README.md                             |  64 ++-
 translations/config.json                           |   9 +-
 translations/release-notes-baseline.json           | 594 +++++++++++++++++++++
 translations/scope/ja.json                         |   9 +-
 translations/scope/ko.json                         |   9 +-
 translations/scope/ru.json                         |   9 +-
 28 files changed, 1248 insertions(+), 54 deletions(-)

diff --git a/.github/workflows/build-check.yml 
b/.github/workflows/build-check.yml
index bd85273539a6..67caefd37d92 100644
--- a/.github/workflows/build-check.yml
+++ b/.github/workflows/build-check.yml
@@ -116,7 +116,7 @@ jobs:
             - name: Validate manual content and shared messages
               if: steps.detect.outputs.validation_mode != 'skip'
               run: |
-                  node scripts/i18n/check-configuration.cjs --all
+                  node scripts/i18n/check-configuration.cjs --all 
--release-history
                   node scripts/i18n/manual-report.cjs
                   node scripts/i18n/check-blog-translations.cjs
 
@@ -140,6 +140,7 @@ jobs:
               run: |
                   node scripts/i18n/verify-content-fixture.cjs
                   node scripts/i18n/verify-blog-fixture.cjs
+                  node scripts/i18n/verify-release-policy-fixture.cjs
 
             - name: Workflow security boundary tests
               run: PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s 
.github/scripts -p 'test_*.py' -v
@@ -185,6 +186,9 @@ jobs:
             - name: Verify global language fixture in Chromium
               if: steps.detect.outputs.content_fixture == 'true'
               run: node scripts/i18n/verify-global-language.cjs --fixture 
--site .i18n-build/content-fixture/build --manifests 
.i18n-build/content-fixture/.i18n-build/content-manifests --output 
.i18n-build/browser-global-language-fixture.json
+            - name: Verify release-note policy fixture in Chromium
+              if: steps.detect.outputs.content_fixture == 'true'
+              run: node scripts/i18n/verify-release-policy-fixture.cjs 
--verify-existing --browser
 
             - name: Build
               env:
diff --git a/.github/workflows/japanese-coverage-audit.yml 
b/.github/workflows/japanese-coverage-audit.yml
index 64285b6b23ae..5418591c2e68 100644
--- a/.github/workflows/japanese-coverage-audit.yml
+++ b/.github/workflows/japanese-coverage-audit.yml
@@ -22,8 +22,9 @@ jobs:
     steps:
       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
         with:
-          # The audit only reads HEAD; its git comparisons never need history.
-          fetch-depth: 1
+          # Release-note eligibility follows the fixed cutoff and Git lineage.
+          fetch-depth: 0
+          filter: blob:none
           persist-credentials: false
       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
diff --git a/.github/workflows/korean-coverage-audit.yml 
b/.github/workflows/korean-coverage-audit.yml
index 6ffd588aa558..7c0e8621df86 100644
--- a/.github/workflows/korean-coverage-audit.yml
+++ b/.github/workflows/korean-coverage-audit.yml
@@ -22,8 +22,9 @@ jobs:
     steps:
       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
         with:
-          # The audit only reads HEAD; its git comparisons never need history.
-          fetch-depth: 1
+          # Release-note eligibility follows the fixed cutoff and Git lineage.
+          fetch-depth: 0
+          filter: blob:none
           persist-credentials: false
       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
diff --git a/.github/workflows/russian-coverage-audit.yml 
b/.github/workflows/russian-coverage-audit.yml
index 80332a295c27..add255077f76 100644
--- a/.github/workflows/russian-coverage-audit.yml
+++ b/.github/workflows/russian-coverage-audit.yml
@@ -22,7 +22,9 @@ jobs:
     steps:
       - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
         with:
-          fetch-depth: 1
+          # Release-note eligibility follows the fixed cutoff and Git lineage.
+          fetch-depth: 0
+          filter: blob:none
           persist-credentials: false
       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 9fc1ed4e1d7a..f47a974508f9 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -556,13 +556,13 @@ const config = {
                 highlightSearchTermsOnTargetPage: true,
                 // indexPages: true,
                 indexDocs: true,
-                docsRouteBasePath: ['docs', 'course', 'zh-CN/docs', 
'zh-CN/course'],
+                docsRouteBasePath: ['docs', 'course', 'releases', 
'zh-CN/docs', 'zh-CN/course', 'zh-CN/releases'],
                 indexBlog: false,
                 explicitSearchResultPath: true,
                 searchBarShortcut: true,
                 searchBarShortcutHint: true,
                 searchResultLimits: 100,
-                searchContextByPaths: ['docs', 'course'],
+                searchContextByPaths: ['docs', 'course', { path: 'releases', 
label: { en: 'Release Notes', 'zh-CN': '发行说明' } }],
                 useAllContextsWithNoSearchContext: false,
                 ignoreFiles: [/^docs\/(?:[^/]+\/)?key-features\//],
             },
diff --git a/scripts/i18n/__tests__/expanded-scope.test.cjs 
b/scripts/i18n/__tests__/expanded-scope.test.cjs
index 41dcfd127237..b5b4a968ba88 100644
--- a/scripts/i18n/__tests__/expanded-scope.test.cjs
+++ b/scripts/i18n/__tests__/expanded-scope.test.cjs
@@ -90,6 +90,13 @@ function fixture(t) {
     const root = fs.mkdtempSync(path.join(os.tmpdir(), 
'expanded-translation-'));
     t.after(() => fs.rmSync(root, {recursive: true, force: true}));
     const write = (file, text) => {fs.mkdirSync(path.dirname(path.join(root, 
file)), {recursive: true}); fs.writeFileSync(path.join(root, file), text);};
+    const git = (...args) => execFileSync('git', args, {cwd: root, encoding: 
'utf8', stdio: ['ignore', 'pipe', 'pipe']}).trim();
+    git('init', '-q'); git('config', 'user.name', 'Fixture'); git('config', 
'user.email', '[email protected]');
+    write('fixture-baseline.txt', 'Before release-note registration.\n');
+    git('add', '.'); git('commit', '-qm', 'Release-note cutoff');
+    const cutoff = git('rev-parse', 'HEAD');
+    write('translations/release-notes-baseline.json', 
JSON.stringify({schemaVersion:1, sourceCommit:cutoff,
+        pluginId:'releases', version:'current', sourceRoot:'releasenotes', 
documents:[], sourceFiles:[], historicalPaths:[]}));
     fs.symlinkSync(path.join(repo, 'node_modules'), path.join(root, 
'node_modules'));
     write('translations/config.json', read('translations/config.json'));
     for (const locale of ['ja', 'ko', 'ru']) {
@@ -116,8 +123,6 @@ plugins:[require(${JSON.stringify(path.join(repo, 
'scripts/i18n/content-plugin.c
 [require(${JSON.stringify(path.join(repo, 
'scripts/i18n/docs-plugin.cjs'))}),{id:'course',path:'course',routeBasePath:'course',sidebarPath:false}],
 [require(${JSON.stringify(path.join(repo, 
'scripts/i18n/docs-plugin.cjs'))}),{id:'releases',path:'releasenotes',routeBasePath:'releases',sidebarPath:false}]],
 presets:[['classic',{docs:false,blog:false}]]};\n`);
-    const git = (...args) => execFileSync('git', args, {cwd: root, encoding: 
'utf8', stdio: ['ignore', 'pipe', 'pipe']}).trim();
-    git('init', '-q'); git('config', 'user.name', 'Fixture'); git('config', 
'user.email', '[email protected]');
     const commit = () => {git('add', '.'); git('commit', '-qm', 'fixture'); 
return git('rev-parse', 'HEAD');};
     return {root, write, git, commit};
 }
diff --git a/scripts/i18n/__tests__/multi-locale.test.cjs 
b/scripts/i18n/__tests__/multi-locale.test.cjs
index bfa77a3a7d80..7c0e1ea8d6c4 100644
--- a/scripts/i18n/__tests__/multi-locale.test.cjs
+++ b/scripts/i18n/__tests__/multi-locale.test.cjs
@@ -211,7 +211,7 @@ test('inventory CLI snapshots committed page records for 
the selected locale', a
             {id: 'COMMUNITY', pluginId: 'community', version: 'current', 
sourceRoot: 'community'},
         ], retainEnglish: []}));
     // Exercise the real CLI boundary without adding a production root 
override.
-    for (const file of ['inventory.cjs', 'state.cjs', 'source-groups.cjs', 
'code-prose.cjs', 'docs-language-policy.cjs'])
+    for (const file of ['inventory.cjs', 'state.cjs', 'source-groups.cjs', 
'code-prose.cjs', 'docs-language-policy.cjs', 'release-policy.cjs'])
         write(`scripts/i18n/${file}`, fs.readFileSync(path.join(repoRoot, 
'scripts/i18n', file), 'utf8'));
     const git = (...args) => execFileSync('git', args, {cwd: root, encoding: 
'utf8', stdio: ['ignore', 'pipe', 'pipe']});
     git('init', '-q');
diff --git a/scripts/i18n/__tests__/release-policy.test.cjs 
b/scripts/i18n/__tests__/release-policy.test.cjs
new file mode 100644
index 000000000000..5c63aa4f1693
--- /dev/null
+++ b/scripts/i18n/__tests__/release-policy.test.cjs
@@ -0,0 +1,154 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const {execFileSync} = require('node:child_process');
+const {hash, identity} = require('../state.cjs');
+const {releasePolicy, loadBaseline} = require('../release-policy.cjs');
+const {checkedTask} = require('../translation-engine.cjs');
+const {prepare} = require('../translate-docs.cjs');
+const {plan} = require('../plan-translations.cjs');
+const {batchWide} = require('../master-drift.cjs');
+const {policy: ciPolicy} = require('../ci-plan.cjs');
+const {sidebarAudit, hasPending} = require('../audit-coverage.cjs');
+
+function fixture(t) {
+    const root = fs.mkdtempSync(path.join(os.tmpdir(), 
'doris-release-policy-'));
+    t.after(() => fs.rmSync(root, {recursive:true,force:true}));
+    const git = (...args) => execFileSync('git', args, 
{cwd:root,encoding:'utf8',stdio:['ignore','pipe','pipe']}).trim();
+    const write = (name, text) => 
{fs.mkdirSync(path.dirname(path.join(root,name)),{recursive:true});fs.writeFileSync(path.join(root,name),text);};
+    const commit = () => {git('add','.');git('commit','-qm','Fixture 
change');return git('rev-parse','HEAD');};
+    
git('init','-q');git('config','user.name','Fixture');git('config','user.email','[email protected]');
+    write('releasenotes/legacy.md','---\ntitle: Release 1.0\n---\n\nHistorical 
release details.\n');
+    const earlier = commit();
+    git('mv','releasenotes/legacy.md','releasenotes/history.md');
+    write('releasenotes/unregistered.md','An existing unregistered release 
note.\n');
+    const cutoff = commit();
+    const group = 
{id:'RELEASES',pluginId:'releases',version:'current',sourceRoot:'releasenotes',
+        
sourcePolicy:{mode:'new-only',baseline:'translations/release-notes-baseline.json'}};
+    const baseline = 
{schemaVersion:1,sourceCommit:cutoff,pluginId:'releases',version:'current',sourceRoot:'releasenotes',
+        
documents:[{docId:'history',sourcePath:'releasenotes/history.md',sourceHash:hash(fs.readFileSync(path.join(root,'releasenotes/history.md'),'utf8'))}],
+        sourceFiles:['releasenotes/history.md','releasenotes/unregistered.md'],
+        
historicalPaths:['releasenotes/history.md','releasenotes/legacy.md','releasenotes/unregistered.md']};
+    const saveBaseline = () => 
write(group.sourcePolicy.baseline,JSON.stringify(baseline));
+    saveBaseline();commit();
+    const scope = 
{schemaVersion:1,locale:'ja',maxBatchPages:20,automaticGroups:['RELEASES'],groups:[group],retainEnglish:[]};
+    const page = (file, docId = path.basename(file,'.md')) => 
({pluginId:'releases',version:'current',locale:'ja',docId,
+        
sourcePath:file,sourceHash:hash(fs.readFileSync(path.join(root,file),'utf8')),
+        
targetPath:'i18n/ja/docusaurus-plugin-content-docs-releases/current/'+file.slice('releasenotes/'.length)});
+    const selectedPlan = task => 
({schemaVersion:1,locale:'ja',sourceCommit:git('rev-parse','HEAD'),selected:['selected'],
+        tasks:[{...task,id:'selected',kind:'translate_new'}]});
+    return 
{root,git,write,commit,cutoff,earlier,group,scope,baseline,saveBaseline,page,selectedPlan};
+}
+
+test('the cutoff excludes registered, unregistered and previously renamed 
historical sources', t => {
+    const f=fixture(t),p=releasePolicy(f.root,f.group);
+    
assert.equal(p.classify(f.page('releasenotes/history.md')).kind,'historical_excluded');
+    
assert.equal(p.classify(f.page('releasenotes/unregistered.md')).kind,'historical_excluded');
+    f.write('releasenotes/legacy.md','A historical path registered again with 
different text.\n');f.commit();
+    
assert.equal(releasePolicy(f.root,f.group).classify(f.page('releasenotes/legacy.md')).kind,'historical_excluded');
+});
+
+test('a genuinely new note and its later updates remain eligible', t => {
+    const f=fixture(t);f.write('releasenotes/release-2.md','---\ntitle: 
Release 2.0\n---\n\nA genuinely new release.\n');f.commit();
+    const task=f.page('releasenotes/release-2.md');
+    assert.equal(releasePolicy(f.root,f.group).classify(task).kind,'eligible');
+    
assert.equal(checkedTask(f.selectedPlan(task),'selected',f.scope,f.root).source.includes('new
 release'),true);
+    f.write('releasenotes/release-2.md','---\ntitle: Release 
2.0\n---\n\nUpdated new release.\n');f.commit();
+    
assert.equal(releasePolicy(f.root,f.group).classify(f.page('releasenotes/release-2.md')).kind,'eligible');
+});
+
+test('historical moves, later edits and re-registered IDs do not become new 
notes', t => {
+    const 
f=fixture(t);f.git('mv','releasenotes/history.md','releasenotes/moved.md');f.commit();
+    f.write('releasenotes/moved.md','---\ntitle: Historical 
update\n---\n\nChanged after moving.\n');f.commit();
+    
assert.equal(releasePolicy(f.root,f.group).classify(f.page('releasenotes/moved.md')).kind,'historical_excluded');
+    f.write('releasenotes/registered-again.md','An old identity under a new 
source path.\n');f.commit();
+    
assert.equal(releasePolicy(f.root,f.group).classify(f.page('releasenotes/registered-again.md','history')).kind,'historical_excluded');
+});
+
+test('exact historical copies and ambiguous delete/add changes cannot trigger 
generation', t => {
+    const 
f=fixture(t);f.write('releasenotes/copy.md',fs.readFileSync(path.join(f.root,'releasenotes/history.md'),'utf8'));f.commit();
+    
assert.notEqual(releasePolicy(f.root,f.group).classify(f.page('releasenotes/copy.md')).kind,'eligible');
+    
f.git('rm','releasenotes/history.md');f.write('releasenotes/uncertain.md','Completely
 rewritten source with an unidentified historical deletion.\n');f.commit();
+    
assert.equal(releasePolicy(f.root,f.group).classify(f.page('releasenotes/uncertain.md')).kind,'policy_review');
+});
+
+test('a forced historical task is rejected before any adapter/model call', 
async t => {
+    const f=fixture(t),task=f.page('releasenotes/history.md');let calls=0;
+    await 
assert.rejects(prepare({root:f.root,plan:f.selectedPlan(task),taskId:'selected',scope:f.scope,
+        state:{data:{pages:[]}},adapter:{translate(){calls++;throw Error('must 
not run')}},glossary:{},target:{locale:'ja'}}),
+    /Release Notes task excluded/);
+    assert.equal(calls,0);
+});
+
+test('generation rejects an uncommitted policy change and an absent policy', t 
=> {
+    const f=fixture(t);f.write('releasenotes/new.md','A new 
release.\n');f.commit();
+    const task=f.page('releasenotes/new.md'),p=f.selectedPlan(task);
+    f.write(f.group.sourcePolicy.baseline,JSON.stringify(f.baseline)+'\n');
+    assert.throws(()=>checkedTask(p,'selected',f.scope,f.root),/policy changed 
since planning/);
+    const scope={...f.scope,groups:[{...f.group,sourcePolicy:undefined}]};
+    assert.throws(()=>checkedTask(p,'selected',scope,f.root),/requires a 
new-only source policy/);
+});
+
+test('incomplete, changed or non-ancestor cutoff evidence fails closed', t => {
+    const f=fixture(t);
+    assert.throws(()=>releasePolicy(f.root,f.group,f.earlier),/baseline must 
be an ancestor/);
+    
f.baseline.historicalPaths=f.baseline.historicalPaths.filter(file=>!file.endsWith('/legacy.md'));f.saveBaseline();
+    assert.throws(()=>releasePolicy(f.root,f.group),/omits historical path 
identities/);
+    
f.baseline.historicalPaths.push('releasenotes/legacy.md');f.baseline.documents[0].sourceHash='0'.repeat(64);f.saveBaseline();
+    assert.throws(()=>releasePolicy(f.root,f.group),/hash mismatch/);
+    
assert.throws(()=>loadBaseline(f.root,{...f.group,sourcePolicy:{mode:'new-only',baseline:'../outside.json'}}),/Invalid
 release-note source policy/);
+});
+
+test('historical targets stay out of pending, deletion tasks and duplicate 
generation', async t => {
+    const f=fixture(t);f.write('releasenotes/new.md','New release 
details.\n');const sourceCommit=f.commit();
+    const 
old=f.page('releasenotes/history.md'),fresh=f.page('releasenotes/new.md'),eligibility=releasePolicy(f.root,f.group).classify(old);
+    const 
report={schemaVersion:1,sourceCommit,baselineCommit:f.cutoff,reports:[],
+        policyExclusions:[{...old,...eligibility}],
+        
groups:[{...f.group,unregistered:[],entries:[{...old,state:'historical_excluded'},{...fresh,state:'untranslated'}]}]};
+    const args={report,scope:f.scope,records:new 
Map([[identity(old),old]]),baseline:[old],
+        
readCurrent:file=>fs.readFileSync(path.join(f.root,file),'utf8'),readAt:()=>null};
+    const result=await plan(args);
+    
assert.equal(result.counts.required,1);assert.equal(result.automaticCounts.pending,1);
+    
assert.equal(result.tasks.length,1);assert.equal(result.tasks[0].sourcePath,fresh.sourcePath);
+    assert.equal(result.selected.length,1);
+    const reserved=await 
plan({...args,active:{schemaVersion:1,candidates:[{...fresh,pr:'https://github.com/apache/doris-website/pull/1'}]}});
+    
assert.equal(reserved.selected.length,0);assert.equal(reserved.counts.activeCandidates,1);
+    const empty=await 
plan({...args,report:{...report,groups:[{...f.group,unregistered:[],entries:[{...old,state:'historical_excluded'}]}]},
+        readCurrent:()=>{throw Error('An excluded historical source must not 
be read for translation')}});
+    
assert.equal(empty.automaticCounts.required,0);assert.equal(empty.selected.length,0);assert.deepEqual(empty.tasks,[]);
+});
+
+test('cutoff changes invalidate a batch and receive representative CI with 
configuration validation', () => {
+    assert(batchWide('ja')('translations/release-notes-baseline.json'));
+    const p=ciPolicy(['translations/release-notes-baseline.json']);
+    
assert.equal(p.mode,'sample');assert.equal(p.runTranslationTests,true);assert.equal(p.publicationAllowed,false);
+});
+
+test('baseline symlinks cannot read files outside the reviewed source tree', t 
=> {
+    const 
f=fixture(t),baseline=path.join(f.root,f.group.sourcePolicy.baseline);
+    fs.renameSync(baseline,baseline+'.saved');
+    fs.symlinkSync(baseline+'.saved',baseline);
+    assert.throws(()=>loadBaseline(f.root,f.group),/Symlink in Release Notes 
baseline path/);
+});
+
+test('historical navigation stays in the census without becoming an automatic 
label backlog', t => {
+    const f=fixture(t);f.write('releasenotes/new.md','A genuinely new 
release.\n');f.commit();
+    const policy=releasePolicy(f.root,f.group);
+    const 
native={pages:[f.page('releasenotes/history.md'),f.page('releasenotes/new.md')],
+        labels:[{pluginId:'releases',files:[{path:'current',content:{
+            'sidebar.releases.doc.history':{message:'Release 1.0'},
+            'sidebar.releases.doc.new':{message:'Release 2.0'},
+            'sidebar.releases.category.Core':{message:'Doris Core'},
+        }}]}]};
+    const rows=sidebarAudit(native,f.root,f.scope,new 
Map([['RELEASES',policy]]));
+    
assert.equal(rows.find(row=>row.key.endsWith('.history')).maintenance,'historical_excluded');
+    
assert.equal(rows.find(row=>row.key.endsWith('.new')).maintenance,'eligible');
+    
assert.equal(rows.find(row=>row.key.endsWith('.Core')).maintenance,'retained_navigation');
+    const 
report={groups:[{counts:{pending:83}}],labels:{missing:83,untranslated:10},
+        
automaticCounts:{pending:0},automaticLabels:{missing:0,untranslated:0}};
+    assert.equal(hasPending(report),false);
+    assert.equal(hasPending({...report,automaticCounts:{pending:1}}),true);
+    assert.equal(hasPending({...report,automaticLabels:{missing:1}}),true);
+});
diff --git a/scripts/i18n/__tests__/search-contexts.test.cjs 
b/scripts/i18n/__tests__/search-contexts.test.cjs
index f241fe45e4e0..a8716e86550a 100644
--- a/scripts/i18n/__tests__/search-contexts.test.cjs
+++ b/scripts/i18n/__tests__/search-contexts.test.cjs
@@ -95,7 +95,7 @@ test('the localized root index holds only documents outside 
every context', () =
     const all = [[doc('/ja/docs/dev/a/', 1), doc('/ja/docs/4.x/a/', 2), 
doc('/ja/community/a/', 3)], []];
     const selected = new Map([['/ja/docs/dev/a', 'docs/dev'], 
['/ja/docs/4.x/a', 'docs/4.x'], ['/ja/community/a', 'community']]);
     const groups = indexGroups(all, selected);
-    assert.deepEqual([...groups.keys()], ['docs/dev', 'docs/4.x', 'community', 
'course', '']);
+    assert.deepEqual([...groups.keys()], ['docs/dev', 'docs/4.x', 'community', 
'course', 'releases', '']);
     assert.deepEqual(groups.get('docs/4.x'), [[doc('/ja/docs/4.x/a/', 2)], 
[]]);
     assert.deepEqual(groups.get(''), [[], []]);
     assert.deepEqual(indexGroups([[doc('/ja/other/', 4)]], selected).get(''), 
[[doc('/ja/other/', 4)]]);
diff --git a/scripts/i18n/__tests__/search-verification.test.cjs 
b/scripts/i18n/__tests__/search-verification.test.cjs
index fb2085ecb076..79b88cb55c8b 100644
--- a/scripts/i18n/__tests__/search-verification.test.cjs
+++ b/scripts/i18n/__tests__/search-verification.test.cjs
@@ -18,20 +18,22 @@ function fixture(t) {
         {pluginId:'default',version:'4.x',permalink:'/ko/docs/4.x/current/'},
         
{pluginId:'community',version:'current',permalink:'/ko/community/current/'},
         
{pluginId:'course',version:'current',permalink:'/ko/course/101/lesson/'},
+        
{pluginId:'releases',version:'current',permalink:'/ko/releases/v5.0/release-5.0.0/'},
     ].map(page => 
({...page,locale:'ko',status:'current',indexable:true,useTarget:true}));
     const index = url => [{documents:[{u:url}]}];
-    write('ko-search-summary.json', 
{locale:'ko',indexedPages:4,contexts:{'docs/dev':1,'docs/4.x':1,community:1,course:1}});
+    write('ko-search-summary.json', 
{locale:'ko',indexedPages:5,contexts:{'docs/dev':1,'docs/4.x':1,community:1,course:1,releases:1}});
     write('search-index.json', [{documents:[]}]);
     write('search-index-docs-dev.json', index(pages[0].permalink));
     write('search-index-docs-4.x.json', index(pages[1].permalink));
     write('search-index-community.json', index(pages[2].permalink));
     write('search-index-course.json', index(pages[3].permalink));
+    write('search-index-releases.json', index(pages[4].permalink));
     return {site,write,pages,index};
 }
 
 test('Korean index verification agrees with current native targets and an 
empty root union', t => {
     const {site,pages} = fixture(t);
-    
assert.equal(checkIndexes({site,locale:'ko',manifest:{pages}}).summary.indexedPages,
 4);
+    
assert.equal(checkIndexes({site,locale:'ko',manifest:{pages}}).summary.indexedPages,
 5);
 });
 
 test('fallback, stale, unlisted and unverified targets cannot enter the Korean 
index', t => {
diff --git a/scripts/i18n/audit-coverage.cjs b/scripts/i18n/audit-coverage.cjs
index b19bab45d1b5..b5410c7c8233 100644
--- a/scripts/i18n/audit-coverage.cjs
+++ b/scripts/i18n/audit-coverage.cjs
@@ -6,6 +6,7 @@ const path = require('node:path');
 const {execFileSync, spawnSync} = require('node:child_process');
 const {hash, identity, inventoryBaseline, loadRecordsAt, pipelineLocale} = 
require('./state.cjs');
 const {collect, validateScope} = require('./inventory.cjs');
+const {policiesFor, docKey} = require('./release-policy.cjs');
 
 const keyFor = locale => page => identity({...page,locale});
 // One `git show` per page is several thousand processes for a full-scope
@@ -42,7 +43,7 @@ function baselineEntries(root) {
         return meta.entryFiles.flatMap(shard => 
JSON.parse(fs.readFileSync(path.join(root,shard),'utf8')).entries);
     });
 }
-function sidebarAudit(native,root,scope) {
+function sidebarAudit(native,root,scope,policies = new Map()) {
     const result = [];
     for (const group of scope.groups) {
         if (group.kind) continue; // Source modules/partials have no native 
sidebar.
@@ -62,8 +63,16 @@ function sidebarAudit(native,root,scope) {
                 throw new Error(`Invalid retained sidebar label ${name}`);
             const state = !translated[name]?.message ? 'missing' : 
translated[name].message === value.message
                 ? exception ? 'retained_technical' : 'untranslated' : 
'translated';
+            const policy = policies.get(group.id);
+            const docId = name.match(/\.doc\.(.+)$/)?.[1];
+            const page = policy && native.pages.find(page => page.pluginId === 
group.pluginId
+                && page.version === group.version && page.docId === docId);
+            // Existing release categories/version labels remain manual native
+            // navigation; they do not become a historical translation backlog.
+            const maintenance = policy ? page ? policy.classify(page).kind : 
'retained_navigation' : undefined;
             result.push({group:group.id, 
pluginId:group.pluginId,version:group.version,key:name,
-                source:value.message,translation:translated[name]?.message || 
null,state});
+                source:value.message,translation:translated[name]?.message || 
null,state,
+                ...(maintenance && {maintenance})});
         }
         for (const exception of retained.filter(entry => entry.pluginId === 
group.pluginId && entry.version === group.version))
             assert(sourceKeys.some(([name]) => name === exception.key),`Unused 
sidebar exception ${exception.key}`);
@@ -82,6 +91,7 @@ async function audit({root,sourceRef,baseline,native}) {
     const scope = 
JSON.parse(fs.readFileSync(path.join(root,`translations/scope/${locale}.json`),'utf8'));
     validateScope(scope, locale);
     const committedRecords = loadRecordsAt(root, locale, commit);
+    const policies = policiesFor(root, scope, commit);
     native ||= await collect(root);
     const old = new Map(baselineEntries(baseline).map(page => 
[key(page),page]));
     const current = new Set(), groups = [];
@@ -105,7 +115,9 @@ async function audit({root,sourceRef,baseline,native}) {
             assert(!seen.has(page.englishPath),`Duplicate native source 
${page.englishPath}`);
             seen.add(page.englishPath); current.add(key(page));
             const previous = old.get(key(page));
-            const disposition = page.draft ? 'draft' : 
scope.retainEnglish.some(entry => key(entry) === key(page)) ? 'retain_english' 
: 'translate';
+            const eligibility = policies.get(group.id)?.classify(page);
+            const disposition = page.draft ? 'draft' : 
scope.retainEnglish.some(entry => key(entry) === key(page)) ? 'retain_english'
+                : eligibility && eligibility.kind !== 'eligible' ? 
eligibility.kind : 'translate';
             const mergedCurrent = page.status === 'current' && 
page.reviewStatus !== 'unverified' && page.useTarget
                     && targetHashes.get(page.targetPath) !== null
                     && targetHashes.get(page.targetPath) === page.targetHash
@@ -115,30 +127,38 @@ async function audit({root,sourceRef,baseline,native}) {
             
entries.push({pluginId:page.pluginId,version:page.version,docId:page.docId,sourcePath:page.sourcePath,
                 
targetPath:page.targetPath,sourceHash:page.sourceHash,targetHash:page.targetHash,
                 
sourceCommit:commit,permalink:page.permalink,reviewStatus:page.reviewStatus,state,
+                ...(eligibility && {eligibility, contentState: page.status}),
                 change:!previous ? 'new' : previous.sourcePath !== 
page.sourcePath ? 'moved'
                     : previous.sourceHash !== page.sourceHash ? 
'source_changed' : 'unchanged'});
         }
         const unregistered = sourceFiles.filter(file => !seen.has(file));
-        const required = entries.filter(entry => 
!['draft','retain_english'].includes(entry.state));
+        const required = entries.filter(entry => 
!['draft','retain_english','historical_excluded','policy_review'].includes(entry.state));
         groups.push({id:group.id,pluginId:group.pluginId,version:group.version,
             
counts:{registered:entries.length,required:required.length,current:required.filter(entry
 => entry.state === 'current').length,
                 pending:required.filter(entry => entry.state !== 
'current').length,new:entries.filter(entry => entry.change === 'new').length,
                 changed:entries.filter(entry => entry.change === 
'source_changed').length,moved:entries.filter(entry => entry.change === 
'moved').length,
-                drafts:entries.filter(entry => entry.state === 
'draft').length,unregistered:unregistered.length},
+                drafts:entries.filter(entry => entry.state === 'draft').length,
+                historicalExcluded:entries.filter(entry => entry.state === 
'historical_excluded').length,
+                policyReview:entries.filter(entry => entry.state === 
'policy_review').length,unregistered:unregistered.length},
             unregistered,entries});
     }
     const removed = [...old.values()].filter(page => !current.has(key(page)))
         .map(page => 
({pluginId:page.pluginId,version:page.version,docId:page.docId,sourcePath:page.sourcePath,
             likelyMoveTo: groups.flatMap(group => group.entries).find(entry => 
entry.sourceHash === page.sourceHash
                 && entry.pluginId === page.pluginId && entry.version === 
page.version)?.sourcePath || null}));
-    const sidebar = sidebarAudit(native,root,scope);
+    const sidebar = sidebarAudit(native,root,scope,policies);
     const 
automaticGroups=groups.filter(group=>!scope.automaticGroups||scope.automaticGroups.includes(group.id));
     const 
automaticCounts=Object.fromEntries(['required','current','pending'].map(name=>[name,
         automaticGroups.reduce((sum,group)=>sum+group.counts[name],0)]));
     const labels = 
Object.fromEntries(['translated','untranslated','missing','retained_technical']
         .map(state => [state,sidebar.filter(entry => entry.state === 
state).length]));
+    const automaticLabels = 
Object.fromEntries(['translated','untranslated','missing','retained_technical']
+        .map(state => [state,sidebar.filter(entry => entry.state === state
+            && (!scope.automaticGroups || 
scope.automaticGroups.includes(entry.group))
+            && (!entry.maintenance || entry.maintenance === 
'eligible')).length]));
     const routingInputs = 
['docusaurus.config.js','versions.json','sidebars.ts','sidebarsCommunity.json','versioned_sidebars/version-4.x-sidebars.json',
         'translations/config.json',`translations/scope/${locale}.json`];
+    routingInputs.push(...[...policies.values()].map(policy => 
policy.baselinePath));
     if (scope.groups.some(group => group.pluginId === 'course')) 
routingInputs.push('sidebarsCourse.ts');
     if (scope.groups.some(group => group.pluginId === 'releases')) 
routingInputs.push('sidebarsReleases.json');
     routingInputs.push(`translations/state/${locale}/docs.json`, 
`translations/state/${locale}/resolved-deletions.json`);
@@ -151,11 +171,23 @@ async function audit({root,sourceRef,baseline,native}) {
         `Routing input differs from source ref: ${file}`);
     return 
{schemaVersion:1,sourceCommit:commit,baselineCommit:JSON.parse(fs.readFileSync(path.join(baseline,'summary.json'),'utf8')).sourceCommit,
         routingInputHashes:Object.fromEntries(routingInputs.map(file => 
[file,routingHashes.get(file)])),
-        groups,removed,labels,sidebar,reports:native.reports,automaticCounts,
+        
groups,removed,labels,automaticLabels,sidebar,reports:native.reports,automaticCounts,
+        policyExclusions: [...new Map([...old.values(), 
...committedRecords.values(), ...native.pages]
+            .flatMap(page => {
+                const group = scope.groups.find(group => group.pluginId === 
page.pluginId && group.version === page.version);
+                const eligibility = policies.get(group?.id)?.classify(page);
+                return eligibility && eligibility.kind !== 'eligible'
+                    ? [[docKey(page), 
{pluginId:page.pluginId,version:page.version,docId:page.docId,sourcePath:page.sourcePath,
 ...eligibility}]] : [];
+            })).values()],
         
deferredAutomaticGroups:groups.filter(group=>!automaticGroups.includes(group)).map(group=>group.id),
         fallbackOnlyVersions: 
require('./docs-language-policy.cjs').fallbackOnlyVersions.map(version => 
({pluginId:'default',version,
             
proseLocales:['en','zh-CN'],otherLocaleContent:'en',requiredTranslation:false}))};
 }
+function hasPending(report) {
+    const pending = report.automaticCounts?.pending ?? 
report.groups.some(group => group.counts.pending);
+    const labels = report.automaticLabels ?? report.labels;
+    return !!(pending || labels.missing || labels.untranslated);
+}
 if (require.main === module) {
     const args = process.argv.slice(2), option = name => 
args[args.indexOf(name)+1];
     const root = path.resolve(__dirname,'../..');
@@ -164,7 +196,7 @@ if (require.main === module) {
             if(args.includes('--output')) 
fs.writeFileSync(option('--output'),output); else process.stdout.write(output);
             
console.log(JSON.stringify({sourceCommit:result.sourceCommit,groups:result.groups.map(({id,counts})
 => ({id,counts})),
                 labels:result.labels,removed:result.removed.length}));
-            if(args.includes('--fail-on-pending') && (result.groups.some(group 
=> group.counts.pending) || result.labels.missing || 
result.labels.untranslated)) process.exitCode=1;
+            if(args.includes('--fail-on-pending') && hasPending(result)) 
process.exitCode=1;
         }).catch(error => {console.error(error);process.exitCode=1;});
 }
-module.exports = {audit,sidebarAudit};
+module.exports = {audit,sidebarAudit,hasPending};
diff --git a/scripts/i18n/build-scope.cjs b/scripts/i18n/build-scope.cjs
index f5a46cab6a94..d3d7f0136daf 100644
--- a/scripts/i18n/build-scope.cjs
+++ b/scripts/i18n/build-scope.cjs
@@ -71,7 +71,7 @@ function buildScope(files, versions, managed = [], 
dependencies = null) {
             json.add(file); // Offline configuration checks cover these 
without a site build.
         }
         // Translation configuration applies to every managed locale at once.
-        else if (file === 'translations/config.json') {managed.forEach(locale 
=> languages.add(locale)); build();}
+        else if (['translations/config.json', 
'translations/release-notes-baseline.json'].includes(file)) 
{managed.forEach(locale => languages.add(locale)); json.add(file); build();}
         else if ((match = 
file.match(/^translations\/state\/([^/]+)\/[^/]+\.json$/))) {
             if (!locales.includes(match[1])) throw new Error(`Unconfigured 
locale: ${match[1]}`);
             languages.add(match[1]); build();
diff --git a/scripts/i18n/check-configuration.cjs 
b/scripts/i18n/check-configuration.cjs
index 9bd0f2b377a3..32793fd0232a 100644
--- a/scripts/i18n/check-configuration.cjs
+++ b/scripts/i18n/check-configuration.cjs
@@ -5,11 +5,20 @@ const {loadConfig, pipelineLocale} = require('./state.cjs');
 const {validateScope} = require('./inventory.cjs');
 
 // Validate inputs without loading Docusaurus, an inventory, credentials or a 
model.
-function checkConfiguration(root, locale) {
+function checkConfiguration(root, locale, {releaseHistory = false} = {}) {
     assert(loadConfig(root).locales[locale], `Unconfigured translation locale: 
${locale}`);
     const read = kind => JSON.parse(fs.readFileSync(path.join(root, 
`translations/${kind}/${locale}.json`), 'utf8'));
     const scope = read('scope');
     validateScope(scope, locale);
+    for (const group of scope.groups.filter(group => group.pluginId === 
'releases')) {
+        if (!scope.automaticGroups || scope.automaticGroups.includes(group.id))
+            assert(group.sourcePolicy, `${locale}: automatic Release Notes 
need a new-only source policy`);
+        if (group.sourcePolicy) {
+            const policy = require('./release-policy.cjs');
+            policy.loadBaseline(root, group);
+            if (releaseHistory) policy.releasePolicy(root, group, 'HEAD');
+        }
+    }
     const retained = scope.retainSidebarLabels ?? [];
     assert(Array.isArray(retained), `${locale}: retainSidebarLabels must be an 
array`);
     const labels = new Set();
@@ -47,9 +56,11 @@ if (require.main === module) {
     try {
         const root = path.resolve(__dirname, '../..');
         const args = process.argv.slice(2);
-        assert(args.every(arg => arg === '--all'), 'Usage: 
check-configuration.cjs [--all], or set I18N_LOCALE');
+        assert(args.every(arg => ['--all','--release-history'].includes(arg)),
+            'Usage: check-configuration.cjs [--all] [--release-history], or 
set I18N_LOCALE');
         const locales = args.includes('--all') ? 
Object.keys(loadConfig(root).locales) : [pipelineLocale(root)];
-        console.log(JSON.stringify({checked: locales.map(locale => 
checkConfiguration(root, locale))}, null, 2));
+        console.log(JSON.stringify({checked: locales.map(locale => 
checkConfiguration(root, locale,
+            {releaseHistory:args.includes('--release-history')})), 
releaseHistory:args.includes('--release-history')}, null, 2));
     } catch (error) {console.error(error.message); process.exitCode = 1;}
 }
 module.exports = {checkConfiguration};
diff --git a/scripts/i18n/ci-plan.cjs b/scripts/i18n/ci-plan.cjs
index b14aa90c00cc..0615ba127796 100644
--- a/scripts/i18n/ci-plan.cjs
+++ b/scripts/i18n/ci-plan.cjs
@@ -21,7 +21,7 @@ const maintenanceNotes = /^translations\/[^/]+\.md$/;
 // through native metadata plus representative builds, like other known shared
 // language inputs. Unknown translation configuration still requires full CI.
 const maintenanceConfig = file => {
-    if (file === 'translations/config.json') return true;
+    if (['translations/config.json', 
'translations/release-notes-baseline.json'].includes(file)) return true;
     const 
match=file.match(/^translations\/(?:scope|glossary)\/([^/]+)\.json$/);
     return !!match && 
localeRegistry.some(locale=>locale.optional&&locale.code===match[1]);
 };
diff --git a/scripts/i18n/inventory.cjs b/scripts/i18n/inventory.cjs
index 9716d373ba24..553809099b49 100644
--- a/scripts/i18n/inventory.cjs
+++ b/scripts/i18n/inventory.cjs
@@ -5,6 +5,7 @@ const fs = require('node:fs');
 const path = require('node:path');
 const {execFileSync} = require('node:child_process');
 const {hash, identity, decide, loadConfig, languageName, loadRecordsAt, 
pipelineLocale} = require('./state.cjs');
+const {validatePolicy, policiesFor} = require('./release-policy.cjs');
 
 const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
 // The scope's own locale must be the one this run manages, so a scope file for
@@ -18,6 +19,7 @@ function validateScope(scope, locale) {
     const identities = new Set();
     assert(Array.isArray(scope.groups) && scope.groups.length && 
Array.isArray(scope.retainEnglish), 'Scope needs groups and explicit 
exceptions');
     for (const group of scope.groups) {
+        validatePolicy(group);
         
assert(!require('./docs-language-policy.cjs').fallbackOnly(group.pluginId, 
group.version),
             `${group.version} supports English/Chinese only; other locales use 
English fallback, not automatic translation`);
         assert(typeof group.pluginId === 'string' && group.pluginId && typeof 
group.version === 'string' && group.version, 'Group needs plugin/version');
@@ -65,7 +67,7 @@ function mergedCurrent(page, committedHash) {
         && committedHash(page.targetPath) === page.targetHash;
 }
 
-function buildGroup({group, pages, files, order, scope, committedHash, 
committedRecords}) {
+function buildGroup({group, pages, files, order, scope, committedHash, 
committedRecords, policy}) {
     const key = keyFor(scope.locale);
     const relevant = pages.filter(page => page.pluginId === group.pluginId && 
page.version === group.version && page.locale === scope.locale);
     assert(relevant.length, `Missing native plugin/version: ${group.id}`);
@@ -79,19 +81,22 @@ function buildGroup({group, pages, files, order, scope, 
committedHash, committed
         assert(page.englishPath.startsWith(group.sourceRoot + '/'), `Source 
root mismatch: ${page.englishPath}`);
         identities.add(key(page)); routes.add(page.permalink); 
paths.add(page.englishPath);
         const allowed = scope.retainEnglish.find(entry => key(entry) === 
key(page));
-        const disposition = page.draft ? 'draft' : allowed ? 'retain_english' 
: 'translate';
+        const eligibility = policy?.classify(page);
+        const disposition = page.draft ? 'draft' : allowed ? 'retain_english'
+            : eligibility && eligibility.kind !== 'eligible' ? 
eligibility.kind : 'translate';
         const record = committedRecords.get(key(page));
         const complete = disposition === 'translate' && mergedCurrent(page, 
committedHash)
             && record?.sourceHash === page.sourceHash && record?.targetHash 
=== page.targetHash
             && record?.sourcePath === page.sourcePath && record?.targetPath 
=== page.targetPath
             && record?.sourceLocale === page.sourceLocale && 
record?.reviewStatus === page.reviewStatus;
         if (disposition !== 'translate') exceptions.push({kind: disposition, 
sourcePath: page.englishPath,
-            docId: page.docId, reason: page.draft ? 'Native front matter marks 
this document as draft; no production page.' : allowed.reason});
+            docId: page.docId, reason: page.draft ? 'Native front matter marks 
this document as draft; no production page.'
+                : allowed?.reason ?? eligibility.reason});
         return {pluginId: page.pluginId, version: page.version, docId: 
page.docId, locale: scope.locale,
             sourceLocale: page.sourceLocale, sourcePath: page.sourcePath, 
englishPath: page.englishPath,
             targetPath: page.targetPath, permalink: page.permalink, 
englishPermalink: page.englishPermalink,
             sourceHash: page.sourceHash, targetHash: page.targetHash, status: 
page.status,
-            reviewStatus: page.reviewStatus, unlisted: page.unlisted, 
disposition,
+            reviewStatus: page.reviewStatus, unlisted: page.unlisted, 
disposition, ...(eligibility && {eligibility}),
             mergedCurrent: complete, batchId: null};
     }).sort((a, b) => (order.get(a.docId) ?? Infinity) - (order.get(b.docId) 
?? Infinity)
         || compare(a.sourcePath, b.sourcePath));
@@ -122,6 +127,8 @@ function buildGroup({group, pages, files, order, scope, 
committedHash, committed
         needsUpdate: required.filter(page => !page.mergedCurrent && 
page.status !== 'untranslated').length,
         drafts: entries.filter(page => page.disposition === 'draft').length,
         retainEnglish: entries.filter(page => page.disposition === 
'retain_english').length,
+        historicalExcluded: entries.filter(page => page.disposition === 
'historical_excluded').length,
+        policyReview: entries.filter(page => page.disposition === 
'policy_review').length,
         unregistered: exceptions.filter(entry => entry.kind === 
'unregistered_file').length,
         pending: pending.length, batches: batches.length};
     assert.equal(counts.required, counts.mergedCurrent + counts.untranslated + 
counts.needsUpdate);
@@ -251,6 +258,7 @@ async function main() {
         const englishHash = hash(fs.readFileSync(path.join(root, 
page.englishPath), 'utf8'));
         assert.equal(committedHash(page.englishPath), englishHash, `English 
source differs from pinned commit: ${page.englishPath}`);
     }
+    const policies = policiesFor(root, scope, sourceCommit);
     const groups = scope.groups.map(group => {
         const files = group.kind ? 
require('./source-groups.cjs').sourceFiles(root, group)
             .filter(file => require('./source-groups.cjs').hasProse(root, 
group, file)) : markdownFiles(root, group.sourceRoot);
@@ -258,12 +266,13 @@ async function main() {
             .split('\n').filter(file => group.kind ? 
require('./source-groups.cjs').matchesSource(group, file)
                 && require('./source-groups.cjs').hasProse(root, group, file) 
: /\.(md|mdx)$/.test(file)).sort(compare);
         assert.deepEqual(files, committedFiles, `Source census differs from 
pinned commit: ${group.sourceRoot}`);
-        return buildGroup({group, pages: native.pages, files, scope, 
committedHash, committedRecords,
+        return buildGroup({group, pages: native.pages, files, scope, 
committedHash, committedRecords, policy: policies.get(group.id),
             order: native.versions.find(version => version.pluginId === 
group.pluginId && version.version === group.version)?.order ?? new Map()});
     });
     const reports = native.reports.map(report => ({...report, inScope: 
scope.groups.some(group => group.pluginId === report.pluginId && group.version 
=== report.version)}))
         .sort((a, b) => compare(JSON.stringify(a), JSON.stringify(b)));
     const routingInputs = ['docusaurus.config.js', 'versions.json', 
'sidebars.ts', 'sidebarsCommunity.json', 
'versioned_sidebars/version-4.x-sidebars.json', 'translations/config.json', 
`translations/scope/${locale}.json`];
+    routingInputs.push(...[...policies.values()].map(policy => 
policy.baselinePath));
     const snapshot = {schemaVersion: 1, locale, language: languageName(root, 
locale), sourceCommit,
         routingInputs: Object.fromEntries(routingInputs.map(file => [file, 
hash(fs.readFileSync(path.join(root, file), 'utf8'))])),
         nativeVersions: native.versions.map(({order, ...version}) => version), 
groups, reports};
diff --git a/scripts/i18n/master-drift.cjs b/scripts/i18n/master-drift.cjs
index 692b965a83bc..39f4b979b9ff 100644
--- a/scripts/i18n/master-drift.cjs
+++ b/scripts/i18n/master-drift.cjs
@@ -14,7 +14,7 @@ function repositoryUrl(repository = 
process.env.GITHUB_REPOSITORY || 'apache/dor
 }
 
 function batchWide(locale) {
-    const files = new Set(['translations/config.json', 'config/i18n.js', 
'docusaurus.config.js', 'versions.json',
+    const files = new Set(['translations/config.json', 
'translations/release-notes-baseline.json', 'config/i18n.js', 
'docusaurus.config.js', 'versions.json',
         'sidebarsCourse.ts', 'sidebarsReleases.json',
         `translations/scope/${locale}.json`, 
`translations/glossary/${locale}.json`,
         `translations/protection-exceptions/${locale}.json`]);
diff --git a/scripts/i18n/plan-translations.cjs 
b/scripts/i18n/plan-translations.cjs
index 0c1631010485..a128b13bf2a4 100644
--- a/scripts/i18n/plan-translations.cjs
+++ b/scripts/i18n/plan-translations.cjs
@@ -9,6 +9,7 @@ const {audit} = require('./audit-coverage.cjs');
 const {baselineEntries} = require('./plan-translations-baseline.cjs');
 const {hash, identity, loadConfig, languageName, loadRecords, pipelineLocale} 
= require('./state.cjs');
 const {structure} = require('./structure.cjs');
+const {docKey} = require('./release-policy.cjs');
 
 const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
 const keyFor = locale => page => identity({...page, locale});
@@ -116,10 +117,12 @@ async function plan({report, scope, records, baseline = 
[], active, readCurrent,
     const resolvedDeletions = validateResolved(resolved);
     const activeByKey = new Map(candidates.map(candidate => [key(candidate), 
candidate]));
     const baselineByKey = new Map(baseline.filter(page => managed(scope, 
page)).map(page => [key(page), page]));
-    const old = new Map(baselineByKey);
+    const excluded = new Set((report.policyExclusions ?? []).map(docKey));
+    const old = new Map([...baselineByKey].filter(([, page]) => 
!excluded.has(docKey(page))));
     for (const record of records.values()) if (managed(scope, record)) 
old.set(key(record), {...old.get(key(record)), ...record});
+    for (const [id, page] of old) if (excluded.has(docKey(page))) 
old.delete(id);
     const pages = report.groups.filter(group => managed(scope, 
group)).flatMap(group =>
-        group.entries.filter(page => page.state !== 'draft' && page.state !== 
'retain_english')
+        group.entries.filter(page => 
!['draft','retain_english','historical_excluded','policy_review'].includes(page.state))
             .map(page => ({...page, groupId: group.id})));
     const sourcePaths = new Set(pages.map(page => page.sourcePath));
     for (const file of filters.paths ?? []) assert(sourcePaths.has(file), `Not 
a registered translatable source file: ${file}`);
@@ -131,6 +134,8 @@ async function plan({report, scope, records, baseline = [], 
active, readCurrent,
     const tasks = [];
     const exceptions = report.groups.flatMap(group => 
group.unregistered.map(sourcePath => ({kind: 'unregistered_file',
         groupId: group.id, sourcePath}))).concat(report.reports.filter(item => 
managed(scope, item)));
+    exceptions.push(...(report.policyExclusions ?? []).map(page => ({...page,
+        groupId:managed(scope, page)?.id})));
     const changedChinese = new Set(chineseChangedPaths);
     for (const page of pages) {
         const previous = old.get(key(page));
diff --git a/scripts/i18n/release-policy.cjs b/scripts/i18n/release-policy.cjs
new file mode 100644
index 000000000000..27712af2d3e3
--- /dev/null
+++ b/scripts/i18n/release-policy.cjs
@@ -0,0 +1,131 @@
+// The release-note cutoff controls automation only. Native publication and
+// existing translated files/records keep their ordinary fallback semantics.
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const {execFileSync} = require('node:child_process');
+const {hash} = require('./state.cjs');
+
+const cache = new Map();
+const sourcePath = file => typeof file === 'string' && file && 
!path.isAbsolute(file)
+    && !/[\\\x00-\x1f\x7f]/.test(file) && file.split('/').every(part => part 
&& part !== '.' && part !== '..');
+const docKey = page => JSON.stringify([page.pluginId, page.version, 
page.docId]);
+
+function validatePolicy(group) {
+    const policy = group.sourcePolicy;
+    if (policy === undefined) return;
+    assert(group.pluginId === 'releases' && group.version === 'current' && 
!group.kind
+        && policy && policy.mode === 'new-only' && policy.baseline === 
'translations/release-notes-baseline.json',
+    'Invalid release-note source policy');
+}
+
+function loadBaseline(root, group) {
+    validatePolicy(group);
+    assert(group.sourcePolicy, 'Automatic Release Notes generation requires a 
new-only source policy');
+    const file = group.sourcePolicy.baseline;
+    let cursor = root;
+    for (const part of file.split('/')) {
+        cursor = path.join(cursor, part);
+        assert(!fs.lstatSync(cursor).isSymbolicLink(), 'Symlink in Release 
Notes baseline path');
+    }
+    assert(fs.lstatSync(cursor).isFile(), 'Release Notes baseline must be a 
regular file');
+    const source = fs.readFileSync(path.join(root, file), 'utf8');
+    const data = JSON.parse(source);
+    assert(data.schemaVersion === 1 && /^[a-f0-9]{40}$/.test(data.sourceCommit)
+        && data.pluginId === group.pluginId && data.version === group.version
+        && data.sourceRoot === group.sourceRoot, 'Invalid Release Notes 
baseline identity');
+    for (const name of ['documents', 'sourceFiles', 'historicalPaths']) 
assert(Array.isArray(data[name]), `Missing baseline ${name}`);
+    const ids = new Set(), files = new Set();
+    for (const doc of data.documents) {
+        assert(typeof doc.docId === 'string' && doc.docId && 
!ids.has(doc.docId)
+            && sourcePath(doc.sourcePath) && 
doc.sourcePath.startsWith(group.sourceRoot + '/')
+            && !files.has(doc.sourcePath) && 
/^[a-f0-9]{64}$/.test(doc.sourceHash), 'Invalid/duplicate historical release 
note');
+        ids.add(doc.docId); files.add(doc.sourcePath);
+    }
+    for (const name of ['sourceFiles', 'historicalPaths']) {
+        assert(new Set(data[name]).size === data[name].length && 
data[name].every(file => sourcePath(file)
+            && file.startsWith(group.sourceRoot + '/') && 
/\.mdx?$/.test(file)), `Invalid baseline ${name}`);
+    }
+    assert(data.sourceFiles.every(file => data.historicalPaths.includes(file))
+        && [...files].every(file => data.sourceFiles.includes(file)), 
'Incomplete historical source census');
+    return {file, source, data};
+}
+
+function releasePolicy(root, group, sourceRef = 'HEAD') {
+    if (!group.sourcePolicy) return null;
+    const baseline = loadBaseline(root, group);
+    const git = args => execFileSync('git', args, {cwd: root, encoding: 
'utf8', maxBuffer: 64 * 1024 * 1024,
+        stdio: ['ignore', 'pipe', 'pipe']});
+    const ref = git(['rev-parse', `${sourceRef}^{commit}`]).trim();
+    const key = JSON.stringify([root, group, ref, hash(baseline.source)]);
+    if (cache.has(key)) return cache.get(key);
+    const data = baseline.data;
+    try {git(['merge-base', '--is-ancestor', data.sourceCommit, ref]);}
+    catch {throw new Error('Release Notes baseline must be an ancestor of the 
audited source commit');}
+    const census = git(['ls-tree', '-r', '--name-only', data.sourceCommit, 
'--', group.sourceRoot])
+        .split('\n').filter(file => /\.mdx?$/.test(file)).sort();
+    assert.deepEqual([...data.sourceFiles].sort(), census, 'Release Notes 
baseline omits historical source files');
+    const historicalCensus = [...new Set(git(['log', '--format=', 
'--name-only', data.sourceCommit, '--', group.sourceRoot])
+        .split('\n').filter(file => file.startsWith(group.sourceRoot + '/') && 
/\.mdx?$/.test(file)))].sort();
+    assert.deepEqual([...data.historicalPaths].sort(), historicalCensus, 
'Release Notes baseline omits historical path identities');
+    for (const doc of data.documents) assert.equal(hash(git(['show', 
`${data.sourceCommit}:${doc.sourcePath}`])),
+        doc.sourceHash, `Historical release-note hash mismatch: 
${doc.sourcePath}`);
+    const historical = new Set(data.historicalPaths), ids = new 
Set(data.documents.map(doc => doc.docId));
+    const hashes = new Set(data.documents.map(doc => doc.sourceHash));
+    const origins = new Map([...historical].map(file => [file, {kind: 
'historical', origin: file}]));
+    const inGroup = file => file?.startsWith(group.sourceRoot + '/') && 
/\.mdx?$/.test(file);
+    // First-parent diffs describe the actual committed tree at each mainline
+    // step. Branch commits must not be replayed a second time at their merge.
+    const history = git(['log', '--first-parent', 
'--diff-merges=first-parent', '--reverse',
+        '--format=%x1e%H', '--name-status', '-z', '-M', '-C100%', 
'--find-copies-harder', `${data.sourceCommit}..${ref}`]);
+    for (const chunk of history.split('\x1e').filter(Boolean)) {
+        const tokens = chunk.split('\0'), commit = tokens.shift().trim();
+        assert(/^[a-f0-9]{40}$/.test(commit), 'Invalid release-note Git 
history');
+        const events = [];
+        for (let index = 0; index < tokens.length;) {
+            const status = tokens[index++].trim();
+            if (!status) continue;
+            assert(/^[ACDMRTUXB][0-9]*$/.test(status), 'Invalid release-note 
Git change');
+            const from = tokens[index++];
+            const to = /^[RC]/.test(status) ? tokens[index++] : from;
+            events.push({status, from, to});
+        }
+        const ambiguous = events.some(event => event.status === 'D' && 
inGroup(event.from)
+            && origins.get(event.from)?.kind !== 'new');
+        for (const event of events) {
+            const prior = origins.get(event.from);
+            if (event.status[0] === 'R' || event.status[0] === 'C') {
+                if (prior || inGroup(event.to)) origins.set(event.to, prior ?? 
{kind: 'review', origin: event.from, commit});
+            } else if (event.status === 'A' && inGroup(event.to) && 
!origins.has(event.to)) {
+                origins.set(event.to, {kind: ambiguous ? 'review' : 'new', 
origin: event.to, commit});
+            }
+            // Retain deleted path identities, so deleting and later adding a
+            // historical note never turns it into a newly eligible document.
+        }
+    }
+    function classify(page) {
+        const file = page.englishPath ?? page.sourcePath;
+        const origin = origins.get(file);
+        let kind, reason;
+        if (ids.has(page.docId) || historical.has(file) || origin?.kind === 
'historical') {
+            kind = 'historical_excluded'; reason = 'Registered or present 
before the fixed release-note cutoff, including moves and re-registrations.';
+        } else if (origin?.kind !== 'new' || hashes.has(page.sourceHash)) {
+            kind = 'policy_review'; reason = 'Cannot establish a genuinely new 
release note after the cutoff; reconcile its source identity manually.';
+        } else {
+            kind = 'eligible'; reason = 'A new source identity committed after 
the fixed release-note cutoff.';
+        }
+        return {kind, reason, baselineCommit: data.sourceCommit, originPath: 
origin?.origin ?? file,
+            introducedCommit: origin?.commit ?? null};
+    }
+    const result = {classify, baselinePath: baseline.file, baselineCommit: 
data.sourceCommit,
+        baselineHash: hash(baseline.source), sourceCommit: ref};
+    cache.set(key, result);
+    return result;
+}
+
+function policiesFor(root, scope, ref = 'HEAD') {
+    return new Map(scope.groups.filter(group => group.sourcePolicy)
+        .map(group => [group.id, releasePolicy(root, group, ref)]));
+}
+
+module.exports = {docKey, validatePolicy, loadBaseline, releasePolicy, 
policiesFor};
diff --git a/scripts/i18n/search-plugin.cjs b/scripts/i18n/search-plugin.cjs
index 744344a81ba4..7fb3c2949b3d 100644
--- a/scripts/i18n/search-plugin.cjs
+++ b/scripts/i18n/search-plugin.cjs
@@ -15,18 +15,19 @@ const {buildIndex: buildNativeIndex} = 
require('@yang1666204/docusaurus-search-l
 const {loadConfig, managedLocales} = require('./state.cjs');
 const {normalizeRussianSearch} = 
require('../../src/utils/searchNormalization.js');
 
-const contexts = ['docs/dev', 'docs/4.x', 'community', 'course'];
+const contexts = ['docs/dev', 'docs/4.x', 'community', 'course', 'releases'];
 const normalize = url => url.replace(/\/+$/, '');
 function groupFor(page) {
     if (page.pluginId === 'default' && page.version === 'current') return 
'docs/dev';
     if (page.pluginId === 'default' && page.version === '4.x') return 
'docs/4.x';
     if (page.pluginId === 'community' && page.version === 'current') return 
'community';
     if (page.pluginId === 'course' && page.version === 'current') return 
'course';
+    if (page.pluginId === 'releases' && page.version === 'current') return 
'releases';
     return null;
 }
 function localizedOptions(options, locale, labels) {
     return {...options, language:locale === 'ru' ? ['en', 'ru'] : 
[locale.split('-')[0]],
-        
docsRouteBasePath:['docs','community','course',`${locale}/docs`,`${locale}/community`,`${locale}/course`],
+        
docsRouteBasePath:['docs','community','course','releases',`${locale}/docs`,`${locale}/community`,`${locale}/course`,`${locale}/releases`],
         // A locale without configured labels falls back to the context path.
         searchContextByPaths:contexts.map(value => 
({path:value,label:{[locale]:labels?.[value] ?? value}})),
         // Outside every context, search all of them. The browser queries each
@@ -61,7 +62,7 @@ function targetFingerprint(root, locale) {
     }
     scan(path.join(root,'i18n',locale));
     scan(path.join(root,'translations/state',locale));
-    for (const dir of 
['docs','versioned_docs/version-4.x','community','course']) 
scan(path.join(root,dir));
+    for (const dir of 
['docs','versioned_docs/version-4.x','community','course','releasenotes']) 
scan(path.join(root,dir));
     return digester.digest('hex').slice(0,16);
 }
 function indexFilename(context) {
diff --git a/scripts/i18n/translation-engine.cjs 
b/scripts/i18n/translation-engine.cjs
index d01ceb6b573a..a000db0ed58f 100644
--- a/scripts/i18n/translation-engine.cjs
+++ b/scripts/i18n/translation-engine.cjs
@@ -407,6 +407,15 @@ function checkedTask(plan, taskId, scope, root) {
     managedPath(scope, task);
     const group = scope.groups.find(group => group.pluginId === task.pluginId 
&& group.version === task.version);
     assert(!scope.automaticGroups || scope.automaticGroups.includes(group.id), 
'Automatic generation is not enabled for this scope group');
+    if (group.pluginId === 'releases') {
+        const {releasePolicy} = require('./release-policy.cjs');
+        assert(group.sourcePolicy, 'Automatic Release Notes generation 
requires a new-only source policy');
+        const policy = releasePolicy(root, group, plan.sourceCommit);
+        assert.equal(execFileSync('git', ['show', 
`${plan.sourceCommit}:${policy.baselinePath}`], {cwd:root,encoding:'utf8'}),
+            fs.readFileSync(safeFile(root, policy.baselinePath), 'utf8'), 
'Release Notes policy changed since planning');
+        const eligibility = policy.classify(task);
+        assert.equal(eligibility.kind, 'eligible', `Release Notes task 
excluded by source policy: ${eligibility.kind}`);
+    }
     const source = fs.readFileSync(safeFile(root, task.sourcePath), 'utf8');
     assert(sha(task.sourceHash) && hash(source) === task.sourceHash, 'Source 
changed since planning');
     assert.equal(execFileSync('git', ['show', `HEAD:${task.sourcePath}`], 
{cwd: root, encoding: 'utf8'}), source,
diff --git a/scripts/i18n/verify-release-policy-fixture.cjs 
b/scripts/i18n/verify-release-policy-fixture.cjs
new file mode 100644
index 000000000000..8c1e63fb9bca
--- /dev/null
+++ b/scripts/i18n/verify-release-policy-fixture.cjs
@@ -0,0 +1,160 @@
+// Small real native-docs fixture: historical/current/missing release notes,
+// automatic eligibility, model-call guards, publication, search and SEO.
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+const {execFileSync, spawnSync} = require('node:child_process');
+const cheerio = require('cheerio');
+const {createFixture} = require('./__tests__/fixtures/multi-locale.cjs');
+const {hash, identity, loadRecords} = require('./state.cjs');
+const {structure} = require('./structure.cjs');
+const {audit} = require('./audit-coverage.cjs');
+const {plan} = require('./plan-translations.cjs');
+const {prepare, readState, targetFor} = require('./translate-docs.cjs');
+const {checkIndexes} = require('./verify-search.cjs');
+const {createServer} = require('../publish/serve.cjs');
+
+const repo = path.resolve(__dirname, '../..');
+const root = path.join(repo, '.i18n-build/release-policy-fixture');
+const locales = ['en', 'zh-CN', 'ja', 'ko', 'ru'];
+const managed = ['ja', 'ko', 'ru'];
+const words = {ja:'日本語',ko:'한국어',ru:'Русский','zh-CN':'中文'};
+const git = (...args) => execFileSync('git', args, 
{cwd:root,encoding:'utf8',stdio:['ignore','pipe','pipe']}).trim();
+function write(file, data) {const 
target=path.join(root,file);fs.mkdirSync(path.dirname(target),{recursive:true});fs.writeFileSync(target,data);}
+function commit(message) 
{git('add','.');git('-c','user.name=Fixture','-c','[email protected]','commit','-qm',message);return
 git('rev-parse','HEAD');}
+const sourceFor = name => `---\ntitle: Release ${name}\nlast_update:\n  date: 
2026-01-01\n---\n\n## Usage\n\nReleaseFixtureSQL documents 
${name}.\n\n\`\`\`sql\nSELECT 1;\n\`\`\`\n`;
+
+async function addTarget(locale, name, sourceCommit) {
+    const 
sourcePath=`releasenotes/${name}.md`,source=fs.readFileSync(path.join(root,sourcePath),'utf8');
+    const 
targetPath=`i18n/${locale}/docusaurus-plugin-content-docs-releases/current/${name}.md`;
+    const target=source.replace('title: Release',`title: 
${words[locale]}`).replace('ReleaseFixtureSQL documents',`${words[locale]} 
ReleaseFixtureSQL`);
+    write(targetPath,target);
+    if (!managed.includes(locale)) return;
+    const 
file=`translations/state/${locale}/docs.json`,state=JSON.parse(fs.readFileSync(path.join(root,file)));
+    
state.pages.push({pluginId:'releases',version:'current',docId:name,locale,sourceLocale:'en',sourcePath,targetPath,sourceCommit,
+        
sourceHash:hash(source),targetHash:hash(target),sourceTechnicalHash:(await 
structure(source,'.md')).technicalHash,
+        
reviewStatus:'machine',ownership:'machine',translatedAt:'2026-10-27T00:00:00Z',glossaryVersion:'fixture'});
+    write(file,JSON.stringify(state));
+}
+
+async function create() {
+    fs.rmSync(root,{recursive:true,force:true});
+    await createFixture(root);
+    write('releasenotes/historical.md',sourceFor('historical'));
+    let source=commit('Historical source');
+    for (const locale of ['zh-CN',...managed]) await 
addTarget(locale,'historical',source);
+    const cutoff=commit('Historical translations');
+    
write('translations/release-notes-baseline.json',JSON.stringify({schemaVersion:1,sourceCommit:cutoff,
+        
pluginId:'releases',version:'current',sourceRoot:'releasenotes',sourceFiles:['releasenotes/historical.md'],
+        
historicalPaths:['releasenotes/historical.md'],documents:[{docId:'historical',sourcePath:'releasenotes/historical.md',sourceHash:hash(sourceFor('historical'))}]}));
+    const 
group={id:'RELEASES',pluginId:'releases',version:'current',sourceRoot:'releasenotes',
+        
sourcePolicy:{mode:'new-only',baseline:'translations/release-notes-baseline.json'}};
+    for (const locale of managed) {
+        
write(`translations/scope/${locale}.json`,JSON.stringify({schemaVersion:1,locale,maxBatchPages:20,
+            
groups:[group],automaticGroups:['RELEASES'],retainEnglish:[],retainSidebarLabels:[]}));
+        
write(`translations/state/${locale}/resolved-deletions.json`,'{"schemaVersion":1,"deletions":[]}');
+    }
+    for (const name of ['new-current','new-missing']) 
write(`releasenotes/${name}.md`,sourceFor(name));
+    source=commit('New release-note sources');
+    for (const locale of ['zh-CN',...managed]) await 
addTarget(locale,'new-current',source);
+    
fs.renameSync(path.join(root,'docusaurus.config.js'),path.join(root,'fixture.base.config.cjs'));
+    write('docusaurus.config.js',`const 
base=require('./fixture.base.config.cjs');module.exports=()=>{const 
config=base();
+config.plugins.splice(2,0,[require(${JSON.stringify(path.join(repo,'scripts/i18n/docs-plugin.cjs'))}),{id:'releases',path:'releasenotes',routeBasePath:'releases',sidebarPath:false}]);
+config.themes=[[require(${JSON.stringify(path.join(repo,'scripts/i18n/search-plugin.cjs'))}),{hashed:false,language:['en'],docsRouteBasePath:['docs','community','releases'],searchContextByPaths:['docs','community','releases']}]];
+return config;};\n`);
+    for (const file of 
['sidebars.ts','sidebarsCommunity.json','sidebarsCourse.ts','sidebarsReleases.json'])
 write(file,'{}');
+    // Exercise the actual site's index-root and cross-context worker adapters,
+    // not the upstream theme's different locale URL assumptions.
+    const {bodyDependencies}=require('./page-body.cjs');
+    for (const entry of 
['src/theme/SearchPage/index.js','src/theme/SearchBar/index.js']) {
+        for (const file of bodyDependencies(repo,entry,{shared:true}).files)
+            write(file,fs.readFileSync(path.join(repo,file)));
+    }
+    
write('static/images/search-icon.svg',fs.readFileSync(path.join(repo,'static/images/search-icon.svg')));
+    const final=commit('Release-note policy fixture');
+    const baseline=path.join(root,'audit-baseline');
+    write('audit-baseline/summary.json',JSON.stringify({sourceCommit:cutoff}));
+    for (const group of ['dev','4x','community']) 
write(`audit-baseline/${group}.json`,'{"entryFiles":[]}');
+    const results=[];
+    for (const locale of managed) {
+        process.env.I18N_LOCALE=locale;
+        const report=await audit({root,sourceRef:final,baseline});
+        const releases=report.groups.find(group=>group.id==='RELEASES');
+        assert.equal(releases.counts.historicalExcluded,1);
+        
assert.deepEqual(report.automaticCounts,{required:2,current:1,pending:1});
+        const 
scope=JSON.parse(fs.readFileSync(path.join(root,`translations/scope/${locale}.json`)));
+        const planned=await 
plan({report,scope,records:loadRecords(root,locale),baseline:[],
+            
readCurrent:file=>fs.readFileSync(path.join(root,file),'utf8'),readAt:()=>null});
+        assert.equal(planned.selected.length,1);
+        const task=planned.tasks.find(task=>task.id===planned.selected[0]);
+        assert.equal(task.docId,'new-missing');
+        let calls=0;
+        const candidate=await 
prepare({root,plan:planned,taskId:task.id,scope,state:readState(root,locale),target:targetFor(root,locale),
+            
glossary:{schemaVersion:1,version:'fixture',keep:[],terms:[]},adapter:{model:'fixture',async
 translate({units}){
+                calls++;return 
{translations:units.map(unit=>({id:unit.id,text:`${words[locale]} 
${unit.text}`}))};}}});
+        assert(calls>0&&candidate.target.includes(words[locale]));
+        assert(candidate.target.includes('SELECT 1;'));
+        assert(!fs.existsSync(path.join(root,task.targetPath)),'The mock 
candidate must not replace the missing-target publication case');
+        const 
active={schemaVersion:1,candidates:[{...task,pr:'https://github.com/apache/doris-website/pull/1'}]};
+        const reserved=await 
plan({report,scope,records:loadRecords(root,locale),active,readCurrent:file=>fs.readFileSync(path.join(root,file),'utf8'),readAt:()=>null});
+        assert.equal(reserved.selected.length,0);
+        
results.push({locale,counts:releases.counts,mockRequests:calls,selected:task.docId,reservedCount:reserved.selected.length});
+    }
+    
fs.writeFileSync(path.join(root,'eligibility.json'),JSON.stringify(results,null,2)+'\n');
+    return results;
+}
+
+function checkSite() {
+    const site=path.join(root,'build'),pages=[],search=[];
+    for (const locale of locales) for (const name of 
['historical','new-current','new-missing']) {
+        const 
prefix=locale==='en'?'':`/${locale}`,route=`${prefix}/releases/${name}/`;
+        const 
$=cheerio.load(fs.readFileSync(path.join(site,route,'index.html'),'utf8'));
+        const fallback=locale!=='en'&&name==='new-missing';
+        
assert.equal($('.theme-doc-markdown').attr('lang'),fallback?'en':locale);
+        
assert.equal($('link[rel=canonical]').attr('href'),`https://doris.apache.org${fallback?'':prefix}/releases/${name}/`);
+        
assert.equal(/noindex/.test($('meta[name=robots]').attr('content')??''),fallback);
+        
if(fallback)assert.equal($(`link[rel=alternate][hreflang="${locale}"]`).length,0);
+        pages.push({locale,name,route,fallback});
+    }
+    for(const locale of managed){
+        const 
manifest=JSON.parse(fs.readFileSync(path.join(root,`.i18n-build/manifests/${locale}.json`)));
+        const checked=checkIndexes({site,locale,manifest});
+        assert.equal(checked.summary.contexts.releases,2);
+        search.push({locale,indexedReleases:2});
+    }
+    return {pages,search};
+}
+
+async function checkBrowser() {
+    const 
{chromium}=require('playwright'),server=createServer(path.join(root,'build'));
+    await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
+    const origin=`http://127.0.0.1:${server.address().port}`,browser=await 
chromium.launch({headless:true}),errors=[],results=[];
+    try{const page=await 
browser.newPage();page.on('pageerror',error=>errors.push(error.message));
+        for(const locale of managed){
+            await page.goto(`${origin}/${locale}/releases/new-current/`);
+            assert.equal(await 
page.locator('.theme-doc-markdown').getAttribute('lang'),locale);
+            await 
page.goto(`${origin}/${locale}/search/?q=ReleaseFixtureSQL&ctx=releases`);
+            await page.locator('section article 
a').first().waitFor({timeout:30000});
+            const links=await page.locator('section article 
a').evaluateAll(nodes=>nodes.map(node=>node.getAttribute('href')));
+            assert(links.some(link=>link.includes('/releases/new-current/')));
+            
assert(links.every(link=>link.startsWith(`/${locale}/releases/`)&&!link.includes('new-missing')));
+            results.push({locale,workerSearch:true,noFallbackIndex:true});
+        }assert.deepEqual(errors,[]);return {results,errors};
+    }finally{await browser.close();await new 
Promise(resolve=>server.close(resolve));}
+}
+
+async function main(){
+    if(!process.argv.includes('--verify-existing')){
+        await create();
+        const 
built=spawnSync(process.execPath,[path.join(repo,'scripts/build-locales.cjs'),'--site-dir',root],
+            
{stdio:'inherit',env:{...process.env,DORIS_ASSET_ORIGIN:'',DORIS_OPTIMIZE_PUBLISH:'',NODE_OPTIONS:'--max-old-space-size=4096'}});
+        assert.equal(built.status,0,'Release-note fixture build failed');
+    }
+    const result={scope:'synthetic three-note, five-language fixture; no real 
model calls or production notes',
+        
sourceCommit:git('rev-parse','HEAD'),eligibility:JSON.parse(fs.readFileSync(path.join(root,'eligibility.json'))),...checkSite()};
+    if(process.argv.includes('--browser'))result.browser=await checkBrowser();
+    
fs.writeFileSync(path.join(repo,'.i18n-build/release-policy-fixture-verification.json'),JSON.stringify(result,null,2)+'\n');
+    
console.log(JSON.stringify({releasePages:result.pages.length,search:result.search,browser:result.browser}));
+}
+if(require.main===module)main().catch(error=>{console.error(error);process.exitCode=1});
+module.exports={create,checkSite,checkBrowser};
diff --git a/scripts/i18n/workflow-scan.cjs b/scripts/i18n/workflow-scan.cjs
index 1e026ee83757..38ba72b5a2bf 100644
--- a/scripts/i18n/workflow-scan.cjs
+++ b/scripts/i18n/workflow-scan.cjs
@@ -9,7 +9,7 @@ const {hash, languageName, pipelineLocale} = 
require('./state.cjs');
 const root = path.resolve(__dirname, '../..');
 const configInputs = ['docusaurus.config.js', 'config/i18n.js', 'sidebars.ts', 
'sidebarsCommunity.json',
     'sidebarsCourse.ts', 'sidebarsReleases.json',
-    'versioned_sidebars/version-4.x-sidebars.json', 
'translations/config.json'];
+    'versioned_sidebars/version-4.x-sidebars.json', 
'translations/config.json', 'translations/release-notes-baseline.json'];
 // A push to this locale's own scope or glossary changes what may be generated.
 const rulesFor = locale => new Set([...configInputs,
     `translations/scope/${locale}.json`, 
`translations/glossary/${locale}.json`]);
@@ -59,6 +59,7 @@ function summarize(plan, reconciliation, eligible, mode, 
language, reused = fals
         `- Open PRs inspected: ${reconciliation.openPrCount}; relevant: 
${reconciliation.relevantPrs.length}; active candidates: 
${plan.counts.activeCandidates}`,
         `- Selected for a possible next batch: ${plan.selected.length}; 
blocked targets: ${reconciliation.blockedTargets.length}`,
         `- Outside enabled automatic groups: ${plan.deferredAutomationTasks ?? 
0}; reported separately, not sent to a model`,
+        `- Release-note policy: historical 
excluded=${plan.exceptions.filter(item=>item.kind==='historical_excluded').length};
 manual 
reconciliation=${plan.exceptions.filter(item=>item.kind==='policy_review').length}`,
         `- Robot batch PRs already open: ${reconciliation.botPrs.join(', ') || 
'none'}`,
         `- Exceptions: ${[...exceptionKinds].map(([kind, count]) => 
`${kind}=${count}`).join(', ') || 'none'}`,
         '',
diff --git a/translations/README.md b/translations/README.md
index 656858fce1d7..621d1844b002 100644
--- a/translations/README.md
+++ b/translations/README.md
@@ -49,7 +49,7 @@ workflows explicitly enable all three; default local builds 
still serve English
 and Chinese. Ordinary Russian pages retain Russian URLs and navigation; 
missing manual prose falls back to English.
 Russian translation runs twice daily and on manual dispatch, as described 
below.
 
-The ja/ko/ru scopes explicitly enable DEV, 4X, COMMUNITY and COURSE in
+The ja/ko/ru scopes explicitly enable DEV, 4X, COMMUNITY, COURSE and RELEASES 
in
 `automaticGroups`. Course bodies use the same source hashes, per-page state,
 manual-edit protection, open-PR reservations and incremental maintenance as the
 other documentation plugins. Existing valid targets are reused. Chinese course
@@ -57,10 +57,9 @@ bodies remain manually maintained; their page records retain 
machine review
 status and human ownership, without enabling a Chinese translation workflow.
 Course search uses its own localized context and indexes current targets only.
 
-Native audits still report releases and supplemental page groups so
-their existing content remains visible. These groups are not sent to a model.
-Release Notes expansion must pin a historical boundary before admitting future
-new entries; historical files and existing translations stay unchanged. React 
landing pages
+RELEASES uses the new-only policy below; supplemental page groups remain 
outside
+automatic generation. Historical release-note files and existing translations
+stay unchanged. React landing pages
 and weekly reports use their manual page/entry state. Existing candidates from
 earlier expanded runs remain separate pending reviews, not merged coverage.
 The audit's `automaticCounts` and discovery summary report the effective
@@ -68,6 +67,61 @@ required/current/pending totals. Additional registered 
groups remain in the
 raw census and `deferredAutomaticGroups`; their old-channel missing files do
 not become required translation backlog or override manual page/report state.
 
+### New-only Release Notes maintenance
+
+`translations/release-notes-baseline.json` freezes the English native 
identities,
+source hashes and historical paths at commit
+`b207a93551a3705652bc261639e3eec7fd738c7a`: 83 registered notes and 83 source
+files, including the existing release index. Each managed RELEASES group names
+this file through `sourcePolicy: {mode: "new-only", baseline:
+"translations/release-notes-baseline.json"}`. Chinese remains manually 
maintained.
+
+The inventory and audit classify historical identities as 
`historical_excluded`.
+They remain in the census, with their ordinary native content/fallback state,
+but do not enter required/pending translation totals, update/deletion tasks or
+automatic generation. Existing targets and records are preserved; native stale
+checks and English fallback continue to apply. Publication does not depend on
+the automation policy.
+
+Raw sidebar label status remains visible. `automaticLabels` and
+`--fail-on-pending` use only enabled automatic groups and eligible note labels;
+historical release categories and version navigation remain manual native
+labels. They do not create an automatic missing-label backlog.
+
+Newly registered notes must have a genuinely new source identity committed 
after
+the cutoff. Their later source updates use the existing incremental workflow,
+open-PR reservations, ownership protection, batch limits, shared lock and 
normal
+PR/CI process. Current real translated notes have a `releases` search context;
+untranslated or stale English-fallback pages do not enter a managed locale's
+search index.
+
+Git lineage prevents a historical rename, move or re-registration from becoming
+a new translation task. Exact copies and ambiguous delete/add histories are
+excluded or reported as `policy_review` for manual identity reconciliation.
+These cases are not sent to a model. The generator independently checks the
+committed policy and eligibility before reading a task for translation, so a
+forced task or stale plan cannot bypass the cutoff.
+
+Do not regenerate the baseline or move its source commit forward to hide
+pending work. Configuration validation checks the policy format; CI also runs
+`check-configuration.cjs --all --release-history` to verify its Git census,
+hashes and ancestry without a site build. Inventory and generation 
independently
+check the same evidence. A baseline
+or pipeline change during generation invalidates the batch.
+
+For a read-only check, use the existing locale workflow's `dry-run` mode or:
+
+```bash
+I18N_LOCALE=ru node scripts/i18n/plan-translations.cjs --source-ref HEAD \
+  --plugin releases --output /tmp/release-plan.json --audit-output 
/tmp/release-audit.json
+```
+
+If no new eligible note exists, selected tasks stay empty and the workflow does
+not call a model or open a translation PR. Do not manufacture a note or source
+drift for acceptance. Record the first real future note's generation, merge and
+public result when it occurs. To pause RELEASES, remove it from the affected
+locale's `automaticGroups`; retain the fixed policy, existing content and URLs.
+
 The [initial Russian inventory](inventory/ru/2026-10-20/batches.md) records 
1,674 Dev,
 1,619 4.x and 51 community documents, excluding two drafts. Four launch samples
 have independent state shards and machine review status. They have not received
diff --git a/translations/config.json b/translations/config.json
index e5d64d527999..a4d8eb67cafc 100644
--- a/translations/config.json
+++ b/translations/config.json
@@ -10,7 +10,8 @@
         "docs/dev": "Dev ドキュメント",
         "docs/4.x": "4.x ドキュメント",
         "community": "コミュニティ",
-        "course": "コース"
+        "course": "コース",
+        "releases": "リリースノート"
       }
     },
     "ko": {
@@ -22,7 +23,8 @@
         "docs/dev": "Dev 문서",
         "docs/4.x": "4.x 문서",
         "community": "커뮤니티",
-        "course": "강좌"
+        "course": "강좌",
+        "releases": "릴리스 노트"
       }
     },
     "ru": {
@@ -34,7 +36,8 @@
         "docs/dev": "Документация Dev",
         "docs/4.x": "Документация 4.x",
         "community": "Сообщество",
-        "course": "Курсы"
+        "course": "Курсы",
+        "releases": "Примечания к выпуску"
       }
     }
   }
diff --git a/translations/release-notes-baseline.json 
b/translations/release-notes-baseline.json
new file mode 100644
index 000000000000..277755f58729
--- /dev/null
+++ b/translations/release-notes-baseline.json
@@ -0,0 +1,594 @@
+{
+  "schemaVersion": 1,
+  "sourceCommit": "b207a93551a3705652bc261639e3eec7fd738c7a",
+  "pluginId": "releases",
+  "version": "current",
+  "sourceRoot": "releasenotes",
+  "documents": [
+    {
+      "docId": "all-release",
+      "sourcePath": "releasenotes/all-release.md",
+      "sourceHash": 
"392f3af162a087eca7416af910e10a045896765271d5bbe443fc20d33d3410dc"
+    },
+    {
+      "docId": "core",
+      "sourcePath": "releasenotes/core.md",
+      "sourceHash": 
"d744120104a6512d9f24a74268135a15c9c116079581001142537f5fc6cb10cf"
+    },
+    {
+      "docId": "ecosystem/doris-cli",
+      "sourcePath": "releasenotes/ecosystem/doris-cli.md",
+      "sourceHash": 
"282dc69d1690e92a24b0930eefb808c77bd6e9f60a6116cce58c6996cc88aa26"
+    },
+    {
+      "docId": "ecosystem/doris-flink-connector",
+      "sourcePath": "releasenotes/ecosystem/doris-flink-connector.md",
+      "sourceHash": 
"ab235970b433a4f1e0ef05761020c1515aa6c36eab134b7e1231490c9b18c913"
+    },
+    {
+      "docId": "ecosystem/doris-kafka-connector",
+      "sourcePath": "releasenotes/ecosystem/doris-kafka-connector.md",
+      "sourceHash": 
"4ef58f5761af434718b93275b8785320eeaf20e86fa79e2785b590ff65ec894f"
+    },
+    {
+      "docId": "ecosystem/doris-mcp-server",
+      "sourcePath": "releasenotes/ecosystem/doris-mcp-server.md",
+      "sourceHash": 
"26de2dd77c7b09d00aed7d7f525cf253c47393c14aaf446fecc710970784ceb1"
+    },
+    {
+      "docId": "ecosystem/doris-operator",
+      "sourcePath": "releasenotes/ecosystem/doris-operator.md",
+      "sourceHash": 
"c5038d3c275542480cefa8dcb535afb6c61072071b255b09adef78266aaf7b90"
+    },
+    {
+      "docId": "ecosystem/doris-skills",
+      "sourcePath": "releasenotes/ecosystem/doris-skills.md",
+      "sourceHash": 
"0e7d43ffc650a706c774ef8129b2035b571dc86dad621f287efc57e48a106839"
+    },
+    {
+      "docId": "ecosystem/doris-spark-connector",
+      "sourcePath": "releasenotes/ecosystem/doris-spark-connector.md",
+      "sourceHash": 
"89aeb845646272d599b9b9ca5f4fa469d06591a0e7227d10d8da48da0f62ff67"
+    },
+    {
+      "docId": "ecosystem/doris-streamloader",
+      "sourcePath": "releasenotes/ecosystem/doris-streamloader.md",
+      "sourceHash": 
"dd5ac0749629e2b676a658181aa07410df80f8bc8f6024c632f04a3d4ba8bda4"
+    },
+    {
+      "docId": "v1.1/release-1.1.0",
+      "sourcePath": "releasenotes/v1.1/release-1.1.0.md",
+      "sourceHash": 
"258d418c0d7e93e2c7326e584c2cca370c873e37cae5bf993fc56778873df9bc"
+    },
+    {
+      "docId": "v1.1/release-1.1.1",
+      "sourcePath": "releasenotes/v1.1/release-1.1.1.md",
+      "sourceHash": 
"0609e6b8a674c67cfda7ff819183144348b4a8626cf7b29a89af8b77ab59a142"
+    },
+    {
+      "docId": "v1.1/release-1.1.2",
+      "sourcePath": "releasenotes/v1.1/release-1.1.2.md",
+      "sourceHash": 
"8e82ee174e086d7d0eb645581209041aa25934f2c0d47f06a8e9fdd4d931ff77"
+    },
+    {
+      "docId": "v1.1/release-1.1.3",
+      "sourcePath": "releasenotes/v1.1/release-1.1.3.md",
+      "sourceHash": 
"f5a4510f8a4c659de1ed06b38f2d51af77b62d4214a5f92ebba07a448ab786af"
+    },
+    {
+      "docId": "v1.1/release-1.1.4",
+      "sourcePath": "releasenotes/v1.1/release-1.1.4.md",
+      "sourceHash": 
"669fa10d9852af0f2e23e114633edb74163df8f1f5c8ba9eeeb8d5df9f40d94b"
+    },
+    {
+      "docId": "v1.1/release-1.1.5",
+      "sourcePath": "releasenotes/v1.1/release-1.1.5.md",
+      "sourceHash": 
"c62c676fede523ffeed0d928a24bfe1ed02c3bf831c27ff96580b51de656e40a"
+    },
+    {
+      "docId": "v1.2/release-1.2.0",
+      "sourcePath": "releasenotes/v1.2/release-1.2.0.md",
+      "sourceHash": 
"7753839430aaaf7f0333df91fde807cbd729a9caf83de2938352cad9a4418427"
+    },
+    {
+      "docId": "v1.2/release-1.2.1",
+      "sourcePath": "releasenotes/v1.2/release-1.2.1.md",
+      "sourceHash": 
"08376473f8c1a45ad07a8da04be70eb1874faede274b1dd074fbb482596ec749"
+    },
+    {
+      "docId": "v1.2/release-1.2.2",
+      "sourcePath": "releasenotes/v1.2/release-1.2.2.md",
+      "sourceHash": 
"733723b493a62f8684f9f6f0c1dff75b2788b9e0b69a78eb56db2c8f86b5118b"
+    },
+    {
+      "docId": "v1.2/release-1.2.3",
+      "sourcePath": "releasenotes/v1.2/release-1.2.3.md",
+      "sourceHash": 
"f8066c4e30bcf1d77ac184b9ddffe4bd0ed1465fab5e118bce6610d65bca4d38"
+    },
+    {
+      "docId": "v1.2/release-1.2.4",
+      "sourcePath": "releasenotes/v1.2/release-1.2.4.md",
+      "sourceHash": 
"ccd81744c6cc257a2b99a34da362c8e88e7d58b05c4a0381ae63c5975a810225"
+    },
+    {
+      "docId": "v1.2/release-1.2.5",
+      "sourcePath": "releasenotes/v1.2/release-1.2.5.md",
+      "sourceHash": 
"bf9487b04132700d6db0ed1be42047cf3bbee06efd62220f653379e44be29b09"
+    },
+    {
+      "docId": "v1.2/release-1.2.6",
+      "sourcePath": "releasenotes/v1.2/release-1.2.6.md",
+      "sourceHash": 
"8ac327d956f0e56c025d795abe48878b7fe917e5e95ccb754493923757915d68"
+    },
+    {
+      "docId": "v1.2/release-1.2.7",
+      "sourcePath": "releasenotes/v1.2/release-1.2.7.md",
+      "sourceHash": 
"1df6eaf88b34e7fc4ced08d18859b6891a52f4b02c8a0ab7da8d04333d822d1e"
+    },
+    {
+      "docId": "v1.2/release-1.2.8",
+      "sourcePath": "releasenotes/v1.2/release-1.2.8.md",
+      "sourceHash": 
"be475325e33ae1e27884b58fb983eca076f8bf268ed09562dbd9e47e4f5cea1b"
+    },
+    {
+      "docId": "v2.0/release-2.0.0",
+      "sourcePath": "releasenotes/v2.0/release-2.0.0.md",
+      "sourceHash": 
"5714e0bb902451809eb0c8a8150be7e423db1a95a9b7b7ce8376e020556caa28"
+    },
+    {
+      "docId": "v2.0/release-2.0.1",
+      "sourcePath": "releasenotes/v2.0/release-2.0.1.md",
+      "sourceHash": 
"9643e05d30d2fa86844882efbe65e6747118db4dcdac175dd9042ddfc6c14570"
+    },
+    {
+      "docId": "v2.0/release-2.0.10",
+      "sourcePath": "releasenotes/v2.0/release-2.0.10.md",
+      "sourceHash": 
"f94b574439f8ca7b85d3b6bf3c8fbea12d211fa918e60c0233d1c3f35e998c18"
+    },
+    {
+      "docId": "v2.0/release-2.0.11",
+      "sourcePath": "releasenotes/v2.0/release-2.0.11.md",
+      "sourceHash": 
"bb0d97ebcb34d440aeedf06b1316f37cd6e4d21cea84e6fac578275d35648e85"
+    },
+    {
+      "docId": "v2.0/release-2.0.12",
+      "sourcePath": "releasenotes/v2.0/release-2.0.12.md",
+      "sourceHash": 
"4855df1592d495b233d3e61bf9d9071ad564fe85a25275c38ff1752a10f16b1e"
+    },
+    {
+      "docId": "v2.0/release-2.0.13",
+      "sourcePath": "releasenotes/v2.0/release-2.0.13.md",
+      "sourceHash": 
"4748d3af8414fec9bafefc1ecc9349165d72f382d1b0e9888ec91cdc1560f5fe"
+    },
+    {
+      "docId": "v2.0/release-2.0.14",
+      "sourcePath": "releasenotes/v2.0/release-2.0.14.md",
+      "sourceHash": 
"d4f83609d816fd0d0dc867b13f766c7adb2bc5452634c2b1dfc5e1d8e0addd14"
+    },
+    {
+      "docId": "v2.0/release-2.0.15",
+      "sourcePath": "releasenotes/v2.0/release-2.0.15.md",
+      "sourceHash": 
"8d824f2faf0cd199a8eb5e682b550d18e097390de05a04b97ab60994ff3e346a"
+    },
+    {
+      "docId": "v2.0/release-2.0.2",
+      "sourcePath": "releasenotes/v2.0/release-2.0.2.md",
+      "sourceHash": 
"a6ef57e3ecbb9b519d785138113e6bd4440fd92e5ff14ef5e29db19116494b0c"
+    },
+    {
+      "docId": "v2.0/release-2.0.3",
+      "sourcePath": "releasenotes/v2.0/release-2.0.3.md",
+      "sourceHash": 
"39f2c50cc80c5e5fbc0780ede2cc0dfd6d19670dd5a1ff12c82590cf3f6083b8"
+    },
+    {
+      "docId": "v2.0/release-2.0.4",
+      "sourcePath": "releasenotes/v2.0/release-2.0.4.md",
+      "sourceHash": 
"bfbc06bbf43e14fa06f2b66871cb1741b925030972c95f99600c25ba92dbd4ab"
+    },
+    {
+      "docId": "v2.0/release-2.0.5",
+      "sourcePath": "releasenotes/v2.0/release-2.0.5.md",
+      "sourceHash": 
"323ebef54b8aa7c59595f8e251f9efa35c082e90e22c864f4bdfb2e83b402c5e"
+    },
+    {
+      "docId": "v2.0/release-2.0.6",
+      "sourcePath": "releasenotes/v2.0/release-2.0.6.md",
+      "sourceHash": 
"f26798750b09dab6c3e4eae510c58045e6bff2ca806cb557e383063ae5186075"
+    },
+    {
+      "docId": "v2.0/release-2.0.7",
+      "sourcePath": "releasenotes/v2.0/release-2.0.7.md",
+      "sourceHash": 
"09ec0b145fe543754bf1a63ae35ab2a0757e51fea2387b257540a0c92b1e5039"
+    },
+    {
+      "docId": "v2.0/release-2.0.8",
+      "sourcePath": "releasenotes/v2.0/release-2.0.8.md",
+      "sourceHash": 
"e1951e12a66efc185c3122c5d337c6787478c58ba11b6273188f9a9cbab4e4b2"
+    },
+    {
+      "docId": "v2.0/release-2.0.9",
+      "sourcePath": "releasenotes/v2.0/release-2.0.9.md",
+      "sourceHash": 
"a4644fb253b6ea4bd497c08d4da7f3cae84f6737bb5f50a9b6561d019cb1ed93"
+    },
+    {
+      "docId": "v2.1/release-2.1.0",
+      "sourcePath": "releasenotes/v2.1/release-2.1.0.md",
+      "sourceHash": 
"9510af3cc26eba327ad96e1a4ca619ca0bf2b1a748f91cc844f7f5c07aff987c"
+    },
+    {
+      "docId": "v2.1/release-2.1.1",
+      "sourcePath": "releasenotes/v2.1/release-2.1.1.md",
+      "sourceHash": 
"55d7946e87ce4c5522ff7f786a9f17ec3d00369657c3bd988158b4af356aff6a"
+    },
+    {
+      "docId": "v2.1/release-2.1.10",
+      "sourcePath": "releasenotes/v2.1/release-2.1.10.md",
+      "sourceHash": 
"5adffc245c59f5378ae2c65a7486c7b67c2a2f456f4172601148ee2b9775f178"
+    },
+    {
+      "docId": "v2.1/release-2.1.11",
+      "sourcePath": "releasenotes/v2.1/release-2.1.11.md",
+      "sourceHash": 
"01a26efab9251e3471432d24210ce824496dd2073f4a1d2a79c2bf5f5ad33457"
+    },
+    {
+      "docId": "v2.1/release-2.1.2",
+      "sourcePath": "releasenotes/v2.1/release-2.1.2.md",
+      "sourceHash": 
"f4d4fb5f1d3eb59d4df2569ba5ea6496d0825c7b91d96ec9b88d448b9c918961"
+    },
+    {
+      "docId": "v2.1/release-2.1.3",
+      "sourcePath": "releasenotes/v2.1/release-2.1.3.md",
+      "sourceHash": 
"d54d3252d42244047993b90cd20fe3098052a5653922b6d96fcb521a2e281314"
+    },
+    {
+      "docId": "v2.1/release-2.1.4",
+      "sourcePath": "releasenotes/v2.1/release-2.1.4.md",
+      "sourceHash": 
"ce21a04e3dd6cfd51bec00d815b83c98f6a6f39983e1b529414fb570f4bff2d2"
+    },
+    {
+      "docId": "v2.1/release-2.1.5",
+      "sourcePath": "releasenotes/v2.1/release-2.1.5.md",
+      "sourceHash": 
"02a22e10fcb4bb236a1e2711af5eb6e7903bd9d2e905f59914121eb592e43e9a"
+    },
+    {
+      "docId": "v2.1/release-2.1.6",
+      "sourcePath": "releasenotes/v2.1/release-2.1.6.md",
+      "sourceHash": 
"36fd06bf7bac7c67128778d05dfbfb1405bc80b3bc80e541b560b3ec599c82a8"
+    },
+    {
+      "docId": "v2.1/release-2.1.7",
+      "sourcePath": "releasenotes/v2.1/release-2.1.7.md",
+      "sourceHash": 
"81d9da477b72199c956134ef29ddaecc5c39bf4edcd460a16ab59163bd4ac8d8"
+    },
+    {
+      "docId": "v2.1/release-2.1.8",
+      "sourcePath": "releasenotes/v2.1/release-2.1.8.md",
+      "sourceHash": 
"dda4268a8c304eaa01477ef9ff34f5f46298a02d1bad547529f646645d404ead"
+    },
+    {
+      "docId": "v2.1/release-2.1.9",
+      "sourcePath": "releasenotes/v2.1/release-2.1.9.md",
+      "sourceHash": 
"9a073bfaf6d5789efb8fbf42b52c8ca28f531ffe15179b6d140bf93fbd847ddb"
+    },
+    {
+      "docId": "v3.0/release-3.0.0",
+      "sourcePath": "releasenotes/v3.0/release-3.0.0.md",
+      "sourceHash": 
"346191397c7e8bba4cb5e41cce17863e2545ab6106e973dd99d3b0399b905269"
+    },
+    {
+      "docId": "v3.0/release-3.0.1",
+      "sourcePath": "releasenotes/v3.0/release-3.0.1.md",
+      "sourceHash": 
"086c5693948c48cc547392c4c9f63ecfbb66c917f3ab1af39076c9643647697e"
+    },
+    {
+      "docId": "v3.0/release-3.0.2",
+      "sourcePath": "releasenotes/v3.0/release-3.0.2.md",
+      "sourceHash": 
"18abdab8852acf2c751a46d5e0fbcdff355cfeaf22bfd167dfb9da286d1b30c4"
+    },
+    {
+      "docId": "v3.0/release-3.0.3",
+      "sourcePath": "releasenotes/v3.0/release-3.0.3.md",
+      "sourceHash": 
"e399289eaab25a1bd406b8c76783b74e0d5b359c91a7d2d25e0b8b94a82a1da7"
+    },
+    {
+      "docId": "v3.0/release-3.0.4",
+      "sourcePath": "releasenotes/v3.0/release-3.0.4.md",
+      "sourceHash": 
"e08e7b3aa3f9331b77e4043ff0b2ab27b96a5317b6cd1a8e4c499a193cfb3886"
+    },
+    {
+      "docId": "v3.0/release-3.0.5",
+      "sourcePath": "releasenotes/v3.0/release-3.0.5.md",
+      "sourceHash": 
"6bd2333765efb92cd6aa14d2dfe3505e0b12c3aab8addedce1f3c001ce575768"
+    },
+    {
+      "docId": "v3.0/release-3.0.6",
+      "sourcePath": "releasenotes/v3.0/release-3.0.6.md",
+      "sourceHash": 
"ed4d69ddade3800aeba77746aabfe35cbf9bf9492f4fcef7b334307348822bb8"
+    },
+    {
+      "docId": "v3.0/release-3.0.7",
+      "sourcePath": "releasenotes/v3.0/release-3.0.7.md",
+      "sourceHash": 
"8f42f8eabd9659cbb1e994df13ee15ec57ad1fb2c0893ab850d2b5242812e598"
+    },
+    {
+      "docId": "v3.0/release-3.0.8",
+      "sourcePath": "releasenotes/v3.0/release-3.0.8.md",
+      "sourceHash": 
"1513ddf8bc9c2283568eab5ca08f4a14bad896db1458a5b0b72c4fc6d0fe72d4"
+    },
+    {
+      "docId": "v3.1/release-3.1.0",
+      "sourcePath": "releasenotes/v3.1/release-3.1.0.md",
+      "sourceHash": 
"54fc225e329e341f1b28566d7ce74b420a7ecb1fcdd5f57779d17a1bc7646847"
+    },
+    {
+      "docId": "v3.1/release-3.1.1",
+      "sourcePath": "releasenotes/v3.1/release-3.1.1.md",
+      "sourceHash": 
"e5809a67ec4977a6203d5db6384d6ea6383261727fd579721c47bad9c99a8e47"
+    },
+    {
+      "docId": "v3.1/release-3.1.2",
+      "sourcePath": "releasenotes/v3.1/release-3.1.2.md",
+      "sourceHash": 
"cc93666eeb50039d2fb563940a1fb6e434ccc7fbe741f714453066f0a3b6e020"
+    },
+    {
+      "docId": "v3.1/release-3.1.3",
+      "sourcePath": "releasenotes/v3.1/release-3.1.3.md",
+      "sourceHash": 
"3fbf7fdb1bf5c3264bb5304be8db79edc1691d0fc5b984bf74bb9844595bb6ce"
+    },
+    {
+      "docId": "v3.1/release-3.1.4",
+      "sourcePath": "releasenotes/v3.1/release-3.1.4.md",
+      "sourceHash": 
"1153ef38791c75651067427cb63115bb6b7ee86a9206935d3746501bd21c2326"
+    },
+    {
+      "docId": "v4.0/release-4.0.0",
+      "sourcePath": "releasenotes/v4.0/release-4.0.0.md",
+      "sourceHash": 
"a0c6bd68abc36e2ff8f59dedd8574de502a86d5d9ea73d5c07fbcffc3756c17d"
+    },
+    {
+      "docId": "v4.0/release-4.0.1",
+      "sourcePath": "releasenotes/v4.0/release-4.0.1.md",
+      "sourceHash": 
"2b6cf112245ac12e131065e44829e6ebe8acf7785b65a82cead70cbd8dbfc2f6"
+    },
+    {
+      "docId": "v4.0/release-4.0.2",
+      "sourcePath": "releasenotes/v4.0/release-4.0.2.md",
+      "sourceHash": 
"6c681f7ce1c71a08bb792f50ef11cf34a68e18692d8e8f202f71d9620ef192c4"
+    },
+    {
+      "docId": "v4.0/release-4.0.3",
+      "sourcePath": "releasenotes/v4.0/release-4.0.3.md",
+      "sourceHash": 
"1acaf4e8f8b0b4e9aed9fa3f550f91aa4d7dff7b8f50e47342f1102e90e0880b"
+    },
+    {
+      "docId": "v4.0/release-4.0.4",
+      "sourcePath": "releasenotes/v4.0/release-4.0.4.md",
+      "sourceHash": 
"c8a725af3fd77a73d37efc370501506b71619debe58676f68311ae7aa34fcd30"
+    },
+    {
+      "docId": "v4.0/release-4.0.5",
+      "sourcePath": "releasenotes/v4.0/release-4.0.5.md",
+      "sourceHash": 
"c4000459fb999fdc33c8efad7d7de2cd1f6093b5b0f38bc0727feac678613cbd"
+    },
+    {
+      "docId": "v4.0/release-4.0.6",
+      "sourcePath": "releasenotes/v4.0/release-4.0.6.md",
+      "sourceHash": 
"643fa678a51dadf09bdd9af190e13d2568e1277d25334e51bb2f523794892515"
+    },
+    {
+      "docId": "v4.0/release-4.0.7",
+      "sourcePath": "releasenotes/v4.0/release-4.0.7.md",
+      "sourceHash": 
"096052ed0e7d93f03a6c73270470c42f6e680bdb75814e9b0578fbe3d04a39b4"
+    },
+    {
+      "docId": "v4.0/release-4.0.8",
+      "sourcePath": "releasenotes/v4.0/release-4.0.8.md",
+      "sourceHash": 
"794469ab16e546baa6627ab1c978579e6ffa44a8e3b93cfeb332c39753efffbb"
+    },
+    {
+      "docId": "v4.1/release-4.1.0",
+      "sourcePath": "releasenotes/v4.1/release-4.1.0.md",
+      "sourceHash": 
"82bd7d1d958c98220464d45a7ed43c25ca59f90f82bdacae5f2133affa760292"
+    },
+    {
+      "docId": "v4.1/release-4.1.1",
+      "sourcePath": "releasenotes/v4.1/release-4.1.1.md",
+      "sourceHash": 
"5546a2af12f0d1f8d12813dbb0a2092c61c5dca04a4aedf25053cf092fe4b718"
+    },
+    {
+      "docId": "v4.1/release-4.1.2",
+      "sourcePath": "releasenotes/v4.1/release-4.1.2.md",
+      "sourceHash": 
"817c126dd9b3546cf4cad63edf20a93ebe7b56394fcc986d20b888fb7b16ffca"
+    },
+    {
+      "docId": "v4.1/release-4.1.3",
+      "sourcePath": "releasenotes/v4.1/release-4.1.3.md",
+      "sourceHash": 
"1b26109c975a9e5cbbbd09b27fad032bc74e520ddf291ae87f057d9d1d541b05"
+    },
+    {
+      "docId": "v4.1/release-4.1.4",
+      "sourcePath": "releasenotes/v4.1/release-4.1.4.md",
+      "sourceHash": 
"5beeb0e25d86cd57f1ca39e70f4fbef352b65d4869d2135555ed27d0d7573105"
+    },
+    {
+      "docId": "v4.1/release-4.1.4.1",
+      "sourcePath": "releasenotes/v4.1/release-4.1.4.1.md",
+      "sourceHash": 
"fe55d5c11b933ae46798ef59ba3f4ce8a13cb819c1becbea15a3395c755df244"
+    },
+    {
+      "docId": "v4.1/release-4.1.5",
+      "sourcePath": "releasenotes/v4.1/release-4.1.5.md",
+      "sourceHash": 
"908effe8089816fb8ca499104daa5394507d2b7bbfa5423c330ad92d3aeb1044"
+    }
+  ],
+  "sourceFiles": [
+    "releasenotes/all-release.md",
+    "releasenotes/core.md",
+    "releasenotes/ecosystem/doris-cli.md",
+    "releasenotes/ecosystem/doris-flink-connector.md",
+    "releasenotes/ecosystem/doris-kafka-connector.md",
+    "releasenotes/ecosystem/doris-mcp-server.md",
+    "releasenotes/ecosystem/doris-operator.md",
+    "releasenotes/ecosystem/doris-skills.md",
+    "releasenotes/ecosystem/doris-spark-connector.md",
+    "releasenotes/ecosystem/doris-streamloader.md",
+    "releasenotes/v1.1/release-1.1.0.md",
+    "releasenotes/v1.1/release-1.1.1.md",
+    "releasenotes/v1.1/release-1.1.2.md",
+    "releasenotes/v1.1/release-1.1.3.md",
+    "releasenotes/v1.1/release-1.1.4.md",
+    "releasenotes/v1.1/release-1.1.5.md",
+    "releasenotes/v1.2/release-1.2.0.md",
+    "releasenotes/v1.2/release-1.2.1.md",
+    "releasenotes/v1.2/release-1.2.2.md",
+    "releasenotes/v1.2/release-1.2.3.md",
+    "releasenotes/v1.2/release-1.2.4.md",
+    "releasenotes/v1.2/release-1.2.5.md",
+    "releasenotes/v1.2/release-1.2.6.md",
+    "releasenotes/v1.2/release-1.2.7.md",
+    "releasenotes/v1.2/release-1.2.8.md",
+    "releasenotes/v2.0/release-2.0.0.md",
+    "releasenotes/v2.0/release-2.0.1.md",
+    "releasenotes/v2.0/release-2.0.10.md",
+    "releasenotes/v2.0/release-2.0.11.md",
+    "releasenotes/v2.0/release-2.0.12.md",
+    "releasenotes/v2.0/release-2.0.13.md",
+    "releasenotes/v2.0/release-2.0.14.md",
+    "releasenotes/v2.0/release-2.0.15.md",
+    "releasenotes/v2.0/release-2.0.2.md",
+    "releasenotes/v2.0/release-2.0.3.md",
+    "releasenotes/v2.0/release-2.0.4.md",
+    "releasenotes/v2.0/release-2.0.5.md",
+    "releasenotes/v2.0/release-2.0.6.md",
+    "releasenotes/v2.0/release-2.0.7.md",
+    "releasenotes/v2.0/release-2.0.8.md",
+    "releasenotes/v2.0/release-2.0.9.md",
+    "releasenotes/v2.1/release-2.1.0.md",
+    "releasenotes/v2.1/release-2.1.1.md",
+    "releasenotes/v2.1/release-2.1.10.md",
+    "releasenotes/v2.1/release-2.1.11.md",
+    "releasenotes/v2.1/release-2.1.2.md",
+    "releasenotes/v2.1/release-2.1.3.md",
+    "releasenotes/v2.1/release-2.1.4.md",
+    "releasenotes/v2.1/release-2.1.5.md",
+    "releasenotes/v2.1/release-2.1.6.md",
+    "releasenotes/v2.1/release-2.1.7.md",
+    "releasenotes/v2.1/release-2.1.8.md",
+    "releasenotes/v2.1/release-2.1.9.md",
+    "releasenotes/v3.0/release-3.0.0.md",
+    "releasenotes/v3.0/release-3.0.1.md",
+    "releasenotes/v3.0/release-3.0.2.md",
+    "releasenotes/v3.0/release-3.0.3.md",
+    "releasenotes/v3.0/release-3.0.4.md",
+    "releasenotes/v3.0/release-3.0.5.md",
+    "releasenotes/v3.0/release-3.0.6.md",
+    "releasenotes/v3.0/release-3.0.7.md",
+    "releasenotes/v3.0/release-3.0.8.md",
+    "releasenotes/v3.1/release-3.1.0.md",
+    "releasenotes/v3.1/release-3.1.1.md",
+    "releasenotes/v3.1/release-3.1.2.md",
+    "releasenotes/v3.1/release-3.1.3.md",
+    "releasenotes/v3.1/release-3.1.4.md",
+    "releasenotes/v4.0/release-4.0.0.md",
+    "releasenotes/v4.0/release-4.0.1.md",
+    "releasenotes/v4.0/release-4.0.2.md",
+    "releasenotes/v4.0/release-4.0.3.md",
+    "releasenotes/v4.0/release-4.0.4.md",
+    "releasenotes/v4.0/release-4.0.5.md",
+    "releasenotes/v4.0/release-4.0.6.md",
+    "releasenotes/v4.0/release-4.0.7.md",
+    "releasenotes/v4.0/release-4.0.8.md",
+    "releasenotes/v4.1/release-4.1.0.md",
+    "releasenotes/v4.1/release-4.1.1.md",
+    "releasenotes/v4.1/release-4.1.2.md",
+    "releasenotes/v4.1/release-4.1.3.md",
+    "releasenotes/v4.1/release-4.1.4.1.md",
+    "releasenotes/v4.1/release-4.1.4.md",
+    "releasenotes/v4.1/release-4.1.5.md"
+  ],
+  "historicalPaths": [
+    "releasenotes/all-release.md",
+    "releasenotes/core.md",
+    "releasenotes/ecosystem/doris-cli.md",
+    "releasenotes/ecosystem/doris-flink-connector.md",
+    "releasenotes/ecosystem/doris-kafka-connector.md",
+    "releasenotes/ecosystem/doris-mcp-server.md",
+    "releasenotes/ecosystem/doris-operator.md",
+    "releasenotes/ecosystem/doris-skills.md",
+    "releasenotes/ecosystem/doris-spark-connector.md",
+    "releasenotes/ecosystem/doris-streamloader.md",
+    "releasenotes/v1.1/release-1.1.0.md",
+    "releasenotes/v1.1/release-1.1.1.md",
+    "releasenotes/v1.1/release-1.1.2.md",
+    "releasenotes/v1.1/release-1.1.3.md",
+    "releasenotes/v1.1/release-1.1.4.md",
+    "releasenotes/v1.1/release-1.1.5.md",
+    "releasenotes/v1.2/release-1.2.0.md",
+    "releasenotes/v1.2/release-1.2.1.md",
+    "releasenotes/v1.2/release-1.2.2.md",
+    "releasenotes/v1.2/release-1.2.3.md",
+    "releasenotes/v1.2/release-1.2.4.md",
+    "releasenotes/v1.2/release-1.2.5.md",
+    "releasenotes/v1.2/release-1.2.6.md",
+    "releasenotes/v1.2/release-1.2.7.md",
+    "releasenotes/v1.2/release-1.2.8.md",
+    "releasenotes/v2.0/release-2.0.0.md",
+    "releasenotes/v2.0/release-2.0.1.md",
+    "releasenotes/v2.0/release-2.0.10.md",
+    "releasenotes/v2.0/release-2.0.11.md",
+    "releasenotes/v2.0/release-2.0.12.md",
+    "releasenotes/v2.0/release-2.0.13.md",
+    "releasenotes/v2.0/release-2.0.14.md",
+    "releasenotes/v2.0/release-2.0.15.md",
+    "releasenotes/v2.0/release-2.0.2.md",
+    "releasenotes/v2.0/release-2.0.3.md",
+    "releasenotes/v2.0/release-2.0.4.md",
+    "releasenotes/v2.0/release-2.0.5.md",
+    "releasenotes/v2.0/release-2.0.6.md",
+    "releasenotes/v2.0/release-2.0.7.md",
+    "releasenotes/v2.0/release-2.0.8.md",
+    "releasenotes/v2.0/release-2.0.9.md",
+    "releasenotes/v2.1/release-2.1.0.md",
+    "releasenotes/v2.1/release-2.1.1.md",
+    "releasenotes/v2.1/release-2.1.10.md",
+    "releasenotes/v2.1/release-2.1.11.md",
+    "releasenotes/v2.1/release-2.1.2.md",
+    "releasenotes/v2.1/release-2.1.3.md",
+    "releasenotes/v2.1/release-2.1.4.md",
+    "releasenotes/v2.1/release-2.1.5.md",
+    "releasenotes/v2.1/release-2.1.6.md",
+    "releasenotes/v2.1/release-2.1.7.md",
+    "releasenotes/v2.1/release-2.1.8.md",
+    "releasenotes/v2.1/release-2.1.9.md",
+    "releasenotes/v3.0/release-3.0.0.md",
+    "releasenotes/v3.0/release-3.0.1.md",
+    "releasenotes/v3.0/release-3.0.2.md",
+    "releasenotes/v3.0/release-3.0.3.md",
+    "releasenotes/v3.0/release-3.0.4.md",
+    "releasenotes/v3.0/release-3.0.5.md",
+    "releasenotes/v3.0/release-3.0.6.md",
+    "releasenotes/v3.0/release-3.0.7.md",
+    "releasenotes/v3.0/release-3.0.8.md",
+    "releasenotes/v3.1/release-3.1.0.md",
+    "releasenotes/v3.1/release-3.1.1.md",
+    "releasenotes/v3.1/release-3.1.2.md",
+    "releasenotes/v3.1/release-3.1.3.md",
+    "releasenotes/v3.1/release-3.1.4.md",
+    "releasenotes/v4.0/release-4.0.0.md",
+    "releasenotes/v4.0/release-4.0.1.md",
+    "releasenotes/v4.0/release-4.0.2.md",
+    "releasenotes/v4.0/release-4.0.3.md",
+    "releasenotes/v4.0/release-4.0.4.md",
+    "releasenotes/v4.0/release-4.0.5.md",
+    "releasenotes/v4.0/release-4.0.6.md",
+    "releasenotes/v4.0/release-4.0.7.md",
+    "releasenotes/v4.0/release-4.0.8.md",
+    "releasenotes/v4.1/release-4.1.0.md",
+    "releasenotes/v4.1/release-4.1.1.md",
+    "releasenotes/v4.1/release-4.1.2.md",
+    "releasenotes/v4.1/release-4.1.3.md",
+    "releasenotes/v4.1/release-4.1.4.1.md",
+    "releasenotes/v4.1/release-4.1.4.md",
+    "releasenotes/v4.1/release-4.1.5.md"
+  ]
+}
diff --git a/translations/scope/ja.json b/translations/scope/ja.json
index 6480136e7207..40ac9da14a4b 100644
--- a/translations/scope/ja.json
+++ b/translations/scope/ja.json
@@ -6,7 +6,8 @@
     "DEV",
     "4X",
     "COMMUNITY",
-    "COURSE"
+    "COURSE",
+    "RELEASES"
   ],
   "groups": [
     {
@@ -37,7 +38,11 @@
       "id": "RELEASES",
       "pluginId": "releases",
       "version": "current",
-      "sourceRoot": "releasenotes"
+      "sourceRoot": "releasenotes",
+      "sourcePolicy": {
+        "mode": "new-only",
+        "baseline": "translations/release-notes-baseline.json"
+      }
     },
     {
       "id": "COURSEUI",
diff --git a/translations/scope/ko.json b/translations/scope/ko.json
index acee050bd895..0e1afdbd9051 100644
--- a/translations/scope/ko.json
+++ b/translations/scope/ko.json
@@ -6,7 +6,8 @@
     "DEV",
     "4X",
     "COMMUNITY",
-    "COURSE"
+    "COURSE",
+    "RELEASES"
   ],
   "groups": [
     {
@@ -37,7 +38,11 @@
       "id": "RELEASES",
       "pluginId": "releases",
       "version": "current",
-      "sourceRoot": "releasenotes"
+      "sourceRoot": "releasenotes",
+      "sourcePolicy": {
+        "mode": "new-only",
+        "baseline": "translations/release-notes-baseline.json"
+      }
     },
     {
       "id": "COURSEUI",
diff --git a/translations/scope/ru.json b/translations/scope/ru.json
index 73208a9683cd..f18eef027392 100644
--- a/translations/scope/ru.json
+++ b/translations/scope/ru.json
@@ -6,7 +6,8 @@
     "DEV",
     "4X",
     "COMMUNITY",
-    "COURSE"
+    "COURSE",
+    "RELEASES"
   ],
   "groups": [
     {
@@ -37,7 +38,11 @@
       "id": "RELEASES",
       "pluginId": "releases",
       "version": "current",
-      "sourceRoot": "releasenotes"
+      "sourceRoot": "releasenotes",
+      "sourcePolicy": {
+        "mode": "new-only",
+        "baseline": "translations/release-notes-baseline.json"
+      }
     },
     {
       "id": "COURSEUI",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to