frontend-patterns/scripts/reference_consume.py
tegwick bd85fcd179 Add verified reference lookup and immutable consumer receipts
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1045b-933d-7aa1-b2db-104557487a79
2026-10-04 09:02:35 +02:00

176 lines
11 KiB
Python

#!/usr/bin/env python3
"""Filter a verified preview and prepare pinned consumer events; no inference."""
import argparse
from datetime import datetime, timezone
import hashlib
import json
import math
from pathlib import Path
import re
from reference_preview import ROOT, build, git_blob, release, require
from classification_coverage import section_values, RESPONSIBILITIES
HOST = 'https://forgejo.coulomb.social/coulomb/frontend-patterns'
SHA = re.compile(r'[0-9a-f]{40}')
DIGEST = re.compile(r'[0-9a-f]{64}')
IDENTITY = re.compile(r'[A-Za-z0-9][A-Za-z0-9:_.-]{0,159}')
def published(root=ROOT, version=None):
base, pin, manifest = release(root, version)
generated = build(root, version)
for name, content in generated.items():
require((root / base / name).read_text() == content, 'Published export changed: ' + name)
return json.loads(generated['index.json']), pin, manifest
def locator(path, revision):
return {'path': path, 'revision': revision,
'url': f'{HOST}/src/commit/{revision}/{path}'}
def lookup(index, pin, manifest, candidates=(), forces=(), responsibilities=(), root=ROOT):
known = {c['id'] for c in index['candidates']}
vocab = git_blob(root, manifest['corpus_revision'], 'canon/vocabulary.yaml').decode()
require(set(candidates) <= known, 'Unknown candidate in selected release')
require(set(forces) <= set(section_values(vocab, 'forces')), 'Unknown force')
require(set(responsibilities) <= set(RESPONSIBILITIES), 'Unknown responsibility')
matches = []
if candidates or forces or responsibilities:
for candidate in index['candidates']:
classification = candidate['classification']
if candidates and candidate['id'] not in candidates:
continue
if not set(forces) <= set(classification['forces']['improves']):
continue
if not set(responsibilities) <= set(classification['responsibilities']):
continue
record = dict(candidate)
record['contract'] = locator(candidate['path'], index['corpus_revision'])
record['evidence'] = [dict(e, locator=locator(e['ref'], pin['packaging_revision']))
for e in candidate['evidence']]
matches.append(record)
ids = {c['id'] for c in matches}
return {
'schema_version': 1,
'status': 'needs-context' if not (candidates or forces or responsibilities) else ('matches' if matches else 'no-match'),
'reference': {'version': index['version'], 'packaging_revision': pin['packaging_revision'],
'manifest_sha256': pin['manifest_sha256'], 'corpus_revision': index['corpus_revision']},
'filters': {'candidates': list(candidates), 'improving_forces': list(forces), 'responsibilities': list(responsibilities)},
'candidates': matches,
'relations': [e for e in index['relations'] if e['source'] in ids or e['target'] in ids],
'entrypoints': {k: locator(v, pin['packaging_revision']) for k, v in index['entrypoints'].items()},
'limits': ['Metadata filtering is not applicability, ranking or compatible-configuration proof.',
'Read full pinned contracts, force costs, alternatives and gaps before selecting.',
'Relations are conditional proposals; no inverse, transitive or automatic selection.',
'No match means no record satisfies these filters, not that no architecture exists.',
'This preview contains hypotheses; independent usefulness remains unverified.'],
}
def text_ok(value, limit=1000):
return isinstance(value, str) and 0 < len(value.strip()) <= limit
def receipt(source, index, pin, now=None):
require(isinstance(source, dict), 'Decision input must be an object')
fields = {'use_id', 'event_id', 'recorded_at', 'kind', 'consumer', 'context', 'decision', 'outcome'}
require(fields <= source.keys() <= fields | {'previous_event_sha256'}, 'Decision input fields mismatch')
event = json.loads(json.dumps(source))
event.update(schema_version=1, reference={
'version': index['version'], 'packaging_revision': pin['packaging_revision'],
'manifest_sha256': pin['manifest_sha256']})
for key in ('use_id', 'event_id'):
require(isinstance(event[key], str) and IDENTITY.fullmatch(event[key]), 'Invalid event identity')
require(event['kind'] in {'real', 'synthetic'}, 'Explicit real/synthetic kind required')
previous = event.setdefault('previous_event_sha256', None)
require(previous is None or isinstance(previous, str) and DIGEST.fullmatch(previous), 'Invalid predecessor digest')
require(isinstance(event['recorded_at'], str), 'Timestamp required')
stamp = datetime.fromisoformat(event['recorded_at'].replace('Z', '+00:00'))
require(stamp.tzinfo is not None and stamp <= (now or datetime.now(timezone.utc)), 'Invalid/future timestamp')
consumer = event['consumer']
require(isinstance(consumer, dict) and set(consumer) == {'repo', 'revision', 'task_ref', 'producer', 'run_ref'}, 'Consumer fields mismatch')
require(isinstance(consumer['repo'], str) and re.fullmatch(r'[a-z0-9][a-z0-9-]{0,79}', consumer['repo']), 'Invalid consumer repo')
require(isinstance(consumer['revision'], str) and SHA.fullmatch(consumer['revision']), 'Full consumer revision required')
require(all(text_ok(consumer[k]) for k in ('task_ref', 'producer', 'run_ref')), 'Missing consumer provenance')
context = event['context']
require(isinstance(context, dict) and set(context) == {'problem', 'constraints', 'considered', 'selected'}, 'Context fields mismatch')
require(text_ok(context['problem']), 'Problem required')
require(isinstance(context['constraints'], list) and all(text_ok(x, 500) for x in context['constraints']), 'Invalid constraints')
known = {c['id'] for c in index['candidates']}
for key in ('considered', 'selected'):
values = context[key]
require(isinstance(values, list) and all(isinstance(v, str) for v in values), 'Invalid candidate list')
require(len(values) == len(set(values)) and set(values) <= known, 'Unknown/duplicate candidate in release')
require(set(context['selected']) <= set(context['considered']), 'Unconsidered selection')
decision = event['decision']
require(isinstance(decision, dict) and set(decision) == {'action', 'reason', 'artifact_ref'}, 'Decision fields mismatch')
require(decision['action'] in {'applied', 'adapted', 'rejected', 'no-match'}, 'Invalid decision action')
require(bool(context['selected']) == (decision['action'] in {'applied', 'adapted'}), 'Action/selection mismatch')
require(text_ok(decision['reason']), 'Decision reason required')
require(decision['artifact_ref'] is None or text_ok(decision['artifact_ref']), 'Invalid artifact reference')
outcome = event['outcome']
defaults = {'value': 'unknown', 'basis': 'consumer-report', 'stage': 'decision',
'observation': None, 'evidence_refs': [], 'next_observation': None,
'measured_decision_minutes': None, 'measured_rework_minutes': None, 'comparison_ref': None}
require(isinstance(outcome, dict) and 'expectation' in outcome and set(outcome) <= set(defaults) | {'expectation'}, 'Outcome fields mismatch')
event['outcome'] = outcome = dict(defaults, **outcome)
require(text_ok(outcome['expectation']), 'Observable expectation required')
require(outcome['value'] in {'helpful', 'harmful', 'mixed', 'no-effect', 'unknown'}, 'Invalid outcome')
require(outcome['basis'] in {'consumer-report', 'model-assessment', 'observed-result'}, 'Invalid outcome basis')
require(outcome['stage'] in {'decision', 'implementation', 'test', 'operation'}, 'Invalid outcome stage')
refs = outcome['evidence_refs']
require(isinstance(refs, list) and len(refs) <= 10 and all(text_ok(v) for v in refs), 'Invalid outcome evidence')
require(outcome['observation'] is None or text_ok(outcome['observation']), 'Invalid observation')
require(outcome['value'] == 'unknown' or refs and text_ok(outcome['observation']), 'Observed outcome needs evidence')
for key in ('next_observation', 'comparison_ref'):
require(outcome[key] is None or text_ok(outcome[key]), 'Invalid outcome reference')
for key in ('measured_decision_minutes', 'measured_rework_minutes'):
value = outcome[key]
require(value is None or type(value) in (int, float) and math.isfinite(value) and value >= 0, 'Invalid measured effort')
return event
def write_receipt(event, relative, consumer_root=None):
require(isinstance(relative, str) and 0 < len(relative) <= 240
and re.fullmatch(r'[A-Za-z0-9_./-]+', relative)
and all(part not in {'', '.', '..'} for part in relative.split('/')), 'Unsafe receipt path')
root = (consumer_root or Path.cwd()).resolve()
path = root / relative
require(path.resolve().is_relative_to(root), 'Receipt escapes consumer root')
content = (json.dumps(event, indent=2, allow_nan=False) + '\n').encode()
require(len(content) <= 131072, 'Receipt exceeds collector size limit')
path.parent.mkdir(parents=True, exist_ok=True)
with path.open('xb') as stream:
stream.write(content)
return {'path': relative, 'sha256': hashlib.sha256(content).hexdigest()}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--version', help='Published version; defaults to current pointer')
commands = parser.add_subparsers(dest='command', required=True)
query = commands.add_parser('lookup', help='Intersect force/responsibility filters; candidate IDs form an explicit allowlist')
query.add_argument('--candidate', action='append', default=[])
query.add_argument('--force', action='append', default=[])
query.add_argument('--responsibility', action='append', default=[])
prepare = commands.add_parser('receipt', help='Prepare a consumer-authored event with verified release pins')
prepare.add_argument('--input', required=True, type=Path, help='Decision/provenance JSON; see feedback/decision-input.example.json')
prepare.add_argument('--output', help='New source-relative receipt path under current consumer directory; never overwrite')
args = parser.parse_args()
try:
index, pin, manifest = published(version=args.version)
if args.command == 'lookup':
result = lookup(index, pin, manifest, args.candidate, args.force, args.responsibility)
else:
require(args.input.stat().st_size <= 131072, 'Decision input too large')
event = receipt(json.loads(args.input.read_text()), index, pin)
result = write_receipt(event, args.output) if args.output else event
print(json.dumps(result, indent=2, allow_nan=False))
except (ValueError, TypeError, KeyError, OSError) as exc:
parser.exit(1, f'Reference consumption failed: {exc}\n')
if __name__ == '__main__':
main()