fix(docs): let a release without licence declarations still build

The documentation is built per release tag from that tag's own source tree, while the generators come from the current image. The licence generator imports utils.licensing, which a release predating that feature does not carry, so the build of 'latest' died with ModuleNotFoundError, never finished, and the server answered 500 to /latest/. Nine deploy jobs failed on Playwright specs that only ever saw that 500; the cause is in the rescue bundle's containers/docs.log, not in the job log.

The import is guarded now and the page falls back to a stub carrying the same _generated-licensing label, because the table of contents references it for every version and writing nothing would only move the failure into Sphinx. COLUMNS is computed conditionally for the same reason: it is built from the rights the absent module defines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-10-21 12:17:56 +02:00
1 parent 5faa4d1aec
commit 6f7dda1a9f
1 file changed
+32 -7
@@ -13,16 +13,36 @@ from __future__ import annotations
import argparse
from pathlib import Path
from utils.licensing.contexts import CONTEXTS, UNKNOWN
from utils.licensing.rights import DISCLAIMERS, DUTIES, GRANTS, NOT_GRANTED
from utils.licensing.roles import Declared, declared, deploys_nothing
from utils.roles.required_by_coverage import role_is_invokable
try:
from utils.licensing.contexts import CONTEXTS, UNKNOWN
from utils.licensing.rights import DISCLAIMERS, DUTIES, GRANTS, NOT_GRANTED
from utils.licensing.roles import Declared, declared, deploys_nothing
from utils.roles.required_by_coverage import role_is_invokable
except ImportError:
DECLARED_LICENCES = False
else:
DECLARED_LICENCES = True
COLUMNS: tuple[tuple[str, str], ...] = tuple(
(field, field.replace("_", " ").capitalize())
for field in GRANTS + DUTIES + DISCLAIMERS + NOT_GRANTED
COLUMNS: tuple[tuple[str, str], ...] = (
tuple(
(field, field.replace("_", " ").capitalize())
for field in GRANTS + DUTIES + DISCLAIMERS + NOT_GRANTED
)
if DECLARED_LICENCES
else ()
)
ABSENT = """\
.. _generated-licensing:
Licence contexts
================
This release predates the per-role licence declarations, so there is nothing
to tabulate for it. The page exists because the table of contents references
it for every version; newer releases carry the matrix.
"""
HEADER = """\
.. _generated-licensing:
@@ -180,6 +200,11 @@ def main(argv=None):
parser.add_argument("--roles-dir", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args(argv)
if not DECLARED_LICENCES:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(ABSENT, encoding="utf-8")
print(f"licensing: release without declarations -> {args.output}")
return 0
count = generate(args.roles_dir, args.output)
print(f"licensing: {count} roles -> {args.output}")
return 0