Skip to content

Latest commit

 

History

1,947 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Zilliz Documentation

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.

Prerequisites

  • Node.js 22 or newer
  • pnpm 10.33.0 (corepack enable)

Install dependencies with:

pnpm install --frozen-lockfile

Local development and builds

English includes the Japanese translation tree:

pnpm start:en
pnpm build:en

Preview 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-CN

Build output is written to build/en and build/zh-CN.

Content ownership

  • 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.

Branch ownership

Logical content ownership above is distinct from Git branch ownership. deploy/contracts/master-tooling-sync.json is authoritative:

  • master owns 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.
  • dev owns generated publication state under the contract's devOwnedPaths, including the main content, generated, i18n, cache, snapshot/report, legacy publication, and sidebar-override roots. Only serialized publication/reconciliation workflows should update those paths.
  • masterAuthoritativePaths are exact policy or preserved-content exceptions inside otherwise dev-owned roots. They remain master-owned and are carried into dev by the sync workflow.
  • candidateDerivedPaths are 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.

Content production

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:retirement

GitHub 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.

Adding a new reference manual

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.

  1. Add a definition to packages/docs-tooling/src/manuals/registry.ts with kind: 'reference', the remote/local sources, publications, and a presentation block (see an existing manual such as node). The registry validator requires presentation for reference manuals and cross-checks sidebar, documentIdPrefix, and landingPage consistency.

  2. 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).

  3. Update the static deploy contract deploy/contracts/path-filters.json: add generated/en/sidebars/<sidebar>.sidebar.js (where <sidebar> is the manual's presentation.sidebar) to canonicalEnglishReference.include and to siteOwned.en.exclude. check:reference-presentation fails with a drift error until this is done.

  4. Update the static CI input list in .github/workflows/fetch-docs.yml: the group input description lists valid groups, and the prepare job's case enumerates known groups. Add the new manual id to both (GitHub Actions workflow_dispatch inputs cannot be derived from the registry).

  5. Update test fixtures that hard-code the group list (['guides','python','java','node','go','cli','rest']) to include the new manual id. These live in scripts/docs-workflow/*.test.js, scripts/translation/*.test.js, scripts/*.test.js, and packages/docs-tooling/src/**/*.test.ts; run the affected suites after updating them.

  6. Verify: pnpm typecheck, pnpm test:workflow-policy, pnpm vitest run packages/docs-tooling/src/manuals, the docs-workflow and translation node:test suites, and pnpm build:en. For a first real fetch, dispatch fetch-docs.yml with publish=false and run_translations=false to validate the matrix and selection before any production publish.

Editing REST spec fragments

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:

  1. Add or update the operation in the correct fragment, including its HTTP method, path, tag, summary, request/response schemas, and localized x-i18n fields. Keep the fragment registered in meta/fragment-migration.json when it is part of the formal REST publication set.
  2. For every operation generated for zh-CN, add the exact page title used by the Chinese generator as an entry to packages/docs-tooling/src/reference/rest/meta/titles.json. The value is the stable English slug without the automatic -v2 suffix. For example, 创建 Spark 回填任务 maps to create-spark-backfill-job. Missing this entry fails Chinese REST generation before the site build.
  3. 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.
  4. 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.
  5. Before submitting, run the selector's full command union, pnpm test:rest-publication-contract, pnpm test:workflow-policy, and git 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.

Production publication runbook

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.

Before publishing

  1. Confirm that the intended tooling is already on dev through the PR-based sync-master-tooling-to-dev.yml workflow. The candidate must be an exact master commit and the resulting merge must be identifiable in the workflow and PR history.
  2. Check the current dev tip and make sure no production Fetch, Translation, or tooling run is already active.
  3. 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.

Replay harnesses

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:fetch covers Fetch FIFO publication, fault injection, final business evidence, and local-remote safety.
  • pnpm test:replay:recovery covers retained Translation selection/results/progress authentication, legacy REST exclusion, recovery-map construction, and non-mutating fault overlays.
  • pnpm test:replay:translation covers Translation FIFO publication and monitor artifact authentication; it is slower and runs on the scheduled replay workflow.
  • pnpm test:replay:requested covers 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:replay is the PR-level contract + Fetch + recovery gate.
  • pnpm test:replay:all runs 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 false

The 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.

Replay the requested Guides workflow against real retained artifacts

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:

  1. Pick a successful fetch lark docs run on dev, note its run ID, attempt, masterSha, and devBaselineSha from the checkpoint manifest.
  2. Download the retained guides-sources-en-<run> artifact, the docs-checkpoint-guides-en-<run> artifact, and the Guides table artifacts for the run's affected tables into an isolated evidence root (never the real origin), recording each artifact ID and sha256 in a manifest.txt line format <id> <name> <sha256>.
  3. Extract guides-source.tar and the checkpoint/table archives below artifacts/ in that root and write a real-context.json describing the run identity, the baseline snapshot path (from git 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.
  4. 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.
  5. 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.

Start and monitor a publication

For a normal full release, dispatch fetch lark docs with:

  • group=all
  • target_branch=dev
  • publish=true
  • media_upload_mode=write
  • run_translations=true when the downstream Translation handoff is required
  • the intended tooling_ref and source_ref (normally master and dev)

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:

  1. producer jobs create and validate the immutable checkpoint artifacts;
  2. publish_ready consumes the immutable selection and publishes in producer-completion FIFO order;
  3. reconcile_reference_state performs the allowed generated-state reconciliation;
  4. source_publication_barrier proves every required source group succeeded before paid Translation is dispatched;
  5. prepare_translation_handoff writes the schema-v3 handoff bound to the exact source commits and reconciled target SHA;
  6. dispatch_translations starts the single child Translation run;
  7. 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.

Feishu progress cards and the notification kill switch

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.

Evidence required after a successful run

Record the run URLs and retain these facts before handing off to Jenkins:

  • Fetch selection and terminal results, including the runtime FIFO and final dev SHA;
  • 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 en and zh-CN, and the final card contains exactly nine available notes with no Unavailable entry;
  • 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_publication job and the docs-translation-publication-handoff-* metadata artifact);
  • the exact dev SHA 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.

Failure handling and recovery

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.yml with the previous Translation producer workflow run ID (not a job ID, and not the publish translation publisher run ID). For a split run the planner reads the docs-translation-publication-handoff-* metadata artifact to discover and authenticate the publisher run's progress/results evidence automatically. First run with publish=false to authenticate the recovery plan and inspect rejected units. Only after the plan is compatible should you rerun with publish=true; the recovery run translates on its own read-only concurrency group and publishes through the publish-translation.yml short lock like any other producer. The plan excludes units whose retained files are already superseded on dev by a newer published source generation (listed under rejected units with reason superseded source generation); those units belong to a fresh translation, not to recovery. allow_full_retranslate=true is 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 from strict to revalidated and 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-*.json artifact is produced when a deletion or path change requires human authorization. Generate the deterministic approval PR with scripts/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 to dev; 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.

Migrate the Japanese publication manifest

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.

Precheck a translation diff with the fast-path validator

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.

Publish retained Japanese Guides offline

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:

  1. Refresh origin/dev and record its exact SHA as target_baseline_sha. Create refs/heads/offline-translation-candidates/<name> as one commit whose only parent is that SHA.
  2. Limit the commit to Japanese Guides Markdown under the two current/tutorials roots, .translation-cache/ja-JP.json, and, when regenerated, the two exact sibling locale files:
    • i18n/ja-JP/docusaurus-plugin-content-docs/current.json
    • i18n/ja-JP/docusaurus-plugin-content-docs-byoc/current.json
  3. Update one cache entry for every changed translated file. Its sourceHash must equal the raw source bytes at source_checkpoint_sha; unrelated cache mutations, symlinks, executable files, and paths outside the fixed allowlist are rejected. Supply reconciliation_source_checkpoint_sha as the exact published English Guides checkpoint that follows the retained Translation checkpoint. The workflow generates a standard ja-JP/guides reconciliation plan from source_checkpoint_sha to 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 in offline-guides-canonical-replacements.json must 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 and targetPath. A preserved old sourceHash intentionally keeps later Translation source-drift detection active.
  4. Push the candidate ref without rewriting it. Record both tooling identities separately: source_tooling_sha is the tooling used by the retained Translation run, while execution_tooling_sha is the reviewed master commit containing the offline validator. The former must be an ancestor of the latter.

Validate retained Chinese Python Reference translations offline

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.

  1. Refresh origin/dev. The MVP requires source_checkpoint_sha and target_baseline_sha to be the same exact current dev SHA. Create one candidate commit whose only parent is that SHA and whose diff contains only content/zh-CN/reference/api/python/**/*.md or .mdx additions/modifications.
  2. Produce offline-reference-python-receipt.json as canonical JSON data with schemaVersion: 1, document: offline-reference-translation-receipt, unitKey: translation/zh-CN-reference/python, exact toolingSha, sourceCheckpointSha, and targetBaselineSha, plus a sorted non-empty files array. Each entry contains sourcePath, raw sourceSha256, canonical targetPath, nullable raw baseTargetSha256, and raw targetSha256. Set receiptSha256 to 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-reported sha256: digest.
  3. 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 with publish=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.
  4. 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=true is a separate production mutation requiring explicit authorization and the docs-production-dev queue. On REMOTE_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.

Feishu reconciliation approval card

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-pr

The 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 to planSha256, source/target/tooling identities, reviewer, and expiry; the card is updated to approved. With --durable-pr, it also creates a PR adding the matching durable policy exception.
  • reject: writes run-scoped rejection evidence and updates the card to rejected; 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.

Hosting the Feishu callback consumer

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-consumer

Adjust 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.

Analytics

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.

Search-origin 404 report

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:

  1. 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.
  2. Add the repository variables GA4_PROPERTY_ID_EN and GA4_PROPERTY_ID_ZH_CN (numeric GA4 property IDs).
  3. 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.

Containers

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.

Release Channel gating (CURRENT / NEXT / RETIRE-IN-NEXT)

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: next or channel: retire-in-next, each with the matching sidebar_custom_props: {channel} — Docusaurus's official bridge onto runtime sidebar link items) and customProps: {channel} onto generated sidebar entries, and persists release_channel in 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.sh writes ZDOC_RELEASE_CHANNEL (default current, fail closed) into window.__ZDOC_ENV__.RELEASE_CHANNEL via /env.js.
  • On a next deployment, NEXT pages render normally with a dismissible unreleased-feature floating notice (toast) in the site's locale. On a current deployment, 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 carry noindex (which also excludes the routes from the sitemap), they are skipped in llms.txt, structured data, and .md build copies, and the routes listed in release-channel-routes.txt are 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 current deployments (it is still the live production page — it stays out of noindex and stays listed in llms.txt and .md copies, which render the CURRENT view), while next deployments hide it exactly as current deployments hide NEXT pages: sidebar/DocCard removal, pagination pruning, the chrome-less 404 swap, and Nginx 404s from the release-channel-retired-routes.txt listing (with the same token propagation to stale translations). The runtime-injection fallback fails toward current, so a missing injection serves a retired page — the safe direction, since that content is already live in production; the trade-off is that a next deployment's prerendered HTML shows the entry until hydration replaces it, while direct access is server-blocked.
  • To promote a page, flip Release Channel from NEXT to CURRENT in the Base; the next Fetch re-renders the page and every environment picks it up on its next deployment. The new-record default of NEXT means 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.

Staging and promoting a split or restructure (RETIRE-IN-NEXT)

To split one legacy page into two new pages (or restructure a section) without disturbing production URLs:

  1. Stage: create the new records as NEXT with 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 to RETIRE-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.
  2. Promote: flip the new records from NEXT to CURRENT and 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.
  3. Redirect: add the legacy slug's return 301 to the site's hand-maintained Nginx map (deploy/en/nginx.conf, deploy/zh-CN/nginx.conf — for example location = /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.

Block-level channel gating (<NextChannel>)

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 NextChannel MDX component (packages/docs-ui/src/shared/components/NextChannel), which resolves the deployment channel from the same window.__ZDOC_ENV__.RELEASE_CHANNEL signal as page-level gating. action="include" blocks render only on next deployments; action="exclude" blocks only on current.
  • Prerendering and hydration resolve to current (fail closed), so include blocks are absent from the static HTML and appear after mount on next. 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/edition include/exclude tags 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 .md copies 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-line and # current-channel-start/next-line (same five comment styles as the product variants, see packages/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), delete action="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.

Verification

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

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages