Skip to content
↑↓Navigate↵SelectescClose

Documentation Sync Automation

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 whenever documentation changes reach its canonical main branch. External contributions become eligible only after the team reviews and merges them into the internal repository. Nothing is published automatically: every sync arrives as a merge request requiring sign-off from both the Nexus author who made the change and a docs maintainer.

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:

  1. Developer merges documentation changes to nexus main
  2. Nexus pipeline triggers a cross-project pipeline in this repository
  3. The sync job clones Nexus, detects changed docs, and applies them to a sync/* branch
  4. A merge request is opened, with the Nexus author and the docs maintainer requested as reviewers
  5. Both approve (or adjust) the MR; merging it publishes via the normal build/deploy pipeline
Documentation sync flow from Nexus to publication
  1. NexusMerge to main or push a protected v* tagThe Nexus pipeline must pass before its dispatch job runs.
  2. Sender gateIs this the canonical internal Nexus project and an eligible ref?The public mirror and merge request pipelines cannot dispatch a sync.
  3. Receiver gatesValidate pipeline source, trigger type, and commit identifierThe receiver always clones the fixed internal Nexus repository.
  4. Merge path

    Incremental documentation sync

    1. Verify the requested commit belongs to canonical Nexus main.
    2. Diff from the last reviewed sync state and filter to documentation paths.
    3. Exit without an MR for a code-only or no-op change.
    4. Update mapped pages and propose placement for new pages.
    Release path

    Frozen version snapshot

    1. Require a safe semantic version and resolve the tag from Nexus.
    2. Verify the tag commit belongs to canonical main.
    3. Fail on broken relative links or missing assets.
    4. Update versions/manifest.json.
  5. Push gateDid content actually change?No change: exit. Otherwise: push (or refresh) the sync branch and open an MR.
  6. Human gateThe Nexus author and a docs maintainer both review the MRBoth approve, revise, or reject every proposed documentation change.
  7. PublicationMerging to 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_docs_path() Code-only merges exit successfully without creating a branch or 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:

  1. Nexus Pipeline — runs tests/builds; final job docs-sync-dispatch triggers this repo
  2. Nexus-Docs Triggered Pipeline — runs docs-sync-merge only (build/deploy are skipped in triggered pipelines). The job pushes (or refreshes) the sync/nexus-main branch and opens a review MR
  3. Human review — the Nexus author and a docs maintainer both review the MR; placement suggestions for new pages are part of the diff
  4. Merge — merging the MR lands on main, which runs the normal build → deploy pipeline and publishes the site

The 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.md
docs/runbooks/audit-out-of-band.md security/audit-archive-export.md
docs/dev/ci.md contributing/ci-cd-pipeline.md
CHANGELOG.md reference/changelog.md

This 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 pages (src/content/docs/nexus/) Nexus Parked 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:

  • Edits to mapped files touch only the page body — zero sidebar changes
  • New pages get a sidebar entry appended to the end of the suggested section (or nested group); you can move it anywhere in the MR
  • Deletions remove only that page’s entry, leaving everything else in place

When docs/architecture/data-model.md changes in Nexus:

  1. The sync finds its mapping → concepts/data-models.md
  2. The site page’s frontmatter block is kept as-is
  3. The body is replaced with the Nexus content (the duplicate # Heading is stripped)
  4. The change appears in the MR under “Updated in place (existing sidebar order unchanged)”

New Files (Unmapped) — Auto-Suggested Placement

Section titled “New Files (Unmapped) — Auto-Suggested Placement”

When a brand-new doc appears in Nexus, the sync applies a placement suggestion directly in the MR diff:

  1. Section is inferred from the Nexus path:

    Nexus path Suggested destination Sidebar group
    docs/decisions/* concepts/ Architecture Decisions (ADRs)
    docs/architecture/* concepts/ Key Concepts
    docs/reference/* reference/ Reference
    docs/security/* security/ Security
    docs/runbooks/*, docs/ops/* operations/ Operations
    docs/dev/* contributing/ Contributing
    docs/perf/* contributing/ Testing & Performance Baselines
    docs/process/*, docs/community/*, docs/legal/* contributing/ Governance & Process
    docs/guides/*, docs/getting-started/* getting-started/ Getting Started
  2. 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)

  3. A sidebar entry is appended to the end of that section/group in astro.config.mjs

  4. 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:

  • Approve — merge as-is
  • Adjust — move the file, reposition the sidebar entry, and update the path-map.tsv line to match; future syncs follow your decision
  • Reject — delete the page, its sidebar entry, and its path-map.tsv line

New Files With No Good Suggestion — Holding Area

Section titled “New Files With No Good Suggestion — Holding Area”

Files the sync can’t place confidently (e.g. services/gateway/README.md, or a filename that would collide with an existing page) are parked under src/content/docs/nexus/<original path> with no sidebar entry. The MR flags them under “New pages needing a placement decision” — reachable by direct URL but invisible in navigation until you place them.

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:

  1. The complete doc set at the tagged commit is written to versions/<tag>/
  2. versions/manifest.json is updated
  3. All relative links are validated — broken links fail the job before any MR is opened (submodule and out-of-snapshot links are warnings)
  4. A review MR is opened on release/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 Title
description: Page description
owner: Product
lastReviewed: 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:

  • New pages created by the sync automatically get owner: Engineering and lastReviewed set to today’s date (UTC)
  • Existing mapped pages have their lastReviewed date automatically updated to today’s date (UTC) when content changes
  • If an existing page doesn’t have an owner field, it’s automatically set to Engineering

For docs-side pages (not in path-map.tsv):

  • Owner remains Product (or whatever the page specifies)
  • Update lastReviewed manually whenever you make substantial content changes

Display:

  • Metadata appears at the top of each page (below the description)
  • Format: “Owner: Product Last reviewed: August 5, 2026” (formatted with the page viewer’s local timezone)
  • The date is stored in UTC in frontmatter but displayed in the user’s browser timezone

Credentials and review policy live in this repository. The Nexus repository requires one code change (below) so its docs-sync-dispatch job targets this project directly; nothing else needs to change in Nexus project settings.

Nexus’s .gitlab-ci.yml docs-sync-dispatch job must:

  • Hardcode trigger: project: vortexaq/vortexai/nexus-docs (not read a DOCS_SYNC_PROJECT_PATH variable — this project no longer accepts a caller-supplied path anyway)
  • Restrict its own rules to the canonical internal host and project, e.g. if: $CI_SERVER_HOST != "lab.10xnatsec.com" || $CI_PROJECT_PATH != "vortexaq/vortexai/nexus" → when: never, so the public mirror can never dispatch a sync
  • Forward NEXUS_COMMIT_SHA, RELEASE_TAG, and NEXUS_ACTOR ($GITLAB_USER_LOGIN) — nothing else is read by this receiver

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 (masked; protect if your sync branches are protected):

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 Token that can clone the internal Nexus repo (vortexaq/vortexai/nexus). A personal access token from any account with read access to Nexus works — no Nexus project settings required
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
  • Nexus mergers need Developer+ membership in this project: the native trigger: keyword runs with the triggering user’s permissions. Manage this in this repo’s Settings → Members.
  • The DOCS_SYNC_BOT_TOKEN bot user only needs to create branches and MRs (no push-to-main rights required — everything goes through MRs)
  • Protect 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.
  1. Merge a doc change in Nexus (or manually trigger — see below)
  2. Check the docs-sync-merge job log:
    docs-sync trigger received:
    TRIGGER_SOURCE=merge
    NEXUS_COMMIT_SHA=abc1234...
    cloning canonical vortexaq/vortexai/nexus (main only) at abc1234
    diffing verified range def5678..abc1234
    documentation changes:
    M docs/decisions/backlog.md
    A docs/decisions/0022-registry-state-from-signed-artifacts.md
    pushed sync/nexus-main
    docs sync merge request opened: <url>
  3. Review the opened (or refreshed) sync/nexus-main merge request; nothing publishes until it’s merged

CI/CD → Run pipeline → branch main → add variables:

TRIGGER_SOURCE = merge
NEXUS_COMMIT_SHA = <full SHA from internal Nexus main>

(Manually run pipelines have $CI_PIPELINE_SOURCE == "web", so also temporarily widen the job rule to allow it.)

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 Is NEXUS_REPO_READ_TOKEN set and unexpired? Does its owner still have Nexus read 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 MR keeps updating Expected: retried triggers force-push the same sync/nexus-main branch instead of duplicating MRs

What counts as documentation lives in scripts/docs-sync/lib.sh:

Terminal window
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.

Delete its line from scripts/docs-sync/path-map.tsv. Future Nexus edits to that source file will be treated as a new file (and likely park in the holding area) instead of overwriting your page.

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.

Comment out the docs-sync-merge / docs-sync-release rules in .gitlab-ci.yml.

  • Fixed source and ancestry: the receiver always clones the internal Nexus repository and rejects commits outside canonical main. Trigger variables cannot redirect the read token to another repository.
  • Internal host not published: this site is public, so the Nexus git host lives only in a protected CI/CD variable, never in tracked source or in generated MR/doc output (commits and tags are referenced by id, not by link).
  • External contributions: the public GitLab mirror never dispatches syncs. Its contributions become eligible only after internal review and merge to canonical Nexus main.
  • Two-person sign-off: nothing reaches 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.
  • Every sync MR carries the verified source commit, previous synchronized commit, and the Nexus author.
  • The bot token only needs branch/MR rights — it cannot push to protected main
  • Release snapshots are link-validated before an MR is opened

Docs 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 general CI/CD overview for this repo: stages, gates, and release flow.

See the pipeline →