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
@@ -108,3 +108,36 @@ The preserved S1–S4, r22 index, old registered validation and f5 full178 run r
The eleven phases remain APPLY_COMMANDS, TIME_BOUNDARIES, MANA, CONSTRUCTION, LOGISTICS, FACILITY_COMPLETION, VITALS_AND_ENVIRONMENT, INCIDENTS_AND_EVENTS, FACILITY_QUEUES, AI_DECISIONS, PUBLISH_SNAPSHOT. See the structured audit for preserved phase analysis and the new source manifest for exact M references.
See [CURRENT_STATUS.md](CURRENT_STATUS.md) and [REPAIR31_STATUS.md](REPAIR31_STATUS.md) before any Root execution.
## REPAIR50 source preparation (2026-10-18; no acceptance claim)
The commander handoff reports an identity order mutation on an all-equal array
(`recent_events/17/payload/current_cell = [0,0,0]`), raw SHA prefix
`bb0ccb1d…`, and 761.085 seconds elapsed. These are supplied observations,
not measurements repeated by this worker; the full raw SHA/log remains Root
material. The old control reversed every multi-element array and classified
the resulting identical bytes as a negative. The oracle correctly accepted
that identity. Existing failure records remain unchanged.
REPAIR50 separates byte-identical identity positives from negatives, swaps the
first unequal typed byte pair, and lists collections with no genuine order
variant. All AFTER keys/values, journal/body fields and both copied modes
remain traversed. Type-invalid mutations still require ValueError rejection
and are also listed as parser rejections; copied records contain the changed
bytes in both AFTER and EXPECTED even when an intermediate parse is invalid.
The oracle, adapters, public checker, native-series implementation and R49
closure roster are unchanged. No Node patch optimization is used.
The temporary expected memo keys full BEFORE/command/guard bytes by SHA256
and length plus oracle/parser source SHA256. Its cache contains only genuine
derivation output bytes. It reconstructs fresh Nodes on each return, restores
the wrapper with finally, and retains verify for each parseable mutation.
Self-controls compare hit/nonmemo bytes for first/last and each mutated kind,
require a one-byte BEFORE miss and reject, and check normal/exception cleanup.
The SHA fixture now streams 1 MiB implementation chunks without changing
file selection, traversal, relative keys or digest semantics.
Worker validation: AST parsing only; pure, stream, native and formal checks
are unexecuted. See [REPAIR50_STATUS.md](REPAIR50_STATUS.md) for Root commands,
static estimation and reuse prerequisites. Original 30/88, formal 16 and P04
credit 0 remain unchanged; the whole unit is incomplete.
@@ -0,0 +1,68 @@
# REPAIR50 source-ready handoff
Scope: control classification/memo, tree() read method only, stream equivalence
control and documentation. No Git write, engine, Runner, integration or formal
test was performed. Three changed/new Python sources passed AST parsing.
## Root-only execution and estimation
From this package directory, run the following pure controls once and retain
their separate JSON output and external elapsed/RSS measurements:
```
python3 s5/s5_order_adapter_controls.py --pure-only
python3 repair50_stream_controls.py
```
The first emits separate negative counts/lists, identity positive counts/lists,
no-genuine-order lists, memo hit/miss, parser-rejection list and elapsed per
guard. The second compares the complete whole-read and streamed SHA maps and
key insertion order for empty, Unicode name/content, binary, sparse, chunk
boundary -1/exact/+1, multiple chunks and nested files. It uses temporary
synthetic files only and creates no native acceptance evidence.
Before heavy typed controls, parse each actual retained guard record with:
```
python3 s5/s5_order_adapter_controls.py --estimate-record /absolute/path/to/guard.var
```
This performs no derivation or verify: output includes record byte size,
AFTER node count, kind histogram, replacement count and replacement count x2
(including identities, never reported as negatives). Guard mutation disk
estimate is zero; stream control logical fixture size is about 10 MiB, with
the sparse part filesystem-dependent. Memory scales with retained parsed
representatives (at most one per mutated kind, plus first/last), record bytes,
parser overhead and output labels. Memo holds one pair of expected/result raw
bytes for unchanged inputs; it never caches AFTER or recorded EXPECTED.
Time scales with mutation count x record bytes: parse/verify still examine
whole records. Use the synthetic run's external peak RSS and elapsed, together
with its emitted counts/bytes, as an initial calibration; real record shape
and source-derivation costs make this an estimate, not an ETA or resource cap.
Actual guard bytes/counts and calibrated measurements are Root-pending.
## complete_B scope reuse remains conditional
The supplied commander handoff reports R49 session56645 exit 0, 49-file map,
S5 facts/payload equality, no B changes, log SHA prefix bdef6715 and closure
tree prefix b54a35ed. This worker has not independently verified that run.
Root must mechanically compare its saved full raw map with the new tree and
record only the authorized control/tree-read/new-control/document changes.
Inspect import/exec and input closure for builder, collector, native-series,
reader, oracle, wrapper, probe and helper; preserve S47 raw, 21pin, legacy5,
full184 originals, oldQ source/engine SHA and public run_all_tests.py SHA.
Important: repair48_identity_controls.CLOSURE explicitly lists the changed
order control, identity control and S5_ROOT_RUN.md as snapshot material.
The roster is unchanged. Direct searches in builder/native-series/order
adapter did not reference these names, but this is not a complete graph proof
and does not establish reuse. Root must distinguish the actual two-directory
analysis/payload execution/input closure from this snapshot roster and apply
all commander conditions. If any condition fails, rerun complete_B once.
No R49 closure bytes or raw evidence were rewritten to obtain reuse.
Then preserve the recorded Root order: pure controls → clean exact-Q check-only
→ capacity/PID confirmation → fresh native6 → reader/collector → all eight
full exact-source reuse conditions (failure means one full run) → index → one
registered p04-acceptance validation → independent whole-unit review → normal
gates/RootDB join/ledger/main push. Only whole-unit acceptance awards credit.
@@ -30,8 +30,15 @@ def rejects(call):
def tree(root):
return {p.relative_to(root).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
for p in root.rglob('*') if p.is_file()}
rows = {}
for p in root.rglob('*'):
if p.is_file():
digest = hashlib.sha256()
with p.open('rb') as source:
for chunk in iter(lambda: source.read(1024 * 1024), b''):
digest.update(chunk)
rows[p.relative_to(root).as_posix()] = digest.hexdigest()
return rows
# Explicit audited package-local closure; see REPAIR49_STATUS.md.
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Root-only engine-free whole-read/stream SHA equivalence controls."""
import hashlib
import importlib.util
import json
from pathlib import Path
import sys
import tempfile
import time
sys.dont_write_bytecode = True
def main():
started = time.monotonic()
path = Path(__file__).with_name('repair48_identity_controls.py')
spec = importlib.util.spec_from_file_location('r50_identity', path)
module = importlib.util.module_from_spec(spec)
exec(compile(path.read_bytes(), str(path), 'exec'), module.__dict__)
chunk = 1024 * 1024 # Implementation boundary, never an acceptance limit.
with tempfile.TemporaryDirectory(prefix='p04-r50-stream-') as directory:
root = Path(directory)
(root / 'empty').write_bytes(b'')
(root / '日本語🌳').write_bytes('木と水\n🌳'.encode())
(root / 'binary').write_bytes(bytes(range(256)) * 5)
for size in (chunk - 1, chunk, chunk + 1, chunk * 3 + 17):
(root / str(size)).write_bytes((bytes(range(256)) * (size // 256 + 1))[:size])
with (root / 'sparse').open('wb') as stream:
stream.seek(chunk * 4 + 3)
stream.write(b'\xff')
nested = root / 'nested'
nested.mkdir()
(nested / '全bytes').write_bytes(b'\x00\xff\x80\n')
whole = {p.relative_to(root).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
for p in root.rglob('*') if p.is_file()}
streamed = module.tree(root)
assert list(streamed) == list(whole), 'same traversal/key insertion order'
assert streamed == whole, 'complete SHA map equality'
print(json.dumps({'stream_equivalence': 'pass', 'files': whole,
'elapsed': time.monotonic() - started, 'formal_acceptance': False}, sort_keys=True))
if __name__ == '__main__':
main()
@@ -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))