9 Commits

Author SHA1 Message Date
3bd67c7869 [claude] Relay account whitelist + trust-gated access (#47) (#90)
Co-authored-by: Claude (Opus 4.6) <claude@hermes.local>
Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
2026-03-23 20:20:48 +00:00
4ea59f7198 [claude] Context injection — pass conversation history to work model (#39) (#78) 2026-03-23 01:51:22 +00:00
5954a2fdc0 [claude] Fix moderation infinite re-review loop (#27) (#71) 2026-03-23 00:44:57 +00:00
alexpaynex
8a81918226 task/29: fix vouch idempotency + replay guard — unique constraints + DB push
## Code review round 2 issues resolved

### Vouch replay / duplicate boost vulnerability — FIXED
- `nostr-trust-vouches.ts` schema: added `eventId` column + two unique guards:
  1. `UNIQUE(event_id)` — same signed event cannot be replayed for any pair
  2. `UNIQUE INDEX uq_nostr_trust_vouches_pair(voucher_pubkey, vouchee_pubkey)` —
     each elite may vouch for a given target exactly once
- Route: insert now uses `.onConflictDoNothing().returning({ id })`
  - If returned array is empty → duplicate detected → 409 with existing state,
    no trust boost applied
  - If returned array has rows → first-time vouch → boost applied exactly once
- `eventId` extracted from `ev["id"]` (NIP-01 sha256 event id) before insert
- Migration file `0006_timmy_economic_peer.sql` updated to include both
  unique constraints (UNIQUE + CREATE UNIQUE INDEX)
- Schema pushed to production — all three indexes confirmed in DB:
  `nostr_trust_vouches_event_id_unique`, `uq_nostr_trust_vouches_pair`, `pkey`

### Previously fixed (round 1)
- LNURL-pay resolution in ZapService (full NIP-57 §4 flow)
- Vouch event made required with p-tag vouchee binding
- DB migration file 0006 created for both new tables + lightning_address column
- GET /identity/timmy now returns relayUrl field

### Verified
- TypeScript: 0 errors (tsc --noEmit clean)
- DB: all constraints confirmed live in production
- API: /identity/timmy 200, /identity/challenge nonce, /identity/vouch 401/400
2026-03-19 19:51:50 +00:00
alexpaynex
33b47f8682 task/29: fix code review findings — LNURL zap, vouch binding, migration SQL
## Code review issues resolved

### 1. Zap-out: real LNURL-pay resolution (was: log-only when no bolt11)
- `zap.ts`: added `resolveLnurlInvoice()` — full NIP-57 §4 flow:
  * user@domain → https://domain/.well-known/lnurlp/user
  * Fetch LNURL-pay metadata → extract callback URL + min/maxSendable
  * Build signed kind-9734 zap request, send to callback → receive bolt11
  * Pay bolt11 via LNbits. Log event regardless of payment outcome.
- `nostr-identities.ts`: added `lightningAddress` column (nullable TEXT)
- `identity.ts /verify`: extracts `["lud16", "user@domain.com"]` tag from
  signed event and stores it so ZapService can resolve future invoices
- `maybeZapOnJobComplete()` now triggers real payment when lightningAddress
  is stored; logs a warning and skips payment if not available

### 2. Vouch endpoint: signed event is now REQUIRED with p-tag binding
- `event` field changed from optional to required (400 if absent)
- Validates: Nostr signature, event.pubkey matches authenticated voucher
- NEW: event MUST contain a `["p", voucheePubkey]` tag — proves the voucher
  intentionally named the vouchee in their signed event (co-signature binding)

### 3. DB migration file added
- `lib/db/migrations/0006_timmy_economic_peer.sql` — covers:
  * CREATE TABLE IF NOT EXISTS timmy_nostr_events (with indexes)
  * CREATE TABLE IF NOT EXISTS nostr_trust_vouches (with indexes)
  * ALTER TABLE nostr_identities ADD COLUMN IF NOT EXISTS lightning_address
- Schema pushed to production: `lightning_address` column confirmed live

### Additional
- `GET /api/identity/timmy` now includes `relayUrl` field (null when unset)
- TypeScript compiles cleanly (tsc --noEmit: 0 errors)
- All smoke tests pass: /timmy 200, /challenge nonce, /vouch 401/400
2026-03-19 19:47:00 +00:00
alexpaynex
26e0d32f5c Task #27: Complete cost-routing + free-tier gate — all critical fixes applied
Fix 1 — Add `estimateRequestCost(request, model)` to PricingService (pricing.ts)
  - Unified: estimateInputTokens + estimateOutputTokens + calculateWorkFeeUsd
  - Replaces duplicated estimation in jobs.ts, sessions.ts, estimate.ts

Fix 2 — Sessions pre-gate: estimate → decide → execute → reconcile
  - freeTierService.decide() runs on ESTIMATED cost BEFORE executeWork()
  - Fixed double-margin: estimateRequestCost already includes infra+margin; convert directly
  - absorbedSats capped at actual cost post-execution (Math.min)

Fix 3 — Correct isFree derivation for partial jobs in advanceJob() (jobs.ts)
  - isFreeExecution = workAmountSats === 0 (not job.freeTier)
  - Partial jobs run paid accounting: actual sats, refund, pool credit, deferred grant

Fix 4 — Defer ALL grant recording to post-work execution (jobs.ts)
  - Fully-free path: removed recordGrant from eval time; now in runWorkInBackground
  - For isFree jobs: absorbCap = actual post-execution cost (calculateActualChargeSats)
  - For partial jobs: grant deferred from invoice creation to after work completes

Fix 5 — Atomic, pool-bounded grant recording with row locking (free-tier.ts)
  - SELECT ... FOR UPDATE locks pool row inside transaction
  - actualAbsorbed = Math.min(absorbSats, poolBalance) — pool can never go negative
  - Daily absorption: SQL CASE expression atomically handles new-day reset
  - Audit log and identity counter both reflect actualAbsorbed, not requested amount
  - If pool is empty at grant time, transaction returns without writing

Fix 6 — Remove fire-and-forget from all recordGrant() call sites
  - All three call sites now use await; failures propagate correctly

Fix 7 — Add migration 0005_free_tier.sql
  - Creates timmy_config, free_tier_grants tables
  - Adds nostr_identities.sats_absorbed_today / absorbed_reset_at columns
  - Adds jobs.free_tier / absorbed_sats columns
  - Adds sessions.nostr_pubkey FK column (for migration-driven deploys)
  - All IF NOT EXISTS — safe to run on already-pushed DBs
2026-03-19 17:02:02 +00:00
alexpaynex
ab2cc06a79 Add session mode for pre-funded request processing
Implement session-based API endpoints for creating, managing, and interacting with pre-funded sessions, including deposit and top-up invoice generation, macaroon authentication, and per-request debiting of compute costs.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 2dc3847e-7186-4a22-9c7e-16cd31bca8d9
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 20:00:24 +00:00
alexpaynex
dfc9ecdc7b Add honest accounting and automatic refund mechanism for completed jobs
Implement honest accounting post-job completion, calculating actual costs, adding margin, and enabling automatic refunds for overpayments via a new endpoint.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: c6386de2-d5f4-47cc-a557-73416f09e118
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/sPDHkg8
Replit-Helium-Checkpoint-Created: true
2026-03-18 19:32:34 +00:00
alexpaynex
e5bdae7159 Task #6: Cost-based work fee pricing with BTC oracle
## New files
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper (ceil, min 1 sat),
  5s abort timeout, fallback to BTC_PRICE_USD_FALLBACK env var (default $100k)
- lib/db/migrations/0002_cost_based_pricing.sql: SQL migration artifact adding 6 new columns
  to jobs table (estimated_cost_usd, margin_pct, btc_price_usd, actual_input_tokens,
  actual_output_tokens, actual_cost_usd); idempotent via ADD COLUMN IF NOT EXISTS

## Modified files
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var overridable),
  DO infra amortisation per request, originator margin %, estimateInputTokens/Output by tier,
  calculateActualCostUsd() for post-work ledger, async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
  workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- lib/db/src/schema/jobs.ts: 6 new real/integer columns; schema pushed to DB
- jobs.ts route: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
  stores estimatedCostUsd/marginPct/btcPriceUsd; post-work stores actualInputTokens/
  actualOutputTokens/actualCostUsd; GET response includes pricingBreakdown and costLedger
  with totalTokens (input + output computed field)
- openapi.yaml: PricingBreakdown + CostLedger schemas (with totalTokens) added
- lib/api-zod/src/generated/api.ts: Regenerated with new schemas
- lib/api-client-react/src/generated/api.schemas.ts: Regenerated (PricingBreakdown, CostLedger)
- replit.md: 17 new env vars documented in cost-based pricing section
2026-03-18 19:25:06 +00:00