diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/IMPLEMENTATION.md b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/IMPLEMENTATION.md index cf697f9..8f1fac0 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/IMPLEMENTATION.md +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/IMPLEMENTATION.md @@ -81,3 +81,11 @@ bundle 作成後にファイルを追加・編集すると inventory 不一致 原本加算は0、正式完了23/88、4/30、予算 false 6件を保持します。P27 完了は主張しません。 repair3: 最終の局所 unit を再実行して PASS(19件、0.780秒)。 + +## repair4: native parse の静的依存修正 + +- P1: probe の manifest version は、実際の Runtime の `get_script().get_script_constant_map()` から準備段階で取得します。script・key の存在と int 型を確認し、取得した値そのものを manifest 比較の期待値へ渡します。取得不能なら理由を errors に記録して終了します。値の固定や検査対象 manifest の流用はありません。 +- P2: `autoload/*.gd` の class_name 宣言を確認し、probe の該当する静的参照は除去した1箇所だけでした。残る GameStateScript の preload 先、MvpContentCatalog と GuestDefinition には autoload 識別子への依存がありません。engine による compile 成功は未確認です。 +- P3: checker は autoload の class_name を製品ファイルから機械的に抽出し、probe の識別子との一致を拒否します。公開読取の get_script / get_script_constant_map を既存の許可リストに追加しました。修正後 probe の通過、全抽出名の定数参照・型注釈の拒否、新しい宣言の自動検出を最小の追加 unit で確認しました。 +- `python3 test_diag.py`: 20件 PASS、0.861秒。標準出力・標準エラーの生出力を UNIT_OUTPUT.txt に保存しました。変更は diag_probe.gd、check_diag.py、test_diag.py、UNIT_OUTPUT.txt、IMPLEMENTATION.md のみ。BLOCKED.md は解消済みの履歴として維持します。 +- native import・parse・3 PID、正式 validation、独立レビューは提出後の Root 側で実施します。worker は engine・Runner 操作、git 書込み、製品変更、全回帰、他エージェント起動を行っていません。原本加算0、23/88、4/30、予算 false 6件を維持します。 diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/UNIT_OUTPUT.txt b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/UNIT_OUTPUT.txt index 047a2a7..936691d 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/UNIT_OUTPUT.txt +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/UNIT_OUTPUT.txt @@ -1,3 +1,4 @@ +test_autoload_class_references_rejected (__main__.DiagnosticTests) ... ok test_bundle_missing_and_tampered (__main__.DiagnosticTests) ... ok test_connections_and_preparation (__main__.DiagnosticTests) ... ok test_cross_pid_only_named_kernel_fields (__main__.DiagnosticTests) ... ok @@ -8,7 +9,7 @@ test_missing_not_zero (__main__.DiagnosticTests) ... ok test_nonadditive_scope (__main__.DiagnosticTests) ... ok test_pid_local_catalog_identity (__main__.DiagnosticTests) ... ok test_queries_and_observer_rejection (__main__.DiagnosticTests) ... ok -test_real_owned_python_child_and_raw_binding (__main__.DiagnosticTests) ... RUNTIME_SCALE_PROCESS_START {"name": "import", "pid": 62682} +test_real_owned_python_child_and_raw_binding (__main__.DiagnosticTests) ... RUNTIME_SCALE_PROCESS_START {"name": "import", "pid": 70915} ok test_scratch_ownership (__main__.DiagnosticTests) ... ok test_service_snapshot_inventory_matches_current_product (__main__.DiagnosticTests) ... ok @@ -20,6 +21,6 @@ test_tick_id_and_minute_rejection (__main__.DiagnosticTests) ... ok test_valid_raw_intervals (__main__.DiagnosticTests) ... ok ---------------------------------------------------------------------- -Ran 19 tests in 0.780s +Ran 20 tests in 0.861s OK diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/check_diag.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/check_diag.py index 6fa7684..468e20f 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/check_diag.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/check_diag.py @@ -120,10 +120,20 @@ def check_modules(modules=None, root=ROOT): "loaded project module outside closure") -def check_probe(text): +def autoload_class_names(root=ROOT): + paths = sorted((root / 'autoload').glob('*.gd')) + require(paths, 'autoload scripts missing') + return {name for path in paths for name in + re.findall(r'^\s*class_name\s+([A-Za-z_][A-Za-z_0-9]*)\b', path.read_text(), re.M)} + + +def check_probe(text, root=ROOT): """Conservative finite call policy, plus mutation-site checks; native parse is separate.""" code = '\n'.join(line.split('#', 1)[0] for line in text.splitlines()) tokens = re.sub(r'"(?:\\.|[^"\\])*"', '""', code) + identifiers = set(re.findall(r'\b[A-Za-z_][A-Za-z_0-9]*\b', tokens)) + require(not identifiers.intersection(autoload_class_names(root)), + 'autoload class_name reference in probe') forbidden = (r'\b(?:set_script|set_process|set_physics_process|set_deferred|callv?|call_thread_safe|' r'call_deferred_thread_group|emit_signal|subscribe|unsubscribe|initialize|reset|' r'clear_performance_reports|register_tick_handler|unregister_tick_handler|' @@ -153,7 +163,7 @@ def check_probe(text): require('set_paused(false)' not in code, 'resume forbidden') for receiver, allowed in { 'clock': {'get', 'set_paused', 'advance_minutes'}, - 'runtime': {'get', 'create_snapshot', 'restore_snapshot', 'get_save_policy'}, + 'runtime': {'get', 'get_script', 'create_snapshot', 'restore_snapshot', 'get_save_policy'}, 'main': {'get'}, 'target': {'create_snapshot'}, 'SaveService': {'save_snapshot', 'load_snapshot'}, 'SimulationSnapshot': {'validate', 'restore'}, @@ -185,7 +195,8 @@ def check_probe(text): 'get_ticks_usec', 'get_ticks_msec', 'advance_minutes', 'save_snapshot', 'validate', 'restore', 'load_snapshot', 'free', 'merge', 'get_node', 'get_environment', 'get_process_id', 'parse_string', 'set_paused', 'get_sha256', 'open', 'get_var', 'close', 'restore_snapshot', 'connect', - 'instantiate', 'add_child', 'get_save_policy', 'snapshot', 'disconnect', 'emit' + 'instantiate', 'add_child', 'get_save_policy', 'snapshot', 'disconnect', 'emit', + 'get_script', 'get_script_constant_map' }, 'unapproved member call') for path in re.findall(r'"(user://[^"\n]*)"', code): require(path.startswith('user://p28_diag/scratch/'), 'nonprivate scratch path') diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/diag_probe.gd b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/diag_probe.gd index 93687ed..b4b869a 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/diag_probe.gd +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/diag_probe.gd @@ -12,6 +12,7 @@ const K_FIELDS := ["absolute_minute", "speed", "paused", "snapshot_version", "_s "_pause_request_serial", "_abort_current_tick_requested", "_last_tick_advanced"] var clock: Variant var runtime: Variant +var manifest_version: Variant var main: Variant var report: Dictionary = {} var ticks: Array = [] @@ -151,7 +152,7 @@ func comparison(before: Dictionary, after: Dictionary) -> Dictionary: var guest_ids: Array[String] = [] for definition: GuestDefinition in catalog.build_all_guest_definitions(): guest_ids.append(String(definition.guest_type_id)) - var expected := {&"manifest_version": SimulationRuntimeService.MVP_CONTENT_MANIFEST_VERSION, + var expected := {&"manifest_version": manifest_version, &"schema_version": MvpContentCatalog.SCHEMA_VERSION, &"guest_class_ids": string_names(catalog.get_guest_class_ids()), &"species_ids": string_names(catalog.get_species_ids()), @@ -323,6 +324,21 @@ func begin() -> void: var k0 := kernel_state() clock.set_paused(true) report.preparation.append({"operation": "initial_pause", "K_before": k0, "K_after": kernel_state()}) + var runtime_script: Variant = runtime.get_script() + if runtime_script == null: + failed.append("Runtime script unavailable for manifest version") + finish() + return + var constants: Dictionary = runtime_script.get_script_constant_map() + if not constants.has("MVP_CONTENT_MANIFEST_VERSION"): + failed.append("Runtime constant MVP_CONTENT_MANIFEST_VERSION missing") + finish() + return + if typeof(constants["MVP_CONTENT_MANIFEST_VERSION"]) != TYPE_INT: + failed.append("Runtime constant MVP_CONTENT_MANIFEST_VERSION must be int") + finish() + return + manifest_version = constants["MVP_CONTENT_MANIFEST_VERSION"] var path := OS.get_environment("THE_TREE_P28_FIXTURE") report["input_sha256"] = FileAccess.get_sha256(path) if report.input_sha256 != OS.get_environment("THE_TREE_P28_FIXTURE_SHA"): diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/test_diag.py b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/test_diag.py index b6eff10..d2e8f42 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/test_diag.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-02/diag/test_diag.py @@ -226,6 +226,26 @@ class DiagnosticTests(unittest.TestCase): with self.subTest(mutation=mutation[-100:]), self.assertRaises(ValueError): check.check_probe(mutation) + def test_autoload_class_references_rejected(self): + source = (check.HERE / 'diag_probe.gd').read_text() + check.check_probe(source) + names = check.autoload_class_names() + self.assertTrue(names) + for name in names: + for statement in (f'var dependency = {name}.MVP_CONTENT_MANIFEST_VERSION', + f'var dependency: {name}'): + with self.subTest(statement=statement), self.assertRaisesRegex( + ValueError, 'autoload class_name reference'): + check.check_probe(source + '\nfunc illegal():\n\t' + statement + '\n') + # Discover new product declarations without maintaining a name list. + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / 'autoload').mkdir() + (root / 'autoload/new_service.gd').write_text('class_name NewAutoloadService\nextends Node\n') + self.assertEqual(check.autoload_class_names(root), {'NewAutoloadService'}) + with self.assertRaisesRegex(ValueError, 'autoload class_name reference'): + check.check_probe(source + '\nvar dependency: NewAutoloadService\n', root) + def test_service_snapshot_inventory_matches_current_product(self): source = (check.ROOT / 'autoload/simulation_runtime.gd').read_text() body = source.split('func create_snapshot() -> Dictionary:', 1)[1].split('\nfunc restore_snapshot', 1)[0]