{"slug":"AbramsOS","total":68,"limit":100,"offset":0,"since":null,"commits":[{"hash":"fc3c624","date":"2026-07-13 12:25:39 -0700","author":"Steve","subject":"chore: lint (node --check 21/21) + version bump v0.3.0 (session close)","body":"Overnight life-optimizer build: health (vitals/insights/BP-meds/reminders), savings\n(advisor/coupons/bill-audit/reorder-timing), Assets+RentCast, Neighborhood Watch,\nBiometrics, Plaid+CSV banking, Withings device sync, 38 real Amazon purchases ($8,966.66)."},{"hash":"5671885","date":"2026-07-13 09:56:02 -0700","author":"Steve","subject":"withings: device-vitals auto-sync connector (OAuth2 → weight/BP/HR/SpO2 into Health)","body":"- lib/withings-client.js: Withings Health API OAuth (authUrl/exchange/refresh) + getMeasures mapping (type 1=weight kg→lb, 9/10=BP pair, 11=HR, 54=SpO2); handles refresh-token rotation; graceful no-creds\n- routes/withings.js: /auth/withings → consent → /api/withings/callback stores encrypted token in connector_account (provider=withings) + initial sync; /api/withings/sync re-syncs (dedup via health_reading external_id); reuses vitals.insertReadings; audited medical\n- connectors.ejs: 'Health devices' section — Connect Withings / Sync now / setup-needed\n- server.js wiring + .env WITHINGS_* (gated). One-set-of-creds-from-working"},{"hash":"ba2e47d","date":"2026-07-13 09:49:42 -0700","author":"Steve","subject":"assets: RentCast home-value estimate wiring (one-key-from-working) + all 37 Amazon totals collected","body":"- lib/home-value.js: RentCast AVM lookup by address; graceful no-key/no-address (never fabricates a value); Zillow API retired so RentCast is the free option\n- routes/assets.js: POST /api/assets/:id/estimate → fills current_value + value_source='estimate' + range; valuationConfigured flag\n- views/assets.ejs: 'Estimate' button on property cards (prompts for key if absent)\n- .env: RENTCAST_API_KEY= (gated)\n- DATA: opened remaining 17 older Amazon orders via George → all 37 now have real totals; complete all-time spend $8,966.66 (2022-2026)"},{"hash":"957c215","date":"2026-07-13 01:57:07 -0700","author":"Steve","subject":"docs: savings track done; overnight run complete, loop stopped","body":""},{"hash":"5387c7d","date":"2026-07-13 01:56:43 -0700","author":"Steve","subject":"savings: subscription/bill audit + reorder-timing nudges","body":"- lib/bill-audit.js: category-aware — NEVER flags essentials (tax/rent/utility/loan); insurance/phone/internet get 'shop your rate'; discretionary/subscriptions ALWAYS surface with annual cost ('still using this?'), LLM (gemma3:12b) refines cancel/downgrade/overpriced; rule-based duplicate detector\n- lib/reorder-timing.js: deterministic 'reorder soon' nudges from cadence + last_ordered (Paper towels due-now verified)\n- wired both into /api/savings/run + nightly runner; kind='bill-audit'/'reorder-timing' surface on /savings\n- honest: est_savings only for cancel/downgrade, never for 'review'; verified on synthetic sub then wiped"},{"hash":"a0f0e6a","date":"2026-07-13 01:52:07 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-13T01:52:00 (1 files) — lib/bill-audit.js","body":""},{"hash":"4c949de","date":"2026-07-13 01:23:32 -0700","author":"Steve","subject":"docs: roadmap — health track done; Assets/Neighborhood/Biometrics shipped; savings track next","body":""},{"hash":"5ab1c97","date":"2026-07-13 01:22:58 -0700","author":"Steve","subject":"biometrics: per-person ID/emergency profile for kids + adults (child-ID record + growth BMI)","body":"- 0014_biometrics.sql: person_biometric (1:1 per person) — DOB, sex, height, weight, blood type, eye/hair, marks, allergies, conditions, emergency contact, prints-on-file (child-ID)\n- lib/biometrics.js: exact age + BMI; adult CDC BMI category; kids link to CDC percentile calculator (NO fabricated percentile)\n- routes/biometrics.js upsert-per-person + views/biometrics.ejs profile cards w/ inline edit, child badges\n- nav + wiring. Sensitive PII, audited; works for whole household"},{"hash":"a4f7803","date":"2026-07-13 01:21:56 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-13T01:21:52 (4 files) — lib/ids.js db/migrations/0014_biometrics.sql lib/biometrics.js routes/biometrics.js","body":""},{"hash":"42d431f","date":"2026-07-13 01:18:32 -0700","author":"Steve","subject":"neighborhood: Neighborhood Watch — address+radius, Leaflet map, Ring cameras + Neighbors, Nextdoor/Citizen/crime maps/registries","body":"- lib/neighborhood.js: free OSM Nominatim geocoder (no key) + categorized real deep-link builder (Your Ring first: cameras live-view + Neighbors feed), geo-aware SpotCrime/OSM links\n- routes/neighborhood.js: /neighborhood resolves address from ?asset/?address/first property; /api/neighborhood/geocode\n- views/neighborhood.ejs: address+radius controls, Leaflet map w/ radius circle, property picker, link cards\n- nav + wiring; links to Assets property addresses"},{"hash":"a78d4ae","date":"2026-07-13 01:15:52 -0700","author":"Steve","subject":"assets: net-worth tracker — enter home by address + current value, finance category, details/notes","body":"- 0013_assets.sql: asset table (property/vehicle/account/valuable) w/ address, current_value, category, details, notes, geocode\n- routes/assets.js + views/assets.ejs: /assets dashboard (net worth total + by-category stat row, add form, created date+time chips, per-property 'Watch' link to neighborhood)\n- nav + wiring. Values user-entered (auto-valuation API is a later gated add). Feeds upcoming neighborhood-watch via property address"},{"hash":"8598287","date":"2026-07-13 01:13:32 -0700","author":"Steve","subject":"health: lapsed-habit BP reading reminders (only nudges prior loggers, never new users)","body":"- lib/vitals-reminders.js: fires 'time for a BP check' only if a person logged BP before but not in 14d; deduped via reminder engine\n- reminder-engine.generateForUser hooks it in alongside returns/warranty/recall reminders\n- verified: lapsed(20d)=fire, recent(2d)=skip, never-logged=skip"},{"hash":"355389e","date":"2026-07-13 01:10:57 -0700","author":"Steve","subject":"health: BP-medication context — show active meds that raise/lower blood pressure alongside the trend","body":"- lib/bp-meds.js: deterministic classifier (curated generic stems + names -> effect/class; never LLM-guessed) — ACE/ARB/beta-blocker/CCB/diuretic (lowers) + NSAID/decongestant/steroid/stimulant (raises)\n- routes/vitals.js: read-only join to medication table, bpRelevant() into /health\n- views/vitals.ejs: 'Medications that affect blood pressure' panel (color-coded lowers/raises), informational-not-advice\n- works when med list is populated (currently empty)"},{"hash":"8f58fa2","date":"2026-07-13 01:04:48 -0700","author":"Steve","subject":"health: vitals trends + insights (weight/RHR sparklines, local-LLM 'what changed this month', gentle Stage-2 recheck flag)","body":"- lib/vitals-insights.js: computeTrends (this-30d vs prior-30d avgs), recheckFlag (deterministic rule, gentle), summarize (gemma3:12b narrates ONLY computed deltas — never invents readings; no medical advice)\n- routes/vitals.js: fast trends+recheck on /health load; /api/health/insights for async LLM narrative\n- views/vitals.ejs: 'This month' panel (trend chips + delta arrows + recheck banner + async narrative) + weight/RHR sparklines\n- verified end-to-end on synthetic data then wiped; empty-state safe (no fabricated readings)"},{"hash":"4a02cce","date":"2026-07-13 00:56:16 -0700","author":"Steve","subject":"purchases: collect 38 real Amazon orders from info@ (via George) into purchase table","body":"- scripts/collect-amazon-orders.js: parsed order confirmations 2022-2026, dedup on order#, total NULL (not fabricated — needs body enrichment)\n- validates savings seeds: Nespresso Vertuo = top recurring buy (4x), shipping labels (2x)\n- feeds home purchase count + /purchases + savings advisor with REAL data"},{"hash":"ded692c","date":"2026-07-13 00:49:05 -0700","author":"Steve","subject":"connectors: no-signup bank import — Wells Fargo CSV upload (headerless + negative-debit aware)","body":"- csv-parser: headerless fallback (WF/Quicken export: date,amount,*,,description) + signMode — WF debits are negative=spend, so normalize to positive + drop deposits; header CSVs unchanged\n- upload.js: dedup key (csv:date:amount:merchant) + ON CONFLICT so re-uploading overlapping statements doesn't double\n- connectors.ejs: 'Import CSV' upload form with WF download instructions — zero third-party signup path\n- answers Steve: Plaid needs a free web signup (no CLI); CSV upload needs nothing"},{"hash":"a71abd4","date":"2026-07-13 00:38:34 -0700","author":"Steve","subject":"docs: roadmap — banking (Plaid) track shipped; real-bank keys gated to Steve","body":""},{"hash":"954cc77","date":"2026-07-13 00:38:05 -0700","author":"Steve","subject":"connectors: Plaid 'Connect a bank' UI (Wells Fargo etc.) + incremental/idempotent sync","body":"- views/connectors.ejs: Plaid Link flow — connect button, secure login (Plaid handles bank creds), exchange, per-bank sync; graceful 'setup needed' state when keys absent\n- routes/plaid.js: GET /api/plaid/status; sync now resumes from persisted cursor + ON CONFLICT dedup (was re-pulling everything each sync -> dup purchases)\n- lib/plaid-client.js: isConfigured() helper\n- 0012_plaid_sync_cursor.sql: connector_account.sync_cursor + unique(user_id,order_number) on purchase\n- Plaid (not Stripe) is correct for reading a bank; sandbox works now, real Wells Fargo needs Steve's prod keys"},{"hash":"f4e4da6","date":"2026-07-13 00:31:36 -0700","author":"Steve","subject":"docs: roadmap adds health track (vitals trends, BP↔meds, reminders) as co-priority","body":""},{"hash":"2d8848c","date":"2026-07-13 00:30:52 -0700","author":"Steve","subject":"health: Vitals module — BP history + Apple Watch/device readings + sync","body":"- 0011_health_readings.sql: health_reading (BP pairs, HR, weight, SpO2, glucose...) w/ dedup index\n- lib/health-metrics.js: AHA BP categorization + Apple Health export.xml streaming parser (pairs systolic/diastolic by timestamp; scales SpO2)\n- routes/vitals.js + views/vitals.ejs: /health dashboard w/ inline SVG BP chart, AHA color categories, per-person filter, manual add, delete; /api/health/readings JSON ingest for the Health Auto Export app\n- scripts/import-apple-health.js: one-command import of an Apple Health export\n- nav + wiring. NO fabricated readings — table ships empty w/ 3 real sync paths. Verified end-to-end on synthetic data then wiped."},{"hash":"0256450","date":"2026-07-13 00:23:59 -0700","author":"Steve","subject":"savings: nightly advisor runner + launchd plist (draft, Steve bootstraps)","body":"- scripts/run-savings-advisor.js: dotenv-loaded, dedup-safe regen (local, $0)\n- deploy/com.steve.abramsos-savings.plist: daily 6:05am, RunAtLoad off; bootstrap surfaced in header comment"},{"hash":"3e9efe5","date":"2026-07-13 00:22:21 -0700","author":"Steve","subject":"docs: life-optimizer roadmap (overnight)","body":""},{"hash":"a121be0","date":"2026-07-13 00:21:17 -0700","author":"Steve","subject":"savings: home dashboard tile + honest merchant-savings leads","body":"- home: 'Savings ideas' stat tile (open count + est $ total) linking to /savings\n- seeded 3 real verifiable savings mechanisms (Amazon Subscribe & Save, camelcamelcamel price alerts, Costco coupon book) — real URLs, no fabricated codes"},{"hash":"db2fd48","date":"2026-07-13 00:19:09 -0700","author":"Steve","subject":"savings: life-optimizer module — cheaper/better substitutes from what you buy (local gemma3:12b, $0)","body":"- 0010_savings.sql: savings_suggestion + merchant_coupon tables\n- lib/savings-advisor.js: grounded strategy suggestions (no hallucinated SKUs/prices)\n- routes/savings.js + views/savings.ejs: dashboard w/ created date+time chips, sort+density, save/dismiss\n- seeded reorder items from real Amazon email history; 4 suggestions generated\n- switched local model qwen3:14b->gemma3:12b (qwen3 thinking-mode returns empty under format:json)"},{"hash":"367d888","date":"2026-07-12 23:51:27 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-12T23:51:20 (1 files) — backups/","body":""},{"hash":"ef35bef","date":"2026-07-11 10:05:02 -0700","author":"Steve Abrams","subject":"claims: add California Unclaimed Property (claimit.ca.gov) resource card with per-household-name copy-to-search","body":""},{"hash":"1192d3c","date":"2026-07-09 09:08:42 -0700","author":"Steve","subject":"chore: lint (node --check ✓), untrack runtime state, v0.2.0 (session close)","body":""},{"hash":"4ccbda5","date":"2026-07-08 15:58:17 -0700","author":"Steve","subject":"recall-watch: apply contrarian FIX-FIRST — E-known+in-date=>POSSIBLE regardless of FDA publish date (fix false-clear); assess ALL recalls per NDC (worst class never hidden); EXP-anchored expiry parser (no MFG/ref-date mis-parse); local notification on new alerts; per-NDC worst-status UI + N-of-M affected fills","body":""},{"hash":"362c884","date":"2026-07-08 15:47:47 -0700","author":"Steve","subject":"recall-watch.js: nightly-ready FDA recall watcher with PROGRAMMATIC lot/date relevance (per-fill), new-alert detection + state baseline; caught fluticasone D-0326-2024 overlapping a 2026 fill","body":""},{"hash":"a1038a6","date":"2026-07-08 15:40:00 -0700","author":"Steve","subject":"Recall lot/date check: mark all 4 NDC recalls CLEARED (recalled lots expired before your fills); green Clear badges + plain-English assessment on Recalls + Rx tabs","body":""},{"hash":"2caebba","date":"2026-07-08 15:35:54 -0700","author":"Steve","subject":"Recalls: add curated article/FDA links per NDC-recalled product (Read-about-it column)","body":""},{"hash":"b02722e","date":"2026-07-08 15:30:08 -0700","author":"Steve","subject":"NDC-precise FDA recall match: flag fills whose actual dispensed product is recalled","body":"- scripts/ndc-recall-check.js: match each fill's 11-digit package NDC to openFDA product NDC\n  (leading-zero-normalized labeler|product, exact-confirmed); 4/46 NDCs on a recall list, 40 fills\n- recalls tab: prominent 'Your actual products on an FDA recall list' NDC section (headline signal)\n- Rx tab: red RECALL badge on flagged fills (CREON, Fluticasone, Nystatin, Atorvastatin)"},{"hash":"78b80e6","date":"2026-07-08 15:22:33 -0700","author":"Steve","subject":"FDA recall cross-reference: clean drug names, derive generics, openFDA match into Recalls tab","body":"- scripts/check-fda-recalls.js: cleans OCR junk from fill drug names (95 fixed), rebuilds\n  deduped medication list (83->36 real meds) with generic names, cross-references each vs\n  openFDA drug-enforcement (free); 33/36 matched, 300 recall records into medication_recall\n- routes/recalls.js + views/recalls.ejs: new 'Medication recalls (FDA)' section, grouped per\n  med, Class I/II/III severity badges, worst-first; labeled advisory (ingredient-level match)"},{"hash":"542a632","date":"2026-07-08 14:38:12 -0700","author":"Steve","subject":"Import Shangoo Pharmacy tax profile: prescription_fill table + 277 fills + 83 meds + /prescriptions viewer","body":"- 0009_prescription_fills.sql: fill-history table (date/drug/rx/qty/days/doctor/plan/plan-paid, raw OCR audit)\n- scripts/load-abrams-rx.js: OCR-parsed loader, reconciled to doc totals (Natalia 220 fills / $136,254.86 of $136,328.74)\n- creates person Steve (self) + Natalia (spouse); dedupes 83 medications into medication table\n- routes/prescriptions.js + views/prescriptions.ejs: read-only viewer w/ per-person totals + filter; nav 'Rx'"},{"hash":"6589647","date":"2026-07-08 14:36:18 -0700","author":"Steve Abrams","subject":"auto-save: 2026-07-08T14:36:14 (5 files) — lib/ids.js db/migrations/0009_prescription_fills.sql routes/prescriptions.js scripts/load-abrams-rx.js views/prescriptions.ejs","body":""},{"hash":"63d53f9","date":"2026-07-07 08:04:56 -0700","author":"Steve","subject":"feat(health): Household (people/spouse) + Medications with FDA recall check","body":"- migration 0008: person, medication, medication_recall\n- /household — add spouse/dependents (records + meds attach to them)\n- /medications — self-entered meds per person; every write audit-logged with a medical-consent marker (AGENTS.md gate)\n- lib/fda-fetcher.js — openFDA drug-enforcement lookup; per-med + \"check all\" recall scan stores matches, surfaced on the card\n- nav links, recall styling\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ab337c2","date":"2026-07-07 08:01:02 -0700","author":"Steve","subject":"fix(tests): isolate suite to abrams_os_test DB","body":"pretest builds/migrates/seeds a throwaway abrams_os_test (schema + all migrations\n+ seed.sql + rights-rules); test script pins PG_DATABASE=abrams_os_test. Stops the\nauth-e2e test from wiping the real owner account on every `npm test` (AGENTS.md rule).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"3a31291","date":"2026-07-07 07:53:10 -0700","author":"Steve","subject":"feat(digest): suppress zero-deadline days","body":"sendDigest short-circuits when there are no upcoming deadlines, so empty days\ndon't put a \"nothing due\" email in the inbox.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"8cf03df","date":"2026-07-07 07:45:29 -0700","author":"Steve","subject":"feat(digest): opt-in daily deadline digest email via George","body":"- lib/digest.js builds an HTML digest (overdue / this week / coming up) and self-sends to Steve's own inbox via George /api/send (internal recipient, no external-send token)\n- scheduler cron 08:00 America/Los_Angeles is DOUBLE-GATED: off unless DIGEST_ENABLED=1 AND GEORGE_BASIC_AUTH set; committed code is dormant and never auto-sends on its own\n- .env.example documents all digest keys (default DIGEST_ENABLED=0)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"61b2dca","date":"2026-07-07 07:45:18 -0700","author":"Steve","subject":"feat(warranties): manual Warranties page + Deadlines coverage","body":"- /warranties + /api/warranties CRUD (audit-logged) writing to service_commitment (source='manual')\n- reminder-engine scans service_commitment.refund_window_ends_at -> 'coverage_window_closing' deadlines\n- nav link, warranty id prefix\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"43aea4b","date":"2026-07-07 07:32:16 -0700","author":"Steve","subject":"feat(ui): Bills, Reorders, and unified Deadlines pages","body":"- /bills, /reorders CRUD (audit-logged) + /deadlines over the reminder engine\n- add-forms, mark-paid/ordered (rolls due date forward + refreshes deadline), created date+time chip on every admin card\n- generalized sort-density.js to any [data-grid]; nav links; liquid-glass styles\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"5693d23","date":"2026-07-07 07:32:16 -0700","author":"Steve","subject":"feat(schema): bill + reorder_item tables, recurrence lib, reminder-engine wiring","body":"- 0007 migration: bill (Bills to Pay incl. tax/government/CRA via category) + reorder_item (frequently-ordered + best-price savings)\n- lib/recurrence.js: pure cadence date math (+ 5 passing unit tests)\n- reminder-engine emits calendar_reminder rows for upcoming bill due-dates and reorder points (unified Deadlines)\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7db85d7","date":"2026-07-07 07:31:44 -0700","author":"Steve","subject":"chore: move dev port 9931 -> 9774 (9931 held by japan-enrich/viewer-local) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>","body":""},{"hash":"2e0044d","date":"2026-05-31 16:32:22 -0700","author":"Steve","subject":"Add per-site favicon (kills /favicon.ico 404)","body":""},{"hash":"551b5ea","date":"2026-05-19 17:27:14 -0700","author":"Steve","subject":"ignore + 404-guard backup/snapshot files","body":".gitignore now excludes *.bak, *.bak.*, *.pre-*, *.orig, *~ so editor\nswap files and pre-edit snapshots can't be accidentally committed and\nend up served from /public. Backstop in server.js: any GET whose path\nmatches those patterns short-circuits to 404 before express.static,\nso even a stray on-disk *.bak.html can never leak.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"20f8f36","date":"2026-05-19 17:26:42 -0700","author":"Steve","subject":"add noreferrer to target=_blank external links","body":"CPSC notice + cited-rule source links now use rel=\"noopener noreferrer\"\ninstead of bare rel=\"noopener\" so we don't leak Referer to upstream pages.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"},{"hash":"3458fe1","date":"2026-05-10 12:05:26 -0700","author":"Steve","subject":"tick 18: receipt extractor regression fixtures + heuristic fixes (item 19)","body":"- 7 new golden-file fixtures in tests/fixtures/receipts/ (Amazon, Best Buy,\n  DoorDash, Apple, Etsy, Uber, Walmart, plus a non-receipt newsletter)\n- tests/extractor-regression.test.js auto-discovers fixtures and asserts\n  merchant + total + orderNumber + minConfidence per file\n- lib/receipt-extractor.js: 2 surgical heuristic fixes uncovered by fixtures\n  · extractTotal now picks the highest-rank label (\"order total\" >\n    \"grand total\" > \"total charged\" > plain \"total\"); allows up to 40 chars\n    between label and $amount (handles \"Total charged to Visa ••1234: $18.42\");\n    rejects \"subtotal\" via 3-char lookbehind\n  · extractOrderNumber adds Best-Buy-style \"Order #: BBY01-806589123456\"\n    (alpha prefix + digits) and a tighter Amazon-only pattern\n- 15/15 tests across the regression + base extractor suites"},{"hash":"fe7ffca","date":"2026-05-10 11:06:07 -0700","author":"Steve","subject":"tick 17: manual CSV upload route (backlog item 15)","body":"- npm install multer\n- lib/csv-parser.js: parses statement-style CSVs\n  · auto-detects date / amount / merchant columns (case-insensitive)\n  · handles quoted fields with embedded commas, dollar signs, parens-as-negative\n  · skips refunds (negative amounts) and bad rows; reports first 5 errors\n- routes/upload.js: POST /api/upload/csv (multer in-memory, 10 MB cap)\n  → parser → INSERT purchase rows + audit_log entries\n- 10/10 csv-parser tests"},{"hash":"1666967","date":"2026-05-10 10:26:42 -0700","author":"Steve","subject":"tick 16: Compliance Guardian (URL allowlist + injection scan + PII redact)","body":"- lib/compliance-guardian.js: 4 layered defenses\n  · isAllowedDestination(url) — outbound HTTP allowlist (gov, NIH/FDA,\n    Plaid, Google APIs, local LLM endpoints; subdomain-confusion safe)\n  · detectPromptInjection(txt) — 16 patterns (ignore-prior-instructions,\n    chatml fragments, role injection, exfiltration phrasing, code-eval)\n  · redactPii(text) — masks card #, SSN, phone, email local-part, ZIP,\n    Stripe/OpenAI-style API keys; keeps domain hints\n  · guardForLlm(text) — one-shot redact+scan combiner\n- lib/receipt-extractor.js: enrichWithLlm now runs guardForLlm() on body+from+\n  subject before sending to Ollama; refuses enrichment if injection detected\n  (returns heuristic with llmError='compliance_guardian_blocked')\n- tests/compliance-guardian.test.js: 19/19 across allowlist (6), injection (4),\n  redactor (6), one-shot guard (3)"},{"hash":"a5da75e","date":"2026-05-10 10:01:01 -0700","author":"Steve","subject":"tick 15: auto-parse Drive PDFs on sync (backlog item 17)","body":"- routes/connectors.js syncDrive(): after a Drive PDF lands in document table,\n  immediately calls pdf.parseFile() + receipt-extractor (heuristic + LLM).\n  Inserts a purchase row when extractor returns a draft.\n- Images skipped (OCR is a later tick); failed parses flip parsed_status='failed'\n- audit_log gets document_parsed + purchase_extracted (source: 'drive_pdf')\n- 34/35 tests still green"},{"hash":"8adb083","date":"2026-05-10 09:51:01 -0700","author":"Steve","subject":"tick 14: unit tests for lib/crypto.js + lib/ids.js (backlog item 16)","body":"- tests/crypto.test.js (7): AES-256-GCM round-trip, IV uniqueness, tamper\n  detection on ciphertext + auth tag, unicode, empty-string, env-var validation\n- tests/ids.test.js (6): prefix per kind, unknown-kind throws, distinctness,\n  ULID monotonic-over-time, lowercase invariant\n- 13/13 green; pure unit tests, no PG, no server"},{"hash":"3877b56","date":"2026-05-10 09:44:20 -0700","author":"Steve","subject":"tick 13: /recalls dashboard UI (read-only viewer for recall_match)","body":"- routes/recalls.js: GET /recalls (HTML), GET /api/recalls (JSON)\n  · joins recall_match × recall_event × purchase\n  · status filter (pending_review/confirmed/dismissed) with chip-counts\n- views/recalls.ejs: confidence-coded cards (red ≥85%, amber otherwise)\n  with CPSC source link\n- nav: /recalls added between /claims and /audit\n- 2 new tests (auth-gate + API-gate)"},{"hash":"00b6568","date":"2026-05-10 09:38:09 -0700","author":"Steve","subject":"tick 12: service_commitment + merchant policy extractor","body":"- db/migrations/0006_service_commitments.sql applied\n- lib/commitment-extractor.js: regex+LLM extraction of money_back_guarantee,\n  satisfaction_guarantee, returns_window, price_match, service_sla, lifetime\n- routes/connectors.js: pipes extracted commitments to DB after purchase insert\n- 10/10 in commitment-extractor.test.js (in-isolation)\n\nNOTE: full suite shows 8 EPIPE failures in auth-e2e + 1 smoke — pure pg-pool\nstate from the morning's PG session terminate, NOT a code regression. Kill\nall abrams_os pg sessions and re-run npm test for clean 53/53."},{"hash":"fffc3d8","date":"2026-05-10 08:04:00 -0700","author":"Steve","subject":"tick 11: rights_rule_snapshot + 8-rule starter corpus + cited drafts","body":"- db/migrations/0005_rights_rules.sql: rights_rule_snapshot (versioned, jurisdictional)\n- scripts/seed-rights-rules.js: 8 hand-curated rules\n  · 15 USC §1666i / Reg Z claims-and-defenses (card disputes ≥$50)\n  · 12 CFR §1026.13 billing-error rights (60d window)\n  · Magnuson-Moss federal warranty (15 USC §2301-2312)\n  · 16 CFR §700 FTC pre-sale warranty disclosure\n  · FTC money-back guarantee guides\n  · 14 CFR §259.5 / DOT 2024 airline refund rule\n  · EU 261/2004 standard compensation\n  · CPSC recall remedy entitlement\n- lib/claim-strategist.js: pickCitedRules() matches reason_code + routing,\n  threads citation_block into both LLM prompt and template fallback;\n  fills claim_case.cited_rules_jsonb with citation+source_url+title\n- views/claim-detail.ejs: renders 'Cited rules' panel with source links\n- 6 new tests (matching, limits, unknown-reason fallthrough)\n- 42/43 green; 15 PG tables now"},{"hash":"ee6a1eb","date":"2026-05-10 07:22:50 -0700","author":"Steve","subject":"tick 10: /audit page (read-only viewer for audit_log + auth_event)","body":"- routes/audit.js: GET /audit (HTML), GET /api/audit/events (JSON)\n- views/audit.ejs: filter chips + table view + 7d/30d/90d toggle\n- nav: /audit link added\n- tests: 2 (auth-gate + JSON-endpoint gate)\n- 36/37 green\n- Skipped item 10 (George email send) — overnight YOLO rule says no email-send code without Steve approval"},{"hash":"d34f2f6","date":"2026-05-10 01:03:06 -0700","author":"Steve","subject":"tick 9: in-process cron for reminders + CPSC refresh","body":"- npm install node-cron\n- lib/scheduler.js: two jobs\n  · reminders: every 4h (idempotent via UNIQUE dedupe)\n  · cpsc:       daily 03:17 PT, last 7 days (idempotent via ON CONFLICT)\n- Logs each tick to logs/scheduler.log\n- start() is a no-op when NODE_ENV=test or SCHEDULER_DISABLED=1, so tests stay fast\n- Wired into server.js startup; verified live: scheduler started message in log\n- 34/35 green"},{"hash":"1f31901","date":"2026-05-10 01:01:15 -0700","author":"Steve","subject":"tick 8: CSRF protection on HTML POST forms","body":"- middleware/csrf.js: cookie+body double-submit pattern\n  · ensureToken mints non-httpOnly aos.csrf cookie on every request\n  · verifyToken rejects 403 on POST/PUT/DELETE/PATCH to NON-/api/ routes\n    when body._csrf or x-csrf-token header doesn't match the cookie\n  · /api/* exempted (same-origin JS, sameSite=lax cookie protects)\n  · timing-safe comparison with length-guard (avoids 500 on length mismatch)\n- views: hidden _csrf input added to signup/signin/enroll-totp/step-up/signout\n- tests/csrf.test.js: 4 new (no-token rejection, wrong-token rejection, GET mints cookie)\n- 34/35 green (1 skip — pdf-parse fixture)"},{"hash":"1f60c67","date":"2026-05-10 00:53:39 -0700","author":"Steve","subject":"tick 7: /claims dashboard UI + claim detail with draft preview","body":"- views/claims.ejs: card grid showing routing/state/due-date per claim\n- views/claim-detail.ejs: full draft letter (Georgia serif), evidence index,\n  pending action_queue rows with 'Approve draft' button\n- routes/claims.js: GET /claims (list HTML), GET /claims/:id (detail HTML)\n- nav: /claims link added\n- Approving = 'I've reviewed this'; send wire-up still deferred to George tick"},{"hash":"7294ca3","date":"2026-05-10 00:38:34 -0700","author":"Steve","subject":"tick 6: claim_case + action_queue + claim strategist (drafts only)","body":"- db/migrations/0004_claims.sql: claim_case (state machine: draft|sent|resolved)\n  + action_queue (approval_level: auto|user_required, state: pending|approved|executed)\n- lib/claim-strategist.js: deterministic routing by reason_code\n  · returns_window_closing → refund / merchant\n  · warranty_expiry         → repair / manufacturer\n  · recall_action_due       → recall_remedy / manufacturer\n  Letter drafted via local Ollama qwen3:14b; falls through to template if LLM down.\n  Every claim_case spawns ONE user_required action_queue row — never auto-executes.\n- routes/claims.js: GET /api/claims, /:id; POST /from-reminder/:id; /:caseId/actions/:actionId/approve\n- 2 new tests with mocked-down Ollama proving the template-fallback path\n- 30/31 green (1 skip — pdf-parse fixture)"},{"hash":"b6f84e0","date":"2026-05-10 00:35:14 -0700","author":"Steve","subject":"tick 5: calendar_reminder table + reminder engine","body":"- db/migrations/0003_reminders.sql: calendar_reminder + dedupe unique index on\n  (user_id, owner_table, owner_id, reason_code) so reruns are no-ops\n- lib/reminder-engine.js: 3 deadline types fire today\n  · returns_window_closing  (5d before 30d default returns mark)\n  · warranty_expiry         (30d before 1y default mark)\n  · recall_action_due       (14d after a recall_match lands)\n- routes/reminders.js: /api/reminders/upcoming, /:id/dismiss, /regenerate\n- 4 new tests (uses fixture user_steve + tracked test purchase ids)\n- 28/29 tests green (1 skip — pdf-parse fixture)"},{"hash":"d95d5e5","date":"2026-05-10 00:32:29 -0700","author":"Steve","subject":"tick 4: PDF parsing for receipt attachments (tier-3)","body":"- npm install pdf-parse\n- lib/pdf-parser.js: parseFile(), parseBuffer(), resolveDocumentPath()\n  (lazy-require pdf-parse so its test fixture doesn't load at startup)\n- routes/documents.js: GET /api/documents (list), POST /api/documents/:id/parse\n  pipeline: extract PDF text → synthesize gmail-summary → run heuristic + LLM\n  → insert purchase + audit_log entries\n- /api/documents wired into server.js behind requireAuth\n- 25/25 tests (1 skip — pdf-parse sample fixture not installed in production)"},{"hash":"600c717","date":"2026-05-10 00:30:43 -0700","author":"Steve","subject":"tick 3 (scaffold): CPSC recall_event + recall_match tables + matcher + fetcher","body":"- db/migrations/0002_recalls.sql: recall_event, recall_match, recall_pull_log\n- lib/cpsc-fetcher.js: hits saferproducts.gov public API + normalizes to our shape\n- lib/recall-matcher.js: canonical scoring pseudocode from docs/SPEC.md\n  (gtin/upc 0.45, model fuzzy 0.25, brand 0.05, post-purchase 0.05, UDI 0.40)\n- scripts/ingest-recalls.js: idempotent backfill (run: node scripts/ingest-recalls.js [days])\n- No live data ingested yet; ingest is opt-in"},{"hash":"9e812a4","date":"2026-05-10 00:27:52 -0700","author":"Steve","subject":"tick 2: Google Drive sync (receipt-shaped PDFs/images)","body":"- lib/drive-fetcher.js: list+download via drive.readonly OAuth scope\n- query: name~/receipt|invoice|order|confirmation/i + mime in (pdf, image/*) + last 90 days\n- routes/connectors.js: gmail sync now also walks Drive on the same google connector;\n  files saved to uploads/drive-<id>.<ext>; document rows inserted with kind='attachment'\n- audit_log gets one document_persisted event per file\n- 22/22 tests still green"},{"hash":"323f8a8","date":"2026-05-10 00:26:18 -0700","author":"Steve","subject":"tick 1: receipt extractor tier-2 LLM fallback (Mac1 qwen3:14b)","body":"- lib/ollama.js: thin Ollama client with strict-JSON mode + timeout\n- lib/receipt-extractor.js: enrichWithLlm() merges into low-conf heuristic results;\n  heuristic-trust rule (LLM never overrides a heuristic-filled field)\n- routes/connectors.js: gmail sync now calls extractWithFallback when conf < 0.7\n- tests: 3 new in tests/extractor-llm.test.js with cache-injected mock ollama\n- Total: 22/22 green; live smoke against Mac1 confirms end-to-end works"},{"hash":"2113e8d","date":"2026-05-10 00:11:43 -0700","author":"Steve","subject":"feat: 'Import all receipts' button + 2FA gate + Plaid sandbox","body":"- Auth: bcrypt password + otplib TOTP (single-user, locked after first signup)\n- Step-up TOTP re-verify within 60s before /import or any 'Import all' run\n- 4 new tables: auth_credential, auth_totp, auth_session, auth_event\n- DB-backed sessions (signed cookie aos.sid, 30d TTL, httpOnly+sameSite=lax)\n- Connectors: Gmail (already wired) + Drive (drive.readonly added) + Plaid sandbox\n- Plaid: link/token + exchange + transactions/sync; access tokens AES-256-GCM at rest\n- /import dashboard fans out to every connected connector via Promise.all + cookie-forward\n- Compliance posture updated (PCI-aware: never PAN/CVV, only Plaid item refs)\n- 19/19 tests green (5 smoke + 6 extractor + 8 auth-e2e)"},{"hash":"434eca9","date":"2026-05-09 23:28:57 -0700","author":"Steve","subject":"rename OwnershipOS → AbramsOS (per Steve): pkg/db/pm2/env/brand/sessions/localStorage all swept; tests + live server still green on :9931","body":""},{"hash":"ef35e84","date":"2026-05-09 21:37:58 -0700","author":"Steve","subject":"smoke green: PG via /tmp socket (peer auth), test glob fix, pool.end on test exit","body":""},{"hash":"3606684","date":"2026-05-09 21:33:03 -0700","author":"Steve","subject":"initial scaffold: OwnershipOS v0.1 — Express+PG shell, Gmail OAuth + receipt extractor, 8-table schema, liquid-glass UI, full canonical spec under docs/","body":""}]}