P04-01: 同時発生の更新規約
This commit is contained in:
@@ -0,0 +1,95 @@
|
|||||||
|
# Repair21 — source submission; tests NOTRUN
|
||||||
|
|
||||||
|
P04-01 source-only performance repair. Changes are restricted to the checker,
|
||||||
|
the new `bulk_reader_equivalence.py` fixture, and this status file. No Git
|
||||||
|
command, test, fixture, collector, Runner, native/Godot run, agent, commit,
|
||||||
|
branch or integration operation was executed by the worker.
|
||||||
|
|
||||||
|
`read_blobs` validates full lowercase 40-hex OIDs before starting a process,
|
||||||
|
deduplicates/sorts them, and uses one `git cat-file --batch` process per nonempty
|
||||||
|
request. Empty requests start no process. `check_output` inherits stderr and
|
||||||
|
raises for nonzero exit status just like `git_bytes`. The separately injectable
|
||||||
|
byte parser verifies ordered OIDs, blob type, decimal size, exact framing and
|
||||||
|
complete output consumption. There is deliberately no blob cache, so no object
|
||||||
|
can be reused across `_GIT_ENV` namespaces. The two bulk call sites preserve
|
||||||
|
selected-path sorted manifests and every actual-file byte/mode comparison.
|
||||||
|
|
||||||
|
No existing require message or assertion was removed. The `NATIVE_RUNS` through
|
||||||
|
`verify_native` region, commit-type checks, ancestry, merge-tree isolation,
|
||||||
|
status/untracked/symlink/module checks, schema/encoder, six existing suites and
|
||||||
|
role case IDs were not edited. Checker self-blob reads remain unchanged.
|
||||||
|
|
||||||
|
The fixture source covers binary NUL/CR/LF/trailing LF, empty and large blobs,
|
||||||
|
unicode `ls-tree -z` path resolution, duplicates, empty input, malformed/short
|
||||||
|
OIDs, missing objects, tree/commit objects, invalid/truncated/oversized/garbage
|
||||||
|
responses, process failure, one-process sorted input, and private object
|
||||||
|
visibility inside versus outside and across isolated namespaces. It has not run.
|
||||||
|
|
||||||
|
Static validation: `ast.parse` and in-memory `compile(..., 'exec')` completed for
|
||||||
|
the checker and new fixture. Neither module was imported or executed. This is
|
||||||
|
syntax validation only. Tests: **NOTRUN**. Runtime equivalence and completion
|
||||||
|
within the registered 180 seconds remain for Root to measure.
|
||||||
|
|
||||||
|
The existing schema2 index at B/p04-evidence/index.json was read as data, along
|
||||||
|
with the existing builder and role fixture. Its registered/execution/tested
|
||||||
|
roles remain deb1bcb/9767b0a/5f6bc11 and original_credit remains 13/88.
|
||||||
|
The existing index and r20 collection are untouched. No new generation is
|
||||||
|
needed if Root confirms the recomputed payload is identical. If generation is
|
||||||
|
needed, use a new explicit destination, never overwrite existing evidence.
|
||||||
|
Original P04-01 remains incomplete; criteria and nativeWindows pending status
|
||||||
|
are unchanged. No original credit or acceptance is claimed.
|
||||||
|
|
||||||
|
## Root execution order — all NOTRUN
|
||||||
|
|
||||||
|
Use the clean new auto-submitted checkout, whose SHA is assigned after this
|
||||||
|
source submission. Do not use the registered base SHA as the new submission.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export B=/Users/korizatou114/Projects/TheTree-managed-worktrees/p04-01/.runtime/P04-01/build
|
||||||
|
export E=docs/plans/original-completion-2026-10-02/execution/p04-01
|
||||||
|
export BUILD_OUTPUT_DIR="$B"
|
||||||
|
export PYTHONDONTWRITEBYTECODE=1
|
||||||
|
python3 "$E/bulk_reader_equivalence.py"
|
||||||
|
python3 "$E/checker_negative_fixtures.py"
|
||||||
|
python3 "$E/checker_applicability_fixtures.py" --native-bundle "$B/audit-support/native-first-current"
|
||||||
|
python3 "$E/checker_native_fixtures.py" "$B/audit-support/native-first-current"
|
||||||
|
python3 "$E/checker_series_fixtures.py" --build "$B"
|
||||||
|
python3 "$E/checker_encoder_fixtures.py"
|
||||||
|
python3 "$E/checker_join_fixtures.py" --build "$B"
|
||||||
|
python3 "$E/checker_role_fixtures.py" --build "$B" --evidence "$B/p04-evidence"
|
||||||
|
```
|
||||||
|
|
||||||
|
Next run the registered validation on the **new auto-submitted SHA**, retaining
|
||||||
|
its existing 180-second timeout and actual timed receipt. The supplied formal
|
||||||
|
API call is exactly:
|
||||||
|
|
||||||
|
```python
|
||||||
|
runner.validate("P04-01")
|
||||||
|
```
|
||||||
|
|
||||||
|
The external Runner launcher/configuration is not present in the supplied task
|
||||||
|
context; use Root's already registered invocation, without substituting an
|
||||||
|
invented CLI, changing timeout, or treating direct checker timing as registered
|
||||||
|
validation. Record actual SHA, elapsed time and exit status from that invocation.
|
||||||
|
|
||||||
|
Then run direct real-candidate controls from actual clean submitted, merged and
|
||||||
|
final checkouts, preserving the formal review/integration state machine:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tools/check_p04_order_evidence.py --verify-only
|
||||||
|
python3 "$P04_MERGED_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||||
|
python3 "$P04_FINAL_CHECKOUT/tools/check_p04_order_evidence.py" --verify-only
|
||||||
|
```
|
||||||
|
|
||||||
|
After required formal validation/review and exact final-source binding, run the
|
||||||
|
full178 product regression once, using a fresh output destination:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 "$P04_FINAL_CHECKOUT/tools/run_all_tests.py" \
|
||||||
|
--project-root "$P04_FINAL_CHECKOUT" --output "$B/p04-final-product-full-r21"
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not regenerate raw collections, overwrite index/r20 evidence, alter existing
|
||||||
|
case semantics, or run an additional native/full suite to compensate for a
|
||||||
|
timeout. Any unrelated improvement is deferred. Pending Root execution is not
|
||||||
|
a worker specification gap and does not change this submission's scope.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Root-only disposable repository controls; never original acceptance evidence."""
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
sys.dont_write_bytecode = True
|
||||||
|
ROOT = Path(__file__).resolve().parents[5]
|
||||||
|
|
||||||
|
|
||||||
|
def rejected(operation):
|
||||||
|
try:
|
||||||
|
operation()
|
||||||
|
except (ValueError, subprocess.CalledProcessError):
|
||||||
|
return
|
||||||
|
raise AssertionError('invalid input/response accepted')
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
spec = importlib.util.spec_from_file_location('bulk_gate', ROOT / 'tools/check_p04_order_evidence.py')
|
||||||
|
gate = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(gate)
|
||||||
|
with tempfile.TemporaryDirectory(prefix='p04-bulk-fixture-') as temporary:
|
||||||
|
base = Path(temporary)
|
||||||
|
repo = base / 'repo'
|
||||||
|
repo.mkdir()
|
||||||
|
gate.ROOT = repo
|
||||||
|
env = gate.git_environment()
|
||||||
|
env.update(GIT_AUTHOR_NAME='fixture', GIT_AUTHOR_EMAIL='fixture@invalid',
|
||||||
|
GIT_COMMITTER_NAME='fixture', GIT_COMMITTER_EMAIL='fixture@invalid')
|
||||||
|
|
||||||
|
def git(*args, data=None):
|
||||||
|
return subprocess.check_output(['git', '-c', 'core.hooksPath=/dev/null', *args],
|
||||||
|
input=data, cwd=repo, env=env)
|
||||||
|
|
||||||
|
git('init', '--quiet')
|
||||||
|
payloads = [b'\x00binary\r\nline\n', b'', bytes(range(256)) * 32768]
|
||||||
|
oids = [git('hash-object', '-w', '--stdin', data=data).decode().strip() for data in payloads]
|
||||||
|
name = '日本語の木.dat'
|
||||||
|
(repo / name).write_bytes(payloads[0])
|
||||||
|
git('add', '--', name)
|
||||||
|
git('commit', '--quiet', '-m', 'fixture')
|
||||||
|
entries = gate.tree_entries('HEAD') # ls-tree -z resolves the unicode path.
|
||||||
|
assert entries[name][1] == oids[0]
|
||||||
|
expected = {oid: git('cat-file', 'blob', oid) for oid in oids}
|
||||||
|
original_output = subprocess.check_output
|
||||||
|
with patch.object(gate.subprocess, 'check_output', wraps=original_output) as process:
|
||||||
|
assert gate.read_blobs(reversed(oids + [oids[0]])) == expected
|
||||||
|
assert process.call_count == 1
|
||||||
|
assert process.call_args.kwargs['input'] == ('\n'.join(sorted(set(oids))) + '\n').encode()
|
||||||
|
with patch.object(gate.subprocess, 'check_output', side_effect=AssertionError('process started')):
|
||||||
|
assert gate.read_blobs([]) == {}
|
||||||
|
for invalid in ('abc', 'g' * 40, 'a' * 39, 'a' * 41, 'HEAD', oids[0] + '\n', None):
|
||||||
|
rejected(lambda: gate.read_blobs([invalid]))
|
||||||
|
for oid in ('0' * 40, git('rev-parse', 'HEAD').decode().strip(),
|
||||||
|
git('rev-parse', 'HEAD^{tree}').decode().strip()):
|
||||||
|
rejected(lambda: gate.read_blobs([oid]))
|
||||||
|
|
||||||
|
oid = oids[0]
|
||||||
|
header = oid.encode() + b' blob '
|
||||||
|
valid = header + b'3\na\x00\n\n'
|
||||||
|
assert gate.parse_blob_batch(valid, [oid]) == {oid: b'a\x00\n'}
|
||||||
|
bad = [b'', header + b'3', header + b'3\na', valid[:-1],
|
||||||
|
header + b'999\na\n', header + b'1\nabc\n', valid + b'garbage',
|
||||||
|
oid.encode() + b' missing\n', oid.encode() + b' ambiguous\n',
|
||||||
|
header + b'-1\n', header + b'x\n', header + b'1.0\n',
|
||||||
|
b'f' * 40 + b' blob 0\n\n', oid.encode() + b' tree 0\n\n']
|
||||||
|
for response in bad:
|
||||||
|
rejected(lambda: gate.parse_blob_batch(response, [oid]))
|
||||||
|
with patch.object(gate.subprocess, 'check_output', return_value=response):
|
||||||
|
rejected(lambda: gate.read_blobs([oid]))
|
||||||
|
rejected(lambda: gate.parse_blob_batch(valid + valid, [oid]))
|
||||||
|
rejected(lambda: gate.parse_blob_batch(valid, [oid, oids[1]]))
|
||||||
|
with patch.object(gate.subprocess, 'check_output',
|
||||||
|
side_effect=subprocess.CalledProcessError(7, ['git'])):
|
||||||
|
rejected(lambda: gate.read_blobs([oid]))
|
||||||
|
|
||||||
|
with patch.dict(os.environ, BUILD_OUTPUT_DIR=str(base)):
|
||||||
|
with gate.isolated_objects():
|
||||||
|
private_data = b'isolated-only unique payload\x00\n'
|
||||||
|
private = subprocess.check_output(
|
||||||
|
['git', 'hash-object', '-w', '--stdin'], input=private_data,
|
||||||
|
cwd=repo, env=gate._GIT_ENV).decode().strip()
|
||||||
|
assert gate.read_blobs([private]) == {private: private_data}
|
||||||
|
rejected(lambda: gate.read_blobs([private]))
|
||||||
|
with gate.isolated_objects():
|
||||||
|
rejected(lambda: gate.read_blobs([private]))
|
||||||
|
assert gate.read_blobs(oids) == expected
|
||||||
|
assert gate._GIT_ENV is None
|
||||||
|
print('bulk reader equivalence controls completed')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run()
|
||||||
@@ -141,6 +141,44 @@ def git_bytes(*args):
|
|||||||
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
|
return subprocess.check_output(['git', '--no-replace-objects', *args], cwd=ROOT, env=env)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_blob_batch(stdout, oids):
|
||||||
|
"""Decode length-framed output without splitting binary payloads into lines."""
|
||||||
|
result = {}
|
||||||
|
offset = 0
|
||||||
|
for oid in oids:
|
||||||
|
end = stdout.find(b'\n', offset)
|
||||||
|
require(end >= 0, 'truncated blob batch header')
|
||||||
|
header = stdout[offset:end].split(b' ')
|
||||||
|
require(len(header) == 3 and header[0] == oid.encode('ascii')
|
||||||
|
and header[1] == b'blob' and re.fullmatch(rb'[0-9]+', header[2]) is not None,
|
||||||
|
'invalid blob batch header: ' + oid)
|
||||||
|
size = int(header[2])
|
||||||
|
start = end + 1
|
||||||
|
end = start + size
|
||||||
|
require(end < len(stdout) and stdout[end:end + 1] == b'\n',
|
||||||
|
'truncated blob batch payload or missing LF: ' + oid)
|
||||||
|
result[oid] = stdout[start:end]
|
||||||
|
offset = end + 1
|
||||||
|
require(offset == len(stdout), 'extra blob batch output')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def read_blobs(oids):
|
||||||
|
"""One process per request; no cache can cross object namespaces."""
|
||||||
|
requested = list(oids)
|
||||||
|
require(all(isinstance(oid, str) and re.fullmatch('[0-9a-f]{40}', oid) is not None
|
||||||
|
for oid in requested), 'invalid full blob OID')
|
||||||
|
requested = sorted(set(requested))
|
||||||
|
if not requested:
|
||||||
|
return {}
|
||||||
|
env = _GIT_ENV if _GIT_ENV is not None else git_environment()
|
||||||
|
# Inherit stderr and raise on nonzero status, exactly as git_bytes does.
|
||||||
|
stdout = subprocess.check_output(['git', '--no-replace-objects', 'cat-file', '--batch'],
|
||||||
|
input=('\n'.join(requested) + '\n').encode('ascii'),
|
||||||
|
cwd=ROOT, env=env)
|
||||||
|
return parse_blob_batch(stdout, requested)
|
||||||
|
|
||||||
|
|
||||||
def git_environment():
|
def git_environment():
|
||||||
env = {k: v for k, v in os.environ.items() if not k.startswith('GIT_')}
|
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',
|
env.update(GIT_OPTIONAL_LOCKS='0', GIT_NO_REPLACE_OBJECTS='1', GIT_CONFIG_NOSYSTEM='1',
|
||||||
@@ -197,9 +235,11 @@ def allowed_path(path):
|
|||||||
|
|
||||||
def product_manifest(entries):
|
def product_manifest(entries):
|
||||||
result = {}
|
result = {}
|
||||||
|
blobs = read_blobs(oid for path, (_, oid) in entries.items()
|
||||||
|
if selected_path(path) and path != CHECKER_PATH)
|
||||||
for path, (_, oid) in sorted(entries.items()):
|
for path, (_, oid) in sorted(entries.items()):
|
||||||
if selected_path(path) and path != CHECKER_PATH:
|
if selected_path(path) and path != CHECKER_PATH:
|
||||||
data = git_bytes('cat-file', 'blob', oid)
|
data = blobs[oid]
|
||||||
result[path] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
result[path] = {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -337,9 +377,10 @@ def actual_checkout():
|
|||||||
require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all',
|
require(not git_bytes('status', '--porcelain=v1', '--untracked-files=all',
|
||||||
'--ignore-submodules=none'), 'checkout is not clean')
|
'--ignore-submodules=none'), 'checkout is not clean')
|
||||||
require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution')
|
require(not git_bytes('ls-files', '--others', '-z'), 'untracked/ignored substitution')
|
||||||
|
blobs = read_blobs(oid for _, oid in entries.values())
|
||||||
for path, (mode, oid) in entries.items():
|
for path, (mode, oid) in entries.items():
|
||||||
actual = safe(ROOT, path)
|
actual = safe(ROOT, path)
|
||||||
require(actual.read_bytes() == git_bytes('cat-file', 'blob', oid), 'HEAD/tree/FS bytes: ' + path)
|
require(actual.read_bytes() == blobs[oid], 'HEAD/tree/FS bytes: ' + path)
|
||||||
require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode,
|
require(('100755' if actual.stat().st_mode & 0o111 else '100644') == mode,
|
||||||
'HEAD/tree/FS mode: ' + path)
|
'HEAD/tree/FS mode: ' + path)
|
||||||
# Also reject untracked directory symlinks, including ones ignored by Git.
|
# Also reject untracked directory symlinks, including ones ignored by Git.
|
||||||
|
|||||||
Reference in New Issue
Block a user