Repository navigation
feat: prepare saved documentation editions from pinned releases - #379
Conversation
The edition builder needs to assemble a site exactly as `make assemble` does. `assemble_site` runs the stages in order, and `main` now calls it, so a stage added later reaches both.
`scripts/edition.py prepare` builds one release's edition with the assembler and saves it under `editions/<product>/<version>/`, with a release record in `releases/` naming the release and the commit of every root. Each root is read from Git at its commit, only from its docs folder. An Enterprise release must be qualified, and its shared pages come from the LanceDB commit it bundles. An OSS edition names its Enterprise source explicitly. A saved edition is never replaced: preparing the same release again changes nothing, even after a correction, and different inputs stop. A failed preparation saves nothing. `check` pairs records with editions and requires changed editions to list their corrections.
VERSIONING.md carries the approved design: decisions, components, records, corrections, planned reader and publication behavior, and the open questions. The README explains how to prepare and correct an edition, with a sample record.
A record that lost its docs source, carried a malformed commit or dropped an Enterprise release's qualification or bundled LanceDB still loaded, because only a few nested keys were read. Records are now checked against what `prepare` writes: every release and source field, product-specific fields, commit and checksum formats, and agreement between each source and the release field that selected it. Each problem names its record and field. The check is offline and does not require a tag for a bundled LanceDB commit.
0d6083e to
3ec217e
Compare
Once a record listed any correction, `check` accepted every later change to its edition, including an unrelated deleted page. Each correction now records the checksum of the edition it accepts, and the edition must match the latest one, or the prepared checksum when there is none. `check` prints the checksum to record, and the README's correction steps use it. Preparing a saved release again still keeps the edition and its corrections.
3ec217e to
62675cc
Compare
`check` compared each edition only with the record beside it, so an edition rewritten together with its prepared checksum, an earlier correction edited or removed, or an edition deleted with its record all passed. `check --base COMMIT` also compares the editions and records on that commit: each must remain, every record field but `corrections` must be unchanged, and existing corrections must stay as they were, with new ones only after them. Records are compared as parsed values, so formatting does not matter, and new editions are checked as before. The Assemble workflow's test job runs it offline, without credentials: a pull request against the base commit its event names, and a push against the commit the branch pointed at before it, each fetched by hash. A manual run checks every edition against its record only.
The record named this checkout's HEAD as the source of the docs-owned pages, assemble.yaml and the assembler. On a pull request's branch that commit gets a new hash when the branch is squash merged, and a second edition prepared after the first was committed named the unmerged edition commit. `docs/` and assemble.yaml are now read from Git at origin/main, or at an earlier commit of main named with `--docs`, and that is the commit the record names. Preparation stops when the commit is not on origin/main, or when this checkout's scripts/, the assembler that runs, are not that commit's. Preparing a saved release again is still a no-op that keeps its corrections.
A short hash of the right commit was reported as a different bundled LanceDB; `--bundled-lancedb` now asks for the full hash. Tests now cover guards that had none: a lightweight qualification tag, a root in assemble.yaml without recorded provenance, Git control files in an edition and a folder in editions/ that is not a product's. Two test docstrings describe their cases rather than where the cases came from.
Product pages come from the release commits, and the docs-owned pages, dataset cards and REST specification from the commit of main that preparation read; both are fixed when the edition is prepared. The README gives the updated preparation and correction commands, lists the rules `check` enforces, including the base comparison the pull request check runs, and marks the sample record as illustrative. A recreated qualification tag is described as new evidence rather than a duplicate, and naming an OSS edition's Enterprise source as pinning, not approving, it.
Preparation trusted origin/main without knowing where it came from. In the usual fork layout, origin is the contributor's fork, so a docs-owned change only on the fork's main reached the edition and its record named the commit as lancedb/docs. Preparation now requires origin to be lancedb/docs over HTTPS or SSH, with URL rewriting applied as fetching applies it, and stops with how to fix it before fetching or saving anything. It then fetches main from origin itself and checks the docs input commit against what that fetch brought, not against a remote-tracking branch fetched earlier. The ancestry and matching-scripts checks are unchanged, and `check` stays offline. Tests stand in for lancedb/docs with a local bare repository that they accept as its URL; one test checks the real URL forms alone.
The check protects saved editions only if it must pass before a pull request merges, which is a repository rule set up separately. The check on a push to main reports a violation in that push but cannot undo it, and a later push does not find it, because its base already includes the change. The README, VERSIONING.md and the module docstring now say so.
Preparation fetched lancedb/docs main into refs/remotes/origin/main but then resolved the shorthand origin/main, which was also the default for --docs. A local branch or tag named origin/main wins that lookup, so its unmerged commit passed the ancestry check and went into the edition. The fetched main is now resolved as refs/remotes/origin/main, and without --docs preparation uses the commit that fetch returned. A commit named with --docs is still checked against it.
The CI step fetches its comparison commit with the job's GitHub token; `edition.py check` itself is offline and needs no Enterprise repository credentials. Alongside requiring the check before merging, changes to the checker and its workflow need enforced review, such as required code-owner review of scripts/, the Makefile and .github/workflows/.
| @@ -0,0 +1,93 @@ | |||
| # Versioned documentation | |||
There was a problem hiding this comment.
hmm high level thought: I think the combined approach makes sense to me.
One thing though, when we say docs for a version, aren't we actually saving the site built from a few different commits? For OSS, we use the release tag, an Enterprise version, and docs main.
should we make that more clear here? Otherwise it sounds like every page comes from that OSS release.
There was a problem hiding this comment.
Made this more clear
|
|
||
|
|
||
| def prepare(args: argparse.Namespace, repo: Path = REPO_ROOT) -> str: | ||
| requested = resolve_release(args, repo) |
There was a problem hiding this comment.
am I reading this right? prepare checks the docs commit and scripts/ before checking if the edition already exists. So if I already prepared a release and later changed something in scripts/, running prepare again would fail even though we're not rebuilding anything. wdyt? Should we check the saved release inputs first and only require the match when building the new edition?
There was a problem hiding this comment.
fixed, if the edition exists, we will just validate it and saved contents
| others = [url for url in urls if not DOCS_URLS.fullmatch(url)] | ||
| if not urls or others: | ||
| raise EditionError( | ||
| f"this checkout's {DOCS_REMOTE} is {', '.join(others) if urls else 'not set'}, not " |
There was a problem hiding this comment.
nit: uncommon i guess but this could expose the remote token url for someone when printing the ci logs.
There was a problem hiding this comment.
fixed. good catch
| Enterprise tag or full commit hash named with `--enterprise`; a branch name | ||
| is not accepted, so the record pins an exact commit. Pinning a commit does | ||
| not decide that it may be published with that OSS release, which is not | ||
| settled yet; see [Open questions](VERSIONING.md#open-questions). |
There was a problem hiding this comment.
nit: looks like the open-questions section was removed.
There was a problem hiding this comment.
removed. i missed this in my cleanup 🤦
geruh
left a comment
There was a problem hiding this comment.
Awesome thanks for addressing the comments LGTM!!
This adds the foundation for versioned docs: a command to save documentation from a release’s exact sources, a record of what built it, and CI checks that keep older editions intact while allowing reviewed corrections. It doesn’t change the live site yet.
Next steps
Why save each edition in this repository?
Authors continue writing documentation alongside the product code. When we prepare a release’s docs, we save a copy here so Mintlify can serve multiple versions together, and later source changes won’t alter older guides.
The tradeoff is that documentation exists in two places: the source guides for future releases, and saved editions for published releases. We only edit a saved edition when correcting that version’s documentation. For example, fixing an incorrect example. That correction goes through a reviewed PR and is recorded without changing the edition’s original source history. A correction to the source guides won’t automatically update older editions. We deliberately choose which versions need it, rather than rebuilding old docs with content that might describe newer functionality.