This repository builds the English/Japanese and Chinese documentation sites from one audited codebase. The two sites share tooling and UI packages, but use independent site profiles and content trees. Chinese is not rebuilt through Docusaurus i18n because its product capabilities and document structure differ from English.
- Node.js 22 or newer
- pnpm 10.33.0 (
corepack enable)
Install dependencies with:
pnpm install --frozen-lockfileEnglish includes the Japanese translation tree:
pnpm start:en
pnpm build:enPreview Japanese locally with pnpm start:en --locale ja-JP. The English build includes both locales and writes Japanese pages below build/en/ja-JP.
Chinese is an independent site profile:
pnpm start:zh-CN
pnpm build:zh-CNBuild output is written to build/en and build/zh-CN.
- English source content:
content/en - Chinese source and translated content:
content/zh-CN - Japanese translations:
i18n/ja-JP - Generated sidebars and manifests:
generated/<site> - Site configuration:
packages/site-config - Docusaurus application:
apps/docs - Shared and site-specific UI:
packages/docs-ui - Content production and publication CLI:
packages/docs-tooling
Japanese content follows the English document structure and ships as part of the English site. Agent-driven translation has three explicit targets: ja-JP, zh-CN-reference, and zh-CN-tools. Chinese source publication must preserve the Agent-owned content/zh-CN/guides/tutorials/tools subtree.
Logical content ownership above is distinct from Git branch ownership. deploy/contracts/master-tooling-sync.json is authoritative:
masterowns reviewed tooling: workflows, scripts, apps, ordinary packages, tests, repository configuration, and prose. Merge changes through a master PR, then use the validated master-to-dev sync PR when production needs them.devowns generated publication state under the contract'sdevOwnedPaths, including the maincontent,generated,i18n, cache, snapshot/report, legacy publication, and sidebar-override roots. Only serialized publication/reconciliation workflows should update those paths.masterAuthoritativePathsare exact policy or preserved-content exceptions inside otherwise dev-owned roots. They remain master-owned and are carried into dev by the sync workflow.candidateDerivedPathsare regenerated and validated on the exact sync candidate; do not hand-edit them.
See the code-to-test and branch-ownership matrix, or run pnpm test:for-change -- <repository-relative-path>... to print both branch policy and required tests. For the complete committed diff, use pnpm test:for-change -- --base origin/master --head HEAD.
Use the site-qualified docs tooling commands and workflows. Do not publish by invoking retired root Docusaurus or plugin wrappers.
pnpm test:workflow-policy
pnpm test:retirementGitHub Actions owns source production, translation, validation, and image build orchestration. English/Japanese and Chinese production remain independently addressable. External Jenkins UAT and Prod pipelines consume the selected repository branch through the same site-qualified build interface; Jenkins configuration is maintained outside this repository.
Reference manuals (SDK/API docs such as python, java, node, go, cli, rest) are
single-sourced from packages/docs-tooling/src/manuals/registry.ts. Adding a
new manual is a registry change plus generated-artifact regeneration; do not
hand-edit navigation, sidebars, validation sets, or translation policy.
-
Add a definition to
packages/docs-tooling/src/manuals/registry.tswithkind: 'reference', the remote/localsources,publications, and apresentationblock (see an existing manual such as node). The registry validator requirespresentationfor reference manuals and cross-checks sidebar,documentIdPrefix, andlandingPageconsistency. -
Regenerate all derived artifacts and run the drift checks:
pnpm generate:lark-config pnpm generate:reconciliation-policy pnpm generate:reference-presentation pnpm check:lark-config pnpm check:reconciliation-policy pnpm check:reference-presentation pnpm check:localization-input-inventory
This updates
config/lark-docs.config.ts,config/translation/reconciliation-policy.json,config/reference-navigation.json, the generated site-config fragments (packages/site-config/src/generated/referencePresentation.ts, sidebars{en,zh-CN}/reference.ts), and the docs-ui reference targets module (packages/docs-ui/src/shared/navigation/referenceTargets.generated.ts). -
Update the static deploy contract
deploy/contracts/path-filters.json: addgenerated/en/sidebars/<sidebar>.sidebar.js(where<sidebar>is the manual'spresentation.sidebar) tocanonicalEnglishReference.includeand tositeOwned.en.exclude.check:reference-presentationfails with a drift error until this is done. -
Update the static CI input list in
.github/workflows/fetch-docs.yml: thegroupinput description lists valid groups, and thepreparejob'scaseenumerates known groups. Add the new manual id to both (GitHub Actions workflow_dispatch inputs cannot be derived from the registry). -
Update test fixtures that hard-code the group list (
['guides','python','java','node','go','cli','rest']) to include the new manual id. These live inscripts/docs-workflow/*.test.js,scripts/translation/*.test.js,scripts/*.test.js, andpackages/docs-tooling/src/**/*.test.ts; run the affected suites after updating them. -
Verify:
pnpm typecheck,pnpm test:workflow-policy,pnpm vitest run packages/docs-tooling/src/manuals, the docs-workflow and translation node:test suites, andpnpm build:en. For a first real fetch, dispatchfetch-docs.ymlwithpublish=falseandrun_translations=falseto validate the matrix and selection before any production publish.
REST interfaces are generated from the OpenAPI fragments under
packages/docs-tooling/src/reference/rest/meta/openapi/. Treat a fragment
change as a source-and-contract change; do not hand-edit generated REST MDX,
Chinese REST sidebars, or Japanese REST translation files.
When adding an operation:
- Add or update the operation in the correct fragment, including its HTTP
method, path, tag,
summary, request/response schemas, and localizedx-i18nfields. Keep the fragment registered inmeta/fragment-migration.jsonwhen it is part of the formal REST publication set. - For every operation generated for
zh-CN, add the exact page title used by the Chinese generator as an entry topackages/docs-tooling/src/reference/rest/meta/titles.json. The value is the stable English slug without the automatic-v2suffix. For example,创建 Spark 回填任务maps tocreate-spark-backfill-job. Missing this entry fails Chinese REST generation before the site build. - Check whether the change affects lifecycle evidence, manifest inputs, generated sidebars, or translation ownership. These are derived outputs and must be regenerated by the supported tooling, not edited manually.
- Run
pnpm test:for-change -- <changed-path>...for every changed source, metadata, workflow, or test path. A selector failure means the code-to-test matrix is missing an entry and must be handled in the same PR. - Before submitting, run the selector's full command union,
pnpm test:rest-publication-contract,pnpm test:workflow-policy, andgit diff --check. Also run the Chinese REST generator in a temporary output directory and confirm the expected MDX filenames, titles, and slugs.
When deleting or renaming an operation, remove it from the source fragment and update the corresponding fragment migration/lifecycle evidence and any explicit navigation or manifest contract that refers to it. Verify the generated diff contains the expected deletion or rename and no unrelated generated files. A deleted generated page may also leave a translation input or cache entry; let the reconciliation tooling classify and handle that change rather than deleting translation files directly.
REST publication also produces one generated/<locale>/manifests/rest-derivation.json
for each generated locale. The English REST lane owns the en and ja-JP
manifests; the Chinese REST lane owns zh-CN. Each manifest binds the complete
OpenAPI fragment set by SHA-256 to the exact generator tooling commit, and the
Fetch validation and atomic publication boundary rechecks those fragment hashes.
These files are dev-owned publication state and must be generated by the REST
lane rather than edited on master.
The English build runs both English and Japanese locales. A new REST operation
can therefore fail later, during Validate and build generated docs, if its
generated Japanese localization input is untracked or lacks valid candidate
provenance, even when English generation succeeded. The Chinese producer has a
separate build:zh-CN path and the Chinese titles.json check happens earlier,
but it must still pass its own validation and build stage after generation.
For a new interface, the minimum review checklist is: source operation and
x-i18n; Chinese title mapping; fragment/lifecycle/manifest contracts; test
matrix coverage; generated English/Chinese output; and the English build's
Japanese localization provenance.
Pipeline map:
fetch-translation-pipeline-map.md— the phase-by-phase flow, artifact contracts, the 4 Git push points, and the known failure modes with their recovery entries.
The normal production entry point is fetch-docs.yml. It publishes the selected English source units to dev, performs final verification, and can dispatch one downstream Translation workflow. Every dev writer is serialized through the docs-production-dev concurrency group with queue: max; do not bypass that queue with a second manual writer. Translation and operator recovery both use the short production lock: the paid producer run stays in a unique read-only group (translation-readonly-* or translation-recovery-readonly-*), and when it finishes it dispatches publish-translation.yml, which owns docs-production-dev only for the publication transaction (Fetch and offline publication keep their existing whole-run queue ownership). The publisher also refuses a producer selection whose units were already superseded by a successful operator recovery, so a stale retained selection cannot be re-dispatched after its recovery published.
Incremental Translation reuses unchanged published semantic-unit translations only when it is explicitly enabled. Set the Actions repository variable TRANSLATION_SEMANTIC_SEEDS=true to turn it on for pipeline-triggered runs (or pass the semantic_seeds input on a manual dispatch); remove the variable to fall back to full per-file retranslation. Each run's translation report shows Seeded files and Seeded units so the reuse rate is observable.
Japanese translation also carries two quality assets maintained on master. The authoritative Japanese and Simplified Chinese house styles live in .github/prompts/codex-style-guide.ja-JP.md and .github/prompts/codex-style-guide.zh-CN-reference.md; each is registered as the target's style prompt and injected into every translation stage between the locale contract and the stage prompt, and each participates in the recovery prompt-contract hash (promptContractSha256), so editing one changes that target's recovery compatibility exactly like any other prompt change. A read-only translation-memory helper backs style and terminology retrieval: node scripts/translation/tm-retrieve.js --repository <abs> --checkpoint <sha> --page <content/en/...> returns published EN/JA neighbor pairs with anchor-aligned excerpts, and --render-fewshot renders them as a prompt block; it is offline tooling and no workflow invokes it yet. An experimental whole-file agentic provider is also wired behind a repository variable: setting TRANSLATION_PROVIDER=agentic (with DEEPSEEK_API_KEY/DEEPSEEK_BASE_URL, optionally AGENTIC_TRANSLATION_MODEL, default deepseek-flash) makes the Translation producer run scripts/translation/agenticProvider.js — file-level agent sessions with bounded deterministic repair — instead of the unit pipeline; remove the variable to return to the default. Its surgical incremental lane (scripts/translation/surgicalUpdate.js) updates changed files in place against the source delta — unchanged bytes pass through untouched — behind the same provider; lane selection is a deterministic section-hash classifier and a protected-token guard proves link/anchor/code updates landed. See the design spec for the asset roadmap.
- Confirm that the intended tooling is already on
devthrough the PR-basedsync-master-tooling-to-dev.ymlworkflow. The candidate must be an exact master commit and the resulting merge must be identifiable in the workflow and PR history. - Check the current
devtip and make sure no production Fetch, Translation, or tooling run is already active. - For a risky workflow or artifact change, run the repository tests and a local real-artifact replay first. A replay must use real retained checkpoint archives, a local bare Git remote, the exact validation commands and site environment, and an isolated dependency layout. It must never push to the real
origin.
Use the docs-workflow code-to-test matrix to map changed files to focused tests, replay harnesses, and broader gates. pnpm test:for-change -- <repository-relative-path>... prints the required command union and fails when a workflow path is not mapped.
Use the stable package scripts so local development and CI exercise the same suites:
pnpm test:replay:fetchcovers Fetch FIFO publication, fault injection, final business evidence, and local-remote safety.pnpm test:replay:recoverycovers retained Translation selection/results/progress authentication, legacy REST exclusion, recovery-map construction, and non-mutating fault overlays.pnpm test:replay:translationcovers Translation FIFO publication and monitor artifact authentication; it is slower and runs on the scheduled replay workflow.pnpm test:replay:requestedcovers the requested Guides fetch surfaces (planner closure, state-merge receipts, checkpoint scope) against a committed real-data slice extracted from retained production artifacts.pnpm test:replayis the PR-level contract + Fetch + recovery gate.pnpm test:replay:allruns every hermetic replay suite before a cross-pipeline merge.
The replay harnesses use the platform system temporary directory for their isolated lanes, replicas, and evidence roots. The translation replay harness accepts an explicit override via ZDOC_REPLAY_SAFE_ROOT when the default is not appropriate.
These hermetic suites are regression harnesses; they do not replace a real retained-artifact replay. For a recovery change, prepare an isolated snapshot root containing run.json, attempt.json, jobs.json, artifacts-unique.json, artifact-directories.json, and the mapped artifact directories, then run:
node scripts/docs-workflow/replay-recovery-plan.js \
--snapshot-root "$ZDOC_RECOVERY_SNAPSHOT" \
--output-root "$ZDOC_RECOVERY_OUTPUT" \
--repository zilliztech/zdoc \
--execution-tooling-sha "$ZDOC_RECOVERY_TOOLING_SHA" \
--target-baseline-sha "$ZDOC_RECOVERY_TARGET_SHA" \
--publish falseThe snapshot and output roots must be distinct absolute paths below the platform's system temporary directory (or the safe root explicitly set with ZDOC_RECOVERY_REPLAY_SAFE_ROOT), and the output root must be empty. The harness authenticates the original retained identity, never pushes Git state, never invokes paid Translation, and writes fault injection to a separate overlay. Use --simulate-failure only for synthetic regression scenarios, for example translation/ja-JP/guides,translation/zh-CN-reference/python. Preserve the generated recovery-plan.json, selection SHA, target baseline, recovery units, rejected units, retained-file count, and source-candidate count as merge evidence.
pnpm test:replay:requested runs hermetically against a committed real-data slice. Before enabling or changing publish=true behavior in fetch-guides-requested.yml, additionally replay the complete retained artifacts of a recent successful Fetch run:
- Pick a successful
fetch lark docsrun ondev, note its run ID, attempt,masterSha, anddevBaselineShafrom the checkpoint manifest. - Download the retained
guides-sources-en-<run>artifact, thedocs-checkpoint-guides-en-<run>artifact, and the Guides table artifacts for the run's affected tables into an isolated evidence root (never the realorigin), recording each artifact ID andsha256in amanifest.txtline format<id> <name> <sha256>. - Extract
guides-source.tarand the checkpoint/table archives belowartifacts/in that root and write areal-context.jsondescribing the run identity, the baseline snapshot path (fromgit show <devBaselineSha>:packages/docs-tooling/src/lark/meta/snapshots/guides-uat-last-success.json), the candidate snapshot path, the source directory, the site root token, and the table artifact manifest directories. - Run
REPLAY_REQUESTED_GUIDES_ROOT=<evidence-root> pnpm test:replay:requested. The harness verifies every recorded digest, preflights the checkpoint archive, replays the stable-table, outline-change, cross-table-move, and closure-external-conflict scenarios, and proves the ordinary automatic planner still detects the external change after a blocked requested run. - Preserve the generated
replay-report.json(run identity, artifact digests, checkpoint file count, per-scenario plan/receipt hashes, isolation results) as the merge and rollout evidence.
For a normal full release, dispatch fetch lark docs with:
group=alltarget_branch=devpublish=truemedia_upload_mode=writerun_translations=truewhen the downstream Translation handoff is required- the intended
tooling_refandsource_ref(normallymasteranddev)
Use publish=false only for an artifact-only validation. media_upload_mode=skip is restricted to the controlled artifact-only Guides validation contract; it is not a production publish mode.
Monitor the parent run through these boundaries, in order:
- producer jobs create and validate the immutable checkpoint artifacts;
publish_readyconsumes the immutable selection and publishes in producer-completion FIFO order;reconcile_reference_stateperforms the allowed generated-state reconciliation;source_publication_barrierproves every required source group succeeded before paid Translation is dispatched;prepare_translation_handoffwrites the schema-v3 handoff bound to the exact source commits and reconciled target SHA;dispatch_translationsstarts the single child Translation run;verify,aggregate, and card finalization reach terminal state.
Do not treat a successful parent run as proof that Translation finished. When Translation is requested, open the child run from the parent handoff metadata and wait for its own terminal aggregate/publish_ready evidence. The final production identity is the reconciled dev SHA, not merely the last producer result.
Fetch and Translation runs report progress through Feishu cards created by pnpm docs-tooling report-card, and the scheduled watchdogs (docs ingestion watchdog, External Link Watchdog, Broken Links Report) post alert cards only when they detect a problem. Card sending is non-essential reporting: every card step is best effort, and monitor jobs start only when a card exists.
To silence all workflow Feishu cards without touching the Feishu app credentials (which Fetch also uses to read source content), set the repository variable FEISHU_NOTIFICATIONS_DISABLED to true in GitHub Actions settings. The five card-sending workflows forward it to docs-tooling through their root env, the report-card command skips every action without contacting Feishu, and the resulting empty card_id output keeps all monitor and downstream card jobs disabled. Unset the variable, or set it to any other value, to restore notifications — no code or workflow change is required. This switch does not affect Feishu bitable reporting (report-broken-links, report-external-links) or Fetch content reads.
Record the run URLs and retain these facts before handing off to Jenkins:
- Fetch selection and terminal results, including the runtime FIFO and final
devSHA; - every successful result SHA is an ancestor of the final target;
- source publication barrier and final verification passed;
- localization input inventory and English revision inventory passed;
- Guides reports were collected for both
enandzh-CN, and the final card contains exactly nine available notes with noUnavailableentry; - if Translation ran, its own selection/results, reconciliation result, FIFO order, ancestry, and absence of unknown remote state — for a split run, the selection/results live in the producer run while the publication progress/results artifacts are uploaded by the publisher run (linked from the producer's
dispatch_publicationjob and thedocs-translation-publication-handoff-*metadata artifact); - the exact
devSHA that Jenkins builds.
Jenkins should be started or inspected only after the exact SHA is known. Confirm that the build label contains that SHA and that the intended target (for example EN+CN → UAT) is shown. A branch name alone is not sufficient evidence.
Stop at the first failed boundary and classify the failure before retrying:
- Prepare or producer failure: no publication writer should have run. Inspect the failed job log, selection, and checkpoint artifact preflight. Fix the local cause and rerun the same workflow inputs; do not start Translation from a partial handoff.
- Validation or known Git publication failure: inspect
publication-results.json, the unit failure code, and the target tip. Ordinary unit failures may allow later ready units to continue, so use the terminal results rather than assuming that a failed producer means nothing was written. REMOTE_STATE_UNKNOWN: treat the run as a safe stop. Do not cancel a running production Translation, force-push, reset, rebase, or blindly rerun the writer. Inspect the remote branch, candidate/result SHAs, and the exact probe evidence first. Resume only after the remote state is known.- Reference reconciliation failure: source publication may already be present. Preserve the run artifacts, verify the published ancestry, and repair or rerun only the reconciliation boundary after confirming the current target tip. Do not pay for Translation again until the source barrier and schema-v3 handoff are valid.
- Translation unit failure: a normal unit failure is recorded and later ready units can continue; an unknown remote state stops later writes. Inspect the child run's unit results, reports, remaining count, and reconciliation output before deciding whether recovery is needed.
- Expired or incompatible Translation artifacts: use
recover-translation.ymlwith the previous Translation producer workflow run ID (not a job ID, and not thepublish translationpublisher run ID). For a split run the planner reads thedocs-translation-publication-handoff-*metadata artifact to discover and authenticate the publisher run's progress/results evidence automatically. First run withpublish=falseto authenticate the recovery plan and inspect rejected units. Only after the plan is compatible should you rerun withpublish=true; the recovery run translates on its own read-only concurrency group and publishes through thepublish-translation.ymlshort lock like any other producer. The plan excludes units whose retained files are already superseded ondevby a newer published source generation (listed under rejected units with reasonsuperseded source generation); those units belong to a fresh translation, not to recovery.allow_full_retranslate=trueis an advanced, explicitly authorized path for the case where no retained file is compatible; it may invoke paid models and must not be enabled casually. Prompt-contract changes (for example, editing the Japanese style guide or advancing the locale contract) do not invalidate retained artifacts outright: records downgrade fromstricttorevalidatedand are restored or resumed after passing the current deterministic gates, while review receipts are not inherited across the boundary. - Reconciliation review: a
translation-reconciliation-review-*.jsonartifact is produced when a deletion or path change requires human authorization. Generate the deterministic approval PR withscripts/docs-workflow/reconciliation-review-pr.js, review the exact plan, expected mutations, source/target identities, and policy exception body, then merge only when the decision should remain standing. Do not hand-edit or push policy files directly todev; after merge, run the normal master-to-dev tooling sync. - Card/reporting failure: the card is observability, not the Git writer. Preserve the publication selection/results and final verification artifacts, then use the final card artifact or monitor finalization evidence to determine whether the business flow actually succeeded.
After the migration tooling has reached dev through the normal master-to-dev sync, run the migration against an isolated checkout of the exact current dev commit. The retained-report receipt binds legacy cache entries to authenticated Translation run artifacts and source checkpoints; the command still verifies every checkpoint source blob and current Japanese target hash before writing the manifest.
node scripts/translation/migrate-ja-publication-manifest.js \
--repository <isolated-dev-checkout> \
--source-commit <exact-dev-sha> \
--revision HEAD \
--source-evidence deploy/evidence/ja-publication-source-checkpoints-2026-09-08.json \
--audit-output <absolute-audit-output>A nonzero rejected count is a safe stop. Do not edit the generated Japanese manifest or substitute a newer source hash. Preserve the audit output, receipt checksum, exact dev SHA, and subsequent Japanese validation/build logs with the migration PR.
Before opening a PR for hand-validated translated content on top of dev, run the local fail-closed precheck from an isolated worktree whose HEAD is based on the current origin/dev:
node scripts/translation/fastpath-precheck.js --locale <ja-JP|zh-CN> --path <repository-relative-path>...The precheck derives its scope from the evidence equivalence groups: Japanese Guides and Japanese Reference diffs are validated against the Japanese translation manifest, Chinese non-REST Reference diffs regenerate reference-translations.json through reference-manifest --write, Chinese Guides diffs ride the fetch-group publication manifest, and Japanese REST output is checked against the per-locale rest-derivation.json fragment hashes. It then executes every selector command plus validate-reference for the affected sites, re-checks origin/dev for drift, and writes report.json/report.md under tmp/translation-fastpath/<run>/. Only READY_FOR_PR exits 0; every other status is a safe stop, and the tool never commits, pushes, or acquires the production queue.
Use publish-offline-translation.yml only when authenticated Japanese Guides output already exists but cannot truthfully satisfy the schema-v2/v3 Translation batch-set contract. This path does not invoke Translation or review agents and does not synthesize translation-guides-batch-set evidence. It validates one exact candidate commit and promotes it through the normal publication coordinator.
Prepare the candidate only after the reviewed workflow tooling has been synced to dev:
- Refresh
origin/devand record its exact SHA astarget_baseline_sha. Createrefs/heads/offline-translation-candidates/<name>as one commit whose only parent is that SHA. - Limit the commit to Japanese Guides Markdown under the two
current/tutorialsroots,.translation-cache/ja-JP.json, and, when regenerated, the two exact sibling locale files:i18n/ja-JP/docusaurus-plugin-content-docs/current.jsoni18n/ja-JP/docusaurus-plugin-content-docs-byoc/current.json
- Update one cache entry for every changed translated file. Its
sourceHashmust equal the raw source bytes atsource_checkpoint_sha; unrelated cache mutations, symlinks, executable files, and paths outside the fixed allowlist are rejected. Supplyreconciliation_source_checkpoint_shaas the exact published English Guides checkpoint that follows the retained Translation checkpoint. The workflow generates a standardja-JP/guidesreconciliation plan fromsource_checkpoint_shato that checkpoint. Every target deletion must match that plan. An orphan deletion removes every exact cache entry for the deleted target. A canonical replacement reviewed inoffline-guides-canonical-replacements.jsonmust atomically remove the old target/cache keys, add the new target/cache keys, preserve the Japanese bytes and cache provenance exactly, and change only each migrated cache entry's key andtargetPath. A preserved oldsourceHashintentionally keeps later Translation source-drift detection active. - Push the candidate ref without rewriting it. Record both tooling identities separately:
source_tooling_shais the tooling used by the retained Translation run, whileexecution_tooling_shais the reviewed master commit containing the offline validator. The former must be an ancestor of the latter.
Use publish-offline-reference-python.yml only after its tooling has landed on master and reached dev through the standard master-to-dev sync. The MVP is fixed to translation/zh-CN-reference/python; it does not accept REST, Reference landing pages, another SDK group, deletions, renames, or worker-generated manifests and sidebars. Chinese REST remains spec-generated.
- Refresh
origin/dev. The MVP requiressource_checkpoint_shaandtarget_baseline_shato be the same exact currentdevSHA. Create one candidate commit whose only parent is that SHA and whose diff contains onlycontent/zh-CN/reference/api/python/**/*.mdor.mdxadditions/modifications. - Produce
offline-reference-python-receipt.jsonas canonical JSON data withschemaVersion: 1,document: offline-reference-translation-receipt,unitKey: translation/zh-CN-reference/python, exacttoolingSha,sourceCheckpointSha, andtargetBaselineSha, plus a sorted non-emptyfilesarray. Each entry containssourcePath, rawsourceSha256, canonicaltargetPath, nullable rawbaseTargetSha256, and rawtargetSha256. SetreceiptSha256to SHA-256 of the canonical receipt body without that field. Upload the file as one immutable artifact and retain its source workflow run ID, artifact ID, exact name, and GitHub-reportedsha256:digest. - Push the one-commit candidate only to
refs/heads/offline-reference-candidates/python/<name>. Dispatch the workflow with that exact ref and SHA, exact tooling/source/target SHAs, and all four receipt artifact identities, initially withpublish=false. The trusted producer explicitly fetches the restricted ref and requires it to resolve to the supplied SHA, authenticates the completed receipt source run and exact artifact before downloading it, re-reads all bytes from Git, rejects source drift or target divergence, rebuilds both Reference manifests and sidebars with the formal tool, packages only Python-owned Chinese paths into the standard checkpoint/baseline pair, and routes it through the publication coordinator in artifact-only mode. Publication-time validation rebuilds the formal derived state again and requires a clean generated-state diff, so the coordinator cannot silently rely on uncommitted manifest or sidebar drift. - Authenticate selection, ready, checkpoint, baseline, and terminal results artifacts. A passing artifact-only run is invalid after candidate, tooling, source checkpoint, or target baseline drift.
publish=trueis a separate production mutation requiring explicit authorization and thedocs-production-devqueue. OnREMOTE_STATE_UNKNOWN, stop without retrying or mutating Git state.
Recovery for this MVP is reassembly from the retained candidate and receipt after refreshing every identity, beginning again with publish=false. Do not claim compatibility with recover-translation.yml, and do not use paid Translation to replace missing evidence.
Dispatch the workflow first with publish=false. Supply the exact candidate ref/SHA, source tooling/baseline/checkpoint SHAs, reconciliation source checkpoint SHA, execution tooling SHA, current target baseline, and expected Markdown count. The read-only run must produce an immutable Translation selection, checkpoint and baseline tar archives, authenticated reconciliation plan, publication-ready document, seven successful Guides validation receipts, publication progress, and terminal artifact-only results.
If every identity and receipt is correct and origin/dev is still exactly target_baseline_sha, rerun the same inputs with publish=true. The workflow owns docs-production-dev, allows only publish_ready to write, pushes one authenticated docs-translation-staging/guides/** ref, promotes by normal fast-forward, and deletes that staging ref with an exact SHA lease only after publication is confirmed. Target drift fails closed; prepare a new one-commit candidate instead of merging, rebasing, force-pushing, or reusing stale evidence. Preserve the terminal selection/results, final SHA, seven receipts, and staging cleanup evidence.
When a Translation unit enters reconciliation review, the monitor reads translation-reconciliation-review-state-<target>-<group>-<run_id>-<batch>.json and renders Approve / Reject buttons on the Feishu progress card.
Run one long-lived consumer to handle those callbacks:
node scripts/docs-workflow/reconciliation-card-action-consumer.js \
--repository zilliztech/zdoc \
--token-env GITHUB_TOKEN \
--evidence-root tmp/reconciliation-card-actions \
--durable-prThe consumer uses lark-cli event consume card.action.trigger --as bot; the Feishu app must be the same app that sent the card and must have the card.action.trigger callback enabled. GITHUB_TOKEN needs actions:read; when --durable-pr is enabled it also needs contents:write and pull_requests:write.
Pass --durable-pr when Approve should also create a durable policy-exception PR against master. The PR updates config/translation/reconciliation-policy-exceptions.json and follows the normal master-to-dev tooling sync path. Without that flag, Approve only writes the run-scoped receipt and updates the card.
For a newly processed callback, the consumer downloads and validates both the standalone review state and the full review artifact before writing evidence:
approve: writes a run-scoped human approval receipt bound toplanSha256, source/target/tooling identities, reviewer, and expiry; the card is updated toapproved. With--durable-pr, it also creates a PR adding the matching durable policy exception.reject: writes run-scoped rejection evidence and updates the card torejected; no policy file is modified and no PR is created.
Evidence is written atomically below <evidence-root>/<target>/<group>/<run_id>/<batch>/<action>-<event_id>.json. The same Feishu event_id is idempotent.
For a durable decision, the generated PR targets master. Merge it and run the normal master-to-dev tooling sync; never hand-edit or push policy files directly to dev.
A systemd unit template is provided at deploy/systemd/reconciliation-card-action-consumer.service:
sudo install -o root -g root -m 0644 \
deploy/systemd/reconciliation-card-action-consumer.service \
/etc/systemd/system/reconciliation-card-action-consumer.service
sudo install -o root -g zdoc -m 0600 \
deploy/systemd/reconciliation-card-action-consumer.env.example \
/etc/zdoc/reconciliation-card-action-consumer.env
sudo systemctl daemon-reload
sudo systemctl enable --now reconciliation-card-action-consumerAdjust User, Group, WorkingDirectory, ReadWritePaths, and the env file path for the actual host. The service keeps the lark-cli bot identity from the host and never receives Feishu credentials through the unit file.
Never infer success from a green producer job, an artifact-only run, or a Chinese Reference no-candidate Translation run. Those can be useful evidence for their own boundaries, but they do not by themselves prove a complete all-locale Translation FIFO.
For workflow changes, keep the local replay root, artifact identities, logs, final SHA, and ancestry checks as the handoff package. This makes a later recovery auditable and avoids repeating paid work merely to reconstruct missing evidence.
Both sites measure user actions (Ask-AI chat funnel, code copy, TOC navigation, console CTA clicks, 404 landings) through GTM into GA4. Theme code only pushes events to window.dataLayer via trackEvent in packages/docs-ui/src/shared/utils/analytics.ts; consent gating, trigger/tag wiring, custom definitions, and key events live in the GTM containers (en GTM-MBBF2KR, zh-CN GTM-MBBL6Z9Q) and the GA4 properties. The full event schema, the GA4/GTM setup steps, and the container-side Consent Mode decision are specified in .claude/specs/2026-09-20-docs-analytics-and-search-404-design.md.
GA4 Search 404 Report (.github/workflows/ga4-search-404-report.yml) lists the pages that Google search results still point at which now return 404 — the page_not_found events whose referrer was Google, per site, for a sliding window. It is dispatch-only until the GA4 access is configured:
- Create a service account with read-only access to both GA4 properties and store its JSON key in the repository secret
GA4_SERVICE_ACCOUNT_JSON. - Add the repository variables
GA4_PROPERTY_ID_ENandGA4_PROPERTY_ID_ZH_CN(numeric GA4 property IDs). - Dispatch the workflow once and verify the Feishu card and the report artifact, then uncomment the schedule in the workflow.
Without the secret the run degrades to a notice instead of failing. Remediation for recurring paths is an nginx return 301 in deploy/{en,zh-CN}/nginx.conf with a matching redirect assertion in deploy/contracts/container.test.mjs — the same pattern as the existing "Analytics-derived 404 cleanup" blocks.
The runtime images contain only Nginx plus the selected static build output. Build from the repository root:
SOURCE_SHA="$(git rev-parse HEAD)"
docker build --build-arg ZDOC_SHA="$SOURCE_SHA" --build-arg ZDOC_SITE=en --build-arg JENKINS_BUILD_ID=local-preview -f deploy/en/Dockerfile -t zdoc-en .
docker build --build-arg ZDOC_SHA="$SOURCE_SHA" --build-arg ZDOC_SITE=zh-CN --build-arg JENKINS_BUILD_ID=local-preview -f deploy/zh-CN/Dockerfile -t zdoc-zh-cn .The English image includes Japanese content. The two commands are independent; invoke only the selected target or invoke both without treating one target's failure as a repository-level requirement for the other. The Dockerfiles build the static sites internally, so Jenkins does not need to run pnpm build:* before these container builds. Image naming and registry tagging remain Jenkins-owned.
The site-owned Nginx configurations are deploy/en/nginx.conf and deploy/zh-CN/nginx.conf. Runtime environment rendering is owned by deploy/runtime/40-zdoc-env.sh.
Guides records carry a Release Channel field in the Feishu Base. CURRENT is the default and the fallback for records without the field; NEXT marks documentation for a feature that has not shipped yet; RETIRE-IN-NEXT is the mirror — a live page that the upcoming release removes, used to stage a split or restructure without touching production.
- Fetch renders the channel into the page front matter (
channel: nextorchannel: retire-in-next, each with the matchingsidebar_custom_props: {channel}— Docusaurus's official bridge onto runtime sidebar link items) andcustomProps: {channel}onto generated sidebar entries, and persistsrelease_channelin the source snapshot so a channel flip re-renders the page even when the document itself is unchanged. - Both environments deploy the same image. The deployment's channel is injected at container start:
deploy/runtime/40-zdoc-env.shwritesZDOC_RELEASE_CHANNEL(defaultcurrent, fail closed) intowindow.__ZDOC_ENV__.RELEASE_CHANNELvia/env.js. - On a
nextdeployment, NEXT pages render normally with a dismissible unreleased-feature floating notice (toast) in the site's locale. On acurrentdeployment, the docs shell swaps the entire page for the shared 404 page (no sidebar, chrome-less — the exact presentation of a native miss under/docs, resolved through the sidebar entry's runtime channel); NEXT entries also disappear from sidebars and DocCard lists, the pages carrynoindex(which also excludes the routes from the sitemap), they are skipped inllms.txt, structured data, and.mdbuild copies, and the routes listed inrelease-channel-routes.txtare additionally blocked with Nginx 404s that serve the styled 404 page. - RETIRE-IN-NEXT inverts every one of those directions: the page serves normally on
currentdeployments (it is still the live production page — it stays out ofnoindexand stays listed inllms.txtand.mdcopies, which render the CURRENT view), whilenextdeployments hide it exactly ascurrentdeployments hide NEXT pages: sidebar/DocCard removal, pagination pruning, the chrome-less 404 swap, and Nginx 404s from therelease-channel-retired-routes.txtlisting (with the same token propagation to stale translations). The runtime-injection fallback fails towardcurrent, so a missing injection serves a retired page — the safe direction, since that content is already live in production; the trade-off is that anextdeployment's prerendered HTML shows the entry until hydration replaces it, while direct access is server-blocked. - To promote a page, flip
Release ChannelfromNEXTtoCURRENTin the Base; the next Fetch re-renders the page and every environment picks it up on its next deployment. The new-record default ofNEXTmeans every newly created record must be promoted explicitly before it reaches production. <NextChannel>block tags are rejected on RETIRE-IN-NEXT records at Fetch: the page ships its final CURRENT wording and is removed wholesale.
To split one legacy page into two new pages (or restructure a section) without disturbing production URLs:
- Stage: create the new records as
NEXTwith their own fresh slugs (never reuse the legacy slug — the retired page still occupies that route in the shared build until its record is removed), and flip the legacy record toRETIRE-IN-NEXT. One Fetch run captures both states, so the UAT (next) channel previews exactly the post-split world while production keeps serving the legacy page. - Promote: flip the new records from
NEXTtoCURRENTand delete the legacy record's row (or move it out of a publishable Progress state) as one Base edit batch, then trigger the Fetch. The record removal flows through the normal incremental reconciliation (source JSON, generated MDX, and translation outputs are deleted), and the same build both drops the legacy route and un-gates the new pages. - Redirect: add the legacy slug's
return 301to the site's hand-maintained Nginx map (deploy/en/nginx.conf,deploy/zh-CN/nginx.conf— for examplelocation = /docs/legacy-slug { return 301 /docs/successor-slug; }) via a master PR synced to dev. Order matters: the deletion Fetch must deploy first and the 301 second. Deploying the 301 early sends production visitors to a still-gated NEXT page; the reverse order merely leaves the legacy path returning 404 for one deployment cycle. Never deploy the 301 before the record is gone.
The promotion is two independent Base edits (flip plus delete) plus one repository edit (the 301 map). If a Fetch runs between the flip and the delete, the intermediate build is wrong in one of two ways, so complete both Base edits before triggering the Fetch.
Untagged content is CURRENT by convention; only staged blocks inside an otherwise-current page are tagged in the Feishu document:
<NextChannel action="include">Ships with the next release: visible on next deployments only.</NextChannel>
<NextChannel action="exclude">Current wording, removed once next ships.</NextChannel><NextChannel action="include">Its replacement.</NextChannel>
- The tags pass through Fetch untouched and compile to the globally registered
NextChannelMDX component (packages/docs-ui/src/shared/components/NextChannel), which resolves the deployment channel from the samewindow.__ZDOC_ENV__.RELEASE_CHANNELsignal as page-level gating.action="include"blocks render only onnextdeployments;action="exclude"blocks only oncurrent. - Prerendering and hydration resolve to
current(fail closed), soincludeblocks are absent from the static HTML and appear after mount onnext. The right-rail "On this page" TOC is generated at build time, so the TOC wrapper (packages/docs-ui/src/shared/theme/TOCItems) filters it after mount, dropping entries whose heading anchor the deployment channel hides — a gated heading never leaves a dead TOC link on either channel. The product/editioninclude/excludetags are unaffected: they keep their scrape-time filtering, and channel values on that axis (target="next"/target="current") are rejected at Fetch with repair guidance, as are malformed, nested, or NEXT-page-redundant<NextChannel>usage. - Translation treats the tags as protected JSX spans and the output validator asserts tag/action parity against the source, so a dropped or flipped gate fails the Translation check instead of leaking staged prose to production.
- Static build capabilities cannot evaluate the runtime channel and therefore render the CURRENT view, matching the prerendered page: the
.mdcopies and llms.txt summaries resolve include blocks away and unwrap exclude blocks (apps/docs/plugins/next-channel-view.js), and the Fetch description front matter (consumed by structured data and llms.txt) applies the same resolution to inline gates. - Inside code blocks, use the comment directives
# next-channel-start/next-lineand# current-channel-start/next-line(same five comment styles as the product variants, seepackages/docs-tooling/src/lark/CODE_VARIANTS.md). Both variants ship in the shared build; the code renderer filters them per deployment channel before client-side highlighting, so directive lines never render or copy and next-only lines are absent from the prerendered HTML. Whole examples can instead be wrapped in<NextChannel action="include">around the fenced block. - Promotion is tag removal: unwrap
action="include"blocks (keep the inner text), deleteaction="exclude"blocks, and let the next Fetch re-render; existing translations seed-match the unchanged prose.
Deployment overlays own the runtime value: set ZDOC_RELEASE_CHANNEL: next on the UAT overlays (vdc-deploy → zdocs/overlays/uat3, zdocs-cn/overlays/ali-vdc-uat) and ZDOC_RELEASE_CHANNEL: current on the production overlays (vdc-deploy-prod → zdocs/overlays/vdc-global, zdocs-cn/vdc-ali-global). An overlay without the variable behaves as current.
Known limitation: the Chinese site's client-side search index is built from source folders and still lists NEXT titles (and, symmetrically, RETIRE-IN-NEXT titles surface in the index on next deployments); results resolve to the gated page. Server-side Nginx blocking and the client gate remain in force. Likewise, existing Japanese translations only gain the channel front matter the next time Translation regenerates them; until then their pages rely on the token-based Nginx blocking (route listings propagate the channel through the shared Feishu token) and show no unreleased-feature notice on NEXT deployments. Block-level gating inherits the soft-hiding posture of page-level gating: include block text still ships inside the production browser JS chunks (it is absent from the static HTML, the .md copies, and llms.txt, which all render the CURRENT view) because the shared build needs it to reveal the blocks on next deployments; the page TOC — built from the prerendered heading tree — does not list NEXT-only sections until promotion. The same secrets-driven dual-build fallback applies if content must not ship at all.
Run proportional checks while developing. Before a repository-wide retirement or release change, run:
pnpm test:retirement
pnpm typecheck
pnpm test:frontend
pnpm test:containers
pnpm build:en
pnpm build:zh-CN