Consumer contract¶
Content-preserving migration baseline¶
The committed Guide, Blog, Reference, and theme files began as a content-preserving snapshot of the public documentation tree. docs/migration-baseline.json records each historical source path, byte length, and SHA-256 digest. scripts/verify_migration_baseline.py always protects authored Guide/Blog and theme bytes. It protects the historical Reference bytes while docs-release.lock.json awaits its first release, then retains that manifest as history while the verified release owns current docs/reference.
The consumer never reads /root/spexcode/.spec, any product checkout, or a documentation-server working directory. It never invokes a product generator. A product release is the only way to replace current Reference content.
Unified immutable release¶
One GitHub release contains exactly three assets:
| Asset | Role |
|---|---|
docs-release.json |
The spexcode.docs-release/v1 envelope. |
guidance-catalog.json |
The exact spexcode.guidance-catalog/v1 payload. |
reference-snapshot.json |
The exact spexcode.reference-snapshot/v1 payload. |
The manifest declares one producer repository, release tag, and revision plus catalog and Reference declarations. Each declaration contains its schema, name, byte length, lowercase SHA-256 digest, and GitHub release-asset retrieval identity. The consumer pins manifest and payload asset IDs/URLs from release metadata, then requires every identity, schema, byte count, digest, source revision, and canonical payload bundle hash to agree. The old spexcode.guidance-release/v1 envelope and guidance.lock.json are rejected loudly; Guidance cannot advance without Reference.
docs-release.lock.json records the whole verified tuple. The committed release/ directory retains the exact downloaded manifest and both payloads, so verification and rendering need no network or product checkout. A matching tuple is a no-op. A malformed candidate, network failure, or verification disagreement exits non-zero and leaves the committed tuple unchanged.
The catalog renderer escapes only its surrounding HTML representation, preserving prompt bytes. The Reference payload is self-contained: each page has rendered bytes and provenance, and its navigation must exactly derive from the same page list. Applying a ready tuple replaces only docs/reference, the marked Reference block in mkdocs.yml, raw release assets, the unified lock, and generated Guidance. Authored Guide/Blog pages and theme assets are outside that write set.
Review and deployment¶
The update workflow serializes discovery, verification, rendering, validation, and review proposal under one repository lock. It prepares automation/docs-release; it does not merge or deploy documentation. Reviewers see the rendered diff and locked producer tuple in the normal pull-request path.
Deployment remains separate. PUBLISH-BLOCKED is an unconditional local stop, and the deploy job remains skipped unless repository variable SPEXCODE_DOCS_PUBLISH_ENABLED is exactly true. Its transport checks the local marker, checks /root/spexcode-docs/PUBLISH-BLOCKED before creating a remote staging directory or uploading bytes, and checks it again before atomically replacing only the served site symlink. The unified consumer never removes, bypasses, or interprets that marker.