diff --git a/apps/gittensory-ui/src/components/site/app-shell.tsx b/apps/gittensory-ui/src/components/site/app-shell.tsx index 7c9ff84630..7c91a8113c 100644 --- a/apps/gittensory-ui/src/components/site/app-shell.tsx +++ b/apps/gittensory-ui/src/components/site/app-shell.tsx @@ -77,7 +77,7 @@ const GROUPS: NavGroup[] = [ to: "/app/analytics", label: "Analytics", icon: BarChart3, - roles: ["operator", "maintainer"], + roles: ["operator"], }, { to: "/app/operator", label: "Operator", icon: Wrench, roles: ["operator"] }, ], diff --git a/apps/gittensory-ui/src/components/site/usage-analytics-panels.tsx b/apps/gittensory-ui/src/components/site/usage-analytics-panels.tsx new file mode 100644 index 0000000000..7e3e6762cf --- /dev/null +++ b/apps/gittensory-ui/src/components/site/usage-analytics-panels.tsx @@ -0,0 +1,353 @@ +import { Stat, StatusPill } from "@/components/site/control-primitives"; + +type WeeklyMetric = { + id: string; + label: string; + value: number; + detail: string; +}; + +type RoleRow = { + role: string; + count: number; + activeActors: number; + activeRepos: number; +}; + +type RetentionWindow = { + window: string; + activeActors: number; + retainedActors: number; + retentionRate: number; + capped: boolean; + byRole: Array<{ + role: string; + activeActors: number; + retainedActors: number; + retentionRate: number; + }>; +}; + +type CommandBucket = { + command: string; + feedbackCount: number; + usefulCount: number; + notUsefulCount: number; + usefulnessRate: number | null; +}; + +export function WeeklyValueMetricsPanel({ + metrics, + warnings, +}: { + metrics: WeeklyMetric[]; + warnings: string[]; +}) { + if (metrics.length === 0) return null; + return ( +
+
+
+

Weekly value metrics

+

+ Rollup-backed adoption and ecosystem value without raw secrets or source data. +

+
+ 0 ? "degraded" : "ready"}> + {warnings.length > 0 ? `${warnings.length} warning(s)` : "rollup-backed"} + +
+
+ {metrics.map((metric) => ( + {metric.detail}} + /> + ))} +
+ {warnings.length > 0 ? ( + + ) : null} +
+ ); +} + +export function AdoptionRetentionPanel({ + byRole, + retention, + activationByRole, +}: { + byRole: RoleRow[]; + retention: RetentionWindow[]; + activationByRole: Array<{ + role: string; + firstUsefulActionActors: number; + doctorPassActors: number; + }>; +}) { + if (byRole.length === 0 && retention.length === 0) return null; + return ( +
+

Adoption & retention

+

+ Active miners and maintainers from hashed rollups — no wallets, hotkeys, or private source + data. +

+
+ ({ key: row.role, count: row.activeActors }))} + /> + ({ + key: row.role, + count: row.firstUsefulActionActors, + hint: `${row.doctorPassActors} doctor pass`, + }))} + /> +
+
+ Retention windows +
+
+ {retention.length > 0 ? ( + retention.map((window) => ( +
+
+ + {formatRetentionWindow(window.window)} + + + {formatPercent(window.retentionRate)} + +
+
+ {window.retainedActors}/{window.activeActors} actors + {window.capped ? " · capped scan" : ""} +
+ {window.byRole.length > 0 ? ( +
+ {window.byRole.slice(0, 4).map((row) => ( +
+ {row.role} + {formatPercent(row.retentionRate)} +
+ ))} +
+ ) : null} +
+ )) + ) : ( +
No retention rollups yet
+ )} +
+
+
+
+ ); +} + +type CommandUsefulnessTotals = Omit & { answerCount: number }; + +export function CommandUsefulnessPanel({ + totals, + commands, + windowDays, +}: { + totals: CommandUsefulnessTotals; + commands: CommandBucket[]; + windowDays: number; +}) { + return ( +
+

Command usefulness

+

+ Maintainer feedback on GitHub command answers — separate from security audit events. +

+
+ last {windowDays} days} + /> + positive signal} + /> + answers: {totals.answerCount}} + /> + improvement signal} + /> +
+ {commands.length > 0 ? ( +
+ + + + + + + + + + + {commands.slice(0, 8).map((row) => ( + + + + + + + ))} + +
CommandFeedbackUsefulRate
{row.command}{row.feedbackCount}{row.usefulCount} + {row.usefulnessRate === null ? "—" : `${Math.round(row.usefulnessRate * 100)}%`} +
+
+ ) : ( +

+ No command feedback recorded in this window. +

+ )} +
+ ); +} + +export function ProductUsageBreakdownPanel({ + byEvent, + bySurface, + byTool = [], +}: { + byEvent: Array<{ eventName: string; count: number }>; + bySurface: Array<{ surface: string; count: number }>; + byTool?: Array<{ key: string; count: number }>; +}) { + const highlights = pickUsageHighlights(byEvent, byTool); + return ( +
+

Product usage breakdown

+

+ MCP commands, GitHub commands, PR packets, quiet skips, decision-pack tools, and drift + signals (7-day window). +

+
+ {highlights.map((item) => ( + {item.detail}} + /> + ))} +
+
+ ({ key: row.eventName, count: row.count }))} + /> + ({ key: row.surface, count: row.count }))} + /> +
+
+ ); +} + +function DimensionList({ + title, + rows, +}: { + title: string; + rows: Array<{ key: string; count: number; hint?: string }>; +}) { + return ( +
+
{title}
+
+ {rows.length > 0 ? ( + rows.map((row) => ( +
+ {row.key} + {row.count} +
+ )) + ) : ( +
No data
+ )} +
+
+ ); +} + +function pickUsageHighlights( + byEvent: Array<{ eventName: string; count: number }>, + byTool: Array<{ key: string; count: number }>, +) { + const sum = (names: string[]) => + byEvent + .filter((row) => names.includes(row.eventName)) + .reduce((total, row) => total + row.count, 0); + const mcp = sum(["mcp_request", "mcp_tool_called"]); + const github = sum(["agent_command_replied", "agent_command_skipped"]); + const quietSkips = sum(["agent_command_skipped"]); + const prPackets = sum(["agent_pr_packet_completed"]); + const preflights = sum(["agent_preflight_branch_completed", "local_branch_analysis_completed"]); + const decisionPacks = byTool + .filter((row) => row.key.includes("decision_pack")) + .reduce((total, row) => total + row.count, 0); + const driftSignals = sum(["upstream_drift_detected", "upstream_drift_filed"]); + return [ + { id: "mcp", label: "MCP usage", value: mcp, detail: "requests + tool calls" }, + { id: "github", label: "GitHub commands", value: github, detail: "replies + quiet skips" }, + { id: "quiet", label: "Quiet skips", value: quietSkips, detail: "intentional no-reply" }, + { id: "packets", label: "PR packets", value: prPackets, detail: "completed packets" }, + { + id: "preflight", + label: "PR preflights", + value: preflights, + detail: "branch preflight events", + }, + { + id: "decision", + label: "Decision packs", + value: decisionPacks, + detail: "MCP decision-pack tool calls", + }, + { + id: "drift", + label: "Drift incidents", + value: driftSignals, + detail: "upstream drift product events", + }, + ]; +} + +function formatRetentionWindow(window: string): string { + if (window === "previous_7_days") return "7-day retention"; + if (window === "previous_30_days") return "30-day retention"; + return window.replaceAll("_", " "); +} + +function formatPercent(rate: number): string { + return `${Math.round(rate * 1000) / 10}%`; +} diff --git a/apps/gittensory-ui/src/routes/app.analytics.tsx b/apps/gittensory-ui/src/routes/app.analytics.tsx index 317fc6c16d..b708c9a6c4 100644 --- a/apps/gittensory-ui/src/routes/app.analytics.tsx +++ b/apps/gittensory-ui/src/routes/app.analytics.tsx @@ -3,6 +3,12 @@ import { createFileRoute } from "@tanstack/react-router"; import { BoundaryBadge, Stat, StatusPill } from "@/components/site/control-primitives"; import { StateBoundary } from "@/components/site/state-views"; import { TrendChart } from "@/components/site/trend-chart"; +import { + AdoptionRetentionPanel, + CommandUsefulnessPanel, + ProductUsageBreakdownPanel, + WeeklyValueMetricsPanel, +} from "@/components/site/usage-analytics-panels"; import { useApiResource } from "@/lib/api/use-api-resource"; export const Route = createFileRoute("/app/analytics")({ @@ -12,6 +18,12 @@ export const Route = createFileRoute("/app/analytics")({ type OperatorDashboard = { metrics: Array<{ label: string; value: string; delta: string }>; noiseReduction: Array<{ label: string; value: number; spark: number[] }>; + usageSummary?: { + totalEvents: number; + activeActors: number; + byEvent: Array<{ eventName: string; count: number }>; + bySurface: Array<{ surface: string; count: number }>; + }; usageRollupStatus?: { status: "empty" | "ready" | "partial" | "stale" | "incomplete"; latestRollupDay?: string | null; @@ -23,11 +35,53 @@ type OperatorDashboard = { totalEvents: number; activeActors: number; activeRepos: number; + byRole: Array<{ role: string; count: number; activeActors: number; activeRepos: number }>; + activationByRole: Array<{ + role: string; + firstUsefulActionActors: number; + doctorPassActors: number; + }>; + retention: Array<{ + window: string; + activeActors: number; + retainedActors: number; + retentionRate: number; + capped: boolean; + byRole: Array<{ + role: string; + activeActors: number; + retainedActors: number; + retentionRate: number; + }>; + }>; + byTool?: Array<{ key: string; count: number }>; activation: { fullyActivatedActors: number; githubActivatedRepos: number; }; }>; + weeklyValueReport?: { + metrics: Array<{ id: string; label: string; value: number; detail: string }>; + warnings: string[]; + freshness: { status: string; latestRollupDay?: string | null }; + }; + commandUsefulness?: { + windowDays: number; + totals: { + feedbackCount: number; + usefulCount: number; + notUsefulCount: number; + answerCount: number; + usefulnessRate: number | null; + }; + commands: Array<{ + command: string; + feedbackCount: number; + usefulCount: number; + notUsefulCount: number; + usefulnessRate: number | null; + }>; + }; mcpCompatibilityAdoption?: { totalEvents: number; activeActors: number; @@ -43,6 +97,7 @@ type OperatorDashboard = { count: number; }>; }; + upstreamDrift?: { status?: string; openReportCount?: number } | null; }; function ProductAnalytics() { @@ -51,6 +106,10 @@ function ProductAnalytics() { "Product analytics", ); const data = dashboard.status === "ready" ? dashboard.data : null; + const latestRollup = + data?.usageRollups && data.usageRollups.length > 0 + ? [...data.usageRollups].sort((a, b) => b.day.localeCompare(a.day))[0] + : null; return (

- Product analytics + Usage & value analytics

- Aggregate deployment, session, digest, and installation metrics from the live API. + Operator-facing adoption, activation, retention, and ecosystem value from product + usage rollups — not security audit logs or private source data.

@@ -91,6 +151,11 @@ function ProductAnalytics() { > {data.usageRollupStatus?.status ?? "Live API"} + {data.upstreamDrift?.status ? ( + + Drift · {data.upstreamDrift.status} + + ) : null}
@@ -106,6 +171,37 @@ function ProductAnalytics() { ))} + {data.weeklyValueReport ? ( + + ) : null} + + {data.usageSummary ? ( + + ) : null} + + {latestRollup ? ( + + ) : null} + + {data.commandUsefulness ? ( + + ) : null} +

Operational trend signals

@@ -218,6 +314,13 @@ function ProductAnalytics() { {data.usageRollupStatus?.latestRollupDay ?? "current"} + {data.usageRollupStatus?.warnings.length ? ( +

    + {data.usageRollupStatus.warnings.slice(0, 4).map((warning) => ( +
  • · {warning}
  • + ))} +
+ ) : null}
diff --git a/src/api/routes.ts b/src/api/routes.ts index 3796613244..94bd6d82c4 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -137,6 +137,7 @@ import { LATEST_RECOMMENDED_MCP_VERSION, MINIMUM_SUPPORTED_MCP_VERSION, } from "../services/mcp-compatibility"; +import { buildOperatorDashboardPayload } from "../services/operator-dashboard"; import { buildWeeklyValueReport, formatWeeklyValueReportMarkdown, @@ -840,87 +841,7 @@ export function createApp() { app.get("/v1/app/operator-dashboard", async (c) => { const forbidden = await requireAppRole(c, ["operator"]); if (forbidden) return forbidden; - const usageSince = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(); - const [ - repositories, - installations, - health, - registry, - scoring, - upstreamDrift, - activeSessions, - digestSubscriptions, - rateLimits, - usageSummary, - usageRollups, - usageRollupStatus, - mcpCompatibilityAdoption, - commandUsefulness, - ] = await Promise.all([ - listRepositories(c.env), - listInstallations(c.env), - listInstallationHealth(c.env), - getLatestRegistrySnapshot(c.env), - getLatestScoringModelSnapshot(c.env), - loadUpstreamStatus(c.env), - countActiveAuthSessions(c.env), - countActiveDigestSubscriptions(c.env), - listLatestGitHubRateLimitObservations(c.env, 20), - summarizeProductUsageEvents(c.env, usageSince), - listProductUsageDailyRollups(c.env, { limit: 14 }), - getProductUsageRollupStatus(c.env), - summarizeMcpCompatibilityAdoption(c.env, usageSince), - getCommandUsefulnessSummary(c.env), - ]); - const weeklyValueReport = buildWeeklyValueReport({ - generatedAt: nowIso(), - variant: "operator", - days: 7, - repositories, - installations, - health, - registry, - scoring, - upstreamDrift, - usageSummary, - usageRollups, - usageRollupStatus, - activeSessions, - digestSubscriptions, - }); - const installedRepos = repositories.filter((repo) => repo.isInstalled).length; - const registeredRepos = repositories.filter((repo) => repo.isRegistered).length; - return c.json({ - generatedAt: nowIso(), - metrics: [ - { label: "Active sessions", value: String(activeSessions), delta: "browser + CLI/MCP" }, - { label: "Installations", value: String(installations.length), delta: `${installedRepos} installed repos` }, - { label: "Registered repos", value: String(registeredRepos), delta: registry ? `${registry.repoCount} in latest registry` : "registry missing" }, - { label: "Digest subscriptions", value: String(digestSubscriptions), delta: "store-only" }, - { label: "Product events", value: String(usageSummary.totalEvents), delta: "last 7 days" }, - { label: "Active users", value: String(usageSummary.activeActors), delta: "hashed, last 7 days" }, - { label: "Activation rollups", value: usageRollupStatus.status, delta: usageRollupStatus.latestRollupDay ?? "not generated" }, - { label: "MCP stale clients", value: String(mcpCompatibilityAdoption.staleEvents + mcpCompatibilityAdoption.incompatibleEvents), delta: `${mcpCompatibilityAdoption.totalEvents} MCP event(s)` }, - { label: "Command usefulness", value: `${commandUsefulness.totals.usefulCount}/${commandUsefulness.totals.feedbackCount}`, delta: usefulnessDelta(commandUsefulness.totals.usefulnessRate) }, - { label: "Install issues", value: String(health.filter((record) => record.status !== "healthy").length), delta: "current health cache" }, - { label: "Rate-limit events", value: String(rateLimits.length), delta: "latest observations" }, - ], - noiseReduction: [ - { label: "Healthy installations", value: health.filter((record) => record.status === "healthy").length, spark: sparklineFromCounts(health.filter((record) => record.status === "healthy").length, Math.max(health.length, 1)) }, - { label: "Registered coverage", value: registeredRepos, spark: sparklineFromCounts(registeredRepos, Math.max(repositories.length, 1)) }, - { label: "Installed coverage", value: installedRepos, spark: sparklineFromCounts(installedRepos, Math.max(repositories.length, 1)) }, - ], - weeklyReport: weeklyValueReport.summary, - weeklyValueReport, - usageSummary, - usageRollups, - usageRollupStatus, - mcpCompatibilityAdoption, - commandUsefulness, - registry, - scoringModel: scoring, - upstreamDrift, - }); + return c.json(await buildOperatorDashboardPayload(c.env)); }); app.get("/v1/app/notification-model", async (c) => { @@ -2667,10 +2588,6 @@ function sampleMinerSnapshot(login: string) { }; } -function usefulnessDelta(rate: number | null): string { - return rate === null ? "no feedback yet" : `${Math.round(rate * 100)}% useful over 30 days`; -} - function clampInteger(value: number, min: number, max: number): number { if (!Number.isFinite(value)) return min; return Math.min(max, Math.max(min, Math.round(value))); diff --git a/src/services/operator-dashboard.ts b/src/services/operator-dashboard.ts new file mode 100644 index 0000000000..3c37a2ff4b --- /dev/null +++ b/src/services/operator-dashboard.ts @@ -0,0 +1,186 @@ +import { + countActiveAuthSessions, + countActiveDigestSubscriptions, + getCommandUsefulnessSummary, + getLatestScoringModelSnapshot, + getProductUsageRollupStatus, + listInstallationHealth, + listInstallations, + listLatestGitHubRateLimitObservations, + listProductUsageDailyRollups, + listRepositories, + summarizeMcpCompatibilityAdoption, + summarizeProductUsageEvents, +} from "../db/repositories"; +import { getLatestRegistrySnapshot } from "../registry/sync"; +import type { + CommandUsefulnessSummary, + InstallationHealthRecord, + McpCompatibilityAdoptionSummary, + ProductUsageDailyRollupRecord, + ProductUsageRollupStatus, + ProductUsageSummary, + RegistrySnapshot, + RepositoryRecord, + ScoringModelSnapshotRecord, + WeeklyValueReport, +} from "../types"; +import { loadUpstreamStatus, type UpstreamStatus } from "../upstream/ruleset"; +import { nowIso } from "../utils/json"; +import { buildWeeklyValueReport } from "./weekly-value-report"; + +export type OperatorDashboardMetric = { + label: string; + value: string; + delta: string; +}; + +export type OperatorDashboardNoiseMetric = { + label: string; + value: number; + spark: number[]; +}; + +export type OperatorDashboardPayload = { + generatedAt: string; + metrics: OperatorDashboardMetric[]; + noiseReduction: OperatorDashboardNoiseMetric[]; + weeklyReport: string[]; + weeklyValueReport: WeeklyValueReport; + usageSummary: ProductUsageSummary; + usageRollups: ProductUsageDailyRollupRecord[]; + usageRollupStatus: ProductUsageRollupStatus; + mcpCompatibilityAdoption: McpCompatibilityAdoptionSummary; + commandUsefulness: CommandUsefulnessSummary; + registry: RegistrySnapshot | null; + scoringModel: ScoringModelSnapshotRecord | null; + upstreamDrift: UpstreamStatus; +}; + +const USAGE_WINDOW_DAYS = 7; + +export async function buildOperatorDashboardPayload(env: Env): Promise { + const usageSince = new Date(Date.now() - USAGE_WINDOW_DAYS * 24 * 60 * 60 * 1000).toISOString(); + const [ + repositories, + installations, + health, + registry, + scoring, + upstreamDrift, + activeSessions, + digestSubscriptions, + rateLimits, + usageSummary, + usageRollups, + usageRollupStatus, + mcpCompatibilityAdoption, + commandUsefulness, + ] = await Promise.all([ + listRepositories(env), + listInstallations(env), + listInstallationHealth(env), + getLatestRegistrySnapshot(env), + getLatestScoringModelSnapshot(env), + loadUpstreamStatus(env), + countActiveAuthSessions(env), + countActiveDigestSubscriptions(env), + listLatestGitHubRateLimitObservations(env, 20), + summarizeProductUsageEvents(env, usageSince), + listProductUsageDailyRollups(env, { limit: 14 }), + getProductUsageRollupStatus(env), + summarizeMcpCompatibilityAdoption(env, usageSince), + getCommandUsefulnessSummary(env), + ]); + const weeklyValueReport = buildWeeklyValueReport({ + generatedAt: nowIso(), + variant: "operator", + days: USAGE_WINDOW_DAYS, + repositories, + installations, + health, + registry, + scoring, + upstreamDrift, + usageSummary, + usageRollups, + usageRollupStatus, + activeSessions, + digestSubscriptions, + }); + const installedRepos = repositories.filter((repo: RepositoryRecord) => repo.isInstalled).length; + const registeredRepos = repositories.filter((repo: RepositoryRecord) => repo.isRegistered).length; + return { + generatedAt: nowIso(), + metrics: [ + { label: "Active sessions", value: String(activeSessions), delta: "browser + CLI/MCP" }, + { label: "Installations", value: String(installations.length), delta: `${installedRepos} installed repos` }, + { label: "Registered repos", value: String(registeredRepos), delta: registry ? `${registry.repoCount} in latest registry` : "registry missing" }, + { label: "Digest subscriptions", value: String(digestSubscriptions), delta: "store-only" }, + { label: "Product events", value: String(usageSummary.totalEvents), delta: "last 7 days" }, + { label: "Active users", value: String(usageSummary.activeActors), delta: "hashed, last 7 days" }, + { label: "Activation rollups", value: usageRollupStatus.status, delta: usageRollupStatus.latestRollupDay ?? "not generated" }, + { + label: "MCP stale clients", + value: String(mcpCompatibilityAdoption.staleEvents + mcpCompatibilityAdoption.incompatibleEvents), + delta: `${mcpCompatibilityAdoption.totalEvents} MCP event(s)`, + }, + { + label: "Command usefulness", + value: `${commandUsefulness.totals.usefulCount}/${commandUsefulness.totals.feedbackCount}`, + delta: usefulnessDelta(commandUsefulness.totals.usefulnessRate), + }, + { + label: "Install issues", + value: String(health.filter((record: InstallationHealthRecord) => record.status !== "healthy").length), + delta: "current health cache", + }, + { label: "Rate-limit events", value: String(rateLimits.length), delta: "latest observations" }, + ], + noiseReduction: [ + { + label: "Healthy installations", + value: health.filter((record: InstallationHealthRecord) => record.status === "healthy").length, + spark: sparklineFromCounts( + health.filter((record: InstallationHealthRecord) => record.status === "healthy").length, + Math.max(health.length, 1), + ), + }, + { + label: "Registered coverage", + value: registeredRepos, + spark: sparklineFromCounts(registeredRepos, Math.max(repositories.length, 1)), + }, + { + label: "Installed coverage", + value: installedRepos, + spark: sparklineFromCounts(installedRepos, Math.max(repositories.length, 1)), + }, + ], + weeklyReport: weeklyValueReport.summary, + weeklyValueReport, + usageSummary, + usageRollups, + usageRollupStatus, + mcpCompatibilityAdoption, + commandUsefulness, + registry, + scoringModel: scoring, + upstreamDrift, + }; +} + +export function latestUsageRollup(rollups: ProductUsageDailyRollupRecord[]): ProductUsageDailyRollupRecord | null { + if (rollups.length === 0) return null; + return [...rollups].sort((a, b) => b.day.localeCompare(a.day))[0] ?? null; +} + +function usefulnessDelta(rate: number | null): string { + return rate === null ? "no feedback yet" : `${Math.round(rate * 100)}% useful over 30 days`; +} + +function sparklineFromCounts(value: number, total: number): number[] { + const safeTotal = Math.max(total, 1); + const ratio = Math.min(1, Math.max(0, value / safeTotal)); + return [Math.round(ratio * 40), Math.round(ratio * 55), Math.round(ratio * 70), Math.round(ratio * 85), Math.round(ratio * 100)]; +} diff --git a/test/unit/operator-dashboard.test.ts b/test/unit/operator-dashboard.test.ts new file mode 100644 index 0000000000..532e37cf61 --- /dev/null +++ b/test/unit/operator-dashboard.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from "vitest"; +import { buildOperatorDashboardPayload, latestUsageRollup } from "../../src/services/operator-dashboard"; +import type { ProductUsageDailyRollupRecord } from "../../src/types"; +import { createTestEnv } from "../helpers/d1"; + +const FORBIDDEN_EXPORT_TERMS = + /wallet|hotkey|raw trust|trust[-\s]?score|payout|reward[-\s]?estimate|farming|private[-\s]?reviewability|public[-\s]?score[-\s]?(?:estimate|prediction)|\/Users|github_pat|ghp_/i; + +describe("operator dashboard payload", () => { + it("builds operator metrics from product usage rollups without sensitive strings", async () => { + const env = createTestEnv({ PRODUCT_USAGE_HASH_SALT: "operator-dashboard-test-salt" }); + const payload = await buildOperatorDashboardPayload(env); + const serialized = JSON.stringify(payload); + + expect(payload.metrics).toEqual( + expect.arrayContaining([ + expect.objectContaining({ label: "Product events" }), + expect.objectContaining({ label: "Command usefulness" }), + expect.objectContaining({ label: "Activation rollups" }), + ]), + ); + expect(payload.weeklyValueReport.variant).toBe("operator"); + expect(payload.usageSummary).toMatchObject({ totalEvents: expect.any(Number), activeActors: expect.any(Number) }); + expect(payload.commandUsefulness.totals).toMatchObject({ feedbackCount: expect.any(Number) }); + expect(serialized).not.toMatch(FORBIDDEN_EXPORT_TERMS); + }); + + it("picks the newest rollup day for adoption insights", () => { + const rollups: ProductUsageDailyRollupRecord[] = [ + rollup("2026-05-28"), + rollup("2026-05-30"), + rollup("2026-05-29"), + ]; + expect(latestUsageRollup(rollups)?.day).toBe("2026-05-30"); + expect(latestUsageRollup([])).toBeNull(); + }); +}); + +function rollup(day: string): ProductUsageDailyRollupRecord { + return { + day, + status: "complete", + totalEvents: 1, + activeActors: 1, + activeSessions: 1, + activeRepos: 1, + sourceEventCount: 1, + maxEventCapacity: 1000, + bySurface: [], + byOutcome: [], + byEvent: [], + byRepo: [], + byCommand: [], + byTool: [], + byRouteClass: [], + activation: { + loginActors: 1, + doctorPassActors: 1, + firstUsefulActionActors: 1, + fullyActivatedActors: 1, + githubInstalledRepos: 1, + githubFirstCommandRepos: 1, + githubUsefulMaintainerRepos: 1, + githubActivatedRepos: 1, + }, + byRole: [{ role: "miner", count: 1, activeActors: 1, activeRepos: 0 }], + activationByRole: [ + { + role: "miner", + loginActors: 1, + doctorPassActors: 1, + firstUsefulActionActors: 1, + fullyActivatedActors: 1, + githubInstalledRepos: 0, + githubFirstCommandRepos: 0, + githubUsefulMaintainerRepos: 0, + githubActivatedRepos: 0, + }, + ], + activationBySurface: [], + retention: [], + generatedAt: "2026-06-01T00:00:00.000Z", + updatedAt: "2026-06-01T00:00:00.000Z", + }; +}