Close local review loose ends and record blocked workplan gates

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e241-8285-7a63-8c0c-51c9cb824dc3
This commit is contained in:
tegwick 2026-09-27 17:20:43 +02:00
parent bcdb02323c
commit 7295172f1e
20 changed files with 510 additions and 95 deletions

View file

@ -3,14 +3,13 @@
import argparse
import hashlib
import json
from pathlib import Path
from pathlib import Path, PurePosixPath
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):
@ -18,21 +17,46 @@ def require(condition, message):
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 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 build(root=ROOT):
manifest = json.loads((root / BASE / 'manifest.json').read_text())
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(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']
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 = (root / 'canon/vocabulary.yaml').read_text()
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')
@ -40,10 +64,8 @@ def build(root=ROOT):
require(len(classes) == len(classifications) == len(profiles), 'Invalid classification coverage')
records = []
for p in profiles:
raw = locator(root, p['path']).read_bytes()
raw = read_corpus(p['path'])
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')
@ -61,7 +83,7 @@ def build(root=ROOT):
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'])
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)
@ -71,20 +93,20 @@ def build(root=ROOT):
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)
read_package(ref)
for ref in manifest['entrypoints'].values():
locator(root, ref)
read_package(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(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='0.1', status='research-preview', corpus_revision=manifest['corpus_revision'],
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 — 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.', '']
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:
@ -102,9 +124,11 @@ def build(root=ROOT):
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()
for name, content in build().items():
path = ROOT / BASE / name
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:

View file

@ -53,7 +53,10 @@ def validate(data, root=ROOT):
require(not p['review']['independent_human'] or p['review']['role'] == 'human', 'Model review cannot be human validation')
date.fromisoformat(p['review']['assessed_at'])
if p['review']['last_substantive_review_date'] is not None:
date.fromisoformat(p['review']['last_substantive_review_date'])
review = p['review']
require(date.fromisoformat(review['last_substantive_review_date']) <= date.fromisoformat(review['assessed_at']), 'Review date exceeds assessment')
require(review.get('substantive_review_ref') and review.get('substantive_review_scope', '').strip(), 'Substantive review needs provenance and scope')
ref_check(root, review['substantive_review_ref'])
require(p['independent_full_contract_uses'] is None or type(p['independent_full_contract_uses']) is int and p['independent_full_contract_uses'] >= 0, 'Invalid independent-use count')
require(set(p['superseded_by']) <= set(ids) - {p['id']}, 'Unknown/self successor')
for e in p['evidence']:
@ -164,7 +167,7 @@ def render(data, baseline=None):
lines += ['## Coherence and maintainability', '',
'Profile hashes and local references passed validation when this report was generated. That checks traceability, not source truth or external URL freshness.', '',
f'Profiles with unknown last substantive review date: {sum(p["review"]["last_substantive_review_date"] is None for p in profiles)}. '
'Assessment dates record this metadata review, not a new substantive validation. No age-based freshness claim is available for those profiles.', '',
'Assessment dates record this metadata review, not a new substantive validation. Recorded substantive dates identify scoped author-model reviews, not renewed empirical or external-source freshness.', '',
'Coherence metrics (ambiguous selections, unjustified relations and overlap resolved): not measured. Conflict Copy / Explicit Reconciliation overlap remains an explicit open question.', '',
'Review backlog and evidence gaps remain in ' + ', '.join(link(r) for r in context['open_work']) + '.', '',
'## Pattern profiles', '']