frontend-patterns/scripts/reference_preview.py
tegwick 7295172f1e Close local review loose ends and record blocked workplan gates
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e241-8285-7a63-8c0c-51c9cb824dc3
2026-09-27 17:20:43 +02:00

140 lines
8.9 KiB
Python

#!/usr/bin/env python3
"""Build or check the pinned 0.1 candidate reference (not a canonical graph)."""
import argparse
import hashlib
import json
from pathlib import Path, PurePosixPath
import re
import subprocess
from classification_coverage import section_values, RESPONSIBILITIES
ROOT = Path(__file__).resolve().parents[1]
def require(condition, message):
if not condition:
raise ValueError(message)
def git_blob(root, revision, path):
require(isinstance(revision, str) and re.fullmatch(r'[0-9a-f]{40}', revision), 'Full pinned revision required')
require(isinstance(path, str) and path and not PurePosixPath(path).is_absolute()
and all(part not in {'.', '..', ''} for part in path.split('/'))
and '\\' not in path and ':' not in path, 'Invalid reference: ' + str(path))
try:
return subprocess.check_output(['git', 'show', revision + ':' + path], cwd=root, stderr=subprocess.DEVNULL)
except subprocess.CalledProcessError as exc:
raise ValueError('Missing pinned reference: ' + path) from exc
def release(root, version=None):
registry = json.loads((root / 'reference/releases.json').read_text())
require(registry.get('schema_version') == 1, 'Unsupported release registry')
version = version or registry['current']
require(isinstance(version, str) and re.fullmatch(r'[0-9]+\.[0-9]+', version), 'Invalid reference version')
require(version in registry['releases'], 'Unknown reference version')
pin = registry['releases'][version]
base = 'reference/' + version + '/'
manifest_bytes = git_blob(root, pin['packaging_revision'], base + 'manifest.json')
require(hashlib.sha256(manifest_bytes).hexdigest() == pin['manifest_sha256'], 'Release manifest digest mismatch')
require((root / base / 'manifest.json').read_bytes() == manifest_bytes, 'Published manifest changed')
for name in ('development-cases.json', 'README.md', 'ReleaseNotes.md', 'ReleaseReview.md'):
require((root / base / name).read_bytes() == git_blob(root, pin['packaging_revision'], base + name), 'Published artifact changed: ' + name)
manifest = json.loads(manifest_bytes)
require(manifest['version'] == version, 'Release version mismatch')
return base, pin, manifest
def build(root=ROOT, version=None):
base, pin, manifest = release(root, version)
corpus = manifest['corpus_revision']
read_corpus = lambda path: git_blob(root, corpus, path)
read_package = lambda path: git_blob(root, pin['packaging_revision'], path)
for path, digest in manifest['inputs'].items():
require(hashlib.sha256(read_corpus(path)).hexdigest() == digest, 'Stale pinned input: ' + path)
profiles = json.loads(read_corpus('research/evolution/profiles.json'))['profiles']
classifications = json.loads(read_corpus('research/catalog/ClassificationCoverage.json'))['candidates']
classes = {r['path']: r for r in classifications}
vocab = read_corpus('canon/vocabulary.yaml').decode()
ids = [p['id'] for p in profiles]
selected = manifest['core'] + manifest['extensions']
require(len(ids) == len(set(ids)) and all(re.fullmatch(r'CAND-\d{4}', x) for x in ids), 'Invalid candidate identities')
require(len(selected) == len(set(selected)) and set(selected) == set(ids), 'Invalid manifest selection')
require(len(classes) == len(classifications) == len(profiles), 'Invalid classification coverage')
records = []
for p in profiles:
raw = read_corpus(p['path'])
require(hashlib.sha256(raw).hexdigest() == p['sha256'], 'Stale candidate: ' + p['id'])
front = raw.decode().split('---')[1]
c = classes[p['path']]
require(c['sha256'] == p['sha256'] and c['title'] == p['title'], 'Stale classification')
require(p['status'] == 'draft' and p['maturity'] == 'hypothesis', 'Re-review preview maturity')
require(p['gaps'] and p['decision'].strip() and c['rationale'].strip(), 'Missing qualification')
record = dict(p, subset='core' if p['id'] in manifest['core'] else 'extension', classification=c,
classification_status='provisional author-model interpretation')
for key in ('families', 'scales'):
match = re.search(r'^' + key + r': \[(.+)\]$', front, re.M)
require(match is not None, 'Missing ' + key)
labels = [s.strip() for s in match[1].split(',')]
require(len(set(labels)) == len(labels) and set(labels) <= set(section_values(vocab, key)), 'Invalid ' + key)
record[key] = labels
require(set(c['responsibilities']) <= set(RESPONSIBILITIES), 'Invalid responsibility')
for labels in c['forces'].values():
require(set(labels) <= set(section_values(vocab, 'forces')), 'Invalid force')
for e in p['evidence']:
read_package(e['ref'])
require(e['kind'] and e['scope'], 'Missing evidence qualification')
require(p['evidence'] or p['independent_full_contract_uses'] is None, 'Missing evidence/unknown')
records.append(record)
for edge in manifest['relations']:
require(edge['source'] in ids and edge['target'] in ids, 'Invalid relation endpoint')
require(edge['type'] in section_values(vocab, 'pattern_relations'), 'Invalid relation type')
require(edge['status'] == 'proposed' and all(edge[k].strip() for k in ('condition', 'rationale', 'unknown', 'evidence_kind')), 'Unqualified relation')
require(edge['evidence_refs'], 'Missing relation evidence')
for ref in edge['evidence_refs']:
read_package(ref)
for ref in manifest['entrypoints'].values():
read_package(ref)
for task in manifest['open_work']:
require(task['id'] in read_package(task['path']).decode(), 'Missing residual task')
cases = json.loads(read_package(base + 'development-cases.json'))['cases']
require(len(cases) >= 6 and len({c['id'] for c in cases}) == len(cases), 'Invalid development cases')
for case in cases:
require(case['entrypoint'] in manifest['entrypoints'] and set(case['expected_candidates']) <= set(ids), 'Invalid development reference')
require(all(case[k].strip() for k in ('query', 'reviewed_answer', 'prohibited_claim', 'boundary')), 'Incomplete development case')
index = dict(schema_version=1, version=manifest['version'], status='research-preview', corpus_revision=manifest['corpus_revision'],
candidates=records, relations=manifest['relations'], entrypoints=manifest['entrypoints'],
relation_semantics='Conditional proposals only; no inverse or transitive inference.')
catalog = ['# Candidate reference — ' + manifest['version'], '', 'All records are draft hypotheses. Classification is provisional; linked candidate prose contains context, alternatives and consequences. Evidence locators describe partial support, not full-contract recurrence.', '']
for subset in ('core', 'extensions'):
catalog += ['## ' + subset.title(), '', '| Candidate / decision | Families; scales | Evidence limits |', '| --- | --- | --- |']
for p in records:
if p['id'] not in manifest[subset]:
continue
catalog.append(f"| [{p['id']} — {p['title']}](../../{p['path']}) — {p['decision']} | {', '.join(p['families'])}; {', '.join(p['scales'])} | {' '.join(p['gaps'])} |")
catalog += ['']
catalog += ['Extensions are optional research leads. VC-03/06 remain provisional; other planned cases are unsupported. Follow the [starting guide](../../START.md) for the two core contexts.', '']
checks = ['# Disclosed development navigation checks', '', 'Author-model review; scenarios and answers were authored together. These checks validate navigation and stated boundaries, not measured retrieval or independent usefulness.', '']
for c in cases:
checks += ['## ' + c['id'] + ' — ' + c['query'], '', '[' + c['entrypoint'] + '](../../' + manifest['entrypoints'][c['entrypoint']] + ') → ' + (', '.join(c['expected_candidates']) or 'clarify / abstain') + '.', '', c['reviewed_answer'], '', 'Boundary: ' + c['boundary'], '', 'Reject: ' + c['prohibited_claim'], '', 'Editorial disposition: ' + c['review_result'] + '.', '']
return {'index.json': json.dumps(index, indent=2) + '\n', 'Catalog.md': '\n'.join(catalog), 'DevelopmentChecks.md': '\n'.join(checks)}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--check', action='store_true')
parser.add_argument('--version', help='Immutable published version; default is reference/releases.json current')
args = parser.parse_args()
base, _, _ = release(ROOT, args.version)
for name, content in build(version=args.version).items():
path = ROOT / base / name
if args.check:
require(path.is_file() and path.read_text() == content, 'Stale export: ' + name)
else:
path.write_text(content)
print('Preview identities, pinned candidates, vocabulary, references and development cases verified.')
if __name__ == '__main__':
main()