P04-01: 同時発生の更新規約

This commit is contained in:
Agent Control Runner
2026-10-16 06:12:33 +09:00
parent accd07c91d
commit b543c26c95
19 changed files with 3076 additions and 24 deletions
+316 -18
View File
@@ -15,6 +15,7 @@ import subprocess
import sys
import tempfile
sys.dont_write_bytecode = True
ROOT = Path(__file__).resolve().parents[1]
SPEC = ROOT / 'docs/spec/acceptance/P04-01.json'
SPEC_HASH = '9ec722e7dfb0bee178d291624a026420c08412574e238bba1fde012c7a6f7881'
@@ -24,7 +25,8 @@ REGISTERED_COMMIT = 'deb1bcb2374eb378b840ca345db952bd8572cb6e'
EXECUTION_ORIGIN = '9767b0a28ca8d252520307dbbac60a1d8cfb87ab'
CHECKER_PATH = 'tools/check_p04_order_evidence.py'
DOC_SCOPE = 'docs/plans/original-completion-2026-10-02/execution/p04-01/'
MAIN_ANCHOR = '9694f50ee63750192376ca69bc7dda3f884784c5'
MAIN_ANCHOR = '9b1a18c1eb25ca1300dcd59da78423c0dda2f498'
TESTED_PRODUCT = '5f6bc11d6bb6e92d22d787989b219fef3a435bc1'
SELECTION_SHA = '320ca668732b4ff72fc4449998af2327d4e5c527b06896740806aaf0487282b4'
_GIT_ENV = None
# Fixed membership, never supplied by the evidence index.
@@ -135,10 +137,17 @@ def literal(a, b):
def git_bytes(*args):
env = _GIT_ENV if _GIT_ENV is not None else dict(os.environ, GIT_OPTIONAL_LOCKS='0')
env = _GIT_ENV if _GIT_ENV is not None else git_environment()
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
def git_environment():
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_CONFIG_NOSYSTEM='1',
GIT_CONFIG_GLOBAL=os.devnull)
return env
@contextmanager
def isolated_objects():
"""merge-tree writes only disposable objects; canonical refs/index stay untouched.
@@ -149,11 +158,9 @@ def isolated_objects():
global _GIT_ENV
require(_GIT_ENV is None, 'nested object store')
objects = git_bytes('rev-parse', '--path-format=absolute', '--git-path', 'objects').decode().strip()
with tempfile.TemporaryDirectory(prefix='p04-object-audit-') as temporary:
env = dict(os.environ)
for key in ('GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES',
'GIT_INDEX_FILE', 'GIT_QUARANTINE_PATH'):
env.pop(key, None)
build = Path(os.environ['BUILD_OUTPUT_DIR']).resolve(strict=True)
with tempfile.TemporaryDirectory(prefix='p04-object-audit-', dir=build) as temporary:
env = git_environment()
env.update(GIT_OBJECT_DIRECTORY=temporary, GIT_ALTERNATE_OBJECT_DIRECTORIES=objects,
GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1')
_GIT_ENV = env
@@ -208,7 +215,7 @@ def supplied_file(directory, descriptor):
if path.is_absolute():
support = directory / 'audit-support'
roots = [support / n for n in ('p04-whole-closure-r01', 'native-first-current',
'p04-current-native-series-r01')]
'p04-current-native-series-r01', 'p04-r17-full-inputs-r01')]
matches = [root for root in roots if path.is_relative_to(root)]
require(len(matches) == 1, 'unsupplied external material: ' + name)
path = safe(matches[0], path.relative_to(matches[0]).as_posix())
@@ -304,6 +311,128 @@ def verify_product_target(index, directory, checker_commit):
'tested_product': tested, 'product_target': target}
def ancestor(older, newer):
# merge-base's nonzero status is data only for the ordinary non-ancestor case.
try:
git_bytes('merge-base', '--is-ancestor', older, newer)
return True
except subprocess.CalledProcessError as exc:
require(exc.returncode == 1, 'cannot determine ancestry')
return False
def submission_role(commit):
require(ancestor(REGISTERED_COMMIT, commit) and not ancestor(MAIN_ANCHOR, commit),
'invalid submission ancestry')
changed = git_bytes('diff', '--no-ext-diff', '--no-renames', '--name-only', '-z',
REGISTERED_COMMIT, commit).split(b'\0')
require(all(allowed_path(p.decode()) for p in changed if p), 'submission outside allowed_paths')
def actual_checkout():
"""Bind the executing modules and every worktree file to the actual HEAD."""
commit = git_bytes('rev-parse', '--verify', 'HEAD^{commit}').decode().strip()
tree = git_bytes('rev-parse', 'HEAD^{tree}').decode().strip()
entries = tree_entries(tree)
require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all',
'--ignore-submodules=none'), 'checkout is not clean')
require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution')
for path, (mode, oid) in entries.items():
actual = safe(ROOT, path)
require(actual.read_bytes() == git_bytes('cat-file', 'blob', oid), 'HEAD/tree/FS bytes: ' + path)
require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode,
'HEAD/tree/FS mode: ' + path)
# Also reject untracked directory symlinks, including ones ignored by Git.
for parent, directories, files in os.walk(ROOT, followlinks=False):
if Path(parent) == ROOT:
directories[:] = [p for p in directories if p != '.git']
files = [p for p in files if p != '.git']
for name in directories + files:
require(not (Path(parent) / name).is_symlink(), 'worktree symlink')
require(Path(__file__).resolve() == safe(ROOT, CHECKER_PATH), 'checker module location')
return commit, tree, entries
def verify_roles(index, directory):
"""Infer roles from Git, never from an index or a caller's stage label."""
require(not ({'candidate_commit', 'candidate_tree', 'code_only', 'candidate', 'phase'} & index.keys()),
'candidate/code-only/phase declarations are not evidence')
commit, actual_tree, actual = actual_checkout()
require(_GIT_ENV is not None, 'isolated object store required')
if not ancestor(MAIN_ANCHOR, commit):
submission_role(commit)
submitted, main, role = commit, MAIN_ANCHOR, 'prospective composition'
else:
parents = git_bytes('show', '-s', '--format=%P', commit).decode().split()
require(len(parents) == 2, 'integration/final requires a two-parent no-ff merge')
main, submitted = parents
require(ancestor(MAIN_ANCHOR, main), 'merge main ancestry')
submission_role(submitted)
role = 'integration/final'
composed = git_bytes('-c', 'rerere.enabled=false', 'merge-tree', '--write-tree',
main, submitted).decode().strip()
require(re.fullmatch('[0-9a-f]{40}', composed) is not None, 'conflicted composition')
main_entries, patch, target = [tree_entries(t) for t in (main, submitted, composed)]
anchor_entries = tree_entries(MAIN_ANCHOR)
for path in main_entries.keys() | target.keys():
require(main_entries.get(path) == target.get(path) or allowed_path(path),
'composition changed game/unknown source: ' + path)
# Metadata membership comes from the real main anchor, never a declared exclusion.
metadata = sorted(p for p in anchor_entries if not selected_path(p))
for path in target:
require(selected_path(path) or allowed_path(path) or path in metadata,
'unknown source hidden as metadata: ' + path)
if role == 'integration/final':
require(actual_tree == composed and actual == target, 'actual merge differs from composition')
require(target[CHECKER_PATH] == patch[CHECKER_PATH] == actual[CHECKER_PATH],
'checker blob differs from submission')
for path in patch:
if allowed_path(path):
require(target.get(path) == patch[path], 'reviewed patch differs in merge: ' + path)
binding = index['product_binding']
require(set(binding) == {'tested_product', 'runner_inputs'}, 'unknown product role/metadata declaration')
require(binding['tested_product'] == TESTED_PRODUCT, 'wrong tested revision')
product = product_manifest(target)
tested_entries = tree_entries(TESTED_PRODUCT)
expected = product_manifest(tested_entries)
require(len(product) == 1338 and product == expected and
product_manifest(main_entries) == expected and product_manifest(anchor_entries) == expected,
'complete 1338-file native product mismatch')
require(all(target[p][0] == main_entries[p][0] == tested_entries[p][0] for p in product),
'native product mode mismatch')
require(index['source_map'] == {p: v['sha256'] for p, v in product.items()}, 'stale pin: source map')
runner_paths = set()
for descriptor in binding['runner_inputs']:
path = supplied_file(directory, descriptor)
require(path not in runner_paths, 'duplicate runner input')
runner_paths.add(path)
native = load(path)
before = native['source_before']
require(set(before) == set(product) and all(
{'bytes': v['bytes'], 'sha256': v['sha256']} == product[p] for p, v in before.items()),
'runner complete source map mismatch')
require(native['runner_sha256'] == product['tools/run_all_tests.py']['sha256'], 'runner source pin')
overrides = native['QA_overrides']
require(len(overrides) == 1 and overrides[0].startswith('tests/') and overrides[0] in product,
'QA override membership')
selected = {p: v['sha256'] for p, v in product.items()}
selected[overrides[0]] = native['QA']['sha256']
require(native['selected_source_map'] == selected, 'selected QA source map')
needed = native.get('fixed_input_pins', []) + [native['QA']]
require(set(descriptor['materials']) == {p['path'] for p in needed}, 'input material membership')
for pin in needed:
material = supplied_file(directory, descriptor['materials'][pin['path']])
require(material.stat().st_size == pin['bytes'] and sha(material) == pin['sha256'], 'input bytes')
require(sha(supplied_file(directory, descriptor['wrapper'])) == native['wrapper_sha256'], 'wrapper bytes')
require(runner_paths, 'runner inputs missing')
require(index['versions'] == {'registered': REGISTERED_COMMIT, 'execution_origin': EXECUTION_ORIGIN,
'tested': TESTED_PRODUCT}, 'evidence version roles')
return composed, {'role': role, 'actual_head': commit, 'actual_tree': actual_tree,
'submission': submitted, 'main': main, 'existing_metadata': metadata,
'manifest_sha256': manifest_digest(product), 'runner_paths': runner_paths,
'tested_product': TESTED_PRODUCT}
def function_bytes(data):
"""Top-level named func through next named func, preserving every raw byte.
@@ -366,6 +495,10 @@ def boundary_conditions(path, function):
def applicability(index, spec, source, head):
tested = index.get('versions', {}).get('tested')
require(isinstance(tested, str) and tree_entries(tested) and
product_manifest(tree_entries(tested)) == product_manifest(tree_entries(head)),
'wrong tested revision')
registered = spec['current_source_pins']
require(index.get('registered_pins') == registered, 'registered pin declaration')
for path, digest in registered.items():
@@ -805,6 +938,148 @@ def verify_native(base):
verified_conditions=['dependency_stop', 'recovery_completion', 'paused_midnight'])
def verify_shift_completion(base):
"""C's first 18:00 transaction, never the paid-midnight 1442 sale."""
for row in load(safe(base, 'SOURCE_MANIFEST.json'))['files']:
native_file(base, row['copy'], row)
require(native_applicability(base) == TESTED_PRODUCT, 'shift tested revision')
pids = native_ownership(base)
require([pids['actual-native/' + phase] for phase in NATIVE_RUNS['actual-native']]
== [77462, 77504, 77536], 'shift ended PID identity')
inputs = load(safe(base, 'actual-native/runner-input.json'))
expected = product_manifest(tree_entries(TESTED_PRODUCT))
require(len(inputs['source_before']) == 1338 and
{p: {'bytes': v['bytes'], 'sha256': v['sha256']} for p, v in inputs['source_before'].items()}
== expected, 'shift complete native source')
prefix = 'actual-native/order-evidence/'
observations = load(safe(base, prefix + 'observations.json'))
labels = ['first-before-17-59', 'first-18-00-before-facility', 'first-18-00-after-facility',
'first-18-00-after-staff', 'first-18-00-tick-complete', 'first-18-01-tick-complete']
points = []
previous_ledger = None
service_id = None
paid_rows = None
original_service = None
completed_service = None
for i, label in enumerate(labels):
rows = [r for r in observations['observations'] if r['label'] == label]
require(len(rows) == 1, 'shift phase membership')
observation = rows[0]
snapshot = load(safe(base, prefix + label + '.json'))
minute = [1079, 1080, 1080, 1080, 1080, 1081][i]
require(snapshot['clock']['absolute_minute'] == observation['minute'] == minute, 'shift phase clock')
state = snapshot['systems']['runtime']
service = observation['service']
if i == 0:
service_id = service['service_id']
original_service = service
identity = ('service_id', 'request_id', 'facility_id', 'guest_ids', 'inventory_reservations',
'price_snapshot', 'duration_minutes', 'started_at', 'staff_ids')
require(all(service[k] == original_service[k] for k in identity), 'shift original service identity')
require(service['service_id'] == service_id and service['elapsed_minutes'] == (44 if i < 2 else 45)
and service['duration_minutes'] == 45 and service['price_snapshot'] == 25,
'shift service identity/progress')
candidates = state['facilities']['service_history'] + [s for f in state['facilities']['instances']
for s in f['active_services']]
require([s for s in candidates if s['service_id'] == service_id] == [service], 'shift raw service join')
require(observation['reservations'] == state['inventory'], 'shift inventory join')
reserved = [r for r in state['inventory']['reservations'] if r['reservation_id'] in service['inventory_reservations']]
require(reserved and len(reserved) == len(service['inventory_reservations']) and all(
r['requester_id'] == service['request_id'] and r['state'] == ('ACTIVE' if i < 2 else 'CONSUMED')
and r['terminal_at'] == (-1 if i < 2 else 1080) for r in reserved), 'shift reservation join')
day2 = ['p04.order.server.a', 'p04.order.server.b']
require(observation['active_staff'] == (day2 if i < 3 else []), 'shift DAY2 before/after handover')
facility = [f for f in state['facilities']['instances'] if f['instance_id'] == service['facility_id']]
require(len(facility) == 1 and facility[0]['active_staff_ids'] == observation['active_staff'],
'shift full-state active staff join')
require([w['staff']['staff_id'] for w in observation['workers']] == day2, 'shift worker identity set')
for worker in observation['workers']:
require(worker['staff'] in state['staff']['staff'],
'shift worker raw join')
if i < 3:
require(worker['shift'] in state['staff']['shifts'] and worker['is_present'] is True and worker['shift']['kind'] == 'DAY' and
worker['shift']['end_at'] == 1080 and worker['shift']['actual_ended_at'] == -1,
'shift original DAY worker present')
else:
ended = [r for r in state['staff']['shifts'] if r['staff_id'] == worker['staff']['staff_id']]
require(worker['shift'] == {} and worker['is_present'] is False and
worker['staff']['state'] == 'OFF_DUTY' and len(ended) == 1 and
ended[0]['actual_ended_at'] == ended[0]['end_at'] == 1080,
'shift normal handover history')
ledger = state['economy']['entries']
sale = [r for r in ledger if r['category'] == 'SERVICE_SALE']
if i < 2:
require(sale == [] and observation['receipts'] == [], 'shift precompletion charge')
else:
require(service['state'] == 'COMPLETED' and service['completed_at'] == 1080 and len(sale) == 1,
'shift single completion')
transaction = sale[0]
require(transaction['transaction_id'] == 'ledger_00000006' and transaction['amount'] == 25 and
transaction['absolute_minute'] == 1080 and transaction['source_entity_id'] == service['facility_id']
and transaction['credit_account'] == 'GUEST_WALLET:' + service['guest_ids'][0], 'shift sale identity')
receipts = receipt_rows(observation['receipts'])
require(receipts == [r for r in ledger if r['transaction_id'] == 'ledger_00000006'], 'shift receipt raw join')
require([r for r in state['economy']['idempotency'] if r['key'] == 'service.payment.' + service_id]
== [{'key': 'service.payment.' + service_id, 'transaction_id': 'ledger_00000006'}],
'shift idempotency join')
if i == 2:
completed_service = service
paid_rows = observation['receipts']
require(paid_rows == observations['first_receipts'] and paid_rows[0]['staff_at_receipt'] == day2,
'shift receipt precedes handover')
else:
require(service == completed_service and ledger == previous_ledger and
observation['receipts'] == paid_rows, 'shift extra charge/completion')
previous_ledger = ledger
points.append(label)
return {'verified_conditions': ['shift_completion'], 'phases': points, 'transaction_id': 'ledger_00000006'}
def combine_native_series(directory, native):
"""Independent R composition; rerun raw checks, never trust condition scalars.
The C verifier and its fixtures remain unchanged. This function does not
establish candidate applicability, and cannot grant a successful whole gate.
"""
import importlib.util
module_path = ROOT / DOC_SCOPE / 'p04_native_series.py'
module_spec = importlib.util.spec_from_file_location('p04_series', module_path)
series = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(series)
c_base = directory / 'audit-support/native-first-current'
require(native == verify_native(c_base), 'C result must be recomputed from raw')
base = directory / 'audit-support/p04-raw-collect-r20'
manifest_path = safe(base, 'MANIFEST.json')
manifest = load(manifest_path)
require(manifest['protocol'] == 'p04-native-series-collection-v1' and
manifest['relocation_manifest_sha256'] == series.MANIFEST_SHA256,
'series relocation binding')
require(set(manifest['series']) == set(series.RUNS), 'series manifest membership')
shift = verify_shift_completion(c_base)
verified = set(native['verified_conditions']) | set(shift['verified_conditions'])
bindings = {}
for name in series.RUNS:
binding = manifest['series'][name]
path = safe(base, binding['path'])
require(path.name == name + '.json' and sha(path) == binding['sha256'] and
path.stat().st_size == binding['bytes'] and
binding['relocation_manifest_sha256'] == series.MANIFEST_SHA256, 'series manifest SHA binding')
recorded = load(path)
require(recorded['verifier_sha256'] == sha(module_path), 'series verifier changed since collection')
actual = getattr(series, 'verify_' + name)(directory)
require({k: v for k, v in recorded.items() if k != 'verifier_sha256'} == actual,
'series raw result/reference drift')
verified.update(actual['verified_conditions'])
bindings[name] = binding['sha256']
return {'verified_conditions': sorted(verified), 'series_manifest_sha256': sha(manifest_path),
'C_source_manifest_sha256': sha(safe(c_base, 'SOURCE_MANIFEST.json')),
'C_provenance_sha256': sha(safe(c_base, 'OWNERSHIP_RAW_PROVENANCE.json')),
'series_bindings': bindings, 'relocation_manifest_sha256': series.MANIFEST_SHA256,
'missing_conditions': sorted((set(REQUIRED) | {'EV'}) - verified),
'nativeWindows': {name: 'pending' for name in sorted(set(REQUIRED) | {'EV'})},
'stage_A': 'candidate binding stage A pending', 'formal_acceptance': False}
def verify(directory):
# Contract hashes the supplied spec string, while the checked-in JSON has
# one final LF. Accept only that exact representation difference.
@@ -812,14 +1087,23 @@ def verify(directory):
require(sha(SPEC) == SPEC_HASH or (spec_bytes.endswith(b'\n') and
hashlib.sha256(spec_bytes[:-1]).hexdigest() == SPEC_HASH), 'dispatch spec pin mismatch')
spec = load(SPEC)
evidence = directory / 'p04-evidence'
evidence = Path(os.environ.get('P04_EVIDENCE_DIR', str(directory / 'p04-evidence'))).absolute()
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(type(index.get('schema_version')) is int and index['schema_version'] in (1, 2), 'schema_version')
require(index.get('task_id') == 'P04-01' and index.get('formal_acceptance') is False, 'task/acceptance')
checker_commit = git_bytes('rev-parse', 'HEAD').decode().strip()
require(index.get('candidate_commit') == checker_commit, 'checker invocation commit mismatch')
head, target_result = verify_product_target(index, directory, checker_commit)
head, target_result = verify_roles(index, directory)
if index['schema_version'] == 2:
import importlib.util
module_path = ROOT / DOC_SCOPE / 'build_p04_index.py'
module_spec = importlib.util.spec_from_file_location('p04_index', module_path)
builder = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(builder)
expected = builder.payload(directory, sys.modules[__name__])
require(index == expected, 'forged index/raw mapping differs from recomputation')
require(not index['raw_conditions']['missing_conditions'], 'uncollected original boundaries: ' +
', '.join(index['raw_conditions']['missing_conditions']))
return target_result
source = index.get('source_map')
applicability(index, spec, source, head)
oldmap = load(ROOT / 'docs/plans/original-completion-2026-10-02/execution/p04-01-input-r01/historical-source-map.json')
@@ -903,23 +1187,37 @@ def verify(directory):
require(native['production_commit'] == target_result['tested_product'], 'native production role mismatch')
# This adapter covers only the supplied dependency/formal series. Never
# promote it to the missing original scenarios through scalar booleans.
missing = (set(REQUIRED) | {'EV'}) - set(native['verified_conditions'])
combined = combine_native_series(directory, native)
missing = set(combined['missing_conditions'])
require(not missing, 'uncollected original boundaries: ' + ', '.join(sorted(missing)) +
'; formal_original_credit=0')
return target_result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--verify-only', action='store_true', required=True)
parser.parse_args()
mode = parser.add_mutually_exclusive_group(required=True)
mode.add_argument('--verify-only', action='store_true')
mode.add_argument('--verify-series-only', action='store_true', help='Read-only Stage R composition; always leaves Stage A pending')
args = parser.parse_args()
try:
require(bool(os.environ.get('BUILD_OUTPUT_DIR')), 'BUILD_OUTPUT_DIR required')
if args.verify_series_only:
directory = Path(os.environ['BUILD_OUTPUT_DIR']).absolute()
native = verify_native(directory / 'audit-support/native-first-current')
result = combine_native_series(directory, native)
result.update(task_id='P04-01', machine_verified=False,
error='candidate binding stage A pending; uncollected original boundaries: ' +
', '.join(result['missing_conditions']))
print(json.dumps(result, ensure_ascii=False, sort_keys=True))
return 1
with isolated_objects():
verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve())
result = verify(Path(os.environ['BUILD_OUTPUT_DIR']).resolve())
except (ValueError, OSError, KeyError, TypeError, IndexError, AttributeError, 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
print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False}))
print(json.dumps({'task_id': 'P04-01', 'machine_verified': True, 'formal_acceptance': False,
'role': result['role'], 'actual_head': result['actual_head']}))
return 0