Skip to content
Open
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
17 changes: 14 additions & 3 deletions apps/api/src/lib/pricing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ describe("provider pricing", () => {
expect(providers.length).toBeGreaterThanOrEqual(7);
});

it("all providers have a valid provenance", () => {
for (const provider of providers) {
expect(["mock", "fallback", "live", "unknown"]).toContain(provider.provenance);
}
});

it("returns provider-specific prices for search, news, and scrape", () => {
expect(getProviderById("search.basic")?.priceUsd).toBe(0.01);
expect(getProviderById("search.pro")?.priceUsd).toBe(0.02);
Expand Down Expand Up @@ -58,6 +64,7 @@ describe("provider catalog baseline", () => {
priceUsd: number;
enabled: boolean;
sourceType: string;
provenance: string;
}

// These are the canonical baseline providers the demo and SCF pitch depend on.
Expand All @@ -69,21 +76,24 @@ describe("provider catalog baseline", () => {
category: "search",
priceUsd: 0.01,
enabled: true,
sourceType: "deterministic-fallback"
sourceType: "deterministic-fallback",
provenance: "mock"
},
{
id: "news.fast",
category: "news",
priceUsd: 0.015,
enabled: true,
sourceType: "deterministic-fallback"
sourceType: "deterministic-fallback",
provenance: "mock"
},
{
id: "scrape.page",
category: "scrape",
priceUsd: 0.02,
enabled: true,
sourceType: "deterministic-fallback"
sourceType: "deterministic-fallback",
provenance: "mock"
}
];

Expand All @@ -101,6 +111,7 @@ describe("provider catalog baseline", () => {
expect(actual!.priceUsd, `${rowLabel} priceUsd mismatch`).toBe(expected.priceUsd);
expect(actual!.enabled, `${rowLabel} enabled mismatch`).toBe(expected.enabled);
expect(actual!.sourceType, `${rowLabel} sourceType mismatch`).toBe(expected.sourceType);
expect(actual!.provenance, `${rowLabel} provenance mismatch`).toBe(expected.provenance);
});
}
});
7 changes: 7 additions & 0 deletions apps/api/src/lib/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 1500,
qualityScore: 99,
sourceType: "live",
provenance: "live",
enabled: true
},
{
Expand All @@ -21,6 +22,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 700,
qualityScore: 75,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
},
{
Expand All @@ -32,6 +34,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 1100,
qualityScore: 90,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
},
{
Expand All @@ -43,6 +46,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 800,
qualityScore: 72,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
},
{
Expand All @@ -54,6 +58,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 1400,
qualityScore: 93,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
},
{
Expand All @@ -65,6 +70,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 1000,
qualityScore: 70,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
},
{
Expand All @@ -76,6 +82,7 @@ export const providers: ProviderDefinition[] = [
latencyEstimateMs: 1700,
qualityScore: 95,
sourceType: "deterministic-fallback",
provenance: "mock",
enabled: true
}
];
Expand Down
37 changes: 37 additions & 0 deletions apps/api/src/routes/public.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,43 @@ describe("public routes", () => {
expect(catalogResponse.body.byCategory.scrape.length).toBeGreaterThan(0);
});

it("every provider in catalog and providers endpoint includes provenance", async () => {
const app = await createPublicApp();

const providersResponse = await request(app).get("/api/providers");
const catalogResponse = await request(app).get("/api/catalog");

const allProviders = [
...providersResponse.body.providers,
...catalogResponse.body.providers,
...catalogResponse.body.byCategory.search,
...catalogResponse.body.byCategory.news,
...catalogResponse.body.byCategory.scrape
];

for (const provider of allProviders) {
expect(provider).toHaveProperty("provenance");
expect(["mock", "fallback", "live", "unknown"]).toContain(provider.provenance);
}
});

it("live provider has provenance live and mock providers have provenance mock", async () => {
const app = await createPublicApp();

const providersResponse = await request(app).get("/api/providers");
const providersList = providersResponse.body.providers;

const live = providersList.find((p: { id: string }) => p.id === "search.live");
expect(live).toBeDefined();
expect(live.provenance).toBe("live");

for (const p of providersList) {
if (p.id !== "search.live") {
expect(p.provenance).toBe("mock");
}
}
});

it("returns safe default analytics shape for fresh storage", async () => {
const app = await createPublicApp();

Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/pages/ControlDeckPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ export default function ControlDeckPage() {
<span className={`source-badge ${provider.sourceType}`}>
{provider.sourceType}
</span>
<span className={`provenance-badge ${provider.provenance}`}>
{provider.provenance}
</span>
</div>
</button>
))
Expand Down
20 changes: 20 additions & 0 deletions apps/web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,26 @@ body {
padding: 0.2rem 0.45rem;
}

.provenance-badge.mock {
border-color: rgba(250, 204, 21, 0.5);
color: #facc15;
}

.provenance-badge.fallback {
border-color: rgba(251, 146, 60, 0.5);
color: #fb923c;
}

.provenance-badge.live {
border-color: rgba(74, 222, 128, 0.5);
color: #4ade80;
}

.provenance-badge.unknown {
border-color: rgba(148, 163, 184, 0.5);
color: #94a3b8;
}

.action-row {
border: 1px dashed rgba(130, 159, 199, 0.34);
border-radius: 14px;
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export const queryModeSchema = z.enum(["search", "news", "scrape"]);

export const providerCategorySchema = queryModeSchema;

export const provenanceSchema = z.enum(["mock", "fallback", "live", "unknown"]);

export const providerSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
Expand All @@ -13,6 +15,7 @@ export const providerSchema = z.object({
latencyEstimateMs: z.number().int().positive(),
qualityScore: z.number().min(1).max(100),
sourceType: z.enum(["live", "deterministic-fallback", "unavailable"]),
provenance: provenanceSchema,
enabled: z.boolean()
});

Expand Down
2 changes: 2 additions & 0 deletions packages/shared/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type QueryMode = "search" | "news" | "scrape";
export type ProviderCategory = QueryMode;
export type SourceType = "live" | "deterministic-fallback" | "unavailable";
export type Provenance = "mock" | "fallback" | "live" | "unknown";
export type ExecutionFallbackReason =
| "timeout"
| "circuit-open"
Expand Down Expand Up @@ -30,6 +31,7 @@ export interface ProviderDefinition {
latencyEstimateMs: number;
qualityScore: number;
sourceType: SourceType;
provenance: Provenance;
enabled: boolean;
}

Expand Down
Loading