# Customer journey rules — Step 3

## What it does

`ReadinessEngine` calculates a versioned, explainable snapshot without database, network, provider, cache, job or wall-clock calls. It does not save evidence, assign staff, create tasks, send messages or activate a feature. There is no public endpoint accepting `JourneyContext`.

The output is seven evidence states and four decision gates for an opportunity; an intake has Need and Relationship only. It does not replace the existing numeric Zoom scores with a new invented percentage. Existing Copilot screens remain unchanged.

## How it works

### Input boundary

`JourneyContext` is an internal, already-authorized, complete read model. It is not an HTTP request or raw Eloquent serialization. A future loader must resolve customer/context identities, permission intersections and canonical references before invoking it. It must provide all current competing allocations for selected funds and all applicable consultation history; passing only the latest event is not a valid stage-rebuild input.

Required identity: positive integer `lead_id`, `kind` (`intake`/`opportunity`), positive context `id`, and an explicit ISO-8601 `now` with timezone. Input instants require an offset; funding/payment dates use the supplied business calendar, default Asia/Kuala_Lumpur. SQL DATETIME adapters must use the application's stored timezone, not append a UTC suffix to local values.

| Input group | Contract |
| --- | --- |
| `assertions`, `reviews`, `links` | Typed values from the 31-field registry, original origin/authority, explicit context, source revision and dependency bindings. Reviews are authorized append-only decisions with actor/service identity. AI cannot verify itself. |
| `sources` | Keyed source IDs, same customer, explicit `context_keys`, visible/eligible flags, occurrence timestamp and current revision. Restricted, deleted/redacted or unmatched sources cannot supply facts or copied previews. Channel adapters must derive flags; clients cannot submit them. |
| `versions`, `invalidations` | Current target/price/route/borrower/scenario/need/party dependencies and explicit affected fields. Missing bindings cannot silently pass. Material changes require applicable confirmations. |
| `target`, `route`, `parties` | Canonical current purchase references; sourced price, principal/fraction and repayment; stable identities for deciding/funding/signing roles. Catalogue UUIDs remain truth; numeric catalogue IDs are caches. |
| `funding` | Complete projection of selected pools, all competing allocations, immutable revision histories, payment and booking references. See below. |
| `primary_commitment`, `appointment`, `handoffs` | Current open primary, context owner, purpose/outcome, exact deadline and agreement source; accepted appointment host distinct from journey ownership. |
| `activities` | Explicit request ownership and reply linkage. A sent substantive reply resolves named requests; template/bulk/failed messages do not. |
| `decision_review`, `stage_command`, `park`, `booking` | Authorized context-scoped commands or recorded facts. An actual canonical booking plus instruction takes precedence over checklist gaps. Commands here are evaluated only, never executed. |
| `contact_policy`, `calendar` | Customer-wide DNC/outbound hold and expiry; timezone/weekends/holidays. Suggestions respect restrictions and require staff acceptance. |

### Evidence lifecycle

Multiple confirmed assertions for the same field/resource produce conflict, not a latest-timestamp winner. A pending proposal cannot replace an accepted statement. Explicit supersession chains must terminate at an accepted assertion of the same field/resource. Source authority, field eligibility, source revision and quoted text are checked before returning facts.

Intake Need/Relationship reuse requires an authorized applicability link to the first opportunity. Shared gross pool balance/reserve/availability may be referenced by another purchase only with a `shared_funding_pool` link naming the pool, assertion and review. Net purchase allocations and purchase-specific facts are not implicitly shared.

Reconfirmation does not edit an assertion. The normalized review may carry `revalidated_bindings` resolved from its immutable `trigger_event_id`, alongside a human actor and reason. The future repository must verify that event and its dependency versions. Changed values require a new assertion and supersession. A new optional note does not clear another field's reconfirmation.

AI quote validation confirms occurrence and a mapper-verified speaker; it does not by itself prove semantic correctness. Automatic extraction, quote-failure logging and evaluated AI precision remain Step 8 work.

### Money and funding

Money uses integer sen; integer ringgit or decimal strings are accepted, floats and implicit FX are refused. A positive, sourced target price and explicit principal/fraction are required; no universal financing percentage is inferred. A passing current screening must cover principal and match the documented repayment accepted for that route.

Each selected pool has a gross balance before allocations, a protected reserve, dated tranches, currency and revision, all linked to accepted evidence. A gross statement and a net purchase allocation are distinct facts. Unclear gross/net meaning or an incomplete payment baseline yields `incomplete`, not a shortage. Unrelated incomplete pools do not block a purchase using another pool.

Allocation heads are selected from complete, continuous immutable revision histories. Every competing active/consumed/booking reservation remains counted. Current purchase allocation agreement must name its lineage, amount, currency, opportunity and schedule/pool versions. A net cap is checked cumulatively across lineages, not independently per row. Consumed funds require verified payment references and cannot be released or reduced; partial consumption requires separate lineages. Booking transfer requires the canonical transaction reference and retains the cash claim.

At each relevant date, all reservations must fit within the pool's available tranches minus its reserve; this purchase's allocation must also cover its cumulative buyer payments. Credits reduce the later milestone once: RM100 booking credited against RM50,000 deposit totals RM50,000. An empty or incomplete schedule cannot pass. `schedule_complete` must be established by the future source/command loader, including mandatory costs and the full buyer obligations of a cash route; the engine does not infer omitted real-world fees.

A competing reservation that exceeds available funds returns a proven shortage and `cash_double_allocation`. A missing input returns incomplete. The snapshot carries pool revisions and a dependency hash. `stillCurrent()` is a pure comparison only. Step 4 must take the customer/pool locks, rebuild/recheck the complete projection, update all affected context revisions, then commit; this step does not claim an atomic allocation writer exists.

### Readiness, stages and priority

- Need requires purpose, budget, timing and a must-have. Activity alone cannot satisfy it.
- Relationship requires a real exchange, qualifying live interaction and agreed current dated action. DNC blocks outreach independently.
- Understanding requires all three advisor-confirmed teach-backs; webinar attendance is optional supporting engagement.
- Financed Funding requires **Loan Ready and Cash Ready**. Cash-route Loan becomes Not applicable only when current Cash Ready is established, including explicit blocks and reconfirmation.
- Required deciding/funding/signing parties must be identified, involved and agreed. A required objection blocks; silence does not count as agreement.
- Fit requires current target, complete assessment, explicit acceptance of trade-offs, explicit none/resolved objections and a proceed statement.
- Screening older than 60 days and used cash evidence older than 45 days need reconfirmation at review. Obsolete loan facts do not block a current cash route. A new target caps affected Loan/Cash/Fit at Developing until re-established.
- Only a matched qualifying consultation establishes Consulted. Webinar/test/missed/unmatched recordings cannot do so. An old cancellation does not erase an available completed consultation.
- Decision review requires an advisor command and current dependency hash; an exception must cover exactly the missing fields and expire. It cannot bypass a block or contact restriction.
- Canonical booking remains Booked with unmet-checklist exceptions. Pending host/ownership acceptance never transfers journey ownership.
- No-response Lost requires an explicit command, at least 30 parked days and three distinct eligible attempts. DNC alone does not make Lost.

Queue order: missing owner/valid primary; unanswered request; overdue action; blocked; unknown gate at Appointment/Consulted/Review; reconfirm; due today; scheduled; nurture. Earlier exception tiers still apply to parked work. Stage SLA, 30-day lack of confirmed progress, 24-hour handoff/outcome escalation and original missed deadline are flags. They do not fabricate an overdue primary or erase a historical miss.

Each suggestion contains a useful outcome, relevant field, permitted customer preview and proposed next working-day 10:00 deadline. It is a proposal, never an already agreed promise. Closed work has no outreach suggestion. UI adapters should translate reason codes rather than displaying internal codes as customer instructions.

## Reference usage

Inject `FieldRegistry` and pass the matching configuration registry version to `ReadinessEngine`. Persist/serve only the viewer-scoped projection produced within the command's authorization and lock boundary. Callers must bind snapshots to the expected context as well as compare rule/registry/input/pool versions. A rule rebuild must not trigger sends.

The synthetic `RuleFixture` is executable documentation of the normalized input shape. It is not a production loader or a source of default financial facts. `PrototypeTest` covers the twelve supplied personas' key policy behaviors with corrected synthetic evidence; it does not certify every number in the demo HTML.

## Related files

- `src/RevenueJourney/Rules/ReadinessEngine.php`
- `src/RevenueJourney/Rules/JourneyContext.php`
- `src/RevenueJourney/Rules/EvidenceReducer.php`
- `src/RevenueJourney/Rules/FactSet.php`
- `src/RevenueJourney/Rules/CashFeasibility.php`
- `src/RevenueJourney/Rules/ExactMoney.php`
- `src/RevenueJourney/Rules/RuleTime.php`
- `src/RevenueJourney/Rules/RuleValues.php`
- `src/RevenueJourney/Rules/DimensionEvaluator.php`
- `src/RevenueJourney/Rules/GateEvaluator.php`
- `src/RevenueJourney/Rules/WorkflowFacts.php`
- `src/RevenueJourney/Rules/StagePolicy.php`
- `src/RevenueJourney/Rules/PriorityPolicy.php`
- `src/RevenueJourney/Rules/ActionSuggester.php`
- `tests/Fixtures/RevenueJourney/RuleFixture.php`
- `tests/Unit/RevenueJourney/Rules/EngineTest.php`
- `tests/Unit/RevenueJourney/Rules/GuardTest.php`
- `tests/Unit/RevenueJourney/Rules/PrototypeTest.php`
- `tests/Unit/RevenueJourney/Rules/RevisionTest.php`
