diff --git a/adapters/acp-adapter/package.json b/adapters/acp-adapter/package.json index 8a52328..360c8f6 100644 --- a/adapters/acp-adapter/package.json +++ b/adapters/acp-adapter/package.json @@ -16,6 +16,7 @@ "dependencies": { "@grey/core": "workspace:*", "@grey/pipeline": "workspace:*", + "@grey/schemas": "workspace:*", "@grey/x402-middleware": "workspace:*", "@virtuals-protocol/acp-node-v2": "^0.0.4", "pg": "^8.22.0", diff --git a/adapters/acp-adapter/src/main.ts b/adapters/acp-adapter/src/main.ts index 11bbd57..92ae1d8 100644 --- a/adapters/acp-adapter/src/main.ts +++ b/adapters/acp-adapter/src/main.ts @@ -6,8 +6,9 @@ import process from 'node:process'; import { fileURLToPath } from 'node:url'; import pg from 'pg'; import { offeringHandlers, createHandlerDeps } from '@grey/core'; -import { PAID_SLUGS, priceUsdFor } from '@grey/x402-middleware'; +import { PAID_SLUGS } from '@grey/x402-middleware'; import { loadConfig } from './config.js'; +import { priceUsdForAcp } from './pricing.js'; import { AcpAdapter } from './acpAdapter.js'; import { createRealSdkBundle } from './sdk.js'; import { createLogger } from './logger.js'; @@ -60,10 +61,11 @@ async function main(): Promise { reputationReconciler, }); - // Register the 7 paid offerings from the single price source (invariant #20), BEFORE start() — - // no boot-buffer needed (one process; no cross-plugin registration race). + // Register the 7 paid offerings from the single canonical price source (Invariant #31), + // resolved through ACP's own networkMultiplier — BEFORE start() (one process; no cross-plugin + // registration race). for (const slug of PAID_SLUGS) { - adapter.registerOffering({ slug, priceUsd: priceUsdFor(slug) }); + adapter.registerOffering({ slug, priceUsd: priceUsdForAcp(slug) }); } log.info('acp-adapter: starting', { diff --git a/adapters/acp-adapter/src/pricing.ts b/adapters/acp-adapter/src/pricing.ts new file mode 100644 index 0000000..36c461e --- /dev/null +++ b/adapters/acp-adapter/src/pricing.ts @@ -0,0 +1,14 @@ +// ACP's own price resolver (E1-A / Invariant #31) — resolves the ACP channel's networkMultiplier +// (1.00×, grandfathered, no repricing) against @grey/schemas/pricing, the single canonical source. +// Mirrors adapters/x402-middleware/src/prices.ts's resolution, kept separate per-adapter so a +// future channel-specific multiplier (this file's `acp` vs. x402's `x402`) is a config entry in +// @grey/schemas, not new code in either adapter. +import type { PaidOfferingSlug } from '@grey/schemas/responses'; +import { resolvePriceUsd } from '@grey/schemas/pricing'; + +const CHANNEL = 'acp' as const; + +/** USD price for a paid offering on the ACP channel. Throws on an unpriced offering (fail-closed). */ +export function priceUsdForAcp(slug: PaidOfferingSlug): number { + return resolvePriceUsd(slug, CHANNEL); +} diff --git a/adapters/acp-adapter/test/pricing.test.ts b/adapters/acp-adapter/test/pricing.test.ts new file mode 100644 index 0000000..f997883 --- /dev/null +++ b/adapters/acp-adapter/test/pricing.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest'; +import { priceUsdForAcp } from '../src/pricing.js'; + +// E1-A: ACP resolves its own channel multiplier (1.00×, grandfathered) against the canonical +// table in @grey/schemas/pricing — same values as before, now derived rather than hardcoded. +const EXPECTED: Record = { + legitimacy_scan: 0.25, + verify_whitepaper: 1.5, + verify_full_tech: 3.0, + claim_extraction: 0.75, + claim_history: 0.25, + quick_protocol_facts: 0.3, + daily_tech_brief: 8.0, +}; + +describe('priceUsdForAcp — ACP adapter boundary price resolution (Invariant #31)', () => { + it.each(Object.entries(EXPECTED))( + '%s resolves to the grandfathered 1.00× canonical price', + (slug, usd) => { + expect(priceUsdForAcp(slug as never)).toBe(usd); + }, + ); +}); diff --git a/adapters/x402-middleware/package.json b/adapters/x402-middleware/package.json index b77043c..8a948d2 100644 --- a/adapters/x402-middleware/package.json +++ b/adapters/x402-middleware/package.json @@ -15,6 +15,7 @@ "test": "vitest run" }, "dependencies": { + "@grey/schemas": "workspace:*", "fastify": "^5.8.5", "viem": "^2.53.1" } diff --git a/adapters/x402-middleware/src/prices.ts b/adapters/x402-middleware/src/prices.ts index f3d0aa7..86f6fe3 100644 --- a/adapters/x402-middleware/src/prices.ts +++ b/adapters/x402-middleware/src/prices.ts @@ -1,29 +1,48 @@ -// THE single price source (invariant #20). Every route price, the 402 `maxAmountRequired`, -// and grey-core's envelope `costUsd` derive from this one table. No price literal lives -// anywhere else. USD values are the authoritative OpenAPI `x-x402-pricing`; atomic units are -// USDC (6 decimals). Kept as a string USD label + bigint atomic so no float rounding creeps in. +// x402's price resolver (E1-A / Invariant #31). @grey/schemas/pricing is THE single canonical +// price source (supersedes this file's old PRICE_TABLE literal, invariant #20 → #31); this file +// resolves x402's networkMultiplier (1.00× today) at the adapter boundary and converts the +// resolved USD to USDC atomic units. No price literal lives here anymore — PRICE_TABLE below is +// derived, kept only so existing consumers (route registration, tests) don't need to change +// shape. Every route price, the 402 `maxAmountRequired`, and grey-core's envelope `costUsd` +// derive from this resolution. Atomic units are USDC (6 decimals). +import type { PaidOfferingSlug } from '@grey/schemas/responses'; +import { resolvePriceUsd } from '@grey/schemas/pricing'; import type { X402Network, UsdcAsset } from './types.js'; -export type PaidSlug = - | 'legitimacy_scan' - | 'verify_whitepaper' - | 'verify_full_tech' - | 'claim_extraction' - | 'claim_history' - | 'quick_protocol_facts' - | 'daily_tech_brief'; - -export const PRICE_TABLE: Record = { - legitimacy_scan: { usd: '0.25', atomic: 250_000n }, - verify_whitepaper: { usd: '1.50', atomic: 1_500_000n }, - verify_full_tech: { usd: '3.00', atomic: 3_000_000n }, - claim_extraction: { usd: '0.75', atomic: 750_000n }, - claim_history: { usd: '0.25', atomic: 250_000n }, - quick_protocol_facts: { usd: '0.30', atomic: 300_000n }, - daily_tech_brief: { usd: '8.00', atomic: 8_000_000n }, -}; +export type PaidSlug = PaidOfferingSlug; + +const CHANNEL = 'x402' as const; + +/** USD → USDC atomic units (6-dec), rounded to avoid float drift (e.g. 0.30 * 1e6 in IEEE754). */ +function toAtomic(usd: number): bigint { + return BigInt(Math.round(usd * 1_000_000)); +} + +function usdLabel(usd: number): string { + return usd.toFixed(2); +} + +const PAID_SLUG_ORDER: PaidSlug[] = [ + 'legitimacy_scan', + 'verify_whitepaper', + 'verify_full_tech', + 'claim_extraction', + 'claim_history', + 'quick_protocol_facts', + 'daily_tech_brief', +]; + +/** Derived from @grey/schemas/pricing — NOT the source. Kept as a stable {usd,atomic} shape for + * existing consumers/tests; recomputed from the canonical table + x402's networkMultiplier. */ +export const PRICE_TABLE: Record = + Object.fromEntries( + PAID_SLUG_ORDER.map((slug) => { + const usd = resolvePriceUsd(slug, CHANNEL); + return [slug, { usd: usdLabel(usd), atomic: toAtomic(usd) }]; + }), + ) as Record; -export const PAID_SLUGS = Object.keys(PRICE_TABLE) as PaidSlug[]; +export const PAID_SLUGS = [...PAID_SLUG_ORDER]; export function isPaidSlug(slug: string): slug is PaidSlug { return Object.prototype.hasOwnProperty.call(PRICE_TABLE, slug); @@ -32,13 +51,13 @@ export function isPaidSlug(slug: string): slug is PaidSlug { /** USDC atomic units (6-dec) required for a slug. Throws on unknown slug (fail-closed). */ export function priceAtomicFor(slug: string): bigint { if (!isPaidSlug(slug)) throw new Error(`x402: no price for slug ${slug}`); - return PRICE_TABLE[slug].atomic; + return toAtomic(resolvePriceUsd(slug, CHANNEL)); } /** USD price for a slug (grey-core envelope `costUsd`). Throws on unknown slug (fail-closed). */ export function priceUsdFor(slug: string): number { if (!isPaidSlug(slug)) throw new Error(`x402: no price for slug ${slug}`); - return Number(PRICE_TABLE[slug].usd); + return resolvePriceUsd(slug as PaidSlug, CHANNEL); } /** Per-network USDC asset literals — the ONE place addresses + EIP-712 domains live. diff --git a/packages/grey-core/src/orchestration/cacheOrLive.ts b/packages/grey-core/src/orchestration/cacheOrLive.ts index ada0180..e02ac42 100644 --- a/packages/grey-core/src/orchestration/cacheOrLive.ts +++ b/packages/grey-core/src/orchestration/cacheOrLive.ts @@ -8,6 +8,7 @@ // (§16). The run variants (runL1/runL1L2/runFullPipeline) are ordinary pipeline exports. The sole // sanctioned cross-boundary discovery-type reference lives in deps/index.ts (§15/§16). import type { ComputeOfferingSlug, RequestFor, DiscoveryStatus, DiscoveryAttempt } from '@grey/schemas'; +import { computeClassFor } from '@grey/schemas/pricing'; import { runL1, runL1L2, @@ -102,6 +103,16 @@ export async function cacheOrLive( input: RequestFor, deps: HandlerDeps, ): Promise { + // Invariant #30, defense-in-depth: `O extends ComputeOfferingSlug` already makes a CACHE_ONLY + // slug uncallable at compile time (only the 4 live-capable offerings satisfy that constraint) — + // this is the runtime fail-closed backstop against the class violation, same posture as + // Invariant #27's fail-open guards elsewhere, inverted (fail closed, not open). + const computeClass = computeClassFor(offering); + if (computeClass === 'CACHE_ONLY') { + throw new Error( + `cacheOrLive: refusing live compute for CACHE_ONLY offering "${offering}" (Invariant #30) — no offering may be served below its computeClass floor, including a paid retry.`, + ); + } const body = input as unknown as Record; const tokenAddress = (body.token_address as string | undefined) ?? null; const projectName = body.project_name as string | undefined; diff --git a/packages/grey-core/test/cacheOrLive.test.ts b/packages/grey-core/test/cacheOrLive.test.ts index 0752493..ec08b56 100644 --- a/packages/grey-core/test/cacheOrLive.test.ts +++ b/packages/grey-core/test/cacheOrLive.test.ts @@ -180,6 +180,30 @@ describe('cacheOrLive — subject derivation', () => { }); }); +describe('cacheOrLive — Invariant #30 (CACHE_ONLY never triggers live compute)', () => { + const CACHE_ONLY_SLUGS = ['claim_history', 'quick_protocol_facts', 'daily_tech_brief', 'daily_greenlight_list', 'scam_alert_feed'] as const; + + for (const slug of CACHE_ONLY_SLUGS) { + it(`${slug} is structurally unreachable from cacheOrLive() — compile-time AND runtime rejection`, async () => { + const deps = fakeDeps({ discover: discovered }); + // The type system already makes this uncallable (O extends ComputeOfferingSlug excludes + // every CACHE_ONLY slug) — the cast below simulates a bypass to prove the runtime + // fail-closed assertion is the actual backstop, not just the type constraint. + await expect( + cacheOrLive(slug as never, { token_address: TOKEN } as never, deps), + ).rejects.toThrow(/CACHE_ONLY/); + expect(runL1).not.toHaveBeenCalled(); + expect(runL1L2).not.toHaveBeenCalled(); + expect(runFullPipeline).not.toHaveBeenCalled(); + }); + } + + it('rejects before any discovery/pipeline side effect runs, even on a "paid retry" shape', async () => { + const deps = fakeDeps({ discover: discovered }); + await expect(cacheOrLive('scam_alert_feed' as never, {} as never, deps)).rejects.toThrow(/Invariant #30/); + }); +}); + describe('createHandlerDeps — M3.5 wiring sanity', () => { it('constructs HandlerDeps carrying pipeline + discovery (§15)', () => { const deps = createHandlerDeps({ databaseUrl: '' }); diff --git a/packages/grey-schemas/build.mjs b/packages/grey-schemas/build.mjs index 2e13d08..d7a4944 100644 --- a/packages/grey-schemas/build.mjs +++ b/packages/grey-schemas/build.mjs @@ -13,6 +13,7 @@ await buildPackage({ 'src/requests/index.ts', 'src/envelope/index.ts', 'src/validators/index.ts', + 'src/pricing/index.ts', ], // ajv 8.x has no `exports` map; its extensionless deep import must carry `.js` for Node ESM. alias: { 'ajv/dist/2020': 'ajv/dist/2020.js' }, diff --git a/packages/grey-schemas/package.json b/packages/grey-schemas/package.json index 6d4dfbd..c08a3f2 100644 --- a/packages/grey-schemas/package.json +++ b/packages/grey-schemas/package.json @@ -11,6 +11,7 @@ "./requests": { "types": "./dist/requests/index.d.ts", "default": "./dist/requests/index.js" }, "./envelope": { "types": "./dist/envelope/index.d.ts", "default": "./dist/envelope/index.js" }, "./validators": { "types": "./dist/validators/index.d.ts", "default": "./dist/validators/index.js" }, + "./pricing": { "types": "./dist/pricing/index.d.ts", "default": "./dist/pricing/index.js" }, "./openapi": "./openapi/openapi.yaml" }, "files": ["dist", "openapi"], diff --git a/packages/grey-schemas/src/pricing/index.ts b/packages/grey-schemas/src/pricing/index.ts new file mode 100644 index 0000000..9f5f237 --- /dev/null +++ b/packages/grey-schemas/src/pricing/index.ts @@ -0,0 +1,10 @@ +// @grey/schemas/pricing — computeClass + canonical pricing barrel (E1-A, Invariant #30/#31). +export type { ComputeClass, OfferingPricing, Channel } from './types'; +export { + PRICING_TABLE, + NETWORK_MULTIPLIER, + computeClassFor, + networkMultiplierFor, + canonicalUsdFor, + resolvePriceUsd, +} from './table'; diff --git a/packages/grey-schemas/src/pricing/table.ts b/packages/grey-schemas/src/pricing/table.ts new file mode 100644 index 0000000..21e99ce --- /dev/null +++ b/packages/grey-schemas/src/pricing/table.ts @@ -0,0 +1,64 @@ +// @grey/schemas/pricing — the canonical price + computeClass table (E1-A). Supersedes +// adapters/x402-middleware/src/prices.ts's PRICE_TABLE as the single source (Invariant #31); +// that file now derives its (byte-identical) output from here instead of holding literals. +// +// Canonicalizes EXISTING values as-is (E1-A directive): no repricing on this phase. The 7 priced +// values below are carried over unchanged from the prior PRICE_TABLE. `daily_greenlight_list` and +// `scam_alert_feed` have no existing canonical price — canonicalUsd is `null`, flagged here for +// Desktop to source before merge, not invented. +import type { OfferingSlug } from '../responses/types'; +import type { Channel, ComputeClass, OfferingPricing } from './types'; + +export const PRICING_TABLE: Record = { + // LIVE_ALLOWED — resolve through cacheOrLive on a cache miss (grey-core/src/handlers/index.ts). + legitimacy_scan: { slug: 'legitimacy_scan', canonicalUsd: 0.25, computeClass: 'LIVE_ALLOWED' }, + verify_whitepaper: { slug: 'verify_whitepaper', canonicalUsd: 1.5, computeClass: 'LIVE_ALLOWED' }, + verify_full_tech: { slug: 'verify_full_tech', canonicalUsd: 3.0, computeClass: 'LIVE_ALLOWED' }, + claim_extraction: { slug: 'claim_extraction', canonicalUsd: 0.75, computeClass: 'LIVE_ALLOWED' }, + + // CACHE_ONLY — structurally never reach cacheOrLive today (no call site passes them). + claim_history: { slug: 'claim_history', canonicalUsd: 0.25, computeClass: 'CACHE_ONLY' }, + quick_protocol_facts: { + slug: 'quick_protocol_facts', + canonicalUsd: 0.3, + computeClass: 'CACHE_ONLY', + }, + daily_tech_brief: { slug: 'daily_tech_brief', canonicalUsd: 8.0, computeClass: 'CACHE_ONLY' }, + // UNPRICED — flagged for Desktop (E1-A directive); do not invent a number. + daily_greenlight_list: { + slug: 'daily_greenlight_list', + canonicalUsd: null, + computeClass: 'CACHE_ONLY', + }, + scam_alert_feed: { slug: 'scam_alert_feed', canonicalUsd: null, computeClass: 'CACHE_ONLY' }, +}; + +/** Spec §2.3: x402/Base and Virtuals ACP are both grandfathered at 1.00× — no repricing. */ +export const NETWORK_MULTIPLIER: Record = { + x402: 1.0, + acp: 1.0, +}; + +export function computeClassFor(slug: OfferingSlug): ComputeClass { + return PRICING_TABLE[slug].computeClass; +} + +export function networkMultiplierFor(channel: Channel): number { + return NETWORK_MULTIPLIER[channel]; +} + +/** Canonical USD price for a slug. Throws if unpriced (fail-closed — never silently 0/NaN). */ +export function canonicalUsdFor(slug: OfferingSlug): number { + const v = PRICING_TABLE[slug].canonicalUsd; + if (v === null) { + throw new Error( + `pricing: "${slug}" has no canonical price yet (flagged for Desktop, see E1-A PR)`, + ); + } + return v; +} + +/** Resolved USD price for a slug on a given channel: canonicalUsd × networkMultiplier. */ +export function resolvePriceUsd(slug: OfferingSlug, channel: Channel): number { + return canonicalUsdFor(slug) * networkMultiplierFor(channel); +} diff --git a/packages/grey-schemas/src/pricing/types.ts b/packages/grey-schemas/src/pricing/types.ts new file mode 100644 index 0000000..a41e6fc --- /dev/null +++ b/packages/grey-schemas/src/pricing/types.ts @@ -0,0 +1,22 @@ +// @grey/schemas/pricing — computeClass + canonical pricing types (E1-A, Invariant #30/#31). +import type { OfferingSlug } from '../responses/types'; + +/** + * Anti-dilution class (spec §2.2, Invariant #30). CACHE_ONLY offerings may never trigger live + * compute, including on a paid retry — enforced at the `cacheOrLive` boundary in @grey/core. + * No offering currently carries LIVE_PRIORITY (no premium-queue variant exists yet). + */ +export type ComputeClass = 'CACHE_ONLY' | 'LIVE_ALLOWED' | 'LIVE_PRIORITY'; + +/** + * One canonical USD price per offering (spec §2.3, Invariant #31) — channel-agnostic. `null` + * means no canonical price has been sourced yet (flagged for Desktop, not a value to invent). + */ +export interface OfferingPricing { + readonly slug: OfferingSlug; + readonly canonicalUsd: number | null; + readonly computeClass: ComputeClass; +} + +/** A channel this canonical price is realised on. Grows with each expansion (E2 Kite, E3 Olas, ...). */ +export type Channel = 'x402' | 'acp'; diff --git a/packages/grey-schemas/test/pricing.test.ts b/packages/grey-schemas/test/pricing.test.ts new file mode 100644 index 0000000..e382a01 --- /dev/null +++ b/packages/grey-schemas/test/pricing.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from 'vitest'; +import type { OfferingSlug } from '../src/responses/types'; +import { + PRICING_TABLE, + NETWORK_MULTIPLIER, + computeClassFor, + networkMultiplierFor, + canonicalUsdFor, + resolvePriceUsd, +} from '../src/pricing'; + +const ALL_SLUGS: OfferingSlug[] = [ + 'legitimacy_scan', + 'verify_whitepaper', + 'verify_full_tech', + 'claim_extraction', + 'claim_history', + 'quick_protocol_facts', + 'daily_tech_brief', + 'daily_greenlight_list', + 'scam_alert_feed', +]; + +const LIVE_ALLOWED: OfferingSlug[] = [ + 'legitimacy_scan', + 'verify_whitepaper', + 'verify_full_tech', + 'claim_extraction', +]; + +const CACHE_ONLY: OfferingSlug[] = [ + 'claim_history', + 'quick_protocol_facts', + 'daily_tech_brief', + 'daily_greenlight_list', + 'scam_alert_feed', +]; + +const UNPRICED: OfferingSlug[] = ['daily_greenlight_list', 'scam_alert_feed']; + +describe('pricing — computeClass + canonical table (E1-A, Invariant #30/#31)', () => { + it('classifies all 9 offerings, matching cacheOrLive reachability', () => { + expect(Object.keys(PRICING_TABLE).sort()).toEqual([...ALL_SLUGS].sort()); + for (const slug of LIVE_ALLOWED) expect(computeClassFor(slug)).toBe('LIVE_ALLOWED'); + for (const slug of CACHE_ONLY) expect(computeClassFor(slug)).toBe('CACHE_ONLY'); + }); + + it('no offering is LIVE_PRIORITY yet (no premium-queue variant exists)', () => { + for (const slug of ALL_SLUGS) expect(computeClassFor(slug)).not.toBe('LIVE_PRIORITY'); + }); + + it('canonicalizes the 7 existing prices as-is (no repricing on this phase)', () => { + const expected: Record = { + legitimacy_scan: 0.25, + verify_whitepaper: 1.5, + verify_full_tech: 3.0, + claim_extraction: 0.75, + claim_history: 0.25, + quick_protocol_facts: 0.3, + daily_tech_brief: 8.0, + }; + for (const [slug, usd] of Object.entries(expected)) { + expect(canonicalUsdFor(slug as OfferingSlug)).toBe(usd); + } + }); + + it('leaves the 2 unpriced offerings flagged (null), not invented', () => { + for (const slug of UNPRICED) { + expect(PRICING_TABLE[slug].canonicalUsd).toBeNull(); + expect(() => canonicalUsdFor(slug)).toThrow(/no canonical price/); + } + }); + + it('networkMultiplier resolves to 1.00 for both live channels today (Invariant #31)', () => { + expect(NETWORK_MULTIPLIER.x402).toBe(1.0); + expect(NETWORK_MULTIPLIER.acp).toBe(1.0); + expect(networkMultiplierFor('x402')).toBe(1.0); + expect(networkMultiplierFor('acp')).toBe(1.0); + }); + + it('resolvePriceUsd = canonicalUsd × networkMultiplier (identity at 1.00×)', () => { + expect(resolvePriceUsd('legitimacy_scan', 'x402')).toBe(0.25); + expect(resolvePriceUsd('legitimacy_scan', 'acp')).toBe(0.25); + expect(resolvePriceUsd('daily_tech_brief', 'x402')).toBe(8.0); + }); + + it('resolvePriceUsd fails closed for an unpriced offering', () => { + expect(() => resolvePriceUsd('scam_alert_feed', 'x402')).toThrow(/no canonical price/); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d23d8ca..e918de7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@grey/pipeline': specifier: workspace:* version: link:../../packages/grey-pipeline + '@grey/schemas': + specifier: workspace:* + version: link:../../packages/grey-schemas '@grey/x402-middleware': specifier: workspace:* version: link:../x402-middleware @@ -78,6 +81,9 @@ importers: adapters/x402-middleware: dependencies: + '@grey/schemas': + specifier: workspace:* + version: link:../../packages/grey-schemas fastify: specifier: ^5.8.5 version: 5.8.5 diff --git a/vitest.config.ts b/vitest.config.ts index 1d4c987..ad8ceec 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -30,6 +30,7 @@ export default defineConfig({ { find: /^@grey\/schemas\/requests$/, replacement: src('./packages/grey-schemas/src/requests/index.ts') }, { find: /^@grey\/schemas\/envelope$/, replacement: src('./packages/grey-schemas/src/envelope/index.ts') }, { find: /^@grey\/schemas\/validators$/, replacement: src('./packages/grey-schemas/src/validators/index.ts') }, + { find: /^@grey\/schemas\/pricing$/, replacement: src('./packages/grey-schemas/src/pricing/index.ts') }, { find: /^@grey\/pipeline$/, replacement: src('./packages/grey-pipeline/src/index.ts') }, { find: /^@grey\/x402-middleware$/, replacement: src('./adapters/x402-middleware/src/index.ts') }, ],