The property knowledgebase is the one record that holds everything Clara knows about a property — deposits, fees, specials, utilities, office hours, pet rules, policy prose. Until this week the same facts lived in two rows that could disagree; the change now in review puts them in one row behind one door. This brief shows that change in three diagrams, records what was decided on 8 August, and proposes the shape the whole thing should settle into — every move deleting or collapsing rather than adding.
The same property facts lived in two database rows and different parts of Clara read different ones, with nothing reconciling them.
The typed lease policy moves into the knowledge row and every surface reads it through one resolver, so two answers to the same fee question can no longer exist.
The Willows is the test grounds. The new knowledgebase runs there first, under heavy testing — replaying real production questions and trying hard to make it give a wrong answer. When the results show it answers better than today, it ships. Until a property is switched on, nothing changes for it, and Camellia cannot be switched on by accident.
Measured on the real repo and the real production table on 8 August 2026. Production has exactly four property knowledge records (Camellia House, Yale 25 Station, the test property, and one stub), so “how many properties actually use this?” is a question with a real answer, not a guess.
Renewal terms are stored in three places at once. Camellia's month-to-month premium is 100 on the knowledge row and 100 on the leasing-settings row; its term options are [6, 12] in both. A third, separately-versioned RENEWAL_POLICY# row holds the approval thresholds. The lease-money dual-home was just fixed — this is the same bug, one table over.
Evidence: prod rows PROP#1773625953462 / KNOWLEDGE.renewalPolicy, / LEASING_SETTINGS.renewalTermOptions,renewalMtmPremium, / RENEWAL_POLICY#000002.
renewal* fields from leasing settings, fold the thresholds into renewalPolicy, keep one versioned history.renewalPolicy out of knowledge and make RENEWAL_POLICY# authoritative. More correct on paper, but touches every renewal reader.Net complexity: −2 storage homes, −4 duplicated fields, −1 write path. Adds nothing.
The scraper is the only writer that can quietly destroy facts: it saves the whole knowledge record at once from a local file, so any field that file doesn't know about is gone. It also skips the blessed save path, which is what refreshes the voice agent's cached answers — so a scrape can leave the phone quoting last month's office hours. That is the bug, not the scraping.
Evidence: src/lib/platform/scrapers/registry.ts (2 property configs), scrapers/shared.ts:660 (whole-record save, bypasses the blessed writer), prod scan of all 4 KNOWLEDGE rows.
Net complexity: −1 bypass, −1 whole-record overwrite. Adds one confirmation step on changed values.
Today a property fact can be written down two different ways: as a typed field (a labelled slot like securityDeposit: 500, which Clara's tools and every prompt read) or as a paragraph of free text pasted in from a document (which only some prompts see). Five of the 14 paragraphs in production simply restate a fact that already has a field — and when the two disagree, whichever the prompt happens to list last wins.
Example: Camellia has a typed security deposit and a “Security Deposit” paragraph saying something different. That is exactly where the wrong deposit answer came from. The proposal: paragraphs may still describe things a field can't hold — what the neighbourhood feels like, how to find the office — but every fact (any number, policy or rule) lives only in a field.
Evidence: prod sections[] titles on all 4 KNOWLEDGE rows; typed leaves in PropertyKnowledge.pricingDetails.
Net complexity: −5 duplicate paragraphs, −1 authority conflict. Adds one line of policy, no new code.
The two consolidations above each fix one duplicate. Neither answers the bigger question: at hundreds of properties, what shape should property facts have at all? Today a single property's facts are spread over three kinds of row — a knowledge record, a leasing-settings record, and a stack of versioned renewal-policy records — and only the renewal stack has history, review and rollback. Four candidate shapes:
LEASE_POLICY#, RENEWAL_POLICY#, TURNOVER#… each append-only like the renewal stack is today. Best audit trail, but every reader now has to know which record holds which fact — the exact ambiguity we just spent two PRs deleting — and the voice path would go from one lookup to five.Why D. It is the only option that keeps all three things we actually rely on at once: one place to look for a fact (so no reader ever has to pick), a typed shape (so “this field is empty” can keep meaning “don't answer, hand to a human”), and a real audit trail (so a decision made in June can be replayed against the facts as they stood in June). B buys the audit trail by re-fragmenting the read path; C buys flexibility by giving up the fail-closed behaviour that keeps Clara from inventing deposits. D is also the cheapest from where we stand after the two in-flight changes: the readers do not move at all, only the save path does.
Staging — each stage ships and is proven at The Willows before the next.
Net at the end: 5 kinds of row → 2, and one save path instead of fourteen.
tourUrl is truly dead — it was meant to hold a property's tour-booking link, was never wired to anything, and has been empty on every property since March. lastScrapeError and holidayPolicy are read but populated nowhere — punt-only fields.petPolicy, so any pricing save silently erases an API-set pet policy.A verification stamp (who confirmed this fact, when) and an extract-then-confirm ingestion flow are not proposed here. A verification stamp is largely subsumed by versioning the record (§D gives you who changed what, when and why for free), and extract-then-confirm only earns its place as the same confirm step the wipe-proof scraper needs — one review surface, not two. Revisit after stage 3, not before.