0148 — A conflict row with a clear higher-ranked winner fills
- Status: Proposed (Gera's rulings 2026-10-23; for Fede's read)
- Date: 2026-10-23
- Deciders: Gera (rulings G, H and "listings count", 2026-10-23)
- Amends: the ruling cited in code as "agentflow ADR-0059 amendment 2026-10-16" (propflow-ingest's pre-fill contract). No document for that ruling was found in agentflow
docs/adr(its 0059 is a different decision), in propflow-ingest, or on docs.propflowai.co, so this record quotes the ruling text as the code carried it: "a conflict row never fills a field on its own: its values are shown as a dispute for the customer" (src/lib/domain/onboarding/ingest-suggestions.ts, before this change).
Context
propflowai reads ONE item of ingest's: the pre-fill contract (PREFILL#<org> / v1). Until now two rules held:
- Ingest filled only questions the company-records computation (
company-suggestions.ts: AppFolio charges, the buildings' own PMS fees, the listings, the website) never filled, so the two sources never met. - A
conflictrow never filled anything. Its values were shown side by side for the customer to pick.
Western Slope showed what that costs. The application fee had a staff statement ($40 per application, from the onboarding call) and an AppFolio charge ($27, the most common billed amount). Under rule 1 the staff value was never offered. Under rule 2, if it came as a conflict, nothing was offered at all, although one source is plainly better than the other.
Decision
- One resolver decides every pre-fill (
src/lib/domain/prefill/resolve-prefill.tsin propflowai). For each question it gathers every candidate value, from the records computation and from every contract row, including each value inside a conflict row. - Source precedence, written down in one place (
PREFILL_PRECEDENCE), highest first:- staff: what the customer's own team said (ingest
staff) - pms: the PMS's records (ingest
pms; the records computation's values) - document: other emails and documents (ingest
other) - website: the company's own website
- listing: public listings
- staff: what the customer's own team said (ingest
- Fill when the best available source is consistent; listings count. The highest tier present must hold one value (case, spacing and money formatting do not make a difference). Then it fills, down to and including listings (
FILL_FLOOR = 'listing'), with its source shown behind a small (i). If the highest tier disagrees with itself, nothing fills and its values are offered for the person to pick, as before. - A conflict row with a clear higher-ranked winner fills. This is the amendment. The other values are not lost: they appear under the value as "Also found (N)", each with where it came from, plus a hint to update their records if one is out of date.
- Until ingest names the kind of every value in a conflict, an unknown-kind value blocks the fill.
conflicts[]entries carrysource_labelbut nosource_kind, so such a value might outrank the winner. When one differs from the would-be winner, nothing fills and every value is offered to pick. A same-value entry is no disagreement. - One rule for what the hub takes from a row (
ingestRowCandidates): the page, the onboarding gaps count and the save's ingest stamp all read it. A staff statement may now fill any listed question, not only the three phone questions, and a fee row offers one dollar amount read off its words.
Consequences
- Nothing reaches Clara before the customer's save. That is unchanged: the resolver only chooses the starting value in the box.
- The Western Slope application fee pre-fills $40 (staff), with "Also found: $27 from AppFolio › Charges".
- Follow-up for propflow-ingest: emit
source_kindon everyconflicts[]entry. Rule 5 then stops blocking fills that a known lower-ranked value cannot outrank. - Follow-up for propflow-ingest: emit a record pointer (
ref) on every row and fact, so the (i) can link to the AppFolio record (worksheet "source links we can't produce yet"). source_kind: 'history'stays unranked (never wins) until ingest says whether it means PMS history or staff conversations.
Alternatives considered
- Keep "a conflict row never fills". Safe, but it leaves the customer to resolve every conflict by hand even when one side is their own staff's word. Gera ruled against it.
- Newest source date wins. The date says when a source last showed a value, not how trustworthy it is. A fresh listing would beat what staff said last week.
- Listings never fill alone. Rejected by Gera (2026-10-23): listings count, with the source shown.