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.
This file is the operator runbook: setup, branch ownership, the publication runbook, and verification. Deep-dive reference pages live under runbook/:
- REST reference pipeline — editing fragments, upstream drift watchdog and refresh, availability operations
- Replay harnesses — long-form replay evidence modes
- Translation operations — quality assets, the scheduled dispatcher, manifest migration, fast-path precheck, reconciliation card
- Shared Guides generation — source generation, publication evidence, requirement accounting
- Release Channel gating — CURRENT / NEXT / RETIRE-IN-NEXT and
<NextChannel> - Analytics — GA4 reports and console reconciliation
- Content production recipes — adding a new reference manual
- 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-CNpnpm test:shared-docs and pnpm test:frontend discover their complete Vitest
file selection and run ordinary files in parallel. Frontend (all scope) then runs
the complete translationManifest.integration.test.ts file in a separate
reference-integration phase. Both gates finish with the complete local-update
transaction, runner and update-prepare.test.ts command test files in that order
(transactions, local-runner, update-prepare). This gives frontend five phases
and shared-docs four, including light. Each of these isolated phases
uses a separate single-worker process; shared-docs has no reference phase.
Case deadlines and normal platform-dependent skips are unchanged.
Heavy phases override an inherited VITEST_MAX_WORKERS to one; ordinary files
retain their usual worker configuration. Use pnpm test:vitest-groups for the
supervisor's selection, reporting and actual child-process failure contracts.
Each complete gate has a 15-minute monotonic execution limit covering preflight, discovery, all phases, validation and actual writes of every requested report. Expired work cannot start a child or freeze a passing execution verdict; preflight Git calls are bounded by the remaining budget. Only after all that work completes within the limit is the measured verdict frozen. One separate terminal confirmation attempt has at most 1 second for exclusive staging, persistence, atomic publication and reader confirmation. This allowance runs no tests or report copies and adds no retry. A failed test/module, invalid or incomplete report, nonzero exit, or surviving owned process fails the gate and prevents later phases. Failed heavy work is terminated only within that invocation's process group; tests left unrun after failure are not ordinary skips. The CI tooling job's existing 20-minute total limit, which also includes installation, Translation and replay tests, is unchanged. Denied process probes/signals stop that cleanup attempt: receipts retain the first failure and structured cleanup errors, with settlement unknown rather than assumed complete; a receipt-write failure is explicitly unconfirmed and remains failed.
The supervisor supports macOS and Linux. Its dedicated default parent is
~/.cache/zdoc-vitest on both platforms (macOS Library ancestors can have ACLs
unsupported by the existing trusted-tool policy);
ZDOC_TEST_TMP_PARENT can select an absolute parent. The parent must be owned by
the current user, mode 0700, and free of symlink/case aliases. Canonical paths must
not overlap or contain the checkout, actual Git/common directories, object stores
(including alternates), or the requested aggregate output. Missing directories are created privately; existing directories are
never chmodded or cleaned. Each gate gets a fresh owned0700 root and each phase a
separate temporary directory. Ancestor identities and entry counts are retained;
directory identities are checked before and after creation and phase execution.
Neither a crowded system temporary directory nor this layout is presumed equivalent
in performance to an older fixture. Global environment/permissions and older tool
proofs remain untouched.
The printed evidence directory retains discovery, raw phase logs/reports,
run-correlated reporter events, process-group receipts and the final aggregate.
An aborted gate also retains its stage and owned-group stop result. An incomplete
raw report has unknown full case counts; observed reporter failures are reported
separately and are not replaced by a misleading zero-failure total.
--reporter=default, --reporter=json, and a fresh
--outputFile.json=/absolute/path/result.json are supported. Aggregate and requested
JSON files are provisional and have no standalone success flag. The sole committed
version-2 terminal.json binds the run, scope, original discovery bytes, measured
execution verdict, all report identities and hashes, and each complete phase's raw
result and settled process receipt. The reader derives the ordered, exhaustive
phase selection from the scope policy and bound discovery, including isolated
worker configuration; reports cannot declare their own required phase count.
Historical version-2 evidence with the earlier three/four-phase policy must use
its matching retained reader, available at commit 21d6add193; it cannot qualify
the new four/five-phase policy. Preserve the original artifacts unchanged.
Historical version-1 evidence must be read with its matching retained supervisor,
not treated as qualification under this new phase policy. Commit
7e6e337b50b6aa191d12c8f9661a1d23dda5ba8d retains the version-1 reader. To inspect
an existing version-1 aggregate without changing its artifacts or the checkout:
reader_dir=$(mktemp -d "${TMPDIR:-/tmp}/zdoc-v1-reader.XXXXXX")
git show 7e6e337b50b6aa191d12c8f9661a1d23dda5ba8d:scripts/testing/run-vitest-groups.mjs > "$reader_dir/reader.mjs"
node --input-type=module - "$reader_dir/reader.mjs" /absolute/path/version-1/aggregate.json <<'JS'
import {pathToFileURL} from 'node:url';
const {readGateReport} = await import(pathToFileURL(process.argv[2]).href);
console.log(await readGateReport(process.argv[3]));
JSUse the current reader for version-2 evidence: actual reader to qualify either report path:
import {readGateReport} from './scripts/testing/run-vitest-groups.mjs';
const result = await readGateReport('/absolute/path/result.json');
console.log(result.success, result.executionStatus, result.commandStatus);The CLI uses the same reader and returns zero only for an acknowledged passing
commit. Missing, partial or mismatched terminal evidence cannot qualify a report.
An error or exhausted confirmation allowance returns nonzero with settlement status
unknown, without rollback or retry: the terminal might already have committed.
An offline reader can verify that committed execution result, but its
commandStatus: not-established-by-artifacts does not prove CLI delivery or exit.
No filesystem timestamp substitutes for the measured monotonic execution budget.
All discovered files must be accounted for and every owned group settled.
Narrowing options, retries and deadline overrides
are rejected by these complete gates; use direct pnpm exec vitest run ... for
explicit focused diagnostics, which do not qualify the complete selection.
Build 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. They are the localization input inventory and the REST reference derivation set (generated REST pages for en/zh-CN/ja-JP,restful.sidebar.js, the per-localerest-derivation.jsonmanifests, and the Reference manifest pair). They may sit below dev-owned roots and stay dev-owned for every other gate, somasterstill may not edit them; the preserved REST landing pages inside the REST output directory stay master-authoritative.
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.
Task recipes and deep-dives live under runbook/:
- Adding a new reference manual: runbook/content-production.md.
- Editing REST spec fragments, the REST upstream drift watchdog and refresh, and the REST upstream and availability operations cadence: runbook/rest-reference-pipeline.md.
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 keeps its 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 quality assets (house style guides, the translation-memory helper, the experimental agentic provider with its surgical incremental lane) and the hourly translation dispatcher (pending budgets, arming, the source build gate, dispatch backoff, guard alerts) are documented in runbook/translation-operations.md. In short: the dispatcher dispatches only when armed with the repository variable TRANSLATION_DISPATCHER_DISPATCH=true (or a manual dispatch with the dispatch input), and a failed producer run's completed batches are usually salvageable through recover-translation.yml with publish=false rather than a fresh paid dispatch.
- 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:replay:shared-pagesincludes the focused independent regeneration tests and exercises selected shared Guides generation, authenticated source artifacts, offline tables, fresh assembly, immutable image uploads and a real checkpoint transaction against a local bare remote. It also compares ordinary Feishu output with valid and malformed inactive configuration.pnpm test:replayis the PR-level contract + Fetch + recovery + requested Guides + shared-pages 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.
Before accepting shared Fetch publication changes, additionally run the full local evidence mode:
node scripts/docs-workflow/replay-shared-pages.js \
--evidence-root /absolute/path/to/shared-pages-evidence \
--legacy-baseline --site \
--site-baseline-repository /absolute/path/to/local-retained-replay-repository \
--site-baseline-sha <exact-successful-local-replay-commit>The long-form evidence procedures live in runbook/replay-harnesses.md: the semantics of the --legacy-baseline/--comparison-baseline-sha/--site flags above, the retained-artifact recovery replay through replay-recovery-plan.js (snapshot-root contract, safe roots, evidence to preserve), and the step-by-step requested Guides replay against real retained artifacts.
When a shared page cannot be generated or activated, the source job logs a
[shared-fallback] JSON line with its Base record ID, page, error code, reason,
and retained paths before canonical-link auditing. Start with that reason even
if the source job later fails before uploading its source artifact.
Fallback keeps the exact pinned dev record and file bytes. Explicit snapshot
output_paths remain authoritative; legacy Feishu records with empty paths use
the pinned publication authority's retained paths when available, with snapshot
and file-hash binding. Only the first fallback derives paths from the baseline's
navigation and Git inventory. This keeps consecutive fallbacks stable when an
ancestor is renamed or removed; safe multi-segment Slugs remain supported. Current Base edits or cached source paths cannot expand the retained set.
Source-bundle and final-publication validation use the same rule and still check
file hashes. A partial fallback run is not eligible for successful cache promotion.
A retained source bundle with a legacy empty-path fallback must include
baseline/publication.json if its pinned inventory contains the publication
authority. Older bundles missing that proof fail with missing baseline publication; do not remove the inventory entry or bypass validation. Inspect
and regenerate the source artifact from its pinned inputs before reusing it.
Success-only bundles and explicit-path or shared-page fallbacks do not acquire
this new proof requirement.
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) force_full_fetch=trueonly for a manual full Guides re-fetch; it bypasses incremental planning for the en and zh-CN Guides source stages (scheduled runs always stay incremental)
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.
scripts/docs-workflow/watchdog-known-issues.json is the reviewed, machine-readable ledger of watchdog triage knowledge: for every scheduled watchdog it records the fetch channel (job logs, artifact, or conclusion), the steady-state baseline, known issues with a status (known-expected / watch / in-progress / resolved), and closed-incident history. A daily digest automation compares live watchdog output against this ledger; it also names the five docs-production-dev queue members and the translation producer so tooling can tell queue occupancy from an off-queue producer run.
- Validate the ledger fail-closed (schema version, referenced workflow files exist, status/cadence enums, producer stays off the production queue) with
node scripts/docs-workflow/daily-ops-summary.js --validate-ledger; the digest consumes the ledger only after this passes. - Get the digest's mechanical sections on demand — current production-queue occupancy (including wait times), per-workflow run statistics for yesterday (or a custom
--window), and a watchdog run cross-check — withnode scripts/docs-workflow/daily-ops-summary.js --markdown(add--jsonfor machine consumption). The script is read-only: it lists runs through theghCLI and never dispatches, reruns, or cancels anything. - Status changes in the ledger go through reviewed changes like any other tooling. Volatile daily numbers (for example the rest-upstream drift
substantiveCount/diffDigestseries) deliberately stay in a local state file outside the repository — only rules, statuses, and thresholds belong in the ledger.
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.
- Every
build:en-gated publication fails on a file already ondev: pipeline-owned output (translated pages, generated state) can deadlock the whole pipeline — see Unlock dev when pipeline-owned output blocks every build.
A broken file that is already published on dev (for example invalid YAML front matter in a translated reference page — incident 2026-10-24, commit 460f7d921) deadlocks the pipeline: every build:en-gated validation fails, so Fetch cannot publish the source change that would trigger retranslation, and no workflow can repair the file by itself. A manual hotfix commit to dev is then the only repair path, and it must follow this exact pattern. dev currently has no branch protection, so the guardrails are procedural:
- Confirm the true fatal cause before touching anything. Publication failure messages carry only the first 500 characters of stderr and can mislabel the cause (the 2026-10-24 incident surfaced as "Docusaurus found broken links" — a perennial non-fatal warning — while the fatal error was a YAML exception). Reproduce the failing gate locally on the failing tree, or validate the suspect file directly, before concluding.
- Verify the deadlock actually holds. The pattern applies only when no normal path can self-heal: the broken file is pipeline-owned (translated output or generated state, never hand-authored source), the pending source change would have to land through the same failing gate, and no retained recovery artifact can republish a good version. If the English source can still land or a recovery run covers it, use the normal paths instead.
- Repair in an isolated worktree, broken bytes only. Start from the current
origin/devtip in a detached worktree, change exactly the broken bytes in exactly the broken file, and do not "improve" anything else in the same commit. - Run the failing gate locally before pushing. Install in the worktree and run the gate that was failing in CI (for a
build:endeadlock,pnpm run build). Never push an unverified hotfix — a hotfix that fails the next run doubles the incident. - Confirm no production writer is active, then push forward-only. Check that no Fetch/Translation producer or publisher run is in flight (a manual push bypasses the
docs-production-devqueue), thengit push origin HEAD:devfrom the worktree. Any rejection (non-fast-forward) is a safe stop: stop and re-diagnose. Never force-push, reset, or rebasedev. - Close the loop. Watch the next Fetch run land the previously blocked units, and file the tooling gate that would have caught the file class at the publication boundary as a
masterPR, so the manual unlock stays a last resort.
git fetch origin dev
git worktree add --detach .claude/worktrees/dev-hotfix origin/dev
cd .claude/worktrees/dev-hotfix && pnpm install --frozen-lockfile
# fix only the broken bytes in the one broken file, then:
pnpm run build # the exact gate that was failing in CI
git commit -am "hotfix(dev): repair <path> (<incident reference>)"
git push origin HEAD:devTranslation-side operator procedures — the Japanese publication manifest migration, the fast-path precheck for hand-validated translated diffs, the Feishu reconciliation approval card consumer, and hosting that consumer — live in runbook/translation-operations.md. 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. The full event schema, the GA4/GTM setup, the scheduled search-origin 404 report and engagement funnel report, and the monthly GA4 console reconciliation are documented in runbook/analytics.md.
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. deploy/runtime/nginx.main.conf replaces the stock image main config solely to load_module the njs module (main-context-only directive; see the cloud-data chain below).
Jenkins pipelines are external, but the repository owns the trigger interface and the release-evidence contract. Two repo-side surfaces apply:
- Triggering.
scripts/doc-publish-botplans the Jenkins triggers (see its README for the bot flow and credentials): UAT builds go to thezilliz-docs-devjob withBRANCH(defaultdev), and production builds go to thezilliz-docs-prodjob, which requires avX.X.Xrelease branch and accepts an optionalimage_tagto promote an already-built image instead of rebuilding. The evidence rule from the runbook applies verbatim: start or inspect Jenkins only after the exact source SHA is known (devfor UAT; thevX.X.Xbranch head for production), and confirm the build label carries that SHA. - Release records. Every UAT and production deployment is described by a release record (
deploy/contracts/release.schema.json, shape example indeploy/contracts/release-record.example.json):site(en/zh-CN),environment(uat/prod),mode(rebuildfor UAT and rebuild-from-branch;specified-imagefor image-tag promotion), the 40-hexsourceSha, the immutablesha256:digests, and ajenkinsBuildIdentityof the formvdc-jenkins/<english|chinese>-<uat|prod>/<build>. Production records additionally carrysourceUatDigest(plusoperatorImageRefinspecified-imagemode), and the Dockerfiles bake the build args into image labels —ZDOC_SHAintoorg.opencontainers.image.revisionandJENKINS_BUILD_IDintocom.zilliz.jenkins.build-id— binding each image to its source revision and build.
deploy/contracts/verify-image.mjs verifies the contract fail-closed:
node deploy/contracts/verify-image.mjs verify-record --root <dir> --record <record.json>verify-record checks one record's shape (--record is a path relative to --root). verify-rebuild and verify-specified-image additionally prove that a production record has unique successful UAT evidence for the same site, source SHA, and digest — both read evidence/uat-records.json from a trusted evidence root (VDC_JENKINS_EVIDENCE_ROOT, kernel-read-only mounted, asserted by VDC_JENKINS_EVIDENCE_PROTECTION=kernel-read-only-mount; all three evidence files must exist read-only). verify-specified-image resolves operatorImageRef through evidence/resolved-images.json and requires the resolved digest to equal both sourceUatDigest and finalDeployedDigest — you promoted exactly the digest UAT tested. verify-rollback reads evidence/prod-records.json and accepts only a target digest that has a recorded successful production release.
The cloud-providers-and-regions and zilliz-cloud-ips pages carry globally registered <CloudRegions>/<CloudFeatures>/<RegionIps> components (HTML pages fetch the CMS JSON after mount) and standalone markers inside the Feishu source. embed-markdown bakes every marker into an SSI pair — a <!--# block --> stub rendered from the committed snapshot (packages/docs-ui/src/shared/cloud-data/snapshots/) plus <!--# include virtual="/api/cloud-data/fragment.md?…&site=…" stub=… -->. At request time nginx resolves the include through /api/cloud-data/ (loopback proxy) → /_njs/fragment.md (njs renderer, the same packages/docs-ui/src/shared/cloud-data modules as the build) → /api/cloud-json/* (per-site CMS proxy: en cms.zilliz.cc, zh-CN cms-cn.zilliz.cc, 60s proxy_cache with stale-on-error). Any upstream failure is intercepted into an empty 200 so SSI serves the snapshot stub — the .md route degrades to the last committed snapshot, never to an error page.
Operational prerequisites: the container must reach the site's CMS instance at startup (static proxy_pass hostnames resolve at config time), and the JSON responses must stay below the 1m subrequest_output_buffer_size. Refresh the committed snapshots with node scripts/docs-workflow/cloud-data-snapshot.mjs --site <en|zh-CN> --from-base <cms-origin> --out packages/docs-ui/src/shared/cloud-data/snapshots/<site>.json and gate freshness with --check (the wire format for a future scheduled refresher). Verification: pnpm test:containers (static contract) and pnpm test:cloud-data-nginx (live four-scenario run in nginx:1.28-alpine; skipped automatically without Docker).
English Fetch generates selected shared Guides pages (Feishu Base records whose Docs points at a shared source tree), including image-bed upload, first activation, alias-evidence, and ownership semantics. The full generation, publication-evidence, and requirement-accounting reference lives in runbook/shared-guides-generation.md.
Guides records carry a Release Channel field in the Feishu Base that stages unshipped (NEXT) and retiring (RETIRE-IN-NEXT) pages per deployment channel, including block-level <NextChannel> gating inside otherwise-current pages. The full reference lives in runbook/release-channel-gating.md.
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-CNShared Guides final publication evidence and shared Fetch requirement accounting are documented in runbook/shared-guides-generation.md. The REST upstream and availability operations cadence (watchdog, refresh, availability matrices, zilliz probes) lives in runbook/rest-reference-pipeline.md.
The report-only command uses already acquired local Git repositories. Prepare
a non-shallow Milvus docs repository whose origin matches
shared-docs/guides/source.yaml, and a local zdoc repository containing the
intended dev commit. The command never fetches or changes either repository.
From the zdoc tooling checkout, run:
pnpm docs-tooling shared-docs update \
--shared-docs shared-docs/guides \
--repo /absolute/path/to/milvus-docs \
--dev /absolute/path/to/zdoc \
--out /absolute/path/to/new-reportMaster defaults to the tooling checkout's HEAD; dev defaults to the supplied
zdoc repository's HEAD (which may not be dev). Use --master-sha, --dev-sha
and --source-sha to pin exact lowercase 40-character commits. Source defaults
to the configured local tracking branch head. --comparison-master-sha
optionally supplies earlier committed configuration for a configuration-change
comparison; otherwise both configurations use master. Upstream source/output
comparisons always use the current master configuration on both endpoints,
so a configuration-only edit cannot invent an upstream next delta. --pages accepts unique
comma-separated page IDs. --mode report is optional; write mode is rejected.
Uncommitted input edits are not included in the fixed snapshots.
Optional --evidence-run selects a retained production Fetch run. Read-only
GitHub authentication through gh must be available. The command verifies the
run, exact attempt, source job/steps and unique source artifact. It downloads
the source ZIP, or verifies a supplied --evidence-zip against the API digest.
Archive preflight requires Python 3. It does not start Fetch or query Base.
Without retained evidence it still analyzes pages and reports lifecycle unknown.
Current incremental snapshots cannot prove complete fresh lifecycle state;
archive authentication alone does not change that unknown classification.
The absent/empty output directory must not overlap input data or Git metadata
and must have no symlink components. A separate task directory under .claude/
is suitable. Existing evidence is never overwritten. Outputs are pins.json,
report.json, report.md and plan.json; the plan always has an empty
operations array. It records proposed next deltas for review, never edits locks,
PRs, dev or production state, uploads images, or invokes translation. Public
image reads follow the existing generation policy.
Exit 2 means invalid arguments or unsafe/nonempty output. Exit 1 means failed infrastructure, with partial reports retained when output validation succeeded. Exit 0 means a complete report, including no-op, unknown lifecycle, retirement observations or explicitly failed content generation; it does not mean every page built successfully or authorize a mutation. Inspect per-page statuses and reasons. Current and old-next/target observations remain distinct, including source changes hidden by Cloud replacements and image-byte changes.
Run the local commands from the trusted tooling checkout at the exact committed
implementation you intend to use. They validate the launcher before reading any
.env; unsupported Node/Git/package-manager overrides are rejected. Inputs must
be already acquired local repositories. Supply a strict JSON request with exact
master/dev/source commits and complete subjects; see the
request and evidence contract.
The request's master must match the independently captured tooling HEAD.
Before capturing a toolset after Vite/Vitest, wait for the owning command process
to exit: ESM config loading can unlink its temporary file asynchronously. The
checkout's exact root node_modules/.vite-temp must be absent or an ordinary,
canonical, empty directory with safe access; symlinks, aliases and contents are
rejected. If it remains nonempty, stop and inspect the reported paths without
deleting or ignoring them to pass validation. Capture and pre-launch verification
bind the observed directory state and access identity. Persistent changes observed
on revalidation require a fresh capture; these checks are not an atomic snapshot
or continuous monitor. Child load guards refuse unmeasured executable files,
including files introduced after verification.
pnpm docs-tooling shared-docs update-plan \
--request /absolute/request.json --out /absolute/new-plan
pnpm docs-tooling shared-docs update-prepare \
--request /absolute/request.json --plan /absolute/new-plan \
--unit '<unitKey from the plan>' --out /absolute/new-candidateInspect result.json, reports/ and units/ before choosing a unit. Planning
may construct an isolated prepared base; preparation rechecks the plan, source,
configuration, authority and toolset, and creates at most one local candidate
that changes only the selected next locks. Scratch Git objects, journals and
proofs stay in the owned output. Input refs and repositories remain unchanged.
SDK subjects currently return PROVIDER_NOT_INSTALLED; incomplete Guides
authority remains held. Neither is permission to bypass an eligibility check.
Use absent/empty, absolute canonical output directories outside input data,
Git metadata and prior evidence; a dedicated .claude/ directory is suitable.
Ordinary invocations never overwrite evidence. To resume an interrupted prepare,
repeat its exact command with --resume; only the matching owned journal is
accepted and all bindings are revalidated. For PLAN_CHANGED, generate a fresh
plan and use a new output. Preserve conflict/hold evidence and resolve the
reported input problem; do not reset, force-push or delete arbitrary directories.
Exit 2 means invalid arguments/request/output; exit 1 means infrastructure or
acquisition failure, retaining only safely owned partial evidence. Exit 0 means
a completed observation: inspect status (planned, held, prepared,
reused), disposition and reasons. A draft-failure candidate is not
publishable. Holds contain no candidate/proof. The result is also printed as
JSON to stdout; a hold before output ownership exists only there, with no
result.json or fabricated pins. External operations is always empty.
These commands do not publish, push, create PRs or authorize production actions.