Adds 36 unit + contract tests across 7 files:
* `import-apple-health-export.test.ts` — end-to-end parse of a tiny
hand-authored fixture (spot weight, cumulative steps fold, sleep
stage, workout with sport mapping, deferred + unknown identifiers,
clinical record skip). Re-import idempotency: a second run reports
0 inserts + N updates. Memory ceiling: synthetic 10 000-record
export keeps heap delta under 100 MB.
* `hk-workout-activity-type-map.test.ts` — every value referenced
in the map exists in the canonical `workoutSportTypeEnum` union;
the iOS 18 canonical roster is covered; unknown identifiers fall
through to `"other"` with a falsy `known` flag.
* `unzip-export-xml.test.ts` — central-directory walker round-trips a
hand-built single-entry deflate ZIP; extractor throws on a missing
`export.xml` member.
* `stream-to-disk.test.ts` — boundary parser; named-field capture +
SHA-256 + text-field collection; size-cap enforcement; boundary-
missing error path.
* Route tests — auth gate (401), 403 on non-admin to the admin
variant, 413 on content-length cap, 429 on rate-limit; status
endpoint admits owner + triggering admin and 404s otherwise.
Three new routes wire the streaming parser to the wire:
* `POST /api/import/apple-health-export` — user-facing kick-off.
Streams the multipart upload straight to `/tmp` (no in-memory
buffer for the 1.5 GB hard cap), hashes the bytes inline for
content-based idempotency, creates an `ImportJob` row in
`queued`, and enqueues the `apple-health-import` pg-boss job.
Returns `202 { jobId }` so the client can poll for progress.
* `GET /api/import/apple-health-export/[jobId]/status` — polling
endpoint returning the canonical `ImportJobStatusResponse`
envelope (R-1 §8). Authorises on owner-id OR triggering admin.
* `POST /api/admin/import-apple-health-export` — admin variant
(cookie-only via `requireAdmin()`; Bearer never elevates) with
a `userId` text field naming the target user. Same content-hash
idempotency scoped per target user.
The kick-off path uses a hand-rolled multipart boundary walker
(`src/lib/multipart/stream-to-disk.ts`) so a multi-gigabyte
`export.zip` upload never lands in V8 heap — Next.js's
`request.formData()` buffers the full body. Captured bytes
stream to disk while a SHA-256 hash accumulates in the same pass.
Locks per `.planning/research/v1434-r-1-xml-import.md` §5.3, §5.4,
§5.5, §9.