P04-01: 同時発生の更新規約
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Read-only P04 evidence preflight. Never grants original acceptance.
|
||||
|
||||
This initial-phase verifier deliberately rejects unsupported semantic evidence:
|
||||
D05 is pending in the dispatch, irrespective of declarations in an index.
|
||||
See execution/p04-01/CHECKER_PROTOCOL.md for the supported input subset.
|
||||
"""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC = ROOT / 'docs/spec/acceptance/P04-01.json'
|
||||
SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881'
|
||||
DOMAINS = {'閉店', '停電', '破壊', '退出', '課金', 'シフト交代', '00:00決算'}
|
||||
|
||||
|
||||
def require(condition, message):
|
||||
if not condition:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def unique(pairs):
|
||||
result = {}
|
||||
for key, value in pairs:
|
||||
require(key not in result, 'duplicate JSON key: ' + key)
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
def load(path):
|
||||
return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=unique,
|
||||
parse_constant=lambda x: require(False, 'nonfinite JSON: ' + x))
|
||||
|
||||
|
||||
def safe(base, relative):
|
||||
require(isinstance(relative, str) and relative, 'invalid path')
|
||||
p = Path(relative)
|
||||
require(not p.is_absolute() and all(x not in {'.', '..'} for x in relative.split('/')),
|
||||
'unsafe path: ' + relative)
|
||||
current = base
|
||||
for part in p.parts:
|
||||
current = current / part
|
||||
require(not current.is_symlink(), 'symlink: ' + relative)
|
||||
require(current.resolve().is_relative_to(base.resolve()) and current.is_file(),
|
||||
'missing/outside file: ' + relative)
|
||||
return current
|
||||
|
||||
|
||||
def pins(mapping, base):
|
||||
require(isinstance(mapping, dict) and mapping, 'missing pin map')
|
||||
for name, digest in mapping.items():
|
||||
require(isinstance(digest, str) and re.fullmatch('[0-9a-f]{64}', digest), 'invalid hash: ' + name)
|
||||
require(sha(safe(base, name)) == digest, 'stale pin: ' + name)
|
||||
|
||||
|
||||
def pointer(value, path):
|
||||
require(isinstance(path, str) and (path == '' or path.startswith('/')), 'invalid JSON pointer')
|
||||
for token in path.split('/')[1:]:
|
||||
require(not re.search('~(?![01])', token), 'invalid pointer escape')
|
||||
token = token.replace('~1', '/').replace('~0', '~')
|
||||
if isinstance(value, list):
|
||||
require(re.fullmatch('0|[1-9][0-9]*', token) is not None, 'invalid array index')
|
||||
value = value[int(token)]
|
||||
else:
|
||||
require(isinstance(value, dict) and token in value, 'missing pointer: ' + path)
|
||||
value = value[token]
|
||||
return value
|
||||
|
||||
|
||||
def literal(a, b):
|
||||
return type(a) is type(b) and json.dumps(a, sort_keys=True) == json.dumps(b, sort_keys=True)
|
||||
|
||||
|
||||
def verify(directory):
|
||||
require(sha(SPEC) == SPEC_HASH, 'dispatch spec pin mismatch')
|
||||
spec = load(SPEC)
|
||||
evidence = directory / 'p04-evidence'
|
||||
require(not evidence.is_symlink(), 'symlink evidence directory')
|
||||
index = load(safe(evidence, 'index.json'))
|
||||
require(type(index.get('schema_version')) is int and index['schema_version'] == 1, 'schema_version')
|
||||
require(index.get('task_id') == 'P04-01' and index.get('formal_acceptance') is False, 'task/acceptance')
|
||||
head = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=ROOT, text=True).strip()
|
||||
require(index.get('candidate_commit') == head, 'candidate_commit mismatch')
|
||||
source = index.get('source_map')
|
||||
pins(source, ROOT)
|
||||
for name, digest in spec['current_source_pins'].items():
|
||||
require(source.get(name) == digest, 'immutable source pin: ' + name)
|
||||
oldmap = load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json')
|
||||
required = {p for p in oldmap if p.endswith('.gd') and p.startswith(('autoload/', 'scripts/'))}
|
||||
require(required <= source.keys(), 'missing runtime/service dependency source pins')
|
||||
environment = index.get('environment')
|
||||
require(isinstance(environment, dict) and isinstance(environment.get('os'), str) and environment['os'], 'environment OS')
|
||||
for kind in ('engine', 'configuration', 'inputs'):
|
||||
pins(environment.get(kind), evidence)
|
||||
artifacts = index.get('artifacts')
|
||||
require(isinstance(artifacts, dict) and artifacts, 'missing artifacts')
|
||||
paths, raw = set(), {}
|
||||
for aid, item in artifacts.items():
|
||||
require(isinstance(aid, str) and aid and isinstance(item, dict), 'invalid artifact')
|
||||
path = safe(evidence, item['path'])
|
||||
require(path not in paths, 'duplicate artifact path')
|
||||
paths.add(path)
|
||||
require(type(item.get('bytes')) is int and item['bytes'] == path.stat().st_size, 'artifact size: ' + aid)
|
||||
require(sha(path) == item.get('sha256'), 'artifact hash: ' + aid)
|
||||
require(item.get('type') in {'observation', 'state', 'receipt', 'stdout', 'native_log', 'save', 'authority'}, 'artifact type')
|
||||
raw[aid] = path
|
||||
authority = index.get('authority')
|
||||
require(isinstance(authority, dict) and authority, 'missing authority')
|
||||
domains = index.get('domains')
|
||||
require(isinstance(domains, dict) and set(domains) == DOMAINS, 'required seven domains')
|
||||
for name, domain in domains.items():
|
||||
require(isinstance(domain, dict), 'invalid domain: ' + name)
|
||||
for field in ('source_refs', 'authority_refs', 'cases', 'invariants'):
|
||||
require(isinstance(domain.get(field), list) and domain[field], name + ': missing ' + field)
|
||||
require(domain.get('applicability') == {'source_map': source, 'environment': environment}, name + ': applicability mismatch')
|
||||
for ref in domain['source_refs']:
|
||||
require(isinstance(ref, dict) and ref.get('path') in source and isinstance(ref.get('function'), str) and ref['function'], 'invalid source ref')
|
||||
require(ref.get('sha256') == source[ref['path']], 'source ref hash')
|
||||
for ref in domain['authority_refs']:
|
||||
require(ref in authority and authority[ref].get('status') in {'original', 'approved'}, 'pending/missing authority')
|
||||
require(authority[ref].get('artifact_id') in raw, 'missing raw authority')
|
||||
case_ids = set()
|
||||
for case in domain['cases']:
|
||||
require(isinstance(case, dict) and isinstance(case.get('id'), str) and case['id'] not in case_ids, 'duplicate/invalid case')
|
||||
case_ids.add(case['id'])
|
||||
for field in ('input', 'before', 'operation', 'after', 'receipt'):
|
||||
require(case.get(field) in raw, name + ': missing case artifact ' + field)
|
||||
receipt = load(raw[case['receipt']])
|
||||
pid = receipt.get('start_pid')
|
||||
require(type(pid) is int and pid > 0 and receipt.get('end_pid') == pid and type(receipt.get('exit_code')) is int and receipt['exit_code'] == 0 and receipt.get('engine_ended') is True, 'unexecuted/incomplete receipt')
|
||||
require(receipt.get('source_map') == source and receipt.get('environment') == environment, 'receipt applicability')
|
||||
for field in ('stdout', 'native_log'):
|
||||
require(receipt.get(field) in raw, 'receipt missing raw ' + field)
|
||||
for pred in domain['invariants']:
|
||||
require(isinstance(pred, dict) and pred.get('artifact_id') in raw, 'missing predicate artifact')
|
||||
require(pred.get('authority_ref') in domain['authority_refs'], 'predicate authority')
|
||||
require(pred.get('raw_origin') == 'engine', 'unsupported derivation/origin')
|
||||
value = pointer(load(raw[pred['artifact_id']]), pred['json_pointer'])
|
||||
op = pred.get('operator')
|
||||
if op in {'same_literal', 'distinct'}:
|
||||
require(pred.get('paired_artifact_id') in raw, 'missing paired artifact')
|
||||
other = pointer(load(raw[pred['paired_artifact_id']]), pred['paired_json_pointer'])
|
||||
result = literal(value, other) if op == 'same_literal' else not literal(value, other)
|
||||
elif op == 'eq':
|
||||
result = literal(value, pred['expected'])
|
||||
elif op == 'nonnegative':
|
||||
result = type(value) in (int, float) and value >= 0
|
||||
elif op == 'count_eq':
|
||||
result = isinstance(value, (list, dict)) and type(pred.get('expected')) is int and len(value) == pred['expected']
|
||||
else:
|
||||
raise ValueError('unsupported predicate: ' + str(op))
|
||||
require(result, name + ': predicate failed')
|
||||
require(index.get('review_status') in {'pending_review', 'approved'}, 'review status')
|
||||
# Raw observation claims cannot establish production provenance, mandatory
|
||||
# scenario coverage or an approval absent from this immutable dispatch.
|
||||
raise ValueError('D05 pending: closure service / final shift interval; semantic coverage and native provenance require independent raw-evidence review. Machine gate unavailable; original acceptance not granted.')
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--verify-only', action='store_true', required=True)
|
||||
parser.parse_args()
|
||||
try:
|
||||
require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required')
|
||||
verify(Path(os.environ['BUILD_OUTPUT_DIR']))
|
||||
except (ValueError, OSError, KeyError, TypeError, IndexError, subprocess.SubprocessError) as exc:
|
||||
print(json.dumps({'task_id': 'P04-01', 'machine_verified': False, 'formal_acceptance': False, 'error': str(exc)}, ensure_ascii=False))
|
||||
return 1
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user