{"slug":"Patty","total":6,"limit":100,"offset":0,"since":null,"commits":[{"hash":"58691c1","date":"2026-07-03 08:08:09 -0700","author":"Steve","subject":"chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)","body":""},{"hash":"da4d191","date":"2026-06-01 16:18:31 -0700","author":"Steve Abrams","subject":"Add deploy.sh (from Grant template, patty-petitions :7460)","body":""},{"hash":"2d7ba5c","date":"2026-06-01 16:17:35 -0700","author":"Steve Abrams","subject":"Add 3 guest login tiers via shared capability layer","body":"Consumes @dw/nextjs-admin-login@0.3.0 createCapabilityRegistry + capabilityGate.\nLogin route now multi-credential (admin + guest/viewer/demo); middleware\nenforces canWrite/canUseAI authoritatively; session returns capabilities;\nAuthProvider exposes them + header tier badge. Server-enforced; guest passwords\nfrom PATTY_GUEST/VIEWER/DEMO_PASSWORD env.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ca02103","date":"2026-05-30 12:37:25 -0700","author":"Steve Abrams","subject":"patty: add /pulse — Petition Pulse public visualization (CNCP item 19)","body":"ECG/heartbeat SVG sweep mirroring Grant's Deadline River aesthetic in\nPatty's violet palette. Each petition is a pulse spike: x = days since\nlaunch (right = newest), y = signature velocity (signatures/day, taller\n= faster growth), color = category (climate / ai_safety / governance /\ncivil_rights / economic_justice / healthcare), glow intensity = progress\ntoward signature goal.\n\nPublic route — no auth (middleware only matches /api/*). 24 representative\nsample petitions baked in so the page demos without backend setup; can be\nswapped for a server-side SELECT against the petitions table when wired.\nMirrors Grant's RiverCanvas component split (page → server, Canvas → client\nisland, Table → static companion). 10-min ISR matching Grant's revalidate.\n\nAll three files (page.tsx + PulseCanvas.tsx + PulseTable.tsx) esbuild-parse\nclean. Fixed a Fragment-without-key issue (used <Fragment key={cat}> in the\nLegend grid instead of <>)."},{"hash":"6065515","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":"244b86c","date":"2026-05-06 10:22:13 -0700","author":"Steve Abrams","subject":"initial scaffold (2026-05-06 overnight session)","body":""}]}