Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@akashnetwork/logging": "*",
"@akashnetwork/net": "*",
"@akashnetwork/openapi-sdk": "*",
"@akashnetwork/provider-verification": "*",
"@amplitude/analytics-node": "^1.3.8",
"@casl/ability": "^6.8.1",
"@chain-registry/assets": "^1.64.79",
Expand Down
133 changes: 133 additions & 0 deletions apps/api/src/bid-screening/http-schemas/bid-screening.schema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { AuditorSelectionMode, CapabilityFlag, VerificationTier } from "@akashnetwork/chain-sdk/private-types/akash.v1";
import { describe, expect, it } from "vitest";

import { BidScreeningRequestSchema, BidScreeningResponseSchema } from "./bid-screening.schema";

describe("BidScreeningRequestSchema", () => {
it("accepts the canonical verification requirement and preserves its enum values", () => {
const result = BidScreeningRequestSchema.parse(createRequest());

expect(result.requirements.verification).toEqual({
minTier: VerificationTier.verification_tier_verified,
requiredCapabilities: [CapabilityFlag.capability_persistent_storage],
requiredAuditors: ["akash1auditor"],
auditorMode: AuditorSelectionMode.auditor_selection_mode_all,
minAuditorCount: 2
});
expect(result.resources[0].resource.endpoints).toEqual([{ kind: "SHARED_HTTP", sequenceNumber: 1 }]);
});

it("rejects enum values outside the chain contract", () => {
const request = createRequest();
request.requirements.verification.minTier = 99 as VerificationTier;
request.requirements.verification.requiredCapabilities = [CapabilityFlag.capability_unspecified];

expect(BidScreeningRequestSchema.safeParse(request).success).toBe(false);
});
});

describe("BidScreeningResponseSchema", () => {
it("accepts pass, not-evaluated, and exclusion results", () => {
const summary = createVerificationSummary();
const response = {
providers: [
createProvider({ outcome: "pass", summary }),
createProvider({ outcome: "not_evaluated", incompleteFacts: ["snapshot", "attestations"], summary })
],
exclusions: [
{
owner: "akash1excluded",
firstFailure: {
code: "insufficient_tier",
actual: VerificationTier.verification_tier_identified,
required: VerificationTier.verification_tier_verified
},
failures: [
{
code: "insufficient_tier",
actual: VerificationTier.verification_tier_identified,
required: VerificationTier.verification_tier_verified
},
{ code: "missing_capability", capability: CapabilityFlag.capability_persistent_storage }
],
summary
}
]
};

expect(BidScreeningResponseSchema.parse(response)).toEqual(response);
});

it("rejects exclusion enum values outside the chain contract", () => {
const summary = createVerificationSummary();
const response = {
providers: [],
exclusions: [
{
owner: "akash1excluded",
firstFailure: { code: "required_auditor_not_found", mode: 99, missing: ["akash1auditor"] },
failures: [{ code: "required_auditor_not_found", mode: 99, missing: ["akash1auditor"] }],
summary
}
]
};

expect(BidScreeningResponseSchema.safeParse(response).success).toBe(false);
});
});

function createRequest() {
return {
requirements: {
signedBy: { allOf: ["akash1legacy"], anyOf: ["akash1existing"] },
attributes: [{ key: "region", value: "us-west" }],
verification: {
minTier: VerificationTier.verification_tier_verified,
requiredCapabilities: [CapabilityFlag.capability_persistent_storage],
requiredAuditors: ["akash1auditor"],
auditorMode: AuditorSelectionMode.auditor_selection_mode_all,
minAuditorCount: 2
}
},
resources: [
{
resource: {
id: 1,
cpu: { units: { val: "1000" } },
memory: { quantity: { val: "1048576" } },
gpu: { units: { val: "0" } },
storage: [{ name: "default", quantity: { val: "1073741824" } }],
endpoints: [{ kind: "SHARED_HTTP" as const, sequenceNumber: "1" }]
},
count: 1,
price: { denom: "uakt", amount: "1000" }
}
],
timezone: "UTC"
};
}

function createVerificationSummary() {
return {
bestStatusValidTier: VerificationTier.verification_tier_verified,
tierGateTier: VerificationTier.verification_tier_verified,
capabilities: [CapabilityFlag.capability_persistent_storage],
validAttestationCount: 2,
validAuditors: ["akash1auditor", "akash1auditor2"],
snapshotState: "current" as const,
observedHeight: "1234"
};
}

function createProvider(verification: Record<string, unknown>) {
return {
owner: "akash1provider",
hostUri: "https://provider.example.com:8443",
isAudited: true,
createdAt: "2026-08-24T00:00:00.000Z",
location: "us-west",
organization: "Akash",
verification,
incidents: []
};
}
99 changes: 96 additions & 3 deletions apps/api/src/bid-screening/http-schemas/bid-screening.schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AuditorSelectionMode, CapabilityFlag, VerificationTier } from "@akashnetwork/chain-sdk/private-types/akash.v1";
import { z } from "@hono/zod-openapi";

const UIntStringSchema = z.string().regex(/^\d+$/, "Must be an unsigned integer string");
Expand Down Expand Up @@ -65,7 +66,14 @@ const ResourceSchema = z.object({
attributes: z.array(AttributeSchema).optional()
}),
storage: z.array(StorageResourceSchema),
endpoints: z.array(z.unknown()).optional().optional()
endpoints: z
.array(
z.object({
kind: z.enum(["SHARED_HTTP", "RANDOM_PORT", "LEASED_IP", "UNRECOGNIZED"]).optional(),
sequenceNumber: z.number({ coerce: true }).int().nonnegative().optional()
})
)
.optional()
});

const PriceSchema = z.object({
Expand All @@ -84,9 +92,63 @@ const SignedBySchema = z.object({
anyOf: z.array(z.string()).default([])
});

const VerificationTierSchema = z.union([
z.literal(VerificationTier.verification_tier_unspecified),
z.literal(VerificationTier.verification_tier_identified),
z.literal(VerificationTier.verification_tier_verified),
z.literal(VerificationTier.verification_tier_established),
z.literal(VerificationTier.verification_tier_trusted)
]);

const CapabilityFlagSchema = z.union([
z.literal(CapabilityFlag.capability_tee_hardware_attestation),
z.literal(CapabilityFlag.capability_confidential_computing),
z.literal(CapabilityFlag.capability_persistent_storage),
z.literal(CapabilityFlag.capability_bare_metal)
]);

const AnyVerificationTierSchema = z.nativeEnum(VerificationTier);
const AnyCapabilityFlagSchema = z.nativeEnum(CapabilityFlag);
const AnyAuditorSelectionModeSchema = z.nativeEnum(AuditorSelectionMode);

const AuditorSelectionModeSchema = z.union([
z.literal(AuditorSelectionMode.auditor_selection_mode_unspecified),
z.literal(AuditorSelectionMode.auditor_selection_mode_any),
z.literal(AuditorSelectionMode.auditor_selection_mode_all)
]);

const ProviderVerificationSummarySchema = z.object({
bestStatusValidTier: AnyVerificationTierSchema,
tierGateTier: AnyVerificationTierSchema,
capabilities: z.array(AnyCapabilityFlagSchema),
validAttestationCount: z.number().int().nonnegative(),
validAuditors: z.array(z.string()),
snapshotState: z.enum(["unknown", "not_posted", "current", "stale", "suspended"]),
observedHeight: z.string()
});

export const VerificationRequirementSchema = z
.object({
minTier: VerificationTierSchema,
requiredCapabilities: z.array(CapabilityFlagSchema).default([]),
requiredAuditors: z.array(z.string().min(1)).default([]),
auditorMode: AuditorSelectionModeSchema.default(AuditorSelectionMode.auditor_selection_mode_unspecified),
minAuditorCount: z.number().int().min(0).max(4_294_967_295).default(0)
})
.superRefine((requirement, ctx) => {
if (requirement.minTier !== VerificationTier.verification_tier_unspecified) return;
if (requirement.requiredCapabilities.length === 0 && requirement.requiredAuditors.length === 0 && requirement.minAuditorCount === 0) return;

ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Tier 0 cannot be combined with capabilities, auditors, or a minimum auditor count"
});
});

const RequirementsSchema = z.object({
signedBy: SignedBySchema.default({}),
attributes: z.array(AttributeSchema).default([])
attributes: z.array(AttributeSchema).default([]),
verification: VerificationRequirementSchema.optional()
});

/**
Expand Down Expand Up @@ -133,6 +195,19 @@ const ProviderResultSchema = z.object({
description: "Provider organization from the organization attribute (signed preferred, else self-declared); null if unset",
example: "Akash"
}),
verification: z
.discriminatedUnion("outcome", [
z.object({
outcome: z.literal("pass"),
summary: ProviderVerificationSummarySchema
}),
z.object({
outcome: z.literal("not_evaluated"),
incompleteFacts: z.array(z.enum(["params", "attestations", "graces", "snapshot", "module_inactive"])),
summary: ProviderVerificationSummarySchema
})
])
.optional(),
incidents: z
.array(
z.object({
Expand All @@ -145,8 +220,26 @@ const ProviderResultSchema = z.object({
.openapi({ description: "Per-day downtime over a rolling 7-day window" })
});

const VerificationFailureSchema = z.discriminatedUnion("code", [
z.object({ code: z.literal("snapshot_not_posted") }),
z.object({ code: z.literal("snapshot_suspended") }),
z.object({ code: z.literal("snapshot_stale") }),
z.object({ code: z.literal("insufficient_tier"), actual: AnyVerificationTierSchema, required: AnyVerificationTierSchema }),
z.object({ code: z.literal("missing_capability"), capability: AnyCapabilityFlagSchema }),
z.object({ code: z.literal("insufficient_auditor_count"), actual: z.number().int().nonnegative(), required: z.number().int().nonnegative() }),
z.object({ code: z.literal("required_auditor_not_found"), mode: AnyAuditorSelectionModeSchema, missing: z.array(z.string()) })
]);

const VerificationExclusionSchema = z.object({
owner: z.string(),
firstFailure: VerificationFailureSchema,
failures: z.array(VerificationFailureSchema).min(1),
summary: ProviderVerificationSummarySchema
});

export const BidScreeningResponseSchema = z.object({
providers: z.array(ProviderResultSchema)
providers: z.array(ProviderResultSchema),
exclusions: z.array(VerificationExclusionSchema).optional()
});
export type BidScreeningResponse = z.infer<typeof BidScreeningResponseSchema>;

Expand Down
Loading
Loading