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

This commit is contained in:
Agent Control Runner
2026-10-18 21:01:07 +09:00
parent 13aefdd3dd
commit a9ef681fb8
6 changed files with 352 additions and 17 deletions
@@ -359,3 +359,25 @@ Runtime helper fingerprints and source/provenance guards bind the actual Q.
No full regression, 15+EV batch or original credit is claimed. The S5
destruction/midnight/exit drivers, S1–S4/collector fresh Q, derived 184 suites /
329 parse, truthful index and Root exact-candidate qualification remain pending.
## REPAIR50 preparation (2026-10-18)
Commander-reported prior observation: all-equal array reversal produced
identical bytes (raw SHA prefix `bb0ccb1d…`), correctly accepted by the oracle;
the old control incorrectly required reject. Reported elapsed: 761.085 seconds.
This worker has not rerun that observation and does not supply a full SHA
from the abbreviated handoff. Earlier failure records are preserved.
The order control now records identity accept separately, swaps unequal typed
bytes, lists no-genuine-order cases, avoids duplicate parsing of the uncopied
record and memoizes only genuine BEFORE/command/guard derivations under strict
source-bound keys with finally restoration. Both copied modes and all
AFTER/journal/body traversal remain. Negative rejection is still ValueError;
parser rejection is separately visible. Synthetic equal-array, swap, type,
value, AFTER-copy and memo controls are prepared in --pure-only.
repair48_identity_controls.tree now hashes fixed-size chunks; its roster and
all other fixture logic remain unchanged. Root must run the new
repair50_stream_controls.py equivalence control. Only AST parse was performed
by this worker. No pure/native/formal pass, reuse approval or credit is claimed.
See ../REPAIR50_STATUS.md before running the existing Root sequence.
@@ -1,10 +1,13 @@
#!/usr/bin/env python3
"""Root-only typed mutations against fresh native S5 raw; never synthetic positives."""
import argparse
from contextlib import contextmanager
import hashlib
import json
from pathlib import Path
import struct
import sys
import time
sys.dont_write_bytecode = True
import s5_order_adapter as a
@@ -13,6 +16,70 @@ mutation = a.module('order_typed_mutation', Path(__file__).with_name('s5_power_a
BUILD = Path('/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build')
def order_variant(node):
"""Swap the first unequal typed byte pair, preserving the header."""
rows = [r.raw if node.kind == 28 else r[0].raw + r[1].raw for r in node.value]
for i in range(len(rows)):
for j in range(i + 1, len(rows)):
if rows[i] != rows[j]:
header = len(node.raw) - sum(map(len, rows))
rows[i], rows[j] = rows[j], rows[i]
return node.raw[:header] + b''.join(rows)
return None
def static_estimate(record):
kinds, nodes, candidates = {}, 0, 0
def visit(node):
nonlocal nodes, candidates
nodes += 1
kinds[str(node.kind)] = kinds.get(str(node.kind), 0) + 1
candidates += (2 if node.kind in (4, 21, 2) else 1 if node.kind == 1 else
int(bool(node.value)) + int(len(node.value) > 1) if node.kind == 28 else
int(len(node.value) > 1) if node.kind == 27 else 0)
if node.kind == 28:
for child in node.value:
visit(child)
elif node.kind == 27:
for key, child in node.value:
visit(key)
visit(child)
visit(record.at('after'))
return {'record_bytes': len(record.raw), 'after_nodes': nodes, 'kind_counts': kinds,
'candidate_count': candidates, 'verify_mutations': candidates * 2,
'disk_mutation_bytes': 0,
'memory_model': 'O(record_bytes * (mutated_kinds + 2) + after_nodes + output_labels); parser overhead unmeasured',
'time_model': 'O(verify_mutations * record_bytes); calibrate with --pure-only; not a measured ETA'}
@contextmanager
def expected_memo(stats, guard_raw):
oracle = a.guard_oracle
original = oracle.expected
sources = tuple(hashlib.sha256(Path(m.__file__).read_bytes()).digest()
for m in (oracle, a.series))
cache = {}
def wrapped(before, command, guard, parse, require):
require(parse(guard_raw).value == guard, 'memo exact guard binding')
blobs = (before.raw, command.raw, guard_raw)
key = (tuple((len(b), hashlib.sha256(b).digest()) for b in blobs), sources)
if key not in cache:
stats['miss'] += 1
tree, result = original(before, command, guard, parse, require)
cache[key] = (tree.raw, result.raw)
else:
stats['hit'] += 1
return tuple(parse(b) for b in cache[key])
oracle.expected = wrapped
try:
yield original
finally:
oracle.expected = original
def guard_mutations(record):
"""All typed AFTER nodes are tested, including every changed journal field.
@@ -20,11 +87,20 @@ def guard_mutations(record):
must still reject it. No native evidence is produced by these controls.
"""
oracle = a.guard_oracle
estimate = static_estimate(record)
a.require(oracle.verify(record, a.series.variant, a.require), 'guard control prerequisite')
checks = []
started = time.monotonic()
checks, identities, no_order = [], [], []
stats = {'hit': 0, 'miss': 0}
kinds, nodes, candidates = {}, 0, 0
representatives, first_last = {}, []
parser_rejections = []
after = record.at('after')
def visit(node, path):
nonlocal nodes, candidates
nodes += 1
kinds[str(node.kind)] = kinds.get(str(node.kind), 0) + 1
replacements = []
if node.kind in (4, 21):
replacements = [('value', oracle.atom(node.kind, node.value + '.mutated')),
@@ -40,30 +116,101 @@ def guard_mutations(record):
replacements.append(('extra-row', node.raw[:start-4] + struct.pack('<I', len(node.value)+1)
+ node.raw[start:] + node.value[-1].raw))
if len(node.value) > 1:
replacements.append(('unsorted', node.raw[:start] + b''.join(r.raw for r in reversed(node.value))))
encoded = order_variant(node)
if encoded is None:
no_order.append(path + ':order-no-genuine-variant')
replacements.append(('identity-order' if encoded is None else 'unsorted',
node.raw if encoded is None else encoded))
for i, child in enumerate(node.value):
visit(child, path + '/' + str(i))
elif node.kind == 27:
if len(node.value) > 1:
replacements.append(('unsorted', oracle.dictionary([(k.raw, v.raw) for k, v in reversed(node.value)])))
encoded = order_variant(node)
if encoded is None:
no_order.append(path + ':order-no-genuine-variant')
replacements.append(('identity-order' if encoded is None else 'unsorted',
node.raw if encoded is None else encoded))
for key, child in node.value:
visit(key, path + '/key:' + str(key.value))
visit(child, path + '/' + str(key.value))
for label, encoded in replacements:
candidates += 1
identity = encoded == node.raw
changed_after = mutation.replace(after, node, encoded)
changed_bytes = mutation.replace(record, after, changed_after)
try:
changed = a.series.variant(changed_bytes)
except ValueError:
if identity:
raise
changed = None
for copied in (False, True):
changed_record = mutation.replace(record, after, changed_after)
name = path + ':' + label + (':AFTER-copy' if copied else '')
# Copy even malformed typed AFTER bytes into EXPECTED, without
# parsing the invalid intermediate record a second time.
copied_bytes = (record.raw[:8] + b''.join(
k.raw + (changed_after if v is after or v is record.at('expected') else v.raw)
for k, v in record.value)) if copied else None
try:
changed = a.series.variant(changed_record)
if copied:
changed_record = mutation.replace(changed, changed.at('expected'), changed_after)
oracle.verify(a.series.variant(changed_record), a.series.variant, a.require)
tested = a.series.variant(copied_bytes) if copied else changed
except ValueError:
checks.append(path + ':' + label + (':AFTER-copy' if copied else ''))
continue
raise ValueError('guard mutation accepted: ' + path + ':' + label)
visit(after, 'after')
return checks
if identity:
raise
tested = None
if tested is None:
checks.append(name)
parser_rejections.append(name)
# Derivation inputs remain the original record's inputs.
tested = record
elif identity:
a.require(oracle.verify(tested, a.series.variant, a.require), 'identity accepted')
identities.append(name)
else:
try:
oracle.verify(tested, a.series.variant, a.require)
except ValueError:
checks.append(name)
else:
raise ValueError('guard mutation accepted: ' + name)
representatives.setdefault(node.kind, tested)
first_last[:] = [first_last[0] if first_last else tested, tested]
original = oracle.expected
with expected_memo(stats, record.at('guard').raw) as derive:
visit(after, 'after')
for tested in first_last + list(representatives.values()):
args = (tested.at('before'), tested.at('command'), tested.at('guard').value,
a.series.variant, a.require)
hit, plain = oracle.expected(*args), derive(*args)
a.require(tuple(n.raw for n in hit) == tuple(n.raw for n in plain), 'memo bytes')
before = record.at('before')
target = before.at('systems').at('runtime').at('next_command_id')
bad = bytearray(target.raw)
bad[4] ^= 1
changed_before = a.series.variant(mutation.replace(before, target, bytes(bad)))
changed_record = a.series.variant(mutation.replace(record, before, changed_before.raw))
misses = stats['miss']
try:
oracle.verify(changed_record, a.series.variant, a.require)
except ValueError:
pass
else:
raise ValueError('one-byte BEFORE mutation accepted')
a.require(stats['miss'] == misses + 1, 'one-byte BEFORE cache miss')
a.require(oracle.expected is original, 'memo wrapper restored')
try:
with expected_memo({'hit': 0, 'miss': 0}, record.at('guard').raw):
raise ValueError('cleanup sentinel')
except ValueError:
pass
a.require(oracle.expected is original, 'exceptional memo wrapper restored')
a.require(estimate['verify_mutations'] == candidates * 2 and estimate['after_nodes'] == nodes
and estimate['kind_counts'] == kinds, 'static enumeration agrees')
return {'negative_controls': checks, 'negative_count': len(checks),
'identity_positive_count': len(identities), 'identity_positive_list': identities,
'no_genuine_order_variant': no_order, 'memo': stats,
'parser_rejections': parser_rejections,
'elapsed': time.monotonic() - started,
'static_estimate': estimate}
def pure_controls():
@@ -82,7 +229,9 @@ def pure_controls():
('id_generator', d([('version', atom(2, 1)), ('counters', d([('domain_event', atom(2, 2))]))])),
('domain_events', d([('version', atom(2, 1)), ('published_ids', arr([atom(4, 'domain_event.000000000001'), atom(4, 'domain_event.000000000002')], 4)), ('recent_events', arr([], 27))])),
('operations', d([('enabled', atom(1, 0))])),
('business', d([('wallet', atom(2, 100)), ('reservations', arr([], 27))]))])
('business', d([('wallet', atom(2, 100)), ('reservations', arr([], 27)),
('equal_array', arr([atom(2, 0)] * 3, 2)),
('swap_array', arr([atom(2, 1), atom(2, 2)], 2))]))])
before = a.series.variant(d([('systems', d([('runtime', runtime)]))]))
results = {}
for guard in ('G1', 'G2'):
@@ -102,17 +251,25 @@ def pure_controls():
record = a.series.variant(d([('before', before.raw), ('command', command.raw), ('guard', atom(4, guard)),
('expected', expected.raw), ('after', expected.raw), ('result', result.raw)]))
results[guard] = guard_mutations(record)
report = results[guard]
a.require(any('equal_array:identity-order' in x for x in report['identity_positive_list']), 'equal array positive')
for label in ('swap_array:unsorted', 'wallet:value', 'wallet:int-float', 'wallet:value:AFTER-copy'):
a.require(any(label in x for x in report['negative_controls']), 'synthetic coverage: ' + label)
return {'synthetic_serializer_controls_only': True, 'negative_controls': results, 'formal_acceptance': False}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--pure-only', action='store_true')
parser.add_argument('--estimate-record', type=Path, help='parse one guard record and print static estimate only')
parser.add_argument('--raw-root', type=Path)
parser.add_argument('--writer-sha256')
parser.add_argument('--reader-sha256')
parser.add_argument('--scratch', type=Path)
args = parser.parse_args()
if args.estimate_record:
print(json.dumps(static_estimate(a.series.variant(args.estimate_record.read_bytes())), sort_keys=True))
return
if args.pure_only:
print(json.dumps(pure_controls(), sort_keys=True))
return
@@ -133,6 +290,7 @@ def main():
cases[case] = ({k: raw.typed(v) for k, v in branch['points'].items()},
[raw.typed(r) for r in branch['phases']])
checks = []
guard_reports = {}
def negative(label, case, owner, target, replacement):
points, phases = cases[case]
@@ -202,7 +360,9 @@ def main():
for case in ('S5D_DESTRUCTION_RAID', 'S5D_IN_FLIGHT'):
branch = raw.reference(writer['branches'][case])
for label, reference in branch['guards'].items():
checks.extend(label + ':' + name for name in guard_mutations(raw.typed(reference)))
report = guard_mutations(raw.typed(reference))
guard_reports[case + ':' + label] = report
checks.extend(label + ':' + name for name in report['negative_controls'])
command = 'S5C_DESTRUCTION_COMMAND'
points, phases = cases[command]
end = points['held']
@@ -251,6 +411,7 @@ def main():
start = 12 if ledger.raw[:4] == struct.pack('<I', 65564) else 8
negative('replay-extra-row', midnight, replay, ledger, ledger.raw[:start-4] + struct.pack('<I', len(ledger.value)+1) + ledger.raw[start:] + ledger.value[-1].raw)
print(json.dumps({'positive_cases': list(positive['cases']), 'negative_controls': checks,
'negative_count': len(checks), 'guard_reports': guard_reports,
'formal_acceptance': False}, sort_keys=True))