feat(bell): add alert acknowledgement vertical slice
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-11 17:01:53 +08:00
parent 477afa6ba2
commit 8208118904
29 changed files with 1820 additions and 56 deletions
+78
View File
@@ -0,0 +1,78 @@
"""Static contract checks for the T-020 Bell alert vertical slice."""
from __future__ import annotations
import json
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
OPENAPI = ROOT / "docs" / "contracts" / "bell-alert-console-v1.openapi.json"
WEB = ROOT / "Bell" / "web" / "assets"
class BellAlertContractTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.contract = json.loads(OPENAPI.read_text(encoding="utf-8"))
cls.html = (WEB / "index.html").read_text(encoding="utf-8")
cls.css = (WEB / "style.css").read_text(encoding="utf-8")
cls.js = (WEB / "app.js").read_text(encoding="utf-8")
def test_loopback_routes_and_bounds_are_frozen(self) -> None:
self.assertEqual("3.1.0", self.contract["openapi"])
self.assertTrue(self.contract["servers"][0]["url"].startswith("http://127.0.0.1:"))
paths = self.contract["paths"]
self.assertEqual(
{"/alerts", "/alerts/{alert_id}", "/alerts/{alert_id}:ack", "/alerts/{alert_id}:close"},
set(paths),
)
limit = paths["/alerts"]["get"]["parameters"][1]["schema"]
self.assertEqual({"type": "integer", "minimum": 1, "maximum": 100, "default": 16}, limit)
key = self.contract["components"]["parameters"]["IdempotencyKey"]["schema"]
self.assertEqual((8, 128), (key["minLength"], key["maxLength"]))
def test_alert_is_separate_from_event_and_missing_capabilities_are_explicit(self) -> None:
detail = self.contract["components"]["schemas"]["AlertDetail"]
encoded = json.dumps(detail, ensure_ascii=False)
self.assertIn("events", encoded)
self.assertIn("transitions", encoded)
self.assertIn("evidence_status", encoded)
self.assertIn("delivery_status", encoded)
self.assertIn("证据切片尚未启用", self.html)
self.assertIn("升级与通知尚未启用", self.html)
self.assertNotRegex(self.html + self.js, r"短信已发送|语音已接通|倒计时[::]\s*\d")
def test_console_is_dependency_free_and_does_not_embed_secrets(self) -> None:
combined = self.html + self.css + self.js
self.assertNotRegex(combined, r"https?://|cdn\.|localStorage|sessionStorage")
self.assertNotRegex(combined, r"postgres(?:ql)?://|rtsp://|BELL_DB_DSN")
self.assertIn('type="password"', self.html)
self.assertIn('Cache-Control", "no-store"', (ROOT / "Bell" / "web" / "web.go").read_text(encoding="utf-8"))
def test_accessibility_and_small_screen_contract(self) -> None:
for marker in (
'aria-live="polite"',
'aria-live="assertive"',
'aria-busy="false"',
'aria-pressed="true"',
'href="#alerts"',
):
self.assertIn(marker, self.html)
self.assertIn("@media(max-width:480px)", self.css.replace(" ", ""))
self.assertIn("prefers-reduced-motion:reduce", self.css.replace(" ", ""))
self.assertIn("[hidden]{display:none!important}", self.css.replace(" ", ""))
self.assertRegex(self.css, r"min-height:44px")
self.assertIn("textContent", self.js)
self.assertNotIn("innerHTML", self.js)
def test_console_has_loading_empty_retry_conflict_and_keyboard_paths(self) -> None:
for marker in ("skeleton", "没有预警", "加载失败,重试", "already_acknowledged", "keydown"):
self.assertIn(marker, self.js)
self.assertRegex(self.js, re.escape('limit:"16"'))
if __name__ == "__main__":
unittest.main()
+20
View File
@@ -64,6 +64,8 @@ class PostgresContractTests(unittest.TestCase):
"015_privileges_audit_relay.sql",
"016_event_ingress.sql",
"017_privileges_event_ingress.sql",
"018_bell_alerts.sql",
"019_privileges_bell_alerts.sql",
],
names,
)
@@ -170,6 +172,24 @@ class PostgresContractTests(unittest.TestCase):
)
self.assertNotIn("endpoint_ref", privileges.split("revoke all on table sense.devices")[1])
def test_alert_history_is_append_only_and_runtime_is_least_privilege(self) -> None:
migration = normalized(migration_text("018_bell_alerts.sql"))
privileges = normalized(migration_text("019_privileges_bell_alerts.sql"))
for marker in (
"create table if not exists bell.rule_versions",
"create table if not exists bell.event_rule_sweeps",
"create table if not exists bell.rule_evaluations",
"create table if not exists bell.alerts",
"create table if not exists bell.alert_events",
"create table if not exists bell.alert_transitions",
"create table if not exists bell.alert_command_receipts",
"insert into bell.schema_migrations(version) values (6)",
):
self.assertIn(marker, migration)
self.assertIn("before update or delete", migration)
self.assertIn("grant select, insert on table", privileges)
self.assertNotRegex(privileges, r"grant\s+(?:update|delete|truncate|all)")
if __name__ == "__main__":
unittest.main()