- 1. Decision record
- 2. Product thesis
- 3. What we never promise (theory constraints)
- 4. Architecture: three layers
- 5. Schema v2: behaviour and tolerance are different variables
- 6. Scoring v2
- 7. Group audit semantics
- 8. Conceal lattice (disclosure tiers)
- 9. Artifacts (what the user takes away)
- 10. Deferred roadmap (in liquidity order)
- 11. Estate invariants (unchanged)
Decided 2026-06-11, after a five-lens survey (matching theory, market landscape, empirical harmony research, repo intent audit, adversarial engine critique). This document is the product spec. Where code and this document disagree, this document wins until amended.
| Decision | Resolution |
|---|---|
User moment (v1) |
Squad Audit: an already-formed friend group (the UK "second-year cliff") gets a harmony + feasibility audit, an auto-drafted house agreement, and a search brief. Works at n=3 with zero marketplace liquidity. Stranger discovery and solo-joins-household are deferred roadmap. |
Data architecture |
Epistemic-typed server: VerisimDB persistence, ordinary Art. 6 UK GDPR
controller duties accepted, no Art. 9 (special-category) fields by schema
construction. Every stored field carries governance metadata
|
Disclosure model |
Conceal lattice with reciprocity (LinkedIn semantics): tiers T0 band /
T1 relational / T2 full; what A sees of B = |
Target wave |
Jan–May 2027 London second-year wave. One user moment, one university to seed, legal posture resolved by December 2026. |
Bring the group you already have; we’ll tell you if it survives a lease — and hand you the agreement to make sure it does.
UK structure creates an unserved moment: first-years are allocated halls by the university; by January they must form private-market groups of 3–6 with people they met eight weeks earlier, with no compatibility input. No incumbent serves this. SpareRoom owns listings liquidity; flat-mate sits upstream of it — the output of flat-mate is a vetted household plus a search brief to take to the listings market. flat-mate does not host listings, and is not a SpareRoom rival.
These are mathematical results, not engineering choices. Product copy and mechanism design must respect them:
-
Never promise a "stable matching". The roommate market is non-bipartite (Irving’s Stable Roommates problem); stable matchings may not exist, and Chin & Michelen (Jan 2026, arXiv:2601.07612) proved nonexistence is the typical case at scale. Ties (inevitable with integer scores) make even deciding existence NP-complete.
-
Couples void guarantees everywhere (Ronn 1990: NP-complete). Couples are modelled as first-class composite units and always routed through heuristic-plus-verification, never through a guarantee path.
-
Households of 3+ are hedonic games: core-stable partitions may not exist; deciding existence is Σ₂ᵖ-complete; even verifying stability is coNP-complete.
-
Legitimate imports: Roth’s market design (thickness via academic-cycle cohorts, congestion control, safety); blocking-pair detection as a polynomial advisory "stability audit"; deferred acceptance only for the genuinely bipartite solo-joins-household sub-market; Nash-stable welfare-maximising partitions (guaranteed to exist for our symmetric scores) as the engine behind future squad suggestions.
| Layer | Role | Status |
|---|---|---|
Harmony engine |
Predicts whether a set of people will live well together. Pure functions, explainable output. The eHarmony-class layer. |
Built (v2 spec in §5–6 corrects the v1 category error). |
Feasibility engine |
PLACE constraint satisfaction: budget intersection, move-in window, lease length, household size vs HMO licensing. Greenfield — no incumbent owns it. |
v2 splits it out of the harmony score; grows toward commute/borough later. |
Market mechanisms |
Sequenced by liquidity: (1) squad audit (no liquidity needed) → (2) solo top-up discovery with conceal tiers → (3) DA rounds for solo-joins-household → (4) optional batch match days. |
v1 ships only (1). |
Matching rules live in packages/shared/src/matching/ and never in UI
components. The conceal/redaction layer is part of the engine module for the
same reason.
The v1 engine’s core flaw was eliciting one variable per dimension where the underlying relation is directional: my behaviour vs your tolerance of that behaviour. v2 elicits both, behaviour-anchored (frequencies and thresholds, not 1–5 self-ratings, which produce false-similarity matches via social-desirability bias).
| Dimension | Behaviour field | Tolerance field |
|---|---|---|
Cleanliness (shared spaces) |
|
|
Noise |
|
|
Sleep |
|
(similarity-scored; sensitivity folds into noise) |
Guests |
|
|
Smoking |
|
|
Alcohol |
|
|
Social energy |
|
(similarity-scored) |
Pets |
|
|
Conflict style |
|
(moderator: both-avoiders escalates warnings) |
Money norms |
|
Removed: accessibilityNeeds free text. It is Art. 9 health data, and even
a boolean flag indirectly reveals disability (CJEU C-184/20: derived data
liable to reveal a sensitive attribute inherits special-category status).
Instead, the house-agreement draft includes a universal "access & needs —
discuss in person" section for every group unconditionally: zero stored
signal, same conversation prompted. The same pattern applies to guarantor
status (a nationality proxy → indirect-discrimination exposure if stored or
filtered): the search brief carries an unconditional per-member guarantor
checklist item, and the platform stores nothing.
Missing answers are stored as null, scored neutrally (0.5) with a warning —
absence of evidence never scores better than adverse evidence.
Every profile field is registered with
{purpose: identity|feasibility|harmony|agreement, revealingness: low|moderate|sensitive, minTier: 0|1|2}.
This registry is the single source for (a) read-time tier gating on the
server, (b) the conceal lattice’s redaction rules, (c) the future retention
schedule and DPIA documentation. This is Art. 25 data-protection-by-design —
a risk reducer and obligation organiser, not a compliance exemption.
Outstanding obligation before launch: privacy notice, retention schedule, and
a verified VerisimDB erasure + subject-access-export story.
Two sub-scores, never blended into one number (the v1 engine put 40% logistics inside "harmony"):
-
Feasibility (budget ×0.6, move-in timing ×0.25, lease length ×0.15): budget pairs score by overlap coefficient (
overlap / min(spanA, spanB)— containment scores 1.0, fixing the Jaccard penalty on generous budgets). -
Harmony (cleanliness ×0.30, noise ×0.20, guests ×0.15, bedtime ×0.10, smoking ×0.10, drinking ×0.10, social ×0.05): weights seeded from the conflict literature (cleanliness is the #1 predictor and curvilinear — perfectionism also dissolves households; substance mismatch is the only causally identified factor). Weights must assert sum=1 in tests and are config pending the calibration flywheel.
Directional dimensions score
factor = min(fit(A.behaviour → B.tolerance), fit(B.behaviour → A.tolerance))
— the weakest direction governs. Interaction semantics for drinking: heavy ×
intolerant is penalised sharply; heavy × heavy is not penalised at all.
Hard dealbreakers (curated, data-driven rules testing the other’s
behaviour, never their tolerance) force verdict not_viable. The vocabulary
includes both tails of cleanliness (messy and spotless_required).
Headline output is a banded verdict (strong ≥75 / workable 55–74 /
risky <55 / not_viable) — raw integers imply false precision until
calibrated. Full result: {harmony, feasibility, verdict, reasons[],
warnings[], dealbreakers[] (structured), dealbreakerHit, factors,
engineVersion}.
auditGroup(group) is the v1 product core:
-
Full pairwise matrix over members — internal dealbreaker detection. (The v1 demo group "Bloomsbury Three" contained two internal dealbreaker kills no code detected; it is retained as a labelled broken fixture.)
-
Budget by intersection
[max(mins), min(maxes)], never by mean (means manufacture windows no member can pay). Empty intersection = hard warning. -
Dispersion detection instead of mean-of-ordinals: a 2-early-birds
2-night-owls group is reported as split, not "average". -
Shared-space forecast = minimum member cleanliness behaviour (norms converge to the laxest member), naming members whose standard exceeds the group minimum by ≥2 — the predicted friction nodes.
-
Weakest pair named; min and mean pair-harmony reported separately (no magic blend constants).
| Tier | A sees of B |
|---|---|
T0 band |
Verdict band only; dealbreakers fold into |
T1 relational |
+ scores + relationally-phrased reasons ("you two differ on
guest norms"); no habit values, no dealbreaker attribution. Built from the
structured |
T2 full |
Full reasons/warnings/factors with attribution. Default within a consented audit group. |
effectiveTier(a, b) = min(a.tier, b.tier) — reciprocity: you may see at most
what you yourself grant. Documented caveat: redaction cannot prevent
self-inference (a smoker told "smoking is a hard incompatibility between
you" learns the counterpart’s dealbreaker from their own data). This is
inherent to explainable matching and is disclosed, not denied.
-
Audit report — verdicts, named divergences, weakest links (T2 within the group).
-
House agreement draft — auto-generated from the group’s actual divergence points (the one intervention with practitioner consensus), plus universal sections: access & needs, guarantor checklist, money norms, a 48-hour raise-it conflict process, exit/replacement clause (joint-and-several liability makes weakest-link financially literal).
-
Search brief — budget intersection (per-person and household total), move-in window, lease preference, household size with HMO licensing note (mandatory at 5+ occupants; many London boroughs license 3–4), member universities (commute constraint placeholder until geo lands).
-
Solo top-up discovery for groups with open slots — requires conceal tiers (built in v1) and a consent lifecycle (Proposal → mutual accept → Match), restoring the mutuality property the v0 swipe domain had.
-
DA rounds for solo-joins-household — the one theory-clean centralized mechanism (bipartite many-to-one); one vacancy per household per round; couples via heuristic + blocking-pair verification.
-
Calibration flywheel — outcome events (moved in; 1-month / 3-month satisfaction) on properly anonymised aggregates; weights become fitted instead of literature-seeded. One learning cycle per academic year.
-
Blocking-pair stability audit over the discovery graph (advisory, private, bounded to pair deviations).
-
London/borough prefilter re-imposition in candidate generation when discovery lands (the London-only invariant in 0-AI-MANIFEST stands; the audit flow is location-light by nature).
VerisimDB sole persistence; London-first MVP; Deno-first JS-first with the
four-file TS carve-out (AffineScript unblock conditions per
.claude/CLAUDE.md); MPL-2.0; mobile via Expo. The legacy swipe+listings v0
remains in-tree as legacy until a quarantine/removal decision; README no
longer claims "matching + listings in one flow".