From 92af3811896ae0d6baf5ec0a12b67cf63b01a304 Mon Sep 17 00:00:00 2001 From: nicodes Date: Thu, 29 Oct 2026 17:16:18 -0600 Subject: [PATCH] Keep one reviewed usage README and enforce clean documentation history --- .github/readme-source.json | 9 +++ .github/scripts/check-documentation.py | 87 ++++++++++++++++++++++ .github/workflows/ci.yml | 2 +- .github/workflows/documentation-policy.yml | 23 ++++++ README.md | 10 ++- 5 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 .github/readme-source.json create mode 100644 .github/scripts/check-documentation.py create mode 100644 .github/workflows/documentation-policy.yml diff --git a/.github/readme-source.json b/.github/readme-source.json new file mode 100644 index 0000000..281083d --- /dev/null +++ b/.github/readme-source.json @@ -0,0 +1,9 @@ +{ + "version": 1, + "sha256": "bc5f55da0c2ec3d129b8dd3c59617e0fa24384fc0a98f3dc5b83764bdcfb2a35", + "history": { + "forbidden_commits": [ + "f10334bdecb8570c0fe76db6fedf8b09867ee660" + ] + } +} diff --git a/.github/scripts/check-documentation.py b/.github/scripts/check-documentation.py new file mode 100644 index 0000000..ae3dbd9 --- /dev/null +++ b/.github/scripts/check-documentation.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Check tracked documentation layout and the reviewed usage README digest.""" +import hashlib +import json +import os +from pathlib import Path, PurePosixPath +import subprocess +import sys + +MARKDOWN = {'.md', '.markdown', '.mdown', '.mkd', '.mkdn', '.mdwn', '.mdx', '.rmd', '.qmd'} +OTHER_DOCS = {'.rst', '.adoc', '.asciidoc'} + + +def check(root, include_history=True, history_required=False): + raw = subprocess.check_output(['git', 'ls-files', '--stage', '-z'], cwd=root) + entries = {} + errors = [] + for record in raw.split(b'\0'): + if not record: + continue + metadata, encoded = record.split(b'\t', 1) + mode, _oid, stage = metadata.decode().split() + name = encoded.decode('utf-8', 'surrogateescape') + if stage != '0': + errors.append('unmerged index entry: ' + name) + entries[name] = mode + path = PurePosixPath(name) + if name != 'README.md' and (path.suffix.lower() in MARKDOWN | OTHER_DOCS + or path.name.lower().startswith('readme.')): + errors.append('documentation must live in the private docs repository: ' + name) + if entries.get('README.md') != '100644' and entries.get('README.md') != '100755': + errors.append('a regular, tracked root README.md is required') + else: + readme = root / 'README.md' + if readme.is_symlink() or not readme.is_file(): + errors.append('root README.md must be a regular file') + else: + try: + body = readme.read_bytes() + if not any(line.startswith('# ') for line in body.decode('utf-8').splitlines()): + errors.append('root README.md needs a product heading') + policy_path = root / '.github/readme-source.json' + if policy_path.is_symlink(): + raise ValueError('README source policy cannot be a symlink') + policy = json.loads(policy_path.read_text()) + if policy.get('version') != 1: + raise ValueError('unsupported README source policy') + if hashlib.sha256(body).hexdigest() != policy.get('sha256'): + errors.append('README differs from its reviewed publication; update the private source first') + history = policy.get('history') + if history_required and history is None: + raise ValueError('public repositories require a history boundary') + if include_history and history is not None: + if set(history) != {'forbidden_commits'} or not isinstance(history['forbidden_commits'], list) or not history['forbidden_commits']: + raise ValueError('invalid public history boundary') + forbidden = set(history['forbidden_commits']) + if any(not isinstance(oid, str) or len(oid) != 40 or any(c not in '0123456789abcdef' for c in oid) for oid in forbidden): + raise ValueError('invalid original history identity') + shallow = subprocess.check_output(['git', 'rev-parse', '--is-shallow-repository'], cwd=root, text=True).strip() + if shallow != 'false': + raise ValueError('public history verification requires fetch-depth: 0') + ancestors = set(subprocess.check_output(['git', 'rev-list', 'HEAD'], cwd=root, text=True).splitlines()) + if ancestors & forbidden: + errors.append('original history was reintroduced; transplant changes onto the cleaned main history') + patterns = [':(icase)*'+suffix for suffix in sorted(MARKDOWN)] + past = subprocess.check_output(['git', 'log', '--format=', '--name-only', '-z', 'HEAD', '--', *patterns], cwd=root) + extra = {name.decode('utf-8', 'surrogateescape') for name in past.split(b'\0') if name and name != b'README.md'} + errors.extend('historical Markdown must remain in private docs: '+name for name in sorted(extra)) + + except (OSError, ValueError, UnicodeError) as error: + errors.append('invalid README source policy or README: ' + str(error)) + return errors + + +def main(): + root = Path(subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True).strip()) + errors = check(root, history_required=os.environ.get('REQUIRE_CLEAN_HISTORY') == 'true') + for error in errors: + print(error, file=sys.stderr) + if errors: + return 1 + print('Documentation policy passed: one root README.md, reviewed source digest matches.') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dee5e6e..3d6677e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: persist-credentials: false - uses: jdx/mise-action@8418fedb1110381a0d3ba51a7a415d7c1b884550 # v5.2.2 - name: Validate developer profile and workflow controls - uses: nicodes/tools/workflow-standards@59a948943c2f7bdd782cad48ddfdabb106c80fbd # v0.27.5 + uses: nicodes/tools/workflow-standards@66992c7abdb653dac5f7da4a4ec6b7d83737c26c # v0.27.5 - name: Build uses: ./.github/actions/build - name: Test diff --git a/.github/workflows/documentation-policy.yml b/.github/workflows/documentation-policy.yml new file mode 100644 index 0000000..479ac0b --- /dev/null +++ b/.github/workflows/documentation-policy.yml @@ -0,0 +1,23 @@ +name: Documentation policy +on: + push: + pull_request: + merge_group: + workflow_dispatch: +permissions: + contents: read +jobs: + documentation-policy: + name: Documentation policy + runs-on: ubuntu-24.04 + timeout-minutes: 3 + steps: + - name: Check out source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + persist-credentials: false + fetch-depth: ${{ github.event.repository.private && 1 || 0 }} + - name: Check documentation boundary + env: + REQUIRE_CLEAN_HISTORY: ${{ !github.event.repository.private }} + run: python3 .github/scripts/check-documentation.py diff --git a/README.md b/README.md index c14de83..c2c3c15 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ -# blvr-web + -For current client installation and usage, see https://github.com/kiwidating/blvr-web#readme. +# Blvr website + +Static Astro splash page for [Blvr](https://info.blvr.app). Open the dating app at [blvr.app](https://blvr.app), or [join its waitlist](https://blvr.app/signin/waitlist). + +## Assets + +The decorative portrait is a bundled [Unsplash](https://unsplash.com/) photo also used by the Blvr development app. It illustrates the product, not a real member profile. Fredoka and Nunito are self-hosted under their SIL Open Font Licenses, included in `public/fonts/`.