#!/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 import re import subprocess from classification_coverage import section_values, RESPONSIBILITIES ROOT = Path(__file__).resolve().parents[1] BASE = 'reference/0.1/' def require(condition, message): if not condition: raise ValueError(message) def locator(root, value): require(isinstance(value, str) and bool(value), 'Empty reference') path = (root / value).resolve() require(path.is_relative_to(root.resolve()) and path.is_file(), 'Invalid reference: ' + value) return path def build(root=ROOT): manifest = json.loads((root / BASE / 'manifest.json').read_text()) for path, digest in manifest['inputs'].items(): require(hashlib.sha256(locator(root, path).read_bytes()).hexdigest() == digest, 'Stale input: ' + path) profiles = json.loads((root / 'research/evolution/profiles.json').read_text())['profiles'] classifications = json.loads((root / 'research/catalog/ClassificationCoverage.json').read_text())['candidates'] classes = {r['path']: r for r in classifications} vocab = (root / 'canon/vocabulary.yaml').read_text() 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 = locator(root, p['path']).read_bytes() require(hashlib.sha256(raw).hexdigest() == p['sha256'], 'Stale candidate: ' + p['id']) pinned = subprocess.check_output(['git', 'show', manifest['corpus_revision'] + ':' + p['path']], cwd=root) require(raw == pinned, 'Candidate differs from pinned revision: ' + 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']: locator(root, 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']: locator(root, ref) for ref in manifest['entrypoints'].values(): locator(root, ref) for task in manifest['open_work']: require(task['id'] in locator(root, task['path']).read_text(), 'Missing residual task') cases = json.loads((root / BASE / 'development-cases.json').read_text())['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='0.1', 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 — 0.1', '', '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') args = parser.parse_args() for name, content in build().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()