Who is the post-approval lease block for?
Three decisions left open by PR #5475 (Clara answers approved applicants' lease questions on voice + SMS). Raised 2026-08-06 by the code reviewer and a conformance audit against the locked post-approval-gap answers.
Q1 — The block has no lifecycle gate. Is that intended?
resolveLeaseAnswerContext has three gates: channel (voice or SMS), policy on file (the property has a lease-policy row), and identity (matched to an approved application → personalized facts; otherwise the published-policy half).
There is no gate on lifecycle stage. Nothing asks whether this conversation is post-approval at all. So on SMS the published block renders on any turn where the Leasing body loads — which includes every unknown sender and every cold prospect. On voice it renders at ring, for every caller, before anyone has said a word.
Concretely: a brand-new prospect texting "what's the deposit?" previously got routed to the team. They now get a quoted deposit tier, and their prompt also carries the application fee and the lease → sign → charges sequence, because the written delivery path is unfiltered.
This may well be right — the block calls itself "the SAME answer for everyone who asks", and published policy is by definition public. But the PR's own "what this does NOT do" section doesn't mention it, RULE 7.6 frames the audience as "someone whose application is already decided", and all 20 eval scenarios are post-approval — there is no prospect-stage coverage and no fair-housing run behind it.
Source: agents/clara/lib/agent/lease-answer-context.ts gates 1–3; reviewer Finding 6 on PR #5475.
Q2 — Decision A's second half was never built. Keep it or drop it? ✓ Decided: B — dropped (Fede, 2026-08-07)
Decided 2026-08-07 (Fede): drop it. The settled refusal stands alone; the "offer the 12-month option in the same breath" half is deliberately dropped, on the record — future audits should not re-raise it as a gap.
On 2026-08-05 you and Gera locked qa = A1 for "free month on a 6-month lease":
Hard no. State the special is 12-month-only and offer the 12-month option in the same breath. No escalation, no exception path, no improvised partial concession.
The hard-no half shipped and is solid — evaluateConcession evaluates term eligibility first and returns a settled "…is available on 12-month leases only, so it does not apply to a 6-month lease." No escalation, no exception path. The eval scenario pins it.
The "offer the 12-month option in the same breath" half is not implemented anywhere — not in the rendered reason, not in RULE 7.6, and the judge rubric asks only that the refusal be stated. So a tenant asking for the free month on a 6-month lease is told no, and is not told what they could do instead.
Source: locked post-approval-gap answers (Fede + Gera, 2026-08-05); conformance audit on PR #5475.
Q3 — Does Camellia's policy row say the special applies to transfers?
This one is a data question, not a code question, and it needs Kenya or a look at the stored row.
Kenya's recorded policy (7/8) is that the move-in special applies to new leases including in-house transfers — "we cannot differentiate". The code implements this data-driven: evaluateConcession treats a transfer with appliesToTransfers !== true as not established, so Clara says the property team has to confirm.
That is only a live divergence if Camellia's stored policy row omits appliesToTransfers: true. If it does, a transfer applicant gets an escalation on a question the policy page says is settled — which is precisely the "refusing to answer something that was on file" failure this whole engine exists to remove.
| If the row… | Then |
|---|---|
has appliesToTransfers: true | No divergence. Code and policy agree; close this. |
| omits it | Set it. One data fix, no code change. |
| Kenya's policy has since changed | Update the policy page first, then the row. |
Context: what else came out of this round
Fixed on the PR, listed so the decisions above sit in context — none of these need a call:
- A real leak. The voice call-start path passed no
renewalShaped, so the published block read every move-in special aloud to renewing tenants — routing around the suppression the same route enforces onproperty_concessions. Fixed, and the route site is now pinned by a test like the SMS one already was. - An unbounded ring-time read. The call-start policy read had no timeout; the try/catch only covered a throw, and DynamoDB throttling hangs rather than throws. Now bounded at the same 6s every other call-start read uses.
- A guard that measured nothing. A test claiming to pin the shared capability predicate passed with the symbol deleted entirely. Re-anchored and positive-controlled.
- An eval judge grading the wrong block. On voice scenarios the judge was shown the full written block — deposit tiers, prorated amounts, term dates — and asked whether the answer covered them, while the composer had already filtered them out. The
personalized20/20 figure was therefore not measuring what it claimed. Re-run with the fix on the subscription runner: facts 20/20, judge 20/20, PASSED — so the number holds, and it is now measuring the right block. One judge run is not evidence of an improvement (see the variance note below); what changed is that the leg is finally measuring the right thing. The deterministic spoken-shape checks were unaffected throughout and remain clean.
Worth carrying forward: the judge leg of this harness varies between 17/20 and 20/20 across repeated runs with no code change, per the harness's own documented measurements. Sustained drops and named criteria failing every run are signal; a single-run delta of a few points is not.