Docs Sync Quick Reference
The cheat sheet version of this page: commands, paths, and review steps at a glance.
Open the cheat sheet →How documentation syncs from the Nexus code repository into review merge requests on this docs site
This documentation site receives automated sync merge requests from the internal Nexus code repository. External contributions become eligible after the team reviews and merges them into the internal repository. The sync requests review from the Nexus author and configured docs maintainers. Publication happens only after the docs merge request reaches main. Required approvals are enforced by GitLab project settings, not by the sync scripts.
Goal: Keep the docs site in sync with documentation in the Nexus codebase, with human approval on every change and without ever disturbing the curated sidebar order.
How it works:
nexus mainsync/* branchmain or push a protected v* tagThe Nexus pipeline must pass before its dispatch job runs.main.main.versions/manifest.json.main runs validation, build, and deploymentThe sync jobs never publish directly.The receiver applies these controls before publication:
| Control | Enforcement point | Result when the check fails |
|---|---|---|
| Canonical sender | Nexus docs-sync-dispatch rules |
Only the canonical internal Nexus project can dispatch; the public mirror is skipped |
| Pipeline source | .gitlab-ci.yml workflow and job rules |
Sync jobs run only for downstream pipelines created with GitLab’s native trigger: keyword |
| Trigger routing | .gitlab-ci.yml job rules |
Only merge and release values select a sync job |
| Fixed repository | clone_nexus() |
Trigger variables cannot change the internal repository, host, or default branch |
| Clone scope | clone_nexus() |
Clones only main (--single-branch) with full history, not other branches — full history is required so ancestry checks and catch-up diffs can walk arbitrarily far back |
| Commit verification | clone_nexus() |
The supplied full commit SHA must exist and belong to canonical Nexus main |
| Catch-up state | state.json and merge.sh |
Each docs-bearing sync starts at the last reviewed source commit; rewritten history fails closed |
| Documentation scope | is_site_docs_path() for live pages; is_docs_path() for snapshots |
Live imports exclude assets and selected repository-only or curated sources; changes outside the selected scope create no sync merge request |
| Release checks | release.sh |
The semantic-version tag must resolve to the verified commit; broken relative links or assets fail snapshot creation |
| Human review | GitLab merge request protections | Sync automation cannot publish directly; publication starts only after the review merge request reaches main |
When a developer merges to Nexus main:
docs-sync-dispatch triggers this repodocs-sync-merge only (build/deploy are skipped in triggered pipelines). The job pushes (or refreshes) the sync/nexus-main branch and opens a review MRmain, which runs the normal build → deploy pipeline and publishes the siteThe heart of the sync is scripts/docs-sync/path-map.tsv, a tab-separated mapping of Nexus repo paths to curated site pages:
docs/architecture/data-model.md concepts/data-models.mddocs/runbooks/audit-out-of-band.md security/audit-archive-export.mddocs/dev/ci.md contributing/ci-cd-pipeline.mdCHANGELOG.md reference/changelog.mdThis map determines ownership:
| File type | Owner | Sync behavior |
|---|---|---|
Mapped pages (in path-map.tsv) |
Nexus | Body replaced on every Nexus edit; curated frontmatter (title/description) preserved; no sidebar change |
| Docs-side pages (not in the map, e.g. this page) | This repo | Never touched by the sync — no Nexus change can modify or delete them |
Holding-area files (sync-pending/) |
Nexus | Unpublished copies awaiting a placement decision; deleted if deleted in Nexus |
The sidebar is manually curated in astro.config.mjs. The sync never reorders existing entries:
When docs/architecture/data-model.md changes in Nexus:
concepts/data-models.mdlastReviewed is refreshed and a missing owner defaults to Engineering# Heading is stripped)When a brand-new doc appears in Nexus, the sync applies a placement suggestion directly in the MR diff:
Section is inferred from the Nexus path:
| Nexus path | Suggested destination | Sidebar group |
|---|---|---|
docs/decisions/* |
concepts/ |
Architecture Decisions (ADRs) |
docs/architecture/* |
concepts/ |
Architecture |
docs/reference/* |
reference/ |
Technical reference |
docs/security/* |
security/ |
Security and compliance |
docs/runbooks/*, docs/ops/* |
operations/ |
Configure and operate |
docs/dev/integrations/* |
contributing/ |
Connect applications |
docs/dev/* |
contributing/ |
Contributing |
docs/perf/* |
contributing/ |
Performance baselines |
docs/process/*, docs/community/*, docs/legal/* |
contributing/ |
Community and maintenance |
docs/guides/*, docs/getting-started/* |
getting-started/ |
Run and deploy Nexus |
The page is created there with proper frontmatter (title from the first heading; ADR numeric prefixes like 0022- are dropped from the filename to match site conventions)
A sidebar entry is appended to the end of that section/group in astro.config.mjs
The new mapping is recorded in path-map.tsv so future edits update the page in place
All of this is in the MR diff. As a reviewer you can:
path-map.tsv line to match; future syncs follow your decisionpath-map.tsv line, then exclude the source in is_site_docs_path() to prevent reimport.Files the sync cannot place confidently, such as an unmapped service README or a filename that would collide with an existing page, are parked under sync-pending/<original path>. This directory is outside the published content collection, search index, and sitemap. The merge request flags them under New pages needing a placement decision. To publish one, move it into src/content/docs/, add its sidebar entry, and record its source and destination in scripts/docs-sync/path-map.tsv.
Live-page sync accepts Markdown files only. Repository entry points, agent instructions, and selected duplicate or package-local READMEs are excluded by is_site_docs_path() in scripts/docs-sync/lib.sh. Release snapshots retain their separate documentation-and-assets selection.
After placement, scripts/docs-sync/finalize.py translates repository-relative links to mapped site URLs. References without a site page point to the public source repository. Code examples are preserved. scripts/docs-sync/link-map.tsv records additional links to curated pages without making those pages body-sync targets. The same finalization step checks duplicate routes and sidebar destinations and updates public/llms.txt for added or deleted pages.
When a mapped file is deleted in Nexus, the sync deletes the site page and removes its sidebar entry. Deletions of unmapped Nexus files with no site counterpart are skipped with a log line. Docs-side pages can never be deleted by the sync.
Git rename detection turns a rename into delete-old + add-new. The old page and sidebar entry are removed; the new path goes through new-file placement. No automatic redirect is created — add one manually if the old URL matters.
When a protected v* tag is pushed to Nexus:
versions/<tag>/versions/manifest.json is updatedrelease/docs-<tag>Latest/current docs are unaffected; they’re owned by the merge path.
All documentation pages include metadata in their frontmatter to track ownership and review status:
---title: Page Titledescription: Page descriptionowner: ProductlastReviewed: 2026-08-05---| Field | Purpose | Values |
|---|---|---|
owner |
Team responsible for maintaining this documentation | Product, Engineering, Design |
lastReviewed |
Date this page was last reviewed for accuracy | ISO date format (YYYY-MM-DD) |
When syncing from Nexus:
owner: Engineering and lastReviewed set to today’s date (UTC)lastReviewed date automatically updated to today’s date (UTC) when content changesowner field, it’s automatically set to EngineeringFor docs-side pages (not in path-map.tsv):
Product (or whatever the page specifies)lastReviewed manually whenever you make substantial content changesDisplay:
Credentials and review policy live in this repository. The Nexus dispatch job targets this project directly. Using the job-token authentication fallback also requires inbound job-token access configured on Nexus.
Nexus’s .gitlab-ci.yml docs-sync-dispatch job must:
trigger: project: vortexaq/vortexai/nexus-docs (not read a DOCS_SYNC_PROJECT_PATH variable — this project no longer accepts a caller-supplied path anyway)TRIGGER_SOURCE (merge or release), NEXUS_COMMIT_SHA, RELEASE_TAG, and NEXUS_ACTOR (the sender’s $GITLAB_USER_LOGIN).This change already ships on the Nexus side as of its ci(docs-sync): target nexus-docs directly and ci(docs-sync): restore NEXUS_ACTOR for reviewer assignment commits.
Set in Settings → CI/CD → Variables. Mask credentials and protect them for the protected docs main branch where the receiver runs.
| Variable | Scope needed | Description |
|---|---|---|
NEXUS_GIT_HOST |
n/a | Git host of the canonical Nexus project. Kept out of tracked source and out of MR/doc output since this repository is public; set once, mark Protected, and restrict “Minimum role to use pipeline variables” to Maintainer+ so a manually-triggered pipeline can’t override it |
DOCS_SYNC_BOT_TOKEN |
api |
Project access token from this project (Settings → Access tokens, role Developer+). Pushes sync branches, opens MRs, resolves reviewer usernames |
NEXUS_REPO_READ_TOKEN |
read_repository |
Optional when job-token access is configured. Otherwise, supply a token whose owner can read the Nexus repository. |
| Variable | Purpose |
|---|---|
DOCS_MAINTAINER |
Comma-separated GitLab usernames always requested as MR reviewers, alongside the Nexus author who made the change; enforce the two-approval requirement itself in project settings |
trigger: keyword runs with the triggering user’s permissions. Manage this in this repo’s Settings → Members.DOCS_SYNC_BOT_TOKEN bot user only needs to create branches and MRs (no push-to-main rights required — everything goes through MRs)main, disallow direct bot pushes, and require a successful pipeline before merge. Configure the approval rule to require sign-off from both the Nexus author (NEXUS_ACTOR, requested automatically) and a docs maintainer — requesting reviewers alone does not enforce that; the project’s approval rule does.docs-sync-merge job log:
docs-sync trigger received: TRIGGER_SOURCE=merge NEXUS_COMMIT_SHA=abc1234...cloning canonical vortexaq/vortexai/nexus (main only) at abc1234diffing verified range def5678..abc1234documentation changes:M docs/decisions/backlog.mdA docs/decisions/0022-registry-state-from-signed-artifacts.mdpushed sync/nexus-maindocs sync merge request opened: <url>sync/nexus-main merge request; nothing publishes until it’s mergedTo pick up a fix merged into docs main:
docs-sync-dispatch in the sync stage. This creates a fresh downstream docs pipeline.The retry button on an existing downstream docs pipeline reruns its original checkout. It does not pick up a newer docs commit. Run pipeline in the docs project also does not select the sync jobs: they require CI_PIPELINE_SOURCE=pipeline from the cross-project trigger.
If a pending sync/nexus-main branch already records the same Nexus commit, the script leaves that branch unchanged. Review or edit that existing branch rather than expecting a retry to regenerate it. A newer source commit may refresh and force-push the branch from docs main; merge placement edits before dispatching another sync to preserve them.
| Symptom | Check |
|---|---|
| Trigger never arrives | Does the Nexus merger have Developer+ in this project? Is the hardened Nexus docs-sync-dispatch job merged and green? |
| Clone of Nexus fails | Check token availability, expiry, scope, and Nexus read access. For the job-token fallback, check the Nexus inbound allowlist and triggering user’s access. |
| Commit verification fails | Confirm the full SHA exists on the internal Nexus main; public-mirror and feature-branch commits are intentionally rejected |
| State ancestry fails | Internal Nexus history was rewritten or state.json was edited incorrectly; reconcile manually rather than bypassing the check |
| MR creation fails (401/403) | Does DOCS_SYNC_BOT_TOKEN have api scope? Is it expired? |
| Reviewer not assigned | Username must exist and be a member here; fallback actor strings like Jane Doe <jane@…> are skipped by design (check job log) |
| Sync MR has odd placement | Adjust in the MR: move file + sidebar entry + fix the path-map.tsv line |
| Same merge request keeps updating | Newer source commits refresh sync/nexus-main; an already-represented commit exits without refreshing it. |
The shared documentation selection lives in scripts/docs-sync/lib.sh. Live pages additionally pass through is_site_docs_path(), which limits imports to Markdown and excludes repository-only or duplicate pages:
DOCS_INCLUDE_PATTERNS='docs/**.md'
DOCS_EXCLUDE_PATTERNS='__snapshots__/**/__snapshots__/**.test.*node_modules/**/node_modules/*.gitlab/*'Section inference lives in suggest_section() and sidebar_group_for_nexus_path() in scripts/docs-sync/lib.sh. Edit these as the Nexus docs layout or the site’s sidebar structure evolves.
The sidebar separates Product documentation (console workflows, team access, reporting, and administration guidance) from Developer documentation (integration, deployment, operations, architecture, security, contribution, and reference). Automatic source placement targets developer groups. Product guides are curated here, with contextual links to technical instructions. File paths do not determine audience: client integration pages retain their contributing/ URLs but appear under Connect applications.
Delete its line from scripts/docs-sync/path-map.tsv and add the source to the exclusions in is_site_docs_path(). This keeps future edits from recreating a second copy. A link-map.tsv entry can still direct links from other synced pages to the curated page.
Testing, Wire Contracts, and the Claude Code session checklist are curated this way. Changes to their Nexus source documents require manual reconciliation so that reader-facing structure and instructions stay current without losing the site’s formatting.
scripts/docs-sync/state.json records the latest internal Nexus commit represented by a merged sync MR. A code-only Nexus merge creates no MR; the next documentation-bearing merge catches up from the recorded commit. The first run without state performs a full reconciliation.
Set each receiver job’s rules to - when: never in .gitlab-ci.yml. Removing its rules entirely does not disable a job.
main. Trigger variables cannot redirect the read token to another repository.main.main without approval from both the Nexus author who made the change (NEXUS_ACTOR, forwarded by the sender and requested as a reviewer) and a docs maintainer (DOCS_MAINTAINER). This is enforced by the nexus-docs approval and protected-branch rules configured by a maintainer — requesting reviewers is routing, not enforcement.mainDocs Sync Quick Reference
The cheat sheet version of this page: commands, paths, and review steps at a glance.
Open the cheat sheet →CI/CD Pipeline
The Nexus source repository’s build checks, security gates, and release flow.
See the pipeline →