Changelog
Platform releases, newest first. Each entry covers what shipped on the frontend and what changed in the backend API surface. Release notes with screenshots live on GitHub.
v0.2 (2026-07-12)
The employer portal is now a complete self-serve loop: sign up, pay, manage the team, file and track claims, and download documents, without leaving employer.dev.hiolly.com. The release also rolls up the week of member-journey work since v0.1: in-app GP booking, general health chat, Movement Level 2 and off-the-record chats. Everything below is wired to the live Go backend. Release notes: employer-app v0.2. The v0.2 milestone is the exhaustive list of closed story tickets (58; v0.1 holds the 70 completed before the first release).
Native in-app login
Email and password on an Olly page, no Keycloak redirect. The identity service exchanges credentials server-side (POST /identity/onboarding/login) and returns a token envelope; POST /identity/onboarding/refresh keeps sessions alive across reloads. Wrong password and unknown email return identical 401s so responses do not reveal which accounts exist. Disabled accounts get 403.
Signed document downloads
Invoice PDFs and policy schedules download from short-lived signed URLs: GET /documents/{locator}/download-url (JWT) returns a URL that is valid for 10 minutes and HMAC-signed, and the public GET /documents/{locator}/content?exp&sig endpoint verifies the signature and serves the file as an attachment. A plain browser click works with no auth header. Tampered or expired links get 401.
Document access now has a real tenancy model. A caller sees documents for their own party or organisation, and the employer of a scheme can fetch its members' policy schedules (resolved through group-scheme). A foreign document locator returns 404, indistinguishable from a document that does not exist. If the scheme resolver is down the check fails closed with 503 rather than serving the document. This closed a hole where any authenticated user could fetch any tenant's PDF by locator. See Document Service.
Claims tab and the accumulator chain
The portal gained a Claims tab: a scheme-wide claims table with search and status, member, and category filters, plus a Make-a-claim panel. Behind it, claim approval now applies utilisation to the member's accumulators, and the apply is idempotent per claim line (a ledger insert with a uniqueness key guards the balance update), so an approved claim moves the utilisation bars once and only once even on retries. Claims list endpoints are scoped to the caller and a foreign claim locator returns 404.
Member profiles and roster enrichment
The Team roster's View button opens a live member profile: identity, enrolment demographics (age, sex at birth, postcode), and the member's policy. Medical and claims data stay private to the member. GET /api/v1/schemes/{locator}/members now enriches each row with firstName, lastName, and email from policy-admin, so the roster shows names instead of raw party locators. Funnel bulk enrolment and single-member add require age, sexAtBirth, and postcode per member and reject invalid rows with 422.
Utilisation with human labels
GET /eligibility/schemes/{locator}/utilisation returns per-service usage against limits with the product catalogue's display labels ("GP video consultations", not gp_video), the same labels the quote page and the policy schedule use. Uncapped services report unlimited and render as "Included". The endpoint is gated on scheme ownership; a scheme the caller does not own returns 404.
Billing: saved card, real invoices
The Billing tab shows the Stripe payment method on file (GET /billing/payment-method), invoice history with real amounts, and per-invoice PDF downloads through the signed URL flow. A monthly premium draft invoice is raised when a policy is issued. Invoice endpoints are tenancy-scoped and a foreign invoice returns 404.
Quote funnel on the identity APIs
The funnel now runs on the identity service's onboarding endpoints: send-otp and verify-otp (which mints an onboarding session for pre-account scheme creation), redeem-magic-link, verify-pin, and set-password (which requires proof of email ownership via X-Onboarding-Session or a Bearer token). Companies House search in the funnel is live instead of a hardcoded list. See Identity.
Member journey: GP booking end to end
Triage hands off into booking without leaving the chat: a bookable disposition offers a shortlist of clinicians from live slot search, each with a profile (photo, rating, reviews) on GET /providers/{locator}. Booking creates a real episode and appointment, with a confirmation card and add-to-calendar; confirmed video appointments carry a Google Meet join link via a calendar invite. The care service gained a cancellation-reason taxonomy and a double-booking guard that returns 409 when two members race for the same slot. Red-flag cases get an emergency escalation sheet with a 999 countdown.
Member journey: health chat, Movement Level 2, incognito
"I have a health question" opens General Health Chat on the triage service's /health/v1/chat/completions: no pathway interrogation, a grounded conversational answer with an opening disclaimer, and markdown rendering across every chat surface. The Movement pillar gained a chat that logs activities from conversation and a live history chart, backed by a MET engine (Compendium-2011 registry, MET-minute and calorie metrics, weighted 7-day score against a 28-day baseline). An incognito toggle keeps chats out of history and trace export, with hard-purge on exit. Sign-in magic links now open the app directly via Universal/App Links.
Admin: Maat 360 hubs
Party-360 and Claim-360 hubs on a generic Entity360 shell: paste any locator into universal search and land on a hub with independently loading panels and honest miss states. The member timeline query dropped from 19 seconds to under half a second, and the console now sits behind a Keycloak gate (oauth2-proxy, maat-admin role).
Security fixes
- document-service:
GET /documents/{locator}no longer serves any document to any authenticated caller; reads are tenancy-checked (own party or organisation, or employer of the scheme) and foreign locators return 404. - claims and eligibility: list and read endpoints are scoped to the caller's tenant; cross-tenant reads that previously succeeded now return 404.
- identity: login responses are uniform for wrong password and unknown email, and
set-passwordrequires proof of ownership of the target email. - Cluster-only
/internal/*routes are blocked at the APISIX edge and the sensitive ones carry a fail-closedX-Internal-Serviceguard.
Known follow-ups
Recurring billing collection (drafts are raised but nothing finalises them yet, #1438), manual claims approval and accumulator reversal on reject (#1434), and app-level login rate limiting (#1437).
