Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{

Check warning on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #580.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #580.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.

Check warning on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Items reference the same linked issue #580.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Open PR work references issue #580.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 6 meaningful terms.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Possible duplicate overlap

Titles/paths share 7 meaningful terms.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Issue discovery is disabled for this repo

This repo is configured for direct contribution review rather than issue-discovery flow.

Check notice on line 1 in package.json

View check run for this annotation

Deleted GitHub App / Gittensory Context

Open PR queue is busy

This repo has a busy open PR queue in the local Gittensory cache.
"name": "gittensory",
"version": "0.1.0",
"private": true,
Expand Down Expand Up @@ -52,7 +52,8 @@
"test:smoke:production": "node scripts/smoke-production.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"visual:render": "tsx scripts/visual-render.ts",
"test:ci": "git diff --check && npm run actionlint && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run ui:openapi:check && npm run ui:version-audit && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build && npm audit --audit-level=moderate",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci && npm run changelog:check:mcp",
"test:watch": "vitest",
Expand Down
3 changes: 3 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ declare global {
GITTENSOR_UPSTREAM_REPO?: string;
GITTENSOR_UPSTREAM_REF?: string;
GITTENSOR_REGISTRY_URL: string;
/** #697: metagraphed base URL. When set, subnet/netuid integration claims in PRs/issues are validated
* (existence + interface health) and surfaced as ADVISORY gate evidence. Unset = feature dormant. */
METAGRAPHED_API_URL?: string;
GITHUB_PUBLIC_TOKEN?: string;
/** #703: owner-gated global to apply upstream sigmoid time-decay in score previews. Default off. */
SCORING_TIME_DECAY_ENABLED?: string;
Expand Down
7 changes: 7 additions & 0 deletions src/queue/processors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import {
unionScopedOverlapClusters,
} from "../signals/engine";
import { buildIssueSlopAssessment, buildSlopAssessment, type SlopBand } from "../signals/slop";
import { assessSubnetClaimFindings } from "../services/metagraphed";
import { runGittensoryAiSlopAdvisory } from "../services/ai-slop";
import { decidePublicSurface } from "../signals/settings-preview";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
Expand Down Expand Up @@ -738,6 +739,9 @@ async function processGitHubWebhook(env: Env, deliveryId: string, eventName: str
otherOpenPullRequests,
requireLinkedIssue: settings.requireLinkedIssue || settings.linkedIssueGateMode !== "off",
});
// #697: validate any subnet/netuid integration claim against metagraphed and attach the verdict as
// advisory gate evidence. No-op (and no network call) unless METAGRAPHED_API_URL is configured.
advisory.findings.push(...(await assessSubnetClaimFindings(env, { title: pr.title, body: pr.body })));
await persistAdvisory(env, advisory);
if (installationId && shouldProcessPullRequestPublicSurface(payload.action)) {
await maybePublishPrPublicSurface(env, installationId, repoFullName, pr, repo, settings, advisory, {
Expand Down Expand Up @@ -770,6 +774,9 @@ async function processGitHubWebhook(env: Env, deliveryId: string, eventName: str
if (issueSettings.slopGateMode !== "off") {
advisory.findings.push(...buildIssueSlopAssessment({ title: issue.title, body: issue.body }).findings);
}
// #697: subnet/netuid claim validation also applies to issues ("integrates subnet X"). Advisory-only;
// dormant unless METAGRAPHED_API_URL is set.
advisory.findings.push(...(await assessSubnetClaimFindings(env, { title: issue.title, body: issue.body })));
await persistAdvisory(env, advisory);
// #699 path B: a newly opened grabbable, high-multiplier issue notifies the miners watching this repo
// (fanned out through the same #535 pipeline below).
Expand Down
91 changes: 91 additions & 0 deletions src/services/metagraphed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// #697 (roadmap #525): the metagraphed consumer. Validates a claimed Bittensor subnet/netuid against
// metagraphed (netuid existence + interface health) and adapts the verdict into advisory gate findings.
//
// This mirrors gittensor/api.ts's fetch discipline (JSON accept header, hard timeout, never let a slow
// upstream hang the Worker). It is fail-open and ADVISORY: any error, timeout, or unexpected shape maps to
// `unavailable`, which produces NO finding — a metagraphed outage must never block or spam a contributor
// (#525: advisory-first, no auto-block). The feature is dormant until `METAGRAPHED_API_URL` is configured.

import type { AdvisoryFinding } from "../types";
import { assessSubnetClaims, type NetuidValidation } from "../signals/subnet-claim";

/** Hard cap on a single metagraphed request so a slow/half-open upstream can never stall the webhook. */
export const METAGRAPHED_FETCH_TIMEOUT_MS = 10_000;

/** Tolerant view of metagraphed's subnet response. Only `exists === false` and `healthy === false` are
* treated as negative signals; any other/missing shape is read as "exists, healthy" so an unrecognized
* 200 never yields a false-positive finding. `interfaceHealthy` / `interface.healthy` are accepted as
* aliases for `healthy` to match the netuid-existence/chain-binding shape from the reviewbot work. */
export type MetagraphedSubnetResponse = {
netuid?: number;
exists?: boolean;
healthy?: boolean;
interfaceHealthy?: boolean;
interface?: { healthy?: boolean } | null;
};

/** Map a parsed metagraphed subnet response to a validation verdict. Exported for direct unit testing. */
export function interpretSubnetResponse(netuid: number, data: MetagraphedSubnetResponse): NetuidValidation {
if (data.exists === false) {
return { netuid, status: "not_found", detail: `metagraphed reports subnet ${netuid} does not exist.` };
}
const healthy = data.healthy ?? data.interfaceHealthy ?? data.interface?.healthy;
if (healthy === false) {
return { netuid, status: "exists_unhealthy", detail: `metagraphed reports subnet ${netuid} interface health did not pass.` };
}
return { netuid, status: "exists_healthy", detail: `metagraphed confirms subnet ${netuid}.` };
}

export type MetagraphedClientDeps = {
/** metagraphed base URL (no trailing slash required). */
readonly baseUrl: string;
/** Injected fetch for tests; defaults to global `fetch`. */
readonly fetchImpl?: typeof fetch | undefined;
readonly timeoutMs?: number | undefined;
};

/**
* Validate one netuid against metagraphed. Never rejects: a 404 → `not_found`, any other non-2xx / network
* error / timeout / parse failure → `unavailable`, and a 2xx is interpreted by {@link interpretSubnetResponse}.
*/
export async function validateNetuid(netuid: number, deps: MetagraphedClientDeps): Promise<NetuidValidation> {
const base = deps.baseUrl.replace(/\/+$/, "");
const url = `${base}/subnets/${netuid}`;
const fetchImpl = deps.fetchImpl ?? fetch;
try {
const response = await fetchImpl(url, {
headers: { accept: "application/json", "user-agent": "gittensory/0.1" },
signal: AbortSignal.timeout(deps.timeoutMs ?? METAGRAPHED_FETCH_TIMEOUT_MS),
});
if (response.status === 404) {
return { netuid, status: "not_found", detail: `metagraphed reports subnet ${netuid} does not exist.` };
}
if (!response.ok) {
return { netuid, status: "unavailable", detail: `metagraphed returned status ${response.status} for subnet ${netuid}.` };
}
return interpretSubnetResponse(netuid, (await response.json()) as MetagraphedSubnetResponse);
} catch {
return { netuid, status: "unavailable", detail: `metagraphed could not be reached for subnet ${netuid}.` };
}
}

/**
* Top-level adapter used by the webhook pipeline: detect subnet claims in a contribution's title/body and
* return advisory findings sourced from metagraphed. Returns `[]` (and makes no network call) when
* `METAGRAPHED_API_URL` is unset, so the feature is fully opt-in and existing behavior is unchanged.
*/
export async function assessSubnetClaimFindings(
env: Pick<Env, "METAGRAPHED_API_URL">,
input: { readonly title?: string | null | undefined; readonly body?: string | null | undefined },
deps: { readonly fetchImpl?: typeof fetch | undefined; readonly timeoutMs?: number | undefined } = {},
): Promise<AdvisoryFinding[]> {
const baseUrl = env.METAGRAPHED_API_URL?.trim();
if (!baseUrl) return [];
return assessSubnetClaims(input, (netuid) =>
validateNetuid(netuid, {
baseUrl,
...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
...(deps.timeoutMs ? { timeoutMs: deps.timeoutMs } : {}),
}),
);
}
104 changes: 104 additions & 0 deletions src/signals/subnet-claim.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// #697 (roadmap #525): gittensory consumes metagraphed — validate subnet/netuid claims as gate evidence.
//
// PURE core of the subnet-claim gate signal. It (a) detects when a contribution's text claims to integrate
// a Bittensor subnet/netuid, and (b) turns a metagraphed validation verdict for that netuid into a
// public-safe, ADVISORY `AdvisoryFinding`. The actual metagraphed HTTP call lives in
// ../services/metagraphed.ts (injected here as `validate`), so this module stays deterministic and
// unit-testable without a network.
//
// Advisory-first (#525): findings are `warning` severity at most — they surface evidence, they never hard
// block. Public-safe (#542): wording uses only subnet/netuid/interface/metagraphed vocabulary and is run
// through `isPublicSafeText` in tests, so it carries no reward/score/identity language.

import type { AdvisoryFinding } from "../types";

/** A subnet/netuid integration claim parsed from contribution text. */
export type SubnetClaim = { readonly netuid: number; readonly raw: string };

/** Verdict for one claimed netuid, sourced from metagraphed. `unavailable` = could not validate (metagraphed
* unreachable/unexpected) — deliberately produces NO finding so a metagraphed outage is never noisy. */
export type NetuidValidationStatus = "exists_healthy" | "exists_unhealthy" | "not_found" | "unavailable";

export type NetuidValidation = {
readonly netuid: number;
readonly status: NetuidValidationStatus;
readonly detail: string;
};

/** Validate a single netuid against metagraphed. Implemented by ../services/metagraphed.ts; injected so the
* pure layer can be tested with a fake. Must never reject — connectivity failures map to `unavailable`. */
export type NetuidValidator = (netuid: number) => Promise<NetuidValidation>;

/** Highest netuid we treat as a plausible subnet claim. Keeps detection from matching years / PR numbers /
* large unrelated integers (Bittensor netuids are small and dense from 0). */
export const MAX_RECOGNIZED_NETUID = 1023;

// Matches "subnet 42", "subnet #42", "subnet-42", "subnets 42", "netuid 42", "netuid: 5", "net uid 5",
// "netuid=12", "sn74", "SN 74", "subnet number 7". The number is captured; a separator is optional but the
// number must follow within optional whitespace/separator so plain words ("subnetwork", "snapshot") miss.
const SUBNET_CLAIM_PATTERN = /\b(?:net\s?uid|subnets?|sn)\s*(?:number\s*)?[:#=-]?\s*(\d{1,5})\b/gi;

/**
* Detect distinct subnet/netuid integration claims in free text (PR/issue title + body). Returns at most one
* claim per netuid (first mention wins), sorted ascending for deterministic output. Out-of-range numbers
* (> MAX_RECOGNIZED_NETUID) are ignored to avoid false positives on years/IDs.
*/
export function detectSubnetClaims(text: string | null | undefined): SubnetClaim[] {
if (!text) return [];
const byNetuid = new Map<number, string>();
for (const match of text.matchAll(SUBNET_CLAIM_PATTERN)) {
const netuid = Number(match[1]);
if (!Number.isInteger(netuid) || netuid < 0 || netuid > MAX_RECOGNIZED_NETUID) continue;
if (!byNetuid.has(netuid)) byNetuid.set(netuid, match[0].trim());
}
return [...byNetuid.entries()].map(([netuid, raw]) => ({ netuid, raw })).sort((a, b) => a.netuid - b.netuid);
}

/**
* Turn one metagraphed verdict into an advisory finding. Returns `null` when there is nothing to surface —
* the netuid exists and is healthy, or metagraphed could not be reached (`unavailable`). Only a missing
* (`not_found`) or unhealthy (`exists_unhealthy`) subnet produces a finding.
*/
export function buildSubnetClaimFinding(validation: NetuidValidation): AdvisoryFinding | null {
const { netuid } = validation;
if (validation.status === "not_found") {
return {
code: "subnet_claim_not_found",
severity: "warning",
title: `Claimed subnet ${netuid} was not found via metagraphed`,
detail: `This contribution references subnet/netuid ${netuid}, but metagraphed reports no such subnet on the network. ${validation.detail}`,
action: `Verify the netuid and correct or remove the subnet ${netuid} integration claim.`,
publicText: `metagraphed could not find subnet ${netuid}; verify the referenced netuid.`,
};
}
if (validation.status === "exists_unhealthy") {
return {
code: "subnet_claim_unhealthy",
severity: "warning",
title: `Claimed subnet ${netuid} appears unhealthy via metagraphed`,
detail: `metagraphed reports subnet/netuid ${netuid} exists but its interface health check did not pass, so the integration claim could not be confirmed as healthy. ${validation.detail}`,
action: `Confirm the subnet ${netuid} interface is reachable, or note the integration as experimental.`,
publicText: `metagraphed reports subnet ${netuid} exists but its interface health check did not pass.`,
};
}
return null;
}

/**
* Assess all subnet/netuid claims in a contribution's title + body and return the advisory findings sourced
* from metagraphed. Validates each distinct claimed netuid via the injected validator. Deterministic and
* never throws (the validator must map failures to `unavailable`). Empty result = no claims, or all claimed
* subnets validated cleanly / could not be checked.
*/
export async function assessSubnetClaims(
input: { readonly title?: string | null | undefined; readonly body?: string | null | undefined },
validate: NetuidValidator,
): Promise<AdvisoryFinding[]> {
const claims = detectSubnetClaims(`${input.title ?? ""}\n${input.body ?? ""}`);
const findings: AdvisoryFinding[] = [];
for (const claim of claims) {
const finding = buildSubnetClaimFinding(await validate(claim.netuid));
if (finding) findings.push(finding);
}
return findings;
}
100 changes: 100 additions & 0 deletions test/unit/metagraphed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
assessSubnetClaimFindings,
interpretSubnetResponse,
METAGRAPHED_FETCH_TIMEOUT_MS,
validateNetuid,
type MetagraphedSubnetResponse,
} from "../../src/services/metagraphed";

/** Build a minimal fetch stub returning the given status + JSON body. */
function fetchReturning(status: number, body: unknown): typeof fetch {
return vi.fn(async () => ({ status, ok: status >= 200 && status < 300, json: async () => body }) as unknown as Response) as unknown as typeof fetch;
}

afterEach(() => {
vi.unstubAllGlobals();
});

describe("interpretSubnetResponse", () => {
it("maps explicit non-existence to not_found", () => {
expect(interpretSubnetResponse(9, { exists: false }).status).toBe("not_found");
});
it("maps an unhealthy interface (any alias) to exists_unhealthy", () => {
expect(interpretSubnetResponse(9, { healthy: false }).status).toBe("exists_unhealthy");
expect(interpretSubnetResponse(9, { interfaceHealthy: false }).status).toBe("exists_unhealthy");
expect(interpretSubnetResponse(9, { interface: { healthy: false } }).status).toBe("exists_unhealthy");
});
it("treats present/healthy/unknown shapes as exists_healthy", () => {
expect(interpretSubnetResponse(9, { exists: true, healthy: true }).status).toBe("exists_healthy");
expect(interpretSubnetResponse(9, { netuid: 9 }).status).toBe("exists_healthy");
expect(interpretSubnetResponse(9, {} as MetagraphedSubnetResponse).status).toBe("exists_healthy");
expect(interpretSubnetResponse(9, { interface: null }).status).toBe("exists_healthy");
});
});

describe("validateNetuid", () => {
it("maps HTTP 404 to not_found and strips a trailing slash from the base URL", async () => {
const fetchImpl = fetchReturning(404, {});
const result = await validateNetuid(42, { baseUrl: "https://meta.example/", fetchImpl });
expect(result.status).toBe("not_found");
expect(fetchImpl).toHaveBeenCalledWith("https://meta.example/subnets/42", expect.objectContaining({ headers: expect.any(Object) }));
});

it("maps other non-2xx responses to unavailable", async () => {
const result = await validateNetuid(42, { baseUrl: "https://meta.example", fetchImpl: fetchReturning(503, {}) });
expect(result.status).toBe("unavailable");
});

it("interprets a 2xx body (not_found / unhealthy / healthy)", async () => {
expect((await validateNetuid(1, { baseUrl: "https://m", fetchImpl: fetchReturning(200, { exists: false }) })).status).toBe("not_found");
expect((await validateNetuid(1, { baseUrl: "https://m", fetchImpl: fetchReturning(200, { healthy: false }) })).status).toBe("exists_unhealthy");
expect((await validateNetuid(1, { baseUrl: "https://m", fetchImpl: fetchReturning(200, { healthy: true }) })).status).toBe("exists_healthy");
});

it("never rejects — network/parse errors map to unavailable", async () => {
const fetchImpl = vi.fn(async () => {
throw new Error("ECONNREFUSED");
}) as unknown as typeof fetch;
const result = await validateNetuid(42, { baseUrl: "https://meta.example", fetchImpl });
expect(result.status).toBe("unavailable");
expect(result.detail).toContain("42");
});

it("uses the global fetch and default timeout when none are injected", async () => {
const globalFetch = fetchReturning(200, { healthy: true });
vi.stubGlobal("fetch", globalFetch);
const result = await validateNetuid(74, { baseUrl: "https://meta.example", timeoutMs: 1234 });
expect(result.status).toBe("exists_healthy");
expect(globalFetch).toHaveBeenCalledTimes(1);
expect(METAGRAPHED_FETCH_TIMEOUT_MS).toBe(10_000);
});
});

describe("assessSubnetClaimFindings", () => {
it("is dormant (no findings, no fetch) when METAGRAPHED_API_URL is unset or blank", async () => {
const fetchImpl = fetchReturning(404, {});
expect(await assessSubnetClaimFindings({}, { title: "integrates subnet 42" }, { fetchImpl })).toEqual([]);
expect(await assessSubnetClaimFindings({ METAGRAPHED_API_URL: " " }, { title: "integrates subnet 42" }, { fetchImpl })).toEqual([]);
expect(fetchImpl).not.toHaveBeenCalled();
});

it("surfaces an advisory finding when a claimed subnet is not found (acceptance criterion)", async () => {
const findings = await assessSubnetClaimFindings(
{ METAGRAPHED_API_URL: "https://meta.example" },
{ title: "feat: integrates subnet 999", body: "wires the new subnet" },
{ fetchImpl: fetchReturning(404, {}), timeoutMs: 2000 },
);
expect(findings).toHaveLength(1);
expect(findings[0]?.code).toBe("subnet_claim_not_found");
expect(findings[0]?.detail).toContain("metagraphed");
});

it("falls back to global fetch when no fetchImpl is provided", async () => {
const globalFetch = fetchReturning(200, { healthy: false });
vi.stubGlobal("fetch", globalFetch);
const findings = await assessSubnetClaimFindings({ METAGRAPHED_API_URL: "https://meta.example" }, { body: "uses subnet 5" });
expect(findings.map((f) => f.code)).toEqual(["subnet_claim_unhealthy"]);
expect(globalFetch).toHaveBeenCalledTimes(1);
});
});
Loading
Loading