You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add multi-version documentation support, following the copy-on-major-release model
agreed in #320 (versioned directories on one branch, per-product independent latest,
pinned reference generation, a version selector in the UI).
Driver: OpenVox 9 is approaching and we can't yet stand up _openvox_9x while
freezing _openvox_8x. This issue tracks the work.
Scope is split deliberately: a small must-have track to ship 9.0 docs, and a hardening backlog of correctness/automation items that are good ideas but are not release-blocking and should not pad the critical path. Follow-up to #320.
Must-have track (ship OpenVox 9 docs)
Three pieces. This is the whole critical path.
1. Reference generator: per-version pinning (the only real engineering)
Today output dirs and emitted URLs are fixed at load time (OUTPUT_DIR lib/puppet_references.rb:15, puppet/type.rb:19; OpenBolt hardcodes _openbolt_5x
in openbolt/docs.rb:9), and repo.newest_release rejects prereleases (repo.rb:50).
We need to build a chosen version into a chosen collection without cross-writing.
Fix the OpenFact URL bug first: generators write into _openfact_* but stamp @latest = '/openvox/latest' (facter/core_facts.rb:12, facter/facter_cli.rb:10).
Wrong product URL today, independent of versioning.
Move output paths from load-time constants to instance/config-level values so
one process can target a specific collection (don't drive it by mutating ENV['COLLECTION'] in a loop — the constants freeze at require time).
Generator emits intra-product links/canonical: relative to its target
collection, not hardcoded /<product>/latest/.
Rakefile accepts COLLECTION=<dir> + VERSION=; an explicit VERSION bypasses
the prerelease filter (so we can build 9.x from an RC). No-arg behavior unchanged.
A references:all-style task builds each pinned (tag → version dir) pair; build.yaml calls it. latest is an alias, never its own build row — only
real version dirs are built; nothing writes through the _latest symlink.
2. The cutover + runbook
Write the "add a major version" runbook: copy _openvox_8x → _openvox_9x,
repoint the _latest symlink, register the collection + defaults in _config.yml, add nav files (_data/nav/, nav_map.yml, navigation.yml),
build both versions.
Include a rollback line (repoint symlink back, drop the new collection/nav
entries, republish).
Dry-run against an OpenVox 9 prerelease tag before the real release — this is
the gate the timeline is really about. Eyeball both trees; confirm 8.x didn't move. (Done in Add maintainer runbook for the major-version cutover #337: full two-phase cutover dry-run against 9.0.0-alpha1, 8.x confirmed frozen. A final dry-run at the real 9.0 tag is still worth doing at cutover time.)
3. Version selector
_includes/version-selector.html at the top of sidebar.html; reuse the existing current_nav_page_subpath / current_nav_base logic for path mapping.
Show only the current product's versions (from a small _data/products.yml);
mark current; keep the reader on the same page path when switching, falling back
to the version root when the target page doesn't exist. (Shipped in Add a per-product documentation version selector #335: per-product picker from products.yml, marks current, badges the latest-aliased version, hidden until a product has 2+ versions. It links to each
version's root, not the same page path — path-preserving switching was
deferred as a follow-up and is tracked in the hardening backlog below.)
_data/products.yml is introduced here as a lightweight list the selector reads (and a
handy home for the reference pins in piece 1) — not a heavyweight source-of-truth that
everything else is generated from.
Hardening backlog (not release-blocking)
Good ideas surfaced in review. Do them incrementally; none should gate 9.0. Most exist
because we're optimizing an event that happens ~once a year — a runbook checklist and a
human in the loop cover the risk for now.
Intra-product relative-link sweep. Convert /<product>/latest/ authored body
links to relative so frozen docs stay internally coherent (~148 in OpenVox).
Cheaper before the copy, but non-blocking: if left as latest, frozen 8.x links
drift to 9.x — a real but fixable-later UX bug. Depth-aware (subdir pages need ../).
Cross-product link policy. Relative links can't span products. For v1, accept
cross-product links following latest; later, optionally pin partner versions via
a cross_product_affinity map for lockstep products (e.g. openvoxdb 8x → openvox 8x).
Drift validation.(Partial: rake test:products_data runs in CI, but only
validates products.yml internally.) Optional Rake task asserting _config.yml / navigation.yml
/ nav_map.yml / _latest symlinks agree with products.yml, run in CI before jekyll build. A runbook checklist covers the once-a-year case until then. Note: _config.yml collections can't be generated within Jekyll (read before _data),
so generation isn't an option for the highest-drift surface anyway.
Lint gate flagging new hardcoded intra-product /latest/ body links
(pattern-based, not existence-based). Pairs with the relative-link sweep.
Frozen-version link proofing. Extend rake test:links (today checks /latest/
only, Rakefile:76) to also proof frozen version trees.
Canonical on frozen builds. Decide whether frozen versions keep canonical: /<product>/latest/... (SEO → current) or get per-version canonicals.
Default: keep pointing at latest. (Decided in Add pinned out-of-date banner and /latest/ canonicals for versioned docs #437: the numbered copy of the latest version canonicalizes to its /latest/ twin; frozen older versions keep the theme's self-canonical.)
No-redirect awareness. When latest moves 8→9, pages removed/renamed in 9.x
404 at /<product>/latest/<page> for old bookmarks (content still at /8.x/).
Note in the runbook; we have no redirect mechanism. (In MAINTAINING.md, Phase 2 step 5.)
OpenVox Containers edge case. It has a real _openvox-containers_latest dir
(not a symlink) and no numbered collection — selector/validator logic must not
assume the uniform model. (single_version: true in products.yml; selector, banner,
and test:products_data all skip it.)
Shared/version-invariant content (link vs. copy): explicitly out of scope; revisit.
Path-preserving version switch. The selector (Add a per-product documentation version selector #335) links to each version's
root; keeping the reader on the same page path across a switch (falling back to
the root when the target page doesn't exist) needs per-navigation JS. Deferred
follow-up from piece 3.
Summary
Add multi-version documentation support, following the copy-on-major-release model
agreed in #320 (versioned directories on one branch, per-product independent
latest,pinned reference generation, a version selector in the UI).
Driver: OpenVox 9 is approaching and we can't yet stand up
_openvox_9xwhilefreezing
_openvox_8x. This issue tracks the work.Scope is split deliberately: a small must-have track to ship 9.0 docs, and a
hardening backlog of correctness/automation items that are good ideas but are
not release-blocking and should not pad the critical path. Follow-up to #320.
Must-have track (ship OpenVox 9 docs)
Three pieces. This is the whole critical path.
1. Reference generator: per-version pinning (the only real engineering)
Today output dirs and emitted URLs are fixed at load time (
OUTPUT_DIRlib/puppet_references.rb:15,puppet/type.rb:19; OpenBolt hardcodes_openbolt_5xin
openbolt/docs.rb:9), andrepo.newest_releaserejects prereleases (repo.rb:50).We need to build a chosen version into a chosen collection without cross-writing.
_openfact_*but stamp@latest = '/openvox/latest'(facter/core_facts.rb:12,facter/facter_cli.rb:10).Wrong product URL today, independent of versioning.
one process can target a specific collection (don't drive it by mutating
ENV['COLLECTION']in a loop — the constants freeze at require time).canonical:relative to its targetcollection, not hardcoded
/<product>/latest/.COLLECTION=<dir>+VERSION=; an explicitVERSIONbypassesthe prerelease filter (so we can build 9.x from an RC). No-arg behavior unchanged.
references:all-style task builds each pinned(tag → version dir)pair;build.yamlcalls it.latestis an alias, never its own build row — onlyreal version dirs are built; nothing writes through the
_latestsymlink.2. The cutover + runbook
_openvox_8x→_openvox_9x,repoint the
_latestsymlink, register the collection +defaultsin_config.yml, add nav files (_data/nav/,nav_map.yml,navigation.yml),build both versions.
entries, republish).
the gate the timeline is really about. Eyeball both trees; confirm 8.x didn't move.
(Done in Add maintainer runbook for the major-version cutover #337: full two-phase cutover dry-run against
9.0.0-alpha1, 8.x confirmed frozen. A final dry-run at the real 9.0 tag is still worth doing at cutover time.)3. Version selector
_includes/version-selector.htmlat the top ofsidebar.html; reuse the existingcurrent_nav_page_subpath/current_nav_baselogic for path mapping._data/products.yml);mark current; keep the reader on the same page path when switching, falling back
to the version root when the target page doesn't exist.
(Shipped in Add a per-product documentation version selector #335: per-product picker from
products.yml, marks current, badges thelatest-aliased version, hidden until a product has 2+ versions. It links to eachversion's root, not the same page path — path-preserving switching was
deferred as a follow-up and is tracked in the hardening backlog below.)
_data/products.ymlis introduced here as a lightweight list the selector reads (and ahandy home for the reference pins in piece 1) — not a heavyweight source-of-truth that
everything else is generated from.
Hardening backlog (not release-blocking)
Good ideas surfaced in review. Do them incrementally; none should gate 9.0. Most exist
because we're optimizing an event that happens ~once a year — a runbook checklist and a
human in the loop cover the risk for now.
/<product>/latest/authored bodylinks to relative so frozen docs stay internally coherent (~148 in OpenVox).
Cheaper before the copy, but non-blocking: if left as
latest, frozen 8.x linksdrift to 9.x — a real but fixable-later UX bug. Depth-aware (subdir pages need
../).cross-product links following
latest; later, optionally pin partner versions viaa
cross_product_affinitymap for lockstep products (e.g. openvoxdb 8x → openvox 8x).rake test:products_dataruns in CI, but onlyvalidates
products.ymlinternally.) Optional Rake task asserting_config.yml/navigation.yml/
nav_map.yml/_latestsymlinks agree withproducts.yml, run in CI beforejekyll build. A runbook checklist covers the once-a-year case until then. Note:_config.ymlcollections can't be generated within Jekyll (read before_data),so generation isn't an option for the highest-drift surface anyway.
/latest/body links(pattern-based, not existence-based). Pairs with the relative-link sweep.
build once to confirm no cross-write. Don't need a standing CI gate.
(Done: Add maintainer runbook for the major-version cutover #337 dry run and the Add "Component versions in recent OpenVox releases" reference page #339 throwaway 9.x cutover; Add OpenVox 9.x docs collection as a preview (latest stays on 8.x) #430 in prod confirms.)
rake test:links(today checks/latest/only,
Rakefile:76) to also proof frozen version trees.canonical: /<product>/latest/...(SEO → current) or get per-version canonicals.Default: keep pointing at
latest.(Decided in Add pinned out-of-date banner and /latest/ canonicals for versioned docs #437: the numbered copy of the
latestversion canonicalizes to its/latest/twin; frozen older versions keep the theme's self-canonical.)latestmoves 8→9, pages removed/renamed in 9.x404 at
/<product>/latest/<page>for old bookmarks (content still at/8.x/).Note in the runbook; we have no redirect mechanism. (In
MAINTAINING.md, Phase 2 step 5.)_openvox-containers_latestdir(not a symlink) and no numbered collection — selector/validator logic must not
assume the uniform model. (
single_version: trueinproducts.yml; selector, banner,and
test:products_dataall skip it.)root; keeping the reader on the same page path across a switch (falling back to
the root when the target page doesn't exist) needs per-navigation JS. Deferred
follow-up from piece 3.
Part of #320.