P13-03: 内容差異の記録

This commit is contained in:
Agent Control Runner
2026-10-15 19:39:35 +09:00
parent bc1299acf5
commit bd3a97ec74
5 changed files with 160 additions and 64 deletions
@@ -25,7 +25,7 @@
共有観測範囲(各施設の具体的検索結果は機械台帳に保持): 共有観測範囲(各施設の具体的検索結果は機械台帳に保持):
- normal_entry: adult_private_room is in the UI list and baseline catalog; black_market is expansion-only. No other original identity match. Static eligibility only; actual construction is unverified. - normal_entry: adult_private_room is in the UI list and baseline catalog; black_market is expansion-only. No other original identity match. Static eligibility only; actual construction is unverified.
- runtime: Runtime loads baseline/infrastructure/rescue/original paths, not expansion paths at this load site. Generic services do not prove original special behavior. - runtime: Runtime loads ORIGINAL_FACILITY_PATHS, including some files under the expansion directory. It does not load EXPANSION_FACILITY_PATHS or black_market. Generic services do not prove original special behavior.
- event_state: No facility-specific transition established by this search. Generic EventService is not a dedicated content catalog. - event_state: No facility-specific transition established by this search. Generic EventService is not a dedicated content catalog.
- save: Generic snapshot plumbing is a technical lead only. Facility event round-trip and original semantics remain unverified. - save: Generic snapshot plumbing is a technical lead only. Facility event round-trip and original semantics remain unverified.
- test: Selected generic suite sources searched; no formal suite run or dedicated original facility acceptance inferred. - test: Selected generic suite sources searched; no formal suite run or dedicated original facility acceptance inferred.
@@ -228,7 +228,7 @@ event/overlay/history/story/voice/scene の内容ID・数量・受入はunknown
- `docs/plans/original-completion-2026-10-02/asset_inventory_reaudit/facility_usage_slots.json` / current_checkout / SHA256 `56a031b6430aa9cb408f14b66efc5dd9a779841c0e7c19301945c49c12711491` - `docs/plans/original-completion-2026-10-02/asset_inventory_reaudit/facility_usage_slots.json` / current_checkout / SHA256 `56a031b6430aa9cb408f14b66efc5dd9a779841c0e7c19301945c49c12711491`
- `docs/plans/original-completion-2026-10-02/asset_inventory_reaudit/inventory_summary.json` / current_checkout / SHA256 `298b37e65c41febe8a2ccdea10bd105ab7a606904f06b16c1659cda6214859b9` - `docs/plans/original-completion-2026-10-02/asset_inventory_reaudit/inventory_summary.json` / current_checkout / SHA256 `298b37e65c41febe8a2ccdea10bd105ab7a606904f06b16c1659cda6214859b9`
- `docs/plans/original-completion-2026-10-02/decisions.json` / current_checkout / SHA256 `ad0673051b0fa601e525f05128e0b11e2dddd9d4bae6a3e0170bbf47da35efe9` - `docs/plans/original-completion-2026-10-02/decisions.json` / current_checkout / SHA256 `ad0673051b0fa601e525f05128e0b11e2dddd9d4bae6a3e0170bbf47da35efe9`
- `autoload/simulation_runtime.gd` / current_checkout / SHA256 `d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39` - `autoload/simulation_runtime.gd` / main_git_blob / SHA256 `4696df4a77feb465a5a3ba8b806569f7bcbe1d03c88289acfb78a3928f307d67`
- `data/facilities/adult_private_room.tres` / current_checkout / SHA256 `d8501586416b5c57a8b3b3b2afd1c0143428d8a4fa1df7112e995bd36649df6d` - `data/facilities/adult_private_room.tres` / current_checkout / SHA256 `d8501586416b5c57a8b3b3b2afd1c0143428d8a4fa1df7112e995bd36649df6d`
- `data/facilities/cafeteria.tres` / current_checkout / SHA256 `86d540fe2d78b9dcdbe5a0af6e4a001f4cec54934636ed01deb25a943c79128a` - `data/facilities/cafeteria.tres` / current_checkout / SHA256 `86d540fe2d78b9dcdbe5a0af6e4a001f4cec54934636ed01deb25a943c79128a`
- `data/facilities/expansion/black_market.tres` / current_checkout / SHA256 `c209d734c82487f6df4c03714618865d6a5f1d2cf991dc0056fe4bbad8f03a42` - `data/facilities/expansion/black_market.tres` / current_checkout / SHA256 `c209d734c82487f6df4c03714618865d6a5f1d2cf991dc0056fe4bbad8f03a42`
@@ -266,7 +266,7 @@ event/overlay/history/story/voice/scene の内容ID・数量・受入はunknown
- `data/facilities/standard_room.tres` / current_checkout / SHA256 `89f0a3a861d66afa4c1338ff1ec919685cf338b93b8da9a2387ce85e7e5bc336` - `data/facilities/standard_room.tres` / current_checkout / SHA256 `89f0a3a861d66afa4c1338ff1ec919685cf338b93b8da9a2387ce85e7e5bc336`
- `data/facilities/tavern.tres` / current_checkout / SHA256 `eb494ae6ab0d1b9e88ae0a38944609702e43c81a370a29cfb030af9ae46f523c` - `data/facilities/tavern.tres` / current_checkout / SHA256 `eb494ae6ab0d1b9e88ae0a38944609702e43c81a370a29cfb030af9ae46f523c`
- `data/facilities/warehouse.tres` / current_checkout / SHA256 `d0fcf0528da66da7b354ab5efaafe2483a7a76e05b324db255700ef73cdfc791` - `data/facilities/warehouse.tres` / current_checkout / SHA256 `d0fcf0528da66da7b354ab5efaafe2483a7a76e05b324db255700ef73cdfc791`
- `scenes/main/main.gd` / current_checkout / SHA256 `a3d7d7ddd08a8df1f987806e60accf51792d728cf71c06e6b822f5a31d8ed3e3` - `scenes/main/main.gd` / main_git_blob / SHA256 `92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a`
- `scripts/core/save_service.gd` / current_checkout / SHA256 `0c158d3e6ee693b89814bf232c954e4757c5ff91dc9df03bb322c3e593e73617` - `scripts/core/save_service.gd` / current_checkout / SHA256 `0c158d3e6ee693b89814bf232c954e4757c5ff91dc9df03bb322c3e593e73617`
- `scripts/data/facility_catalog.gd` / current_checkout / SHA256 `7bb0c5085ddb361d384314cb219620d1483840942bc3f0f46dcb9dfa29d56e29` - `scripts/data/facility_catalog.gd` / current_checkout / SHA256 `7bb0c5085ddb361d384314cb219620d1483840942bc3f0f46dcb9dfa29d56e29`
- `scripts/events/event_definition.gd` / current_checkout / SHA256 `4af68f2e5ec185a750d59aaa24c7cb63fe612cd6a785e7fd39af11f0b5ce74a2` - `scripts/events/event_definition.gd` / current_checkout / SHA256 `4af68f2e5ec185a750d59aaa24c7cb63fe612cd6a785e7fd39af11f0b5ce74a2`
@@ -278,12 +278,10 @@ event/overlay/history/story/voice/scene の内容ID・数量・受入はunknown
## 統合監査注記 ## 統合監査注記
- Read-only integration audit: contract base e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1 versus main 57131ec. This is a future refresh instruction, not a relabeling of any recorded source/game/asset hash. - The two target sources are immutable main Git blobs at 57131ec616ba06a71d3d97eaea738f209f965349. Matches are re-searched from those bytes with main_git_blob provenance.
- autoload/simulation_runtime.gd adds preview_build_facility and extracts _build_facility_precheck shared by preview and execution. scenes/main/world_preview.gd routes previews through Runtime with a fallback and adds demolition mode. scenes/main/main.gd adds demolition confirmation/command handling, preview error messages and stricter interface-selection version checks. - Read-only Git ancestry determines applicability: descendant HEAD requires working files equal to main blobs; non-descendant HEAD requires base blobs. Git errors reject. Report flags cannot select a mode.
- At normal integration Root must refresh normal_entry observations for original_facility_13, original_facility_14, original_facility_15, original_facility_16, original_facility_24, original_facility_25 and original_facility_26 against the integrated main.gd and preview path; refresh runtime observations against the integrated Runtime. Refresh event_state and save search evidence too because their Runtime source hash changes, even though these changed hunks do not establish dedicated original facility events or save round-trips. Recheck mapping/catalog/definitions without promoting missing identities or black_market eligibility. - Other current-checkout checks remain strict; work packages remain frozen at the contract base. All unknown/unverified states and 30/88 accounting remain unchanged.
- The selected test sources in this ledger, facility catalog, event service and save service are unchanged between these commits. Added tests/transport/test_runtime_build_preview.gd and tests/ui/test_p08_normal_placement_ops.gd concern generic preview/placement/demolition; tests/ui/test_selection_departure.gd and tests/ui/fixtures/selection_cross_process.gd concern selection restoration. Root should assess their bounded relevance when refreshing each facility test observation; none accepts original content. Guest observation selection changes and P08 migration fixtures likewise provide no original facility content acceptance. - Formal Runner validation and separate independent review remain external requirements; static observations confer no content completion credit.
- Minimal handling: retain this base-version evidence, explicitly re-search changed integrated source paths and rebuild affected technical observations, source pins and output receipt as a reviewed integration refresh; retain all unknown/unverified content states and the 30/88 denominator. Do not merely replace old hashes or claim old tests ran against new code. No rebase, overwrite or Git write is performed here.
- If P13 integrates first, P23 9a full tools map gains tools/check_p13_content_difference.py. P23 proof remains bound to its own version and requires future final source verification; this audit does not extend that proof.
## 検証・統合条件 ## 検証・統合条件
@@ -1,3 +1,7 @@
# Superseded historical repair instructions
The old repair-2 limit and 500000/450000-byte caps below are superseded by the current owner direction. Preserve required evidence without omissions or cap-driven compression. The prior failed invocation remains history, not current readiness. The immutable spec, scope and acceptance remain in force.
# P13-03 final bounded repair input (root context, not implementation) # P13-03 final bounded repair input (root context, not implementation)
Original contract/spec/acceptance1 and repair cap2 remain unchanged. Repair1 used. Initial submitted7645c55149d3bebf3ed602cf3039b15b28fa376f records seven facilities,84slots,six non-facility categories. Root read-only diagnostic ran its --negative-checks: integrity pass,13 mutations rejected. This is not formal acceptance. Original contract/spec/acceptance1 and repair cap2 remain unchanged. Repair1 used. Initial submitted7645c55149d3bebf3ed602cf3039b15b28fa376f records seven facilities,84slots,six non-facility categories. Root read-only diagnostic ran its --negative-checks: integrity pass,13 mutations rejected. This is not formal acceptance.
File diff suppressed because one or more lines are too long
@@ -152,8 +152,9 @@
}, },
{ {
"path": "autoload/simulation_runtime.gd", "path": "autoload/simulation_runtime.gd",
"sha256": "d52a995ae715542bbc550f68d35889540658275d12e04ae0b21ac276f5843f39", "sha256": "4696df4a77feb465a5a3ba8b806569f7bcbe1d03c88289acfb78a3928f307d67",
"applicability": "current_checkout", "applicability": "main_git_blob",
"commit": "57131ec616ba06a71d3d97eaea738f209f965349",
"scope": "Bounded static observation; no executed connection/content acceptance." "scope": "Bounded static observation; no executed connection/content acceptance."
}, },
{ {
@@ -380,8 +381,9 @@
}, },
{ {
"path": "scenes/main/main.gd", "path": "scenes/main/main.gd",
"sha256": "a3d7d7ddd08a8df1f987806e60accf51792d728cf71c06e6b822f5a31d8ed3e3", "sha256": "92677e4ac57ed8d418a4a716bf0874cbbaea79cff893e5163c82ad1f75e7140a",
"applicability": "current_checkout", "applicability": "main_git_blob",
"commit": "57131ec616ba06a71d3d97eaea738f209f965349",
"scope": "Bounded static observation; no executed connection/content acceptance." "scope": "Bounded static observation; no executed connection/content acceptance."
}, },
{ {
@@ -434,28 +436,24 @@
} }
], ],
"output_pins": { "output_pins": {
"docs/plans/original-completion-2026-10-02/execution/p13-03/difference-ledger.json": "b7edd7fc8f5fa5602edc540a3eb8c4150b0268e7551c97a5c5ccc725ea5bb341", "docs/plans/original-completion-2026-10-02/execution/p13-03/difference-ledger.json": "d9aa4725e191d9cef194b01695fe18c6c6ecdc5140c7dc0e72d9409dc1606630",
"docs/plans/original-completion-2026-10-02/execution/p13-03/DIFFERENCE_LEDGER.md": "e5964c169bae48a7ae937d8b6a5103ebc34f5f0860b69b18b0755d6f3b2a480a", "docs/plans/original-completion-2026-10-02/execution/p13-03/DIFFERENCE_LEDGER.md": "1217a2356432cbf833e0baab443ee48a39776a3747e965e4fc3f914066959dcd",
"tools/check_p13_content_difference.py": "bf09c77441e821779a381b34b7ab97f754b38b7bc0f778b27c6fb2ebe29a75aa" "tools/check_p13_content_difference.py": "7b29180c776218138bfe1bf23013102932d853481cff41e7d830f1f5cf5c4d26"
}, },
"status": "blocked", "status": "ready_for_external_validation",
"formal_tests": "not_run", "formal_tests": "external_runner_required",
"formal_claude_review": "not_run", "formal_claude_review": "external_independent_review_required",
"worker_local_checks": { "worker_history": [
"python3_B_integrity_and_mandatory_negatives": "pass", "Earlier invocation failed because a literal python alias was unavailable; historical failure is not current readiness or formal acceptance."
"ast_parse": "pass", ],
"registered_argv": "blocked: python executable is unavailable in this worker environment", "command": "{python} -B tools/check_p13_content_difference.py --verify-only",
"git_diff_check": "pass",
"acceptance": "not_claimed"
},
"command": "python -B tools/check_p13_content_difference.py --verify-only",
"purpose": "Verify record integrity and negative rejection only; not content acceptance.", "purpose": "Verify record integrity and negative rejection only; not content acceptance.",
"source_applicability": "Exact IDs, source SHA256 and scopes are in difference-ledger.json. Work package frozen base Git blob and current checkout are separately identified.", "source_applicability": "Exact IDs, source SHA256 and scopes are in difference-ledger.json. Work package base_git_blob, two main_git_blob sources and other current_checkout sources are separately identified.",
"required_before_integration": [ "required_before_integration": [
"Runner p13-difference pass", "Runner p13-difference pass",
"separate formal Claude review pass" "separate formal Claude review pass"
], ],
"worker_tests_run": true, "record_readiness": "Canonical records prepared; formal validation and review are external.",
"test_applicability": { "test_applicability": {
"runtime": "Not applicable to records-only changes; no game or platform acceptance.", "runtime": "Not applicable to records-only changes; no game or platform acceptance.",
"native": "Not applicable to records-only changes; no game or platform acceptance.", "native": "Not applicable to records-only changes; no game or platform acceptance.",
+130 -34
View File
@@ -12,6 +12,7 @@ from pathlib import Path
import re import re
import subprocess import subprocess
import sys import sys
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
OUT = 'docs/plans/original-completion-2026-10-02/execution/p13-03/' OUT = 'docs/plans/original-completion-2026-10-02/execution/p13-03/'
@@ -20,6 +21,8 @@ MAPPING = 'docs/plans/original-completion-2026-09-29/execution/original_facility
SLOTS = PLAN + 'asset_inventory_reaudit/facility_usage_slots.json' SLOTS = PLAN + 'asset_inventory_reaudit/facility_usage_slots.json'
SPEC = 'docs/spec/acceptance/P13-03.json' SPEC = 'docs/spec/acceptance/P13-03.json'
BASE = 'e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1' BASE = 'e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1'
MAIN = '57131ec616ba06a71d3d97eaea738f209f965349'
TARGET_PATHS = ('autoload/simulation_runtime.gd', 'scenes/main/main.gd')
SPEC_RAW = '917cf1504bbfc700e0da42b2db496b42486fe3c36c680f301f58cf34c95267b6' SPEC_RAW = '917cf1504bbfc700e0da42b2db496b42486fe3c36c680f301f58cf34c95267b6'
SPEC_RUNNER = 'e1255835c1227e02bac42eb99b026b0fcbe69c272f9ed0b36199a2daf8d55c53' SPEC_RUNNER = 'e1255835c1227e02bac42eb99b026b0fcbe69c272f9ed0b36199a2daf8d55c53'
VIEWS = ('normal_entry', 'runtime', 'event_state', 'save', 'test') VIEWS = ('normal_entry', 'runtime', 'event_state', 'save', 'test')
@@ -33,12 +36,10 @@ CATEGORIES = {
} }
INTEGRATION_NOTES = [ INTEGRATION_NOTES = [
'Read-only integration audit: contract base e1a1651e15bbc9a0526ef31d0b2786feb20d8cd1 versus main 57131ec. This is a future refresh instruction, not a relabeling of any recorded source/game/asset hash.', 'The two target sources are immutable main Git blobs at ' + MAIN + '. Matches are re-searched from those bytes with main_git_blob provenance.',
'autoload/simulation_runtime.gd adds preview_build_facility and extracts _build_facility_precheck shared by preview and execution. scenes/main/world_preview.gd routes previews through Runtime with a fallback and adds demolition mode. scenes/main/main.gd adds demolition confirmation/command handling, preview error messages and stricter interface-selection version checks.', 'Read-only Git ancestry determines applicability: descendant HEAD requires working files equal to main blobs; non-descendant HEAD requires base blobs. Git errors reject. Report flags cannot select a mode.',
'At normal integration Root must refresh normal_entry observations for original_facility_13, original_facility_14, original_facility_15, original_facility_16, original_facility_24, original_facility_25 and original_facility_26 against the integrated main.gd and preview path; refresh runtime observations against the integrated Runtime. Refresh event_state and save search evidence too because their Runtime source hash changes, even though these changed hunks do not establish dedicated original facility events or save round-trips. Recheck mapping/catalog/definitions without promoting missing identities or black_market eligibility.', 'Other current-checkout checks remain strict; work packages remain frozen at the contract base. All unknown/unverified states and 30/88 accounting remain unchanged.',
'The selected test sources in this ledger, facility catalog, event service and save service are unchanged between these commits. Added tests/transport/test_runtime_build_preview.gd and tests/ui/test_p08_normal_placement_ops.gd concern generic preview/placement/demolition; tests/ui/test_selection_departure.gd and tests/ui/fixtures/selection_cross_process.gd concern selection restoration. Root should assess their bounded relevance when refreshing each facility test observation; none accepts original content. Guest observation selection changes and P08 migration fixtures likewise provide no original facility content acceptance.', 'Formal Runner validation and separate independent review remain external requirements; static observations confer no content completion credit.',
'Minimal handling: retain this base-version evidence, explicitly re-search changed integrated source paths and rebuild affected technical observations, source pins and output receipt as a reviewed integration refresh; retain all unknown/unverified content states and the 30/88 denominator. Do not merely replace old hashes or claim old tests ran against new code. No rebase, overwrite or Git write is performed here.',
'If P13 integrates first, P23 9a full tools map gains tools/check_p13_content_difference.py. P23 proof remains bound to its own version and requires future final source verification; this audit does not extend that proof.',
] ]
@@ -67,27 +68,59 @@ def load(path):
return json.loads(raw(path), object_pairs_hook=unique) return json.loads(raw(path), object_pairs_hook=unique)
def git_blob(commit, path):
return subprocess.check_output(['git', 'show', commit + ':' + path], cwd=ROOT)
def main_is_ancestor():
result = subprocess.run(['git', 'merge-base', '--is-ancestor', MAIN, 'HEAD'],
cwd=ROOT, capture_output=True)
if result.returncode not in (0, 1):
raise ValueError('Git ancestry check failed: ' + result.stderr.decode(errors='replace'))
return result.returncode == 0
def check_target_sources():
commit = MAIN if main_is_ancestor() else BASE
for path in TARGET_PATHS:
if raw(path) != git_blob(commit, path):
raise ValueError('stale working source: ' + path)
def source_bytes(path):
return git_blob(MAIN, path) if path in TARGET_PATHS else raw(path)
def provenance(path):
record = {'path': path, 'sha256': sha(source_bytes(path)),
'applicability': 'main_git_blob' if path in TARGET_PATHS else 'current_checkout'}
if path in TARGET_PATHS:
record['commit'] = MAIN
return record
def observation(paths, tokens, note): def observation(paths, tokens, note):
"""Exact token matches are search observations, never accepted links.""" """Exact token matches are search observations, never accepted links."""
evidence = [] evidence = []
for path in paths: for path in paths:
lines = raw(path).decode('utf-8').splitlines() lines = source_bytes(path).decode('utf-8').splitlines()
matches = [{'line': i + 1, 'text': line} for i, line in enumerate(lines) matches = [{'line': i + 1, 'text': line} for i, line in enumerate(lines)
if any(token in line for token in tokens)] if any(token in line for token in tokens)]
evidence.append({'path': path, 'sha256': sha(raw(path)), evidence.append({**provenance(path),
'searched_tokens': tokens, 'matches': matches, 'searched_tokens': tokens, 'matches': matches,
'scope': 'Static search of this file only; no execution or acceptance.'}) 'scope': 'Static search of this file only; no execution or acceptance.'})
return {'status': 'unverified', 'accepted': False, 'note': note, 'evidence': evidence} return {'status': 'unverified', 'accepted': False, 'note': note, 'evidence': evidence}
def expected(): def expected():
check_target_sources()
if sha(raw(SPEC)) != SPEC_RAW or sha(raw(SPEC).decode().strip().encode()) != SPEC_RUNNER: if sha(raw(SPEC)) != SPEC_RAW or sha(raw(SPEC).decode().strip().encode()) != SPEC_RUNNER:
raise ValueError('specification hash mismatch') raise ValueError('specification hash mismatch')
spec = load(SPEC) spec = load(SPEC)
sources = [] sources = []
for path, pin in spec['source_pins'].items(): for path, pin in spec['source_pins'].items():
if path == PLAN + 'work_packages.json': if path == PLAN + 'work_packages.json':
frozen = subprocess.check_output(['git', 'show', BASE + ':' + path], cwd=ROOT) frozen = git_blob(BASE, path)
if sha(frozen) != pin: if sha(frozen) != pin:
raise ValueError('frozen work package pin mismatch') raise ValueError('frozen work package pin mismatch')
sources.append({'path': path, 'sha256': pin, 'applicability': 'base_git_blob', 'commit': BASE}) sources.append({'path': path, 'sha256': pin, 'applicability': 'base_git_blob', 'commit': BASE})
@@ -139,7 +172,7 @@ def expected():
'normal_entry': observation([entry, catalog], tokens, 'normal_entry': observation([entry, catalog], tokens,
'adult_private_room is in the UI list and baseline catalog; black_market is expansion-only. No other original identity match. Static eligibility only; actual construction is unverified.'), 'adult_private_room is in the UI list and baseline catalog; black_market is expansion-only. No other original identity match. Static eligibility only; actual construction is unverified.'),
'runtime': observation([runtime, catalog] + matching, tokens + ['FacilityCatalog.FACILITY_PATHS'], 'runtime': observation([runtime, catalog] + matching, tokens + ['FacilityCatalog.FACILITY_PATHS'],
'Runtime loads baseline/infrastructure/rescue/original paths, not expansion paths at this load site. Generic services do not prove original special behavior.'), 'Runtime loads ORIGINAL_FACILITY_PATHS, including some files under the expansion directory. It does not load EXPANSION_FACILITY_PATHS or black_market. Generic services do not prove original special behavior.'),
'event_state': observation([runtime, event], tokens, 'event_state': observation([runtime, event], tokens,
'No facility-specific transition established by this search. Generic EventService is not a dedicated content catalog.'), 'No facility-specific transition established by this search. Generic EventService is not a dedicated content catalog.'),
'save': observation([runtime, save], tokens + ['event_service.create_snapshot()', 'save_snapshot'], 'save': observation([runtime, save], tokens + ['event_service.create_snapshot()', 'save_snapshot'],
@@ -171,7 +204,7 @@ def expected():
additional = sorted(set(search_paths + [entry, runtime, event, save, test, additional = sorted(set(search_paths + [entry, runtime, event, save, test,
'tests/s2/test_facility_service.gd', 'tests/s4/test_runtime_relationship_events.gd', 'tests/s2/test_facility_service.gd', 'tests/s4/test_runtime_relationship_events.gd',
'scripts/events/event_definition.gd', 'scripts/ui/event_log_dialog.gd'])) 'scripts/events/event_definition.gd', 'scripts/ui/event_log_dialog.gd']))
sources += [{'path': p, 'sha256': sha(raw(p)), 'applicability': 'current_checkout', sources += [{**provenance(p),
'scope': 'Bounded static observation; no executed connection/content acceptance.'} for p in additional] 'scope': 'Bounded static observation; no executed connection/content acceptance.'} for p in additional]
return {'schema_version': 1, 'task_id': 'P13-03', 'acceptance_ids': ['P13-03'], return {'schema_version': 1, 'task_id': 'P13-03', 'acceptance_ids': ['P13-03'],
'requirement_ids': spec['package_requirement_ids'], 'validation_ids': ['p13-difference'], 'requirement_ids': spec['package_requirement_ids'], 'validation_ids': ['p13-difference'],
@@ -231,7 +264,7 @@ def compact(document):
if signature not in evidence_ids: if signature not in evidence_ids:
identity = 'evidence_%04d' % (len(evidence) + 1) identity = 'evidence_%04d' % (len(evidence) + 1)
evidence_ids[signature] = identity evidence_ids[signature] = identity
provenance = {k: record[k] for k in ('path', 'sha256', 'scope')} provenance = {k: v for k, v in record.items() if k not in ('searched_tokens', 'matches')}
evidence[identity] = { evidence[identity] = {
'source_ref': context(provenance), 'source_ref': context(provenance),
'search_ref': context({'searched_tokens': record['searched_tokens']}), 'search_ref': context({'searched_tokens': record['searched_tokens']}),
@@ -270,9 +303,11 @@ def expand(document):
return visit(document) return visit(document)
def negative_checks(canonical): def negative_checks(canonical, ledger_path=OUT + "difference-ledger.json"):
cases = {} cases = {}
mutations = { mutations = {
'tampered_ancestry_flag': lambda d: d.update(main_is_ancestor=False),
'tampered_report_flag': lambda d: d.update(source_mode='legacy_fallback'),
'facility_omission': lambda d: d['facilities'].pop(), 'facility_omission': lambda d: d['facilities'].pop(),
'slot_omission': lambda d: d['facilities'][0]['media_usage_slots'].pop(), 'slot_omission': lambda d: d['facilities'][0]['media_usage_slots'].pop(),
'stale_pin': lambda d: d['sources'][0].update(sha256='0' * 64), 'stale_pin': lambda d: d['sources'][0].update(sha256='0' * 64),
@@ -301,27 +336,91 @@ def negative_checks(canonical):
cases[name] = 'rejected' cases[name] = 'rejected'
else: else:
raise ValueError('negative case was accepted: ' + name) raise ValueError('negative case was accepted: ' + name)
# Inject source bytes and Git results only in memory; no product/Git writes.
real_raw, real_blob = raw, git_blob
snapshots = {(commit, path): real_blob(commit, path)
for commit in (BASE, MAIN) for path in TARGET_PATHS}
def rejected(name, operation):
try:
operation()
except (ValueError, OSError, subprocess.CalledProcessError):
cases[name] = 'rejected'
else:
raise ValueError('negative case was accepted: ' + name)
for descendant in (False, True):
commit = MAIN if descendant else BASE
label = 'merged' if descendant else 'frozen'
def working(path):
return snapshots[commit, path] if path in TARGET_PATHS else real_raw(path)
with patch(__name__ + '.main_is_ancestor', return_value=descendant):
with patch(__name__ + '.raw', side_effect=working):
verify(compact(expected()), canonical)
for target in TARGET_PATHS:
def corrupt(path):
data = working(path)
return data + b'\n# corrupted source' if path == target else data
with patch(__name__ + '.raw', side_effect=corrupt):
rejected(label + '_stale_' + target, expected)
other = BASE if descendant else MAIN
def wrong_version(path):
return snapshots[other, path] if path in TARGET_PATHS else real_raw(path)
with patch(__name__ + '.raw', side_effect=wrong_version):
rejected(label + '_wrong_snapshot', expected)
def corrupt_blob(version, path):
data = real_blob(version, path)
return data + b'\n# corrupted blob' if version == MAIN else data
with patch(__name__ + '.raw', side_effect=working), patch(__name__ + '.git_blob', side_effect=corrupt_blob):
rejected(label + '_corrupt_git_blob', lambda: verify(compact(expected()), canonical))
for result in (subprocess.CompletedProcess([], 2, b'', b'Git failure'),
subprocess.CompletedProcess([], -1, b'', b'interrupted')):
with patch.object(subprocess, 'run', return_value=result):
rejected('ancestry_git_error_' + str(result.returncode), expected)
with patch.object(subprocess, 'run', side_effect=OSError('Git unavailable')):
rejected('ancestry_git_unavailable', expected)
with patch(__name__ + '.git_blob', side_effect=subprocess.CalledProcessError(128, ['git', 'show'])):
rejected('blob_git_error', expected)
# Exercise the same read-only alternate input path used by the CLI.
alternate = OUT + 'in_memory_candidate.json'
ledger_bytes = real_raw(ledger_path)
def alternate_raw(path):
if path == alternate:
return ledger_bytes
if path == OUT + 'difference-ledger.json':
raise ValueError('default ledger must not be read for alternate input')
return real_raw(path)
with patch(__name__ + '.raw', side_effect=alternate_raw):
verify_records(alternate, canonical)
cases['alternate_ledger_valid'] = 'verified_read_only'
with patch(__name__ + '.raw', side_effect=lambda path: b'{}' if path == alternate else real_raw(path)):
rejected('alternate_ledger_corrupt', lambda: verify_records(alternate, canonical))
return cases return cases
def verify_records(ledger_path, canonical):
verify(load(ledger_path), canonical)
if load(OUT + 'validation.json') != receipt_expected(ledger_path):
raise ValueError('receipt mismatch or false formal acceptance')
if raw(OUT + 'DIFFERENCE_LEDGER.md').decode() != readable(expand(canonical)):
raise ValueError('readable ledger differs from machine ledger')
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--ledger', default=OUT + 'difference-ledger.json') parser.add_argument('--ledger', default=OUT + 'difference-ledger.json',
help='Read-only checkout-relative alternate ledger; must match canonical records and the registered receipt hash. No output is written.')
parser.add_argument('--negative-checks', action='store_true') parser.add_argument('--negative-checks', action='store_true')
parser.add_argument('--verify-only', action='store_true', parser.add_argument('--verify-only', action='store_true',
help='Read-only verification; negative checks are always mandatory.') help='Read-only verification; negative checks are always mandatory.')
args = parser.parse_args() args = parser.parse_args()
try: try:
canonical = compact(expected()) canonical = compact(expected())
verify(load(args.ledger), canonical) verify_records(args.ledger, canonical)
receipt = load(OUT + 'validation.json')
if receipt != receipt_expected():
raise ValueError('receipt mismatch or false formal acceptance')
if raw(OUT + 'DIFFERENCE_LEDGER.md').decode() != readable(expand(canonical)):
raise ValueError('readable ledger differs from machine ledger')
result = {'record_integrity': 'pass', 'content_acceptance': 'not_claimed', result = {'record_integrity': 'pass', 'content_acceptance': 'not_claimed',
'formal_review': 'external_runner_required'} 'formal_review': 'external_runner_required'}
result['negative_checks'] = negative_checks(canonical) result['negative_checks'] = negative_checks(canonical, args.ledger)
print(json.dumps(result, ensure_ascii=False)) print(json.dumps(result, ensure_ascii=False))
return 0 return 0
except (ValueError, OSError, KeyError, TypeError, subprocess.CalledProcessError) as error: except (ValueError, OSError, KeyError, TypeError, subprocess.CalledProcessError) as error:
@@ -329,28 +428,25 @@ def main():
return 1 return 1
def receipt_expected(): def receipt_expected(ledger_path=OUT + 'difference-ledger.json'):
ledger = expand(load(OUT + 'difference-ledger.json')) ledger = expand(load(ledger_path))
return {'task_id': 'P13-03', 'acceptance_ids': ['P13-03'], 'validation_ids': ['p13-difference'], return {'task_id': 'P13-03', 'acceptance_ids': ['P13-03'], 'validation_ids': ['p13-difference'],
'requirement_ids': ledger['requirement_ids'], 'requirement_ids': ledger['requirement_ids'],
'facility_ids': [f['original_id'] for f in ledger['facilities']], 'facility_ids': [f['original_id'] for f in ledger['facilities']],
'slot_ids': [s['slot_id'] for f in ledger['facilities'] for s in f['media_usage_slots']], 'slot_ids': [s['slot_id'] for f in ledger['facilities'] for s in f['media_usage_slots']],
'difference_ids': [n['difference_id'] for n in ledger['nonfacility_differences']], 'difference_ids': [n['difference_id'] for n in ledger['nonfacility_differences']],
'source_applicability_records': ledger['sources'], 'source_applicability_records': ledger['sources'],
'output_pins': {p: sha(raw(p)) for p in [OUT + 'difference-ledger.json', 'output_pins': {p: sha(raw(ledger_path if p == OUT + 'difference-ledger.json' else p)) for p in [OUT + 'difference-ledger.json',
OUT + 'DIFFERENCE_LEDGER.md', 'tools/check_p13_content_difference.py']}, OUT + 'DIFFERENCE_LEDGER.md', 'tools/check_p13_content_difference.py']},
'status': 'blocked', 'formal_tests': 'not_run', 'formal_claude_review': 'not_run', 'status': 'ready_for_external_validation',
'worker_local_checks': { 'formal_tests': 'external_runner_required',
'python3_B_integrity_and_mandatory_negatives': 'pass', 'formal_claude_review': 'external_independent_review_required',
'ast_parse': 'pass', 'worker_history': ['Earlier invocation failed because a literal python alias was unavailable; historical failure is not current readiness or formal acceptance.'],
'registered_argv': 'blocked: python executable is unavailable in this worker environment', 'command': '{python} -B tools/check_p13_content_difference.py --verify-only',
'git_diff_check': 'pass',
'acceptance': 'not_claimed'},
'command': 'python -B tools/check_p13_content_difference.py --verify-only',
'purpose': 'Verify record integrity and negative rejection only; not content acceptance.', 'purpose': 'Verify record integrity and negative rejection only; not content acceptance.',
'source_applicability': 'Exact IDs, source SHA256 and scopes are in difference-ledger.json. Work package frozen base Git blob and current checkout are separately identified.', 'source_applicability': 'Exact IDs, source SHA256 and scopes are in difference-ledger.json. Work package base_git_blob, two main_git_blob sources and other current_checkout sources are separately identified.',
'required_before_integration': ['Runner p13-difference pass', 'separate formal Claude review pass'], 'required_before_integration': ['Runner p13-difference pass', 'separate formal Claude review pass'],
'worker_tests_run': True, 'record_readiness': 'Canonical records prepared; formal validation and review are external.',
'test_applicability': {name: 'Not applicable to records-only changes; no game or platform acceptance.' 'test_applicability': {name: 'Not applicable to records-only changes; no game or platform acceptance.'
for name in ['runtime', 'native', 'Windows', 'engine', 'full_game_regression']}} for name in ['runtime', 'native', 'Windows', 'engine', 'full_game_regression']}}