alexpaynex
a95fd76ebd
task/32: Event moderation queue + Timmy AI review
...
## What was built
Full moderation pipeline: relay_event_queue table, strfry inject helper,
ModerationService with Claude haiku review, policy tier routing, 30s poll loop,
admin approve/reject/list endpoints.
## DB schema (`lib/db/src/schema/relay-event-queue.ts`)
relay_event_queue: event_id (PK), pubkey (FK → nostr_identities), kind,
raw_event (text JSON), status (pending/approved/rejected/auto_approved),
reviewed_by (timmy_ai/admin/null), review_reason, created_at, decided_at.
Exported from schema/index.ts. Pushed via pnpm run push.
## strfry HTTP client (`artifacts/api-server/src/lib/strfry.ts`)
injectEvent(rawEventJson) — POST {STRFRY_URL}/import (NDJSON).
STRFRY_URL defaults to "http://strfry:7777 " (Docker internal network).
5s timeout; graceful failure in dev when strfry not running; never throws.
## ModerationService (`artifacts/api-server/src/lib/moderation.ts`)
- enqueue(event) — insert pending row; idempotent onConflictDoNothing
- autoReview(eventId) — Claude haiku prompt: approve or flag. On flag, marks
reviewedBy=timmy_ai and leaves pending for admin. On approve, calls decide().
- decide(eventId, status, reason, reviewedBy) — updates DB + calls injectEvent
- processPending(limit=10) — batch poll: auto-review up to limit pending events
- Stub mode: auto-approves all events when Anthropic key absent
## Policy endpoint update (`artifacts/api-server/src/routes/relay.ts`)
Tier routing in evaluatePolicy:
read/none → reject (unchanged)
write + elite tier → injectEvent + accept (elite bypass; shadowReject if inject fails)
write + non-elite → enqueue + shadowReject (held for moderation)
Imports db/nostrIdentities directly for tier check. Both inject and enqueue errors
are fail-closed (reject vs shadowReject respectively).
## Background poll loop (`artifacts/api-server/src/index.ts`)
setInterval every 30s calling moderationService.processPending(10).
Interval configurable via MODERATION_POLL_MS env var.
Errors caught per-event; poll loop never crashes the server.
## Admin queue routes (`artifacts/api-server/src/routes/admin-relay-queue.ts`)
ADMIN_SECRET Bearer auth (same pattern as admin-relay.ts).
GET /api/admin/relay/queue?status=... — list all / by status
POST /api/admin/relay/queue/:eventId/approve — approve + inject into strfry
POST /api/admin/relay/queue/:eventId/reject — reject (no inject)
409 on duplicate decisions. Registered in routes/index.ts.
## Smoke tests (all pass)
Unknown → reject ✓; elite → shadowReject (strfry unavailable in dev) ✓;
non-elite write → shadowReject + pending in queue ✓; admin approve → approved ✓;
moderation poll loop started ✓; TypeScript 0 errors.
2026-03-19 20:35:39 +00:00
alexpaynex
31a843a829
task/31: Relay account whitelist + trust-gated access (v2 — code review fixes)
...
## What was built
Full relay access control: relay_accounts table, RelayAccountService,
trust hook, live policy enforcement, admin CRUD API, elite startup seed.
## DB schema (`lib/db/src/schema/relay-accounts.ts`)
relay_accounts table: pubkey (PK, FK nostr_identities ON DELETE CASCADE),
access_level (none/read/write), granted_by (text), granted_at, revoked_at, notes.
Exported from lib/db/src/schema/index.ts. Pushed via pnpm run push.
## RelayAccountService (`artifacts/api-server/src/lib/relay-accounts.ts`)
- getAccess(pubkey) → RelayAccessLevel (none if missing or revoked)
- grant(pubkey, level, reason, grantedBy) — upsert; creates nostr_identity FK
- revoke(pubkey, reason) — sets revokedAt, accessLevel→none, grantedBy→"manual-revoked"
The "manual-revoked" marker prevents syncFromTrustTier from auto-reinstating.
Only explicit admin grant() can restore access after revocation.
- syncFromTrustTier(pubkey) — fetches tier from DB internally (no tier param to
avoid caller drift). Respects: manual-revoked (skip), manual active (upgrade only),
auto-tier (full sync). Never auto-reinstates revoked accounts.
- seedElite(pubkey, notes) — upserts nostr_identities with tier="elite" +
trustScore=200, then grants relay write access as a permanent manual grant.
Called at startup for Timmy's own pubkey.
- list(opts) — returns all accounts, filtered by activeOnly if requested.
- Tier→access: new=none, established/trusted/elite=write (env-overridable)
## Trust hook (`artifacts/api-server/src/lib/trust.ts`)
recordSuccess + recordFailure both call syncFromTrustTier(pubkey) after DB write.
Fire-and-forget with catch (trust flow is never blocked by relay errors).
## Policy endpoint (`artifacts/api-server/src/routes/relay.ts`)
evaluatePolicy() async: queries relay_accounts.getAccess(). write→accept,
read/none/missing→reject. DB error→reject (fail-closed).
## Admin routes (`artifacts/api-server/src/routes/admin-relay.ts`)
ADMIN_SECRET Bearer auth (localhost-only fallback in dev; error log in prod).
GET /api/admin/relay/accounts — list all accounts
POST /api/admin/relay/accounts/:pk/grant — grant (level + notes)
POST /api/admin/relay/accounts/:pk/revoke — revoke (sets manual-revoked)
pubkey validation: 64-char lowercase hex only.
## Startup seed (`artifacts/api-server/src/index.ts`)
Resolves pubkey from TIMMY_NOSTR_PUBKEY env first, falls back to
timmyIdentityService.pubkeyHex. Calls seedElite() — idempotent upsert.
Sets nostr_identities.tier="elite" alongside relay write access.
## Smoke test results (all pass)
Timmy accept ✓; unknown reject ✓; grant→accept ✓; revoke→manual-revoked ✓;
revoked stays rejected ✓; TypeScript 0 errors.
2026-03-19 20:26:03 +00:00
alexpaynex
94613019fc
task/31: Relay account whitelist + trust-gated access
...
## What was built
Full relay access control system: relay_accounts table, RelayAccountService,
trust hook integration, live policy enforcement, admin CRUD API, Timmy seed.
## DB change
`lib/db/src/schema/relay-accounts.ts` — new `relay_accounts` table:
pubkey (PK, FK → nostr_identities.pubkey ON DELETE CASCADE),
access_level ("none"|"read"|"write"), granted_by ("manual"|"auto-tier"),
granted_at, revoked_at (nullable), notes. Pushed via `pnpm run push`.
`lib/db/src/schema/index.ts` — exports relay-accounts.
## RelayAccountService (`artifacts/api-server/src/lib/relay-accounts.ts`)
- getAccess(pubkey) → RelayAccessLevel (none if missing or revoked)
- grant(pubkey, level, reason, grantedBy) — upsert; creates nostr_identity FK
- revoke(pubkey, reason) — sets revokedAt, access_level → none
- syncFromTrustTier(pubkey, tier) — auto-promotes by tier; never downgrades manual grants
- list(opts) — returns all accounts, optionally filtered to active
- Tier→access map: new=none, established/trusted/elite=write (env-overridable)
## Trust hook (`artifacts/api-server/src/lib/trust.ts`)
recordSuccess + recordFailure both call syncFromTrustTier after writing tier.
Failure is caught + logged (non-blocking — trust flow never fails on relay error).
## Policy endpoint (`artifacts/api-server/src/routes/relay.ts`)
evaluatePolicy() now async: queries relay_accounts.getAccess(pubkey).
"write" → accept; "read"/"none"/missing → reject with clear message.
DB error → reject with "policy service error" (safe fail-closed).
## Admin routes (`artifacts/api-server/src/routes/admin-relay.ts`)
ADMIN_SECRET Bearer token auth (localhost-only fallback in dev; error log in prod).
GET /api/admin/relay/accounts — list all accounts
POST /api/admin/relay/accounts/:pk/grant — grant (level + notes body)
POST /api/admin/relay/accounts/:pk/revoke — revoke (reason body)
pubkey validation: must be 64-char lowercase hex.
## Startup seed (`artifacts/api-server/src/index.ts`)
On every startup: grants Timmy's own pubkeyHex "write" access ("manual").
Idempotent upsert — safe across restarts.
## Smoke test results (all pass)
- Timmy pubkey → accept ✓; unknown pubkey → reject ✓
- Admin grant → accept ✓; admin revoke → reject ✓; admin list shows accounts ✓
- TypeScript: 0 errors in API server + lib/db
2026-03-19 20:21:12 +00:00
Replit Agent
eb5dcfd48a
task-29: Timmy as economic peer — Nostr identity, zap-out, vouching, engagement
...
1. TimmyIdentityService (artifacts/api-server/src/lib/timmy-identity.ts)
- Loads nsec from TIMMY_NOSTR_NSEC env var at boot (bech32 decode)
- Generates and warns about ephemeral key if env var absent
- sign(EventTemplate) → finalizeEvent() with Timmy's key
- encryptDm(recipientPubkeyHex, plaintext) → NIP-04 nip04.encrypt()
- Logs npub at server startup
2. ZapService (artifacts/api-server/src/lib/zap.ts)
- Constructs NIP-57 zap request event (kind 9734), signs with Timmy's key
- Pays via lnbitsService.payInvoice() if bolt11 provided (stub-mode aware)
- Logs every outbound event to timmy_nostr_events audit table
- maybeZapOnJobComplete() wired in jobs.ts after trustService.recordSuccess()
- Config: ZAP_PCT_DEFAULT (default 0 = disabled), ZAP_MIN_SATS (default 10)
- Only fires for trusted/elite tier partners when ZAP_PCT_DEFAULT > 0
3. Engagement engine (artifacts/api-server/src/lib/engagement.ts)
- Configurable cadence: ENGAGEMENT_INTERVAL_DAYS (default 0 = disabled)
- Queries nostrIdentities for trustScore >= 50 AND lastSeen < threshold
- Generates personalised DM via agentService.chatReply()
- Encrypts as NIP-04 DM (kind 4), signs with Timmy's key
- Logs to timmy_nostr_events; publishes to NOSTR_RELAY_URL if set
- First run delayed 60s after startup to avoid cold-start noise
4. Vouching endpoint (artifacts/api-server/src/routes/identity.ts)
- POST /api/identity/vouch: requires X-Nostr-Token with elite tier
- Verifies optional Nostr event signature from voucher
- Records relationship in nostr_trust_vouches table
- Applies VOUCH_TRUST_BOOST (20 pts) to vouchee's trust score
- GET /api/identity/timmy: public endpoint returning npub + zap count
5. DB schema additions (lib/db/src/schema/)
- timmy_nostr_events: audit log for all outbound Nostr events
- nostr_trust_vouches: voucher/vouchee social graph with boost amount
- Tables created in production DB via drizzle-kit push
6. Frontend identity card (the-matrix/)
- #timmy-id-card: fixed bottom-right widget with Timmy's npub + zap count
- timmy-id.js: initTimmyId() fetches /api/identity/timmy on load
- Npub shortened (npub1xxxx...yyyyyy), click-to-copy with feedback
- Refreshes every 60s to show live zap count
- Wired into main.js on firstInit
2026-03-19 19:27:13 +00:00
e088ca4cd8
feat(integration): WS bridge + Tower + payment panel + E2E test [10/10 PASS] ( #26 )
2026-03-18 21:20:51 -04:00
83a2ec19e2
fix(testkit): macOS compat + fix test 8c ordering ( #24 )
2026-03-18 21:01:13 -04:00
alexpaynex
4dd3c7f692
Show the application's public URL in server logs
...
Log the full public UI URL using the REPLIT_DEV_DOMAIN environment variable.
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: a00ebe7c-c8e0-4118-81aa-ae93770e942f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
2026-03-18 21:02:06 +00:00
agent
c8ed262197
Initial commit
2026-03-13 23:21:55 +00:00