P04-01: 同時発生の更新規約
This commit is contained in:
@@ -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.
|
||||
|
||||
+176
-15
@@ -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))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user