Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions qa/release_readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,21 @@ def validate_handoff_json(handoff_json: str, expected_sha: str) -> tuple[dict, l
evidence_file = resolve_evidence_file(manifest_path, str(value))
if not evidence_file.exists():
gaps.append(handoff_gap(str(manifest_path), f"manifest evidence_files.{evidence_kind} entry missing on disk: {value}"))
continue
if evidence_kind == "app_status_snapshots":
app_status, app_status_error = read_json_with_error(evidence_file)
if app_status_error:
gaps.append(handoff_gap(str(evidence_file), f"app-status snapshot {app_status_error}"))
continue
if app_status.get("schema") != "worldos.app-status.v1":
gaps.append(handoff_gap(str(evidence_file), "app-status schema is missing or wrong"))
if app_status.get("state_authority") != "engine":
gaps.append(handoff_gap(str(evidence_file), f"app-status state_authority {app_status.get('state_authority') or 'missing'} does not prove engine authority"))
if app_status.get("write_lane") != "/move":
gaps.append(handoff_gap(str(evidence_file), f"app-status write_lane {app_status.get('write_lane') or 'missing'} does not prove /move intent writes"))
app_status_build = app_status.get("build") if isinstance(app_status.get("build"), dict) else {}
if expected_sha and not build_sha_matches(str(app_status_build.get("sha") or ""), expected_sha):
gaps.append(handoff_gap(str(evidence_file), f"app-status build.sha {app_status_build.get('sha') or 'missing'} does not match --build-sha {expected_sha}"))
proof["valid"] = not gaps
return proof, gaps

Expand Down
140 changes: 139 additions & 1 deletion qa/test_release_readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def write_handoff_bundle(
sha: str = "deadbee",
manifest_sha: str | None = None,
reuse_manifest: bool = False,
app_status_overrides: dict | None = None,
) -> Path:
handoff_root = tmp / "handoff"
handoff_root.mkdir()
Expand Down Expand Up @@ -76,7 +77,6 @@ def write_handoff_bundle(
)
for evidence_file in (
"screenshots/final.png",
"app-status.final.json",
"session-surface.final.json",
"moves.ndjson",
"provider-trace-summary.json",
Expand All @@ -87,6 +87,21 @@ def write_handoff_bundle(
path = manifest_path.parent / evidence_file
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("ok\n", encoding="utf-8")
app_status = {
"schema": "worldos.app-status.v1",
"state_authority": "engine",
"write_lane": "/move",
"build": {"sha": manifest_sha or sha},
"live": {
"campaign_id": "camp_test",
"can_act": True,
"enabled_action_count": 5,
"moves_writable": True,
},
}
if app_status_overrides:
app_status.update(app_status_overrides)
(manifest_path.parent / "app-status.final.json").write_text(json.dumps(app_status), encoding="utf-8")
gates.append(
{
"name": gate_name,
Expand Down Expand Up @@ -1144,6 +1159,129 @@ def test_split_vm_persona_evidence_uses_handoff_json_for_native_gate(self):
self.assertTrue(payload["signals"]["handoff_proof"]["valid"])
self.assertIn("handoff_json=", payload["gate_detail"]["native_gate"])

def test_handoff_json_must_prove_engine_state_authority(self):
with tempfile.TemporaryDirectory() as td:
tmp = Path(td)
personas = ("newbie", "veteran", "adversarial", "narrative", "optimizer")
runs = [self.write_persona_run(tmp, persona, include_part_a=False) for persona in personas]
story, mech, behavioral, audit, palette = self.write_release_inputs(tmp)
handoff = self.write_handoff_bundle(tmp, app_status_overrides={"state_authority": "viewer"})

rc, _text, payload = self.run_rri(
tmp,
"--runs",
",".join(str(r) for r in runs),
"--expected-personas",
",".join(personas),
"--story",
str(story),
"--mech",
str(mech),
"--behavioral",
"GREEN",
"--behavioral-path",
str(behavioral),
"--ui-audit",
"PASS",
"--ui-audit-log",
str(audit),
"--palette-live",
"true",
"--palette-source",
str(palette),
"--handoff-json",
str(handoff),
"--build-sha",
"deadbee",
)

self.assertEqual(rc, 1)
self.assertFalse(payload["release_ready"])
self.assertFalse(payload["signals"]["handoff_proof"]["valid"])
self.assertIn("app-status state_authority viewer does not prove engine authority", " ".join(gap["detail"] for gap in payload["evidence_gaps"]))

def test_handoff_json_must_prove_move_write_lane(self):
with tempfile.TemporaryDirectory() as td:
tmp = Path(td)
personas = ("newbie", "veteran", "adversarial", "narrative", "optimizer")
runs = [self.write_persona_run(tmp, persona, include_part_a=False) for persona in personas]
story, mech, behavioral, audit, palette = self.write_release_inputs(tmp)
handoff = self.write_handoff_bundle(tmp, app_status_overrides={"write_lane": "/snapshot"})

rc, _text, payload = self.run_rri(
tmp,
"--runs",
",".join(str(r) for r in runs),
"--expected-personas",
",".join(personas),
"--story",
str(story),
"--mech",
str(mech),
"--behavioral",
"GREEN",
"--behavioral-path",
str(behavioral),
"--ui-audit",
"PASS",
"--ui-audit-log",
str(audit),
"--palette-live",
"true",
"--palette-source",
str(palette),
"--handoff-json",
str(handoff),
"--build-sha",
"deadbee",
)

self.assertEqual(rc, 1)
self.assertFalse(payload["release_ready"])
self.assertFalse(payload["signals"]["handoff_proof"]["valid"])
self.assertIn("app-status write_lane /snapshot does not prove /move intent writes", " ".join(gap["detail"] for gap in payload["evidence_gaps"]))

def test_handoff_json_must_prove_app_status_build_sha(self):
with tempfile.TemporaryDirectory() as td:
tmp = Path(td)
personas = ("newbie", "veteran", "adversarial", "narrative", "optimizer")
runs = [self.write_persona_run(tmp, persona, include_part_a=False) for persona in personas]
story, mech, behavioral, audit, palette = self.write_release_inputs(tmp)
handoff = self.write_handoff_bundle(tmp, app_status_overrides={"build": {"sha": "badcafe"}})

rc, _text, payload = self.run_rri(
tmp,
"--runs",
",".join(str(r) for r in runs),
"--expected-personas",
",".join(personas),
"--story",
str(story),
"--mech",
str(mech),
"--behavioral",
"GREEN",
"--behavioral-path",
str(behavioral),
"--ui-audit",
"PASS",
"--ui-audit-log",
str(audit),
"--palette-live",
"true",
"--palette-source",
str(palette),
"--handoff-json",
str(handoff),
"--build-sha",
"deadbee",
)

self.assertEqual(rc, 1)
self.assertFalse(payload["release_ready"])
self.assertFalse(payload["signals"]["handoff_proof"]["valid"])
self.assertIn("app-status build.sha badcafe does not match --build-sha deadbee", " ".join(gap["detail"] for gap in payload["evidence_gaps"]))

def test_missing_handoff_json_blocks_native_gate_when_persona_runs_have_no_part_a(self):
with tempfile.TemporaryDirectory() as td:
tmp = Path(td)
Expand Down
Loading