Every fleet model card (and the #models docs section) now states, in plain family-facing terms, what the model is equivalent to: frank-large ~ Claude Sonnet 5 / OpenAI-Luna, frank-primary ~ Claude Opus 4.8 / OpenAI-Terra (both Summer 2026 frontier), frank-secondary ~ last year (2025) frontier. Rendered as a new details.equiv line (desc-equiv, styled in the head color, bare-mode clean); pinned in homepage_config.bats.
97 lines
4.2 KiB
Bash
97 lines
4.2 KiB
Bash
#!/usr/bin/env bats
|
|
# Seam: shared/homepage/services.json — the service config the checker reads
|
|
# and the pages render. It must be a JSON list; every entry carries name,
|
|
# category, section, and a host:port probe. category is user or admin;
|
|
# section must be one the page's SECTION_ORDER renders. method, when
|
|
# present, is tcp or http. wip, when set, is "open" or "closed".
|
|
|
|
load helpers
|
|
|
|
@test "services.json: valid schema" {
|
|
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
|
import json, sys
|
|
path = sys.argv[1]
|
|
try:
|
|
data = json.load(open(path))
|
|
except FileNotFoundError:
|
|
sys.exit("services.json not found")
|
|
assert isinstance(data, list), "top level must be a list"
|
|
assert data, "the list must not be empty"
|
|
for svc in data:
|
|
for field in ("name", "category", "section", "probe"):
|
|
assert field in svc, f"{svc.get('name', '?')} is missing {field}"
|
|
assert svc["category"] in ("user", "admin"), svc["category"]
|
|
if "method" in svc:
|
|
assert svc["method"] in ("tcp", "http"), svc["method"]
|
|
wip = svc.get("wip")
|
|
assert wip is False or wip in ("open", "closed"), f"{svc['name']} wip must be false, 'open' or 'closed'"
|
|
host, _, port = svc["probe"].rpartition(":")
|
|
assert host and port.isdigit(), f"bad probe {svc['probe']}"
|
|
PY
|
|
}
|
|
|
|
@test "services.json: every section is one the page renders" {
|
|
# A service whose section is not in app.js's SECTION_ORDER for its
|
|
# category is silently dropped from the page. Pin the contract.
|
|
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
|
import json, sys
|
|
data = json.load(open(sys.argv[1]))
|
|
allowed = {
|
|
"user": {"AI", "Web Apps", "Media Library Apps"},
|
|
"admin": {"Media Library Apps", "Observability", "AI", "Infrastructure"},
|
|
}
|
|
for svc in data:
|
|
assert svc.get("section") in allowed[svc["category"]], (
|
|
f"{svc['name']} has unrenderable section {svc.get('section')!r} for {svc['category']}")
|
|
PY
|
|
}
|
|
|
|
@test "services.json: every user service has an Open link, status-only backends do not" {
|
|
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
|
import json, sys
|
|
data = json.load(open(sys.argv[1]))
|
|
for svc in data:
|
|
show = svc.get("showLink", True)
|
|
if show:
|
|
assert svc.get("url"), f"{svc['name']} shows a link but has no url"
|
|
else:
|
|
assert not svc.get("url"), f"{svc['name']} is status-only but has a url"
|
|
PY
|
|
}
|
|
|
|
@test "the LLM status cards track the fleet, not retired service names" {
|
|
# This drifted twice: the card probing Dankserver's :7770 was still named
|
|
# frank-small long after frank-medium took that endpoint, and the endpoint
|
|
# itself has since been frank-medium (radiance), frank-primary (lucebox)
|
|
# and frank-primary (llama.cpp). The probe target is what matters; the
|
|
# name has to follow it.
|
|
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
|
import json, sys
|
|
services = json.load(open(sys.argv[1]))
|
|
by_probe = {s["probe"]: s["name"] for s in services}
|
|
primary = by_probe.get("192.168.0.235:7770")
|
|
assert primary == "frank-primary", f"Dankserver :7770 card is named {primary!r}"
|
|
# Dankclaw serves two endpoints on two ports: frank-large (unsloth llama.cpp
|
|
# on the iGPU, 7770) and frank-secondary (llama.cpp on the RTX 3090, 7771).
|
|
assert by_probe.get("127.0.0.1:7770") == "frank-large", by_probe.get("127.0.0.1:7770")
|
|
assert by_probe.get("127.0.0.1:7771") == "frank-secondary", by_probe.get("127.0.0.1:7771")
|
|
PY
|
|
}
|
|
|
|
@test "every fleet LLM card carries a user-facing equivalence note" {
|
|
# Frank's capability framing (2026-10-08): each card must state, in plain
|
|
# terms, what the model is equivalent to — the cards' whole purpose for
|
|
# family users is "how smart is this and what is it for".
|
|
python3 - "$REPO_ROOT/shared/homepage/services.json" <<'PY'
|
|
import json, sys
|
|
services = json.load(open(sys.argv[1]))
|
|
fleet = {s["name"]: s for s in services if s.get("name") in ("frank-large", "frank-primary", "frank-secondary")}
|
|
assert len(fleet) == 3, sorted(fleet)
|
|
for name, s in fleet.items():
|
|
eq = s.get("details", {}).get("equiv", "")
|
|
assert eq.strip(), f"{name} card has no equivalence note"
|
|
PY
|
|
grep -q "desc-equiv" "$REPO_ROOT/shared/homepage/app.js"
|
|
grep -q "desc-equiv" "$REPO_ROOT/shared/homepage/style.css"
|
|
}
|