E1 Round 2: Bazaar metadata, evaluation artifacts, trust rung, MCP, cost ledger - #35
Merged
Merged
Conversation
New @grey/schemas/evaluationKit subpath (Invariant #33 single source): branding data authored ONCE per offering, projected into the Bazaar extension shape (discoverable, serviceName, tags, description, inputSchema, outputSchema, iconUrl) with soft-drop validation (printable-ASCII serviceName/tags, absolute-https non-IP/loopback iconUrl) -- a bad field is omitted and recorded in `dropped`, not thrown, matching the spec's own description of Bazaar's indexing behaviour. Wired on every x402 route: challenge.ts's 402 PaymentRequirements now embeds `accepts[].extra.bazaar` from the same projector. New grey-core GET /v1/discovery/services (+/:slug) -- free, ungated -- is the crawlable index; registry-driven off `offeringHandlers` so a later disable-flagged offering (E1-C) is structurally absent without a separate flag to keep in sync. External surface change (unlike E1-A): 402 `extra` gains a `bazaar` key; challenge.test.ts updated for the new shape, not just re-asserted unchanged. EXPANSION-E1-ROUND2-KOV-directive.md sub-unit 1 (was E1-B).
… (sub-unit 2) Evaluation artifacts (ship live): buildEvaluationArtifact() extends the EvaluationKit projection with a schema-valid sample request/response pair per offering (validated against the SAME ajv instances the live routes use, not hand-eyeballed). GET /v1/discovery/services/:slug now returns the full artifact incl. sample; the list route stays lean. Trust rung (BUILT BUT BLOCKED, Forces ruling B-1, Invariant #34): new legitimacy_scan_trust_rung offering, $0.10 CACHE_ONLY, full schema/ codegen/validator/handler citizenship, classified in the canonical pricing table -- but reachable on NO live channel by default: - Deliberately isolated from prices.ts's PAID_SLUG_ORDER/PAID_SLUGS/ PRICE_TABLE (the well-tested 7-slug path stays untouched) -- all trust-rung pricing/challenge/settlement logic lives in a new, self-contained x402-middleware/trustRung.ts, gated by a single-source trustRungEnabled() (explicit 'true' only, mirrors Bion directive-20's autoModeSetting() precedent). - grey-core mounts its route (POST /v1/offerings/legitimacy_scan_trust_rung) and lists it in discovery ONLY when the flag is on; off (default), the route doesn't exist (404, not a gated 200) and it's absent from both discovery endpoints. - offeringHandlers keeps the handler registered unconditionally (harmless in isolation -- Invariant #30's compile-time half already makes it uncallable into cacheOrLive); route mounting and discovery listing are the two places that actually gate reachability, both flag-checked explicitly. - ACP adapter is untouched -- the trust rung was never wired there, which trivially satisfies "unreachable on every channel" for ACP too. Tests assert unreachability by default (route 404s, absent from both discovery endpoints) AND correct behavior when explicitly forced on in tests (402 with the right price, listed, buildServer fails closed if enabled without a preHandler) -- proving the block is a real gate, not dead code. EXPANSION-E1-ROUND2-KOV-directive.md sub-unit 2 (was E1-C).
New POST /v1/mcp: hand-rolled JSON-RPC 2.0 dispatch (initialize, tools/list, tools/call) -- deliberately NOT the @modelcontextprotocol/ sdk package, to avoid a heavy transitive dependency tree on the memory-constrained production VPS (same posture as acp-adapter's dynamic-import SDK loading, sdk.ts). tools/list projects the SAME EvaluationKit source every HTTP route's 402 body and the discovery index use (Invariant #33) -- sub-unit 1 dependency. tools/call reuses the exact verify/settle/decodePaymentHeader functions the HTTP preHandler uses: one payment implementation, two transports. A paid tool without `_meta.x402Payment` gets isError:true carrying the same PaymentRequirements shape as an HTTP 402; resubmitting the same call with payment settles and returns the envelope. The trust rung is never in the MCP tool set regardless of its own disable flag -- B-1 applies uniformly across every surface, not per-channel. Tests assert this by attempting to call it by name directly and confirming a JSON-RPC error, not a CallToolResult. GET /v1/discovery/services now also returns `mcpEndpoint` (spec: "List in Bazaar as MCP") -- one JSON-RPC endpoint for the whole offering set, not a duplicate per-offering listing. PAID/FREE slug lists exported from offerings.ts/resources.ts so this surface reuses the exact same lists rather than re-declaring them. EXPANSION-E1-ROUND2-KOV-directive.md sub-unit 3 (was E1-D).
New grey_two.revenue_events table (append-only, FDQ-52 posture --
INSERT+SELECT only, UPDATE/DELETE/TRUNCATE revoked): one row per
settled payment, channel x offering x revenueUsd. Migration authored
(supabase/migrations/20260730150000_...), NOT applied -- Forces-lane
canonical-path application + applied_migrations.md ledger entry is
outside this diff, matching the repo's established migration-apply
discipline (every prior grey_two migration was psql-applied by
Forces, never by Kov).
grey-core gets its first WRITE repo on HandlerDeps (revenueEvents) --
everything else stays cache-read (M3's posture, unchanged). Written
at every x402-channel settlement point (offerings.ts's 7 normal
routes, trustRung.ts when enabled, mcp.ts's tools/call paid path),
fail-open: a ledger write failure never costs the buyer their
already-paid-for response (try/catch + warn, same posture
throughout). ACP is NOT wired this round -- explicitly out of scope,
consistent with sub-units 2/3's established ACP boundary.
Margin side: computeMarginReport() in grey-pipeline is a pure,
fixture-testable aggregation (mirrors VerificationsRepo.getMonthly
CostSummary's fetch-then-reduce-in-JS convention) -- revenue
attributed per channel x offering (the ledger's real data); cost
attributed per offering only, not per channel, with an explicit
scoping note on why (compute cost is channel-agnostic; a true
channel split needs either end-to-end channel plumbing through
cacheOrLive into the pipeline's persistence layer, or an allocation
methodology -- both judgment calls beyond this pass). Satisfies the
E1->E2 gate's literal wording ("realized margin on LIVE_ALLOWED
offerings", an offering-level metric) without inventing a cost-split
model.
New `pnpm -F @grey/core margin-report [-- --days N]` CLI (mirrors
Bion's `pnpm auto-report` shape) prints revenue/cost/margin per
offering, channel breakdown inline.
EXPANSION-E1-ROUND2-KOV-directive.md sub-unit 4 (was E1-F).
… leak Forces ruling (merge-prep session): daily_greenlight_list and scam_alert_feed are not priced gaps to fill -- they're not being offered yet, period, pending daily-customer usage data. Don't invent a price; mark them not-yet-offered. Task 1: OfferingPricing gets an explicit `enabled: boolean` field. Both slugs are enabled:false (canonicalUsd stays null -- not a value to invent). Table reads as 7 priced + 2 disabled = 9 (the trust rung sits outside this count, gated by its own separate runtime flag, not conflated with this static one). EvaluationKit's `discoverable` now derives from PRICING_TABLE.enabled instead of being hardcoded true -- one field, every listing surface reads it. Task 2: verified all three surfaces, fixed what didn't already follow from the discoverable change: - GET /v1/discovery/services: already correct (existing .filter(kit => kit.discoverable) picked up the change for free). - GET /v1/discovery/services/:slug: WAS NOT checking discoverable at all -- would have 200'd with the full evaluation artifact. Fixed to 404, same as the list. - 402 extra.bazaar: verified structurally unreachable already (these two are free GET routes with no x402PreHandler attached, and isPaidSlug() already excludes them) -- added an explicit slugFromUrl() test rather than leaving it merely assumed. - MCP tools/list + tools/call: MCP_TOOL_SLUGS now filters on isEnabled() -- excludes them from listing AND from the "unknown or unlisted tool" dispatch tools/call uses, so they can't be called by name either. - ACP: confirmed NOT registered there (registerOffering loop is keyed off the same 7-slug PAID_SLUGS that has never included either free resource) -- pre-existing M5 behavior, unaffected by any of this. EXPANSION-E1-MERGE-PREP-KOV-directive.md Tasks 1-2.
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #34 (E1-A). Consolidated E1-B/C/D/F sub-units:
@grey/schemas/evaluationKit, embedded in every 402 response'sextra.bazaarand a newGET /v1/discovery/services(+/:slug). Soft-drop validation (badserviceName/iconUrlis omitted, entry still ships).legitimacy_scan_trust_rung($0.10 CACHE_ONLY) fully built but structurally unreachable by default (Forces ruling B-1) — isolated pricing/routing module, own runtime disable flag, tests proving both the block and that flipping the flag works correctly.POST /v1/mcp(no@modelcontextprotocol/sdk, avoids a heavy dependency tree on the memory-constrained production VPS), reusing the same verify/settle functions as the HTTP gate.revenue_eventstable (migration authored, NOT applied — Forces-lane like every prior grey_two migration), written at every x402 settlement point,pnpm margin-reportCLI.daily_greenlight_list/scam_alert_feedmarkedenabled: false(not-yet-offered, per Forces ruling, not a pricing gap) and verified/fixed structurally absent from all three Bazaar-facing surfaces (discovery list+detail, 402extra.bazaar, MCP tools/list+call). Confirmed not registered on ACP (unaffected, pre-existing behavior).Flagged for review, not decided by this diff
supabase/migrations/20260730150000_create_grey_two_revenue_events.sqlis authored but not applied — needs Forces-lanepsqlapply + anapplied_migrations.mdledger entry.Test plan
pnpm build && pnpm test && pnpm typecheck && pnpm lintgreen across all 7 workspace packages (28/28 tasks)evaluationKit.test.ts,evaluationKit.samples.test.ts,discovery.test.ts,trustRung.test.ts(both packages),mcp.test.ts,revenueLedger.test.ts,marginReport.test.tsEXPANSION-E1-ROUND2-KOV-directive.md, EXPANSION-E1-MERGE-PREP-KOV-directive.md.
🤖 Generated with Claude Code