DEC19: the portal resolves a login by a stored, doubly confirmed binding

The email/phone match in PatientAppointmentsController::resolvePatient is
gone: a household shares a phone number, a recycled number reaches a
stranger, and one mistyped address at reception reached a different
patient's chart. The resolver now reads a live row on patient_user_links
and nothing else.

- patient_user_links migration + PatientUserLink model (pending|active|
  revoked|expired, via staff|patient_self, live scope over status+expiry)
- PatientUserLinkService holds the invariants: the confirmer must differ
  from the requester and from the account under review; a pending request
  grants nothing; one live binding per login; revocation ends access at
  once and keeps the row; expiry resolves to nothing without a sweep;
  cross-clinic binding refused at the service boundary
- PatientUserLinkController + three POST routes on the patient record
  (write door patients.amend - reception and the doctor hold it; a foreign
  account in the payload is a readable refusal, not a silent no-op; a link
  of another patient is 404 through this record's page)
- the record gains the Portal access tab (request, confirm as a second
  person with optional expiry, revoke with reason, ended bindings kept) -
  the portal's empty state advertised this action and no screen offered it
- DemoPatientPortalSeeder binds every patient-role login in every tenant
  (pilot patient@, pharmacy shopper@) THROUGH the service, by two distinct
  staff each; wired into DatabaseSeeder after the operational seed
- AppointmentBookingTest's portal tests and PatientPortalIsolationTest are
  re-based from the email-match premise to a binding precondition in setUp
- the flash on a patient record no longer renders twice at page level
  (show.blade.php duplicate dropped; the layout renders it)

Tests: PatientUserLinkTest 19, PatientPortalIsolationTest 4,
AppointmentBookingTest 20 - 43 tests / 134 assertions green; full suite
695 tests / 2899 assertions green (1 pre-existing skip).
Evidence: outputs/feature-evidence/D53-patient-portal-binding/rev-20261016-001/
(7 screenshots of the full walk-through + verification.md).
The wallet gate is NOT in this slice: the patient-namespace wallet question
is open and recorded in CONTINUE.md and the decision register.