Result of the "PMS code lives in adapters" chain · all 14 PRs merged as of 2026-10-16 · build check live
As of 2026-10-16, 4:26 AM Mountain. Merge states read from GitHub at that time. Numbers from the PR bodies and the allowlist file on main.
Contents
In one breath: Clara's leasing code now reads our own neutral records (application status, unit id, move-in started, rent-roll and tenant rows) instead of AppFolio's raw report strings, and every step was proven to change nothing on the full live book. A new build check now fails any PR that adds the word "appfolio" outside the PMS layer. 2,812 old mentions in 590 files are recorded as debt that can only shrink.
Fede's rule (2026-10-15): core flows, meaning the engines, cadences, gates, lists and screens, never name a PMS (the property management system, AppFolio today, Yardi tomorrow). Every PMS read or write goes through an adapter, a translator module that turns AppFolio's shapes into ours, living in src/lib/domain/pms. Statuses, ids and events are translated once, where the sync pulls them in. Old mentions are removed in small PRs that change no behavior.
Byte-identical replay means we ran the old and the new code over every live customer's real rows and compared the outputs bit for bit. A mutation control is a deliberately planted change that must make that comparison fail, proving the test can see a difference.
| # | PR | What changed | Proof | State |
|---|---|---|---|---|
| 1 | #20201 | Application completeness (screening done, fee paid, whole group submitted) and the neutral status land on our records at the sync | 694 real rows, identical; control differs | merged c2a62548a |
| 2 | #20340 | Status table covers every word core code used to sniff for | 2,893 statuses identical; control differs | merged 2c73fb67c |
| 3 | #20349 | Staff call-note "unreviewed application" fact reads the neutral status | 372 prospects, 118 = 118 identical; control differs | merged ba22acc5e |
| 4 | #20355 | Application stage reads the neutral status, no substring guessing | 2,521 rows, 0 differ; two controls | merged b4304884d |
| 4b | #20386 | Stage follows Fede's rule: Decision Pending + Converting/Converted = approved (3 prospects move) | Replay: exactly the 5 expected rows change | merged 6d1df2b28 |
| 4c | #20394 | No new-lease review item once the move-in has started in the PMS (no duplicate lease) | 2,521 rows: only Camellia #119 changes | merged e556554f2 |
| 5 | #20356 | One accessor for the PMS unit id in the leasing joins | Every live unit row identical; control differs | merged b1d287671 |
| 6 | #20363 | Address key moved out of the AppFolio scraper into a neutral module | 1,256 addresses, 0 differ; control 60 differ | merged 56fe29d40 |
| 7 | #20368 | Staff-desk lines name the company's own PMS, not "AppFolio" | 870 renders identical; Yardi control 6 of 6 differ | merged fc7aed462 |
| 8 | #20374 | Tool result key queued_for_appfolio becomes queued_for_pms | Fixture eval baseline = branch; Willows run after merge | merged 92c6ec483 |
| 9 | #20372 | Move-in planner reads a neutral rent-roll row | 148 properties, 0 differ; control differs | merged 86584b72c |
| 9b | #20395 | Move-in planner reads a neutral tenant-directory row; no AppFolio imports left in it | 148 properties, 0 differ; control 2 differ | merged db6280336 |
| 10 | #20373 | "Open in AppFolio" reminder link moved into the PMS layer | 1,408 cases identical; control 764 of 764 differ | merged bc2d2e70a |
| 11 | #20387 | Build check forbidding "appfolio" outside the adapter layer | Planted literal goes red; real tree 0 violations | merged 7e3d6a4e8 |
Context, same day: #20194 (merged e1e2f2414) made only an approved application hold a home; 9 homes reopened. #20203 (merged ae0ba8f36) made Decision Pending + Converting count as approved for that hold, at the adapter only; its replay of the live report changed exactly 5 of 2,554 rows.
scripts/check-pms-literals.ts reads every app source file and flags any "appfolio" text or AppFolio import outside the PMS layer. Comments and tests don't count. It runs in npm run review:self and in the CI Lint job. Today's mentions are listed in scripts/check-pms-literals.allowlist.json, and that list is shrink-only: a file's count may go down or disappear, never up, and a file not on the list starts at zero.
| Lane | Files | Mentions | Share of mentions |
|---|---|---|---|
| Leasing | 92 | 198 | |
| Renewals | 41 | 127 | |
| Comms mirror | 50 | 201 | |
| Robot | 5 | 32 | |
| Onboarding | 50 | 240 | |
| Other (tools, maintenance, admin) | 352 | 2,014 | |
| Total | 590 | 2,812 |
Read-only dump, 2026-10-16: 2,521 application rows across the three customer databases (JP&Co 38, Western Slope 757, Situs 1,726). After the status table, no row has an unknown status.
| AppFolio decision | Rows | Our value |
|---|---|---|
| Approved | 1,771 | approved |
| Canceled | 399 | canceled |
| Denied | 307 | denied |
| Decision Pending | 39 | pending |
| Decision Pending + Converting | 5 | approved (adapter, #20203) |
The 4c replay: of 2,521 rows, only Camellia #119 changes outcome. Once 4b merges, she will not get a new-lease review item, because staff already started her move-in in AppFolio.
appfolioTenantIdOpen issues from the build check review: #20392 (one advisory hides three different skip causes), #20393 (exclude the public website and legal subprocessor list). Filed by the chain's builders along the way, not cleanup debt: #20361, #20375, #20376.
Related: testing playbook.