Skip to content

Commit d4107ae

Browse files
feat(api): add cross-repo queue health federation index (#479)
Expose operator-only federated queue pressure ranking across registered installed repos, with MCP tool, dashboard wiring, and snapshot cache. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 92959ba commit d4107ae

18 files changed

Lines changed: 778 additions & 0 deletions

apps/gittensory-ui/public/openapi.json

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12391,6 +12391,91 @@
1239112391
"weekly",
1239212392
"byProject"
1239312393
]
12394+
},
12395+
"FederatedRepoEntry": {
12396+
"type": "object",
12397+
"properties": {
12398+
"repoFullName": {
12399+
"type": "string"
12400+
},
12401+
"burdenScore": {
12402+
"type": "number"
12403+
},
12404+
"level": {
12405+
"type": "string",
12406+
"enum": [
12407+
"low",
12408+
"medium",
12409+
"high",
12410+
"critical"
12411+
]
12412+
},
12413+
"compositeScore": {
12414+
"type": "number"
12415+
},
12416+
"stalePullRequestRate": {
12417+
"type": "number",
12418+
"nullable": true
12419+
},
12420+
"pullRequestGrowth7d": {
12421+
"type": "number",
12422+
"nullable": true
12423+
},
12424+
"freshness": {
12425+
"type": "string",
12426+
"enum": [
12427+
"fresh",
12428+
"stale"
12429+
]
12430+
},
12431+
"summary": {
12432+
"type": "string"
12433+
}
12434+
},
12435+
"required": [
12436+
"repoFullName",
12437+
"burdenScore",
12438+
"level",
12439+
"compositeScore",
12440+
"stalePullRequestRate",
12441+
"pullRequestGrowth7d",
12442+
"freshness",
12443+
"summary"
12444+
]
12445+
},
12446+
"FederatedQueueIndex": {
12447+
"type": "object",
12448+
"properties": {
12449+
"generatedAt": {
12450+
"type": "string"
12451+
},
12452+
"repoCount": {
12453+
"type": "number"
12454+
},
12455+
"limitApplied": {
12456+
"type": "number"
12457+
},
12458+
"source": {
12459+
"type": "string",
12460+
"enum": [
12461+
"snapshot",
12462+
"computed"
12463+
]
12464+
},
12465+
"entries": {
12466+
"type": "array",
12467+
"items": {
12468+
"$ref": "#/components/schemas/FederatedRepoEntry"
12469+
}
12470+
}
12471+
},
12472+
"required": [
12473+
"generatedAt",
12474+
"repoCount",
12475+
"limitApplied",
12476+
"source",
12477+
"entries"
12478+
]
1239412479
}
1239512480
},
1239612481
"parameters": {},
@@ -15262,6 +15347,49 @@
1526215347
}
1526315348
]
1526415349
}
15350+
},
15351+
"/v1/app/queue-health/federation": {
15352+
"get": {
15353+
"parameters": [
15354+
{
15355+
"schema": {
15356+
"type": "string"
15357+
},
15358+
"required": false,
15359+
"name": "limit",
15360+
"in": "query"
15361+
}
15362+
],
15363+
"responses": {
15364+
"200": {
15365+
"description": "Ranked cross-repo queue pressure index (operator only)",
15366+
"content": {
15367+
"application/json": {
15368+
"schema": {
15369+
"$ref": "#/components/schemas/FederatedQueueIndex"
15370+
}
15371+
}
15372+
}
15373+
},
15374+
"401": {
15375+
"description": "Unauthorized"
15376+
},
15377+
"403": {
15378+
"description": "Insufficient role — operator access required"
15379+
},
15380+
"422": {
15381+
"description": "Invalid limit parameter"
15382+
}
15383+
},
15384+
"security": [
15385+
{
15386+
"GittensoryBearer": []
15387+
},
15388+
{
15389+
"GittensorySessionCookie": []
15390+
}
15391+
]
15392+
}
1526515393
}
1526615394
},
1526715395
"servers": [
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Cache table for the federated queue pressure index.
2+
-- TTL enforcement matches the burden forecast pattern (6-hour freshness threshold applied at read time).
3+
CREATE TABLE IF NOT EXISTS queue_federation_snapshots (
4+
id TEXT PRIMARY KEY,
5+
generated_at TEXT NOT NULL,
6+
repo_count INTEGER NOT NULL DEFAULT 0,
7+
payload_json TEXT NOT NULL DEFAULT '{}',
8+
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
9+
);
10+
11+
CREATE INDEX IF NOT EXISTS queue_federation_snapshots_generated_idx ON queue_federation_snapshots (generated_at);

src/api/routes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ import {
172172
MINIMUM_SUPPORTED_MCP_VERSION,
173173
} from "../services/mcp-compatibility";
174174
import { buildOperatorDashboardPayload } from "../services/operator-dashboard";
175+
import { buildFederatedQueueIndex, FEDERATED_QUEUE_INDEX_MAX_LIMIT } from "../services/queue-federation";
175176
import { buildSelfDogfoodRegistrationPack, resolveSelfDogfoodRepoFullName } from "../services/self-dogfood-registration-pack";
176177
import { buildSubnetInterfaceDescriptor } from "../services/subnet-interface";
177178
import { buildPublicRepoQuality, type PublicRepoQuality } from "../services/public-repo-quality";
@@ -1329,6 +1330,20 @@ export function createApp() {
13291330
return c.json(await buildOperatorDashboardPayload(c.env));
13301331
});
13311332

1333+
app.get("/v1/app/queue-health/federation", async (c) => {
1334+
const forbidden = await requireAppRole(c, ["operator"]);
1335+
if (forbidden) return forbidden;
1336+
const rawLimit = c.req.query("limit");
1337+
if (rawLimit !== undefined) {
1338+
const parsed = Number(rawLimit);
1339+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > FEDERATED_QUEUE_INDEX_MAX_LIMIT) {
1340+
return c.json({ error: "invalid_limit", message: `limit must be an integer between 1 and ${FEDERATED_QUEUE_INDEX_MAX_LIMIT}` }, 422);
1341+
}
1342+
}
1343+
const limit = rawLimit !== undefined ? Number(rawLimit) : undefined;
1344+
return c.json(await buildFederatedQueueIndex(c.env, limit));
1345+
});
1346+
13321347
app.get("/v1/app/notification-model", async (c) => {
13331348
const forbidden = await requireAppRole(c, ["maintainer", "owner", "operator"]);
13341349
if (forbidden) return forbidden;

src/db/repositories.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
repositories,
4343
repoGithubTotalsSnapshots,
4444
repoQueueTrendSnapshots,
45+
queueFederationSnapshots,
4546
registryDriftEvents,
4647
repoLabels,
4748
repoSnapshots,
@@ -89,6 +90,7 @@ import type {
8990
BountyLifecycleEventRecord,
9091
BountyRecord,
9192
BurdenForecastRecord,
93+
QueueFederationSnapshotRecord,
9294
CheckSummaryRecord,
9395
CollisionEdgeRecord,
9496
CommandUsefulnessSummary,
@@ -2477,6 +2479,32 @@ export async function getBurdenForecast(env: Env, repoFullName: string): Promise
24772479
};
24782480
}
24792481

2482+
const QUEUE_FEDERATION_SNAPSHOT_ID = "current";
2483+
2484+
export async function upsertQueueFederationSnapshot(env: Env, snapshot: QueueFederationSnapshotRecord): Promise<void> {
2485+
const db = getDb(env.DB);
2486+
await db
2487+
.insert(queueFederationSnapshots)
2488+
.values({ id: QUEUE_FEDERATION_SNAPSHOT_ID, generatedAt: snapshot.generatedAt, repoCount: snapshot.repoCount, payloadJson: jsonString(snapshot.payload) })
2489+
.onConflictDoUpdate({
2490+
target: queueFederationSnapshots.id,
2491+
set: { generatedAt: snapshot.generatedAt, repoCount: snapshot.repoCount, payloadJson: jsonString(snapshot.payload) },
2492+
});
2493+
}
2494+
2495+
export async function getQueueFederationSnapshot(env: Env): Promise<QueueFederationSnapshotRecord | null> {
2496+
const db = getDb(env.DB);
2497+
const row = await db.select().from(queueFederationSnapshots).where(eq(queueFederationSnapshots.id, QUEUE_FEDERATION_SNAPSHOT_ID)).limit(1);
2498+
const first = row[0];
2499+
if (!first) return null;
2500+
return {
2501+
id: first.id,
2502+
generatedAt: first.generatedAt,
2503+
repoCount: first.repoCount,
2504+
payload: parseJson<Record<string, JsonValue>>(first.payloadJson, {}),
2505+
};
2506+
}
2507+
24802508
export async function persistRegistryDriftEvents(env: Env, events: RegistryDriftEventRecord[]): Promise<void> {
24812509
const db = getDb(env.DB);
24822510
for (const event of events) {

src/db/schema.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,14 @@ export const repoQueueTrendSnapshots = sqliteTable("repo_queue_trend_snapshots",
768768
generatedAt: text("generated_at").notNull().$defaultFn(() => nowIso()),
769769
});
770770

771+
export const queueFederationSnapshots = sqliteTable("queue_federation_snapshots", {
772+
id: text("id").primaryKey(),
773+
generatedAt: text("generated_at").notNull(),
774+
repoCount: integer("repo_count").notNull().default(0),
775+
payloadJson: text("payload_json").notNull().default("{}"),
776+
createdAt: text("created_at").notNull().default("CURRENT_TIMESTAMP"),
777+
});
778+
771779
export const registryDriftEvents = sqliteTable("registry_drift_events", {
772780
id: text("id").primaryKey(),
773781
repoFullName: text("repo_full_name").notNull(),

src/mcp/server.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ import { buildRemediationPlan } from "../services/remediation-plan";
6767
import { explainScoreBreakdown } from "../services/score-breakdown";
6868
import { loadOrComputeIssueQualityResponse } from "../services/issue-quality";
6969
import { loadOrComputeBurdenForecastResponse } from "../services/burden-forecast";
70+
import { buildFederatedQueueIndex, FEDERATED_QUEUE_INDEX_MAX_LIMIT } from "../services/queue-federation";
7071
import { buildMcpClientTelemetry } from "../services/client-telemetry";
7172
import { loadOrComputeRepoOutcomePatternsResponse } from "../services/repo-outcome-patterns";
7273
import { buildRepoOutcomeCalibration, outcomeCalibrationSummary } from "../services/outcome-calibration";
@@ -158,6 +159,14 @@ const fleetAnalyticsOutputSchema = {
158159
outliers: z.array(z.unknown()).optional(),
159160
};
160161

162+
const queueHealthFederationOutputSchema = {
163+
generatedAt: z.string().optional(),
164+
repoCount: z.number().optional(),
165+
limitApplied: z.number().optional(),
166+
source: z.enum(["snapshot", "computed"]).optional(),
167+
entries: z.array(z.unknown()).optional(),
168+
};
169+
161170
const loginShape = {
162171
login: z.string().min(1),
163172
};
@@ -1050,6 +1059,16 @@ export class GittensoryMcp {
10501059
async (input) => this.toolResult(await this.getBurdenForecast(input)),
10511060
);
10521061

1062+
server.registerTool(
1063+
"gittensory_queue_health_federation",
1064+
{
1065+
description: "Return a ranked cross-repo queue pressure index showing the worst-burden registered repos. Operator-only.",
1066+
inputSchema: { limit: z.number().int().min(1).max(FEDERATED_QUEUE_INDEX_MAX_LIMIT).optional() },
1067+
outputSchema: queueHealthFederationOutputSchema,
1068+
},
1069+
async (input) => this.toolResult(await this.getQueueHealthFederation(input.limit)),
1070+
);
1071+
10531072
server.registerTool(
10541073
"gittensory_get_repo_outcome_patterns",
10551074
{
@@ -1813,6 +1832,22 @@ export class GittensoryMcp {
18131832
};
18141833
}
18151834

1835+
private async getQueueHealthFederation(limit?: number): Promise<ToolPayload> {
1836+
if (this.identity.kind !== "session") {
1837+
throw new Error("Forbidden: gittensory_queue_health_federation requires operator role.");
1838+
}
1839+
const summary = await loadControlPanelRoleSummary(this.env, this.identity.actor);
1840+
if (!summary.roles.includes("operator")) {
1841+
throw new Error("Forbidden: gittensory_queue_health_federation requires operator role.");
1842+
}
1843+
const index = await buildFederatedQueueIndex(this.env, limit);
1844+
const criticalCount = index.entries.filter((entry) => entry.level === "critical" || entry.level === "high").length;
1845+
return {
1846+
summary: `Cross-repo queue pressure index: ${index.repoCount} repo(s) ranked, ${criticalCount} at critical/high burden.`,
1847+
data: index as unknown as Record<string, unknown>,
1848+
};
1849+
}
1850+
18161851
private async getIssueQuality(input: { owner: string; repo: string }): Promise<ToolPayload> {
18171852
const fullName = `${input.owner}/${input.repo}`;
18181853
if (!(await this.canAccessRepo(fullName))) {

src/openapi/schemas.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,29 @@ export const CollisionReportSchema = z
177177
})
178178
.openapi("CollisionReport");
179179

180+
export const FederatedRepoEntrySchema = z
181+
.object({
182+
repoFullName: z.string(),
183+
burdenScore: z.number(),
184+
level: z.enum(["low", "medium", "high", "critical"]),
185+
compositeScore: z.number(),
186+
stalePullRequestRate: z.number().nullable(),
187+
pullRequestGrowth7d: z.number().nullable(),
188+
freshness: z.enum(["fresh", "stale"]),
189+
summary: z.string(),
190+
})
191+
.openapi("FederatedRepoEntry");
192+
193+
export const FederatedQueueIndexSchema = z
194+
.object({
195+
generatedAt: z.string(),
196+
repoCount: z.number(),
197+
limitApplied: z.number(),
198+
source: z.enum(["snapshot", "computed"]),
199+
entries: z.array(FederatedRepoEntrySchema),
200+
})
201+
.openapi("FederatedQueueIndex");
202+
180203
export const QueueHealthSchema = z
181204
.object({
182205
repoFullName: z.string(),

src/openapi/spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
BountyLifecycleEventsSchema,
1212
BountySchema,
1313
BurdenForecastSchema,
14+
FederatedQueueIndexSchema,
15+
FederatedRepoEntrySchema,
1416
CollisionReportSchema,
1517
ConfigQualitySchema,
1618
CommandPreviewResponseSchema,
@@ -147,6 +149,8 @@ export function buildOpenApiSpec() {
147149
registry.register("IssueQualityReport", IssueQualityReportSchema);
148150
registry.register("IssueQualityResponse", IssueQualityResponseSchema);
149151
registry.register("BurdenForecast", BurdenForecastSchema);
152+
registry.register("FederatedRepoEntry", FederatedRepoEntrySchema);
153+
registry.register("FederatedQueueIndex", FederatedQueueIndexSchema);
150154
registry.register("ContributorScoringProfile", ContributorScoringProfileSchema);
151155
registry.register("ContributorStrategy", ContributorStrategySchema);
152156
registry.register("RewardRiskAction", RewardRiskActionSchema);
@@ -715,6 +719,17 @@ export function buildOpenApiSpec() {
715719
401: { description: "Unauthorized" },
716720
},
717721
});
722+
registry.registerPath({
723+
method: "get",
724+
path: "/v1/app/queue-health/federation",
725+
request: { query: z.object({ limit: z.string().optional() }) },
726+
responses: {
727+
200: { description: "Ranked cross-repo queue pressure index (operator only)", content: { "application/json": { schema: FederatedQueueIndexSchema } } },
728+
401: { description: "Unauthorized" },
729+
403: { description: "Insufficient role — operator access required" },
730+
422: { description: "Invalid limit parameter" },
731+
},
732+
});
718733
for (const path of [
719734
"/v1/app/roles",
720735
"/v1/app/miner-dashboard",

0 commit comments

Comments
 (0)