Skip to content

feat: M2 quote gates + M3 buzz loop - #3

Merged
lgalabru merged 5 commits into
mainfrom
feat/m3-buzz-loop
Aug 1, 2026
Merged

feat: M2 quote gates + M3 buzz loop#3
lgalabru merged 5 commits into
mainfrom
feat/m3-buzz-loop

Conversation

@lgalabru

@lgalabru lgalabru commented Aug 1, 2026

Copy link
Copy Markdown
Member

M2 + M3, rebased on main after #2 (M1) merged — 5 commits, tree identical to the previously verified 00a52fa.

M2 — quote flow + gate engine

  • Quote/GatePolicy/GateSpec wire types in studio-types, schemas generated via just schemas (+ drift test)
  • Pure gate engine + quote::issue in studio-core; POST/GET /api/v1/rfqs/{id}/quote (bearer, fail-closed, 409 singleton); expiry sweep
  • just install scarce + README quickstart; figment config (defaults ← YAML ← SCARCED_* env), scarced --config

M3 — buzz loop

  • studio-buzz BuzzPort (buzz-sdk + buzz-ws-client pinned to block/buzz@22be8bb35, NIP-42 + NIP-OA auth)
  • POST /api/v1/rfqs/{id}/quote/accept (free, once, refuses lapsed; migration 0005)
  • scarced mirrors demand/quote/accepted beats to studio-ops; accept creates a private workroom proj-<slug>-<shortid>, auto-adds the buyer, posts contract-start; channel-create event id stored as FUNDED→WORKROOM_ACTIVE evidence
  • ring crypto provider installed explicitly at every bin entry

Verified

just ci — 122 tests, clippy clean; live loop on wss://scarce.communities.buzz.xyz (workroom proj-helius-webhook-replay-debugger-2651b127, buyer auto-added). Rebase is tree-identical, so results carry over; CI re-running on the new head.

🤖 Generated with Claude Code

…val, migrations 0003/0004

Rebuilt from the pre-refactor M2 work (0ff2e8f) on the studio-types
conventions:

studio-types grows the quote wire contract — NewQuote/MilestoneSpec/
PayoutDestination/Split/ChannelParams/Quote/QuoteStatus and GatePolicy/
GateSpec with schemars derives; schemas/quote.json and
schemas/gate-policy.json are now generated (just schemas) and covered by
the drift test. The gate-policy edges map keeps its strict edge-key
patternProperties via a schema_with hook built from ProjectState::ALL, so
the pattern cannot drift from the enum. ProjectState/Edge/EdgePattern move
to studio-types as shared vocabulary. FieldError.field becomes an owned
path (milestones[1].amount). NewRfq/Rfq reserve buyer_signature (recorded,
not verified — archy's RFQ-replay upgrade path), migration 0003.

studio-core keeps the logic: gate engine eval (fail-closed, denial as
first-class outcome, loud operator overrides, escalation windows),
gates_for + commitment_hash as engine fns, and quote::issue(new, rfq_id,
id, now) — the pure issuance path computing the policy commitment hash, so
CLI/MCP surfaces share it; the HTTP handler only supplies uuid and clock.

studio-api: POST /api/v1/rfqs/{id}/quote (studio bearer token, fail-closed
when unconfigured; 401/404/409/422 with field errors) and free
GET /api/v1/rfqs/{id}/quote (status fail-closed against sweep lag);
discovery index updated. studio-store::quotes with the one-quote-per-RFQ
singleton (migration 0004) surfaced as 409; scarced runs the quote expiry
sweep (SCARCED_SWEEP_SECONDS, default 30s).

Verified: just ci green (fmt, clippy -D warnings, 106 tests incl. 18
quote/gate-policy conformance cases against the generated schemas); live
curl demo: 401 no-auth, 201 issue, 409 duplicate, free buyer read,
schemas/gate-policy served.
- justfile: `install` recipe (pay-repo convention) — `just install scarce`
  cargo-installs the scarced binary; extra args forward to cargo install
- README: env var table, self-describing-API walkthrough (index, schema
  discovery, RFQ capture, quote issue/409/free read), milestone status line
- config precedence: defaults <- YAML <- SCARCED_* env (nested keys join
  with `__`, e.g. SCARCED_BUZZ__RELAY_URL); env names unchanged
- new `buzz.relay_url` key — recorded and logged now, consumed by the M3
  orchestrator; scarced.example.yaml points at the scarce community relay
- clap for the binary surface (--config, --version, --help)
- actionable failures: unknown keys named with valid alternatives, missing
  file, zero sweep, non-ws relay URL; empty token still disables (fail-closed)
Shortcut per channel decision: pay.sh intake parked, buzz visibility first.

- studio-buzz: BuzzPort trait + RelayBuzz (buzz-sdk builders + buzz-ws-client
  NIP-42 transport, git-pinned to block/buzz@22be8bb35) + recording mock;
  publishes wait for the relay OK so returned event ids are storable evidence.
  NIP-OA auth tag support for managed-agent identities (buzz.auth_tag)
- POST /api/v1/rfqs/{id}/quote/accept — buyer, free, once; fail-closed vs
  expiry via core pure fn quote::accept + atomic UPDATE guard; ACCEPTED is
  sticky and stands in for FUNDED while payments are stubbed (PLAN §6)
- scarced mirror task: demand/quote/accepted beats -> ops channel; acceptance
  creates proj-<slug>-<shortid> workroom, posts contract-starting there, and
  stores the channel-create event id as FUNDED->WORKROOM_ACTIVE evidence
  (workrooms table, migration 0005 rebuilds quotes for the widened CHECK)
- config: buzz.{private_key,ops_channel,auth_tag}; auth tag env bypasses
  figment's lenient JSON parsing (read raw)

Verified: just ci green (122 tests) + live loop against
wss://scarce.communities.buzz.xyz — see PR body for event ids.
- workrooms (and any BuzzPort channel) are created private — commercial
  terms, members only; the mirror adds the RFQ buyer as member right after
  the evidence row, best-effort with its own loud log
- studio_buzz::pubkey_hex: full bech32 npub decode (fails at the port, not
  the relay); MockBuzz records AddMember; RelayBuzz::set_visibility (9002)
- examples/channel_admin: operator one-offs (set-visibility, add-member)
- install ring as process-level rustls CryptoProvider at every binary entry
  point (same pattern/pin as buzz-cli) — feature unification had been
  picking a provider for scarced by accident and the example panicked
- scarced.example.yaml points at the new private studio-ops (cdc47f86)
@lgalabru
lgalabru force-pushed the feat/m3-buzz-loop branch from 00a52fa to d15c5cc Compare August 1, 2026 17:57
@lgalabru lgalabru changed the title feat: draft buzz loop feat: M2 quote gates + M3 buzz loop Aug 1, 2026
@lgalabru
lgalabru merged commit c18d1ca into main Aug 1, 2026
1 check passed
@lgalabru
lgalabru deleted the feat/m3-buzz-loop branch August 1, 2026 18:20
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