feat: rename dashboard to Capacity Markets with Vector sale proofs

This commit is contained in:
david
2026-08-28 11:59:24 +02:00
parent af405cf378
commit 59cac438f2
11 changed files with 2254 additions and 152 deletions

View File

@@ -69,9 +69,10 @@ export interface SurplusOfferPatch {
export interface SurplusSale {
id: string;
model: string;
offerId: string | null;
offerId: string;
settlementStatus: string;
createdAt: string | null;
createdAt: string;
createdAtMs: number;
sellerCostMicroUsd: number;
inputTokens: number;
outputTokens: number;
@@ -137,6 +138,15 @@ export class SurplusHttpError extends Error {
}
}
export class SurplusHistoricalCompletenessError extends Error {
constructor() {
super(
"Surplus does not provide historical sale pagination; only the observed recent-sales window is available",
);
this.name = "SurplusHistoricalCompletenessError";
}
}
export class SurplusClient {
private readonly apiBaseUrl: string;
private readonly sellerApiKey: string;
@@ -337,6 +347,27 @@ export class SurplusClient {
};
}
async listObservedSales(): Promise<SurplusSale[]> {
const salesById = new Map<string, SurplusSale>();
for (const sale of (await this.getEarnings()).recentSales) {
const existing = salesById.get(sale.id);
if (existing === undefined) {
salesById.set(sale.id, sale);
} else if (!sameImmutableSale(existing, sale)) {
throw new Error(
`Surplus recent-sales window contains conflicting immutable fields for sale ${sale.id}`,
);
}
}
return [...salesById.values()].sort((left, right) =>
left.id.localeCompare(right.id)
);
}
async listAllSales(): Promise<SurplusSale[]> {
throw new SurplusHistoricalCompletenessError();
}
private async requestJson(
path: string,
init: RequestInit = {},
@@ -536,16 +567,21 @@ function parseOrderBookOffer(value: unknown, index: number): SurplusOrderBookOff
function parseSale(value: unknown, index: number): SurplusSale {
const item = record(value, `Surplus sale ${index}`);
const createdAtMs = timestampMs(
item.created_at,
`Surplus sale ${index}.created_at`,
);
return {
id: string(item.id, `Surplus sale ${index}.id`),
model: string(item.model, `Surplus sale ${index}.model`),
offerId: optionalString(item.offer_id),
offerId: string(item.offer_id, `Surplus sale ${index}.offer_id`),
settlementStatus: string(item.settlement_status, `Surplus sale ${index}.settlement_status`),
createdAt: optionalTimestamp(item.created_at, `Surplus sale ${index}.created_at`),
createdAt: new Date(createdAtMs).toISOString(),
createdAtMs,
sellerCostMicroUsd: microUsd(item.seller_cost_usdc, `Surplus sale ${index}.seller_cost_usdc`),
inputTokens: optionalSafeInteger(item.input_tokens, `Surplus sale ${index}.input_tokens`),
outputTokens: optionalSafeInteger(item.output_tokens, `Surplus sale ${index}.output_tokens`),
cacheReadTokens: optionalSafeInteger(
inputTokens: nonNegativeSafeInteger(item.input_tokens, `Surplus sale ${index}.input_tokens`),
outputTokens: nonNegativeSafeInteger(item.output_tokens, `Surplus sale ${index}.output_tokens`),
cacheReadTokens: nonNegativeSafeInteger(
item.cache_read_tokens,
`Surplus sale ${index}.cache_read_tokens`,
),
@@ -561,6 +597,19 @@ function parseSale(value: unknown, index: number): SurplusSale {
};
}
function sameImmutableSale(left: SurplusSale, right: SurplusSale): boolean {
return left.id === right.id &&
left.offerId === right.offerId &&
left.model === right.model &&
left.createdAtMs === right.createdAtMs &&
left.inputTokens === right.inputTokens &&
left.outputTokens === right.outputTokens &&
left.cacheReadTokens === right.cacheReadTokens &&
left.sellerCostMicroUsd === right.sellerCostMicroUsd &&
left.effectiveInputUsdPer1m === right.effectiveInputUsdPer1m &&
left.effectiveOutputUsdPer1m === right.effectiveOutputUsdPer1m;
}
function parseDailyEarnings(
value: unknown,
index: number,
@@ -648,15 +697,19 @@ function optionalString(value: unknown): string | null {
function optionalTimestamp(value: unknown, field: string): string | null {
if (value === null || value === undefined) return null;
return new Date(timestampMs(value, field)).toISOString();
}
function timestampMs(value: unknown, field: string): number {
const timestamp = typeof value === "number"
? value
: typeof value === "string"
? Date.parse(value)
: Number.NaN;
if (!Number.isFinite(timestamp) || timestamp < 0) {
throw new Error(`${field} must be a timestamp`);
if (!Number.isSafeInteger(timestamp) || timestamp < 0) {
throw new Error(`${field} must be a timestamp with integer milliseconds`);
}
return new Date(timestamp).toISOString();
return timestamp;
}
function boolean(value: unknown, field: string): boolean {

View File

@@ -5,7 +5,12 @@ import type {
SurplusEarnings,
SurplusOffer,
SurplusOrderBook,
SurplusSale,
} from "./client.js";
import {
hashSurplusSale,
type SurplusDashboardVectorProof,
} from "./vectorProof.js";
import type {
SurplusControllerStatus,
SurplusSellerController,
@@ -25,6 +30,19 @@ export interface VectorMigrationSummary {
historicalSettledJobs: number;
historicalUpstreamSpendUsd: string;
}
export interface DashboardSaleProof {
status: "pending" | "confirmed" | "failed";
saleHash: string | null;
batchRoot: string | null;
txHash: string | null;
leafIndex: number | null;
siblings: string[];
}
export interface DashboardProofSource {
dashboardProof(saleId: string): SurplusDashboardVectorProof | null;
}
export interface SurplusDashboardSnapshot {
generatedAt: string;
@@ -80,6 +98,7 @@ export interface SurplusDashboardSnapshot {
cacheReadTokens: number;
revenueUsd: string;
transactionHash: string | null;
vectorProof: DashboardSaleProof;
}>;
};
vector: VectorMigrationSummary;
@@ -91,6 +110,7 @@ interface SurplusDashboardOptions {
allowance: Pick<OpenRouterKeyClient, "readAllowance">;
config: SurplusManagerConfig;
vector?: VectorMigrationSummary;
proofs?: DashboardProofSource;
cacheMs?: number;
now?: () => number;
}
@@ -117,6 +137,7 @@ export class SurplusDashboardService {
>;
private readonly allowance: Pick<OpenRouterKeyClient, "readAllowance">;
private readonly config: SurplusManagerConfig;
private readonly proofs: DashboardProofSource | null;
private readonly vector: VectorMigrationSummary;
private readonly cacheMs: number;
private readonly now: () => number;
@@ -128,6 +149,7 @@ export class SurplusDashboardService {
this.client = options.client;
this.allowance = options.allowance;
this.config = options.config;
this.proofs = options.proofs ?? null;
this.vector = options.vector ?? DEFAULT_VECTOR_MIGRATION;
this.cacheMs = options.cacheMs ?? 15_000;
this.now = options.now ?? Date.now;
@@ -218,11 +240,31 @@ export class SurplusDashboardService {
cacheReadTokens: sale.cacheReadTokens,
revenueUsd: formatMicroUsd(sale.sellerCostMicroUsd),
transactionHash: sale.transactionHash,
vectorProof: this.proofFor(sale),
})),
},
vector: this.vector,
};
}
private proofFor(sale: SurplusSale): DashboardSaleProof {
const ledger = this.proofs?.dashboardProof(sale.id) ?? null;
if (ledger !== null) return ledger;
let saleHash: string | null = null;
try {
saleHash = hashSurplusSale(sale);
} catch {
saleHash = null;
}
return {
status: "pending",
saleHash,
batchRoot: null,
txHash: null,
leafIndex: null,
siblings: [],
};
}
}
function findOffer(

File diff suppressed because it is too large Load Diff