Documentation packages, publication and independent restoration

This page defines the delivery contract of tools/documentation/delivery.py. The same documentation must be readable online and offline and independently restorable from its archive, while preserving exact source and software-verification identities. Documentation delivery grants no new firmware qualification and establishes neither physical HIL, product release, safety certification nor long-term support.

Inputs and identity

Delivery consumes the strictly built HTML root and the current Git source. The build root must contain:

File

Purpose

index.html

Documentation entry

en/index.html

English entry

zh_CN/index.html

Chinese entry

api/index.html

Public API entry

licenses/LICENSE.txt

Packaged license

documentation-build.json

Source and tool identities observed during the build

Local diagram sources, SVG, CSS, JavaScript, fonts, download instructions and other licenses are inventoried with the actual build output. Web resources use relative paths and cannot depend on an online CDN. Normal HTTP(S) reference links may remain; pages, scripts, styles and images needed for offline reading must already be in the package.

The build record uses schema 1 and kind nexus_documentation_build. Its source contains actual observations from tools.evidence.identity.observe_git_source(), tools records observed tool versions, and languages is fixed to en and zh_CN. Delivery rejects dirty source, missing or mismatched pinned submodules, and stale builds that do not match the current source.

The existing strict policy loader reads the version from dependencies/platform-compatibility.json, which is the sole authored version authority. CMake calls the same loader to derive the version rather than inferring it from the project(nexus VERSION ...) text. The manifest also records the policy digest, interface epoch and schema epochs; inconsistent versions, duplicate JSON keys and invalid constants are rejected. A separate explicit release_id identifies the release; 1.0.0-docs-g<commit> is a format example and does not imply the current platform version. This identity permits only letters, digits, dots, underscores and hyphens, and cannot contain a directory or URL.

The manifest distinguishes three identities:

  • documentation_source: the full build-time commit/tree and the scoped tree digest of documentation directories.

  • code_source: the full commit/tree of the same checkout and the scoped tree digest of platform code directories.

  • qualified_code: the commit/tree of previously verified code in the delivery index, together with the index-file digest.

The first two may share a Git commit but cover different paths. The third references existing software evidence; a documentation commit cannot turn it into new firmware verification of the current HEAD. physical_status is fixed to not_executed.

File and digest contract

files in documentation-manifest.json lists only actual payload. Each entry contains a normalized relative path, byte count and SHA-256. The manifest, SHA256SUMS, archive itself and execution receipts are excluded to avoid digest self-reference. SHA256SUMS covers payload and manifest, but not itself.

Delivery commands return manifest and archive digests, retained outside the package in execution evidence and remote publication records. Independent restoration receives the saved manifest digest to detect changes to the payload or manifest. Digest verification is not a signature: it cannot authenticate authors or defeat an attacker controlling every trust source.

Build metadata must have status passed. failed, running, missing status and stale successful records cannot be packaged. Independent verification still rejects a failed build record even if someone manually recalculates the manifest and digests.

Verification also checks:

  • Completeness of manifest schema, source identity, languages, tool records and software qualification references.

  • An exact file closure: missing or extra files, duplicate entries, digest mismatches and byte-count mismatches reject.

  • Required entries and licenses, with pages, resources and anchors resolvable within the package.

  • Normalized paths: absolute paths, directory escape, symlinks, tar hardlinks and duplicate members reject.

  • Only regular archive files, with member count and total bytes within tool limits.

Packaging and restoration

package() verifies, copies and archives in an independent temporary directory before installing into a fresh output path. Output cannot overlap source or input payload; publication webroot cannot contain the input package or sit inside it. Reject before writing so bound input bytes remain untouched. Source and build payload are rechecked before and after the operation to reject concurrent changes. Identical verified inputs produce identical tar.gz bytes; archive order, timestamps, owners and modes are fixed.

restore() verifies every archive member and the exact file closure before writing into a fresh independent directory. It does not use extractall(), overwrite an existing directory or leave a partially restored destination on failure. Restored HTML can be opened in a browser without the original Git checkout, Python virtual environment or network; executing verification requires the tool.

The Python interface is below; the unified docs command provides the CLI entry:

from tools.documentation import delivery

result = delivery.package(build_dir, output_dir, source_root=source_root,
                          release_id=release_id)
manifest = delivery.verify(result["archive"],
                           expected_manifest_sha256=result["manifest_sha256"])
delivery.restore(result["archive"], independent_root,
                 expected_manifest_sha256=result["manifest_sha256"])

This is an interface fragment with paths explicitly supplied by the caller. Executable commands and actual acceptance results come from the unified docs CLI and this delivery’s receipts.

Legacy bookmarks and version boundaries

docs/reference/redirects.csv is the sole authored input for legacy URL migration. Each row names the old address, target page, source document and fixed revision, and chooses either a historical contract boundary or a location change of the same current contract. Never treat legacy HAL/OSAL/Kconfig APIs as current interfaces, or describe a location-only move as an API change. Keep old addresses in the ledger; subsequent changes check source, destination and actual bookmark behavior together.

The formal bilingual build validates this CSV through install_alias_catalog() and generates packaged explanation pages plus documentation-aliases.json. The catalog is derived, records the original CSV digest and enters the manifest payload; do not author a separate alias configuration. Single-language development previews lack a complete catalog and cannot be formal bilingual packages. Historical test fixtures without a catalog remain verifiable but do not establish legacy URL acceptance.

When an old URL collides with a current prose path, the generator preserves current prose in the version directory and derives canonical_collisions with the corresponding current source path/digest and HTML digest. Real current source and HTML must exist; handwritten markers cannot bypass consistency checks. The site root still explains the original CSV semantics. Both languages currently have such collisions for development/index.html and tutorials/index.html.

Publication preserves immutable versions/<release_id>/ and generates legacy bookmark explanation pages at the site root. en/index.html and zh_CN/index.html explicitly point to the current version; root api/index.html explains the legacy API contract and directs readers to its historical boundary, without automatic redirection or silently presenting current APIs. The current ledger contains 287 legacy addresses plus three language/API entries, yielding 290 verified root gateways. All links are relative, without scripts, meta refresh or external redirect services.

Unknown fields or semantics, duplicate/noncanonical URLs, reserved namespaces, missing targets, alias chains, and inconsistent language or source semantics reject before writing. Unrelated existing pages, symlinks and parent-directory conflicts cannot be overwritten. Root documentation-gateways.json inventories only tool-generated explanation pages and entry digests; registered bytes are checked before republication, and external changes require explicit maintainer handling. This record lives outside the package and its own digest closure, is unsigned, cannot authenticate a publisher, and grants no board or HIL qualification.

For the current published version, post_deploy() actually reads every root alias and all three language/API entries and checks exact bytes derived from the source catalog. Successful version-directory explanation pages do not replace root-bookmark acceptance. When checking an immutable historical version with stable verification explicitly disabled, the receipt truthfully records that root gateways were not checked.

Immutable publication and stable

publish() installs the verified directory at <webroot>/versions/<release_id>/, then updates <webroot>/stable/index.html. The stable English, Chinese and API entries use relative ../versions/.../ links without altering the selected version. When archive is supplied explicitly, the archive is stored outside the package at <webroot>/downloads/<release_id>.tar.gz. Stable displays the download link and archive digest; root entries and download indexes use relative links. An archive of the same name cannot be replaced with different bytes.

If a release_id already exists, the tool verifies its manifest and entire payload. Repetition is allowed only for identical original bytes; the same identity with different bytes rejects. New content receives a new identity, never an overwritten historical version. Preview builds and publication permissions remain separate; previews do not automatically become stable, and documentation versions are not software-candidate state machines.

delivery.publish(result["directory"], webroot, archive=result["archive"])
delivery.post_deploy(base_url, release_id, result["manifest_sha256"],
                     receipt_path=post_deploy_receipt,
                     expected_archive_sha256=result["archive_sha256"])

Post-publication acceptance must access the actual site URL. post_deploy() reads the manifest, checksum file and every inventoried payload over HTTP(S), compares against externally saved digests, and verifies actual downloaded bytes and packaged links. By default it checks that stable selects this version; when an archive digest is supplied it also checks the download entry and the complete downloaded archive closure. Success writes an independent receipt recording each requested URL, final URL, status, bytes and digest. Local HTTP verification does not replace public-site acceptance.

Before a new acceptance run, the tool marks its owned prior receipt as running. Failure writes failed; a prior verified cannot remain as a false current success. Unrelated files and symlinks cannot act as receipts.

An immutable historical version need not be current stable. For historical verification only, set verify_stable=False explicitly; the receipt also records that stable was not checked and cannot establish acceptance of the latest publication entry.

Long-term retention and exit conditions

Remote publication retains the immutable documentation archive, archive and manifest digests, exact source identity and restoration receipt. The deliverer must actually upload, download again, verify and restore: configured upload is not completed long-term storage. After restoration into a second independent root, check language entries, API, diagrams and licenses, and record results against actual files.

Documentation release notes may point to Release assets or an immutable Git commit on an independent assets branch. Availability depends on the channel that actually succeeded: a failed asset upload cannot be reported as a published asset. Git downloads must include the full commit and exact path/bytes/SHA-256; mutable branch names are discovery entries only. Tool or evidence archives exceeding the per-file limit are stored in ordered parts no larger than 32 MiB, with a full-archive digest. Restoration is established only after all parts are downloaded, actually reassembled, checked as a whole and verified member by member. Preserve original release commits and existing version directories; later evidence adds independent paths or commits instead of replacing same-identity archives. Retention cannot guarantee that a provider never deletes objects; maintainers still keep independent copies according to the download instructions.

Software-tool regressions use stdlib unittest and cover dirty/stale source, missing entries, content tampering, directory escape, link members, same-identity conflicts, independent restoration, and actual HTTP success and tamper rejection. New regression executions do not inherit historical test counts. Without physical execution, physical status always remains not_executed.