P13-03: 内容差異の記録
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user