# Roover homeowner kernel — Engineering contract v1.0

**Status:** build-ready product contract; independent QA pending; not deployment authority  
**Contract date:** 20 August 2026  
**Kernel authority:** the journey and truth rules in this document  
**Frozen visual reference:** [`RoofHero_Homeowner_App_kernel_surgical_v4.html`](./RoofHero_Homeowner_App_kernel_surgical_v4.html)  
**Frozen SHA-256:** `a63dc5ef991750b34e46d0627b69c6bf493609cfd84b01a497c56eb63e2d6a04`  
**Hosted visual reference:** <https://roofhero-kernel-review.pages.dev/>  
**Machine contract:** [`Roover_Homeowner_Kernel_State_Acceptance_v1_0.json`](./Roover_Homeowner_Kernel_State_Acceptance_v1_0.json)

The hosted prototype and exact local SHA are frozen visual and interaction references. They prove a fixture kernel, not live integrations, production data, deployment readiness, or current runtime architecture. If this contract and the prototype differ on state meaning, persistence, truth, or handoff rules, this contract controls. Engineering chooses the implementation architecture after mapping these contracts to the current system.

## 1. Executive kernel definition

The Roover homeowner kernel moves one homeowner from an address and uncertain intent to:

1. one defined roofing **Job**;
2. one evolving **Project Room**;
3. one homeowner-confirmed **Job Brief** and versioned **Job Pack**;
4. comparable indicative ranges and formal proposals against that same Job Pack;
5. one chosen roofer and one site visit; and
6. a site-confirmed outcome.

At every point the product must show:

- what Roover currently knows, including whether it is provisional or confirmed;
- what is happening now;
- the homeowner's single next decision, if one exists; and
- what remains under homeowner control.

The governing doctrine is: **define the Job before anyone prices it; confirm the Job before anyone signs it.** The site visit remains. Its purpose changes from open-ended discovery to confirmation of the already-defined Job and production of the site-confirmed outcome.

### Kernel invariants

1. Address plus one action starts the governed flow.
2. Roover proposes evidence; the homeowner confirms the property correspondence, structure in scope, intent and facts only they can know.
3. Technical evidence is labelled by authority and confidence. The homeowner never “confirms” a measurement they cannot verify.
4. Start ends only when the Job Brief is confirmed. Project Room begins immediately; there is no lead-form or waiting-room interstitial.
5. There is one Job and one evolving Project Room. A retry, correction, reskin or navigation action must not create a second Job.
6. Home is the current milestone and timeline. My Roof is the latest authoritative homeowner-safe roof/Job Pack view and may evolve to the visualiser. Prices is one persistent page. Activity is factual event history.
7. Every roofer receives one persistent Prices card. The card advances on real events; it is not replaced by a new card or moved to a new page for each pricing phase.
8. The same versioned Job Pack is the comparison basis. A material change after release creates a new version and makes affected prices visibly stale until refreshed or reconfirmed.
9. Indicative range, formal proposal and site-confirmed outcome are distinct commercial objects and must never be collapsed into “the price.”
10. Dynamic trust claims—human review, matching, interest, delivery, opening, acceptance, proposal return, booking, notifications and timing—must be backed by events from an authorised source.
11. One homeowner action may choose one roofer for one site visit. This is not acceptance of a final quote. Runner-up proposals remain available unless invalidated by a material Job change or explicitly withdrawn.
12. Mobile and desktop express the same states, transitions, ownership and truth. Layout may differ; product meaning may not.

## 2. Scope and non-goals

### In scope

- Address entry, property resolution/correction and structure-in-scope confirmation.
- Evidence-led Job Brief construction, bounded homeowner questions, edit and confirmation.
- Immediate creation or continuation of one Project Room for the one Job.
- Home, My Roof, Prices and Activity surface behaviour.
- Versioned Job Pack preparation, material choice and explicit price-request boundary.
- Matching, one persistent roofer card per candidate, real interest, indicative range, formal-pricing request, formal proposal, in-place comparison and one site-visit booking.
- Site-confirmed outcome state, including a changed-scope path that preserves history and invalidates stale commercial artefacts safely.
- Event, idempotency, correction, loading, unknown, error, notification and fallback semantics.
- Fixture/live boundaries, instrumentation and acceptance tests.

### Non-goals

- Selecting repositories, services, frameworks, databases, queues, vendors or deployment topology.
- Replacing the current production architecture or claiming any integration already exists.
- Multi-property portfolios, multiple concurrent Jobs in one Project Room, insurance workflows, payments, contracts, installation management or post-install warranty service.
- Exposing raw Nearmap/provider data, BOQ rows, internal measurement artefacts, matching logic, chain-of-thought or operational dashboards to the homeowner.
- Eliminating the site visit or presenting a pre-visit proposal as the final site-confirmed quote.
- City-aerial spectacle, final animation, perfect copy, final brand skin or a final 3D visual treatment. These are non-blocking and outside the kernel.
- Simulated review, acceptance, price arrivals, timers, service levels or notifications.

## 3. Canonical state machine

### 3.1 Model

The canonical state is a projection of accepted domain events, not a route name or screen index. It has four coordinated regions:

- `journey_state`: overall Job progression;
- `roof_state`: evidence and Job Pack readiness;
- `pricing_state`: marketplace progression for the Job;
- `roofer_card_state[roofer_candidate_id]`: one lifecycle per roofer.

Navigation never changes domain state. A destination renders the latest projection for the current Job. Back navigation may change view only, unless the homeowner explicitly invokes an allowed correction/edit command.

### 3.2 Journey states

| ID | Meaning and entry condition | Exit condition | Allowed next states |
|---|---|---|---|
| `START_ADDRESS_REQUIRED` | No resolved property candidate exists for this draft. | A non-empty address action produces a candidate or a recoverable resolution failure. | `START_PROPERTY_CANDIDATE`, self on error |
| `START_PROPERTY_CANDIDATE` | Roover has a candidate property; correspondence is provisional. | Homeowner confirms it, corrects it, or says it is not their property. | `START_EVIDENCE_READING`, `START_ADDRESS_REQUIRED` |
| `START_EVIDENCE_READING` | Property confirmed; Roover is reading available property/roof evidence. No roof attributes are yet confirmed. | Candidate structures are available, unavailable, or require help. | `START_SCOPE_REQUIRED`, self while genuinely pending, `START_ADDRESS_REQUIRED` on correction |
| `START_SCOPE_REQUIRED` | One or more candidate structures exist; none is yet the Job scope. | Homeowner selects the structure or chooses help/not sure. | `START_SCOPED_EVIDENCE`, self for help/unknown, `START_ADDRESS_REQUIRED` on property correction |
| `START_SCOPED_EVIDENCE` | The homeowner-selected structure is the confirmed scope; evidence-derived attributes are provisional guides. | Homeowner explicitly starts Job Brief construction or corrects scope. | `START_BRIEF_BUILDING`, `START_SCOPE_REQUIRED` |
| `START_BRIEF_BUILDING` | The complete Job Brief template exists; known fields populate with provenance; homeowner-held gaps remain open. | Population settles and the first unresolved homeowner-held field is identified. | `START_BRIEF_QUESTIONS`, `START_BRIEF_REVIEW` when no questions remain |
| `START_BRIEF_QUESTIONS` | Exactly one unresolved homeowner-held field is active. | A valid answer is stored, or the homeowner chooses help/not sure. | self for next field/help; `START_BRIEF_REVIEW` when all required homeowner fields are answered; `START_SCOPE_REQUIRED` only for scope correction |
| `START_BRIEF_REVIEW` | Required fields are complete; technical fields remain visibly provisional where applicable. | Homeowner confirms the Brief or explicitly edits a homeowner-held field. | `ROOM_PREPARING_JOB_PACK`, `START_BRIEF_QUESTIONS` |
| `ROOM_PREPARING_JOB_PACK` | A Job, Project Room and immutable confirmed Job Brief version exist. Roof/measurement preparation is not yet sufficient for the next homeowner decision. | Authorised readiness event declares the material decision ready, or a recoverable failure/unknown state persists. | `ROOM_MATERIAL_DECISION`, self |
| `ROOM_MATERIAL_DECISION` | Job Pack components are ready enough for the homeowner to choose material; no pricing-affecting default is confirmed implicitly. | Homeowner confirms a material/colour or edits earlier homeowner-held intent. | `ROOM_JOB_PACK_READY`, `START_BRIEF_QUESTIONS` via explicit correction workflow |
| `ROOM_JOB_PACK_READY` | A versioned Job Pack is complete for marketplace use; nothing has been sent automatically. | Homeowner explicitly requests prices or edits a pricing-relevant input. | `ROOM_MATCHING`, `ROOM_MATERIAL_DECISION`, self |
| `ROOM_MATCHING` | A price request exists and authorised matching is in progress. | At least one matched candidate event arrives, matching ends with none, or matching fails safely. | `ROOM_PRICING_ACTIVE`, self, `ROOM_JOB_PACK_READY` on cancelled/recoverable no-match route |
| `ROOM_PRICING_ACTIVE` | Prices has at least one persistent roofer card. Cards advance independently. | Homeowner selects a proposal for site visit, cancels, or a material Job Pack change invalidates commercial artefacts. | `ROOM_SITE_VISIT_SCHEDULING`, self, `ROOM_JOB_PACK_READY` after material version change |
| `ROOM_SITE_VISIT_SCHEDULING` | One returned formal proposal has been chosen for progression; no visit is booked yet. | A valid slot is booked or homeowner returns to compare. | `ROOM_SITE_VISIT_BOOKED`, `ROOM_PRICING_ACTIVE` |
| `ROOM_SITE_VISIT_BOOKED` | One appointment exists for one chosen roofer; Home is the landing destination. | Site visit result is recorded, appointment is rescheduled/cancelled, or chosen roofer withdraws. | `ROOM_SITE_VISIT_COMPLETED`, `ROOM_SITE_VISIT_SCHEDULING`, `ROOM_PRICING_ACTIVE` |
| `ROOM_SITE_VISIT_COMPLETED` | The visit occurred; an authorised site result is pending or available. | A site-confirmed outcome is recorded. | `ROOM_SITE_CONFIRMED_OUTCOME`, self |
| `ROOM_SITE_CONFIRMED_OUTCOME` | The Job has a factual site outcome: `final_quote_returned`, `scope_change_required`, `no_go`, or another versioned authorised outcome type. | If scope changed, homeowner approves a new Job Pack version before repricing; otherwise this is the kernel terminal state. | self; `ROOM_MATERIAL_DECISION` or `ROOM_JOB_PACK_READY` only through an explicit versioned change workflow |

### 3.3 Persistent roofer-card lifecycle

The card identity is `job_id + roofer_candidate_id`, not the business name. It survives anonymous and named phases and must never be duplicated by retries or state changes.

| Card state | Entry event/condition | Required card content | Allowed next |
|---|---|---|---|
| `CARD_MATCHED_ANONYMOUS` | `roofer.matched` accepted for current Job Pack version. | Anonymous high-trust profile and factual fit signals; no interest, range, identity or proposal claim. | `CARD_INTEREST_ACCEPTED`, `CARD_WITHDRAWN`, `CARD_EXPIRED` |
| `CARD_INTEREST_ACCEPTED` | `roofer.interest_accepted` from an authorised source. | Real interest signal; still the same anonymous card. | `CARD_INDICATIVE_VISIBLE`, `CARD_WITHDRAWN`, `CARD_EXPIRED` |
| `CARD_INDICATIVE_VISIBLE` | `indicative_range.published` tied to the same roofer and Job Pack version. | Currency, low/high, basis/version, provisional label and formal-pricing action eligibility. | `CARD_FORMAL_REQUESTED`, `CARD_WITHDRAWN`, `CARD_EXPIRED`, self on range refresh |
| `CARD_FORMAL_REQUESTED` | Homeowner command accepted for one of one-to-five eligible cards. | Indicative range remains visible; pending request status; no false “sent” claim. | `CARD_REQUEST_SENT`, `CARD_INDICATIVE_VISIBLE` on failed/cancelled send |
| `CARD_REQUEST_SENT` | `formal_request.delivered` or equivalent authorised delivery acknowledgement. | Indicative range remains visible; anonymous profile; factual delivered/opened/reviewing/accepted substatus only as events arrive. | self for request substatus; `CARD_FORMAL_PROPOSAL_RETURNED`, `CARD_DECLINED`, `CARD_EXPIRED` |
| `CARD_FORMAL_PROPOSAL_RETURNED` | `formal_proposal.returned` tied to current Job Pack version. | Same card now shows verified business identity, formal amount/coverage, differences, roofer note and site-visit action. Indicative range remains available as prior context, not the active price. | `CARD_SITE_VISIT_SELECTED`, self for corrected/superseding proposal, `CARD_WITHDRAWN` |
| `CARD_SITE_VISIT_SELECTED` | Homeowner selects this proposal for site visit. | Selected status and scheduling entry; formal proposal retained. Other cards remain visible and unselected. | `CARD_SITE_VISIT_BOOKED`, `CARD_FORMAL_PROPOSAL_RETURNED` on back/cancel |
| `CARD_SITE_VISIT_BOOKED` | Appointment-booked event references this card/roofer. | Appointment details; formal proposal and fallback cards retained. | `CARD_SITE_OUTCOME_RECORDED`, `CARD_SITE_VISIT_SELECTED` on reschedule/cancel |
| `CARD_SITE_OUTCOME_RECORDED` | Site-confirmed outcome references this card/roofer. | Factual outcome and any superseding final quote/version. | terminal unless a versioned scope change reopens the Job |
| `CARD_DECLINED` | Roofer declines formal request. | Anonymous declined/closed state unless identity disclosure has already lawfully occurred; no proposal. | terminal for current request/version |
| `CARD_WITHDRAWN` | Roofer or authorised operator withdraws a previous signal/proposal. | Factual withdrawn status and date; retained in history, never silently removed. | terminal or a new versioned card/request cycle by explicit rule |
| `CARD_EXPIRED` | Authorised expiry policy fires. | Expired status without invented reason; retained in history. | terminal or explicit re-request if policy allows |

Request progress (`delivered`, `opened`, `reviewing`, `accepted`) is a substatus of the same `CARD_REQUEST_SENT` card. It must not create new cards, new Prices pages, or erase the indicative range. A formal-pricing acceptance event may update status, but verified business identity is disclosed on the card when the formal proposal returns.

## 4. Single-Job data and ownership

“Confirmed” means confirmed by the actor who has authority for that datum; it does not mean universally or physically verified.

| Data/object | Write authority | Initial status | Confirmation/supersession rule | Homeowner presentation |
|---|---|---|---|---|
| `job_id` / `project_room_id` | Roover system | Confirmed once created | Stable for the journey; retries reuse it | Usually hidden; one room, one Job |
| Address query | Homeowner | Homeowner-supplied | Editable until property correspondence is confirmed; later correction is explicit and audited | Plain address text |
| Resolved property candidate | Roover/provider | Provisional | Homeowner confirms correspondence or rejects/corrects it | “Is this the right property?” |
| Property/roof evidence | Roover/provider | Observed/provisional with provenance | Superseded only by newer evidence/version; never homeowner-certified as measurement | Known/unknown and source-safe guide labels |
| Structure in scope | Homeowner, from Roover candidates | Provisional until selected | Homeowner selection confirms Job scope; correction creates event and may invalidate downstream work | Selected structure plus correction/help |
| Roof attributes | Roover/evidence process | Provisional guide | Authorised measurement/site evidence may supersede; provenance retained | Plain-language attributes; confidence/guide where material |
| Job intent, reason, access and homeowner-only facts | Homeowner | Draft | Homeowner confirmation locks Brief version; later edits create new versions | Editable in Brief |
| Job Brief version | Roover compiles; homeowner confirms homeowner-held fields | Draft then confirmed | Immutable version; correction creates a new version | Complete Brief with authority labels |
| Measurement/model/BOQ | Roover-authorised evidence/measurement process | Preparing/provisional | Ready only on authorised event; site result may supersede | Homeowner-safe summary, never raw BOQ by default |
| Material/colour for pricing | Homeowner | Unresolved | Explicit selection confirms for a Job Pack version; final physical sample/site detail may supersede | No silent default; editable until release, then versioned change |
| Job Pack version | Roover compiles from source components | Draft then ready/released | Immutable released version; pricing artefacts reference exact version | Status summary and version relevance, not technical dump |
| Match eligibility/fit | Roover matching authority | Unknown | `roofer.matched` creates a candidate; do not expose internal scoring or invented reasons | Factual high-trust fit signals only |
| Roofer interest | Roofer/authorised marketplace source | Unknown | Accepted/withdrawn events only | Real interest state; no simulation |
| Indicative range | Roover-authorised calculation using roofer rate-card inputs | Provisional | Must reference roofer, currency, calculation basis and Job Pack version; refresh supersedes visibly | Range, never quote/guarantee |
| Formal-pricing selection | Homeowner | Draft selection | Command accepts 1–5 currently eligible cards | Visible selection count and confirmation |
| Request delivery/open/review/acceptance | Authorised delivery/roofer source | Unknown | Event-backed only, append-only progression/correction | Factual substatus; no fake timer |
| Business identity/profile | Roofer/verification authority | Held, not disclosed | Disclose with returned formal proposal; corrections are versioned | Verified identity and relevant credentials |
| Formal proposal | Roofer | Returned, pre-site | References exact Job Pack; correction/withdrawal supersedes, never overwrites | Amount, coverage, differences, note, details; not final quote |
| Site-visit choice/time | Homeowner chooses; authorised scheduler confirms | Requested then booked | Only booking acknowledgement makes it booked; reschedule/cancel audited | One roofer, one appointment, clear status |
| Site outcome/final quote | Roofer or authorised site workflow | Unknown | Authorised event references visit, proposal and Job Pack; scope change creates a new version path | Site-confirmed outcome, clearly distinguished from earlier prices |
| Activity projection | Roover from accepted events | Derived | Append-only display; corrections/supersessions remain factual | Reverse chronological, no future leakage |

## 5. Event and command contract

### 5.1 Event envelope

Every state-changing event must include, directly or through an equivalent mapped envelope:

```json
{
  "event_id": "globally-unique-id",
  "event_type": "formal_proposal.returned",
  "schema_version": 1,
  "job_id": "stable-job-id",
  "project_room_id": "stable-room-id",
  "aggregate_type": "roofer_card",
  "aggregate_id": "job-id:roofer-candidate-id",
  "job_pack_version": "jp-3",
  "aggregate_version": 6,
  "occurred_at": "RFC-3339 timestamp",
  "received_at": "RFC-3339 timestamp",
  "actor": { "type": "roofer", "id": "authorised-actor-id" },
  "source": { "system": "authorised-source", "reference": "source-record-id" },
  "correlation_id": "journey-or-request-id",
  "causation_id": "prior-command-or-event-id",
  "idempotency_key": "stable-key-for-logical-write",
  "truth_status": "confirmed",
  "payload": {}
}
```

Required `truth_status` values are `provisional`, `confirmed`, `corrected`, `superseded`, `withdrawn` and `unknown`. UI copy must derive from the accepted status, not guess from elapsed time.

### 5.2 Commands

Homeowner writes are commands, not instant facts. At minimum: `resolve_property`, `confirm_property`, `select_structure`, `answer_brief_field`, `confirm_job_brief`, `select_material`, `request_prices`, `select_roofers_for_formal_pricing`, `choose_site_visit_roofer`, `request_site_visit_slot`, `reschedule_site_visit`, and `correct_job_input`.

- Every command carries `job_id` when one exists, an expected aggregate/version, actor/session context and an idempotency key.
- Disable duplicate submission while one command is unresolved, but correctness must not rely on the button state.
- Success is shown only after authoritative acknowledgement/event projection.
- A version conflict reloads the latest state, preserves unsent input where safe, and asks the homeowner to reconcile; it never silently overwrites newer truth.

### 5.3 Idempotency and ordering

1. Delivery may be at least once; projection effects are exactly once per `event_id` or logical `idempotency_key`.
2. Replayed events must not create a second Job, room, roofer card, request, proposal, notification, activity item or appointment.
3. Enforce monotonic `aggregate_version` per aggregate. Park gaps/out-of-order events until predecessors arrive or reconcile from the source of truth.
4. A stale event may be retained for audit but must not regress the current projection or leak a future/past state into the UI.
5. Corrections are new events referencing the superseded event/object. Do not mutate or delete accepted history.
6. Side effects such as notifications use their own dedupe key derived from event and channel. A state event may be accepted even if notification delivery fails.
7. Pricing artefacts are valid only for their referenced Job Pack version. A pricing-relevant change marks them `stale` or `superseded`; it does not silently rebind them.

### 5.4 Minimum event vocabulary

- Job/Brief: `job.draft_created`, `property.candidate_resolved`, `property.confirmed`, `structure.scope_confirmed`, `job_brief.field_answered`, `job_brief.confirmed`, `job_brief.corrected`.
- Roof/Pack: `roof_evidence.updated`, `measurement.preparation_started`, `measurement.ready`, `visualiser.ready`, `material.selected`, `job_pack.ready`, `job_pack.released`, `job_pack.superseded`.
- Marketplace/cards: `matching.started`, `roofer.matched`, `roofer.interest_accepted`, `indicative_range.published`, `formal_request.created`, `formal_request.delivered`, `formal_request.opened`, `formal_request.reviewing`, `formal_request.accepted`, `formal_request.declined`, `formal_proposal.returned`, `formal_proposal.corrected`, `formal_proposal.withdrawn`.
- Site/outcome: `site_visit.requested`, `site_visit.booked`, `site_visit.rescheduled`, `site_visit.cancelled`, `site_visit.completed`, `site_outcome.recorded`, `final_quote.returned`, `job.scope_change_required`.
- Communication: `notification.delivery_requested`, `notification.delivered`, `notification.failed`.

Event names may be mapped to existing system names. Their meanings, required references and projection rules may not be weakened.

## 6. Surface contract

### 6.1 Start

- No persistent Project Room navigation.
- One primary action per state. Back/correction is secondary but always available where the resolved property, structure or homeowner-held answer may be wrong.
- Order is fixed: address → property correspondence → evidence read → structure scope → scoped roof context → complete Brief template → bounded homeowner questions → editable Brief review → confirm.
- Roof attributes cannot appear as confirmed before scope selection.
- The complete Brief exists before questions; every question corresponds to a visible unresolved field.
- Known fields visibly populate with provenance/authority. Unknown remains unknown; it is not replaced with a plausible fixture/default.
- `Confirm Job Brief` confirms homeowner-held intent and creates a versioned handoff. It does not ask the homeowner to validate technical measurement.
- Successful confirmation lands directly in Project Room Home for the same Job.

### 6.2 Home

- Default Project Room destination and landing after Brief confirmation and site-visit booking.
- Shows exactly one current milestone, plain “what is happening,” the single next homeowner action if any, and notification expectation without unsupported timing.
- Timeline is current/next first, completed history compactly below. It does not become a retrospective replay control.
- Home derives from current domain state; it never rewinds when the homeowner returns from My Roof, Prices or Activity.
- After booking, appointment details are the hero. After the visit, the pending/recorded site outcome becomes the hero.

### 6.3 My Roof

- Latest authoritative homeowner-safe view of the roof, Job Brief and evolving Job Pack.
- Progress language is `done`, `happening now`, `next`, `unknown` or `needs attention`; no unsupported percentage.
- Blueprint/evidence remains available. The visualiser becomes hero only after a real ready event.
- Visualiser failure must not hide the latest valid blueprint/evidence or block a non-visual material-selection fallback.
- Material has no silent pricing default. A starting look is non-binding until explicitly selected.
- Before price request, material may be edited in place. After Job Pack release, a pricing-relevant edit must explain that it creates a new version and may invalidate existing ranges/proposals before committing.
- Raw BOQ, provider payloads and internal measurement/review details are outside the default homeowner surface.

### 6.4 Prices

- Hidden or clearly unavailable until meaningful pricing content exists; never expose an empty destination merely to complete navigation.
- Once active, one persistent Prices destination covers matching, cards, formal-pricing requests, proposals, compare mode and site-visit selection.
- One persistent card exists per `roofer_candidate_id`. Card order may change by an explicit stable sort, but identity/state may not reset on navigation or refresh.
- Card lifecycle is: anonymous match → real interest accepted → indicative range → homeowner requests formal pricing → request sent while indicative remains → formal proposal returns on the same card → choose one for a site visit.
- Selection for formal pricing accepts one to five eligible cards. Zero cannot submit; more than five cannot be selected.
- Request progress updates the same card. It cannot remove the indicative range or imply a proposal exists.
- A returned proposal updates the same card with verified identity and formal-proposal facts. It must reference the same current Job Pack version.
- Compare is a mode in Prices, not a separate destination or journey step. It highlights material differences; deeper detail holds inclusions, exclusions, warranty, timing, scope and profile.
- Choosing a card progresses to one site visit. It is not final-quote acceptance; unchosen proposals remain available.

### 6.5 Activity

- Factual, reverse-chronological projection of accepted events for this Job.
- Shows only events that have occurred. No fixture future events, optimistic success, inferred human activity or notification claims.
- Uses the actual selected material, chosen roofer, appointment and current versions.
- Corrections and supersessions are intelligible: the record may show both the earlier fact and that it was corrected/withdrawn.
- Activity is not the write authority and cannot be edited directly. Its entries deep-link to the relevant destination/detail where safe.

## 7. Cross-cutting behaviour

### Correction and edit

- `Not my property` returns to address resolution and clears/supersedes dependent provisional evidence without creating a second Job.
- Structure correction returns to scope selection and marks downstream roof/pack artefacts stale.
- Homeowner-held Brief fields can be edited before confirmation. Later correction is explicit, versioned and warns before invalidating released pricing.
- Technical evidence corrections come from an authorised evidence/site source, not a homeowner “confirm measurement” control.
- Material change before release is direct. After release it requires explicit impact acknowledgement and creates a new Job Pack version.

### Back and navigation

- Browser/app back changes view without reversing accepted events.
- During Start, back may show prior inputs for correction; it does not erase confirmed data until a correction is committed.
- In Project Room, destination switching always renders the current era. It cannot navigate back into Start or reveal future Activity.
- Unsaved homeowner input is preserved locally where safe and labelled unsaved; it is never presented as confirmed.

### Loading and unknown

- Loading states name the real operation without a fake timer or invented stage. Long-running work may safely continue after navigation.
- Unknown is a first-class value. Show `We don't know yet`, the next way it may be resolved, and whether the homeowner can help.
- Keep the last confirmed projection visible while refreshing. Skeletons must not erase decision-critical confirmed facts.
- A timeout changes presentation to delayed/unknown; it does not fabricate success or failure.

### Error and recovery

- Errors say what was not completed, what remains safe, and the next recoverable action.
- A failed command remains unconfirmed and may be retried with the same idempotency key.
- If source data conflicts, fail closed on the affected decision, retain the last confirmed state, and route to correction/support. Do not choose the more convenient value.
- If the current Job Pack/version cannot be proven, block pricing release and proposal comparison for that version.
- Identity or Job mismatch fails closed and exposes no other homeowner/roofer data.

### Notifications

- Project Room is the source of truth; notifications are delivery hints with a deep link to the current destination/state.
- Notify only for meaningful, accepted events or explicit homeowner-requested reminders. Do not notify for fixture transitions, internal polling or repeated deliveries.
- Respect selected channel/consent and deduplicate by event + channel + recipient.
- Notification failure does not roll back domain state. Record failure operationally and keep the in-room update available.
- Copy must not claim `sent`, `delivered`, `opened`, `booked` or a time promise without the corresponding event.

## 8. Dependency matrix and safe fallbacks

No row asserts that an integration currently exists.

| Dependency/capability | Kernel need | Ready signal | Safe fallback | Must not happen |
|---|---|---|---|---|
| Address/property resolution | Produce a candidate property and stable correspondence check | Candidate with source reference and confidence/status | Preserve address input; allow correction/manual resolution or support; remain provisional | Invent a property, silently choose a close match, or create multiple Jobs on retry |
| Nearmap/aerial/property evidence | Support property/structure candidates and observed context | Evidence payload/version accepted for the Job | Show available map/imagery or a neutral unavailable state; collect scope through bounded help/manual evidence | Call aerial a measurement, expose provider internals, or substitute the pending city-aerial asset as evidence |
| Measurement/model/BOQ | Prepare the Job Pack and unlock pricing-relevant choice/release | Authorised `measurement.ready` and version | Keep My Roof at preparation/needs-attention; use operational review if authorised; block Job Pack release/pricing | Fake completion, ask homeowner to validate technical numbers, expose raw BOQ by default |
| Visualiser | Make current roof/material state tangible once genuinely ready | `visualiser.ready` for current roof/model version | Keep blueprint/evidence hero; offer accessible non-3D material/colour choice | Block the whole kernel, show a generic roof as theirs, or silently apply a material |
| Notification delivery | Alert homeowner to meaningful new decisions/events | Channel acknowledgement/delivery event | In-room Home/Activity update remains canonical; retry or offer another consented channel | Treat delivery failure as domain failure or claim a notification was received |
| Matching and pricing | Create authorised candidates, interest, ranges, requests and proposals | Event-backed source with Job Pack/version references | Keep Job Pack ready and homeowner-controlled; show unavailable/delayed; permit authorised operational fulfilment only if it emits the same events | Simulate candidates/interest, create price bands without basis, claim a request was sent, or compare different Job Pack versions |

## 9. Priority

### P0 — kernel cannot ship without

- One stable Job/Project Room identity and versioned Job Brief/Job Pack.
- All canonical states and guarded transitions through a site-confirmed outcome.
- Four Project Room surface contracts and current-era navigation.
- One persistent Prices page and one idempotent card lifecycle per roofer.
- Exact commercial distinctions and Job Pack version binding.
- Event-backed state, idempotent commands/projections, correction/supersession and no-future-leak Activity.
- Safe dependency fallbacks and failure-closed identity/version handling.
- Accessible mobile core journey: keyboard/focus semantics where applicable, readable labels, no decision blocked by animation/colour/3D, and responsive parity.
- Fixture/live separation in environments, test data and UI claims.
- P0 acceptance tests below and instrumentation sufficient to diagnose drop-off, errors and truth violations.

### P1 — material improvement after kernel proof

- Richer help routes for uncertain scope, Job intent and material selection.
- Notification preference management and additional consented channels.
- Enhanced proposal-detail comparison, document access and support handoff without changing the Prices lifecycle.
- Better operational recovery tooling for stalled/unknown dependencies using the same event contract.
- Visualiser enhancement after blueprint/material fallback is proven.

### P2 — outside initial kernel build

- Final visual skin, perfect copy, city aerial, cinematic transitions and final animation system.
- Multi-Job/portfolio support and broader project lifecycle after the site-confirmed outcome.
- Personalisation or recommendation features beyond factual, explainable kernel decisions.

## 10. Acceptance contract

The JSON manifest contains the executable-friendly form. All P0 tests must pass against fixture-safe tests before any live proof. Live deployment additionally requires real-event contract tests and environment-specific security/privacy gates that are not evidenced by the frozen prototype.

| ID | Priority | Given / When / Then |
|---|---|---|
| `AT-001` | P0 | Given no Job, when address is submitted twice through retry, then one draft Job/room identity is used and no duplicate side effect occurs. |
| `AT-002` | P0 | Given a resolved property candidate, when homeowner rejects it, then Start returns to address correction and dependent evidence is not treated as confirmed. |
| `AT-003` | P0 | Given evidence read before scope, then roof attributes are not presented as scoped/confirmed until homeowner chooses a structure. |
| `AT-004` | P0 | Given the Brief template, when Roover population settles, then known fields show authority/provenance and every required homeowner-held gap remains open. |
| `AT-005` | P0 | Given multiple open Brief fields, then exactly one bounded question maps to the current field; an answer persists and advances logically. |
| `AT-006` | P0 | Given Brief review, when homeowner edits a field, then the revised value persists and no technical measurement is framed as homeowner-confirmed. |
| `AT-007` | P0 | Given confirmed Brief command succeeds, then Project Room Home opens for the same Job with no interstitial or second Job. |
| `AT-008` | P0 | Given measurement/visualiser is not ready, then My Roof shows last confirmed evidence and no fake percentage, timer or ready claim. |
| `AT-009` | P0 | Given no explicit material selection, then Job Pack cannot become ready by silently accepting a displayed starting look. |
| `AT-010` | P0 | Given Job Pack ready, when homeowner has not requested prices, then nothing is matched/sent and Home states that control truthfully. |
| `AT-011` | P0 | Given a duplicate `roofer.matched` event, then exactly one anonymous card exists for its `roofer_candidate_id` and no interest/range is implied. |
| `AT-012` | P0 | Given a real interest event followed by a valid range event, then the same anonymous card advances and shows the indicative range as provisional. |
| `AT-013` | P0 | Given zero or more than five card selections, then formal request submission is blocked; one through five eligible selections are accepted. |
| `AT-014` | P0 | Given homeowner requests formal pricing and send is pending, then cards show requested, not sent; after delivery event they show sent and retain the indicative range. |
| `AT-015` | P0 | Given delivered/opened/reviewing/accepted events arrive, then they update one card substatus without duplicating it or fabricating a proposal. |
| `AT-016` | P0 | Given a proposal for the same Job Pack returns, then the same card shows identity, formal facts and site-visit action; indicative history remains available. |
| `AT-017` | P0 | Given a proposal references an older/different Job Pack, then it is marked stale/incomparable and cannot be chosen as current. |
| `AT-018` | P0 | Given multiple formal proposals, when compare is entered, then the route/destination remains Prices and differences reference the same Job Pack. |
| `AT-019` | P0 | Given one proposal is chosen, then only one site-visit scheduling context is active and runner-up proposals remain visible. |
| `AT-020` | P0 | Given a slot request without booking acknowledgement, then UI does not claim booked; after acknowledgement, Home is the landing surface with exact appointment. |
| `AT-021` | P0 | Given visit completion, when a site outcome arrives, then it is distinct from indicative/formal prices and references the visit and current Job Pack/proposal. |
| `AT-022` | P0 | Given a site outcome requires scope change, then prior prices remain historical/stale and repricing cannot occur until a new Job Pack version is approved. |
| `AT-023` | P0 | Given navigation among all four destinations at any era, then each renders the current projection, cannot rewind into Start, and Activity shows no future events. |
| `AT-024` | P0 | Given duplicate/out-of-order events, then projection is exactly-once and monotonic; stale events do not regress card, appointment or Job state. |
| `AT-025` | P0 | Given a command version conflict, then latest confirmed state reloads, unsent input is preserved where safe, and no newer data is overwritten. |
| `AT-026` | P0 | Given an unavailable dependency, then the documented fallback appears and no unsupported evidence, measurement, match, delivery or timing claim is made. |
| `AT-027` | P0 | Given notification delivery fails, then the domain state and in-room event remain correct, no duplicate alert is sent, and UI does not claim delivery. |
| `AT-028` | P0 | Given a Job/user/roofer identity mismatch, then access and affected mutation fail closed with no cross-Job data disclosure. |
| `AT-029` | P0 | Given animation disabled, 3D unavailable, keyboard-only use or narrow mobile layout, then every P0 decision remains understandable and operable. |
| `AT-030` | P0 | Given fixture mode, then synthetic events/data are visibly and technically separated; no production connector/write/notification can be reached. |
| `AT-031` | P0 | Given a pricing-relevant edit after release, then impact is explained, a new Job Pack version is created only on confirmation, and old prices are not silently rebound. |
| `AT-032` | P0 | Given a corrected/withdrawn event, then current truth updates while Activity retains an intelligible audit trail. |

### Explicit negative cases

The build fails acceptance if any of the following occurs: duplicate Job/card/request/appointment creation; price shown before authorised range/proposal event; roofer identity disclosed before the returned formal proposal; indicative range disappears while formal pricing is pending; a separate Compare destination; future Activity leakage; fake timer or human-review claim; automatic price request after material choice; comparison across Job Pack versions without a stale warning; technical measurement treated as homeowner-confirmed; site-visit choice framed as final-quote acceptance; or fixture data/events able to reach production writes.

## 11. Fixture versus live boundary

| Concern | Fixture/review | Live implementation gate |
|---|---|---|
| Data | Synthetic, recognisable and isolated | Authorised records with identity, consent, privacy and retention controls |
| Events | Deterministic scripted events may exercise every state | Every dynamic claim maps to an authenticated authorised event/source |
| Writes | Local/test-only; no CRM, database, marketplace, notification or production writes | Idempotent commands, version checks, audit and failure recovery proven |
| Pricing | Illustrative values labelled fixture/prototype | Currency, basis, roofer, Job Pack version and commercial authority validated |
| Human/roofer activity | May be simulated only with persistent fixture disclosure | Never simulated; timestamps/statuses come from authorised events |
| Notifications | Captured test sink only | Consent, channel delivery, dedupe and failure behaviour proven |
| Security/privacy | No claim from prototype | Current-system authentication, authorisation, isolation, PII, audit and abuse tests required |

The frozen prototype passed fixture-kernel QA at the stated SHA. It did **not** prove live-event integration. Any environment capable of production side effects must reject fixture credentials, fixture event sources and fixture Job IDs.

## 12. Skin/reskin contract

A skin may change visual design, brand, copy tone, layout, illustration, motion, component implementation and responsive composition. It may not change:

- state IDs, entry/exit meaning or allowed transitions;
- one-Job/one-room identity and version rules;
- Start → immediate Project Room handoff;
- the authority/provisional/confirmed meaning of data;
- the four destination responsibilities;
- one persistent Prices page and one persistent card per roofer;
- the indicative → formal proposal → site-confirmed outcome distinctions;
- event/idempotency/correction/fallback semantics;
- homeowner control boundaries or one-site-visit rule;
- accessibility of P0 decisions; or
- fixture/live disclosure and production-write isolation.

Reskin acceptance is the same manifest run against the new presentation. A skin is not approved if it needs a changed state transition or truth claim to make the design work.

## 13. Instrumentation and success hypotheses

No quantitative target is verified in the supplied evidence. Therefore every target below is a hypothesis to baseline before launch, not a commitment.

### Required event measures

- Unique Jobs reaching each canonical state and valid transition counts.
- Time between accepted events, separated from UI dwell time and notification delivery.
- Correction/help/unknown/error/retry rate by state and dependency.
- Duplicate-command/event suppression and version-conflict counts.
- Job Pack version changes after price release and number of stale commercial artefacts.
- Candidate → interest → indicative → formal request → proposal → site-visit → site-outcome funnel, measured per persistent card and Job.
- Notification request/delivery/failure/dedupe without treating delivery as user engagement.
- Accessibility and client/runtime errors that block a P0 action.

### Hypotheses to validate

| Hypothesis | Proposed measure | Target status |
|---|---|---|
| A complete evidence-led Brief reduces homeowner confusion | Brief completion, correction/help rate, qualitative comprehension | Baseline required; no numeric target approved |
| One persistent card improves pricing comprehension | Card continuity errors, compare completion, “which price is current?” research signal | Baseline required |
| Explicit control boundaries improve trust | Price-request abandonment reasons, support contacts, user-research trust response | Baseline required |
| Same-Job-Pack comparison improves decision progress | Eligible formal proposals → one site-visit selection | Baseline required |
| Event-truth discipline reduces misleading states | Unsupported-claim incidents, stale projection rate, notification/state mismatches | Operational target should be zero for integrity violations; alert threshold to be set by Engineering/Product |
| The kernel reaches its intended outcome | Confirmed Brief → site-confirmed outcome completion and elapsed time | Baseline required |

Instrumentation must not log raw sensitive roof/provider payloads or more homeowner/roofer identity than needed. Metric definitions and retention require a separate current-system review.

## 14. Recommended build sequence

This is dependency order, not an architecture prescription.

1. **Map and reconcile:** map current authoritative records, writers, readers and event equivalents to the contract; identify where one-Job identity, versions or card identity are missing. Do not rename or migrate systems merely to match this vocabulary.
2. **Contract harness:** implement fixture-safe state reducer/projection and the JSON acceptance manifest around current seams. Prove duplicate/out-of-order/version behaviour before UI integration.
3. **Single-Job ingress:** bind address → scope → Brief → immediate room handoff with idempotent Job/room identity and correction paths.
4. **My Roof/Job Pack:** establish provenance, readiness, material choice and version invalidation with safe dependency fallbacks. Blueprint/non-3D fallback precedes visualiser polish.
5. **Persistent Prices model:** establish stable candidate/card IDs and event-backed lifecycle. Keep indicative range on the card through formal request and proposal return.
6. **Site visit/outcome:** add one-choice scheduling, booked Home, completion and site-confirmed outcome/change-version path.
7. **Four-surface projection:** render current-era Home, My Roof, Prices and Activity from the same accepted state; add notifications as non-authoritative side effects.
8. **Live proof gate:** in an isolated non-production environment, prove authorised sources, identity isolation, idempotency, audit, privacy, failure recovery and fixture rejection. Only then seek deployment approval.
9. **Skin/polish:** apply final design, copy, city aerial and motion after the kernel contract passes independently.

## 15. Decisions and candid gaps

### Blocking before implementation begins

None in Product logic. Engineering can begin mapping and contract-harness work without a city aerial, final visualiser, final animation, perfect copy or final skin.

### Blocking before live activation, not before build

- Current-system ownership map for Job, Job Brief, Job Pack, roofer candidate/card, proposal, appointment and site outcome.
- Authorised source and authentication for each real event, including the exact meaning of request `delivered`, `opened`, `reviewing` and `accepted`.
- Current privacy/identity-disclosure, consent, retention and notification controls.
- Current commercial authority and calculation basis for indicative ranges, formal proposals and final quotes.
- Site-confirmed outcome vocabulary and operational owner. This contract supplies the minimum outcomes needed for safe state handling, but Product/Operations must ratify the exact set before live activation.
- Environment isolation and proof that fixture sources cannot reach live writes.

### Evidence gap

The supplied fixture proves the journey only through a booked site visit and current-era Activity. The site-visit-completed and site-confirmed-outcome states are required by the locked kernel objective but are not visually or behaviourally proven in the frozen prototype. They require new fixture coverage and independent QA before live activation.

## 16. Source and authority boundary

- Latest kernel lock supplied for this handoff, including the persistent Prices page/card lifecycle and site-visit return to Home.
- [`Roover_Start_Kernel_v1_0.docx`](./Roover_Start_Kernel_v1_0.docx): brand-neutral kernel and journey logic.
- [`roover-start-kernel-review-notes.md`](../work/roover-start-kernel-review-notes.md): working decisions and truth rules.
- [`RoofHero_kernel_surgical_v4_evidence_matrix.md`](./RoofHero_kernel_surgical_v4_evidence_matrix.md): fixture evidence boundary.
- [`acceptance-register.md`](./kernel_sol_ultra_qa_a63dc5/acceptance-register.md) and [`behavior-receipt.json`](./kernel_sol_ultra_qa_a63dc5/behavior-receipt.json): exact-SHA adversarial fixture QA.
- [`independent-qa-receipt.md`](./kernel_surgical_v4_evidence/independent-qa-receipt.md): independent v4 receipt.

These sources do not authorise production changes or prove any current repository/runtime/integration. This package adds no production implementation claim.
