Skip to content

Commit df87201

Browse files
feat(observability): add an AMS cross-link panel to the resource hub (#5640)
Adds an additive markdown panel to grafana/dashboards/resource-hub.json so a dual-role operator running both ORB and AMS on one box can discover the AMS observability material from the hub, the same way the hub already cross-links its other integrated services. The panel links to the in-repo 'Observing your miner' guide (packages/gittensory-miner/docs/observability.md) -- the AMS observability entry point (same target as the merged #5191 callout) -- rather than a not-yet-published dashboard, so the link is never dead. A dedicated test asserts the panel exists, is additive (the two original panels stay untouched, panel ids stay unique), and that its link target resolves to a real in-repo file. Closes #5189 Co-authored-by: jaytbarimbao-collab <300663773+jaytbarimbao-collab@users.noreply.github.com>
1 parent 941c300 commit df87201

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

grafana/dashboards/resource-hub.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,17 @@
6868
"mode": "markdown",
6969
"content": "## 📊 Dashboards\n- **[Upstream PRs & issues (GitHub)](/d/loopover-github)** — live, accurate census + open-PR triage (GitHub API).\n- **[Reviews & PRs (maintainer)](/d/loopover-maintainer)** — loopover's own review activity + reviewed-PR log.\n- **[AI usage](/d/loopover-ai-usage)** — durable cross-provider ai_usage_events (filterable by provider/feature/model), live Prometheus counters, and Claude Code's own OTEL session telemetry, all in one place.\n- **[LoopOver (infra)](/d/loopover-selfhost)** — queue, jobs, HTTP, GitHub API cache/rate limits.\n- **[GPU metrics](/d/loopover-gpu)** — utilization/VRAM for a self-hosted Ollama GPU box.\n- **[Infra health](/d/loopover-infra-health)** — host CPU/mem/disk/network (node-exporter), per-container resource usage (cAdvisor), Redis, Qdrant, and whether the observability stack itself is up.\n- **[REES (review-enrichment)](/d/loopover-rees)** — request outcomes/latency and per-analyzer run/timeout/degrade rates for the optional `--profile rees` service.\n- **[Browserless (visual review)](/d/loopover-browserless)** — queue depth, concurrency, and rejection/error/timeout rate for the optional `--profile visual-review` screenshot service.\n- **[Sentry issues](/d/loopover-sentry)** — recent unresolved issues, top issues by event count, and error-volume trend, queried live from Sentry (`scripts/setup-sentry-datasource.sh`). The plain link below still opens Sentry itself for actions this read-only view can't do (resolving/assigning issues).\n\n## 📈 Metrics & logs\n- **Prometheus** — [targets](http://localhost:9090/targets) · [graph](http://localhost:9090)\n- **Alertmanager** — [alerts](http://localhost:9093)\n- **Loki** — query in [Explore](/explore) (pick the *Loki* datasource), e.g. `{compose_service=\"loopover\"}`\n- **Sentry** — release/source-map enriched errors. Edit the dashboard link if your project URL differs.\n\n## 🩺 Quick health checks\n| What | Where |\n|---|---|\n| App serving | `GET /ready` → 200 |\n| AI wired | boot log `selfhost_ai_provider` |\n| Embeds wired | boot log `selfhost_embed_provider` |\n| Vectors wired | boot log `selfhost_vectorize` |\n| Token spend | **[AI usage](/d/loopover-ai-usage)** dashboard |\n\n## 📚 Docs\n- [Maintainer self-hosting](https://gittensory.aethereal.dev/docs/maintainer-self-hosting) — setup, configuration, AI, REES, RAG, operations, and troubleshooting."
7070
}
71+
},
72+
73+
{
74+
"id": 3,
75+
"type": "text",
76+
"title": "AMS — Autonomous Miner System",
77+
"gridPos": { "h": 4, "w": 24, "x": 0, "y": 13 },
78+
"options": {
79+
"mode": "markdown",
80+
"content": "## 🤖 AMS — Autonomous Miner System\n- **[Observing your miner](https://github.com/JSONbored/gittensory/blob/main/packages/gittensory-miner/docs/observability.md)** — AMS is a separate, local `gittensory-miner` CLI a dual-role operator may run alongside ORB on the same box. This guide is the AMS observability entry point: it covers pointing Grafana at the redacted AMS ledger datasources and loading its Grafana dashboard, separate from the ORB review-service observability above."
81+
}
7182
}
7283
]
7384
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { existsSync, readFileSync } from "node:fs";
2+
import { join } from "node:path";
3+
import { describe, expect, it } from "vitest";
4+
5+
type TextPanel = {
6+
id?: number;
7+
type?: string;
8+
title?: string;
9+
gridPos?: { h: number; w: number; x: number; y: number };
10+
options?: { mode?: string; content?: string };
11+
};
12+
type Dashboard = { uid: string; title: string; panels: TextPanel[] };
13+
14+
const dashboardPath = join(process.cwd(), "grafana/dashboards/resource-hub.json");
15+
const readDashboard = (): Dashboard => JSON.parse(readFileSync(dashboardPath, "utf8")) as Dashboard;
16+
17+
// The AMS cross-link (#5189) points the hub at the "Observing your miner" guide — the AMS observability entry
18+
// point (mirrors the just-merged #5191 callout target). Kept as an in-repo path so the link-target-exists
19+
// invariant below can prove it is not a dead link.
20+
const AMS_GUIDE_REPO_PATH = "packages/gittensory-miner/docs/observability.md";
21+
const AMS_GUIDE_URL = `https://github.com/JSONbored/gittensory/blob/main/${AMS_GUIDE_REPO_PATH}`;
22+
23+
function amsPanel(dashboard = readDashboard()): TextPanel | undefined {
24+
return dashboard.panels.find((panel) => /AMS/i.test(panel.title ?? ""));
25+
}
26+
27+
describe("LoopOver — Resource hub: AMS cross-link (#5189)", () => {
28+
it("keeps the hub's own identity and its existing two panels untouched (additive change only)", () => {
29+
const dashboard = readDashboard();
30+
expect(dashboard.uid).toBe("loopover-hub");
31+
expect(dashboard.title).toBe("LoopOver — Resource hub");
32+
// The two original panels (Integrated services, Observability & dashboards) still exist unchanged.
33+
expect(dashboard.panels.some((p) => p.title === "Integrated services")).toBe(true);
34+
expect(dashboard.panels.some((p) => p.title === "Observability & dashboards")).toBe(true);
35+
});
36+
37+
it("adds exactly one AMS panel, as a markdown text panel matching the hub's existing panel style", () => {
38+
const panel = amsPanel();
39+
expect(panel, "an AMS panel must exist").toBeDefined();
40+
expect(panel?.type).toBe("text");
41+
expect(panel?.options?.mode).toBe("markdown");
42+
expect(panel?.gridPos, "must have grid sizing like the other panels").toBeDefined();
43+
// It notes AMS is a separate CLI a dual-role operator runs alongside ORB (context, not a feature spec).
44+
expect(panel?.options?.content).toMatch(/separate/i);
45+
expect(panel?.options?.content).toMatch(/gittensory-miner/);
46+
});
47+
48+
it("gives every panel a unique id (a duplicate id silently breaks Grafana panel rendering)", () => {
49+
const ids = readDashboard().panels.map((p) => p.id);
50+
expect(new Set(ids).size).toBe(ids.length);
51+
});
52+
53+
it("INVARIANT: the AMS link target is a well-formed reference to a file that actually exists — never a dead link", () => {
54+
const content = amsPanel()?.options?.content ?? "";
55+
expect(content).toContain(AMS_GUIDE_URL);
56+
// The GitHub-blob URL resolves to a real in-repo file: prove it exists so a typo'd path can't ship.
57+
expect(existsSync(join(process.cwd(), AMS_GUIDE_REPO_PATH))).toBe(true);
58+
});
59+
});

0 commit comments

Comments
 (0)