[object Object]

← back to Homesonspec

feat(specs): InventoryHome.specs Json foundation — schema + migration + publisher wiring

26f91560bd0b8a1990efade94de45981865184d9 · 2026-08-02 20:32:04 -0700 · Steve Abrams

Adds nullable JSONB specs column (extensible grab-bag) + staged migration
(NOT applied to prod) + publisher persist from a reserved 'specs' payload key
(create + update, specless-extract-never-clobbers guard). The moment any
extractor emits a specs payload field it flows to the DB. Publisher typechecks
clean. One 'prisma migrate deploy' from live — Steve-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 26f91560bd0b8a1990efade94de45981865184d9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 2 20:32:04 2026 -0700

    feat(specs): InventoryHome.specs Json foundation — schema + migration + publisher wiring
    
    Adds nullable JSONB specs column (extensible grab-bag) + staged migration
    (NOT applied to prod) + publisher persist from a reserved 'specs' payload key
    (create + update, specless-extract-never-clobbers guard). The moment any
    extractor emits a specs payload field it flows to the DB. Publisher typechecks
    clean. One 'prisma migrate deploy' from live — Steve-gated.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .../migrations/20260802203133_add_inventory_specs/migration.sql   | 3 +++
 packages/database/prisma/schema.prisma                            | 4 ++++
 packages/publisher/src/index.ts                                   | 8 ++++++++
 3 files changed, 15 insertions(+)

diff --git a/packages/database/prisma/migrations/20260802203133_add_inventory_specs/migration.sql b/packages/database/prisma/migrations/20260802203133_add_inventory_specs/migration.sql
new file mode 100644
index 00000000..74954de1
--- /dev/null
+++ b/packages/database/prisma/migrations/20260802203133_add_inventory_specs/migration.sql
@@ -0,0 +1,3 @@
+-- Add extensible spec grab-bag to InventoryHome (see collectors/SPECS-AUDIT.md).
+-- Nullable JSONB, no default, no backfill — zero-risk additive change.
+ALTER TABLE "InventoryHome" ADD COLUMN "specs" JSONB;
diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma
index ed4e9cd0..d9255fc1 100644
--- a/packages/database/prisma/schema.prisma
+++ b/packages/database/prisma/schema.prisma
@@ -200,6 +200,10 @@ model InventoryHome {
   constructionStatus ConstructionStatus @default(UNDER_CONSTRUCTION)
   estCompletionDate  DateTime?
   availabilityStatus String?
+  specs              Json?    // extensible grab-bag of every extra spec a builder exposes
+                              // (monthly payment, was-price, plan code, multi-gen, lifecycle
+                              // flags, incentives, energy, virtual tour, …). Core filter/sort
+                              // fields stay as typed columns above; this holds the long tail.
   status             PublishStatus @default(PUBLISHED)
   freshness          Freshness     @default(FRESH)
   verificationLabel  VerificationLabel
diff --git a/packages/publisher/src/index.ts b/packages/publisher/src/index.ts
index 7faa196a..89b9a1b5 100644
--- a/packages/publisher/src/index.ts
+++ b/packages/publisher/src/index.ts
@@ -151,6 +151,9 @@ export async function publishStagedRecord(stagedRecordId: string) {
           : null,
         status: "PUBLISHED",
         freshness: "FRESH",
+        // Long-tail specs: extractors emit a reserved "specs" payload field (an object
+        // of everything the builder exposes beyond the core columns). Persisted as-is.
+        specs: (value<Record<string, unknown>>("specs") as never) ?? undefined,
         verificationLabel: label,
         lastVerifiedAt: new Date(),
         sourceId: staged.sourceId,
@@ -169,6 +172,11 @@ export async function publishStagedRecord(stagedRecordId: string) {
         // coordless extract never clobbers a good pin. [yolo iter-4 contrarian fix]
         ...(value<number>("lat") != null ? { lat: value<number>("lat"), lon: value<number>("lon") } : {}),
         images: value<string[]>("images") ?? undefined,
+        // Only overwrite specs when the new extract HAS them — a later specless
+        // extract never clobbers a good spec map (same guard as lat/lon above).
+        ...(value<Record<string, unknown>>("specs") != null
+          ? { specs: value<Record<string, unknown>>("specs") as never }
+          : {}),
         constructionStatus: (value<string>("constructionStatus") as never) ?? undefined,
         estCompletionDate: value<string>("estCompletionDate")
           ? new Date(value<string>("estCompletionDate")!)

← dad73003 docs(specs): audit pass-2 — 14 zeros are unparsed blob shape  ·  back to Homesonspec  ·  feat(specs): dr-horton capture proving slice — extract->vali 7c3609cc →