feat(store): add agent x402 capacity purchases

This commit is contained in:
saltbo
2026-07-31 09:27:24 -04:00
parent a968f85d7c
commit 8402844b5e
42 changed files with 15135 additions and 102 deletions
+2
View File
@@ -17,6 +17,7 @@ export const AGENT_OAUTH_RESOURCE_SCOPES = [
AuthorizationScope.SHARES_CREATE,
AuthorizationScope.SHARES_DELETE,
AuthorizationScope.QUOTA_READ,
AuthorizationScope.QUOTA_PURCHASE,
AuthorizationScope.STORAGE_USAGE_READ,
] as const
export const AGENT_OAUTH_SCOPES = [...AGENT_OAUTH_STANDARD_SCOPES, ...AGENT_OAUTH_RESOURCE_SCOPES] as const
@@ -29,5 +30,6 @@ export const AGENT_OAUTH_SCOPE_DESCRIPTIONS: Record<(typeof AGENT_OAUTH_RESOURCE
[AuthorizationScope.SHARES_CREATE]: 'Create public shares',
[AuthorizationScope.SHARES_DELETE]: 'Revoke shares',
[AuthorizationScope.QUOTA_READ]: 'Inspect workspace quota',
[AuthorizationScope.QUOTA_PURCHASE]: 'Purchase workspace storage capacity',
[AuthorizationScope.STORAGE_USAGE_READ]: 'Inspect workspace storage usage',
}
+1
View File
@@ -8,6 +8,7 @@ export const AuthorizationScope = {
SHARES_CREATE: 'shares:create',
SHARES_DELETE: 'shares:delete',
QUOTA_READ: 'quota:read',
QUOTA_PURCHASE: 'quota:purchase',
STORAGE_USAGE_READ: 'storage-usage:read',
IMAGES_UPLOAD: 'images:upload',
DOWNLOAD_TASKS_READ: 'download-tasks:read',
+56
View File
@@ -0,0 +1,56 @@
import { describe, expect, it } from 'vitest'
import { cloudOrderQuotaChangeSchema } from './cloud-store-legacy'
function deliveryEvent(context: Record<string, unknown>) {
return {
eventId: 'event-1',
eventType: 'commerce.order_item.fulfilled',
orderId: 'order-1',
orderItemId: 'item-1',
productId: 'product-1',
productName: 'Storage',
quantity: 1,
deliverable: { storageBytes: 1024 },
target: { orgId: 'org-1' },
context: {
storeId: 'store-1',
paymentProvider: 'x402',
...context,
},
occurredAt: '2026-07-31T00:00:00.000Z',
}
}
describe('cloudOrderQuotaChangeSchema', () => {
it('accepts ISO billing periods', () => {
expect(
cloudOrderQuotaChangeSchema.safeParse(
deliveryEvent({
billingPeriodStart: '2026-07-31T00:00:00.000Z',
billingPeriodEnd: '2026-08-31T00:00:00.000Z',
}),
).success,
).toBe(true)
})
it('rejects malformed billing periods', () => {
expect(
cloudOrderQuotaChangeSchema.safeParse(
deliveryEvent({
billingPeriodStart: 'not-a-date',
billingPeriodEnd: '2026-08-31T00:00:00.000Z',
}),
).success,
).toBe(false)
})
it('rejects incomplete billing periods', () => {
expect(
cloudOrderQuotaChangeSchema.safeParse(
deliveryEvent({
billingPeriodStart: '2026-07-31T00:00:00.000Z',
}),
).success,
).toBe(false)
})
})
+39 -13
View File
@@ -55,18 +55,29 @@ const storeDeliveryEventSchema = z
quantity: z.number().int().positive(),
deliverable: z.record(z.string(), z.unknown()),
target: z.record(z.string(), z.unknown()).nullable(),
context: z.object({
storeId: z.string().min(1),
paymentProvider: z.enum(['stripe', 'gift_card', 'credits']).nullable(),
stripePriceId: z.string().nullable().optional(),
stripePriceLookupKey: z.string().nullable().optional(),
stripePriceRecurring: z.unknown().optional(),
stripePriceMetadata: z.record(z.string(), z.string()).optional(),
stripeSubscriptionId: z.string().nullable().optional(),
stripeInvoiceId: z.string().nullable().optional(),
billingPeriodStart: z.string().nullable().optional(),
billingPeriodEnd: z.string().nullable().optional(),
}),
context: z
.object({
storeId: z.string().min(1),
paymentProvider: z.enum(['stripe', 'gift_card', 'credits', 'x402']).nullable(),
providerTransactionId: z.string().nullable().optional(),
x402AuditContext: z.record(z.string(), z.unknown()).nullable().optional(),
stripePriceId: z.string().nullable().optional(),
stripePriceLookupKey: z.string().nullable().optional(),
stripePriceRecurring: z.unknown().optional(),
stripePriceMetadata: z.record(z.string(), z.string()).optional(),
stripeSubscriptionId: z.string().nullable().optional(),
stripeInvoiceId: z.string().nullable().optional(),
billingPeriodStart: z.string().datetime().nullable().optional(),
billingPeriodEnd: z.string().datetime().nullable().optional(),
})
.superRefine((context, ctx) => {
if (Boolean(context.billingPeriodStart) === Boolean(context.billingPeriodEnd)) return
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: [context.billingPeriodStart ? 'billingPeriodEnd' : 'billingPeriodStart'],
message: 'Billing period start and end must be provided together',
})
}),
occurredAt: z.string().min(1),
})
.superRefine((event, ctx) => {
@@ -104,6 +115,16 @@ function targetOrgId(target: Record<string, unknown> | null) {
function sourceId(event: z.infer<typeof storeDeliveryEventSchema>) {
const orgId = targetOrgId(event.target)
if (event.context.stripeSubscriptionId) return `stripe_subscription:${event.context.stripeSubscriptionId}:${orgId}`
if (event.context.billingPeriodStart && event.context.billingPeriodEnd) {
return [
event.context.paymentProvider ?? 'commerce',
'period',
event.productId,
event.context.billingPeriodStart,
event.context.billingPeriodEnd,
orgId,
].join(':')
}
return event.orderId
}
@@ -128,7 +149,12 @@ export const cloudOrderQuotaChangeSchema = z.union([
storageBytes: numberDeliverableValue(event.deliverable, 'storageBytes'),
trafficBytes: numberDeliverableValue(event.deliverable, 'trafficBytes'),
trafficOveragePriceCents: optionalNumberDeliverableValue(event.deliverable, 'trafficOveragePriceCents'),
source: event.context.stripeSubscriptionId ? 'stripe_subscription' : 'stripe',
source: event.context.billingPeriodStart ? 'commerce_period' : (event.context.paymentProvider ?? 'commerce'),
entitlementType: event.context.billingPeriodStart ? ('plan' as const) : ('grant' as const),
startsAt: event.context.billingPeriodStart ?? event.occurredAt,
paymentProvider: event.context.paymentProvider,
providerTransactionId: event.context.providerTransactionId,
x402AuditContext: event.context.x402AuditContext,
packageId: event.productId,
packageName: stringDeliverableValue(event.deliverable, 'packageName') ?? event.productName,
occurredAt: event.occurredAt,
+1
View File
@@ -14,5 +14,6 @@ export const oauthResourceScopeLabels = {
[AuthorizationScope.SHARES_CREATE]: 'settings.agentAccess.scope.sharesCreate',
[AuthorizationScope.SHARES_DELETE]: 'settings.agentAccess.scope.sharesDelete',
[AuthorizationScope.QUOTA_READ]: 'settings.agentAccess.scope.quotaRead',
[AuthorizationScope.QUOTA_PURCHASE]: 'settings.agentAccess.scope.quotaPurchase',
[AuthorizationScope.STORAGE_USAGE_READ]: 'settings.agentAccess.scope.storageUsageRead',
} as const satisfies Record<OAuthResourceScope, string>