{"slug":"Letsbegin","total":21,"limit":100,"offset":0,"since":null,"commits":[{"hash":"db94bc1","date":"2026-09-22 11:46:44 -0700","author":"Steve Abrams","subject":"HMAC-sign the local letsbegin_session cookie (close forgeable-session bypass)","body":"Same class as TK-11776 #4/#1: the local session was an UNSIGNED base64(JSON) cookie that\nmiddleware.ts and /api/auth/session decoded and trusted on `authenticated` alone, so\nbtoa('{\"authenticated\":true,\"loginTime\":<now>}') was a valid session on every gated route.\n\nNew lib/session-token.ts signs the JSON payload with HMAC-SHA256 under DW_SESSION_SECRET\n(envelope <payloadB64>.<sigB64>, constant-time crypto.subtle.verify, fails closed on unset\nsecret). login mints a signed token; middleware + /api/auth/session now VERIFY the signature\nbefore trusting `authenticated` (+ the existing 30-day age check). Kept separate from the\nlockstep dw-central-session.ts (that is the DW Central username:timestamp/24h contract; this\nis the local letsbegin_session JSON/30-day session). +7-assertion negative test.\n\nNOTE: existing logged-in users are bounced to /login once (old cookies now rejected), and\nDW_SESSION_SECRET must be set in Letsbegin's env or login 500s / middleware fails closed.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01DwQJGSbGmK5rDzRsCqvVEc"},{"hash":"9761423","date":"2026-09-20 10:56:42 -0700","author":"Steve Abrams","subject":"security(TK-11776 #4): extend HMAC DW Central session fix to Letsbegin","body":"Letsbegin/middleware.ts carried the identical forgeable verifyDWCentralSession\n(base64 user:ts, no signature). Now imports the shared @/lib/dw-central-session (byte-\nidentical to the other two apps) and awaits verifyDWCentralSession(token,\nDW_SESSION_SECRET). middleware() made async for the await. No legacy-unsigned accept\npath; fails closed when secret unset.\n\n- lib/dw-central-session.ts: shared HMAC signer+verifier (Web Crypto, Next 16 Edge-safe).\n- __tests__/dw-central-session.test.mjs: node-run negative proof, 8/8 (forged REJECTED,\n  signed ACCEPTED, expired REJECTED, wrong-secret/tampered/no-secret rejected;\n  detector-sanity: OLD logic ACCEPTS the forgery).\n\nDeploy (pm2 letsbegin :7300) + secret provisioning stay Steve-gated (go-live memo appended).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01A3PFQP7b2cFzSLAN6gB4ZB"},{"hash":"8bf96b1","date":"2026-09-10 17:52:31 -0700","author":"Steve Abrams","subject":"governance(TK-11370): make live-by-default Shopify writers dry-run by default","body":"Flip `DRY = argv.includes('--dry')` -> `DRY = !argv.includes('--apply')` so a\nzero-argument run no longer fires customer-facing Shopify writes.\n\nBackward compatible: `--dry` still means dry; `--apply` now required to write.\nOnly zero-arg invocations change behaviour, and every candidate was checked\nagainst its launchd/cron/pm2/wrapper invocation sites first (4 scripts that a\ncadence job runs flagless were EXCLUDED so this cannot silently disable them).\n\nVerified: node --check/py_compile 87/87 with a positive control proving the\nchecker rejects an injected fault; argv truth-table negative test 5/5.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01L53y4oP2itY3qeJ8WQgMzc"},{"hash":"24d29e1","date":"2026-07-16 09:59:11 -0700","author":"Steve Abrams","subject":"Strip hardcoded Shopify API tokens from source (env-first); no rotation/deploy — response to Shopify exposed-credentials notice","body":""},{"hash":"2c41f83","date":"2026-07-03 08:08:07 -0700","author":"Steve","subject":"chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)","body":""},{"hash":"76bceba","date":"2026-06-21 18:53:04 -0700","author":"Steve Abrams","subject":"auto-save: 2026-06-21T18:53:00 (9 files) — rw-fix-rollprice.js rw-price-group-slice16.js rw-price-group-slice17.js rw-price-group-slice19.js rw-price-group-slice2.js","body":""},{"hash":"e1f7121","date":"2026-06-12 07:57:05 -0700","author":"Steve Abrams","subject":"schumacher gallery dedup tooling: visual (md5+phash) audit + keep-first cleanup engine (cleaned 1498 dup images across 161 live products)","body":""},{"hash":"19a2714","date":"2026-06-11 23:14:01 -0700","author":"Steve Abrams","subject":"codehealth: add sandbox guards to 3 env-default Shopify writers; generalize .gitignore for transient artifacts; untrack generated cohort dump","body":"- enrich-write.js / rw-sku-backfill.js / cohort-tag-cleanup.js read SHOPIFY_STORE\n  from env (default sandbox) but lacked the hard sandbox-only guard the sibling\n  scripts (rw-cost-write, rw-material-options, rw-recrawl-backfill, slices) already\n  carry. Added the same guard so a stray SHOPIFY_STORE override cannot point a write\n  run at a live store.\n- .gitignore: replace per-file transient entries with *.run.log / *.progress.jsonl /\n  *.jsonl / .producer-* globs.\n- git rm --cached color-family-cohort.jsonl (generated cohort data dump, was tracked).\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"1c651fe","date":"2026-06-11 15:17:20 -0700","author":"Steve Abrams","subject":"Add rw-recrawl-backfill.js: set-based DB update + idempotent sandbox Shopify variant-sku/metafield backfill for the 94 re-crawled RW murals (94/94, 0 failures)","body":"Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"7f50750","date":"2026-06-11 15:11:02 -0700","author":"Steve Abrams","subject":"Add rw-catalog-recrawl.js: feed-first RW re-crawl into vendor_catalog to recover real R##### skus for the 94 unmatched mural cohort + build backfill match table","body":"Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"df2dbbf","date":"2026-06-11 15:06:27 -0700","author":"Steve Abrams","subject":"Add 3-material-options priced metafield (Standard/P&S/Commercial, per-m2 retail+cost) on 1000 RW murals","body":""},{"hash":"3c0c7fa","date":"2026-06-11 14:55:52 -0700","author":"Steve Abrams","subject":"Backfill SKUs (mfr_sku/dw_sku/variant sku + rw url metafield) on RW cohort via pattern+color rematch","body":"Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"62605f8","date":"2026-06-11 14:44:56 -0700","author":"Steve Abrams","subject":"Write C3 per-m2 cost ($44.50) metafield on 1000 Rebel Walls murals (portal-confirmed Net)","body":""},{"hash":"857b11f","date":"2026-06-11 13:48:48 -0700","author":"Steve Abrams","subject":"Class-wide tag/entity cleanup on 1003-product cohort (decode &, strip color-name tags, lowercase+dedupe, lemma map)","body":""},{"hash":"9b884d7","date":"2026-06-11 13:41:39 -0700","author":"Steve Abrams","subject":"Add color_family controlled-vocab metafield from vendor motif color (1003 cohort)","body":""},{"hash":"f5b11c2","date":"2026-06-11 11:36:46 -0700","author":"Steve Abrams","subject":"Add Claude-vision enrich-write helper (color+specs+tags→metafields, bypass Gemini cap)","body":""},{"hash":"a780b03","date":"2026-06-11 11:19:03 -0700","author":"Steve Abrams","subject":"Add last-20-day Full Monty backfill runner (1003 items, metafields+specs+color+tags; no rooms)","body":"Resumable cohort runner adapted from glitter_full_monty.js: migrates global->custom\nmetafields, Gemini color analysis, title normalization (vendor-aware, multi-pipe safe),\nimage alt tags, then stamps full_monty_at/fm_metafields_verified/fm_specs_at/fm_tags_at\nin shopify_products. Validated on 3 (Cole&Son/DW/Glitter): Shopify writes land cleanly.\nBLOCKED on full 1000-run: Gemini key over monthly spend cap (HTTP 429 RESOURCE_EXHAUSTED).\npm2 wrapper run-last20-fullmonty.sh sources only the 2 needed keys.\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>"},{"hash":"55eb229","date":"2026-05-30 09:32:50 -0700","author":"steve","subject":"security: strip hardcoded dw_admin DSN password -> env-first. No rotation/deploy.","body":""},{"hash":"cecf2c3","date":"2026-05-13 08:40:26 -0700","author":"Steve","subject":"letsbegin: shared cost-tracker helper + hook 7 Gemini routes","body":"New: lib/cost-track.ts — best-effort logGemini wrapper that fails soft when\n~/.claude/skills/cost-tracker isn't present (e.g. Kamatera deploy).\n\nHooked all 7 Gemini-using API routes via the shared helper:\n  /api/url-info, /api/prd/clarify, /api/prd/generate, /api/ralph/convert,\n  /api/claude/suggest, /api/processes/analyze, /api/processes/fix\n\nEvery Gemini call now logs token usage to ~/.claude/cost-ledger.jsonl\nwith app='letsbegin' / note=<route-name>."},{"hash":"7a38d32","date":"2026-05-13 08:36:28 -0700","author":"Steve","subject":"url-info: hook Gemini call into cost-tracker (best-effort)","body":"require()-based dynamic load with try/catch so Next.js Edge runtime falls\nthrough cleanly if cost-tracker isn't present (e.g. on Kamatera deploy).\n\nPattern to replicate across the other 7 Gemini-using routes:\n  /api/prd/clarify, /api/prd/generate, /api/ralph/convert,\n  /api/claude/suggest, /api/processes/analyze, /api/processes/fix,\n  /api/projects/scan"},{"hash":"5987dec","date":"2026-05-06 10:25:05 -0700","author":"Steve Abrams","subject":"initial scaffold (gitify-all 2026-05-06)","body":""}]}