0039 — AppFolio is the source of truth for vendors and tenants (no local fakes in prod)

Context

PropFlow is a mirror of the PMS (AppFolio today; Yardi / RealPage / Entrata later). ADR-0030 established this for work orders (the PMS owns the identifier; PropFlow mirrors). ADR-0023 is the per-field authority registry. ADR-0033 split the vendor model into VendorCompany (counterparty) + VendorMembership (the Person-anchored, org-scoped join carrying isInHouse / role / propertyIds).

Tenants already follow the mirror model: lambda/appfolio-sync pulls occupancies/tenants on a 15-minute schedule, stamps Tenant.af.tenantId / occupancyId / pmsSource, and carries explicit field-ownership rules (e.g. a verified PropFlow phone beats the AppFolio rent-roll phone).

Vendors do not. There is no AppFolio→PropFlow vendor sync. The browser-agent has write L4s (run-create-vendor, run-vendor-phone-set) but no read L4 (get-vendor, list-vendors). So PropFlow's vendor roster is a hand-maintained island, populated only by the Add-Vendor flow, CSV import, or seed scripts.

The consequence, measured against propflow-prod (2026-05-28):

Entity Total AppFolio-linked Fake / demo residue Sync exists?
Vendors 49 1 ~48 (incl. the Denver demo crew, Test Company, Test-Comp) No
Tenants 211 110 ~7 (Denver demo + renewal-gauntlet fixtures) Yes

Fake vendors actively confuse operators — the roster doesn't match what they see in AppFolio (e.g. "ABC Imaging" is in AppFolio, not in PropFlow). Prod should contain only real, PMS-sourced vendors and tenants.

Decision

1. AppFolio owns identity; PropFlow mirrors it and overlays its own facts.

2. Vendors gain a sync (build it); tenants extend the existing one. The vendor sync mirrors the tenant-sync pattern: read L4s → diff against PropFlow keyed on af.vendorId → upsert. Tenants already have the sync; the work there is residue cleanup + an audit of un-linked rows.

3. No local-fake vendors or tenants in prod. Seed/demo data does not belong in propflow-prod. Demo data lives in sandbox/isTest properties or stage. Existing prod fakes are removed (see #4).

4. Removal: archive what was once real; hard-delete pure fakes. AppFolio soft-deletes vendors/tenants (an "Inactive" flag), and PropFlow rows are referenced by work orders, quotes, dispatch history, and Property.handymanVendorIds. The rule splits by provenance:

Implications

Drift guard (to build with the sync)

Alternatives considered

Keep vendors hand-maintained; just clean the fakes once. Rejected — without a sync, the roster drifts from AppFolio again the moment a vendor is added/changed in AppFolio, and the same confusion returns. The sync is what makes "real vendors only" durable, not a one-time scrub.

Hard-delete ALL fake/dropped rows indiscriminately. Rejected — work orders, quotes, and handymanVendorIds reference vendor ids; deleting a referenced row orphans them (mirrors the WorkOrder-history concern in ADR-0030 and the tenant archivedAt precedent). The accepted rule (§4) is narrower: hard-delete only pure-fake, unreferenced rows after a reference check; archive once-real rows; skip referenced rows for an explicit decision.

Per-property vendor pull (derive from WO history). Rejected — AppFolio's vendor list is account-scoped, not property-scoped; a per-property pull silently misses vendors not yet WO'd at that property. The truthful match is a full account-list sync, with Property.handymanVendorIds reconciled from AppFolio's per-property default-vendor association downstream.