Repository navigation
docs: verify bounded workflow adoption planning #1289
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json | |
| # yamllint disable rule:line-length rule:truthy | |
| name: Docs Review | |
| on: | |
| pull_request: | |
| branches: [main, dev] | |
| push: | |
| branches: [main, dev] | |
| paths: | |
| - "**/*.md" | |
| - "**/*.mdc" | |
| - ".github/**" | |
| - "docs/**" | |
| - "resources/**" | |
| - "src/specfact_cli/resources/**" | |
| - "docs/.doc-frontmatter-enforced" | |
| - "tests/unit/docs/**" | |
| - "tests/unit/workflows/test_docs_module_fixture.py" | |
| - "tests/unit/workflows/test_docs_module_authentication.py" | |
| - "tests/unit/scripts/test_doc_frontmatter/**" | |
| - "tests/integration/scripts/test_doc_frontmatter/**" | |
| - "tests/helpers/doc_frontmatter.py" | |
| - "tests/helpers/doc_frontmatter_fixtures.py" | |
| - "tests/helpers/doc_frontmatter_types.py" | |
| - "scripts/check-docs-commands.py" | |
| - "scripts/check-command-contract.py" | |
| - "scripts/check-documentation-accountability.py" | |
| - "scripts/generate-command-overview.py" | |
| - "scripts/verify_docs_module_source.py" | |
| - "docs/reference/commands.generated.*" | |
| - "llms.txt" | |
| - "scripts/check-cross-site-links.py" | |
| - "scripts/check_doc_frontmatter.py" | |
| - "scripts/validate_agent_rule_applies_when.py" | |
| - "scripts/check_version_sources.py" | |
| - "docs/agent-rules/INDEX.md" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| - "requirements/ci/locked.txt" | |
| - "ci/docs-module-fixture.lock.json" | |
| - ".github/actions/setup-frozen-python/**" | |
| - ".github/workflows/docs-review.yml" | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| docs-review: | |
| name: Docs Review | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 | |
| with: | |
| persist-credentials: false | |
| - name: Read immutable module fixture | |
| id: modules-fixture | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| repository="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["repository"])')" | |
| commit="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["commit"])')" | |
| [[ "$repository" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]] | |
| test "$repository" = "nold-ai/specfact-cli-modules" | |
| tree="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["tree"])')" | |
| [[ "$commit" =~ ^[0-9a-f]{40}$ ]] | |
| [[ "$tree" =~ ^[0-9a-f]{40}$ ]] | |
| printf 'repository=%s\n' "$repository" >> "$GITHUB_OUTPUT" | |
| printf 'commit=%s\n' "$commit" >> "$GITHUB_OUTPUT" | |
| - name: Checkout module command sources | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 | |
| with: | |
| repository: ${{ steps.modules-fixture.outputs.repository }} | |
| path: specfact-cli-modules | |
| ref: ${{ steps.modules-fixture.outputs.commit }} | |
| persist-credentials: false | |
| - name: Verify immutable module fixture | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| expected_repository="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["repository"])')" | |
| expected="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["commit"])')" | |
| test "$expected_repository" = "${{ steps.modules-fixture.outputs.repository }}" | |
| actual="$(git -C specfact-cli-modules rev-parse HEAD)" | |
| test "$actual" = "$expected" | |
| expected_tree="$(python -c 'import json; print(json.load(open("ci/docs-module-fixture.lock.json", encoding="utf-8"))["tree"])')" | |
| actual_tree="$(git -C specfact-cli-modules rev-parse 'HEAD^{tree}')" | |
| test "$actual_tree" = "$expected_tree" | |
| test -z "$(git -C specfact-cli-modules status --porcelain --untracked-files=all)" | |
| - name: Set up Python 3.12 | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 | |
| with: | |
| python-version: "3.12" | |
| - name: Set up frozen documentation dependencies | |
| uses: ./.github/actions/setup-frozen-python | |
| - name: Checkout trusted module verifier | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 | |
| with: | |
| ref: ${{ github.event.pull_request.base.sha || github.sha }} | |
| path: trusted-core-docs | |
| persist-credentials: false | |
| - name: Authenticate module command sources | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| python -I - <<'PY' | |
| import importlib.util | |
| from pathlib import Path | |
| modules = Path("specfact-cli-modules").resolve(strict=True) | |
| trusted = Path("trusted-core-docs").resolve(strict=True) | |
| verifier_path = (trusted / "scripts/verify-modules-signature.py").resolve(strict=True) | |
| key_path = (trusted / "resources/keys/module-signing-public.pem").resolve(strict=True) | |
| for path in (verifier_path, key_path): | |
| if not path.is_relative_to(trusted) or path.is_relative_to(modules): | |
| raise ValueError("Authentication inputs must come from the separate base checkout") | |
| spec = importlib.util.spec_from_file_location("trusted_docs_module_verifier", verifier_path) | |
| if spec is None or spec.loader is None: | |
| raise ValueError("Cannot load the base module verifier") | |
| verifier = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(verifier) | |
| required = {f"specfact-{name}" for name in ( | |
| "backlog", "codebase", "code-review", "govern", "project", "requirements", "spec" | |
| )} | |
| bundles = {source.parent.name: source.parent for source in (modules / "packages").glob("*/src") | |
| if source.is_dir()} | |
| if missing := required - bundles.keys(): | |
| raise ValueError(f"Missing required documentation bundles: {', '.join(sorted(missing))}") | |
| public_key = key_path.read_text(encoding="utf-8") | |
| for name, bundle in sorted(bundles.items()): | |
| manifest = bundle / "module-package.yaml" | |
| if not manifest.is_file(): | |
| raise ValueError(f"Missing documentation module manifest: {name}") | |
| verifier.verify_manifest( | |
| manifest, require_signature=True, public_key_pem=public_key, | |
| payload_from_filesystem=True, verify_checksum=True, | |
| ) | |
| print(f"Authenticated {len(bundles)} documentation bundles using the base verifier.") | |
| PY | |
| - name: Export module command source path | |
| run: | | |
| echo "SPECFACT_MODULES_REPO=${GITHUB_WORKSPACE}/specfact-cli-modules" >> "$GITHUB_ENV" | |
| echo "SPECFACT_DOCS_MODULES_REPO=${GITHUB_WORKSPACE}/specfact-cli-modules" >> "$GITHUB_ENV" | |
| - name: Validate docs dependency security floor | |
| run: python -m pytest tests/unit/docs/test_docs_validation_scripts.py::test_docs_json_gem_uses_the_patched_security_floor -q | |
| - name: Validate documentation fixture and Code Review command parity | |
| run: | | |
| test -n "${SPECFACT_DOCS_MODULES_REPO:?Documentation fixture is required}" | |
| python -m pytest \ | |
| tests/unit/docs/test_code_review_command_parity.py \ | |
| tests/unit/docs/test_llms_overview_freshness.py \ | |
| tests/unit/workflows/test_docs_module_fixture.py \ | |
| tests/unit/workflows/test_docs_module_authentication.py -q | |
| - name: Validate docs command examples | |
| run: python scripts/check-docs-commands.py | |
| - name: Validate generated command overview | |
| run: python scripts/generate-command-overview.py --check | |
| - name: Validate generated command contract | |
| run: python scripts/check-command-contract.py | |
| - name: Validate documentation accountability | |
| run: python scripts/check-documentation-accountability.py | |
| - name: Cross-site links (warn-only; live site may lag deploys) | |
| continue-on-error: true | |
| run: python scripts/check-cross-site-links.py --warn-only | |
| - name: Validate documentation frontmatter (enforced paths; aligns with local docs-validate / pre-commit) | |
| run: python scripts/check_doc_frontmatter.py | |
| - name: Validate agent-rules applies_when canonical task signals | |
| run: python scripts/validate_agent_rule_applies_when.py | |
| - name: Run docs review test suites | |
| run: | | |
| mkdir -p logs/docs-review | |
| DOCS_REVIEW_LOG="logs/docs-review/docs-review_$(date -u +%Y%m%d_%H%M%S).log" | |
| python -m pytest tests/unit/docs/ tests/unit/scripts/test_doc_frontmatter/ tests/integration/scripts/test_doc_frontmatter/ -q 2>&1 | tee "$DOCS_REVIEW_LOG" | |
| exit "${PIPESTATUS[0]:-$?}" | |
| - name: Upload docs review logs | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: docs-review-logs | |
| path: logs/docs-review/ | |
| if-no-files-found: ignore |