From be63c6171d67febe0fc0d282a45561fd0923aa2f Mon Sep 17 00:00:00 2001 From: murashit Date: Mon, 6 Jul 2026 14:24:46 +0900 Subject: [PATCH] Model app-server metadata probe fallback --- tests/app-server/query-cache.test.ts | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/tests/app-server/query-cache.test.ts b/tests/app-server/query-cache.test.ts index e1644d45..8fb45a31 100644 --- a/tests/app-server/query-cache.test.ts +++ b/tests/app-server/query-cache.test.ts @@ -52,6 +52,56 @@ describe("AppServerQueryCache", () => { expect(cache.appServerMetadataSnapshot(context)?.serverDiagnostics.probes.models.status).toBe("failed"); }); + it("model-checks last-known-good metadata fallback for all probe status combinations", () => { + for (const statuses of metadataProbeStatusCombinations()) { + const cache = new AppServerQueryCache(); + const context = cacheContext(); + cache.writeAppServerMetadata( + context, + metadata({ + availableModels: [modelMetadata("gpt-previous")], + availableSkills: [skillMetadata("previous-skill")], + availablePermissionProfiles: [permissionProfile(":previous")], + rateLimit: rateLimit(11), + }), + ); + + cache.writeAppServerMetadata( + context, + metadata({ + availableModels: [modelMetadata("gpt-next")], + availableSkills: [skillMetadata("next-skill")], + availablePermissionProfiles: [permissionProfile(":next")], + rateLimit: rateLimit(22), + modelProbeStatus: statuses.models, + skillsProbeStatus: statuses.skills, + permissionProfilesProbeStatus: statuses.permissionProfiles, + rateLimitProbeStatus: statuses.rateLimits, + }), + ); + + const snapshot = cache.appServerMetadataSnapshot(context); + const statusCase = metadataProbeStatusCase(statuses); + expect( + snapshot?.availableModels.map((model) => model.model), + statusCase, + ).toEqual([statuses.models === "ok" ? "gpt-next" : "gpt-previous"]); + expect( + cache.modelsSnapshot(context)?.map((model) => model.model), + statusCase, + ).toEqual([statuses.models === "ok" ? "gpt-next" : "gpt-previous"]); + expect( + snapshot?.availableSkills.map((skill) => skill.name), + statusCase, + ).toEqual([statuses.skills === "ok" ? "next-skill" : "previous-skill"]); + expect( + snapshot?.availablePermissionProfiles.map((profile) => profile.id), + statusCase, + ).toEqual([statuses.permissionProfiles === "ok" ? ":next" : ":previous"]); + expect(snapshot?.rateLimit?.primary?.usedPercent, statusCase).toBe(statuses.rateLimits === "ok" ? 22 : 11); + } + }); + it("does not accept failed metadata resource payloads as cache truth", () => { const cache = new AppServerQueryCache(); const context = cacheContext(); @@ -374,6 +424,28 @@ function metadata( }; } +type MetadataProbeStatus = "ok" | "failed"; + +interface MetadataProbeStatuses { + readonly models: MetadataProbeStatus; + readonly skills: MetadataProbeStatus; + readonly permissionProfiles: MetadataProbeStatus; + readonly rateLimits: MetadataProbeStatus; +} + +function metadataProbeStatusCombinations(): MetadataProbeStatuses[] { + const statuses: readonly MetadataProbeStatus[] = ["ok", "failed"]; + return statuses.flatMap((models) => + statuses.flatMap((skills) => + statuses.flatMap((permissionProfiles) => statuses.map((rateLimits) => ({ models, skills, permissionProfiles, rateLimits }))), + ), + ); +} + +function metadataProbeStatusCase(statuses: MetadataProbeStatuses): string { + return `models=${statuses.models} skills=${statuses.skills} permissionProfiles=${statuses.permissionProfiles} rateLimits=${statuses.rateLimits}`; +} + function skillMetadata(name: string): SkillMetadata { return { name, description: "", path: `/tmp/${name}`, enabled: true }; }