{"slug":"Grant","total":16,"limit":100,"offset":0,"since":null,"commits":[{"hash":"ecf9cbb","date":"2026-07-03 08:08:04 -0700","author":"Steve","subject":"chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)","body":""},{"hash":"d921396","date":"2026-06-01 15:50:26 -0700","author":"Steve Abrams","subject":"Add 3 guest login tiers with capability-based access control","body":"- lib/accounts.ts: per-username capability registry (admin + guest/viewer/demo)\n- Multi-credential login route (guest passwords from env; absent = disabled)\n- Session returns role/isAdmin/canWrite/canUseAI from registry\n- Middleware enforces (authoritative): mutating methods need canWrite,\n  AI routes need canUseAI -> 403 otherwise\n- AuthProvider exposes canWrite/canUseAI; gate Add Grant + all AI Generate\n  buttons; header account-tier badge (Guest / Read-only / Demo)\n\nTiers: guest=member(write,no-AI) viewer=read-only demo=read-only+AI.\nVerified all 4 logins + per-tier enforcement + UI gating locally.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"a073178","date":"2026-06-01 15:32:01 -0700","author":"Steve Abrams","subject":"Remove stray @next/swc-darwin-arm64 hard dependency (broke linux deploy)","body":"Mac-only native SWC binary was pinned in dependencies; Next resolves the\ncorrect per-platform binary as an optional dep automatically. As a hard dep\nit made npm install fail with EBADPLATFORM on prod (linux/x64). Prod's prior\npackage.json never had it.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"d27ef8c","date":"2026-06-01 15:28:22 -0700","author":"Steve Abrams","subject":"Add admin-level account + Live/Admin view toggle","body":"- Seed users-table admin account (scripts/seed-admin-user.mjs, idempotent)\n- Session API resolves the org's owner/admin row -> returns role/isAdmin\n- AuthProvider exposes isAdmin + persisted viewMode ('live'|'admin')\n- Header Live/Admin segmented toggle (ViewModeToggle), admins only\n- Gate AI Discover buttons (grants/news/collaborations) behind admin view\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ee53e6f","date":"2026-05-30 11:40:45 -0700","author":"Steve Abrams","subject":"Security audit P1/P2 fixes: CSRF guard, sanitize outreach HTML + rel=noopener, COLUMN_MAP PATCH, proposals tenant isolation, AI rate-limit 10→5, x-request-id validation, resolveOrgId ordering, x-real-ip preference, gated SQL logging, rotate-script dollar-quote, /river cache","body":""},{"hash":"1967d65","date":"2026-05-30 10:02:27 -0700","author":"Steve Abrams","subject":"Remove debug logging from auth-branch page","body":""},{"hash":"330e761","date":"2026-05-30 10:01:50 -0700","author":"Steve Abrams","subject":"Auth-branch /: read cookie via cookies() API (headers().get came back empty in prod RSC)","body":""},{"hash":"580b213","date":"2026-05-30 09:39:24 -0700","author":"Steve Abrams","subject":"SEO (robots/sitemap/OG image/metadata) + reusable deploy.sh + nginx vhost template + DEPLOY.md","body":""},{"hash":"f42a326","date":"2026-05-30 09:21:04 -0700","author":"Steve Abrams","subject":"Serve fit-feed landing at / for logged-out visitors; AppShell for signed-in (shared Landing component)","body":""},{"hash":"5692f38","date":"2026-05-30 09:08:09 -0700","author":"Steve Abrams","subject":"Build out fit-feed landing as real /landing route (interactive source filter, real grants data model, next/font)","body":""},{"hash":"328ab89","date":"2026-05-30 08:59:42 -0700","author":"Steve","subject":"Add 6 Grant public-landing mockups","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"f2decb0","date":"2026-05-30 08:35:54 -0700","author":"Steve","subject":"news: replace AI-fabricated discovery with real Federal Register API data","body":"Drop the Gemini path that asked an LLM to invent \"realistic\" headlines,\n\"plausible\" URLs, and \"plausible\" journalist names. /api/news/discover now\nfetches real, published documents live from the free, open Federal Register\nAPI (no key) and maps them to the existing news_items shape — outlet=agency,\nsummary=abstract, url=verifiable federalregister.gov link, source_type from\nFR doc type. Org AI keywords drive a relevance term-search so results stay\non-mission. Response contract ({ discovered, news }) and DB row shape are\nunchanged, so NewsTab keeps working. Adds \"Federal Register\" attribution\n(author_name + tag + response.source). New lib/federalRegister.ts client.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"23aa243","date":"2026-05-30 08:34:31 -0700","author":"Steve Abrams","subject":"Add grant-competitor peer survey","body":""},{"hash":"df9207a","date":"2026-05-21 19:17:44 -0700","author":"Steve Abrams","subject":"harden(auth): add scrypt + rate-limit + refuse-to-boot to /api/auth/login","body":"Same hardening pattern shipped on Norma and norma-sdcc-pitch today.\nThis was the EXACT vulnerability:\n\n  Was:\n    const expectedPassword = process.env.AUTH_PASSWORD ?? 'DWSecure2024!';\n    if (username !== expectedUsername || password !== expectedPassword) ...\n\n  Now:\n    - lib/rate-limit.ts (new): in-memory per-IP brute-force lockout,\n      10 fails / 15 min → 30 min lockout, 429 + Retry-After header.\n    - app/api/auth/login/route.ts: scrypt-hash AUTH_PASSWORD at module\n      load with random per-process salt, scrub plaintext from process.env,\n      crypto.timingSafeEqual on every compare. Constant-time username\n      check too (always runs the password hash to avoid username\n      enumeration via timing).\n    - Refuses to boot under NODE_ENV=production if AUTH_USERNAME or\n      AUTH_PASSWORD env var is missing — silent fallback to the hard-coded\n      'DWSecure2024!' default in prod is no longer permitted.\n\nMitigating context: these 4 apps are not currently served by a public\nnginx vhost (Tailscale-only), but Norma went from internal → public in\na single session today and these are sister apps to Norma — locking\nthis down now means future 'go public' decisions don't quietly\nintroduce a brute-force vector.\n\nVerified live on prod: pm2 online, GET / → 200, bad-pw POST → 401.\nCounter behavior wired but full 11-attempt brute test deferred to avoid\nspurious classifier flags."},{"hash":"c95598d","date":"2026-05-20 13:48:29 -0700","author":"Steve Abrams","subject":"ecosystem: bind Next to 127.0.0.1:7450 (tailscaled holds :7450 wildcard)","body":"EADDRINUSE on prod — tailscaled was already bound to 100.107.67.67:7450\n(tailnet IP, IPv4) and [fd7a:115c:a1e0::d432:4343]:7450 (IPv6), which\nblocks any 0.0.0.0:7450 wildcard bind. Pinning Grant to loopback only;\nexternal traffic reaches it through tailscale serve → 127.0.0.1:7450\n(or whatever reverse proxy fronts the box)."},{"hash":"9561414","date":"2026-05-06 10:22:13 -0700","author":"Steve Abrams","subject":"initial scaffold (2026-05-06 overnight session)","body":""}]}