P13-03: 内容差異の記録

This commit is contained in:
Agent Control Runner
2026-10-15 19:21:10 +09:00
parent 7645c55149
commit bc1299acf5
5 changed files with 195 additions and 7853 deletions
+112 -16
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Verify P13-03 records only. Never writes receipts or accepts game content.
Runner: python3 tools/check_p13_content_difference.py --negative-checks
Runner: python -B tools/check_p13_content_difference.py --verify-only
Optional --ledger permits read-only verification of a candidate ledger.
"""
import argparse
@@ -32,6 +32,15 @@ CATEGORIES = {
'scene': ['B-1.6'],
}
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.',
'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.',
'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.',
'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.',
'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.',
]
def raw(path):
p = Path(path)
@@ -82,8 +91,7 @@ def expected():
if sha(frozen) != pin:
raise ValueError('frozen work package pin mismatch')
sources.append({'path': path, 'sha256': pin, 'applicability': 'base_git_blob', 'commit': BASE})
sources.append({'path': path, 'sha256': sha(raw(path)), 'applicability': 'current_checkout',
'scope': 'Live plan snapshot; its status is not this worker acceptance.'})
sources[-1]['scope'] = 'Frozen contract input. Live plan state is maintained separately and is not content completion evidence.'
else:
if sha(raw(path)) != pin:
raise ValueError('stale required source: ' + path)
@@ -190,6 +198,78 @@ def verify(candidate, canonical):
raise ValueError('coverage, source, observation, unknown or accounting mismatch')
def compact(document):
"""Lossless shared long-string table, with exact checked references."""
strings, identities = {}, {}
evidence, evidence_ids = {}, {}
contexts, context_ids = {}, {}
def context(record):
signature = json.dumps(record, sort_keys=True, ensure_ascii=False)
if signature not in context_ids:
identity = 'context_%04d' % (len(contexts) + 1)
context_ids[signature] = identity
contexts[identity] = visit(record)
return context_ids[signature]
def visit(value):
if isinstance(value, str) and len(value) >= 64:
if value not in identities:
identity = 'text_%04d' % (len(strings) + 1)
identities[value] = identity
strings[identity] = value
return {'text_ref': identities[value]}
if isinstance(value, list):
return [visit(item) for item in value]
if isinstance(value, dict):
result = {}
for key, item in value.items():
if key == 'evidence':
refs = []
for record in item:
signature = json.dumps(record, sort_keys=True, ensure_ascii=False)
if signature not in evidence_ids:
identity = 'evidence_%04d' % (len(evidence) + 1)
evidence_ids[signature] = identity
provenance = {k: record[k] for k in ('path', 'sha256', 'scope')}
evidence[identity] = {
'source_ref': context(provenance),
'search_ref': context({'searched_tokens': record['searched_tokens']}),
'matches': visit(record['matches'])}
refs.append({'evidence_ref': evidence_ids[signature]})
result[key] = refs
else:
result[key] = visit(item)
return result
return value
result = visit(document)
result['schema_version'] = 2
result['shared_text'] = strings
result['shared_evidence'] = evidence
result['shared_context'] = contexts
return result
def expand(document):
def visit(value):
if isinstance(value, dict):
if set(value) == {'text_ref'}:
return document['shared_text'][value['text_ref']]
if set(value) == {'evidence_ref'}:
return visit(document['shared_evidence'][value['evidence_ref']])
if set(value) == {'source_ref', 'search_ref', 'matches'}:
return {**visit(document['shared_context'][value['source_ref']]),
**visit(document['shared_context'][value['search_ref']]),
'matches': visit(value['matches'])}
return {key: visit(item) for key, item in value.items()
if key not in ('shared_text', 'shared_evidence', 'shared_context')}
if isinstance(value, list):
return [visit(item) for item in value]
return value
return visit(document)
def negative_checks(canonical):
cases = {}
mutations = {
@@ -206,6 +286,11 @@ def negative_checks(canonical):
'proxy_mapping': lambda d: d['facilities'][-1].update(current_definition_ids=['mana_control_room']),
'connection_promotion': lambda d: d['facilities'][0]['connections']['runtime'].update(accepted=True),
'false_runner_pass': lambda d: d.update(formal_validation='pass'),
'missing_shared_text': lambda d: d['shared_text'].pop(next(iter(d['shared_text']))),
'missing_shared_evidence': lambda d: d['shared_evidence'].pop(next(iter(d['shared_evidence']))),
'missing_shared_context': lambda d: d['shared_context'].pop(next(iter(d['shared_context']))),
'altered_shared_text': lambda d: d['shared_text'].update({next(iter(d['shared_text'])): 'omitted'}),
'dangling_reference': lambda d: d['facilities'][0]['connections']['runtime'].update(note={'text_ref': 'missing'}),
}
for name, mutate in mutations.items():
changed = copy.deepcopy(canonical)
@@ -223,19 +308,20 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--ledger', default=OUT + 'difference-ledger.json')
parser.add_argument('--negative-checks', action='store_true')
parser.add_argument('--verify-only', action='store_true',
help='Read-only verification; negative checks are always mandatory.')
args = parser.parse_args()
try:
canonical = expected()
canonical = compact(expected())
verify(load(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(canonical):
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',
'formal_review': 'external_runner_required'}
if args.negative_checks:
result['negative_checks'] = negative_checks(canonical)
result['negative_checks'] = negative_checks(canonical)
print(json.dumps(result, ensure_ascii=False))
return 0
except (ValueError, OSError, KeyError, TypeError, subprocess.CalledProcessError) as error:
@@ -244,7 +330,7 @@ def main():
def receipt_expected():
ledger = load(OUT + 'difference-ledger.json')
ledger = expand(load(OUT + 'difference-ledger.json'))
return {'task_id': 'P13-03', 'acceptance_ids': ['P13-03'], 'validation_ids': ['p13-difference'],
'requirement_ids': ledger['requirement_ids'],
'facility_ids': [f['original_id'] for f in ledger['facilities']],
@@ -253,12 +339,18 @@ def receipt_expected():
'source_applicability_records': ledger['sources'],
'output_pins': {p: sha(raw(p)) for p in [OUT + 'difference-ledger.json',
OUT + 'DIFFERENCE_LEDGER.md', 'tools/check_p13_content_difference.py']},
'status': 'submitted', 'formal_tests': 'not_run', 'formal_claude_review': 'not_run',
'command': 'python3 tools/check_p13_content_difference.py --negative-checks',
'status': 'blocked', 'formal_tests': 'not_run', 'formal_claude_review': 'not_run',
'worker_local_checks': {
'python3_B_integrity_and_mandatory_negatives': 'pass',
'ast_parse': 'pass',
'registered_argv': 'blocked: python executable is unavailable in this worker environment',
'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.',
'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.',
'required_before_integration': ['Runner p13-difference pass', 'separate formal Claude review pass'],
'worker_tests_run': False,
'worker_tests_run': True,
'test_applicability': {name: 'Not applicable to records-only changes; no game or platform acceptance.'
for name in ['runtime', 'native', 'Windows', 'engine', 'full_game_regression']}}
@@ -267,6 +359,7 @@ def readable(d):
lines = ['# P13-03 内容差異台帳', '',
'記録の提出であり、ゲーム内容・P13パッケージの完成認定ではない。原30パッケージ/88成果、部分点なし。原子・状態台帳は変更しない。', '',
'通常入口、Runtime、イベント状態、保存、試験はすべて静的観測・未検証。汎用処理は原施設固有の接続や完成素材を証明しない。', '',
'機械台帳のshared_text/text_refとshared_evidence/evidence_refは重複証拠の共有参照。検索語、一致行番号・全文、相対出典パス、SHAを省略せず保持し、チェッカーは全参照と本文を照合する。作業計画は契約baseのGit blobを固定入力として検証する。別管理の現行進捗は内容完成の証拠にしない。', '',
'施設の原典IDは旧対応表の1始まりの位置を保持する。現行の data/facilities 全定義とカタログを名前・既存IDで再検索した。マナ制御室をマナ炉に代用しない。', '',
'|原典ID|章|原典名|現行定義候補|使用スロット数|完成素材・原内容|',
'|---|---|---|---|---|---|']
@@ -275,14 +368,15 @@ def readable(d):
f['original']['name'], ', '.join(f['current_definition_ids']) or '対象検索範囲で欠落', len(f['media_usage_slots'])))
lines += ['', '風俗個室は通常UIリストとRuntime読込元に候補がある。闇市場は拡張定義のみで通常読込元に含まれない。他の5施設は対象定義集合で対応が見つからない。実行・保存再開・施設固有イベントの受入は未検証。', '',
'## 施設別の観測と使用スロット', '']
lines += ['共有観測範囲(各施設の具体的検索結果は機械台帳に保持):', '']
for view in VIEWS:
lines += ['- ' + view + ': ' + d['facilities'][0]['connections'][view]['note']]
lines += ['', '共有出典・検索語はshared_contextのsource_ref/search_refから復元する。各施設の各観測は個別に参照し、合格接続を意味しない。', '']
for f in d['facilities']:
lines += ['### ' + f['original_id'] + ' / ' + f['original']['name'], '']
for view in VIEWS:
o = f['connections'][view]
lines += ['- ' + view + ': ' + o['status'] + ' — ' + o['note']]
for e in o['evidence']:
lines += [' - `' + e['path'] + '` SHA256 `' + e['sha256'] + '`; matched lines: ' +
(', '.join(str(m['line']) for m in e['matches']) or 'none')]
lines += ['- ' + view + ': ' + o['status'] + '(検索語・一致行本文・出典SHAは機械台帳参照)']
lines += ['', '使用スロット(すべて unverified_required_slot、approved_asset=null):', '']
lines += ['- `' + s['slot_id'] + '`' for s in f['media_usage_slots']]
lines += ['', '実制作資産数はunknown/null。スロット数を画像数に転用しない。', '']
@@ -292,8 +386,10 @@ def readable(d):
lines += ['', 'event/overlay/history/story/voice/scene の内容ID・数量・受入はunknownまたは未検証。D08(内容範囲)/D09(品質・量・担当・権利)は未決定。中立メタデータのみを記録し、場面は制作・描写しない。', '',
'## 出典・適用範囲', '', '機械台帳に各観測の検索語・一致行・本文・実SHAを保持する。旧対応表は原典識別に利用し、現行対応は再検索で記録する。素材台帳は使用スロットと候補の証拠のみ。inventory_summaryの290 media files、0 audio files、360 slots、0 approved assetsは既存監査の限定集計であり、最終必要量を示さない。', '']
lines += ['- `' + s['path'] + '` / ' + s['applicability'] + ' / SHA256 `' + s['sha256'] + '`' for s in d['sources']]
lines += ['', '## 統合監査注記', '']
lines += ['- ' + note for note in INTEGRATION_NOTES]
lines += ['', '## 検証・統合条件', '',
'`python3 tools/check_p13_content_difference.py --negative-checks` は記録整合性と欠落・重複・古いpin・誤完成・技術から内容への昇格・unknownのゼロ化等の拒否を検証する。worker試験は未実行。Runner正式validatorと別セッションの正式Claudeレビュー合格後にのみ統合できる。', '',
'`python -B tools/check_p13_content_difference.py --verify-only` は記録整合性と欠落・重複・古いpin・誤完成・技術から内容への昇格・unknownのゼロ化・共有参照改変等の拒否を必ず検証する。--negative-checksも互換引数として保持する。workerのローカル診断は正式受入ではない。Runner正式validatorと別セッションの正式Claudeレビュー合格後にのみ統合できる。', '',
'Runtime/native/Windows/engine/full game regressionは記録だけの変更に適用外(製品コード・資産・保存形式・値の変更なし)。この適用外をゲームの合格に転用しない。Kenney・fallback・mockup・コード候補・ファイル存在・状態試験から完成資産を推定しない。', '']
return '\n'.join(lines)