P28-01-RESTART: 現HEADの性能基準:途中保存・別PID再開の再現
This commit is contained in:
+3
@@ -314,6 +314,8 @@ def json_sha(value):
|
||||
# and Python. Fixed decimal form is valid JSON; keys and strings remain verbatim.
|
||||
def encode(v):
|
||||
if isinstance(v, dict):
|
||||
require(len({str(k) for k in v}) == len(v), "canonical_key_collision")
|
||||
require(all(type(k) is str for k in v), "canonical JSON requires text keys")
|
||||
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + encode(v[k])
|
||||
for k in sorted(v)) + "}"
|
||||
if isinstance(v, list):
|
||||
@@ -335,6 +337,7 @@ def check_sidecar(public, sidecar):
|
||||
"sidecar root/missing")
|
||||
check_no_metrics(public)
|
||||
full = sidecar["report"]
|
||||
require(full.get("stop_reason") != "canonical_key_collision", "canonical_key_collision")
|
||||
require(public == redact_report(full), "public redaction differs from W2")
|
||||
restored = copy.deepcopy(public)
|
||||
for path, payload in payload_paths(restored).items():
|
||||
|
||||
+10
-1
@@ -19,6 +19,7 @@ var boundary: Variant = null
|
||||
var at_390_event_verbatim: Array = []
|
||||
var at_390_pending: Array = []
|
||||
var redaction_ok := true
|
||||
var canonical_key_collision := false
|
||||
const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED",
|
||||
"SAVE_LOAD_FAILED", "SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED",
|
||||
"SAVE_RECOVERY_LOADED", "SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_FAILED"]
|
||||
@@ -26,7 +27,13 @@ const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SA
|
||||
func canonical_json(value: Variant) -> String:
|
||||
if value is Dictionary:
|
||||
var keys: Array = value.keys()
|
||||
keys.sort()
|
||||
keys.sort_custom(func(a, b): return str(a) < str(b))
|
||||
for index: int in range(1, keys.size()):
|
||||
if str(keys[index - 1]) == str(keys[index]):
|
||||
canonical_key_collision = true
|
||||
# Unwind serialization before emitting failure evidence.
|
||||
call_deferred("finish", "canonical_key_collision")
|
||||
return ""
|
||||
var parts: PackedStringArray = []
|
||||
for key: Variant in keys:
|
||||
parts.append(JSON.stringify(str(key)) + ":" + canonical_json(value[key]))
|
||||
@@ -567,6 +574,8 @@ func public_has_forbidden(value: Variant) -> bool:
|
||||
func finish(reason: String) -> void:
|
||||
if finished:
|
||||
return
|
||||
if canonical_key_collision:
|
||||
reason = "canonical_key_collision"
|
||||
finished = true
|
||||
report["stop_reason"] = reason
|
||||
report["key_forms"] = key_forms
|
||||
|
||||
+63
@@ -252,6 +252,69 @@ class RestartTests(unittest.TestCase):
|
||||
self.assertIn("test_restart.py", self.inventory_before)
|
||||
self.assertEqual(self.here_inventory(), self.inventory_before)
|
||||
|
||||
def actual_canonical_sidecar(self):
|
||||
relative = Path("native-e524ff2-canonical-failure-r01/report_full.json")
|
||||
roots = [check.ROOT / "build",
|
||||
check.ROOT.parent / ".runtime" / "P28-01-RESTART" / "build"]
|
||||
if os.environ.get("BUILD_OUTPUT_DIR"):
|
||||
roots.insert(0, Path(os.environ["BUILD_OUTPUT_DIR"]))
|
||||
path = next((root / relative for root in roots if (root / relative).is_file()), None)
|
||||
self.assertIsNotNone(path, "required retained native-e524ff2 sidecar is missing")
|
||||
raw = path.read_bytes()
|
||||
self.addCleanup(lambda: self.assertEqual(path.read_bytes(), raw))
|
||||
return json.loads(raw)
|
||||
|
||||
def test_z1_actual_text_key_order_hash(self):
|
||||
sidecar = self.actual_canonical_sidecar()
|
||||
events = sidecar["at_390_event_verbatim"]
|
||||
self.assertEqual(len(events), 16)
|
||||
first = events[0]
|
||||
self.assertEqual(first["event_id"], "domain_event.000000000009")
|
||||
self.assertEqual(first["absolute_minute"], 362)
|
||||
self.assertEqual(first["payload"],
|
||||
{"old_state": 1, "reason": "ENTRY_COMPLETED", "state": 2})
|
||||
self.assertEqual(check.json_sha(first),
|
||||
"9ca80bec0c605ecd288f7671952d30fe66c67f291c5a618ea54623f07394edb9")
|
||||
for key in ("raw_event_sha256", "canonical_sha256"):
|
||||
self.assertEqual(sidecar["report"]["at_390"]["events"][0][key],
|
||||
"f28afa0899d7efea59ab99ae06276ba354779a73cac705fa4801162f27c592d7")
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(sidecar["report"]), sidecar)
|
||||
|
||||
def test_z1_all_actual_event_bindings(self):
|
||||
original = self.actual_canonical_sidecar()
|
||||
sidecar = copy.deepcopy(original)
|
||||
full = sidecar["report"]
|
||||
self.assertEqual(len(sidecar["at_390_event_verbatim"]), 16)
|
||||
self.assertEqual(len(full["at_390"]["events"]), 16)
|
||||
for event, summary in zip(sidecar["at_390_event_verbatim"], full["at_390"]["events"]):
|
||||
summary["raw_event_sha256"] = check.json_sha(event)
|
||||
summary["canonical_sha256"] = check.json_sha(
|
||||
check.canonical_event(event, full["event_id"]))
|
||||
self.assertEqual(sidecar["at_390_event_verbatim"], original["at_390_event_verbatim"])
|
||||
self.assertEqual(check.check_sidecar(check.redact_report(full), sidecar), full)
|
||||
for index in range(16):
|
||||
with self.subTest(index=index):
|
||||
changed = copy.deepcopy(sidecar)
|
||||
changed["report"]["at_390"]["events"][index]["raw_event_sha256"] = "0" * 64
|
||||
with self.assertRaisesRegex(ValueError, "at390 raw/canonical SHA"):
|
||||
check.check_sidecar(check.redact_report(changed["report"]), changed)
|
||||
|
||||
def test_z2_collision_rejected(self):
|
||||
for ambiguous in ({1: "numeric", "1": "text"},
|
||||
{"nested": [{1: "numeric", "1": "text"}]}):
|
||||
with self.subTest(value=ambiguous), self.assertRaisesRegex(
|
||||
ValueError, "canonical_key_collision"):
|
||||
check.json_sha(ambiguous)
|
||||
full = report()
|
||||
full["stop_reason"] = "canonical_key_collision"
|
||||
public, sidecar = evidence(full)
|
||||
with self.assertRaisesRegex(ValueError, "canonical_key_collision"):
|
||||
check.check_report(public, sidecar)
|
||||
public["stop_reason"] = "complete"
|
||||
with self.assertRaises(ValueError):
|
||||
check.check_report(public, sidecar)
|
||||
|
||||
def rejects(self, original, mutations, validator):
|
||||
for name, mutation in mutations.items():
|
||||
with self.subTest(rejection=name):
|
||||
|
||||
Reference in New Issue
Block a user