Skip to content
Merged
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
25 changes: 21 additions & 4 deletions src/scoring/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,32 @@ export async function refreshScoringModelSnapshot(env: Env): Promise<ScoringMode
const warnings: string[] = [];
const fetchedAt = nowIso();
const upstream = scoringUpstreamConfig(env);
const constantsUrl = upstreamRawUrl(upstream, "gittensor/constants.py");
const programmingLanguagesUrl = upstreamRawUrl(upstream, "gittensor/validator/weights/programming_languages.json");
const [registrySnapshot, constantsResult, languagesResult, upstreamSourceSha] = await Promise.all([
// Pin the fetch to the upstream ref's immutable HEAD commit SHA so a force-push / branch-rename can't silently
// change what every repo scores against: resolve ref → SHA first, then fetch the constants AT that SHA (an
// atomic SHA↔constants binding, recorded in the payload). Best-effort — if the SHA can't be resolved (a
// transient API error) fall back to the mutable ref so a refresh is never blocked purely on the SHA lookup.
const upstreamSourceSha = await fetchUpstreamRefSha(upstream, env.GITHUB_PUBLIC_TOKEN);
const fetchRef = upstreamSourceSha ?? upstream.ref;
const constantsUrl = upstreamRawUrl({ repo: upstream.repo, ref: fetchRef }, "gittensor/constants.py");
const programmingLanguagesUrl = upstreamRawUrl({ repo: upstream.repo, ref: fetchRef }, "gittensor/validator/weights/programming_languages.json");
const [registrySnapshot, constantsResult, languagesResult] = await Promise.all([
getLatestRegistrySnapshot(env),
fetchText(constantsUrl, env.GITHUB_PUBLIC_TOKEN),
fetchJson(programmingLanguagesUrl, env.GITHUB_PUBLIC_TOKEN),
fetchUpstreamRefSha(upstream, env.GITHUB_PUBLIC_TOKEN),
]);

// FAIL-CLOSED (#scoring-fail-closed): a failed constants fetch must NEVER silently overwrite the last verified
// upstream constants with hardcoded DEFAULT_SCORING_CONSTANTS — that would move live scoring with no one
// noticing. Freeze the last-good snapshot instead (its age is surfaced by scoringSnapshotStalenessWarning), and
// only bootstrap to defaults when there is no verified last-good to fall back to.
if (!constantsResult.ok) {
const lastGood = await getLatestScoringModelSnapshot(env);
if (lastGood && lastGood.sourceKind !== "fallback") {
const frozenNote = `Upstream scoring constants refresh failed (${constantsResult.error}); froze the last-good snapshot rather than reverting to default constants.`;
return { ...lastGood, warnings: [...lastGood.warnings, frozenNote] };
}
}

let sourceKind: ScoringModelSnapshotRecord["sourceKind"] = "raw-github";
let constants = { ...DEFAULT_SCORING_CONSTANTS };
let activeModelConstants: Record<string, number> = {};
Expand Down
55 changes: 55 additions & 0 deletions test/unit/scoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,61 @@ NOVELTY_BONUS_SCALAR = 3
expect(refreshed.sourceKind).toBe("raw-github");
});

it("pins the constants fetch to the resolved upstream SHA (immutable) when it can be resolved", async () => {
const env = createTestEnv({ GITTENSOR_UPSTREAM_REPO: "custom/upstream", GITTENSOR_UPSTREAM_REF: "test" });
const SHA = "0123456789abcdef0123456789abcdef01234567";
const fetchedUrls: string[] = [];
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = input.toString();
fetchedUrls.push(url);
if (url.includes("github.com/ghapi") && url.includes("/commits/test")) return Response.json({ sha: SHA });
if (url.includes("constants.py")) return new Response("MERGED_PR_BASE_SCORE = 25\n");
if (url.includes("programming_languages.json")) return Response.json({});
return new Response("not found", { status: 404 });
});

const refreshed = await refreshScoringModelSnapshot(env);

// The constants are fetched from the immutable SHA path, not the mutable branch ref.
expect(refreshed.sourceUrl).toBe(`https://github.com/ghraw/custom/upstream/${SHA}/gittensor/constants.py`);
expect(fetchedUrls).toContain(`https://github.com/ghraw/custom/upstream/${SHA}/gittensor/constants.py`);
expect(fetchedUrls).not.toContain("https://github.com/ghraw/custom/upstream/test/gittensor/constants.py");
expect(refreshed.payload.upstreamSourceSha).toBe(SHA);
expect(refreshed.sourceKind).toBe("raw-github");
});

it("FAILS CLOSED on a failed constants fetch: freezes the last-good snapshot instead of reverting to defaults", async () => {
const env = createTestEnv();
// 1) A good refresh persists a verified raw-github snapshot.
vi.stubGlobal("fetch", async (input: RequestInfo | URL) => {
const url = input.toString();
if (url.includes("constants.py")) return new Response("MERGED_PR_BASE_SCORE = 25\nOSS_EMISSION_SHARE = 0.5\n");
if (url.includes("programming_languages.json")) return Response.json({ TypeScript: 1 });
return new Response("not found", { status: 404 });
});
const good = await refreshScoringModelSnapshot(env);
expect(good.sourceKind).toBe("raw-github");
expect(good.constants.OSS_EMISSION_SHARE).toBe(0.5);

// 2) Upstream now fails. Fail-closed: keep the last-good constants, do NOT revert to DEFAULT_SCORING_CONSTANTS.
vi.stubGlobal("fetch", async () => new Response("upstream down", { status: 500 }));
const frozen = await refreshScoringModelSnapshot(env);
expect(frozen.id).toBe(good.id); // same snapshot — froze the last-good
expect(frozen.sourceKind).toBe("raw-github"); // NOT "fallback"
expect(frozen.constants.OSS_EMISSION_SHARE).toBe(0.5); // verified upstream value, never the hardcoded default
expect(frozen.warnings.join(" ")).toMatch(/froze the last-good snapshot/i);
// No defaults snapshot was persisted — the latest is still the verified last-good.
await expect(getLatestScoringModelSnapshot(env)).resolves.toMatchObject({ id: good.id, sourceKind: "raw-github" });
});

it("bootstraps to defaults (fallback) on a failed fetch ONLY when there is no verified last-good", async () => {
const env = createTestEnv();
vi.stubGlobal("fetch", async () => new Response("missing", { status: 404 }));
const bootstrap = await refreshScoringModelSnapshot(env);
expect(bootstrap.sourceKind).toBe("fallback");
expect(bootstrap.warnings.join(" ")).toMatch(/fetch failed/i);
});

it("uses saturation math as the active private preview model", () => {
const saturationSnapshot: ScoringModelSnapshotRecord = {
...snapshot,
Expand Down
Loading