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 88f587c5542 [docs] Add Apache Doris 4.1.3 release info (#3985)
88f587c5542 is described below
commit 88f587c5542884187fd701ad5650854b2f78ee63
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Wed Jul 15 20:03:33 2026 +0800
[docs] Add Apache Doris 4.1.3 release info (#3985)
## Summary
- Publish Apache Doris 4.1.3 as Latest in the quick-download and
all-releases data, using source filename version 4.1.3-rc02.
- Add synchronized English and Chinese release notes, Core index
entries, and sidebar navigation; restore the missing 4.1.2 entry in
ALL_VERSIONS.
- Improve the add-release skill with Core index routing, RC-aware
artifact handling, bilingual and version-drift checks, validation tiers,
and a reusable validator with regression tests.
## Validation
- `node --test
doc-tools/skills/add-release/scripts/validate-release.test.mjs` (4/4
passed)
- `node doc-tools/skills/add-release/scripts/validate-release.mjs
--component doris-core --version 4.1.3 --series 4.1 --source-version
4.1.3-rc02 --release-date 2026-07-13 --position latest --source-dir
https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/` (70 checks
passed, including all 12 artifact URLs)
- Skill structure validation passed with skill-creator quick_validate.py
- `sidebarsReleases.json` JSON parse check
- `git diff --check`
## Build
- `yarn typecheck` and `yarn build` were not run per request.
---
doc-tools/skills/add-release/SKILL.md | 447 ++++++-----
.../add-release/scripts/validate-release.mjs | 839 +++++++++++++++++++++
.../add-release/scripts/validate-release.test.mjs | 232 ++++++
.../current/core.md | 4 +-
.../current/v4.1/release-4.1.3.md | 149 ++++
releasenotes/core.md | 4 +-
releasenotes/v4.1/release-4.1.3.md | 149 ++++
sidebarsReleases.json | 1 +
src/constant/download.data.ts | 104 ++-
9 files changed, 1697 insertions(+), 232 deletions(-)
diff --git a/doc-tools/skills/add-release/SKILL.md
b/doc-tools/skills/add-release/SKILL.md
index 31d433066fe..ab2a0760e60 100644
--- a/doc-tools/skills/add-release/SKILL.md
+++ b/doc-tools/skills/add-release/SKILL.md
@@ -1,329 +1,318 @@
---
name: add-release
-description: Update the Apache Doris website for a new release of Doris core
or a Doris ecosystem component. Use when a release manager asks to add or
update website release information, download links, release notes, all-release
indexes, release sidebar entries, or ecosystem release pages for versions such
as Doris 4.0.7, Flink Connector 26.1.1, Spark Connector 26.0.0, Kafka Connector
26.0.0, Doris Operator 25.8.0, Doris CLI, Doris MCP Server, Doris Skills, or
Streamloader. Always confi [...]
+description: Publish a new Apache Doris Core or Doris ecosystem release on the
Doris website. Use when updating download data, bilingual release notes, the
Doris Core release index, release navigation, or ecosystem release pages.
Confirm the component and release metadata first because Core and ecosystem
releases use different files and validation paths.
---
# Add Doris Release
-Use this skill to update the Apache Doris website after a Doris core or
ecosystem component release is ready. The goal is to publish the right release
content in `/download/` and `/releases/` without accidentally applying the
Doris core workflow to an ecosystem component, or the reverse.
+Publish release information to /download/ and /releases/ from the
doris-website repository root. Keep the change focused, preserve user work
already in the tree, and do not commit unless the release manager asks.
-Work from the `doris-website` repository root.
+## Operating Rules
-## Component Confirmation Gate
+- Treat the current repository structure, AGENTS.md, sidebarsReleases.json,
and src/constant/download.data.ts as authoritative. If an example in this skill
conflicts with the repository, follow the repository and report the drift.
+- Inspect nearby releases for the same component before editing. Match their
paths, frontmatter, ordering, terminology, and data shape.
+- Confirm public artifacts rather than deriving filenames only from the public
version.
+- Update English and zh-CN together whenever both website mirrors exist,
unless the release manager explicitly narrows the locale scope.
+- Keep release-note issue references and section structure aligned between
languages.
+- Do not run yarn typecheck or yarn build when the release manager says no
compile, no compilation, or equivalent. Still run the non-compilation checks.
-Before editing any file, identify the release component and ask the release
manager to confirm it interactively.
+## Preflight
-If the user already names a component, restate it and ask for confirmation
before proceeding. If the component is ambiguous, ask one concise question with
the likely choices.
+Read these sources before choosing a workflow:
-Use these canonical component IDs:
+- AGENTS.md
+- src/constant/download.data.ts
+- sidebarsReleases.json
+- releasenotes/all-release.md
+- releasenotes/core.md
+- i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/all-release.md
+- i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
+- One recent release for the same component in both locales
+- Recent git history for the same files when repository intent remains unclear
-| Component ID | Website area | Typical release artifact area |
-| --- | --- | --- |
-| `doris-core` | Core release notes, `/download/` core packages, all-release
indexes | `https://downloads.apache.org/doris/<series>/<version>/` |
-| `doris-flink-connector` | Ecosystem release page and `TOOL_VERSIONS` Flink
entry | `https://downloads.apache.org/doris/flink-connector/<version>/` |
-| `doris-spark-connector` | Ecosystem release page and `TOOL_VERSIONS` Spark
entry | `https://downloads.apache.org/doris/spark-connector/<version>/` |
-| `doris-kafka-connector` | Ecosystem release page and `TOOL_VERSIONS` Kafka
entry | `https://downloads.apache.org/doris/kafka-connector/<version>/` |
-| `doris-streamloader` | Ecosystem release page and `TOOL_VERSIONS`
Streamloader entry when applicable | Confirm artifact location from nearby
entries or the release manager |
-| `doris-operator` | Ecosystem release page; image/chart links when applicable
| Confirm image/chart/source locations from the release manager |
-| `doris-mcp-server` | Ecosystem release page | Confirm artifact or package
locations from the release manager |
-| `doris-skills` | Ecosystem release page | Confirm artifact or package
locations from the release manager |
-| `doris-cli` | Ecosystem release page | Confirm artifact or package locations
from the release manager |
-| `other-ecosystem` | New or less common ecosystem release page | Confirm page
path, sidebar entry, and artifact locations |
-
-Do not infer `doris-core` only because the version looks numeric. Connector
and operator versions also look numeric.
-
-## Required Inputs from the Release Manager
-
-Ask for any missing item before editing if it cannot be discovered safely from
public release artifacts.
-
-Required for every component:
-
-- **Component**: one of the component IDs above, confirmed by the release
manager.
-- **Version**: full component version, for example `4.0.7`, `26.1.1`, or
`25.8.0`.
-- **Release note source**: usually a GitHub issue, PR, Markdown body, or
upstream release page.
-- **Release date**: prefer the official Apache download directory timestamp,
package publication date, or announcement date. Do not use issue creation time
unless the release manager confirms it.
-- **Localization expectation**: by default, update both English and zh-CN
release pages when both mirrors already exist.
-- **Scope of download updates**: confirm whether this release should update
`/download/`, only `/releases/`, or both.
-
-Additional for `doris-core`:
-
-- **Release series**: major/minor line used in Apache dist paths, for example
`4.0` for `4.0.7`.
-- **Source package location**: usually
`https://downloads.apache.org/doris/<series>/<version>/apache-doris-<version>-src.tar.gz`
and matching `.asc` / `.sha512`.
-- **Binary package locations**: confirm all supported binary tarballs exist on
the configured binary CDN:
- - `https://download.selectdb.com/apache-doris-<version>-bin-x64.tar.gz`
- -
`https://download.selectdb.com/apache-doris-<version>-bin-x64-noavx2.tar.gz`
- - `https://download.selectdb.com/apache-doris-<version>-bin-arm64.tar.gz`
- - matching `.asc` and `.sha512` files
-- **Source filename version suffix**: determine whether the source tarball
uses plain `<version>` or an RC-style suffix such as `<version>-rc02`. In
`download.data.ts`, the `version` field controls the source tarball filename
rendered by the download UI.
-- **Release positioning**: whether the release becomes `VersionEnum.Latest`,
`VersionEnum.Prev`, or only an entry in historical/all-release data.
-
-Additional for ecosystem components:
-
-- **Ecosystem page path**: usually `releasenotes/ecosystem/<component>.md` and
the matching zh-CN mirror.
-- **Download data target**: whether `src/constant/download.data.ts` has an
existing `TOOL_VERSIONS` entry for this component.
-- **Source artifact URL**: usually under
`https://downloads.apache.org/doris/<component-artifact-dir>/<version>/`.
-- **Binary/package URLs**: Maven coordinates, Docker images, Helm charts,
npm/PyPI packages, or other official distribution links as applicable.
-- **Compatibility matrix**: required for components with per-runtime variants,
such as Flink or Spark connector versions.
+Also run git status --short before editing. Existing unrelated modifications
and untracked files belong to the user and must remain untouched.
-## Choose the Workflow
+## Confirmation Gate
+
+Collect all missing release decisions in one concise confirmation instead of
asking one field at a time. Do not ask again for values the user has already
supplied.
+
+Use this template:
-After confirming the component, use only the matching workflow.
+ Please confirm:
+ - Component: <canonical component ID>
+ - Public release version: <version shown on the website>
+ - Release date: <YYYY-MM-DD>
+ - Release-note source: <GitHub issue, PR, Markdown, or upstream page>
+ - Website scope: </download/, /releases/, or both>
+ - Locales: <English, zh-CN, or both>
+ - Position: <Latest, Prev, Earlier, or historical>
+ - Source filename version: <plain version or RC-suffixed version>
+ - Source directory and binary origin: <URLs, if nonstandard>
-- Use **Doris Core Workflow** for `doris-core`.
-- Use **Ecosystem Component Workflow** for all other component IDs.
+Canonical component IDs:
+
+| Component ID | Website surface | Typical artifact directory |
+| --- | --- | --- |
+| doris-core | Core downloads, per-version notes, Core index, release sidebar
| https://dist.apache.org/repos/dist/release/doris/<series>/<version>/ |
+| doris-flink-connector | Flink ecosystem page and TOOL_VERSIONS when present
| https://downloads.apache.org/doris/flink-connector/<version>/ |
+| doris-spark-connector | Spark ecosystem page and TOOL_VERSIONS when present
| https://downloads.apache.org/doris/spark-connector/<version>/ |
+| doris-kafka-connector | Kafka ecosystem page and TOOL_VERSIONS when present
| https://downloads.apache.org/doris/kafka-connector/<version>/ |
+| doris-streamloader | Streamloader ecosystem page and download data when
present | Verify from the release directory |
+| doris-operator | Operator ecosystem page, images, and charts when applicable
| Verify from the release announcement |
+| doris-mcp-server | MCP Server ecosystem page | Verify package locations |
+| doris-skills | Doris Skills ecosystem page | Verify package locations |
+| doris-cli | Doris CLI ecosystem page | Verify package locations |
+| other-ecosystem | A new or less common ecosystem page | Confirm page,
sidebar, and artifact locations |
+
+Never infer doris-core only because a version has three numeric segments.
+
+## Choose the Workflow
-If the confirmed component does not fit either workflow, stop and ask the
release manager for the intended website surface before editing.
+- Use the Doris Core workflow only for doris-core.
+- Use the ecosystem workflow for every other component.
+- If the component has no established website surface, stop after discovery
and ask the release manager to confirm the intended page, navigation, and
download behavior.
## Doris Core Workflow
-### Files to Update
+### Distinguish the Two Version Values
-Update these files for a core release:
+Keep these values separate throughout the change:
-```text
-src/constant/download.data.ts
-releasenotes/v<series>/release-<version>.md
-i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v<series>/release-<version>.md
-releasenotes/all-release.md
-i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/all-release.md
-sidebarsReleases.json
-```
+- Public release version: the version shown in titles, links, VersionEnum,
labels, sidebar IDs, binary filenames, and release-note paths, such as 4.1.3.
+- Source filename version: the segment used by the published source tarball,
which may include an RC suffix such as 4.1.3-rc02.
-### Download Page Data
+The version field inside each architecture row in download.data.ts controls
the source filename rendered by the download UI. Never replace an RC-suffixed
source filename with the public version unless that exact plain source tarball
exists.
-Edit `src/constant/download.data.ts`.
+### Discover and Verify Artifacts
-Update `VersionEnum`:
+Inspect the official release directory before editing. Prefer the stable
dist.apache.org release repository when downloads.apache.org has not
synchronized yet:
-- If the new release is the newest headline version, update `Latest`.
-- If it is the stable previous series shown beside Latest, update `Prev`.
-- Leave `Earlier` alone unless the stable older line changes.
+ https://dist.apache.org/repos/dist/release/doris/<series>/<public-version>/
-Add the new version in both data structures:
+Record the exact source tarball filename and derive the source filename
version from it. Verify this complete matrix:
-- `DORIS_VERSIONS`: drives the quick-download version selector.
-- `ALL_VERSIONS`: drives the all-releases download form.
+| Artifact | Required files |
+| --- | --- |
+| Source | apache-doris-<source-version>-src.tar.gz, .asc, .sha512 |
+| x64 binary | apache-doris-<public-version>-bin-x64.tar.gz, .asc, .sha512 |
+| x64 no-AVX2 binary | apache-doris-<public-version>-bin-x64-noavx2.tar.gz,
.asc, .sha512 |
+| ARM64 binary | apache-doris-<public-version>-bin-arm64.tar.gz, .asc, .sha512
|
-For each supported architecture, add:
+This is twelve URLs: three source artifacts and nine binary artifacts. A
directory listing alone is insufficient. Check every URL and follow redirects.
A small ranged GET may be used when a server rejects HEAD.
-- `gz`: binary tarball URL using `ORIGIN`.
-- `asc`: binary signature URL using `ORIGIN`.
-- `sha512`: binary checksum URL using `ORIGIN`.
-- `source`: official Apache source directory, typically
`https://dist.apache.org/repos/dist/release/doris/<series>/<version>/` or
`https://downloads.apache.org/doris/<series>/<version>/`, matching nearby
conventions.
-- `version`: the source tarball filename version segment. Use plain `4.0.7`
only if `apache-doris-4.0.7-src.tar.gz` exists; use an RC suffix only if the
source tarball filename includes it.
+If the canonical downloads mirror is delayed but dist.apache.org already
serves the official release, use the working dist.apache.org directory in
download.data.ts and record that choice in the handoff.
-Keep entries sorted newest-first inside their series.
+### Files to Update
-### Core Release Notes
+A normal bilingual Core release that updates both website surfaces touches:
-Add English:
+ src/constant/download.data.ts
+ releasenotes/v<series>/release-<public-version>.md
+
i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v<series>/release-<public-version>.md
+ releasenotes/core.md
+ i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
+ sidebarsReleases.json
-```text
-releasenotes/v<series>/release-<version>.md
-```
+releasenotes/all-release.md and its zh-CN mirror are project indexes. They
route readers to Doris Core and ecosystem pages. Do not add individual Core
versions to them and do not modify them for a routine Core patch release.
-Add zh-CN:
+### Update Download Data
-```text
-i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v<series>/release-<version>.md
-```
+Edit src/constant/download.data.ts.
-Use the existing JSON-inside-`---` frontmatter style. Preserve the
release-note source structure where possible:
+Update VersionEnum only as confirmed:
-- `# Overview`
-- `# Behavior Changes`
-- `# New Features & Improvements`
-- `# Important Bug Fixes`
-- `# Acknowledgments` when provided
+- Latest: the headline current release.
+- Prev: the stable previous release line shown beside Latest.
+- Earlier: the older supported line.
+- Historical: no VersionEnum change.
-If the source issue already contains polished Markdown, copy it carefully,
adapt only the frontmatter/title/overview wording needed for website
consistency, and avoid inventing release content.
+Add the public version to both structures:
-### Core Index and Sidebar
+- DORIS_VERSIONS drives the quick-download selector.
+- ALL_VERSIONS drives the all-releases form.
-Update both all-release indexes:
+In both structures, add x64, x64-noavx2, and arm64 rows. Each row must contain
the binary gz, asc, sha512, source directory, and exact source filename version.
-```text
-releasenotes/all-release.md
-i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/all-release.md
-```
+Keep patch releases newest-first within their series. Audit the entire target
series in both arrays, not only the new entry. Every target-series version in
DORIS_VERSIONS must exist in ALL_VERSIONS and vice versa. Fix a discovered
omission when it is clearly a data drift in the requested release scope;
mention it explicitly in the handoff.
-In each file:
+Do not broadly reformat download.data.ts.
-- Update the relevant tip block entry for that release line.
-- Add a bullet link near the top of the chronological list.
-- Keep the list in reverse chronological order by release date.
-- Use the official release date.
+### Add Bilingual Release Notes
-Update `sidebarsReleases.json`:
+Create:
-- Add the new release note ID under the matching core version category, for
example `v4.0/release-4.0.7`.
-- Place it newest-first, before the previous patch release.
-- Validate that the JSON remains parseable.
+ releasenotes/v<series>/release-<public-version>.md
+
i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v<series>/release-<public-version>.md
-## Ecosystem Component Workflow
+Use the repository JSON-inside-dashes frontmatter shape. At minimum:
-Use this workflow for connector, operator, CLI, MCP, skills, streamloader, and
other ecosystem releases.
+- title contains the public version
+- language is en or zh-CN as appropriate
+- description contains the public version
-### Files to Update
+Use the approved release-note source as the content authority. Preserve its
section order, heading levels, bullet order, issue numbers, code spans, and
acknowledgments. Remove only source-page navigation that is not part of the
release itself, such as a backlink telling GitHub readers to visit a previous
release. Make only small, obvious whitespace or Markdown corrections; do not
invent features or rewrite technical claims.
-Most ecosystem releases update:
+For the translation:
-```text
-releasenotes/ecosystem/<component-page>.md
-i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/<component-page>.md
-```
+- Translate prose and headings, not product names, code, SQL, configuration
keys, paths, or issue numbers.
+- Preserve the issue-reference sequence exactly.
+- Preserve the heading-level sequence and bullet count.
+- Check that every English item has one corresponding zh-CN item.
+- Keep frontmatter valid JSON and remove trailing whitespace.
-Some ecosystem releases also update:
+### Update the Core Index
-```text
-src/constant/download.data.ts
-sidebarsReleases.json
-```
+Edit both Core history pages:
-Update `sidebarsReleases.json` only when adding a new ecosystem page. Existing
ecosystem pages are listed once under `Doris Ecosystem`; individual component
versions are usually sections inside the component page, not separate sidebar
items.
+ releasenotes/core.md
+ i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
-Do not update core `releasenotes/v<series>/...` files or core all-release
indexes for ecosystem component releases unless the release manager explicitly
asks for that cross-link.
+In each file:
-### Component Profiles
+- Update the relevant tip entry when the release is Latest, Prev, or Earlier.
+- Add the dated per-version link to the chronological list.
+- Use the confirmed YYYY-MM-DD release date.
+- Keep the chronological list reverse-sorted by date.
+- Keep English and zh-CN links, dates, and positioning aligned.
-#### Flink Connector
+### Update Release Navigation
-- Page: `releasenotes/ecosystem/doris-flink-connector.md`
-- zh-CN page:
`i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/doris-flink-connector.md`
-- Download data: `ToolsEnum.Flink` inside `TOOL_VERSIONS`
-- Source URL pattern:
`https://downloads.apache.org/doris/flink-connector/<version>/apache-doris-flink-connector-<version>-src.tgz`
for newer releases, but verify exact suffix from Apache downloads.
-- Binary URLs usually vary by Flink runtime, for example
`flink-doris-connector-<flink-version>/<connector-version>/flink-doris-connector-<flink-version>-<connector-version>.jar`.
-- Confirm supported Flink versions before editing.
+Edit sidebarsReleases.json:
-#### Spark Connector
+- Find Doris Core and the matching v<series> category.
+- Add v<series>/release-<public-version>.
+- Place the new patch version first in its series.
+- Add a new series category only when it does not already exist.
+- Preserve valid JSON and unrelated ordering.
-- Page: `releasenotes/ecosystem/doris-spark-connector.md`
-- zh-CN page:
`i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/doris-spark-connector.md`
-- Download data: `ToolsEnum.Spark` inside `TOOL_VERSIONS`
-- Source URL pattern:
`https://downloads.apache.org/doris/spark-connector/<version>/apache-doris-spark-connector-<version>-src.tgz`
for newer releases, but verify exact suffix.
-- Binary URLs usually vary by Spark/Scala runtime. Confirm supported variants
before editing.
+## Ecosystem Component Workflow
-#### Kafka Connector
+The bundled validator currently targets Doris Core. Use this workflow and the
manual checklist for ecosystem components.
-- Page: `releasenotes/ecosystem/doris-kafka-connector.md`
-- zh-CN page:
`i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/doris-kafka-connector.md`
-- Download data: `ToolsEnum.Kafka` inside `TOOL_VERSIONS`
-- Source URL pattern:
`https://downloads.apache.org/doris/kafka-connector/<version>/apache-doris-kafka-connector-<version>-src.tgz`
for newer releases, but verify exact suffix.
-- Binary URL is usually Maven Central or Apache repository coordinates for
`doris-kafka-connector`.
+Most established ecosystem releases update:
-#### Streamloader
+ releasenotes/ecosystem/<component-page>.md
+
i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/<component-page>.md
-- Page: `releasenotes/ecosystem/doris-streamloader.md`
-- zh-CN page:
`i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/doris-streamloader.md`
-- Download data: `ToolsEnum.StreamLoader` inside `TOOL_VERSIONS` when the
component has downloadable website artifacts.
-- Confirm source and binary URL patterns from nearby entries or the release
manager before editing.
+Some also update src/constant/download.data.ts. Update sidebarsReleases.json
only when adding a new ecosystem page; individual ecosystem versions normally
remain sections within one component page.
-#### Operator
+Do not update Core per-version notes, releasenotes/core.md, its zh-CN mirror,
or Core VersionEnum values for an ecosystem release unless the release manager
explicitly requests a cross-surface change.
-- Page: `releasenotes/ecosystem/doris-operator.md`
-- zh-CN page:
`i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/ecosystem/doris-operator.md`
-- Download data: usually not `TOOL_VERSIONS` unless the website has a concrete
download entry for this component.
-- Include image, Helm, or chart information only when the release manager
provides official links or nearby page conventions already show them.
+Component profiles:
-#### MCP Server, Skills, CLI, and Other Ecosystem Components
+| Component | Release page | Download data | Extra checks |
+| --- | --- | --- | --- |
+| Flink Connector | ecosystem/doris-flink-connector.md | ToolsEnum.Flink |
Runtime compatibility matrix; exact .tgz or .tar.gz suffix |
+| Spark Connector | ecosystem/doris-spark-connector.md | ToolsEnum.Spark |
Spark and Scala compatibility; Maven coordinates |
+| Kafka Connector | ecosystem/doris-kafka-connector.md | ToolsEnum.Kafka |
Kafka Connect compatibility; plugin package layout |
+| Streamloader | ecosystem/doris-streamloader.md | ToolsEnum.StreamLoader when
present | Platform binaries, checksums, usage examples |
+| Doris Operator | ecosystem/doris-operator.md | Usually none | Controller
image, Helm chart, CRD compatibility |
+| MCP Server | ecosystem/doris-mcp-server.md | Usually none | Package
installation and supported Doris versions |
+| Doris Skills | ecosystem/doris-skills.md | Usually none | Installation
source and supported environments |
+| Doris CLI | ecosystem/doris-cli.md | Usually none | Platform packages and
install commands |
-- Use the existing ecosystem page if present.
-- If adding a new ecosystem page, create both English and zh-CN pages and add
one sidebar item under `Doris Ecosystem`.
-- Confirm whether `/download/` should expose the component. If not, update
release notes only.
-- Use official artifact URLs only. Do not invent package registry links.
+For an existing page:
-### Ecosystem Release Note Format
+- Add the newest version section near the top.
+- Preserve the page frontmatter and anchors.
+- Update English and zh-CN with matching facts, links, compatibility data, and
issue references.
+- Verify every official artifact or package link directly.
-Add the new version section near the top of the component page, directly below
the intro and above the previous version.
+For a new page:
-Use this structure when the source supports it:
+- Copy the nearest ecosystem page structure.
+- Add both locales when expected.
+- Add one sidebar entry under Doris Ecosystem.
+- Check inbound links, route IDs, frontmatter, and stable URLs.
-```markdown
-## <version>
+When updating TOOL_VERSIONS:
-Source: [Release Notes <version>](<source-url>)
+- Verify the exact source filename and directory.
+- Preserve the existing Option shape.
+- Keep versions newest-first.
+- Do not add a Core VersionEnum entry.
-<one short summary paragraph when the source provides enough context>
+## Validation
-### Features and Improvements
+### Tier 0: Always Run
-- ...
+Run the validator tests:
-### Bug Fixes
+ node --test doc-tools/skills/add-release/scripts/validate-release.test.mjs
-- ...
+For Doris Core, run the bundled validator with the confirmed values:
-### Downloads
+ node doc-tools/skills/add-release/scripts/validate-release.mjs --component
doris-core --version <public-version> --series <series> --source-version
<source-version> --release-date <YYYY-MM-DD> --position
<latest|prev|earlier|historical> --source-dir <official-source-directory>
-- ...
+The validator checks:
-### Thanks
+- Both release-note files, JSON frontmatter, and trailing whitespace
+- English and zh-CN heading levels, bullet counts, and issue-reference
sequences
+- Both core.md indexes, release date, link, and reverse chronology
+- That both all-release.md project indexes still route to core.md and remain
untouched
+- sidebarsReleases.json parseability and newest-first placement
+- VersionEnum positioning
+- Target-series ordering and drift between DORIS_VERSIONS and ALL_VERSIONS
+- All six architecture rows, source filename version, source directory, and
binary filenames
+- The full twelve-artifact URL matrix
-- ...
-```
+Use --skip-links only when external link verification is explicitly out of
scope, and disclose the skipped check. --skip-git-routing is intended for
isolated test fixtures, not routine release work.
-Keep zh-CN structurally aligned with English. Translate faithfully and do not
add technical claims that are not in the source.
+Also run:
-### Ecosystem Download Data
+ node -e "JSON.parse(require('fs').readFileSync('sidebarsReleases.json',
'utf8'))"
+ git diff --check
+ git status --short
-When updating `src/constant/download.data.ts`:
+git diff and git diff --check do not cover untracked release-note files.
Inspect every path shown by git status, and ensure the new files are included
in the validator run. Review the final tracked diff plus the full contents of
every new untracked file.
-- Update only the matching `ToolsEnum.*` component section.
-- Add the new version newest-first.
-- Preserve existing object shape. Some tools use flat entries; Flink and Spark
use nested runtime compatibility entries.
-- For repeated source URLs across runtime variants, follow nearby constants
such as `FLINK_SAME_SOURCE_*` or `SPARK_SAME_SOURCE_*`.
-- Verify whether source files use `.tar.gz` or `.tgz`. Do not normalize
suffixes.
+For ecosystem releases, manually verify:
-## Validation Checklist
+- Exact changed-file scope for the confirmed component
+- English and zh-CN structure and facts
+- Frontmatter and route IDs
+- Download data ordering and field completeness when touched
+- Every source, signature, checksum, package, image, or chart link
+- Sidebar JSON when touched
+- Untracked new files
-Do not rely on visual inspection only. Run lightweight checks unless the user
explicitly asks not to run commands. If the user says not to compile, do not
run Docusaurus build.
+### Tier 1: Type Check
-For core release package links:
+When compilation checks are allowed and download data or TypeScript changed,
run:
-```shell
-curl -sI
https://downloads.apache.org/doris/<series>/<version>/apache-doris-<version>-src.tar.gz
-curl -sI https://download.selectdb.com/apache-doris-<version>-bin-x64.tar.gz
-curl -sI
https://download.selectdb.com/apache-doris-<version>-bin-x64-noavx2.tar.gz
-curl -sI https://download.selectdb.com/apache-doris-<version>-bin-arm64.tar.gz
-```
+ yarn typecheck
-For ecosystem links:
+### Tier 2: Full Site Build
-```shell
-curl -sI <source-artifact-url>
-curl -sI <representative-binary-or-package-url>
-```
+When the release manager requests full validation, or routing and rendering
risk warrants it, run:
-Also spot-check representative `.asc` and `.sha512` URLs for Apache source
artifacts when they exist. Treat `200` as good. If HEAD behaves unexpectedly,
retry with `curl -sIL` or GET before declaring a link broken.
+ yarn build
-Validate changed files:
+If the release manager says not to compile, skip both Tier 1 and Tier 2. Tier
0 still applies. State exactly which tiers ran and which were skipped.
-```shell
-git diff --check
-node -e
"JSON.parse(require('fs').readFileSync('sidebarsReleases.json','utf8'));
console.log('sidebarsReleases.json ok')"
-rg -n "<version>|release-<version>|apache-doris-<version>|<component>"
src/constant/download.data.ts releasenotes
i18n/zh-CN/docusaurus-plugin-content-docs-releases/current sidebarsReleases.json
-```
+Do not claim validation passed unless the corresponding command completed
successfully in the current worktree.
-When build validation is allowed and appropriate, run the repository's normal
Docusaurus validation/build command. If the release manager explicitly says not
to compile, report that build was intentionally skipped.
+## Final Review and Handoff
-## Common Pitfalls
+Before reporting completion:
-- **Skipping component confirmation**: core and ecosystem releases use
different files and URL patterns. Confirm the component first.
-- **Wrong release date**: GitHub issue creation/update time is not always the
official release date. Check Apache downloads, package publication, or the
announcement.
-- **Updating core indexes for an ecosystem release**: ecosystem releases
usually live under `releasenotes/ecosystem/*.md`, not
`releasenotes/all-release.md`.
-- **Missing `sidebarsReleases.json` update for a new ecosystem page**:
existing ecosystem pages do not need a new sidebar item per version, but new
component pages do.
-- **Only updating English**: mirror zh-CN release notes when a zh-CN mirror
already exists unless the release manager explicitly scopes the task to English
only.
-- **Wrong source tarball suffix**: source artifacts may use `.tar.gz` or
`.tgz`. Verify the actual Apache download URL.
-- **Updating only `DORIS_VERSIONS` for core**: quick download and all-releases
use separate structures. Update both `DORIS_VERSIONS` and `ALL_VERSIONS`.
-- **Changing unrelated components**: keep the diff focused on the confirmed
component and version.
-- **Inventing links**: use official Apache downloads, Apache repository, Maven
Central, image registry, or release-manager-provided links only.
+- Re-read the request and compare it with the exact changed-file set.
+- Confirm the public version, source filename version, release date, scope,
locales, and positioning.
+- Confirm all artifact checks and note any mirror choice.
+- Confirm DORIS_VERSIONS and ALL_VERSIONS are aligned for the target series.
+- Confirm Core history is in core.md, not all-release.md.
+- Confirm no unrelated files were modified.
-## Final Response
+Report:
-Summarize:
+- Component and version
+- Files changed, including newly created untracked files
+- Release-note source
+- Public version versus source filename version
+- Release date, position, website scope, and locales
+- Artifact validation result
+- Tier 0, Tier 1, and Tier 2 results or explicit skips
+- Any repaired pre-existing data drift, such as a missing ALL_VERSIONS entry
+- Any remaining limitation or manual follow-up
-- Confirmed component and version.
-- Release pages created or updated, including English and zh-CN status.
-- Download data updated, including whether core quick-download enums or
ecosystem `TOOL_VERSIONS` changed.
-- `all-release.md` and `sidebarsReleases.json` updates, or why they were not
needed.
-- Validation performed, including package-link checks and whether build was
skipped.
+Do not commit, push, publish externally, or open a pull request unless the
release manager asks.
diff --git a/doc-tools/skills/add-release/scripts/validate-release.mjs
b/doc-tools/skills/add-release/scripts/validate-release.mjs
new file mode 100644
index 00000000000..7da955c27d0
--- /dev/null
+++ b/doc-tools/skills/add-release/scripts/validate-release.mjs
@@ -0,0 +1,839 @@
+#!/usr/bin/env node
+
+import { spawnSync } from 'node:child_process';
+import { readFileSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const ARCHITECTURES = ['x64', 'x64-noavx2', 'arm64'];
+const SIDECAR_SUFFIXES = ['', '.asc', '.sha512'];
+
+class ReleaseValidationError extends Error {
+ constructor(failures, checks) {
+ super('Release validation failed:\n- ' + failures.join('\n- '));
+ this.name = 'ReleaseValidationError';
+ this.failures = failures;
+ this.checks = checks;
+ }
+}
+
+function addResult(condition, label, failure, checks, failures) {
+ if (condition) {
+ checks.push(label);
+ } else {
+ failures.push(failure || label);
+ }
+}
+
+function readRequired(repoRoot, relativePath, failures) {
+ try {
+ return readFileSync(path.join(repoRoot, relativePath), 'utf8');
+ } catch (error) {
+ failures.push(relativePath + ' could not be read: ' + error.message);
+ return '';
+ }
+}
+
+function parseFrontmatter(markdown, relativePath, failures) {
+ const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
+ if (!match) {
+ failures.push(relativePath + ' is missing JSON frontmatter inside ---
delimiters');
+ return { data: null, body: markdown };
+ }
+
+ try {
+ return {
+ data: JSON.parse(match[1]),
+ body: markdown.slice(match[0].length),
+ };
+ } catch (error) {
+ failures.push(relativePath + ' has invalid JSON frontmatter: ' +
error.message);
+ return { data: null, body: markdown.slice(match[0].length) };
+ }
+}
+
+function extractAssignedArray(source, variableName) {
+ const declaration = source.search(new RegExp('\\b' + variableName +
'\\b'));
+ if (declaration === -1) {
+ throw new Error(variableName + ' declaration was not found');
+ }
+
+ const equals = source.indexOf('=', declaration);
+ const start = source.indexOf('[', equals);
+ if (equals === -1 || start === -1) {
+ throw new Error(variableName + ' array assignment was not found');
+ }
+
+ let depth = 0;
+ let quote = null;
+ let escaped = false;
+ let lineComment = false;
+ let blockComment = false;
+
+ for (let index = start; index < source.length; index += 1) {
+ const character = source[index];
+ const next = source[index + 1];
+
+ if (lineComment) {
+ if (character === '\n') {
+ lineComment = false;
+ }
+ continue;
+ }
+
+ if (blockComment) {
+ if (character === '*' && next === '/') {
+ blockComment = false;
+ index += 1;
+ }
+ continue;
+ }
+
+ if (quote) {
+ if (escaped) {
+ escaped = false;
+ } else if (character === '\\') {
+ escaped = true;
+ } else if (character === quote) {
+ quote = null;
+ }
+ continue;
+ }
+
+ if (character === '/' && next === '/') {
+ lineComment = true;
+ index += 1;
+ continue;
+ }
+ if (character === '/' && next === '*') {
+ blockComment = true;
+ index += 1;
+ continue;
+ }
+ if (character === "'" || character === '"' || character.charCodeAt(0)
=== 96) {
+ quote = character;
+ continue;
+ }
+ if (character === '[') {
+ depth += 1;
+ } else if (character === ']') {
+ depth -= 1;
+ if (depth === 0) {
+ return source.slice(start, index + 1);
+ }
+ }
+ }
+
+ throw new Error(variableName + ' array is not balanced');
+}
+
+function extractPatchVersions(arraySource, series) {
+ const versions = [];
+ const seen = new Set();
+ const expression = /label\s*:\s*['"](\d+\.\d+\.\d+)['"]/g;
+
+ for (const match of arraySource.matchAll(expression)) {
+ const version = match[1];
+ if (version.startsWith(series + '.') && !seen.has(version)) {
+ versions.push(version);
+ seen.add(version);
+ }
+ }
+
+ return versions;
+}
+
+function compareVersions(left, right) {
+ const leftParts = left.split('.').map(Number);
+ const rightParts = right.split('.').map(Number);
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length);
index += 1) {
+ const difference = (leftParts[index] || 0) - (rightParts[index] || 0);
+ if (difference !== 0) {
+ return difference;
+ }
+ }
+ return 0;
+}
+
+function isNewestFirst(versions) {
+ return versions.every(
+ (version, index) => index === 0 || compareVersions(versions[index -
1], version) >= 0,
+ );
+}
+
+function countMatches(source, expression) {
+ return Array.from(source.matchAll(expression)).length;
+}
+
+function escapeRegExp(value) {
+ return value.replace(/[.*+?^{}$()|[\]\\]/g, '\\$&');
+}
+
+function findCategory(node, label) {
+ if (Array.isArray(node)) {
+ for (const child of node) {
+ const found = findCategory(child, label);
+ if (found) {
+ return found;
+ }
+ }
+ return null;
+ }
+
+ if (!node || typeof node !== 'object') {
+ return null;
+ }
+ if (node.label === label && Array.isArray(node.items)) {
+ return node;
+ }
+
+ for (const value of Object.values(node)) {
+ const found = findCategory(value, label);
+ if (found) {
+ return found;
+ }
+ }
+ return null;
+}
+
+function extractIssueReferences(markdown) {
+ return Array.from(markdown.matchAll(/#\d+/g), match => match[0]);
+}
+
+function extractHeadingLevels(markdown) {
+ return Array.from(markdown.matchAll(/^(#{1,6})\s+\S/gm), match =>
match[1].length);
+}
+
+function countBullets(markdown) {
+ return countMatches(markdown, /^\s*[-*]\s+\S/gm);
+}
+
+function arraysEqual(left, right) {
+ return left.length === right.length && left.every((value, index) => value
=== right[index]);
+}
+
+function validateReleaseNote(markdown, relativePath, language, version,
checks, failures) {
+ if (!markdown) {
+ return { body: '' };
+ }
+
+ addResult(
+ !/[ \t]+$/m.test(markdown),
+ relativePath + ' has no trailing whitespace',
+ relativePath + ' contains trailing whitespace',
+ checks,
+ failures,
+ );
+
+ const parsed = parseFrontmatter(markdown, relativePath, failures);
+ if (parsed.data) {
+ addResult(
+ typeof parsed.data.title === 'string' &&
parsed.data.title.includes(version),
+ relativePath + ' frontmatter title includes ' + version,
+ relativePath + ' frontmatter title must include ' + version,
+ checks,
+ failures,
+ );
+ addResult(
+ parsed.data.language === language,
+ relativePath + ' frontmatter language is ' + language,
+ relativePath + ' frontmatter language must be ' + language,
+ checks,
+ failures,
+ );
+ addResult(
+ typeof parsed.data.description === 'string' &&
+ parsed.data.description.includes(version),
+ relativePath + ' frontmatter description includes ' + version,
+ relativePath + ' frontmatter description must include ' + version,
+ checks,
+ failures,
+ );
+ }
+
+ addResult(
+ extractHeadingLevels(parsed.body).length > 0,
+ relativePath + ' contains release-note headings',
+ relativePath + ' contains no release-note headings',
+ checks,
+ failures,
+ );
+
+ return parsed;
+}
+
+function validateCoreIndex(markdown, relativePath, version, series,
releaseDate, checks, failures) {
+ if (!markdown) {
+ return;
+ }
+
+ const releasePath = './v' + series + '/release-' + version + '.md';
+ addResult(
+ markdown.includes(version),
+ relativePath + ' includes version ' + version,
+ relativePath + ' does not mention version ' + version,
+ checks,
+ failures,
+ );
+ addResult(
+ markdown.includes(releasePath),
+ relativePath + ' links to ' + releasePath,
+ relativePath + ' does not link to ' + releasePath,
+ checks,
+ failures,
+ );
+ addResult(
+ markdown.includes(releaseDate),
+ relativePath + ' includes release date ' + releaseDate,
+ relativePath + ' does not include release date ' + releaseDate,
+ checks,
+ failures,
+ );
+
+ const dates = Array.from(
+ markdown.matchAll(/^\s*-\s+\[(\d{4}-\d{2}-\d{2}),/gm),
+ match => match[1],
+ );
+ addResult(
+ dates.length > 0 && dates.includes(releaseDate),
+ relativePath + ' contains dated release entries',
+ relativePath + ' has no dated entry for ' + releaseDate,
+ checks,
+ failures,
+ );
+ addResult(
+ dates.every((date, index) => index === 0 || dates[index - 1] >= date),
+ relativePath + ' release entries are reverse chronological',
+ relativePath + ' release entries are not reverse chronological',
+ checks,
+ failures,
+ );
+}
+
+function validateProjectIndex(markdown, relativePath, version, checks,
failures) {
+ if (!markdown) {
+ return;
+ }
+
+ addResult(
+ markdown.includes('./core.md'),
+ relativePath + ' routes Doris Core to core.md',
+ relativePath + ' must link Doris Core to ./core.md',
+ checks,
+ failures,
+ );
+ addResult(
+ !markdown.includes('/release-' + version + '.md'),
+ relativePath + ' remains a project index',
+ relativePath + ' must not contain a per-version core release entry',
+ checks,
+ failures,
+ );
+}
+
+function validateGitRouting(repoRoot, projectIndexPaths, checks, failures) {
+ const result = spawnSync('git', ['status', '--porcelain', '--',
...projectIndexPaths], {
+ cwd: repoRoot,
+ encoding: 'utf8',
+ });
+
+ if (result.status !== 0) {
+ failures.push('Could not inspect all-release.md routing with git: ' +
result.stderr.trim());
+ return;
+ }
+
+ addResult(
+ result.stdout.trim() === '',
+ 'Core release leaves both all-release.md project indexes untouched',
+ 'Core release must not modify all-release.md project indexes:\n' +
result.stdout.trim(),
+ checks,
+ failures,
+ );
+}
+
+async function checkUrl(url) {
+ const request = async (method, headers = {}) => {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 15000);
+ try {
+ const response = await fetch(url, {
+ method,
+ headers,
+ redirect: 'follow',
+ signal: controller.signal,
+ });
+ if (response.body) {
+ await response.body.cancel().catch(() => {});
+ }
+ return { ok: response.status === 200 || response.status === 206,
status: response.status };
+ } finally {
+ clearTimeout(timeout);
+ }
+ };
+
+ try {
+ const head = await request('HEAD');
+ if (head.ok) {
+ return head;
+ }
+ return await request('GET', { Range: 'bytes=0-0' });
+ } catch (error) {
+ return { ok: false, error: error.message };
+ }
+}
+
+async function validateLinks(urls, checks, failures) {
+ const results = await Promise.all(
+ urls.map(async url => ({ url, result: await checkUrl(url) })),
+ );
+
+ for (const { url, result } of results) {
+ addResult(
+ result.ok,
+ 'Artifact is reachable: ' + url,
+ 'Artifact is not reachable: ' +
+ url +
+ ' (' +
+ (result.status ? 'HTTP ' + result.status : result.error) +
+ ')',
+ checks,
+ failures,
+ );
+ }
+}
+
+export async function validateCoreRelease(options) {
+ const repoRoot = path.resolve(options.repoRoot || process.cwd());
+ const version = options.version;
+ const series = options.series;
+ const sourceVersion = options.sourceVersion || version;
+ const releaseDate = options.releaseDate;
+ const position = (options.position || 'historical').toLowerCase();
+ const sourceDir =
+ options.sourceDir ||
+ 'https://dist.apache.org/repos/dist/release/doris/' + series + '/' +
version + '/';
+ const binaryOrigin = options.binaryOrigin ||
'https://download.selectdb.com/';
+ const checkLinks = options.checkLinks !== false;
+ const checkGitRouting = options.checkGitRouting !== false;
+ const checks = [];
+ const failures = [];
+
+ if (!/^\d+\.\d+\.\d+$/.test(version || '')) {
+ throw new Error('version must use x.y.z format');
+ }
+ if (!/^\d+\.\d+$/.test(series || '')) {
+ throw new Error('series must use x.y format');
+ }
+ if (version.split('.').slice(0, 2).join('.') !== series) {
+ throw new Error('series ' + series + ' does not match version ' +
version);
+ }
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(releaseDate || '')) {
+ throw new Error('releaseDate must use YYYY-MM-DD format');
+ }
+ if (!['latest', 'prev', 'earlier', 'historical'].includes(position)) {
+ throw new Error('position must be latest, prev, earlier, or
historical');
+ }
+
+ const releasePath = 'releasenotes/v' + series + '/release-' + version +
'.md';
+ const zhReleasePath =
+ 'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v' +
+ series +
+ '/release-' +
+ version +
+ '.md';
+ const corePath = 'releasenotes/core.md';
+ const zhCorePath =
+ 'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md';
+ const projectIndexPaths = [
+ 'releasenotes/all-release.md',
+
'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/all-release.md',
+ ];
+
+ const english = readRequired(repoRoot, releasePath, failures);
+ const chinese = readRequired(repoRoot, zhReleasePath, failures);
+ const englishParsed = validateReleaseNote(
+ english,
+ releasePath,
+ 'en',
+ version,
+ checks,
+ failures,
+ );
+ const chineseParsed = validateReleaseNote(
+ chinese,
+ zhReleasePath,
+ 'zh-CN',
+ version,
+ checks,
+ failures,
+ );
+
+ if (english && chinese) {
+ const englishHeadings = extractHeadingLevels(englishParsed.body);
+ const chineseHeadings = extractHeadingLevels(chineseParsed.body);
+ addResult(
+ arraysEqual(englishHeadings, chineseHeadings),
+ 'English and zh-CN heading-level sequences match',
+ 'English and zh-CN heading-level sequences differ',
+ checks,
+ failures,
+ );
+
+ addResult(
+ countBullets(englishParsed.body) ===
countBullets(chineseParsed.body),
+ 'English and zh-CN release-note bullet counts match',
+ 'English and zh-CN release-note bullet counts differ',
+ checks,
+ failures,
+ );
+
+ const englishIssues = extractIssueReferences(englishParsed.body);
+ const chineseIssues = extractIssueReferences(chineseParsed.body);
+ addResult(
+ arraysEqual(englishIssues, chineseIssues),
+ 'English and zh-CN issue reference sequences match',
+ 'Issue reference sequence differs between English and zh-CN
release notes',
+ checks,
+ failures,
+ );
+ }
+
+ const core = readRequired(repoRoot, corePath, failures);
+ const zhCore = readRequired(repoRoot, zhCorePath, failures);
+ validateCoreIndex(core, corePath, version, series, releaseDate, checks,
failures);
+ validateCoreIndex(zhCore, zhCorePath, version, series, releaseDate,
checks, failures);
+
+ for (const projectIndexPath of projectIndexPaths) {
+ const projectIndex = readRequired(repoRoot, projectIndexPath,
failures);
+ validateProjectIndex(projectIndex, projectIndexPath, version, checks,
failures);
+ }
+ if (checkGitRouting) {
+ validateGitRouting(repoRoot, projectIndexPaths, checks, failures);
+ }
+
+ const sidebarSource = readRequired(repoRoot, 'sidebarsReleases.json',
failures);
+ if (sidebarSource) {
+ try {
+ const sidebar = JSON.parse(sidebarSource);
+ checks.push('sidebarsReleases.json is valid JSON');
+ const coreCategory = findCategory(sidebar, 'Doris Core');
+ addResult(
+ Boolean(coreCategory),
+ 'Doris Core sidebar category exists',
+ 'Doris Core sidebar category was not found',
+ checks,
+ failures,
+ );
+ const seriesCategory = coreCategory
+ ? coreCategory.items.find(item => item && item.label === 'v' +
series)
+ : null;
+ addResult(
+ Boolean(seriesCategory),
+ 'Doris Core sidebar contains v' + series,
+ 'Doris Core sidebar category v' + series + ' was not found',
+ checks,
+ failures,
+ );
+ if (seriesCategory) {
+ const expectedId = 'v' + series + '/release-' + version;
+ addResult(
+ seriesCategory.items[0] === expectedId,
+ expectedId + ' is the first sidebar item in its series',
+ expectedId + ' must be the first sidebar item in its
series',
+ checks,
+ failures,
+ );
+ }
+ } catch (error) {
+ failures.push('sidebarsReleases.json is invalid JSON: ' +
error.message);
+ }
+ }
+
+ const downloadPath = 'src/constant/download.data.ts';
+ const downloadSource = readRequired(repoRoot, downloadPath, failures);
+ if (downloadSource) {
+ let dorisArray = '';
+ let allArray = '';
+ try {
+ dorisArray = extractAssignedArray(downloadSource,
'DORIS_VERSIONS');
+ allArray = extractAssignedArray(downloadSource, 'ALL_VERSIONS');
+ checks.push('DORIS_VERSIONS and ALL_VERSIONS arrays are readable');
+ } catch (error) {
+ failures.push(downloadPath + ': ' + error.message);
+ }
+
+ if (dorisArray && allArray) {
+ const dorisVersions = extractPatchVersions(dorisArray, series);
+ const allVersions = extractPatchVersions(allArray, series);
+ addResult(
+ dorisVersions.includes(version),
+ 'DORIS_VERSIONS includes ' + version,
+ version + ' is missing from DORIS_VERSIONS',
+ checks,
+ failures,
+ );
+ addResult(
+ allVersions.includes(version),
+ 'ALL_VERSIONS includes ' + version,
+ version + ' is missing from ALL_VERSIONS',
+ checks,
+ failures,
+ );
+ addResult(
+ isNewestFirst(dorisVersions),
+ 'DORIS_VERSIONS ' + series + ' entries are newest-first',
+ 'DORIS_VERSIONS ' + series + ' entries are not newest-first',
+ checks,
+ failures,
+ );
+ addResult(
+ isNewestFirst(allVersions),
+ 'ALL_VERSIONS ' + series + ' entries are newest-first',
+ 'ALL_VERSIONS ' + series + ' entries are not newest-first',
+ checks,
+ failures,
+ );
+
+ for (const listedVersion of dorisVersions) {
+ addResult(
+ allVersions.includes(listedVersion),
+ listedVersion + ' is mirrored in ALL_VERSIONS',
+ listedVersion + ' is missing from ALL_VERSIONS',
+ checks,
+ failures,
+ );
+ }
+ for (const listedVersion of allVersions) {
+ addResult(
+ dorisVersions.includes(listedVersion),
+ listedVersion + ' is mirrored in DORIS_VERSIONS',
+ listedVersion + ' is missing from DORIS_VERSIONS',
+ checks,
+ failures,
+ );
+ }
+
+ const sourceVersionExpression = new RegExp(
+ "version\\s*:\\s*['\"]" + escapeRegExp(sourceVersion) +
"['\"]",
+ 'g',
+ );
+ const sourceVersionCount =
+ countMatches(dorisArray, sourceVersionExpression) +
+ countMatches(allArray, sourceVersionExpression);
+ addResult(
+ sourceVersionCount === ARCHITECTURES.length * 2,
+ 'Source filename version ' + sourceVersion + ' is set for all
six package rows',
+ 'Expected source filename version ' +
+ sourceVersion +
+ ' in six package rows, found ' +
+ sourceVersionCount,
+ checks,
+ failures,
+ );
+
+ for (const architecture of ARCHITECTURES) {
+ for (const suffix of SIDECAR_SUFFIXES) {
+ const filename =
+ 'apache-doris-' +
+ version +
+ '-bin-' +
+ architecture +
+ '.tar.gz' +
+ suffix;
+ addResult(
+ dorisArray.includes(filename) &&
allArray.includes(filename),
+ filename + ' exists in both download data arrays',
+ filename + ' must exist in both DORIS_VERSIONS and
ALL_VERSIONS',
+ checks,
+ failures,
+ );
+ }
+ }
+
+ const normalizedSourceDir = sourceDir.endsWith('/') ? sourceDir :
sourceDir + '/';
+ const sourceDirCount =
+ dorisArray.split(normalizedSourceDir).length -
+ 1 +
+ (allArray.split(normalizedSourceDir).length - 1);
+ addResult(
+ sourceDirCount === ARCHITECTURES.length * 2,
+ 'Source directory is set for all six package rows',
+ 'Expected source directory ' +
+ normalizedSourceDir +
+ ' in six package rows, found ' +
+ sourceDirCount,
+ checks,
+ failures,
+ );
+ }
+
+ if (position !== 'historical') {
+ const enumName = {
+ latest: 'Latest',
+ prev: 'Prev',
+ earlier: 'Earlier',
+ }[position];
+ const enumExpression = new RegExp(
+ "\\b" + enumName + "\\s*=\\s*['\"]" + escapeRegExp(version) +
"['\"]",
+ );
+ addResult(
+ enumExpression.test(downloadSource),
+ 'VersionEnum.' + enumName + ' points to ' + version,
+ 'VersionEnum.' + enumName + ' must point to ' + version,
+ checks,
+ failures,
+ );
+ }
+
+ const originMatch = downloadSource.match(
+ /\bORIGIN\s*=\s*['"]([^'"]+)['"]/,
+ );
+ addResult(
+ Boolean(originMatch) &&
+ originMatch[1].replace(/\/?$/, '/') ===
binaryOrigin.replace(/\/?$/, '/'),
+ 'Binary ORIGIN matches ' + binaryOrigin,
+ 'Binary ORIGIN must match ' + binaryOrigin,
+ checks,
+ failures,
+ );
+ }
+
+ if (checkLinks) {
+ const normalizedSourceDir = sourceDir.endsWith('/') ? sourceDir :
sourceDir + '/';
+ const normalizedBinaryOrigin = binaryOrigin.endsWith('/')
+ ? binaryOrigin
+ : binaryOrigin + '/';
+ const urls = [
+ ...SIDECAR_SUFFIXES.map(
+ suffix =>
+ normalizedSourceDir +
+ 'apache-doris-' +
+ sourceVersion +
+ '-src.tar.gz' +
+ suffix,
+ ),
+ ...ARCHITECTURES.flatMap(architecture =>
+ SIDECAR_SUFFIXES.map(
+ suffix =>
+ normalizedBinaryOrigin +
+ 'apache-doris-' +
+ version +
+ '-bin-' +
+ architecture +
+ '.tar.gz' +
+ suffix,
+ ),
+ ),
+ ];
+ await validateLinks(urls, checks, failures);
+ }
+
+ if (failures.length > 0) {
+ throw new ReleaseValidationError(failures, checks);
+ }
+
+ return { checks };
+}
+
+function usage() {
+ return [
+ 'Usage:',
+ ' node doc-tools/skills/add-release/scripts/validate-release.mjs \\',
+ ' --component doris-core --version 4.1.3 --series 4.1 \\',
+ ' --source-version 4.1.3-rc02 --release-date 2026-07-13 \\',
+ ' --position latest [--source-dir URL] [--binary-origin URL] \\',
+ ' [--repo-root PATH] [--skip-links] [--skip-git-routing]',
+ ].join('\n');
+}
+
+function parseArguments(argv) {
+ const options = {};
+ const flags = new Set(['--skip-links', '--skip-git-routing', '--help']);
+
+ for (let index = 0; index < argv.length; index += 1) {
+ const argument = argv[index];
+ if (flags.has(argument)) {
+ options[argument.slice(2)] = true;
+ continue;
+ }
+ if (!argument.startsWith('--')) {
+ throw new Error('Unexpected argument: ' + argument);
+ }
+ const value = argv[index + 1];
+ if (!value || value.startsWith('--')) {
+ throw new Error('Missing value for ' + argument);
+ }
+ options[argument.slice(2)] = value;
+ index += 1;
+ }
+
+ return options;
+}
+
+async function main() {
+ let args;
+ try {
+ args = parseArguments(process.argv.slice(2));
+ } catch (error) {
+ console.error(error.message);
+ console.error(usage());
+ process.exitCode = 2;
+ return;
+ }
+
+ if (args.help) {
+ console.log(usage());
+ return;
+ }
+ if (args.component !== 'doris-core') {
+ console.error(
+ 'The bundled validator currently supports --component doris-core
only. ' +
+ 'Use the manual ecosystem checklist in SKILL.md for other
components.',
+ );
+ process.exitCode = 2;
+ return;
+ }
+
+ const required = ['version', 'series', 'source-version', 'release-date',
'position'];
+ const missing = required.filter(name => !args[name]);
+ if (missing.length > 0) {
+ console.error('Missing required arguments: ' + missing.map(name =>
'--' + name).join(', '));
+ console.error(usage());
+ process.exitCode = 2;
+ return;
+ }
+
+ try {
+ const result = await validateCoreRelease({
+ repoRoot: args['repo-root'],
+ version: args.version,
+ series: args.series,
+ sourceVersion: args['source-version'],
+ releaseDate: args['release-date'],
+ position: args.position,
+ sourceDir: args['source-dir'],
+ binaryOrigin: args['binary-origin'],
+ checkLinks: !args['skip-links'],
+ checkGitRouting: !args['skip-git-routing'],
+ });
+ for (const check of result.checks) {
+ console.log('PASS ' + check);
+ }
+ console.log('Validated Doris Core ' + args.version + ': ' +
result.checks.length + ' checks passed.');
+ } catch (error) {
+ if (Array.isArray(error.checks)) {
+ for (const check of error.checks) {
+ console.log('PASS ' + check);
+ }
+ }
+ console.error(error.message);
+ process.exitCode = 1;
+ }
+}
+
+const isMain =
+ process.argv[1] &&
+ path.resolve(process.argv[1]) ===
path.resolve(fileURLToPath(import.meta.url));
+if (isMain) {
+ await main();
+}
diff --git a/doc-tools/skills/add-release/scripts/validate-release.test.mjs
b/doc-tools/skills/add-release/scripts/validate-release.test.mjs
new file mode 100644
index 00000000000..33bbd06ef51
--- /dev/null
+++ b/doc-tools/skills/add-release/scripts/validate-release.test.mjs
@@ -0,0 +1,232 @@
+import assert from 'node:assert/strict';
+import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from
'node:fs';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import test from 'node:test';
+import { fileURLToPath } from 'node:url';
+
+const scriptDir = path.dirname(fileURLToPath(import.meta.url));
+const skillDir = path.resolve(scriptDir, '..');
+
+function write(root, relativePath, content) {
+ const target = path.join(root, relativePath);
+ mkdirSync(path.dirname(target), { recursive: true });
+ writeFileSync(target, content);
+}
+
+function packageRows(version, sourceVersion) {
+ return ['x64', 'x64-noavx2', 'arm64']
+ .map(
+ arch => `{
+ gz:
'https://download.selectdb.com/apache-doris-${version}-bin-${arch}.tar.gz',
+ asc:
'https://download.selectdb.com/apache-doris-${version}-bin-${arch}.tar.gz.asc',
+ sha512:
'https://download.selectdb.com/apache-doris-${version}-bin-${arch}.tar.gz.sha512',
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/${version}/',
+ version: '${sourceVersion}',
+ }`,
+ )
+ .join(',\n');
+}
+
+function versionEntry(version, sourceVersion) {
+ return `{
+ label: '${version}',
+ value: '${version}',
+ majorVersion: '4.1',
+ packages: [
+ ${packageRows(version, sourceVersion)}
+ ],
+ }`;
+}
+
+function releaseNote(language, issueRef) {
+ const title = language === 'en' ? 'Release 4.1.3' : 'Release 4.1.3';
+ const description = language === 'en' ? 'Apache Doris 4.1.3 release notes'
: 'Apache Doris 4.1.3 版本发布说明';
+ const heading = language === 'en' ? 'New Features' : '新功能';
+ const section = language === 'en' ? 'Query & Execution' : '查询与执行';
+ const item = language === 'en' ? 'Support a feature' : '支持某项功能';
+
+ return `---
+{
+ "title": "${title}",
+ "language": "${language}",
+ "description": "${description}"
+}
+---
+
+# ${heading}
+
+## ${section}
+- ${item} (${issueRef})
+`;
+}
+
+function createFixture({ omit412FromAll = false, zhIssueRef = '#10001' } = {})
{
+ const root = mkdtempSync(path.join(tmpdir(), 'add-release-validator-'));
+ const dorisVersions = [
+ versionEntry('4.1.3', '4.1.3-rc02'),
+ versionEntry('4.1.2', '4.1.2'),
+ ].join(',\n');
+ const allVersions = [
+ versionEntry('4.1.3', '4.1.3-rc02'),
+ ...(omit412FromAll ? [] : [versionEntry('4.1.2', '4.1.2')]),
+ ].join(',\n');
+
+ write(
+ root,
+ 'src/constant/download.data.ts',
+ `export const ORIGIN = 'https://download.selectdb.com/';
+export enum VersionEnum {
+ Latest = '4.1.3',
+ Prev = '4.0.7',
+ Earlier = '3.1.4',
+}
+export const DORIS_VERSIONS = [
+${dorisVersions}
+];
+export const ALL_VERSIONS = [
+{
+ label: '4.1',
+ value: '4.1',
+ children: [
+${allVersions}
+ ],
+}
+];
+`,
+ );
+
+ write(root, 'releasenotes/v4.1/release-4.1.3.md', releaseNote('en',
'#10001'));
+ write(
+ root,
+
'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v4.1/release-4.1.3.md',
+ releaseNote('zh-CN', zhIssueRef),
+ );
+ write(
+ root,
+ 'releasenotes/core.md',
+ ':::tip Latest Release\nVersion 4.1.3\n:::\n- [2026-07-13, Apache
Doris 4.1.3 is released](./v4.1/release-4.1.3.md)\n',
+ );
+ write(
+ root,
+ 'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md',
+ ':::tip 最新发布\n4.1.3 版本\n:::\n- [2026-07-13, Apache Doris 4.1.3
版本发布](./v4.1/release-4.1.3.md)\n',
+ );
+ write(
+ root,
+ 'releasenotes/all-release.md',
+ '## Project Release Notes\n| Doris Core | [Doris Core Release
Notes](./core.md) |\n',
+ );
+ write(
+ root,
+
'i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/all-release.md',
+ '## 项目 Release Notes\n| Doris Core | [Doris Core 版本发布说明](./core.md)
|\n',
+ );
+ write(
+ root,
+ 'sidebarsReleases.json',
+ JSON.stringify(
+ {
+ releases: [
+ 'all-release',
+ {
+ type: 'category',
+ label: 'Doris Core',
+ link: { type: 'doc', id: 'core' },
+ items: [
+ {
+ type: 'category',
+ label: 'v4.1',
+ items: ['v4.1/release-4.1.3',
'v4.1/release-4.1.2'],
+ },
+ ],
+ },
+ ],
+ },
+ null,
+ 2,
+ ),
+ );
+
+ return root;
+}
+
+test('skill routes Doris Core history to core.md and uses the bundled
validator', () => {
+ const skill = readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
+
+ assert.match(skill, /releasenotes\/core\.md/);
+ assert.match(
+ skill,
+
/i18n\/zh-CN\/docusaurus-plugin-content-docs-releases\/current\/core\.md/,
+ );
+ assert.doesNotMatch(skill, /Update both all-release indexes/);
+ assert.match(skill, /scripts\/validate-release\.mjs/);
+});
+
+test('validator accepts a complete bilingual Doris Core release', async () => {
+ const root = createFixture();
+
+ try {
+ const { validateCoreRelease } = await import('./validate-release.mjs');
+ const result = await validateCoreRelease({
+ repoRoot: root,
+ version: '4.1.3',
+ series: '4.1',
+ sourceVersion: '4.1.3-rc02',
+ releaseDate: '2026-07-13',
+ position: 'latest',
+ checkLinks: false,
+ checkGitRouting: false,
+ });
+
+ assert.ok(result.checks.length > 0);
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+});
+
+test('validator catches versions missing from ALL_VERSIONS', async () => {
+ const root = createFixture({ omit412FromAll: true });
+
+ try {
+ const { validateCoreRelease } = await import('./validate-release.mjs');
+ await assert.rejects(
+ validateCoreRelease({
+ repoRoot: root,
+ version: '4.1.3',
+ series: '4.1',
+ sourceVersion: '4.1.3-rc02',
+ releaseDate: '2026-07-13',
+ position: 'latest',
+ checkLinks: false,
+ checkGitRouting: false,
+ }),
+ /4\.1\.2.*missing from ALL_VERSIONS/,
+ );
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+});
+
+test('validator catches mismatched bilingual issue references', async () => {
+ const root = createFixture({ zhIssueRef: '#10002' });
+
+ try {
+ const { validateCoreRelease } = await import('./validate-release.mjs');
+ await assert.rejects(
+ validateCoreRelease({
+ repoRoot: root,
+ version: '4.1.3',
+ series: '4.1',
+ sourceVersion: '4.1.3-rc02',
+ releaseDate: '2026-07-13',
+ position: 'latest',
+ checkLinks: false,
+ checkGitRouting: false,
+ }),
+ /Issue reference sequence differs/,
+ );
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+});
diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
b/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
index 323f269607a..d3f705bae8b 100644
--- a/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
+++ b/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/core.md
@@ -11,7 +11,7 @@
## Doris Core 版本发布说明
:::tip 最新发布
-🎉 4.1.2 版本已于 2026 年 06 月 17 日正式发布,详情可查看[版本发布](./v4.1/release-4.1.2.md)。4.1
系列通过 AI 与搜索能力的深度融合、离线计算的稳定性提升、性能与易用性的双重优化,进一步拓宽了数据库的应用边界,可更好地支撑企业从传统 BI 分析到 AI
智能检索、从实时查询到大规模离线批处理的全场景数据分析需求。无论是互联网、金融、零售等行业的实时报表、用户行为分析,还是政务、医疗领域的文档检索、大规模数据批处理,新版本
Doris 都将为用户提供更高效、更可靠的数据分析体验。
+🎉 4.1.3 版本已于 2026 年 07 月 13 日正式发布,详情可查看[版本发布](./v4.1/release-4.1.3.md)。Apache
Doris 4.1.3 新增 Python UDF/UDAF/UDTF、工作负载策略增强、表级事件驱动预热和 Stream Load zstd
压缩,并修复查询执行、存储、导入、湖仓一体与安全认证方面的多项问题。
<br />
🎉 4.0.7 版本已于 2026 年 07 月 12 日正式发布,详情可查看[版本发布](./v4.0/release-4.0.7.md)。Apache
Doris 4.0.7 是 4.0 系列维护版本,聚焦查询正确性、导入稳定性、存算分离部署、File Cache
可靠性、对象存储访问、可观测性以及外部数据源兼容性。建议所有 4.0.x 用户升级。
@@ -30,6 +30,8 @@
<br />
+- [2026-07-13, Apache Doris 4.1.3 版本发布](./v4.1/release-4.1.3.md)
+
- [2026-07-12, Apache Doris 4.0.7 版本发布](./v4.0/release-4.0.7.md)
- [2026-06-17, Apache Doris 4.1.2 版本发布](./v4.1/release-4.1.2.md)
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v4.1/release-4.1.3.md
b/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v4.1/release-4.1.3.md
new file mode 100644
index 00000000000..9bd894e7d19
--- /dev/null
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs-releases/current/v4.1/release-4.1.3.md
@@ -0,0 +1,149 @@
+---
+{
+ "title": "Release 4.1.3",
+ "language": "zh-CN",
+ "description": "Apache Doris 4.1.3 版本发布说明"
+}
+---
+
+# 新功能
+
+## 查询与执行
+- 支持 Python UDF/UDAF/UDTF (#63387)
+- 在工作负载策略中支持远程扫描字节数熔断 (#64649)
+- 新增基于用户名的 BE 端工作负载策略支持 (#60559)
+- 新增 `is_valid_utf8` 函数 (#62515)
+
+## 云原生
+- 新增表级事件驱动预热 (#63832)
+
+## 导入
+- 支持 Stream Load zstd 压缩 (#64711)
+
+## 索引与搜索
+- 为 ANN TopN 小候选集场景增加暴力搜索回退机制 (#64555)
+
+# 改进
+
+## 查询与执行
+- 将 `inactive_file` 视为可用内存,避免在 cgroup 环境中误取消查询 (#64347)
+- 默认将 profile level 设置为 2,以输出更详细的查询 Profile (#64378)
+- 为 ETL 场景下的 INSERT publish 超时新增可配置的返回模式 (#64583)
+
+## 导入
+- 让 from-to streaming 任务的超时判断感知任务进度 (#64301)
+- 为 Kafka read_committed 导入新增零行提示,并延迟零行重试 (#64585)
+- 更及时地刷新 Routine Load 延迟指标 (#63654)
+
+## 云原生
+- 为 BE 到 Meta-Service 的 RPC 增加限流 (#64396)
+- 新增虚拟 Compute Group 切换指标 (#63036)
+
+# 问题修复
+
+## 索引与搜索
+- 读取 Variant 时处理被截断的 sparse path 统计信息 (#64205)
+- 将 JSONB 转换为 Variant 时保留 JSON 对象 (#63792)
+- 对标量字符串 Variant 调用 `element_at` 时返回原始字符串 (#64103)
+- 转义 JSONB 路径成员中的控制字符 (#63517)
+
+## 查询与执行
+- 修复 `SimplifyAggGroupBy` 未验证单射性的问题 (#64335)
+- 修复 Partition TopN 优化要求窗口函数分区键必须是共置键子集的问题 (#65073)
+- 修复 sender queue 就绪时 sorted merge 的继续执行逻辑 (#65004)
+- 修复使用子节点前缀排序键时的 TopN 合并问题 (#64685)
+- 修复可空聚合函数的 visitor 分派问题 (#64885)
+- 修复存在重复别名时聚合合并结果错误的问题 (#65025)
+- 修复 timeout checker 遇到异常后停止运行的问题 (#65040)
+- 限制 auto salt join 重写,避免产生错误结果 (#64518)
+- 修复 delete partial update 对生成列的处理 (#64884)
+- 在 `PushDownAggThroughJoinOnPkFk` 中使用 arity 检查保护 `Count(*)` 子节点访问 (#64848)
+- 修复云模式查询重试在错误码为 -230 时临时将 `version_cache_ttl` 设为 0 的问题 (#63721)
+- 修复 recursive CTE 数据块未发送到每个 scan instance 的问题 (#64964)
+- 修复 SHOW PROCESSLIST 在 FULL 模式下返回非预期结果的问题 (#64631)
+- 修复 `isNull` 在解析器中未置于 primaryExpression 下的问题 (#63619)
+- 修复子查询错误消除 null-aware anti-join 的问题 (#64639)
+- 当 `light_schema_change=false` 时跳过 TopN 延迟物化 (#64441)
+- 修复 `copy into select` 未绑定文件列占位符的问题 (#64591)
+- 根据父节点 offset 限制 MERGE_TOP_N 的合并 limit (#64306)
+- 阻止 cast project 下推穿过 UNION DISTINCT (#64080)
+- 对共享基列禁用 row-store 延迟读取 (#62864)
+- 修复 FragmentMgr 清理 query context map 时发生的自死锁 (#64552)
+- 修复 month 函数对可空 DateTimeV2 字面量的绑定 (#64459)
+- 移除错误的 TopN-to-MAX 优化器重写 (#63519)
+- 修复本地 Runtime Filter 合并死锁 (#65102)
+- 从物化算子的 row-ID fetcher 返回错误状态 (#62513)
+- 规范化聚合函数的 ORDER BY 下推 (#64787)
+- 修复 proxy 流程因缺少 parsedStatement 而未创建 NereidsCoordinator 的问题 (#64363)
+- 拒绝 COUNT DISTINCT 窗口函数使用超过 1 个参数 (#64783)
+- 避免 `convert_tz` 在 DST 切换期间执行错误的分区裁剪 (#63853)
+- 修复 `MAKE_SET` 常量折叠未清除先前结果的问题 (#64907)
+- 避免 `int_divide` 处理有符号最小值时触发 SIGFPE (#64828)
+- 修复 `nth_value` 对上界窗口的处理 (#64864)
+- 当权重和为零时让 `avg_weighted` 返回 NaN (#64333)
+- 稳定 conjunct cost 排序 (#64637)
+- 取消后停止算子的额外工作 (#64077)
+- 修复 `array_first`/`array_last` 的布尔类型转换 (#64847)
+- 校验 `array_sort` 的 lambda 参数数量 (#64825)
+- 将 `retention` 参数数量限制为 32,避免 BE heap 溢出 (#64521)
+- 修复 `concat_ws` 对可空数组的处理 (#64703)
+- 修复 `split_by_delimiter` 未处理反斜杠转义的问题 (#61995)
+- 规范化 v1 日期字符串的转换结果 (#64575)
+- 修复高 EPS 下 AgentCombiner/TLS 竞态导致 `bvar::take_sample` 触发 SIGSEGV 的问题 (#64040)
+- 校验 sequence pattern 的事件编号 (#64930)
+- 在 pipeline scheduler 中捕获标准异常 (#65019)
+- 捕获 block 序列化异常,避免 coredump (#64852)
+- 校验 task executor 的 scan handle (#65054)
+- 在 AsyncIO worker 线程中初始化 thread context (#64846)
+- 修复读取切片后的 FixedSizeBinary Arrow 字符串的问题 (#64829)
+- 避免对可变 nullable 数据执行 CRC32C 哈希 (#64944)
+- 在 OUTFILE CSV 导出中保留 DateTimeV2 scale (#64344)
+- 在 CREATE TABLE 时拒绝无效的 IPv4 默认值 (#62906)
+- 强化 Arrow Flight split source 错误路径,避免扫描外表时 BE 崩溃 (#64797)
+
+## 存储与压缩
+- 修复 group commit block queue 不可用的问题 (#63722)
+- 修复 prepared statement 复用具有相同 load_id 的计划时 group commit 丢行的问题 (#64362)
+- 修复 shared delta writer state 的生命周期问题 (#64504)
+- 修复 load channel 中 tablet writer map 的查找竞态 (#64604)
+- 避免 cloud compaction 在同一 tablet 上发生 EMPTY_CUMULATIVE / BASE-CUMU 竞态 (#64619)
+- 持久化子事务提交的更新时间 (#64739)
+- 使 S3 限流配置在云模式下动态生效 (#64554)
+- 使用 snapshot read 获取 table version,避免事务冲突 (#64647)
+- 回收缺少 resource ID 的空 rowset (#64630)
+- 修复 recycler_service 重复赋值导致 recycler 异常的问题 (#64168)
+- 避免将未过期 job 临时 rowset 的 delete bitmap 误报为泄漏 (#64313)
+- 在执行 Schema Change V1 前刷新 base tablet (#64312)
+- 禁止在云模式下恢复 `light_schema_change=false` 的表 (#62914)
+- 在云模式恢复期间重写表属性和分区信息 (#64466)
+- 同步清理 File Cache 时使用安全删除 (#64578)
+- 在 HTTP API 中禁用同步清理 File Cache (#64321)
+- 计算 File Cache 命中率指标时排除预热读取 (#63394)
+- 在 INSERT OVERWRITE 中强制删除分区 (#62510)
+- 为 `getBaseViewsOneLevel` 增加空值保护,保持 MTMV 向后兼容 (#64412)
+- 修复 MTMV 修改已排除触发表的问题 (#62984)
+- 避免已结束的 pipeline task 在提交时崩溃 (#64953)
+
+## 导入
+- 串行组装 audit loader batch,修复竞态问题 (#65107)
+- 在提交失败时为 Routine Load 任务续租加锁 (#65007)
+- 修复 Stream Load 对 IPv6 主机地址的解析 (#64147)
+- 使用事件唤醒替换 tablet writer close 轮询 (#64221)
+- 修复转发的 INSERT 统计信息为空的问题 (#64439)
+- 修复 `load_to_single_tablet` 对自动分区的路由 (#64356)
+- 在审计日志中记录每个查询通过 SET_VAR hint 设置的会话变量 (#64569)
+- 修复 AVRO JNI reader 在 TVF 执行期间因空指针导致的 coredump (#64699)
+
+## 湖仓一体
+- 修复 Iceberg snapshot summary 缺少 total-* 计数器时下推 COUNT(*) 触发的 NPE (#64648)
+- 修复 Iceberg 二进制列使用错误 Arrow 类型写入的问题 (#64949)
+- 为 Iceberg/Hive Parquet/ORC writer 增加 LZ4 压缩支持 (#64723)
+- 支持禁用 REST Catalog 的 view 操作 (#63320)
+- 读取 Hive text 文件时保留空记录 (#64671)
+- 根据 Arrow buffer 估算 MaxCompute write block 大小,避免逐行序列化估算 (#64612)
+- JDBC 下推 SQL Server/Oracle 布尔谓词时使用 1/0,而不是 TRUE/FALSE (#64757)
+- 从 Iceberg 表元数据中获取表 comment 属性 (#64263)
+- 避免外部元数据缓存刷新因慢速 miss load 而阻塞 (#64705)
+
+## 安全与认证
+- 为 manager node 和 query error REST API 增加权限检查 (#65080)
diff --git a/releasenotes/core.md b/releasenotes/core.md
index 5e069ba552d..2f21047585f 100644
--- a/releasenotes/core.md
+++ b/releasenotes/core.md
@@ -11,7 +11,7 @@ This document presents Apache Doris Core release notes in
reverse chronological
## Doris Core Release Notes
:::tip Latest Release
-🎉 Version 4.1.2 is released. Check out the 🔗[Release
Notes](./v4.1/release-4.1.2.md) here. Apache Doris 4.1 takes a major step
forward with new AI support (vector search, AI Functions) and better full-text
search capabilities. These upgrades help users stay ahead in the AI and agent
era, enabling companies to handle everything from traditional BI analysis to
AI-driven workloads.
+🎉 Version 4.1.3 is released. Check out the 🔗[Release
Notes](./v4.1/release-4.1.3.md) here. Apache Doris 4.1.3 adds Python
UDF/UDAF/UDTF support, workload policy enhancements, table-level event-driven
warm up, and zstd Stream Load compression. It also includes fixes across query
execution, storage, load, lakehouse, and authentication.
<br />
@@ -34,6 +34,8 @@ This document presents Apache Doris Core release notes in
reverse chronological
<br />
+- [2026-07-13, Apache Doris 4.1.3 is released](./v4.1/release-4.1.3.md)
+
- [2026-07-12, Apache Doris 4.0.7 is released](./v4.0/release-4.0.7.md)
- [2026-06-17, Apache Doris 4.1.2 is released](./v4.1/release-4.1.2.md)
diff --git a/releasenotes/v4.1/release-4.1.3.md
b/releasenotes/v4.1/release-4.1.3.md
new file mode 100644
index 00000000000..c3bc41efe24
--- /dev/null
+++ b/releasenotes/v4.1/release-4.1.3.md
@@ -0,0 +1,149 @@
+---
+{
+ "title": "Release 4.1.3",
+ "language": "en",
+ "description": "Here's the Apache Doris 4.1.3 release notes:"
+}
+---
+
+# New Features
+
+## Query & Execution
+- Support Python UDF/UDAF/UDTF (#63387)
+- Support remote scan bytes breaker in workload policy (#64649)
+- Add username-based backend workload policy support (#60559)
+- Add `is_valid_utf8` function (#62515)
+
+## Cloud Native
+- Add table-level event-driven warm up (#63832)
+
+## Load
+- Support zstd stream load compression (#64711)
+
+## Index & Search
+- Add ANN TopN small-candidate fallback to brute-force search (#64555)
+
+# Improvement
+
+## Query & Execution
+- Treat `inactive_file` as available memory to avoid false query cancellations
in cgroup environments (#64347)
+- Set default profile level to 2 to print more detail query profile by default
(#64378)
+- Add configurable return mode for insert publish timeout in ETL scenarios
(#64583)
+
+## Load
+- Make from-to streaming task timeout progress-aware (#64301)
+- Add zero-row hint for Kafka read_committed load and delay zero-row retries
(#64585)
+- Refresh routine load lag metrics more timely (#63654)
+
+## Cloud Native
+- Add rate limit for BE to Meta-Service RPC (#64396)
+- Add virtual compute group switch metric (#63036)
+
+# Bugfix
+
+## Index & Search
+- Handle truncated sparse path stats when reading Variant (#64205)
+- Preserve JSON object when casting JSONB to Variant (#63792)
+- Return raw string for `element_at` on scalar-string Variant (#64103)
+- Escape JSONB path member control characters (#63517)
+
+## Query & Execution
+- Fix `SimplifyAggGroupBy` should verify injectivity (#64335)
+- Fix partition TopN optimization requiring window function partition key to
be subset of co-located keys (#65073)
+- Fix sorted merge continuing when sender queue is ready (#65004)
+- Fix TopN merge with child prefix order keys (#64685)
+- Fix nullable aggregate visitor dispatch (#64885)
+- Fix incorrect aggregate merge with duplicate aliases (#65025)
+- Fix timeout checker stopping after an exception (#65040)
+- Restrict auto salt join rewrite to avoid incorrect results (#64518)
+- Fix handling of generated columns in delete partial update (#64884)
+- Guard `Count(*)` child access with arity check in
`PushDownAggThroughJoinOnPkFk` (#64848)
+- Fix cloud query retry setting `version_cache_ttl` temporarily to 0 on error
-230 (#63721)
+- Fix recursive CTE blocks not sent to every scan instance (#64964)
+- Fix SHOW PROCESSLIST returning unexpected result in FULL mode (#64631)
+- Fix `isNull` parser placement under primaryExpression (#63619)
+- Fix subquery eliminating null-aware anti-join incorrectly (#64639)
+- Skip TopN lazy materialization when `light_schema_change=false` (#64441)
+- Fix `copy into select` not binding file column placeholders (#64591)
+- Clamp merged limit of MERGE_TOP_N by parent offset (#64306)
+- Prevent cast project pushdown through UNION DISTINCT (#64080)
+- Disable row-store lazy fetch for shared base columns (#62864)
+- Fix self-deadlock in FragmentMgr on query context map clear (#64552)
+- Fix month nullable datetimev2 literal binding (#64459)
+- Remove incorrect topN-to-MAX optimizer rewrite (#63519)
+- Fix local runtime filter merge deadlock (#65102)
+- Return error status from materialization operator row-ID fetcher (#62513)
+- Normalize aggregate order-by pushdown (#64787)
+- Fix NereidsCoordinator not created in proxy flow due to missing
parsedStatement (#64363)
+- Reject more than 1 argument in COUNT DISTINCT window function (#64783)
+- Avoid incorrect `convert_tz` partition pruning across DST transitions
(#63853)
+- Fix `MAKE_SET` constant folding not clearing prior result (#64907)
+- Prevent SIGFPE in `int_divide` for signed minimum value (#64828)
+- Fix `nth_value` for upper-bounded windows (#64864)
+- Return NaN for `avg_weighted` when sum of weights is zero (#64333)
+- Stabilize conjunct cost ordering (#64637)
+- Stop extra operator work after cancellation (#64077)
+- Fix `array_first`/`array_last` boolean cast (#64847)
+- Validate `array_sort` lambda arity (#64825)
+- Limit `retention` param count to 32 to avoid BE heap overflow (#64521)
+- Fix `concat_ws` nullable array handling (#64703)
+- Fix `split_by_delimiter` missing backslash escape handling (#61995)
+- Normalize v1 date string cast result (#64575)
+- Fix SIGSEGV in `bvar::take_sample` under high EPS due to AgentCombiner/TLS
race (#64040)
+- Validate sequence pattern event numbers (#64930)
+- Catch standard exceptions in pipeline scheduler (#65019)
+- Catch block serialization exceptions to avoid coredump (#64852)
+- Validate task executor scan handles (#65054)
+- Initialize thread context on AsyncIO worker threads (#64846)
+- Fix sliced FixedSizeBinary Arrow string reads (#64829)
+- Avoid mutable nullable CRC32C hashing (#64944)
+- Preserve DateTimeV2 scale in OUTFILE CSV export (#64344)
+- Reject invalid IPv4 default value at CREATE TABLE time (#62906)
+- Harden Arrow Flight split source error path to avoid BE crash on external
scan (#64797)
+
+## Storage & Compaction
+- Fix group commit block queue unavailability (#63722)
+- Fix lost rows when prepared statement reuses plan with same load_id in group
commit (#64362)
+- Fix shared delta writer state lifetime (#64504)
+- Fix tablet writer map lookup race in load channel (#64604)
+- Prevent EMPTY_CUMULATIVE / BASE-CUMU races on same tablet in cloud
compaction (#64619)
+- Persist update time for sub-transaction commit (#64739)
+- Make S3 rate limiter config take effect dynamically in cloud mode (#64554)
+- Use snapshot read for table version to avoid transaction conflict (#64647)
+- Recycle empty rowsets without resource ID (#64630)
+- Fix double assignment in recycler_service that caused recycler malfunction
(#64168)
+- Avoid false-positive leaked delete bitmaps for unexpired job tmp rowsets
(#64313)
+- Refresh base tablet before schema change V1 (#64312)
+- Forbid restoring table with `light_schema_change=false` in cloud mode
(#62914)
+- Rewrite table properties and partition info during cloud restore (#64466)
+- Make sync file cache clear use safe removal (#64578)
+- Disable sync file cache clear in HTTP API (#64321)
+- Exclude warmup reads from file cache hit ratio metrics (#63394)
+- Force drop partition in INSERT OVERWRITE (#62510)
+- Add null-safety to `getBaseViewsOneLevel` for MTMV backward compatibility
(#64412)
+- Fix MTMV mutating excluded trigger tables (#62984)
+- Avoid finalized pipeline task submit crash (#64953)
+
+## Load
+- Serialize audit loader batch assembly to fix race (#65107)
+- Lock routine load task renew on submit failure (#65007)
+- Fix stream load IPv6 host parsing (#64147)
+- Replace tablet writer close polling with event wakeup (#64221)
+- Fix empty statistics for forwarded INSERT (#64439)
+- Fix `load_to_single_tablet` routing for auto partition (#64356)
+- Record per-query SET_VAR hint session variables in audit log (#64569)
+- Fix AVRO JNI reader coredump with null pointer during TVF function (#64699)
+
+## Lakehouse
+- Fix NPE in Iceberg COUNT(*) pushdown when snapshot summary omits total-*
counters (#64648)
+- Fix Iceberg binary columns written with wrong Arrow types (#64949)
+- Add LZ4 compression support for Iceberg/Hive Parquet/ORC writers (#64723)
+- Allow disabling REST catalog view operations (#63320)
+- Preserve empty text records when reading Hive text files (#64671)
+- Estimate MaxCompute write block size from Arrow buffers instead of per-row
serialization (#64612)
+- Push SQL Server/Oracle boolean predicates as 1/0 instead of TRUE/FALSE in
JDBC (#64757)
+- Fetch table comment PROP from Iceberg table metadata (#64263)
+- Avoid blocking external meta cache refresh on slow miss load (#64705)
+
+## Security & Authentication
+- Add auth check for manager node and query error REST APIs (#65080)
diff --git a/sidebarsReleases.json b/sidebarsReleases.json
index fab2f92d99a..ad43b9d3b79 100644
--- a/sidebarsReleases.json
+++ b/sidebarsReleases.json
@@ -13,6 +13,7 @@
"type": "category",
"label": "v4.1",
"items": [
+ "v4.1/release-4.1.3",
"v4.1/release-4.1.2",
"v4.1/release-4.1.1",
"v4.1/release-4.1.0"
diff --git a/src/constant/download.data.ts b/src/constant/download.data.ts
index 007902ca470..980f295fd25 100644
--- a/src/constant/download.data.ts
+++ b/src/constant/download.data.ts
@@ -30,7 +30,7 @@ export enum ToolsEnum {
export const ORIGIN = 'https://download.selectdb.com/';
export enum VersionEnum {
- Latest = '4.1.2',
+ Latest = '4.1.3',
Prev = '4.0.7',
Earlier = '3.1.4',
}
@@ -40,6 +40,40 @@ export enum DownloadTypeEnum {
Source = 'Source',
}
export const DORIS_VERSIONS: Option[] = [
+ {
+ label: '4.1.3',
+ value: '4.1.3',
+ majorVersion: '4.1',
+ children: [
+ {
+ label: CPUEnum.X64,
+ value: CPUEnum.X64,
+ gz: `${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz`,
+ asc: `${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz.asc`,
+ sha512: `${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ {
+ label: CPUEnum.X64NoAvx2,
+ value: CPUEnum.X64NoAvx2,
+ gz: `${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz`,
+ asc: `${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ {
+ label: CPUEnum.ARM64,
+ value: CPUEnum.ARM64,
+ gz: `${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz`,
+ asc: `${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz.asc`,
+ sha512: `${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ ],
+ },
{
label: '4.1.2',
value: '4.1.2',
@@ -500,6 +534,74 @@ export const ALL_VERSIONS: AllVersionOption[] = [
label: '4.1',
value: '4.1',
children: [
+ {
+ label: '4.1.3',
+ value: '4.1.3',
+ majorVersion: '4.1',
+ items: [
+ {
+ label: CPUEnum.X64,
+ value: CPUEnum.X64,
+ gz: `${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz`,
+ asc: `${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.3-bin-x64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ {
+ label: CPUEnum.X64NoAvx2,
+ value: CPUEnum.X64NoAvx2,
+ gz:
`${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz`,
+ asc:
`${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.3-bin-x64-noavx2.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ {
+ label: CPUEnum.ARM64,
+ value: CPUEnum.ARM64,
+ gz: `${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz`,
+ asc:
`${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.3-bin-arm64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.3/',
+ version: '4.1.3-rc02',
+ },
+ ],
+ },
+ {
+ label: '4.1.2',
+ value: '4.1.2',
+ majorVersion: '4.1',
+ items: [
+ {
+ label: CPUEnum.X64,
+ value: CPUEnum.X64,
+ gz: `${ORIGIN}apache-doris-4.1.2-bin-x64.tar.gz`,
+ asc: `${ORIGIN}apache-doris-4.1.2-bin-x64.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.2-bin-x64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.2/',
+ version: '4.1.2',
+ },
+ {
+ label: CPUEnum.X64NoAvx2,
+ value: CPUEnum.X64NoAvx2,
+ gz:
`${ORIGIN}apache-doris-4.1.2-bin-x64-noavx2.tar.gz`,
+ asc:
`${ORIGIN}apache-doris-4.1.2-bin-x64-noavx2.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.2-bin-x64-noavx2.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.2/',
+ version: '4.1.2',
+ },
+ {
+ label: CPUEnum.ARM64,
+ value: CPUEnum.ARM64,
+ gz: `${ORIGIN}apache-doris-4.1.2-bin-arm64.tar.gz`,
+ asc:
`${ORIGIN}apache-doris-4.1.2-bin-arm64.tar.gz.asc`,
+ sha512:
`${ORIGIN}apache-doris-4.1.2-bin-arm64.tar.gz.sha512`,
+ source:
'https://dist.apache.org/repos/dist/release/doris/4.1/4.1.2/',
+ version: '4.1.2',
+ },
+ ],
+ },
{
label: '4.1.1',
value: '4.1.1',
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]