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.
|
# and Python. Fixed decimal form is valid JSON; keys and strings remain verbatim.
|
||||||
def encode(v):
|
def encode(v):
|
||||||
if isinstance(v, dict):
|
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])
|
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + encode(v[k])
|
||||||
for k in sorted(v)) + "}"
|
for k in sorted(v)) + "}"
|
||||||
if isinstance(v, list):
|
if isinstance(v, list):
|
||||||
@@ -335,6 +337,7 @@ def check_sidecar(public, sidecar):
|
|||||||
"sidecar root/missing")
|
"sidecar root/missing")
|
||||||
check_no_metrics(public)
|
check_no_metrics(public)
|
||||||
full = sidecar["report"]
|
full = sidecar["report"]
|
||||||
|
require(full.get("stop_reason") != "canonical_key_collision", "canonical_key_collision")
|
||||||
require(public == redact_report(full), "public redaction differs from W2")
|
require(public == redact_report(full), "public redaction differs from W2")
|
||||||
restored = copy.deepcopy(public)
|
restored = copy.deepcopy(public)
|
||||||
for path, payload in payload_paths(restored).items():
|
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_event_verbatim: Array = []
|
||||||
var at_390_pending: Array = []
|
var at_390_pending: Array = []
|
||||||
var redaction_ok := true
|
var redaction_ok := true
|
||||||
|
var canonical_key_collision := false
|
||||||
const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED",
|
const SAVE_EVENTS := ["SAVE_CHECKPOINT_SUCCEEDED", "SAVE_CHECKPOINT_FAILED", "SAVE_LOADED",
|
||||||
"SAVE_LOAD_FAILED", "SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED",
|
"SAVE_LOAD_FAILED", "SAVE_MIGRATION_SUCCEEDED", "SAVE_MIGRATION_FAILED",
|
||||||
"SAVE_RECOVERY_LOADED", "SAVE_RECOVERY_CANDIDATE_LOADED", "SAVE_RECOVERY_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:
|
func canonical_json(value: Variant) -> String:
|
||||||
if value is Dictionary:
|
if value is Dictionary:
|
||||||
var keys: Array = value.keys()
|
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 = []
|
var parts: PackedStringArray = []
|
||||||
for key: Variant in keys:
|
for key: Variant in keys:
|
||||||
parts.append(JSON.stringify(str(key)) + ":" + canonical_json(value[key]))
|
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:
|
func finish(reason: String) -> void:
|
||||||
if finished:
|
if finished:
|
||||||
return
|
return
|
||||||
|
if canonical_key_collision:
|
||||||
|
reason = "canonical_key_collision"
|
||||||
finished = true
|
finished = true
|
||||||
report["stop_reason"] = reason
|
report["stop_reason"] = reason
|
||||||
report["key_forms"] = key_forms
|
report["key_forms"] = key_forms
|
||||||
|
|||||||
+63
@@ -252,6 +252,69 @@ class RestartTests(unittest.TestCase):
|
|||||||
self.assertIn("test_restart.py", self.inventory_before)
|
self.assertIn("test_restart.py", self.inventory_before)
|
||||||
self.assertEqual(self.here_inventory(), 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):
|
def rejects(self, original, mutations, validator):
|
||||||
for name, mutation in mutations.items():
|
for name, mutation in mutations.items():
|
||||||
with self.subTest(rejection=name):
|
with self.subTest(rejection=name):
|
||||||
|
|||||||
Reference in New Issue
Block a user