feat: review durable Human Gate decision history (Closes #1441)
CI / lint (pull_request) Successful in 4m43s
CI / build-release (pull_request) Successful in 13s
CI / browser-journey (pull_request) Successful in 7m26s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-26 19:43:50 +00:00
1 parent efa01cf47f
commit a5d05d7590
22 files changed
+316 -34

No files matched your search

@@ -154,3 +154,84 @@ def test_release_artifact_reopens_human_gates_with_one_fresh_mobile_snapshot(
fake.shutdown()
fake.server_close()
fake_thread.join(timeout=5)
def test_release_artifact_reviews_live_human_gate_history_and_receipt_on_phone(tmp_path: Path):
archives = sorted((ROOT / "dist").glob("stackchain-dashboard-*.tar.gz"))
assert len(archives) == 1
fake = FakeGiteaServer(("127.0.0.1", 0))
fake_thread = threading.Thread(target=fake.serve_forever, daemon=True)
fake_thread.start()
browser_errors: list[str] = []
history_requests: list[str] = []
held = {
"id": "held", "title": "Held candidate", "project": "stackchain/stackchain-dashboard",
"candidate_hash": "bbb222", "state": "held", "revision": 2, "priority": 5,
"created_at": 100, "updated_at": 200, "reason": "Needs mobile evidence",
"override_reason": "", "checklist": {}, "receipt_id": "receipt-2",
"checks": [], "artifacts": [], "links": [], "provenance": {},
"history": [{"action": "held", "at": 200, "receipt_id": "receipt-2"}],
}
released = {
**held, "id": "released", "title": "Released candidate", "candidate_hash": "aaa111",
"state": "released", "updated_at": 150, "reason": "", "receipt_id": "receipt-1",
}
try:
with release_server(
archives[0], tmp_path, f"http://127.0.0.1:{fake.server_port}"
) as origin, sync_playwright() as playwright:
browser = playwright.chromium.launch(args=["--ignore-certificate-errors"])
context = browser.new_context(
viewport={"width": 390, "height": 844}, ignore_https_errors=True
)
page = context.new_page()
page.on("pageerror", lambda error: browser_errors.append(error.stack or str(error)))
def gates_route(route):
history_requests.append(route.request.url)
if route.request.url.endswith("state=all"):
payload = {"pending_count": 0, "items": [held, released]}
elif route.request.url.endswith("/held"):
payload = held
else:
payload = {"pending_count": 0, "items": []}
route.fulfill(status=200, content_type="application/json", body=json.dumps(payload))
page.route("**/api/v1/human-gates**", gates_route)
page.route("**/api/v1/human-gates/**", gates_route)
page.route("**/api/v1/human-gate-receipts/receipt-2", lambda route: route.fulfill(
status=200, content_type="application/json", body=json.dumps({
"receipt_id": "receipt-2", "gate_id": "held", "candidate_hash": "bbb222",
"state": "held", "decided_at": 200, "reason": "Needs mobile evidence",
"override_reason": "", "checklist": {}, "unmet_required_checks": [],
})
))
page.goto(origin + "/", wait_until="networkidle")
page.locator('input[name="device_label"]').fill("Human Gate history phone")
page.locator('input[name="access_token"]').fill(ACCESS_TOKEN)
page.locator("#submit-sign-in").click()
page.wait_for_url(origin + "/", wait_until="networkidle")
page.evaluate("document.querySelector('#open-human-gates').click()")
expect(page.locator("#human-gates")).to_be_visible()
cached_before = page.evaluate("Object.keys(localStorage).sort()")
page.locator("#human-gates-history").click()
expect(page.locator("#human-gates-status")).to_have_text("2 past Human Gate decisions.")
expect(page.locator('[data-human-gate-history-id="held"]')).to_contain_text("Held")
expect(page.locator('[data-human-gate-history-id="released"]')).to_contain_text("Released")
page.locator('[data-human-gate-history-id="held"]').click()
expect(page.locator("#human-gate-detail")).to_contain_text("Needs mobile evidence")
expect(page.locator("#human-gate-detail")).to_contain_text("receipt-2")
expect(page.locator("#human-gate-detail [data-gate-decision]")).to_have_count(0)
assert page.evaluate("Object.keys(localStorage).sort()") == cached_before
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
for selector in ("#human-gates-pending", "#human-gates-history"):
bounds = page.locator(selector).bounding_box()
assert bounds and bounds["height"] >= 44
assert any(url.endswith("state=all") for url in history_requests)
assert not browser_errors
browser.close()
finally:
fake.shutdown()
fake.server_close()
fake_thread.join(timeout=5)