Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a087be-d842-7252-81be-2105c4279c87
60 lines
2.9 KiB
Python
60 lines
2.9 KiB
Python
"""Check document references, work-record identity, and immutable source provenance."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--require-sources', action='store_true')
|
|
args = parser.parse_args()
|
|
errors = []
|
|
for path in ROOT.rglob('*.md'):
|
|
if '.git' in path.parts:
|
|
continue
|
|
for link in re.findall(r'\[[^\]]*\]\(([^)]+)\)', path.read_text()):
|
|
if '://' in link or link.startswith('#'):
|
|
continue
|
|
target = link.split('#')[0]
|
|
if target and not (path.parent / target).exists():
|
|
errors.append(f'{path.relative_to(ROOT)}: missing link {target}')
|
|
ids = set()
|
|
for path in sorted((ROOT / 'workplans').glob('*.md')):
|
|
text = path.read_text()
|
|
for identifier in re.findall(r'^id: (INTERFACE-WP-[^\s]+)$', text, re.M):
|
|
if identifier in ids:
|
|
errors.append(f'duplicate work-record id: {identifier}')
|
|
ids.add(identifier)
|
|
for status in re.findall(r'^status: (\S+)$', text, re.M):
|
|
if status not in {'proposed', 'ready', 'active', 'blocked', 'backlog', 'finished', 'archived', 'wait', 'todo', 'progress', 'done', 'cancel'}:
|
|
errors.append(f'{path.name}: invalid status {status}')
|
|
for path in [ROOT / 'canon.yaml', ROOT / 'infospace/interfaces/federation.yaml']:
|
|
for target in re.findall(r'^\s*(?:path|manifest): (\S+)$', path.read_text(), re.M):
|
|
if target == 'infospace/schemas/interface-card.schema.yaml':
|
|
continue # Explicitly owned upstream.
|
|
if not (ROOT / target).is_file():
|
|
errors.append(f'{path.name}: missing artifact {target}')
|
|
for path in sorted((ROOT / 'infospace').rglob('*.json')):
|
|
manifest = json.loads(path.read_text())
|
|
revision = manifest['source_commit']
|
|
if not re.fullmatch('[0-9a-f]{40}', revision):
|
|
errors.append(f'{path}: invalid source commit')
|
|
source = ROOT.parent / manifest['source_repository']
|
|
records = manifest.get('sources', manifest.get('imports', []))
|
|
if not records:
|
|
errors.append(f'{path}: empty source manifest')
|
|
for record in records:
|
|
if not re.fullmatch('[0-9a-f]{64}', record['sha256']):
|
|
errors.append(f'{path}: invalid SHA-256')
|
|
if not (source / '.git').exists():
|
|
if args.require_sources:
|
|
errors.append(f'missing source checkout: {source}')
|
|
continue
|
|
result = subprocess.run(['git', '-C', str(source), 'show', f'{revision}:{record["path"]}'], capture_output=True)
|
|
if result.returncode or hashlib.sha256(result.stdout).hexdigest() != record['sha256']:
|
|
errors.append(f'{path.name}: source integrity failure for {record["path"]}')
|
|
if errors:
|
|
raise SystemExit('\n'.join(errors))
|
|
print(f'PASS: document links, artifact paths, {len(ids)} work-record IDs, and available pinned Git sources')
|