Skip to content

E1 Round 2 → main: Bazaar metadata, trust rung, MCP, cost ledger - #36

Merged
Mayakovsky merged 6 commits into
mainfrom
expansion-e1-a-computeclass
Aug 2, 2026
Merged

E1 Round 2 → main: Bazaar metadata, trust rung, MCP, cost ledger#36
Mayakovsky merged 6 commits into
mainfrom
expansion-e1-a-computeclass

Conversation

@Mayakovsky

Copy link
Copy Markdown
Owner

Summary

Follow-up to #34/#35 — lands Round 2 into main. #34 (E1-A) already merged into main; #35 (Round 2) merged into the expansion-e1-a-computeclass branch itself (stacked), so this PR carries those same 6 commits forward into main. No new code beyond what #35 already contained.

  • EvaluationKit + Bazaar discovery metadata (GET /v1/discovery/services(+/:slug), extra.bazaar on every 402).
  • Evaluation artifacts + the $0.10 trust rung (built, structurally unreachable by default — Forces ruling B-1).
  • MCP tool surface (POST /v1/mcp, hand-rolled JSON-RPC, no SDK dependency).
  • Cost ledger + margin dashboard (revenue_events table — migration authored, not applied; pnpm margin-report CLI).
  • Merge-prep fix: daily_greenlight_list/scam_alert_feed marked not-yet-offered and verified absent from all three Bazaar-facing surfaces.

Full detail in #35's description.

Test plan

EXPANSION-E1-ROUND2-KOV-directive.md, EXPANSION-E1-MERGE-PREP-KOV-directive.md.

🤖 Generated with Claude Code

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.
E1 Round 2: Bazaar metadata, evaluation artifacts, trust rung, MCP, cost ledger
@Mayakovsky
Mayakovsky merged commit c7b8376 into main Aug 2, 2026
3 checks passed
@Mayakovsky
Mayakovsky deleted the expansion-e1-a-computeclass branch August 2, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant