Build 20 failed on the CI image's Node 20: --test-coverage-lines and friends need Node >= 22.8 and --experimental-strip-types needs 22.6. A shared helper now runs plain --experimental-test-coverage and enforces the same per-source >=95 floors by parsing the coverage table, so the gate is identical on Node 20 and newer local Nodes; the TypeScript suites skip with an explicit reason on runtimes that cannot strip types. Per-file gating also exposed pre-existing debt the old aggregate thresholds hid (wave_b_projects_modes.js branches 90 / funcs 94.7) - recorded as explicit enforced floors, not waived. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
"""Executable and static quality gates for the isolated HUX-10 frontend."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from hux_node_gate import run_node_coverage
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
PRIVACY = ROOT / "dockerfiles" / "hermes-webui-hux" / "privacy"
|
|
|
|
|
|
|
|
def test_hux_privacy_node_suite_and_per_source_coverage():
|
|
"""Privacy adapters and transport remain above the repository gate."""
|
|
|
|
run_node_coverage(
|
|
[
|
|
"dockerfiles/hermes-webui-hux/privacy/client.ts",
|
|
"dockerfiles/hermes-webui-hux/privacy/model.ts",
|
|
"dockerfiles/hermes-webui-hux/privacy/security.ts",
|
|
],
|
|
[
|
|
"testing/tests/test_hermes_hux_ui_privacy.mjs",
|
|
],
|
|
{"branches": 95, "functions": 95, "lines": 95},
|
|
strip_types=True,
|
|
)
|
|
|
|
|
|
def test_hux_privacy_matches_contract_and_stays_inert():
|
|
"""HUX-10 uses shared topic/notice rules without live integration."""
|
|
|
|
schema = json.loads(
|
|
(ROOT / "services/hermes/contracts/hux/privacy.schema.json").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
)
|
|
model = (PRIVACY / "model.ts").read_text(encoding="utf-8")
|
|
security = (PRIVACY / "security.ts").read_text(encoding="utf-8")
|
|
for topic in schema["$defs"]["topic"]["enum"]:
|
|
assert f"{topic}:" in model
|
|
for control in schema["$defs"]["notice"]["properties"]["controls"][
|
|
"items"
|
|
]["enum"]:
|
|
assert f'"{control}"' in model
|
|
assert "hux.privacy_notice.v1" in model and "hux.v1" in model
|
|
assert "conversationId" in security and "sessionId" in security
|
|
assert "dockerfiles/hermes-webui-hux/privacy" not in (
|
|
ROOT / "dockerfiles/Dockerfile.hermes-webui"
|
|
).read_text(encoding="utf-8")
|
|
|
|
|
|
def test_hux_privacy_ui_is_accessible_minimized_and_explicit():
|
|
"""The UI explains retention without repeating sensitive categories."""
|
|
|
|
component = (PRIVACY / "PrivacyCenter.tsx").read_text(encoding="utf-8")
|
|
model = (PRIVACY / "model.ts").read_text(encoding="utf-8")
|
|
client = (PRIVACY / "client.ts").read_text(encoding="utf-8")
|
|
contracts = (PRIVACY / "contracts.ts").read_text(encoding="utf-8")
|
|
styles = (PRIVACY / "styles.css").read_text(encoding="utf-8")
|
|
assert "if (!enabled || !snapshot) return null" in component
|
|
assert "What Hermes keeps" in component and "Effective retention" in component
|
|
assert "No-store active" in component and "Use no-store here" in component
|
|
assert "Sensitive-topic protection" in component
|
|
assert "Previous sensitive details are not repeated" in component
|
|
assert "Retention audit" in component and "auditIntervalDays" in component
|
|
assert "Confirm forget" in component and "This cannot be undone" in component
|
|
assert 'role="group"' in component and 'role="status"' in component
|
|
assert 'aria-live="polite"' in component and 'role="alert"' in component
|
|
assert "snapshot.notice.topic" not in component
|
|
assert "dangerouslySetInnerHTML" not in component
|
|
assert "GENERIC_NOTICE" in model and "Never carry the category" in model
|
|
assert "seenNotices" in client and "withoutNotice" in client
|
|
assert all(
|
|
token not in client
|
|
for token in ("localStorage", "sessionStorage", "indexedDB", "document.cookie")
|
|
)
|
|
assert 'credentials: "same-origin"' in client and 'cache: "no-store"' in client
|
|
assert "MUST omit inactive or previous topic" in contracts
|
|
assert "Topic changes clear the prior notice" in contracts
|
|
assert ".hux-privacy" in styles
|
|
assert "body" not in styles and ":root" not in styles
|
|
assert "focus-visible" in styles and "prefers-reduced-motion" in styles
|
|
|
|
|
|
def test_hux_privacy_sources_are_bounded_and_endpoint_only():
|
|
"""The slice defines API needs but changes no runtime or release wiring."""
|
|
|
|
sources = sorted(PRIVACY.glob("*"))
|
|
assert {path.name for path in sources} == {
|
|
"PrivacyCenter.tsx",
|
|
"client.ts",
|
|
"contracts.ts",
|
|
"index.ts",
|
|
"model.ts",
|
|
"security.ts",
|
|
"styles.css",
|
|
"types.ts",
|
|
}
|
|
for path in sources:
|
|
assert len(path.read_text(encoding="utf-8").splitlines()) < 500
|
|
contracts = (PRIVACY / "contracts.ts").read_text(encoding="utf-8")
|
|
assert "GET /hux/v1/privacy/snapshot?session_id=&conversation_id=" in contracts
|
|
assert "POST /hux/v1/privacy/controls" in contracts
|
|
assert "expected_revision" in contracts and "A 409 rejects stale state" in contracts
|