From 3ac3243dfb691774bc2db25d8a6720bff47cf311 Mon Sep 17 00:00:00 2001 From: Agent Control Runner Date: Tue, 20 Oct 2026 21:48:59 +0900 Subject: [PATCH] =?UTF-8?q?P28-01-COLLECTOR:=20=E7=8F=BEHEAD=E3=81=AE?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E5=9F=BA=E6=BA=96=EF=BC=9Awindowed=20x1/x3?= =?UTF-8?q?=E3=83=BBOperations=20ON=20collector?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../p28-01/collector/check_p28_01_report.py | 91 +++++++++++++++- .../p28-01/collector/collect_p28_01.py | 46 ++++---- .../p28-01/collector/measure_p28_01.gd | 18 ++-- .../p28-01/collector/test_collect_p28_01.py | 101 ++++++++++++++++-- 4 files changed, 222 insertions(+), 34 deletions(-) diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py index 0a52ab2..c78e94b 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/check_p28_01_report.py @@ -8,6 +8,24 @@ from pathlib import Path import re PREFIX = "P28_01_PERF_REPORT=" +RAW_SOURCE = "clock.performance_budget (product diagnostic, raw, not a verdict)" +CAP_SOURCE = "SPEC §5 phase cap" +DEADLINE_SOURCE = "H6 probe deadline (cap−60s)" +VERDICT_SCOPE = ("Budget verdicts come only from windowed phases, using kernel samples, for " + "simulation_x1, simulation_x3 and main_thread_stall. Save, load and path_query " + "verdicts are null in this collector, reason: not_measured_with_kernel_definition.") +RAW_FIELDS = {"budget_at_warmup_end", "budget_at_phase_end", "kernel_at_warmup_end", "kernel_at_phase_end"} +METRICS = {"simulation_x1", "simulation_x3", "main_thread_stall"} +R02_FIELDS = { + **{"x1_outer_advance_one_minute_wall_" + stat: + ("advance_one_minute_wall_ms_x1." + stat, stat, + "headless outer advance_one_minute wall; not kernel-defined") for stat in ("mean", "p95", "max")}, + "path_cold_p95": ("path_after_tick_ms.found_cold.p95", "p95", "headless probe path timing; not kernel-defined"), + "snapshot_mean": ("save_load_ms.snapshot.mean", "mean", "headless probe snapshot timing; not kernel-defined"), + **{kind + "_" + stat: ("save_load_ms." + kind + "." + stat, stat, + "headless probe " + kind + " timing; not kernel-defined") + for kind in ("manual", "autosave", "load") for stat in ("mean", "max")}, +} PHASES = {"R0": ("R", 1, 0, 10), "P1": ("R", 1, 30, 120), "P2": ("R", 3, 30, 120), "O0": ("O", 1, 0, 10), "P3": ("O", 1, 30, 120), "P4": ("O", 3, 30, 120)} @@ -28,6 +46,64 @@ INTEGER_FIELDS = { OPAQUE_FIELDS = {"rng_streams", "startup", "kernel_at_warmup_end", "kernel_at_phase_end", "operations_at_end", "restore_result", "save_policy_after"} +def check_budget_scope(value, report_paths=(), path=(), raw=False): + """Walk the entire receipt; privileges are tied to exact report locations.""" + if isinstance(value, list): + for i, child in enumerate(value): + check_budget_scope(child, report_paths, path + (i,), raw) + return + if not isinstance(value, dict): + return + raw_root = any(path == root + (name,) for root in report_paths for name in RAW_FIELDS) + verdict = any(len(path) == len(root) + 2 and path[:len(root)] == root + and path[-2] == "budget_verdict" and path[-1] in METRICS for root in report_paths) + has_pass = bool({"passed", "within_budget"} & value.keys()) + in_report = any(path[:len(root)] == root for root in report_paths) + if has_pass and in_report and not raw: + need(verdict, "pass flag copied outside kernel verdict") + if "budget_verdict" in value: + need(path in report_paths and isinstance(value["budget_verdict"], dict) + and set(value["budget_verdict"]) <= METRICS, "budget verdict location/metrics") + if has_pass and {"limit", "limit_us", "limit_ms"} & value.keys(): + need(verdict, "budget verdict outside report.budget_verdict") + if raw_root: + need(value.get("source") == RAW_SOURCE, "raw diagnostic source missing") + keys(value, ("source", "data")) + if not raw: + need("budget_exceeded" not in value, "copied product budget_exceeded") + need("limit_us" not in value or verdict, "copied product limit_us") + if "limit_seconds" in value: + need(path and path[-1] in ("projection", "phase_stop", "probe_deadline"), "limit location") + deadline = path[-1] == "probe_deadline" + need(not has_pass and value["limit_seconds"] == (1740 if deadline else 1800) + and value.get("source") == (DEADLINE_SOURCE if deadline else CAP_SOURCE), "execution limit") + for key, child in value.items(): + check_budget_scope(child, report_paths, path + (key,), raw or (raw_root and key == "data")) + + +def check_admission(admission): + check_budget_scope(admission) + need(admission.get("budget_verdict_scope") == VERDICT_SCOPE, "admission verdict scope") + reused = admission["reused_r02"] + def no_verdict_fields(value): + if isinstance(value, dict): + need(all(not k.startswith(("limit", "budget")) and k not in ("passed", "within_budget") + for k in value), "verdict fields in reused_r02") + for child in value.values(): + no_verdict_fields(child) + elif isinstance(value, list): + for child in value: + no_verdict_fields(child) + no_verdict_fields(reused) + observations = reused["observations"] + need(set(observations) == set(R02_FIELDS), "r02 observations") + for name, (field, statistic, scope) in R02_FIELDS.items(): + item = observations[name] + keys(item, ("value", "unit", "statistic", "scope", "r02_field")) + need(number(item["value"]) and item["value"] >= 0, "r02 value") + need((item["unit"], item["statistic"], item["scope"], item["r02_field"]) == + ("ms", statistic, scope, field), "r02 observation provenance") + def normalize_report(value, path=()): """Single ingestion boundary, also used when checking raw stdout binding.""" @@ -187,6 +263,12 @@ def interval(report, name): def check_report(report): + check_budget_scope(report, ((),)) + for name in RAW_FIELDS: + keys(report[name], ("source", "data")) + equal(report["probe_deadline"], {"limit_seconds": 1740, "source": DEADLINE_SOURCE}, "probe deadline") + equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"], + report["budget_at_phase_end"]["data"], report["speed"]), "kernel interval budget") keys(report, ("schema", "pid", "phase", "input", "speed", "metadata", "input_sha256", "frames", "stop_reason", "warmup", "measurement", "actual_minutes", "elapsed_wall_seconds", "kernel_at_phase_end", "operations_at_end", "end_wall", @@ -329,8 +411,8 @@ def check_report(report): interval(report, name) need(report["budget_scope"] == "kernel process_real_delta only; excludes observer_us", "budget scope") need(report["render_submit_note"] == "CPU側の描画提出wall、GPU時間ではない", "render submit scope") - equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"], - report["budget_at_phase_end"], speed), "kernel interval budget") + equal(report["budget_verdict"], budget_verdict(report["budget_at_warmup_end"]["data"], + report["budget_at_phase_end"]["data"], speed), "kernel interval budget") need(number(report["startup_wall_seconds"]) and report["startup_wall_seconds"] >= 0, "startup timing") for save in report["save_events"]: keys(save, ("frame", "game_minute", "ok", "operation", "path", "sample_counts")) @@ -347,6 +429,10 @@ def check_report(report): def check_receipt(receipt, directory): from collect_p28_01 import OWNED_FILES, projection, extrapolation, guard_gate + check_budget_scope(receipt, tuple(("phases", i, "report") + for i, row in enumerate(receipt["phases"]) if "report" in row)) + check_admission(receipt["admission"]) + equal(receipt["admission"], json.loads((directory / "admission.json").read_text()), "admission binding") need(receipt["source_before"] == receipt["source_after"], "source map changed") need(receipt["default_player_before"] == receipt["default_player_after"], "player changed") need(not receipt["cleanup_errors"] and all(r["engine_ended"] is True for r in receipt["cleanup"]), "cleanup") @@ -359,6 +445,7 @@ def check_receipt(receipt, directory): rows = receipt["phases"] need([r["phase"] for r in rows] == ["import", *PHASES], "phase order") for row in rows: + equal(row["phase_stop"], {"limit_seconds": 1800, "source": CAP_SOURCE}, "phase cap origin") forecast = None if row["phase"] in ("P1", "P2", "P3", "P4"): source = PHASES[row["phase"]][0] diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py index 21b683f..bd803a6 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/collect_p28_01.py @@ -13,7 +13,7 @@ import uuid from datetime import datetime, timezone sys.dont_write_bytecode = True -from check_p28_01_report import check_report, check_receipt, check_probe, normalize_report +from check_p28_01_report import check_report, check_receipt, check_probe, normalize_report, check_admission, R02_FIELDS, VERDICT_SCOPE, CAP_SOURCE HERE = Path(__file__).resolve().parent ROOT = HERE.parents[5] FIXTURE_SHA = "eb4f770071eb07cd7e136fe54c3f78768cc8de7a4264e62a9af1f06a8f90f9bd" @@ -110,7 +110,7 @@ def projection(stage0): if evidence["seconds_per_phase"] is not None else None) return {"seconds_per_phase": evidence["seconds_per_phase"], "startup_wall_seconds": stage0["report"]["startup_wall_seconds"], - "projected_total_seconds": seconds, "limit_seconds": LIMIT, + "projected_total_seconds": seconds, "limit_seconds": LIMIT, "source": CAP_SOURCE, "stage0_phase": stage0["phase"], "stop_reason": "projected_over_limit" if seconds is not None and seconds > LIMIT else None} @@ -165,7 +165,8 @@ def phase_schedule(rows): forecast = projection(stage0) if forecast["stop_reason"]: rows.append({"phase": label, "passed": False, "engines_started": 0, - "stop_reason": "projected_over_limit", "projection": forecast}) + "stop_reason": "projected_over_limit", "projection": forecast, + "phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}}) continue yield index, phase, forecast @@ -195,7 +196,8 @@ def run_engine(outer, command, folder, environment): capture.get("passed") is True, capture.get("all_captured_ended") is True, len(capture.get("actual_captured_pids", [])) == 1, all(raw.values()), not bad)) row = {"passed": clean, "process": result, "capture": capture, "raw": raw, - "diagnostics": bad, "timeout_seconds": LIMIT, "argv": command} + "diagnostics": bad, "timeout_seconds": LIMIT, "argv": command, + "phase_stop": {"limit_seconds": LIMIT, "source": CAP_SOURCE}} row["owned_files"] = {name: {"sha256": sha(folder / name), "bytes": (folder / name).stat().st_size} for name in OWNED_FILES if (folder / name).is_file()} @@ -218,6 +220,24 @@ def inputs(readonly): return {"manifest_sha256": sha(manifest_path), "files": pins} +def reused_observations(readonly, pins): + relative = "run-r02/after/evidence/report.json" + path = readonly / relative + require(sha(path) == pins["files"][relative], "r02 report SHA changed") + raw = json.loads(path.read_text()) + observations = {} + for name, (field, statistic, scope) in R02_FIELDS.items(): + value = raw + for part in field.split("."): + value = value[part] + observations[name] = {"value": value, "unit": "ms", "statistic": statistic, + "scope": scope, "r02_field": field} + return {"execution_sha256": pins["files"]["run-r02/execution.json"], + "report_sha256": pins["files"][relative], + "scope": "x1 headless Operations OFF; reused without remeasurement", + "observations": observations} + + def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--readonly", type=Path, required=True) @@ -262,19 +282,9 @@ def main(): admission = {"commit": commit, "source_map": before, "source_map_sha256": digest(before), "readonly": pins, "engine_sha256": ENGINE_SHA, "engine_version": VERSION, "fixture_sha256": FIXTURE_SHA, "planned_argv": planned, "phases": PHASES, - "reused_r02": {"execution_sha256": pins["files"]["run-r02/execution.json"], - "report_sha256": pins["files"]["run-r02/after/evidence/report.json"], - "scope": "x1 headless Operations OFF; reused without remeasurement", - "tick_ms": {"mean": 826.6, "p95": 1702.7, "max": 2918.1}, - "path_cold_p95_ms": 0.61, "snapshot_mean_ms": 77.6, - "manual_save_ms": 297, "autosave_ms": 327, - "load_mean_ms": 3193, "load_max_ms": 3368, - "budget_comparisons_ms": { - "x1_tick_mean": {"value": 826.6, "limit": 4, "passed": False}, - "path_cold_p95": {"value": 0.61, "limit": 2, "passed": True}, - "manual_save": {"value": 297, "limit": 5000, "passed": True}, - "autosave": {"value": 327, "limit": 5000, "passed": True}, - "load_max": {"value": 3368, "limit": 8000, "passed": True}}}} + "budget_verdict_scope": VERDICT_SCOPE, + "reused_r02": reused_observations(args.readonly, pins)} + check_admission(admission) if args.check: print(json.dumps(dict(admission, stages_created=0, engines_started=0), ensure_ascii=False, indent=2)) return 0 @@ -374,7 +384,7 @@ def main(): cleanup, cleanup_errors = outer.finish_captured(outer.OWNED) after = source_map(standard, ROOT) player_after = standard.default_player(ROOT) - receipt = {"phases": rows, "error": error, "source_before": before, "source_after": after, + receipt = {"admission": admission, "phases": rows, "error": error, "source_before": before, "source_after": after, "probe_check": probe_check, "default_player_before": player, "default_player_after": player_after, "cleanup": cleanup, "cleanup_errors": cleanup_errors, diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd index c845cb1..0842b04 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/measure_p28_01.gd @@ -266,7 +266,9 @@ func begin() -> void: "render_submit_note": "CPU側の描画提出wall、GPU時間ではない", "observer_note": "observer_us includes reads, serialization and frame emission; excludes its own timing line and final report; never subtracted", "sample_definition": "is_minute_sample marks a changed game minute; other frames carry counts_from_minute", - "budget_at_warmup_end": {}, "save_events": save_events, "key_form": key_forms, + "budget_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": {}}, + "kernel_at_warmup_end": {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": null, "reason": "warmup_not_reached"}, + "probe_deadline": {"limit_seconds": 1740, "source": "H6 probe deadline (cap−60s)"}, "save_events": save_events, "key_form": key_forms, "count_definitions": {"registered": "service collections; walkable cells", "active": "guests exclude SPAWNING/LEAVING/DESPAWNING; staff current attended unended shift; facilities OPERATING", "drawn": "world_preview submitted model serial and viewport geometry"}, @@ -355,8 +357,8 @@ func begin() -> void: last_stall_count = initial_budget.main_thread_stall.lifetime_count if warmup == 0: warmup_ended = true - report["kernel_at_warmup_end"] = clock.get_performance_reports() - report["budget_at_warmup_end"] = initial_budget + report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()} + report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": initial_budget} report["startup_wall_seconds"] = float(start_us) / 1000000.0 report["start_minute"] = start_minute report["start_wall"] = Time.get_unix_time_from_system() @@ -430,8 +432,8 @@ func sample(observer_started_us: int) -> void: previous_minute = minute if not warmup_ended and minute - start_minute >= warmup: warmup_ended = true - report["kernel_at_warmup_end"] = clock.get_performance_reports() - report["budget_at_warmup_end"] = budget_snapshot() + report["kernel_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()} + report["budget_at_warmup_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()} if warmup > 0: measurement_start = minute var operations: Dictionary = runtime.get_operations_report() @@ -540,9 +542,9 @@ func finish(reason: String) -> void: report["actual_minutes"] = int(clock.absolute_minute) - start_minute if start_us > 0 else 0 report["elapsed_wall_seconds"] = float(Time.get_ticks_usec() - start_us) / 1000000.0 if start_us > 0 else 0.0 report["end_wall"] = Time.get_unix_time_from_system() - report["kernel_at_phase_end"] = clock.get_performance_reports() - report["budget_at_phase_end"] = budget_snapshot() - report["budget_verdict"] = kernel_verdict(report.budget_at_warmup_end, report.budget_at_phase_end) + report["kernel_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": clock.get_performance_reports()} + report["budget_at_phase_end"] = {"source": "clock.performance_budget (product diagnostic, raw, not a verdict)", "data": budget_snapshot()} + report["budget_verdict"] = kernel_verdict(report.budget_at_warmup_end.data, report.budget_at_phase_end.data) report["operations_at_end"] = runtime.get_operations_report() report["warmup"] = summary("warmup") report["measurement"] = summary("measurement") diff --git a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py index 2610cf5..fd8f1d0 100644 --- a/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py +++ b/docs/plans/original-completion-2026-10-02/execution/p28-01/collector/test_collect_p28_01.py @@ -118,10 +118,97 @@ def synthetic_report(): "statistics": {"count": 10, "average_us": 3000, "p95_us": 3000, "maximum_us": 3000}} for k, aggregate, limit in (("simulation_x1", "average", 4000), ("main_thread_stall", "maximum", 50000))} + for name in checker.RAW_FIELDS: + report[name] = {"source": checker.RAW_SOURCE, "data": report[name]} + report["probe_deadline"] = {"limit_seconds": 1740, "source": checker.DEADLINE_SOURCE} return report +def synthetic_admission(): + return {"budget_verdict_scope": checker.VERDICT_SCOPE, "reused_r02": {"observations": { + name: {"value": 1.25, "unit": "ms", "statistic": stat, "scope": scope, "r02_field": field} + for name, (field, stat, scope) in checker.R02_FIELDS.items()}}} + + class ReportTests(unittest.TestCase): + def test_r1_recursive_verdict_scope(self): + report = synthetic_report() + receipt = {"admission": synthetic_admission(), "phases": [{"report": report, "passed": True}], + "process": {"passed": True}, "capture": {"passed": True}, + "probe_check": {"passed": True}, "passed": True} + checker.check_budget_scope(receipt, (("phases", 0, "report"),)) + for flag in ("passed", "within_budget"): + for limit in ("limit", "limit_us", "limit_ms"): + for location in ("admission", "extra"): + broken = copy.deepcopy(receipt) + broken.setdefault(location, {})["nested"] = [{flag: True, limit: 4}] + with self.subTest(flag=flag, limit=limit, location=location), self.assertRaises(ValueError): + checker.check_budget_scope(broken, (("phases", 0, "report"),)) + self.reject(lambda r: r.update(extra={"passed": True, "limit": 4})) + self.reject(lambda r: r["budget_verdict"].update(path_query=copy.deepcopy(r["budget_verdict"]["simulation_x1"]))) + + def test_r2_raw_diagnostics_preserved(self): + r = synthetic_report() + original = [{"limit_us": 4000, "budget_exceeded": True, "measured_us": 5000}] + r["kernel_at_phase_end"]["data"] = copy.deepcopy(original) + checker.check_report(r) + self.assertEqual(r["kernel_at_phase_end"]["data"], original) + for name in checker.RAW_FIELDS: + self.reject(lambda r, name=name: r[name].pop("source")) + self.reject(lambda r, name=name: r[name].update(source="invented")) + for field, value in (("budget_exceeded", False), ("limit_us", 4000), ("passed", True)): + self.reject(lambda r, field=field, value=value: r.update(extra={field: value})) + self.reject(lambda r: r["kernel_at_phase_end"].update(data={"passed": True, "limit_us": 4000})) + + def test_r3_execution_limit_origin(self): + projection = collector.projection({"phase": "R0", "report": {"startup_wall_seconds": 1}, + "extrapolation": {"seconds_per_phase": 20}}) + valid = {"projection": projection, + "phase_stop": {"limit_seconds": 1800, "source": checker.CAP_SOURCE}, + "probe_deadline": {"limit_seconds": 1740, "source": checker.DEADLINE_SOURCE}} + checker.check_budget_scope(valid) + for location in valid: + for change in ({"passed": True}, {"limit_seconds": 1799}, {"source": "wrong"}): + broken = copy.deepcopy(valid) + broken[location].update(change) + with self.subTest(location=location, change=change), self.assertRaises(ValueError): + checker.check_budget_scope(broken) + with self.assertRaises(ValueError): + checker.check_budget_scope({"elsewhere": {"limit_seconds": 1800, "source": checker.CAP_SOURCE}}) + self.reject(lambda r: r["probe_deadline"].update(limit_seconds=1800)) + + def test_r4_observations_and_pinned_input(self): + admission = synthetic_admission() + checker.check_admission(admission) + for key in ("limit", "limit_ms", "passed", "budget_comparisons_ms", "budget_exceeded"): + broken = copy.deepcopy(admission) + broken["reused_r02"]["nested"] = [{key: 4}] + with self.subTest(key=key), self.assertRaises(ValueError): + checker.check_admission(broken) + for key in ("unit", "statistic", "scope", "r02_field"): + broken = copy.deepcopy(admission) + broken["reused_r02"]["observations"]["manual_max"].pop(key) + with self.subTest(key=key), self.assertRaises(ValueError): + checker.check_admission(broken) + # Synthetic source with distinct exact values tests extraction, not rounded constants. + raw = {} + for i, (field, _, _) in enumerate(checker.R02_FIELDS.values()): + target = raw + parts = field.split(".") + for part in parts[:-1]: + target = target.setdefault(part, {}) + target[parts[-1]] = i + .123456789 + pins = {"files": {"run-r02/after/evidence/report.json": "a" * 64, + "run-r02/execution.json": "b" * 64}} + with patch.object(collector, "sha", return_value="a" * 64), \ + patch.object(Path, "read_text", return_value=json.dumps(raw)): + reused = collector.reused_observations(Path("readonly"), pins) + for i, name in enumerate(checker.R02_FIELDS): + self.assertEqual(reused["observations"][name]["value"], i + .123456789) + checker.check_admission({"budget_verdict_scope": checker.VERDICT_SCOPE, "reused_r02": reused}) + with patch.object(collector, "sha", return_value="c" * 64), self.assertRaises(ValueError): + collector.reused_observations(Path("readonly"), pins) + def reject(self, mutate): report = synthetic_report() mutate(report) @@ -225,20 +312,22 @@ class ReportTests(unittest.TestCase): "report": r, "capture": {"actual_captured_pids": [123]}, "extrapolation": collector.extrapolation(r), "guard": synthetic_guard(), "runtime_profile": "private", "runtime_name": "runtime"} - receipt = {"source_before": {"x": "a"}, "source_after": {"x": "a"}, + receipt = {"admission": synthetic_admission(), "source_before": {"x": "a"}, "source_after": {"x": "a"}, "default_player_before": {}, "default_player_after": {}, "cleanup_errors": [], "cleanup": [{"engine_ended": True}], "error": None, "passed": True, "phases": [{"phase": "import", "passed": True, "timeout_seconds": 1800, "raw": {}}, row]} probe_text = (HERE / "measure_p28_01.gd").read_text() receipt["probe_check"] = {"sha256": hashlib.sha256(probe_text.encode()).hexdigest(), "passed": True} for phase in receipt["phases"]: + phase["phase_stop"] = {"limit_seconds": 1800, "source": checker.CAP_SOURCE} phase["probe_check"] = receipt["probe_check"].copy() phase["owned_files"] = {name: {"sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)} for name in collector.OWNED_FILES} with patch.object(checker, "PHASES", {"O0": ("O", 1, 0, 10)}), \ patch.object(Path, "read_bytes", return_value=raw), \ patch.object(Path, "read_text", autospec=True, - side_effect=lambda p: probe_text if p.name == "measure_p28_01.gd" else raw.decode()): + side_effect=lambda p: (probe_text if p.name == "measure_p28_01.gd" else + json.dumps(receipt["admission"]) if p.name == "admission.json" else raw.decode())): checker.check_receipt(receipt, Path("synthetic")) for label, mutate in { "source": lambda x: x.update(source_after={"x": "b"}), @@ -279,9 +368,9 @@ class ReportTests(unittest.TestCase): for reason, count in (("interval_exceeds_window", 600), ("no_interval_samples", 30), ("counts_unavailable", None)): r = synthetic_report() - r["budget_at_phase_end"]["simulation_x1"]["lifetime_count"] = count - r["budget_verdict"] = checker.budget_verdict(r["budget_at_warmup_end"], - r["budget_at_phase_end"], 1) + r["budget_at_phase_end"]["data"]["simulation_x1"]["lifetime_count"] = count + r["budget_verdict"] = checker.budget_verdict(r["budget_at_warmup_end"]["data"], + r["budget_at_phase_end"]["data"], 1) self.assertEqual(r["budget_verdict"]["simulation_x1"]["reason"], reason) self.assertIsNone(r["budget_verdict"]["simulation_x1"]["value"]) checker.check_report(r) @@ -322,7 +411,7 @@ class ReportTests(unittest.TestCase): r["metadata"]["machine"]["memory_bytes"] = 24000000000.0 r["pid"] = 123.0 r["frames"][0]["registered"]["guests"] = 3.0 - r["budget_at_phase_end"]["simulation_x1"]["samples_us"] = [3000.0] * 40 + r["budget_at_phase_end"]["data"]["simulation_x1"]["samples_us"] = [3000.0] * 40 normalized = collector.normalize_report(json.loads(json.dumps(r))) checker.check_report(normalized) self.assertIs(type(normalized["pid"]), int)