{"slug":"homesonspec","total":360,"limit":100,"offset":0,"since":null,"commits":[{"hash":"ed938c0f","date":"2026-09-13 16:36:50 -0700","author":"Steve","subject":"Publish a fleet-health heartbeat: this poller was invisible to the rollup","body":"Found by a red-team pass on my own changes. fleet-health-rollup globs\n~/.claude/skills/*/data/latest.json and SILENTLY SKIPS a skill with no such file\n(`if (!existsSync(file)) continue`). This poller wrote state only to\nops/runtime/, so it contributed ZERO signal and was unknown to\ndw-canary-meta-watchdog too: if it died, the 7am panel would say nothing was\nwrong — about the one app under a live Guideline 5.6 conduct citation.\n\nWorse, my previous commit's own comment claimed \"alert_delivered lands in\nlatest.json for fleet-health-rollup\". There was no latest.json for this skill.\nI asserted an integration I never tested, in the same session spent hunting\nexactly that failure. Comment corrected; the integration now actually exists and\nis actually tested — the rollup picks the row up (verdict=ATTENTION, status=WARN).\n\n- heartbeatStatus(): approved/pending -> PASS, rejected -> WARN (matching\n  ios-release-train's ATTENTION), and an outcome we could NOT measure ->\n  NOT_MEASURED/WARN, never PASS (TK-11431 #1). A null state means ASC was\n  unreachable or the row did not parse; that is not evidence all is well.\n- Published AFTER the durable state write and wrapped so a heartbeat failure can\n  never cost the observation.\n- Never published from a --fixture run (the seam guard learned earlier today).\n- Two tests lock the mapping, incl. the negative case.\n\nTK-11155\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01BPS3gnB4H5MQKmWcHKvuf6"},{"hash":"471d1568","date":"2026-09-13 16:25:48 -0700","author":"Steve","subject":"Poller: notify() must never be able to fail its caller","body":"My previous commit moved notify() BEFORE the log/state write so alert_delivered\nlands in latest.json for fleet-health-rollup. That made bookkeeping able to break\nthe measurement: a throw in the new cncp_post shell-out would cost the observation\nAND leave a stale state file, which then re-fires the same \"change\" next run.\n_shared/alert_receipt.sh states this rule in its own header; I violated it.\n\nWrapped in try/catch returning false. Proven on the LIVE path with a clean runtime\ndir (previousState=null forces stateChanged=true so notify actually runs):\nstate persisted, alert_delivered=true, real runtime dir untouched.\n\nNoted honestly: the success path is proven; the catch is belt-and-braces, since a\nmissing helper makes `bash -c '. helper && …'` exit nonzero rather than throw.\n\nTwo earlier attempts at this test were vacuous and are recorded so they are not\nrepeated: --fixture skips notify entirely (`&& !fixturePath`), and repointing HOME\nbreaks ipa-status's key lookup before notify is ever reached.\n\nTK-11155\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01BPS3gnB4H5MQKmWcHKvuf6"},{"hash":"043c09b7","date":"2026-09-13 16:12:44 -0700","author":"Steve","subject":"Apple review poller: stop disarming itself while awaiting Apple","body":"The poller treated REJECTED as terminal and called launchctl remove on\nevery run, i.e. it tried to unschedule itself 303 times since 09-08. It\nsurvived only because JOB_LABEL said 'com.steve.homesonspec-apple-review'\nwhile the label actually loaded is '...-apple-review-v2', so every removal\nreturned status 3 (no such label). Correcting that constant alone would\nhave armed the self-destruct.\n\n- shouldDisarm(): only an 'approved' outcome may stop the monitor. REJECTED\n  is the state we are waiting to LEAVE while a Resolution Center reply is\n  outstanding; stopping there blinds us at the moment it matters.\n- JOB_LABEL corrected to the label that is actually loaded, with a comment\n  that it is only safe alongside the guard above.\n- Alert on state CHANGE only (was: a desktop banner every 15 minutes) and\n  record alert_delivered via the shared cncp_post helper, which asserts the\n  HTTP status instead of swallowing it.\n- Negative tests: an attention outcome must NOT disarm, with a positive\n  control so the guard cannot pass as dead code, plus a JOB_LABEL/plist\n  assertion. Verified red-then-green by re-injecting the original logic;\n  all 5 pre-existing tests stayed green through the injected fault, which\n  is why this shipped untested.\n\nTK-11155\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01BPS3gnB4H5MQKmWcHKvuf6"},{"hash":"fb984ed6","date":"2026-09-11 14:11:41 -0700","author":"Steve","subject":"eas: pin production ios.image=latest (ITMS-90725 guard)","body":"Fleet sweep under TK-11155 found this app missing the guard that Charge & Explore\nneeded after sitting at 0 builds in ASC on an ITMS-90725 upload bounce. The\ndocumented fix is exactly this line (Xcode 26 / iOS 26 SDK image), and the\nchargeandexplore-release canary treats it as a HARD gate.\n\nCheap insurance: without it a production build can bounce on upload after the\nEAS build has already been paid for.\n\nSemantic diff confirms this adds build.production.ios.image only; nothing else\nremoved or changed. JSON re-validated.\n\nRefs TK-11155.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"42bd9935","date":"2026-09-11 09:01:19 -0700","author":"Steve Abrams","subject":"Record verified production ValidationEvent partitioning","body":""},{"hash":"df934fe2","date":"2026-09-11 08:59:56 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-11T08:57:34 (9 data files) — ops/TK-11364-validationevent-approval.md ops/o3-partition-PROVEN-runbook.md verification/TK-11364/app-after.jsonl verification/TK-11364/independent-after.txt verification/TK-11364/previous-e2e-proof.json","body":""},{"hash":"90b58b81","date":"2026-09-11 08:34:07 -0700","author":"Steve Abrams","subject":"Rehearse and harden ValidationEvent partition conversion","body":""},{"hash":"36033811","date":"2026-09-10 17:59:07 -0700","author":"Steve Abrams","subject":"Reconcile TK-11364 partition state and document remaining approval gate","body":""},{"hash":"0200179c","date":"2026-09-10 13:53:37 -0700","author":"Steve","subject":"TK-11303: retire stale v1 apple-review launchd plist (superseded by v2 wrapper)","body":"The direct-node plist (ops/com.steve.homesonspec-apple-review.plist) is the\njob launchd bootstrapped once via RunAtLoad then DROPPED (no persistence),\nand it ran apple-review-poller.mjs from the wrong cwd -> ENOENT mkdir /ops/runtime.\nIt is superseded by com.steve.homesonspec-apple-review-v2 (~/Library/LaunchAgents,\nruns ~/.claude/skills/homesonspec-apple-review/run.sh, which cd's to the project\nroot and exec's the SAME ops/apple-review-poller.mjs). Nothing auto-bootstraps\nops/*.plist, so the file was an inert footgun. The poller .mjs + its test stay\n(v2 depends on them; 5/5 tests pass). Reversible: git revert restores the file.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01NdRDCrJcC2BcsYP7k3CxyA"},{"hash":"31581ff8","date":"2026-09-10 12:55:36 -0700","author":"Steve","subject":"TK-11364: version + golden-lock the TK-11125 guard's canonicalization contract","body":"Document and version the spurious-generation guard's two canonicalization\nprimitives (codex follow-up). canonicalKey is stored + used as the guard's\nlookup key, so a rule change re-mints one generation per home (drift-SENSITIVE);\npayloadHash is recomputed on both sides at compare time, so a rule change is\nself-consistent and mints nothing (drift-IMMUNE). Adds CANONICAL_KEY_VERSION /\nPAYLOAD_HASH_VERSION, a golden-vector contract test that breaks the build on any\nsilent rule change (forcing a conscious version bump), CANONICALIZATION-CONTRACT.md,\nand a guard-comment cross-reference. Code/docs only — no schema, no data write.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01NdRDCrJcC2BcsYP7k3CxyA"},{"hash":"3e4d499f","date":"2026-09-10 12:29:35 -0700","author":"Steve","subject":"O3b: ValidationEvent partition conversion script (corrected pattern)","body":"Second half of O3. Same ATTACH path as SourceEvidence but built on the\ncorrected pattern from today's run rather than repeating its mistakes:\n\n- per-partition FKs, never a parent FK before ATTACH (that forced a 170M-row\n  revalidation under ACCESS EXCLUSIVE and stalled the site for ~7 minutes)\n- GRANTs on the parent and every partition, and the run aborts if any partition\n  is left ungranted (a postgres-created table has no app-role ACL, which 500'd\n  the public page with 42501)\n- verification includes an app-role query in the exact shape the admin pages\n  use (SET ROLE homesonspec; WHERE passed=false ORDER BY runAt DESC LIMIT 100)\n\nrunAt is timestamp(3) without time zone, so bounds use TIMESTAMP literals\nrather than the ::timestamptz casts used for SourceEvidence.\n\nScratch-tested: rows preserved, new rows route to the correct monthly\npartition, default partition stays empty, admin query shape returns rows.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01HpKbjp2febJ1r8BNTyZwvP"},{"hash":"0f94115d","date":"2026-09-10 11:46:02 -0700","author":"Steve","subject":"O3: add grant script so future partitions inherit the app-role ACL","body":"The inline paste kept line-wrapping in the terminal, which split 'sudo -u' from\nits argument. Same file-pipe pattern as o3-apply.sh: short command, no wrapping.\n\nSets ALTER DEFAULT PRIVILEGES so a partition created later by postgres is\nautomatically granted to the app role instead of silently 42501-ing writes to\nthat range, backfills any partition currently missing the grant, and verifies\nboth the ACL and that a real home detail page still returns 200.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01HpKbjp2febJ1r8BNTyZwvP"},{"hash":"cc0a0516","date":"2026-09-10 11:39:13 -0700","author":"Steve","subject":"O3: fix the two bugs the live run exposed (FK-before-attach stall, missing GRANTs)","body":"The conversion succeeded on prod but caused two problems the 20k-row scratch\ntest was too small to expose. Both are now fixed in the script.\n\n1. FK-before-ATTACH stalled the live site ~7 minutes.\n   Adding the foreign key to the partitioned parent BEFORE attaching makes\n   Postgres re-validate that FK against every row of the incoming partition --\n   170M rows joined against StagedRecord -- while holding ACCESS EXCLUSIVE.\n   Peak 33 app queries blocked, oldest waiting 396s. lock_timeout did not help\n   because the lock was already acquired; the cost was in the work, not the\n   wait. The legacy partition already carries its own valid FK and forward\n   partitions are empty, so per-partition FKs are correct and instant.\n\n2. Missing GRANTs 500'd the public home detail page.\n   A table created by postgres has no ACL for the app role, so\n   prisma.sourceEvidence.findMany() failed with 42501 'permission denied for\n   table SourceEvidence' on /homes/[id]. The script now grants on the parent,\n   the default partition and every forward partition, and sets ALTER DEFAULT\n   PRIVILEGES so future partitions inherit the grant instead of silently\n   breaking writes to a new range.\n\nAlso adds an app-health assertion to VERIFY: it fetches a real published home\ndetail page and aborts unless it returns 200. The original script verified the\ndatabase thoroughly and never once checked that the application still worked,\nwhich is why a 500 reached users.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01HpKbjp2febJ1r8BNTyZwvP"},{"hash":"a15775f6","date":"2026-09-10 11:10:48 -0700","author":"Steve","subject":"O3: add prod apply script for the partition conversion","body":"One self-aborting script implementing ops/o3-partition-PROVEN-runbook.md.\nPreflight hard-aborts on a stale/missing backup, <15G free disk, or row loss.\nSTEP 1 (VALIDATE CONSTRAINT) is online under SHARE UPDATE EXCLUSIVE; STEP 2 is\na single catalog-only transaction with lock_timeout so it aborts cleanly rather\nthan queueing behind a long transaction.\n\nAdds two things the runbook did not have, both surfaced while preparing the\nreal run:\n\n- a DEFAULT partition. Without it, a row whose createdAt falls outside every\n  defined range raises 'no partition of relation found' and ALL ingestion\n  stops. The verify step asserts it stays empty, which is also the alarm for a\n  missing forward range.\n- forward monthly partitions through 2027-12, so partition exhaustion cannot\n  break ingestion unattended.\n\nMac2 is classifier-blocked from prod SSH, so this runs from Steve's shell.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01HpKbjp2febJ1r8BNTyZwvP"},{"hash":"d6400d4d","date":"2026-09-10 09:44:59 -0700","author":"Steve","subject":"O3: prove SourceEvidence partitioning is feasible with no rewrite and no data-model change","body":"Tested the ATTACH-based conversion end-to-end on a scratch PG 14.23 DB and\ndisproved the three blockers this work has assumed since 2026-08-26:\n\n- pg_partman is NOT required (native PG14 declarative partitioning suffices)\n- the conversion needs ~0 extra disk, NOT ~82GB of scratch for a rewrite\n- no composite-PK / Prisma data-model change is needed, because a PK-less\n  partitioned parent lets the legacy partition keep its own local PK\n\nVerified on prod (read-only) that nothing has an incoming FK to\nSourceEvidence.id, createdAt is NOT NULL, and the only constraints are the PK\nand the StagedRecord FK. Verified in code that all four app call sites key on\nstagedRecordId or entityType/entityId -- there is no findUnique by id anywhere,\nwhich is also why SourceEvidence_pkey (11GB) shows 0 scans.\n\nScratch proof: 20k/20k rows preserved with the legacy heap unchanged (no\nrewrite), insert routing correct, FK still enforcing, pruning working, both hot\nindexes still used per partition, DETACH/re-ATTACH round-trips cleanly (the\npayoff: cold archive without deleting history), and all four real app\noperations plus cross-partition row movement pass.\n\nMeasured partition fan-out on the public-page query across 36 monthly\npartitions: execution 0.4-0.6ms and planning within noise of the unpartitioned\nbaseline, so fan-out is not a concern at realistic partition counts.\n\nOnly real prod cost is one ~82GB sequential scan during VALIDATE CONSTRAINT,\nheld under SHARE UPDATE EXCLUSIVE, which does not block reads or writes.\n\nStill deferred: feasibility is no longer the blocker, necessity is. Execute on\na TK-11363 trigger.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01HpKbjp2febJ1r8BNTyZwvP"},{"hash":"82fadf19","date":"2026-09-10 08:46:57 -0700","author":"Steve","subject":"ops: preflight now VALIDATES the rollback dump instead of suggesting it (TK-11364)","body":"Checklist item 2 has always said the dump must be validated with pg_restore\n--list, but that was a note a human had to remember at 3am. Nobody ever ran\nit, so O3's entire rollback rested on an unverified 12.8GB file.\n\nPreflight now does it automatically, cheaply:\n  - pg_restore --list, and assert SourceEvidence/StagedRecord/ValidationEvent/\n    InventoryHome are actually present in the TOC\n  - read the LAST data block in the archive\n\nThe tail read is the one that matters here. A pg_dump killed by a full disk\nproduces a VALID TOC with a truncated tail, and the TOC sits at the FRONT of a\ncustom-format archive — so reading it proves nothing about the tail. That is a\nlive risk on this box, which is at 94% and already skipping other backups (the\nBACKUP-SKIPPED flag printed right below). Reading the last block forces a seek\nto near EOF and does catch it. Both checks measured ~0.04s on the real dump.\n\nAlso prints the full-archive proof command for pre-window use rather than\nrunning ~6 min of CPU on every preflight.\n\nVerified live against today's dump: TOC OK (30 tables), all four critical\ntables present, tail read OK. Separately ran the full proof out-of-band —\npg_restore of the entire SourceEvidence data block exited 0 after 2m11s\n(13GB read), so today's dump is a genuinely valid rollback.\n\nRead-only against prod; no mutations.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"616032a6","date":"2026-09-10 07:47:16 -0700","author":"Steve","subject":"ops: fix preflight rollback check reporting a false \"no backup\" (TK-11364)","body":"Section 7 globbed /root/backups/* which only matches the top level, so it\nmissed the real dumps in /root/backups/db/ and printed \"no obvious dump found\"\nwhile a fresh 12.8GB homesonspec dump sat one directory down. On the rollback\ncheck a false \"none\" is the worst possible answer — it either aborts a window\nunnecessarily or, worse, normalizes proceeding without a verified rollback.\n\nNow finds the actual dump, prints size + age, warns past a nightly cycle,\nrestates the pg_restore --list validation the checklist requires, and fails\nloud with an explicit \"there is no rollback\" if none exists.\n\nAlso surfaces BACKUP-SKIPPED flags, which are themselves a rollback risk.\nVerified live: one is present today — DW-Agents nightly skipped at 03:30 with\nonly 34G free, refusing to run to protect Postgres. That flag would otherwise\nhave sat silent while someone planned a conversion.\n\nVerified read-only against prod; no mutations.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01JhnABoxWtpF7mbBowoBWhb"},{"hash":"14ed1904","date":"2026-09-10 07:44:47 -0700","author":"Steve","subject":"ops: make Phase-2 preflight actually enforce the disk gate (TK-11364)","body":"Three defects found while validating the O3 runbook against live prod.\n\n1. The disk gate only PRINTED df and relied on a human doing the math — at\n   exactly the spot where Cody's hard \"abort if <75GB free\" lives. Combined\n   with the plan doc's (false) claim that the volume grow had happened, a\n   session could read past it and start a rewrite that fills the volume and\n   takes prod Postgres down. Now machine-checked, loudly reported, and the\n   script exits non-zero with an ABORT banner so no wrapper can proceed on a\n   skimmed report. Verified against prod: FAIL at 44GB free, exit 1.\n\n2. Section 6 (\"is import traffic quiet?\") ran count(*) WHERE createdAt >\n   now()-'2 min'. There is no index on createdAt, so that was a full seq scan\n   of the 82GB heap on the check you re-run most often while waiting for a\n   window to go quiet. Replaced with a pg_stat_user_tables n_tup_ins delta:\n   instant, no heap read, and measures live insert RATE. Whole preflight now\n   completes in ~80s instead of blowing a 90s budget unfinished.\n\n3. New section 8 surfaces the index inventory and flags the material fact that\n   SourceEvidence has NO index on createdAt — the column Phase 2 partitions on.\n   partition_data_proc drains by createdAt range, so every batch is a seq scan\n   unless an index is built first. That is a build-it-up-front-vs-accept-it\n   decision, not something to discover mid-window.\n\nSection 5 also now prints an instant reltuples estimate before the slow exact\ncount, with the seq-scan cost called out.\n\nVerified read-only against prod; no mutations.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01JhnABoxWtpF7mbBowoBWhb"},{"hash":"88e393b7","date":"2026-09-10 07:36:27 -0700","author":"Steve","subject":"docs: correct stale/false facts in O3 partition plan (TK-11364)","body":"The plan claimed the Kamatera volume grow had happened and that it solved the\ndisk refill. It did not — sda is still 700G with 112d uptime; Steve deferred\nPhase 1 on 2026-09-03. A future session reading this would assume Phase-2\nheadroom that does not exist.\n\nAdds a status-correction banner reconciling the doc against read-only prod\nmeasurement plus the independent homesonspec-size-canary heartbeat:\ngrowth 16 -> 3.24 GB/wk (TK-11125 payload guard), SourceEvidence 55 -> 82GB,\nfree space 67 -> 44GB (preflight item 1 now FAILS), size canary already armed,\nstep-1 index drop already applied, and the \"retention gets turned ON\" pull\ntrigger permanently dead per Steve's keep-full-history ruling.\n\nBody preserved as written for audit.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01JhnABoxWtpF7mbBowoBWhb"},{"hash":"32416391","date":"2026-09-10 00:54:10 -0700","author":"Steve Abrams","subject":"Require configured slots for manual AdSense units","body":""},{"hash":"6c9cd93d","date":"2026-09-09 16:02:09 -0700","author":"Steve","subject":"TK-11341: add AdSense Auto-Ads loader + ads.txt (revert to remove ads)","body":""},{"hash":"aaf2c487","date":"2026-09-04 22:38:51 -0700","author":"Steve","subject":"snapshot before restart: preserve in-flight work","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_011gbmuMHgCLDEPM4TpfAr4G"},{"hash":"ba37b54c","date":"2026-09-04 11:53:46 -0700","author":"Steve","subject":"mobile: make the tracker-drift canary baseline-aware (was firing on steady state)","body":"As first written the canary exited 1 whenever ANY uncovered third-party host was\npresent — which is permanently true, because homesonspec.com serves listing photos\nfrom www.drhorton.com and awh.widen.net. A canary that fires on the steady state is\nnoise and gets ignored, which is the one failure mode that makes it worthless.\n\nNow baseline-aware, per house canary doctrine: known-benign hosts live in\ntracker-drift-baseline.json, are reported as 'accepted', and do NOT fail. Exit 1\nfires only on a host that is NEW since the baseline — an actual worsening transition.\nAdds --accept to fold a newly verified asset host into the baseline.\n\nBaseline seeded with the two hosts verified 2026-09-04 by inspecting the page source:\nboth appear only as <img src> listing photos (D.R. Horton product catalogue, Widen\nDAM CDN), so they are asset hosts, not trackers.\n\nVerified both directions, not just the quiet one:\n  steady state          -> PASS, exit 0\n  simulated new host    -> FAIL, names www.drhorton.com, exit 1\n  baseline restored     -> PASS, exit 0\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01SEhnWQSSkAhSYxWXHJ3MCw"},{"hash":"693125b2","date":"2026-09-04 11:49:37 -0700","author":"Steve","subject":"mobile: add tracker-drift canary guarding the 'Data Not Collected' privacy label","body":"The Browse WebView blocks trackers with a DENYLIST (lib/tracker-policy.ts, 7 hosts),\nso the App Privacy label's truth is contingent on homesonspec.com's tag set. Add a\npixel whose host is not on that list — a new ad network, a self-hosted fbevents.js,\nor a server-side-GTM first-party proxy — and it passes silently, with no app rebuild\nto catch it, quietly making a filed 'Data Not Collected' label FALSE. On an account\nalready cited under Guideline 5.6 for a declaration that did not match a binary, that\nis the drift that matters.\n\nThe canary fetches the live site, extracts every third-party host, and flags any not\ncovered by the denylist. Exit 1 on drift.\n\nCurrent run: BLOCKED = www.googletagmanager.com + connect.facebook.net (the two real\ntrackers, both covered). UNCOVERED = www.drhorton.com + awh.widen.net, both verified\nby inspecting the page as <img src> listing photos (builder product catalogue and the\nWiden DAM CDN) — asset hosts, not trackers. So the label is accurate today.\n\nAlso confirmed while wiring this up: the blocker IS correctly applied. components/\nTrackedWebView.tsx PREPENDS the block script to any caller-supplied\ninjectedJavaScriptBeforeContentLoaded and AND-s !isTrackerUrl() with the caller's\nonShouldStartLoadWithRequest, so a caller cannot accidentally disable it — and the\nexisting suite enforces it architecturally ('the wrapper is the one place WebView is\nimported', 'no file outside the wrapper imports WebView', 'every WebView rendered in\napp/ is a TrackedWebView'). 12/12 tests pass.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01SEhnWQSSkAhSYxWXHJ3MCw"},{"hash":"90ca06fc","date":"2026-09-04 11:36:07 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-04T11:31:34 (65 data files) — apps/mobile/_recovered/hx/Payload/HomesonSpec.app/Assets.car apps/mobile/_recovered/hx/Payload/HomesonSpec.app/EXConstants.bundle/Info.plist apps/mobile/_recovered/hx/Payload/HomesonSpec.app/EXConstants.bundle/app.config apps/mobile/_recovered/hx/Payload/HomesonSpec.app/Expo.plist apps/mobile/_recovered/hx/Payload/HomesonSpec.app/ExpoApplication_privacy.bundle/Info.plist","body":""},{"hash":"7cf4bed8","date":"2026-09-04 11:30:41 -0700","author":"Steve","subject":"verify Apple review poller lifecycle","body":""},{"hash":"672b9689","date":"2026-09-04 11:26:47 -0700","author":"Steve","subject":"add App Store review completion poller","body":""},{"hash":"a656c9b9","date":"2026-09-04 10:14:47 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-04T10:10:38 (1 data files) — apps/mobile/_recovered/homesonspec-build2.ipa","body":""},{"hash":"307603c2","date":"2026-09-04 09:40:16 -0700","author":"Steve","subject":"record successful iOS review resubmission","body":""},{"hash":"29dc1871","date":"2026-09-04 09:01:56 -0700","author":"Steve","subject":"mobile: repair App Store Connect key path","body":""},{"hash":"d8531bc3","date":"2026-09-04 08:49:35 -0700","author":"Steve","subject":"TK-10337: preserve final iOS device proof","body":""},{"hash":"46e6b219","date":"2026-09-04 08:33:36 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-04T08:26:29 (1 data files) — apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-FINAL-mapfixed-2026-09-04T15-31-01Z.mov.privacy-reviewed.sha256","body":""},{"hash":"4df903f1","date":"2026-09-04 08:20:53 -0700","author":"Steve","subject":"mobile: cap rendered map markers (120, densest-first) + lower grid density — fix react-native-maps on-device OOM crash on the Map tab (TK-11155)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01CQi5poZk3YeUbEJbDcFRmc"},{"hash":"f2de197d","date":"2026-09-04 08:17:39 -0700","author":"Steve","subject":"Homes on Spec: actually test the tracker-blocking script (TK-10387)","body":"The App Store 'Data Not Collected' claim rests on the injected TRACKER_BLOCK script\nworking. Until now only isTrackerUrl() — the native-side URL filter — had tests. The\nscript doing most of the work (neutering gtag/fbq/dataLayer and intercepting\nfetch/XHR/Image/sendBeacon inside the page) had none, so 'it looks right' was the only\nevidence behind a claim we make to Apple.\n\n- lib/tracker-block.ts now exports buildTrackerBlock(hosts) and has ZERO imports, so\n  node --test can load it directly (Node's ESM resolver requires explicit extensions,\n  which the TS/Metro resolver does not use). TrackedWebView composes it from the real\n  TRACKER_HOSTS, so the shipped behaviour is unchanged.\n- lib/tracker-block.test.mjs runs the REAL script text, built from the REAL host list,\n  in a vm sandbox with a fake window/document, and asserts observable behaviour rather\n  than string matching: gtag/fbq/dataLayer.push are no-ops (and dataLayer does not\n  accumulate), tracker fetch short-circuits to an empty 204 while first-party fetch\n  reaches the real implementation, GA4's sendBeacon transport is refused, tracker XHR\n  never sends, the Facebook tr pixel never gets a real src, subdomains of tracker hosts\n  are blocked, and a lookalike domain is NOT blocked so the filter cannot over-reach\n  and break the site.\n\n12/12 tests pass; tsc --noEmit clean. This verifies the MECHANISM only — the panel's\nbinding condition still stands: do not file 'Data Not Collected' until a full-device\nproxy capture across all four tabs is clean.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QfGYEoLBywwJD1nfrHe1on"},{"hash":"16d8e5af","date":"2026-09-04 06:36:10 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-04T06:32:13 (1 data files) — apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-BUILD3-2026-09-04T13-10-12Z.mov.privacy-reviewed.sha256","body":""},{"hash":"2726cf35","date":"2026-09-04 05:59:47 -0700","author":"Steve","subject":"Homes on Spec: correct stale README claims that misled a downstream review (TK-10387)","body":"Item 4 said the EAS projectId was still the SET-AT-EAS-INIT placeholder. It is not —\napp.json has carried a real projectId for some time. That stale line is what led an\nearlier review pass to conclude push registration could never fire, which inverted the\nactual finding: registration DOES succeed, so getExpoPushTokenAsync was handing a\ndevice identifier to Expo for an Alerts feature that cannot deliver anything.\n\nItem 1 now describes the PUSH_BACKEND_READY gate and states the flip must happen in\nthe same change that wires the backend. Item 5 marked done — /privacy returns 200,\nverified today.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QfGYEoLBywwJD1nfrHe1on"},{"hash":"d98894aa","date":"2026-09-03 20:20:36 -0700","author":"Steve","subject":"Homes on Spec: route every WebView through one hardened TrackedWebView (TK-10387, DTD verdict A)","body":"The Browse tab was hardened against homesonspec.com's GTM/GA4/Meta-pixel, but\nsaved.tsx opened a SECOND raw <WebView> to the same site with no injected tracker\nblock and no URL filter — so trackers ran freely there and an App Store\n'Data Not Collected' label would have been FALSE.\n\nThe leak happened because hardening was a per-call-site ritual that had to be\nremembered. A DTD panel ruled A (complete the blocking) 4/7, and the contrarian's\nsurviving objection was that A-as-scoped repeats the same failure mode. So this is\nstructural rather than another copy-paste:\n\n- components/TrackedWebView.tsx is now the ONLY file importing the WebView\n  component. It always applies TRACKER_BLOCK and the isTrackerUrl filter. A caller's\n  injectedJavaScriptBeforeContentLoaded is APPENDED (never replaces the block), and a\n  caller's onShouldStartLoadWithRequest is AND-ed (can restrict, never loosen).\n- browse.tsx and saved.tsx both render <TrackedWebView>; browse keeps WebView only\n  as a type import for useRef<WebView>.\n- lib/webview-guard.test.mjs fails the suite if any file outside the wrapper imports\n  the WebView component or renders a raw <WebView>. It caught a real miss on its\n  first run.\n\nAlso stops handing a device identifier to a third party for a feature that cannot\nwork: getExpoPushTokenAsync() round-trips to Expo's servers, but the backend\nregistration POST is still commented out, so no alert can ever be delivered. Token\nminting is now gated behind PUSH_BACKEND_READY (false), to be flipped in the same\nchange that wires the backend. Permission is still requested, so the Alerts toggle\nis unchanged.\n\ntsc --noEmit clean; 5/5 tests pass. Local only — no deploy, no submit.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01QfGYEoLBywwJD1nfrHe1on"},{"hash":"92f06c15","date":"2026-09-03 17:24:38 -0700","author":"Steve","subject":"mobile: make Alerts an inert coming-soon state — disable the toggle + stop it requesting notification permission until push is wired (App Store 2.1/5.1.1v hardening, TK-11155)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01CQi5poZk3YeUbEJbDcFRmc"},{"hash":"754ecba2","date":"2026-09-03 15:49:11 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-03T15:45:08 (1 data files) — apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-FINAL-2026-09-03T22-28-13Z.mov.privacy-reviewed.sha256","body":""},{"hash":"edbd4388","date":"2026-09-03 15:07:07 -0700","author":"Steve","subject":"web: large mobile-only brand logo leading the homepage hero (TK-11155)","body":"Add a transparent-background house-mark SVG (public/logo-mark.svg, brand\nnavy/orange, distinct from icon.svg's opaque app-icon variant) and render\nit large (h-36 w-36, capped at 55vw) with the \"Homes on Spec\" wordmark\nabove the existing \"Every new home...\" headline in the hero. Scoped with\nsm:hidden so desktop/tablet layout is unchanged; only mobile viewports get\nthe large lead logo.\n\nVerified: pnpm --filter @homesonspec/web build succeeds (Next 16 compiles,\ntypechecks, and generates all routes). Not deployed.\n\nReversible: git revert this commit."},{"hash":"b233a41e","date":"2026-09-03 15:06:59 -0700","author":"Steve","subject":"web: disable the site-wide InstallPrompt PWA banner (TK-11155)","body":"Comment out the import + usage of <InstallPrompt /> in layout.tsx rather\nthan deleting the component, so re-enabling the \"Add to Home Screen\" nag\nlater is a 2-line uncomment. The native iOS/Android app is now the install\npath Homes on Spec is steering users toward.\n\nReversible: uncomment the two marked lines in layout.tsx, or git revert."},{"hash":"e387ffba","date":"2026-09-03 15:06:53 -0700","author":"Steve","subject":"web+mobile: stable data-hos-install-prompt hook + hide banner in app WebView (TK-11155)","body":"- InstallPrompt.tsx: add data-hos-install-prompt to the dialog root so other\n  surfaces (the mobile app's WebView CSS) can target it reliably without\n  depending on Tailwind utility classes that may change.\n- browse.tsx: add \"[data-hos-install-prompt] { display: none !important; }\"\n  to the injected CSS so the site's \"Add to Home Screen\" banner never shows\n  inside the native app's Browse WebView (the native app already IS the\n  install; the web nag is redundant/confusing there).\n\nReversible: git revert this commit."},{"hash":"9b533a90","date":"2026-09-03 15:06:44 -0700","author":"Steve","subject":"mobile: regenerate splash.png as a large full-screen brand logo (TK-11155)","body":"Original splash.png was a full-screen navy canvas with a tiny centered\nhouse-icon + wordmark glyph (content occupied only ~33% of screen height).\nRegenerated the asset by extracting the icon and wordmark artwork and\nupscaling/recentering them to ~88-92% of screen width so the logo reads as\nlarge/full-screen-prominent on launch, keeping the same #1a3a6e background\nand expo-splash-screen config (resizeMode: contain, no app.json change\nneeded since the asset itself now fills the frame).\n\nReversible: git revert this commit to restore the small-glyph asset."},{"hash":"13331cf6","date":"2026-09-03 15:02:34 -0700","author":"Steve","subject":"snapshot before TK-11155 logo/PWA-banner sweep (pre-existing: tracker-policy extraction, debug-log cleanup, device capture evidence)","body":""},{"hash":"febf413e","date":"2026-09-03 14:33:35 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-03T14:29:24 (1 data files) — apps/mobile/device-proof-evidence/homesonspec-DEVICE-journey-2026-09-03T21-10-10Z.mov.privacy-reviewed.sha256","body":""},{"hash":"e44f3a5f","date":"2026-09-03 13:58:56 -0700","author":"Steve","subject":"fix(homesonspec-mobile): add missing app/index.tsx to fix infinite splash hang on real device (TK-11155)","body":"Root cause: expo-router had no route matching the root path (\"/\"), so\nresolving the initial deep link (homesonspec:///) fell through to the\ninternal \"+not-found\" fallback - which has no screen actually registered\nin the root Stack (app/_layout.tsx only declares the \"(tabs)\" group).\nReact Navigation's NavigationContainer never reaches a resolvable ready\nstate, so it renders its `fallback` (null) forever and the native splash\nscreen (SplashScreen.hideAsync() never gets a chance to matter) never\nclears. No JS error, no crash - a silent, permanent hang confirmed via\ndevice syslog breadcrumbs tracing execution through registerRootComponent\n-> ExpoRoot -> getInitialURL -> getStateFromURL (resolved to \"+not-found\")\nand then nothing further for 60+ seconds.\n\nFix: add app/index.tsx that <Redirect>s \"/\" to \"/(tabs)/browse\", giving\nthe router an always-resolvable initial route. Verified on a physical\niPhone (from-source Release build): app now reaches Browse tab with real\nhomesonspec.com content and Map tab with live Apple Maps pins."},{"hash":"3a545346","date":"2026-09-03 13:53:02 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-03T13:47:49 (2 data files) — apps/mobile/package.json pnpm-lock.yaml","body":""},{"hash":"6f64cefe","date":"2026-09-03 13:40:54 -0700","author":"Steve","subject":"snapshot before TK-10337 rejection debugging","body":""},{"hash":"9e884ed1","date":"2026-09-03 13:01:01 -0700","author":"Steve","subject":"HoS mobile: block GTM/GA4/Facebook trackers in Browse WebView (Guideline 2.1 Path B)","body":"Neutralizes analytics/ad trackers in the homesonspec.com WebView so the app\ntruthfully collects nothing: stubs gtag/dataLayer/fbq/google_tag_manager and\nintercepts every WKWebView-exposed network primitive (createElement src,\nImage/img pixels incl. the FB <noscript> fallback, fetch, XHR, sendBeacon) plus\na native onShouldStartLoadWithRequest host filter. Verified via WebKit network\ncapture: 5 tracker requests before, 0 after.\n\nAlso replaces the SET-AT-EAS-INIT push projectId placeholder with the real id\nfrom expo-constants (token stays local; no backend POST, no new data collection).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01YNivxV68DdxP1rvhViBfcN"},{"hash":"6a1800df","date":"2026-09-02 13:53:14 -0700","author":"Steve Abrams","subject":"TK-11125: e2e-proof evidence bundle for the payload guard (R3, scratch DB)","body":"Verdict PASS. 5 data-boundary cases + unit tests, all green on\nhomesonspec_test. Records the no-migration design divergence + gates\nrespected (no prod write/deploy/push).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_016JRL7REtkaBnRrfHk42iYm"},{"hash":"319168ad","date":"2026-09-02 13:52:12 -0700","author":"Steve Abrams","subject":"TK-11125: forward-only spurious-generation guard in extractStage","body":"Skip minting a new StagedRecord generation + SourceEvidence set when a new\nraw snapshot carries byte-identical extracted property data as the latest\ngeneration (only volatile page bytes moved). Compares a canonical payload\nhash against the latest prior-snapshot generation, whose payload is already\nstored verbatim in StagedRecord.payload — so NO new column and NO migration.\n\nPreserves history: every genuine field change still mints a generation, and\na same-snapshot FORCE_REEXTRACT still updates in place (snapshotId-equality\nescape). Guard can only ever skip an exact duplicate, never a real change.\nProven at the DB boundary in payload-guard.itest.ts (5 cases, all green).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_016JRL7REtkaBnRrfHk42iYm"},{"hash":"671fbb26","date":"2026-09-02 13:49:09 -0700","author":"Steve Abrams","subject":"TK-11125: add stableStringify + payloadHash canonical hash helper (shared)","body":"Forward-only dedup guard support (spec TK-11125-impl-spec-2026-09-02).\nCanonical key-sorted JSON hash so a payload compared across a Postgres\nJSONB round-trip is order-independent; any real change yields a different\nhash (guard can only ever SKIP on exact match, never suppress a change).\nPure code, no schema change.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_016JRL7REtkaBnRrfHk42iYm"},{"hash":"c97856c6","date":"2026-09-01 17:52:53 -0700","author":"Steve","subject":"defer SourceEvidence reindex pending bloat evidence","body":""},{"hash":"b9a44831","date":"2026-08-31 14:50:14 -0700","author":"Steve","subject":"TK-10878: monthly SourceEvidence VACUUM/REINDEX maintenance (ready+gated) + plan PHASE 3 delivered","body":""},{"hash":"ddb43e67","date":"2026-08-31 12:14:11 -0700","author":"Steve","subject":"TK-10878 Phase 2 drafts: fix 3 fatal + 2 hardening review findings","body":"- p_type: 'native' is invalid; default to 'range' (v5) with flagged v4\n  'partman' fallback + a version preflight block (SELECT extversion)\n- identifier casing: embed quotes 'public.\"SourceEvidence\"' in every\n  partman call and the retention part_config UPDATE (unquoted folds to\n  lowercase and silently no-ops the provenance guard)\n- backfill exit: CALL does not set ROW_COUNT; loop on count(*) of\n  SourceEvidence_old instead\n- rollback: replace ON CONFLICT (\"id\") (errors under the new\n  (\"id\",\"createdAt\") PK) with plain INSERT..SELECT back into _old\n- import-sweep guard: abort on recent inserts + note the RENAME is the one\n  genuinely-blocking step; pause the sweep first\n\nDrafts only; not applied.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"21b9aad6","date":"2026-08-31 12:05:05 -0700","author":"Steve","subject":"docs(homesonspec): TK-10878 Phase 2 drafts — pg_partman SourceEvidence conversion + provenance-safe retention (not applied)","body":"Draft-only: monthly RANGE partitions on createdAt with batched backfill and\nfull rollback path; retention DISABLED for SourceEvidence (provenance not\nprunable) with a manual detach-to-archive review procedure. Plan file updated\nwith a Phase-2 preflight checklist. No prod touch, no migration run.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7114f6a3","date":"2026-08-31 00:15:48 -0700","author":"Steve","subject":"fix(homesonspec): summit planName cuts at first '<' — no raw markup on fresh crawls (TK-10487)","body":"Investigating summit ingest rejects surfaced 166 historical staged records whose\nplanName carried raw markup (e.g. \"Riverside Collection ... <div class=\\\"label\\\"\ndata-reac\") — an INCOMPLETE tag opener that survives plainText's tag-strip when the\n3000-char overview slice ends mid-element. The earlier end-anchored fix (<[^>]*$)\nmissed the mid-string case. Now planName is cut at the first '<' (a plan name never\ncontains markup), so fresh crawls emit clean plan names.\n\nNOTE: the ingest rejects themselves are NOT this bug — they are the dup.no-active-\naddress integrity rule correctly rejecting re-crawls of addresses that already have\nan active inventory record (summit had 816 from prior runs). The pipeline dedup gate\nis working as designed. tsc 0; vitest 11/11.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"8ffc6951","date":"2026-08-30 23:48:42 -0700","author":"Steve","subject":"fix(homesonspec): correct 2 more dead CPG seed termsUrls (brohn/summit /terms-of-use 404 -> claytonhomes.com/legal) (TK-10487)","body":"A full seed-URL reachability sweep found brohn + summit still carried the\npattern-guessed /terms-of-use that 404s (same class as the oakwood dead-domain +\nmungo/chafin fixes). Repointed both to the footer-verified Clayton parent legal\n(brohn's footer explicitly delegates there). All 13 CPG seed website+termsUrl now\nresolve 200. Full monorepo typecheck clean.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"a0335075","date":"2026-08-30 23:35:23 -0700","author":"Steve","subject":"test(homesonspec): backfill committed selftests for brohn/oakwood/summit — all 13 CPG collectors now fixture-verified (TK-10487)","body":"The 3 batch-1 CPG collectors were live-verified only (no committed fixture-backed\nselftests). Backfilled to the hardened standard (12 real fixtures each, --compressed,\ndistinct addr+id, honest nulls asserted directly, exact-value snapshot, status\nbranches exercised, schema-valid; secrets scrubbed to placeholders — incl a Segment\nanalytics _writeKey in brohn that gitleaks correctly blocked on the first attempt):\n- brohn-homes: 11 tests, 12 TX homes, MOVE_IN_READY+UNDER_CONSTRUCTION + SF/TOWNHOME.\n- oakwood-homes: 13 tests, 12 Colorado homes (oakwoodhomesco.com confirmed live; the\n  dead seed domain oakwoodhomesnc.com fixed separately). +export classifyStatus/toIsoDate.\n- summit-homes-kc: 11 tests, 12 KS/MO homes. REAL BUG FIXED: plan-name parse swallowed\n  raw markup at a 3000-char slice boundary (home-05/12) -> dangling-tag strip in\n  plainText + a regression test. +export parseHome/isDetailUrl.\n\nWhole-workspace typecheck clean. Now 13/13 CPG collectors have committed selftests.\nLocal build/test only; ingest/seed/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"3a4b583a","date":"2026-08-30 23:32:13 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T23:23:51 (5 data files) — collectors/brohn-homes/.gitignore collectors/brohn-homes/fixtures/homes/_urls.tsv collectors/oakwood-homes/.gitignore collectors/oakwood-homes/fixtures/homes/_urls.tsv collectors/summit-homes-kc/fixtures/homes/_urls.tsv","body":""},{"hash":"57212c91","date":"2026-08-30 23:19:21 -0700","author":"Steve","subject":"fix(homesonspec): correct dead Oakwood seed domain oakwoodhomesnc.com(000) -> live oakwoodhomesco.com (TK-10487)","body":"The seed pointed Oakwood at oakwoodhomesnc.com which is dead (HTTP 000); the live\nadapter target + real CPG Oakwood site is oakwoodhomesco.com (Colorado, Clayton-\nreferenced). termsUrl repointed to the footer-verified Clayton parent legal.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7fd3d9fe","date":"2026-08-30 23:18:03 -0700","author":"Steve Abrams","subject":"docs(homesonspec): wire offload-preflight as mandatory step-0 GO/NO-GO in the runbook — toolkit now turnkey (TK-10809)","body":""},{"hash":"c3626f01","date":"2026-08-30 23:14:54 -0700","author":"Steve","subject":"docs(homesonspec): record completed 13-brand CPG roster + corrected authoritative list (TK-10487)","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"bab4fd97","date":"2026-08-30 23:05:37 -0700","author":"Steve Abrams","subject":"ops(homesonspec): read-only offload PREFLIGHT GO/NO-GO — dest capacity + live-crawl + DB checks before the gated 155G rsync (TK-10809)","body":""},{"hash":"235736eb","date":"2026-08-30 21:06:30 -0700","author":"Steve","subject":"fix(homesonspec): berkeley-building — real self-card isolation test + status docs (TK-10487 Cody gate)","body":"Cody confirmed the extraction is CORRECT (exact-path self-card status does not\ncross-contaminate siblings; no fabrication; garage/lot genuinely absent; ids\ndistinct) but flagged test-quality holes:\n- The sibling-isolation test was a DECOY — it proved isolation only against a\n  NONEXISTENT path. Replaced with a same-page multi-path test (home-08): each real\n  sibling path resolves to its OWN card's verified label+status (2069 -> Available\n  December 2026/UNDER_CONSTRUCTION vs 2079 -> Model Home/MOVE_IN_READY on the SAME\n  html) — genuine exact-path isolation. Proven to FAIL when the match is loosened\n  (it then grabs the first card). It also exercises the Pending->null +\n  Available<Month>/Under-Construction + Quick-Move-In/Model branches END-TO-END,\n  which no PRIMARY-home fixture triggers.\n- Documented the \"Model Home -> MOVE_IN_READY with price=null\" semantic tension\n  (constructionStatus claim, NOT sales-availability) so downstream never renders it\n  as \"for sale now\".\n- Documented the HomeCard__status immediate-child platform assumption (a break is a\n  LOUD cov.status===n failure, not a silent mislabel).\n\ntsc 0; vitest 11/11. Local build/test only; ingest/seed/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"6343338a","date":"2026-08-30 20:58:31 -0700","author":"Steve Abrams","subject":"test(homesonspec): wire ops/*.test.mjs into npm test — snapshot-offload safety suite now runs in regression (TK-10809)","body":""},{"hash":"c57211a8","date":"2026-08-30 20:58:13 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T20:54:26 (1 data files) — pnpm-lock.yaml","body":""},{"hash":"669907fb","date":"2026-08-30 15:07:12 -0700","author":"Steve","subject":"add collision-safe Florida Highland collector","body":""},{"hash":"78e4b4f1","date":"2026-08-30 14:28:08 -0700","author":"Steve","subject":"add Berkeley Building CPG collector","body":""},{"hash":"189933f6","date":"2026-08-30 13:31:36 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T13:27:48 (5 data files) — collectors/berkeley-building/.gitignore collectors/berkeley-building/fixtures/homes/_urls.tsv collectors/berkeley-building/package.json collectors/berkeley-building/tsconfig.json pnpm-lock.yaml","body":""},{"hash":"9eb0f321","date":"2026-08-30 12:47:46 -0700","author":"Steve","subject":"wire(homesonspec): register Elite Homes (Louisville KY CPG) collector (TK-10487)","body":"Add elite-homes-site to the worker CLI ADAPTERS map + workers deps + the CPG seed\n(website elitebuilthomes.com, Kentucky/Louisville, claytonhomes.com/legal termsUrl).\nworkers + database typecheck exit 0. Ingest/seed-run/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"d575caa2","date":"2026-08-30 12:46:55 -0700","author":"Steve","subject":"feat(homesonspec): Elite Homes (Louisville KY CPG) collector (TK-10487)","body":"Cycle-4 CPG collector for Elite Homes (Clayton Properties Group; JSON-LD\nbrand.name=\"Elite Homes\"; Louisville KY metro). Source elitebuilthomes.com — same\nReact-SSR JSON-LD platform as arbor/silverthorne (--compressed required). 10 real\nhomes (all of the sitemap inventory), 10 distinct addr+id (N=10 makes the distinct\nassertions genuinely bite), JSON-LD SingleFamilyResidence + HomeOverview.\n\nCody gate caught a REAL data-quality BLOCKER (not just test hardening): status was\nread from the HomeOverview_lead marketing prose, which carries the phrase on only\n2/10 homes -> 8/10 genuinely MOVE_IN_READY homes were emitted status=null, hidden\nbehind a toothless `<= n` assertion. Fixed: status now reads the authoritative\nCarousel_h2Lead banner (\"Move-In Ready\"/\"Quick Move-In\" present on all 10),\nprose as fallback. Test now asserts cov.status===n + pins status in the snapshot +\nstrips BOTH sources for the semantic-null proof — all proven to fail on the old bug.\n\nSelector drift vs siblings: 3-seg detail URL, garage honest-null (Elite omits it),\nstatus from Carousel banner. tsc 0; vitest 10/10. Local build/test only; gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"c233bfe9","date":"2026-08-30 12:01:51 -0700","author":"Steve","subject":"wire(homesonspec): register Silverthorne Homes (Indianapolis CPG) collector (TK-10487)","body":"Add silverthorne-site to the worker CLI ADAPTERS map + workers deps + the CPG seed\n(website silverthornehomes.com, Indiana luxury, claytonhomes.com/legal termsUrl).\nworkers + database typecheck exit 0. Ingest/seed-run/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"f0d509ba","date":"2026-08-30 12:00:50 -0700","author":"Steve","subject":"feat(homesonspec): Silverthorne Homes (Indianapolis CPG) collector (TK-10487)","body":"Cycle-3 CPG collector for Silverthorne Homes — Arbor's luxury sister company\n(JSON-LD sameAs -> linkedin.com/company/yourarborhome), Indianapolis, Clayton\nProperties Group. Source silverthornehomes.com (same React-SSR JSON-LD platform\nas arbor; --compressed required — server gzips). Identity verified: Indiana\n(McCordsville), not the Colorado town.\n\nSource of truth = per-home JSON-LD SingleFamilyResidence/Product + HomeOverview\nspec list. HONEST inventory: Silverthorne is low-volume luxury — the sitemap\nlists exactly 1 available spec home; captured it, floor set to the real count (1),\nNOT padded. Selector drift from arbor handled: garage IS published here (parsed,\nnot null'd); status derived from HomeOverview_lead prose via statusFromLead.\n\nCody gate: extraction confirmed correct + inventory-honest; N=1 test-quality holes\nfixed — added an exact-value SNAPSHOT test pinning all 14 fields (coverage-% is\ntautological at N=1; the snapshot is what actually catches a misparse — proven to\nfail on a flipped value), documented the N=1 distinct-assertion limitation, and\ndisclosed the GTM scrub in _urls.tsv. tsc 0; vitest 10/10. Local build/test only;\nwiring/ingest/seed/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"86d5d249","date":"2026-08-30 11:22:02 -0700","author":"Steve","subject":"wire(homesonspec): register Arbor Homes (Indianapolis CPG) collector (TK-10487)","body":"Add arbor-site to the worker CLI ADAPTERS map + workers deps + the CPG seed\n(website yourarborhome.com, coverage IN/KY/OH, footer-verified claytonhomes.com\n/legal termsUrl). workers + database typecheck exit 0. Ingest/seed-run/deploy gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"9e1cea83","date":"2026-08-30 11:19:46 -0700","author":"Steve","subject":"feat(homesonspec): Arbor Homes (Indianapolis CPG) collector — yourarborhome.com (TK-10487)","body":"Cycle-2 CPG collector for the REAL Arbor Homes (the Indianapolis builder Clayton\nProperties Group acquired 2018), source yourarborhome.com. A prior build against\narborhomes.com was DISCARDED — that domain is an unrelated Oregon builder (2 homes,\nno CPG affiliation); research (claytonhomes.com press release) confirmed the CPG\nArbor is yourarborhome.com + its sister Silverthorne is silverthornehomes.com.\n\nSource of truth = per-home JSON-LD SingleFamilyResidence/Product (address/geo/price\nvia offers[].price/community via containedIn.name/id via 24-hex productId), with\nbeds/baths/sqft/stories/lot from the PRIMARY home's HomeOverview list (never sibling\nHomeCard blocks). 12 fixtures across IN/KY/OH, 12 distinct addr+id, schema-clean.\nHonest nulls: garage (0%, no field), price (Model + coming-soon homes), status\n(Model home). state/ZIP3 guard nulls a self-contradictory ZIP.\n\nCody gate: core parser confirmed contamination-free; test-coverage holes fixed —\nadded a direct semantic-null assertion on the real Model fixture + a statusFromLead\nunit test exercising the PLANNED/UNDER_CONSTRUCTION branches no fixture triggers\n(both proven to fail on an injected bug). tsc exit 0; vitest 8/8. Local build/test\nonly; wiring/ingest/seed/deploy gated. sitemap has 112 homes.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"99a991d9","date":"2026-08-30 11:13:44 -0700","author":"Steve","subject":"add Legacy Alabama CPG collector","body":""},{"hash":"ea4d4aee","date":"2026-08-30 11:01:32 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T10:57:58 (3 data files) — collectors/arbor/fixtures/homes/_urls.tsv collectors/arbor/package.json collectors/arbor/tsconfig.json","body":""},{"hash":"ab1b4f74","date":"2026-08-30 10:13:18 -0700","author":"Steve","subject":"fix(homesonspec): harris-doyle uses authoritative dataLayer, not fragile DOM (TK-10487 Cody gate)","body":"Contrarian red-team caught the just-registered adapter shipping 3 data-quality\ndefects; all reproduced against the captured fixtures and fixed:\n- community name: the hero community-anchor regex was DEAD (hrefs are\n  entity-encoded &#x2f;, not /), silently falling back to a slug titlecaser that\n  mis-cased prepositions (\"Foothills At Blackridge\"). Now read from the builder's\n  own dataLayer (\"113 | The Foothills at Blackridge\") -> correct case. Slug\n  fallback also fixed to keep joining words lowercase.\n- status: was a hardcoded MOVE_IN_READY constant with a truthy-only \"coverage\"\n  test (theater). Now mapped from the dataLayer pageType via statusFromPageType()\n  (honest null when absent); the selftest proves it goes null on an unknown type.\n- zip: the site itself publishes a wrong ZIP on some homes (AL homes tagged FL's\n  32542). Added a state<->ZIP3 guard (AL/FL, extensible) that nulls a ZIP that\n  can't belong to the parsed state — honest null over known-wrong data.\n\nNew selftest asserts community names are naturally cased (no mid-name title-cased\nprepositions). tsc --noEmit exit 0; vitest 5/5. Facts-only; local build/test only;\ningest/seed/deploy remain gated.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"473713d5","date":"2026-08-30 10:11:11 -0700","author":"Steve","subject":"Build and register Harris Doyle collector","body":""},{"hash":"73a0e94f","date":"2026-08-30 09:48:06 -0700","author":"Claude","subject":"TK-10809: snapshot-verify catches DEST symlink false-green before gated delete","body":"FIX1: lstatSync existence check + distinct 'symlink' verdict; never hash-follow a\nDEST symlink (rsync -a preserves links; a link back to SRC would sha-match and score\n'ok' right before a gated delete wipes the real file). Wired through offload-snapshots\ncounters/summary/slice-print/clean-check so a symlink forces clean=false.\nFIX2: +2 tests — absolute-under-SRC legacy-row shape classifies 'ok'; DEST symlink\nback to SRC classifies 'symlink' (not 'ok') and proves non-destructive (SRC intact).\nFIX3: explicit BLOCKING breakdown line (miss/mismatch/symlink/abs-not-under-src).\nFIX4: runbook — step 5 is a prerequisite for step 4 PASSING when abs-not-under-src>0."},{"hash":"915bd9c2","date":"2026-08-30 09:42:48 -0700","author":"steve","subject":"TK-10809: add ops/OFFLOAD-RUNBOOK.md (SAFE prod offload ordering, deletion hard-blocked until legacy-abs rewrite)","body":""},{"hash":"66e0038c","date":"2026-08-30 09:42:41 -0700","author":"steve","subject":"TK-10809: extract classifyForDest pure fn + hermetic verify test; offload VERIFY loop calls it (no behavior change)","body":""},{"hash":"a7904b1e","date":"2026-08-30 09:39:38 -0700","author":"Steve","subject":"fix(homesonspec): correct fabricated CPG termsUrls in seed (TK-10487)","body":"Mungo /terms-of-use and Chafin /terms-of-use/ were pattern-guessed by the\nbatch-2 build loop and both 404. Replaced with footer-verified 200 URLs:\nmungo.com/legal and the CPG-parent claytonhomes.com/legal that Chafin's own\nfooter links (Chafin has no standalone terms page). Facts-only; seed is not\nexecuted (seeding stays Steve-gated).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"ef253225","date":"2026-08-30 09:38:45 -0700","author":"Steve","subject":"Record TK-10809 post-commit proof","body":""},{"hash":"cece23b9","date":"2026-08-30 09:38:19 -0700","author":"Steve","subject":"Harden snapshot offload verification boundaries (TK-10809)","body":""},{"hash":"ca3c67b3","date":"2026-08-30 09:35:29 -0700","author":"Steve","subject":"Add Mungo and Chafin CPG collectors","body":""},{"hash":"aff87907","date":"2026-08-30 09:33:50 -0700","author":"Steve","subject":"TK-10809: offload VERIFY — no false PASS on a sample, tally wrong --src","body":"FIX1: show sampled/total + coverage% of the WHOLE store; a clean SAMPLE prints\n'VERIFY PASS (SAMPLE ONLY …)' + a loud not-proof warning and, under --apply, exits\n3 (distinct) so automation can't treat sample-only as a proven-clean copy. FULL+clean=0,\nany dirty=1. FIX3: count abs-not-under-src rows separately (wrong --src, not a copy\nfailure) so the operator can tell it from an incomplete copy. Uses the shared resolver.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"d461d39d","date":"2026-08-30 09:33:39 -0700","author":"Steve","subject":"TK-10809: single shared snapshot-path resolver (ops/lib), kill 3-way drift","body":"Extract relTail/resolveAgainstDir/resolveAgainstDest into ops/lib/snapshot-resolve.mjs\nand import it in verify-snapshots.mjs + backfill-drh-geo-from-cache.mjs. Make the TS\nresolveSnapshotPath strip the same var/snapshots/ tail so it stops lying about being\nthe single source of truth; add the tail-strip test.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"58cad18e","date":"2026-08-30 09:23:40 -0700","author":"steve","subject":"ops: non-destructive snapshot offload+verify script + read-only integrity canary (TK-10809)","body":""},{"hash":"0a7e6b34","date":"2026-08-30 09:20:32 -0700","author":"steve","subject":"snapshot store: store storagePath relative + resolver for volume-move safety (TK-10809)","body":""},{"hash":"7d5c17e6","date":"2026-08-27 10:23:25 -0700","author":"Steve","subject":"pm2(homesonspec): node-direct next start so pm2 owns the port-holder PID (TK-10499)","body":"Convert homesonspec-web/admin from 'pnpm --filter ... start' (pm2 owns the\nwrapper; orphaned next-server squats the port -> restart EADDRINUSE-crash-loops,\nTK-10001 / usre 186519x) to node-direct 'next start -p <port>' in BOTH the\ncommitted ecosystem.config.js and the authoritative deploy-kamatera.sh. Also\ncorrect ecosystem ports 3100/3101 -> real prod 9975/9976. Local source only;\nKamatera re-register is drafted+gated (pending-approval/TK-10499-*).\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"7312ce4b","date":"2026-08-26 10:40:08 -0700","author":"Steve","subject":"docs(homesonspec): STEP 1 APPLIED on prod — record actual method + 2 gotchas (owner-role DDL, deploy over-applies add_inventory_specs) + flag 24-day pending migration (TK-10878)","body":""},{"hash":"019ab4e8","date":"2026-08-26 10:23:08 -0700","author":"Steve","subject":"docs(homesonspec): O3 partition + add-disk plan (Steve-chosen path) — disk-first ordering, pg_partman raw-SQL migration, size canary (TK-10878)","body":""},{"hash":"26657d75","date":"2026-08-26 10:18:36 -0700","author":"Steve","subject":"docs(homesonspec): runbook reflects measured reality — index-drop is real STEP 1, corrected apply/verify/rollback (TK-10878)","body":""},{"hash":"63fa1185","date":"2026-08-26 10:17:55 -0700","author":"Steve","subject":"db(homesonspec): guard TK-10878 index-drop with lock_timeout=3s; document CONCURRENTLY-vs-Prisma-txn tradeoff + concurrent-rebuild rollback (codex review)","body":""},{"hash":"bd19db5f","date":"2026-08-26 09:59:59 -0700","author":"Steve","subject":"db(homesonspec): drop unused ValidationEvent(ruleId,passed) index — 0 scans, ~508MB reclaim (TK-10878)","body":""},{"hash":"5f8d356c","date":"2026-08-26 09:52:36 -0700","author":"Steve","subject":"ops: DB reclaim runbook (TK-10878) — O1 dedup + O3 partition + revised-O2 truncate; O2-null rejected (public page renders the columns); Cody disk-abort gate","body":""},{"hash":"67563c20","date":"2026-08-24 04:45:19 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-24T04:45:01 (6 data files) — collectors/chafin-communities/package.json collectors/chafin-communities/selftest-live.mts collectors/chafin-communities/tsconfig.json collectors/mungo-homes/package.json collectors/mungo-homes/tsconfig.json","body":""},{"hash":"e1d685cb","date":"2026-08-17 04:24:34 -0700","author":"steve@designerwallcoverings.com","subject":"Add Summit Homes KC collector adapter (TK-10487 batch 2)","body":"- New adapter collectors/summit-homes-kc: React SSR site-scraper using\n  JSON-LD (SingleFamilyResidence/Product) + HomeOverview_content text block\n  for beds/baths/sqft/garage/plan/status; gzip-decompressed HTTP GET\n- Wired into workers (cli.ts + package.json dependency)\n- Seed script: seed-cpg-builders.ts registers Builder+SourceRegistry for\n  goodall-homes, brohn-homes, oakwood-homes, summit-homes-kc (batch 1+2)\n- Pipeline test: 40 pages, 80 records published, 0 errors"}]}