P19-03-TABLE: P19-03 分岐の到達性:原7名×3ルートの全到達表と未制作欠損監査

This commit is contained in:
Agent Control Runner
2026-10-17 20:12:39 +09:00
parent 806de25068
commit 903a2c566e
5 changed files with 9552 additions and 149 deletions
@@ -4,7 +4,7 @@
## 変更範囲
修復開始点e0420d57a381a1644357f0590536e1d47c9bca9fから、このディレクトリの指定5ファイルのみを変更した。原本、原子要件、work packages、decisions、TASK_SPEC、選定記録、frozen pins、ゲーム入力、marker registryは変更していない。git書込み、他worker起動、Runner操作、ゲーム起動は行っていない。素材追加なし。
Repair 1開始点e0420d57a381a1644357f0590536e1d47c9bca9fから、このディレクトリの指定5ファイルのみを変更した。原本、原子要件、work packages、decisions、TASK_SPEC、選定記録、frozen pins、ゲーム入力、marker registryは変更していない。git書込み、他worker起動、Runner操作、ゲーム起動は行っていない。素材追加なし。
## 要件と検査の対応
@@ -35,7 +35,7 @@
- LOW 中断参照: 全21セルのinterruption_cancellationに原本148・153行の本文とbaseline hash、および343–355行のconfirm_route_lockをlegacy_not_originalとして追加。人物固有欠損と担当は維持。
- LOW ハッシュ表示: 263a01b943d8056727335fb76977c010298e1c229077f8da76e48683ba2e89bb は TASK_SPEC.json file-bytes SHA-256。3af3d9b931edc5449796662ad69bb6f67c80b48827a86565b1952931db68dd15 は Runner git-show stripped-UTF-8 contract spec_sha256。上記SPEC_HASH_REPRESENTATIONS.jsonのunchanged_file=trueが証拠であり、両者は同じ無変更ファイルを表す。checker定数と出力にも区別を反映。Git blob OIDは別のgit_blob_oidフィールドでありfile-bytes SHA-256とは区別する。
## 実行記録(今回のローカル確認)
## Repair 1の実行記録(過去のローカル確認)
以下は正式Runner実行ではない。記録済み修復指示に基づき3登録コマンドをローカル実行した。
@@ -73,3 +73,53 @@ python3 -B /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachab
- D05、D08はpending。未知の閾値・率・初期値もpendingのまま。P04の既存D05選択、P13の4件の制作followupも未解決。
- この文書単位のnative GUI/Windows実行は適用外。global same-Windows、held gestures、全ゲーム受入はopen。固定入力一致から既存native証拠の正式再利用承認を推定していない。
- Runnerの3 validator、原本§6–7と元受入に対する実装者から独立したwhole-unit Opus review、その後の通常prepare-integration/finalが必要。どの完了数・元台帳も変更していない。
## Repair 2: BASE定義projectionと台帳更新の分離
開始点Sは806de25068bc423136063791afcea811d251ee1b。編集前にACTUAL_OPUS_REPAIR2_DIRECTION.jsonとACTUAL_FAILED_INTEGRATION.json、immutable TASK_SPECと既存チェッカーを確認。最新run指示に従い、実マージ候補の作成・検証は実装担当では行わない。指定5ファイルのみを変更する。Repair 1の4修正を保持し、任意の低3指摘は延期したまま。
### 実差分とallowlistの根拠
git showによるBASE 8ca9263とmain 6e17caeのkey-level比較を行った。candidate 1875b03のwork_packages.jsonは6e17caeと完全一致。差分は次だけ(配列index 19=P19、remaining_deliverables index 2=P19-03):
|JSON path|差分|
|---|---|
|/19/current_summary|変更|
|/19/updated_at_jst|変更|
|/19/remaining_deliverables/2/current_execution|TASK_SPEC.jsonからROOT_CURRENT_ACCEPTANCE_STATUS.jsonへの参照更新|
|/19/remaining_deliverables/2/current_summary|変更|
|/19/remaining_deliverables/2/progress_summary|変更|
|/19/remaining_deliverables/2/remaining_gaps/0, /1|通常integration/final待ちの進捗説明へ更新|
|/19/remaining_deliverables/2/evidence|1件から2件|
|/19/remaining_deliverables/2/submitted_commit|806de25068bc423136063791afcea811d251ee1bを追加|
|/19/remaining_deliverables/2/updated_at_jst|変更|
package allowlistはstate / legacy_state / current_summary / updated_at_jst。unit allowlistはstate / current_execution / remaining_gaps / updated_at_jst / submitted_commit / current_summary / progress_summary / evidence。state系は通常進捗遷移として明示的に許可し、同じ実registerに既存のキーだけを採用した。通常finalの完了時刻・source-ref用に、BASEの他unitで既に使われるcompleted_at_jst(P13-03)、implementation_commit(P13-03)、integrated_commit(P13-02)、integration_commit(P13-03)、normal_integration_candidate(P04-01)だけをunit階層で追加許可した。キーのprefixや任意のtimestamp/source_refキーは許可しない。allowlistの値は進捗メタデータとしてprojectionから全体を除外する。
それ以外の全キーは未知キーを含め再帰的な値・型・有無を保ち、BASE projectionと比較する。30 package / 88 unitのID・順序・件数、全title/work/acceptance、phase/parent/depends_on/requirement_ids/decisions/supplement_ids/owner_roles/state_scope等を固定する。atomic_requirements.jsonとdecisions.jsonは実main差分なしのため引き続きbyte-exact。D05/D08のstatus/answerは変更不可。selection recordもbyte-exact。
所有者集合・原子要件・authority_snapshotsはBASE blobのみから読む。各snapshotはanchored_base_commit、BASE blobのsha256、projected recordsを保持する。work packagesは全30/88定義を保持し、進捗を除外した。表の21セル、共通ルール・引用・status・owner・欠損・pending/null値は変更していない。Markdownには固定参照の説明だけを追加した。--writeは1回実行、--checkは読込み専用。
### Repair 2 ローカル実行
cwdとproject-rootは /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachability/P19-03-TABLE-implement。
以下は正式Runner試験ではない。実際の呼出しは各scriptをdocs/plans/original-completion-2026-10-02/execution/p19-03/配下の相対パスで指定し、--project-rootには上記実絶対パスを指定した。
- python3 -B docs/plans/original-completion-2026-10-02/execution/p19-03/check_reachability_table.py --project-root /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachability/P19-03-TABLE-implement --write: exit 0。
- p19-table: python3 -B docs/plans/original-completion-2026-10-02/execution/p19-03/check_reachability_table.py --project-root /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachability/P19-03-TABLE-implement --check: exit 0。
- p19-source-binding: python3 -B docs/plans/original-completion-2026-10-02/execution/p19-03/check_reachability_table.py --project-root /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachability/P19-03-TABLE-implement --source-binding-only: exit 0。
上記3呼出しのstdoutはaudit_integrity=pass、formal_acceptance=pending_runner_and_independent_review、actual_commit=806de25068bc423136063791afcea811d251ee1b、selected_files=1368、anchor=1b07b87d3736e777932315a4bdde8b70cbf9cb23、file_equality=true、native_evidence_reuse_accepted=false。spec file-bytesとRunner contractの両hashも従来値のまま。
実mainおよび失敗candidateの実blobをPath.read_bytes差替えで読ませる自己試験は、生成JSONとMarkdownのbyte完全一致を検証する。実registerの書換え・一時worktree・mergeは不要。合成完了/source-ref例も同じ検査を通す。各定義フィールドの変更・削除、package/unit objectの未知キー追加、package/unit/各定義listの件数/順序変更、D05/D08承認、atomic/decisions/selectionの変更を個別subtestで拒否する。既存12テストは維持。
実merged-candidate確認: **Root preflight pending**。新S提出と通常validation後、Rootが最新main上の実Sについてラベル付き非正式isolated preflightで同じ3チェッカーを実行する。その後に別Opus whole-original re-review、通常prepare-integration/finalが必要。今回のローカル成功やworker exit 0は受入ではない。正式進捗14/88は維持。native GUI/Windowsは本単位に適用外、global Windowsとゲーム受入はopen。
自己試験の探索版2回は全recordや深い履歴データの同型フィールドまで反復走査して時間がかかったため中断(各exit 130、KeyboardInterrupt)。package/unitの異なる定義キーごとに変更・削除を検査し、ネストした定義値は全体を比較する形へ整理した。BASE blobを一度実読込して負例ループ内で再利用し、各負例ごとのgit show起動を省いた。検査対象・既存controlは削除していない。shell here-documentを用いた初回の読取り差分確認は環境の一時ファイル制約で失敗(exit 1)、python3 -cで同じ確認を実行した。
最終p19-negative呼出し: python3 -B docs/plans/original-completion-2026-10-02/execution/p19-03/test_reachability_table.py --project-root /Users/korizatou114/Projects/TheTree-managed-worktrees/p19-03-reachability/P19-03-TABLE-implement
exit 0、出力: Ran 16 tests in 16.285s / OK。実main/candidate正例、合成final正例、全package/unit定義フィールド負例と既存controlを含む。git diff --checkもexit 0。最終差分は指定5ファイルのみ。生成前後のJSONを比較しauthority_snapshots以外の全フィールドが一致することも静的確認した。
同じp19-tableコマンドを絶対scriptパスで再確認(exit 0、同じpass出力)。呼出し前後にこのディレクトリの全通常ファイルSHA-256 mapを比較し一致: Read-only byte determinism: pass。最終git diff --checkもexit 0。
File diff suppressed because it is too large Load Diff
@@ -688,6 +688,8 @@ missing / owner: P19-02
- Windows: 未受入: global same-Windows acceptanceはopen。文書単位のnative GUI/Windows実行は適用外。
- 欠損機構: defense_reduction, post_break_metric, barrier_block, coassignment_attack, zero_break, contact_after_break, threshold_route_loss, duel_types, duel_win, duel_loss, love_actions, ntr_actions, corruption_actions, history_fields, history_cycle_parentage, portrait_marks, route_skills, departure
Authority snapshots: anchored_base_commit=8ca9263fee1ec4374d97c01fda82b95e1df6a977; JSON retains anchored blob SHA-256 and projected definition records.
D05/D08、閾値・率・初期値はpending。完全な出典本文・SHA・原子要件全レコードはJSONに保持する。
14/88 unchanged; formal Runner validation, independent whole-unit Opus review and normal integration/final required; no game acceptance
@@ -124,11 +124,50 @@ def binding(root):
'Runner_git_show_stripped_UTF8_contract_spec_sha256': RUNNER_CONTRACT_SPEC_SHA256}
# Exact key-level BASE -> 6e17cae/1875b03 diff; no prefix/wildcard exclusions.
PACKAGE_MUTABLE = frozenset(("state", "legacy_state", "current_summary", "updated_at_jst"))
UNIT_MUTABLE = frozenset((
"state", "current_execution", "remaining_gaps", "updated_at_jst",
"submitted_commit", "current_summary", "progress_summary", "evidence",
# Normal final/source references: these names already occur in BASE units.
"completed_at_jst", "implementation_commit", "integrated_commit",
"integration_commit", "normal_integration_candidate",
))
def definition_projection(register):
require(type(register) is list, "package register must be a list")
projected = []
for package in register:
require(type(package) is dict, "package must be an object")
record = {k: v for k, v in package.items() if k not in PACKAGE_MUTABLE}
units = record.get("remaining_deliverables")
require(type(units) is list, "deliverables must be a list")
require(all(type(unit) is dict for unit in units), "unit must be an object")
record["remaining_deliverables"] = [
{k: v for k, v in unit.items() if k not in UNIT_MUTABLE} for unit in units]
projected.append(record)
return projected
def validate_register(root, name, current):
anchored = blob(root, BASE, PLAN + name)
if name == "work_packages.json":
# JSON serialization preserves types (True must not compare equal to 1).
encode = lambda value: json.dumps(definition_projection(decode(value)),
ensure_ascii=False, sort_keys=True)
require(encode(current) == encode(anchored),
"original definition changed: " + name)
else:
require(current == anchored, "original register changed: " + name)
return anchored
def expected(root):
raw = lambda p: (root / p).read_bytes()
require(sha(raw(BASELINE)) == BASE_HASH, 'baseline changed')
for name in ('atomic_requirements.json', 'work_packages.json', 'decisions.json'):
require(raw(PLAN + name) == blob(root, BASE, PLAN + name), 'original register changed: ' + name)
anchored = {name: validate_register(root, name, raw(PLAN + name))
for name in ('atomic_requirements.json', 'work_packages.json', 'decisions.json')}
require(raw(OUT + 'ACTUAL_OPUS_NEXT_ORIGINAL_UNIT_SELECTION.json') ==
blob(root, BASE, OUT + 'ACTUAL_OPUS_NEXT_ORIGINAL_UNIT_SELECTION.json'), 'selection record changed')
lines = raw(BASELINE).decode().splitlines()
@@ -183,9 +222,9 @@ def expected(root):
['git','rev-parse',ANCHOR+':'+path],cwd=root).decode().strip()
rule['legacy_evidence'].append(entry)
# Read complete selected atomic records, not only their condition strings.
atoms = decode(raw(PLAN + 'atomic_requirements.json'))['requirements']
atoms = decode(anchored['atomic_requirements.json'])['requirements']
atoms = [x for x in atoms if set(x.get('source_group_ids', [])) & {'B-7.' + str(i) for i in range(1, 7)}]
owners = {x['id'] for p in decode(raw(PLAN + 'work_packages.json')) for x in p.get('remaining_deliverables', [])}
owners = {x['id'] for p in decode(anchored['work_packages.json']) for x in p.get('remaining_deliverables', [])}
require(all(r['owner'] in owners for r in rules), 'unknown original owner')
for key, name in PEOPLE:
for route in ROUTES:
@@ -226,10 +265,11 @@ def expected(root):
return {'task_id': 'P19-03-TABLE', 'schema_version': 1,
'identity_policy': '原本§6の名前とatomic entity.keyを監査IDに使用。実装IDを捏造しない。カタログにはheroine_anriのみ。OriginalRelationshipStateはsubject_idを保持し7名の列挙はしない。',
'source_anchor': ANCHOR, 'common_rules': rules, 'cells': cells,
'authority_snapshots': {p: {'sha256': sha(raw(p)), 'base_commit': BASE,
'records': decode(raw(p)) if p.endswith('decisions.json') else atoms if p.endswith('atomic_requirements.json')
else [x for x in decode(raw(p)) if x['id'] in ('P18', 'P19', 'P20', 'P26')]}
for p in (PLAN + 'atomic_requirements.json', PLAN + 'work_packages.json', PLAN + 'decisions.json')},
'authority_snapshots': {PLAN + name: {
'sha256': sha(data), 'anchored_base_commit': BASE,
'records': definition_projection(decode(data)) if name == 'work_packages.json'
else decode(data) if name == 'decisions.json' else atoms}
for name, data in anchored.items()},
'catalog_evidence': evidence(CATALOG, 1, 32),
'pending': {'D05': 'pending', 'D08': 'pending', 'thresholds': None, 'rates': None, 'initial_values': None},
'route_support': {r: {'UI': '欠損: 原本行動・3種決闘・原本3軸/盾の通常操作なし。既存汎用関係UIを原本ルートとしない。',
@@ -289,6 +329,8 @@ def markdown(d):
lines += ['### ' + route, '']
lines += ['- ' + k + ': ' + v for k, v in support.items() if isinstance(v, str)]
lines += ['- 欠損機構: ' + ', '.join(support['missing_mechanisms']), '']
lines += ['Authority snapshots: anchored_base_commit=' + BASE +
'; JSON retains anchored blob SHA-256 and projected definition records.', '']
lines += ['D05/D08、閾値・率・初期値はpending。完全な出典本文・SHA・原子要件全レコードはJSONに保持する。', '', d['accounting'], '']
return '\n'.join(lines)
@@ -21,6 +21,123 @@ class AuditTests(unittest.TestCase):
with self.assertRaises(ValueError):
audit.verify(changed, self.canonical)
def register_output(self, data):
original = Path.read_bytes
def read(path):
return data if path == ROOT / audit.PLAN / "work_packages.json" else original(path)
with patch.object(Path, "read_bytes", read):
document = audit.expected(ROOT)
import json
self.assertEqual(json.dumps(document, ensure_ascii=False, indent=2) + "\n",
(ROOT / audit.OUT / "REACHABILITY_TABLE.json").read_text())
self.assertEqual(audit.markdown(document).encode(),
(ROOT / audit.OUT / "REACHABILITY_TABLE.md").read_bytes())
def test_actual_main_and_candidate_metadata(self):
for commit in ("6e17cae", "1875b03"):
with self.subTest(commit=commit):
self.register_output(audit.blob(ROOT, commit, audit.PLAN + "work_packages.json"))
def test_final_ledger_metadata(self):
import json
register = audit.decode(audit.blob(ROOT, audit.BASE, audit.PLAN + "work_packages.json"))
package = next(p for p in register if p["id"] == "P19")
unit = next(u for u in package["remaining_deliverables"] if u["id"] == "P19-03")
package.update(state="completed", legacy_state="completed",
current_summary="ledger completion", updated_at_jst="2026-10-17")
unit.update({key: "synthetic final reference" for key in audit.UNIT_MUTABLE})
unit["evidence"] = [{"path": "docs/final.json", "sha256_at_integration": "0" * 64}]
unit["remaining_gaps"] = []
self.register_output(json.dumps(register).encode())
def test_every_register_definition_field(self):
import json
base = audit.decode(audit.blob(ROOT, audit.BASE, audit.PLAN + "work_packages.json"))
# Independently mutate/remove every package and unit definition field.
# Compare full nested values; unknown additions fail at both record levels.
def visit(value, path=(), level="packages"):
if isinstance(value, list):
yield path, "count"
if len(value) > 1:
yield path, "order"
for i, child in enumerate(value):
yield from visit(child, path + (i,),
"package" if not path else "unit" if path[-1] == "remaining_deliverables" else "nested")
elif isinstance(value, dict):
yield path, "unknown"
excluded = audit.PACKAGE_MUTABLE if level == "package" else audit.UNIT_MUTABLE if level == "unit" else ()
for key, child in value.items():
if key in excluded:
continue
yield path + (key,), "change"
yield path + (key,), "remove"
if key == "remaining_deliverables":
yield from visit(child, path + (key,), "units")
elif isinstance(child, list):
yield path + (key,), "count"
if len(child) > 1 and child[0] != child[1]:
yield path + (key,), "order"
seen = set()
anchored = audit.blob(ROOT, audit.BASE, audit.PLAN + "work_packages.json")
for path, operation in visit(base):
# Each distinct definition key/structural path once; repeated records
# use the same schema, so do not pad by testing it 30/88 times.
shape = tuple("*" if type(part) is int else part for part in path)
if (shape, operation) in seen:
continue
seen.add((shape, operation))
changed = copy.deepcopy(base)
target = changed
for key in path[:-1]:
target = target[key]
value = target[path[-1]] if path else changed
if operation == "change":
target[path[-1]] = {"altered_definition": True}
elif operation == "remove":
del target[path[-1]]
elif operation == "unknown":
value["unknown_metadata"] = "must fail"
elif operation == "count":
value.append({})
else:
value[0], value[1] = value[1], value[0]
with self.subTest(path=path, operation=operation), patch.object(audit, 'blob', return_value=anchored), self.assertRaises(ValueError):
audit.validate_register(ROOT, "work_packages.json", json.dumps(changed).encode())
def test_atomic_decision_and_selection_integrity(self):
import json
for name in ("atomic_requirements.json", "decisions.json"):
data = audit.blob(ROOT, audit.BASE, audit.PLAN + name)
with self.subTest(name=name), self.assertRaises(ValueError):
audit.validate_register(ROOT, name, data + b" ")
changed = audit.decode(data)
if name == "atomic_requirements.json":
changed["requirements"][0]["id"] = "changed_atomic_requirement"
with self.assertRaises(ValueError):
audit.validate_register(ROOT, name, json.dumps(changed).encode())
decisions = audit.decode(audit.blob(ROOT, audit.BASE, audit.PLAN + "decisions.json"))
def flip(value, identity):
if isinstance(value, dict):
if value.get("id") == identity:
value["status"] = "approved"
return True
return any(flip(v, identity) for v in value.values())
if isinstance(value, list):
return any(flip(v, identity) for v in value)
return False
for identity in ("D05", "D08"):
changed = copy.deepcopy(decisions)
self.assertTrue(flip(changed, identity))
with self.subTest(identity=identity), self.assertRaises(ValueError):
audit.validate_register(ROOT, "decisions.json", json.dumps(changed).encode())
original = Path.read_bytes
target = ROOT / audit.OUT / "ACTUAL_OPUS_NEXT_ORIGINAL_UNIT_SELECTION.json"
def corrupt(path):
return original(path) + (b" " if path == target else b"")
with patch.object(Path, "read_bytes", corrupt), self.assertRaises(ValueError):
audit.expected(ROOT)
def test_valid_implemented_partial_missing(self):
# All three legitimate status classes must survive serialization and the
# exact same verifier used by --check, without status promotion.