Commit Graph

31 Commits

Author SHA1 Message Date
marcuspaico
628e14fde4 Merge M2: blood-test labs pipeline — upload, LLM extraction, review gate, normalized biomarkers, dashboard
All checks were successful
CI / check (push) Successful in 1m14s
2026-08-17 16:26:34 -07:00
marcuspaico
a9256a765f fix(web): error handling on discard and history toggle
- LabDraft.tsx discard button: chain .catch to surface a failed discard in
  the existing error banner instead of an unhandled rejection.
- LabDraw.tsx toggleHistory: wrap the marker-history fetch in try/catch so a
  failed request silently no-ops (the row just doesn't expand) rather than
  throwing unhandled from the click handler.
2026-08-17 16:17:03 -07:00
marcuspaico
e81d07975a fix(labs): strict numeric parsing, atomic confirm guard, test integrity
- normalize.ts num(): parseFloat truncated at the first comma, so "1,200"
  silently became 1 (1000x error) and "5,5" became 5. Now strictly matches
  either US thousands-grouping or a plain number spanning the whole string;
  anything else (incl. ambiguous "5,5") returns null instead of a wrong value.
- labs.ts confirm handler: the pending-status check ran before the request
  body was read, so two concurrent confirms could both pass it and
  double-insert. Added a guarded UPDATE ... WHERE status = 'pending' as the
  first statement inside the existing synchronous transaction; zero rows
  affected throws and the route returns 409, with the fast-path check kept
  for the common case.
- Added missing `await` on two rejects.toThrow assertions (llm.test.ts,
  extract.test.ts) that were previously resolving before the assertion
  settled.
- Bumped the 11th-failed-login rate-limit test to a 30s timeout — 10
  sequential argon2id verifies can exceed bun:test's 5s default under load.
2026-08-17 16:17:03 -07:00
marcuspaico
876aa0f181 feat(security): CSP and hardening headers with upload serving
Implement Content-Security-Policy headers and strict content-type handling
for non-API responses, with x-content-type-options applied to all routes.
Adds security-headers test suite to verify header presence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:06:05 -07:00
marcuspaico
1aa600adfc feat(web): labs upload, draft review, draw dashboard with history sparklines
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 16:01:59 -07:00
marcuspaico
7e70bd1940 feat(labs): draw list/detail, marker history, PDF file serving 2026-08-17 15:57:24 -07:00
marcuspaico
96692163f1 fix(labs): synchronous confirm transaction — async callback broke atomicity
bun-sqlite's Database.transaction is synchronous, but async callbacks return
a pending Promise at the first await, causing immediate COMMIT before the
entire callback completes. This allowed partial inserts with no rollback.

Fixed by:
- Remove async from transaction callback
- Add .run() to each insert/update to execute synchronously
- Add regression test proving atomicity: transaction that throws mid-loop
  rolls back all changes (both tables empty after failure)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:52:59 -07:00
marcuspaico
038c92af45 feat(labs): draft review, confirm with normalization, discard
- Add ConfirmDraftBody schema to shared types
- Implement GET /api/labs/drafts/:id (retrieves draft with extracted data)
- Implement POST /api/labs/drafts/:id/confirm (normalizes markers, inserts lab draw + biomarkers in transaction, marks draft confirmed)
- Implement POST /api/labs/drafts/:id/discard (marks draft discarded)
- Add comprehensive test suite with 3 new tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:46:10 -07:00
marcuspaico
8e9f3d5894 feat(labs): PDF upload, text extraction, LLM draft pipeline 2026-08-17 15:41:02 -07:00
marcuspaico
89394732fd feat(llm): OpenAI-compatible chatJSON helper with injectable fetch
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:33:53 -07:00
marcuspaico
e0bb888115 fix(labs): remove BUN aliases from urea, correct DHEA-S molar mass 2026-08-17 15:28:13 -07:00
marcuspaico
ae7ec9d699 docs: fix DHEA-S molar mass in M2 plan (368.49, was 384.5) 2026-08-17 15:27:09 -07:00
marcuspaico
a08544db7d feat(labs): unit conversion, analyte registry, marker normalization 2026-08-17 15:23:34 -07:00
marcuspaico
bd4817cb77 feat(db): lab_drafts, lab_draws, biomarkers tables
Refs PAI-91.
2026-08-17 15:17:37 -07:00
marcuspaico
e3cfc6592e docs: M2 labs pipeline implementation plan
All checks were successful
CI / check (push) Successful in 53s
Labs pulled forward to M2 (was milestone 4) per Marcus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:15:38 -07:00
marcuspaico
bd00e80b44 ci: fetch via public hostname — job network cannot resolve internal gitea
All checks were successful
CI / check (push) Successful in 45s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 15:02:29 -07:00
marcuspaico
9b2cb6a853 ci: authenticated checkout via injected token
Some checks failed
CI / check (push) Failing after 29s
Anonymous HTTPS clone fails while the repo is private; token works either
way, and the internal gitea:3000 host skips the reverse proxy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:45:07 -07:00
marcuspaico
057d647423 Merge M1 scaffold: auth, encrypted settings, SPA shell, Docker, CI (PAI-90)
Some checks failed
CI / check (push) Failing after 51s
2026-08-17 14:41:07 -07:00
marcuspaico
0b0f81b7bb fix(auth): global login rate limit — XFF was spoofable and Map unbounded
The login rate limiter keyed on the client-controlled x-forwarded-for
header, letting an attacker rotate XFF for unlimited password guesses
while also growing the failures Map unboundedly (memory DoS). Since
this is a single-password instance, replace with one global
{count, resetAt} tracker per app instance: check the 15-min window and
reject at >=10 failures before verifying the password, increment on
failure, reset on success.
2026-08-17 14:34:08 -07:00
marcuspaico
0ac233945c fix: 404 unknown API routes, add .dockerignore
- Add catch-all 404 handler for unknown /api/* routes after API mounts
- Unauthed requests to unknown API paths now return 401, authed return 404
- Add .dockerignore to prevent leaking data/ (DB, secret.key), .git/, node_modules/
- Add test in auth.test.ts verifying unknown /api/nope returns 401 unauthed, 404 authed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:27:15 -07:00
marcuspaico
24bc8917f1 feat(deploy): serve SPA from server, Dockerfile, compose, deploy docs
- Add serveStatic middleware to server/src/app.ts to serve web/dist
- Include SPA fallback route for client-side routing deep links
- Create Dockerfile with multi-stage build (Node deps + build, slim runtime)
- Add docker-compose.yml for single-command deployment
- Add docs/deploy.md with deployment instructions and Caddy reverse proxy example
- Add bun run build:web step to CI pipeline after typecheck

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:23:29 -07:00
marcuspaico
1c97b8e33b fix: typecheck uses -p (non-composite projects)
Projects are non-composite with noEmit, so build mode (-b) re-builds every run.
Use -p for proper project checking: bunx tsc -p server && bunx tsc -p shared && bunx tsc -p web

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:21:07 -07:00
marcuspaico
5fbbf0676f feat(web): SPA shell — gate, settings, today placeholder
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:18:05 -07:00
marcuspaico
f33d395b71 feat(settings): LLM config API with AES-GCM sealed key, masked reads
- Add GET/PUT /api/settings for encrypted LLM configuration
- SettingsResponse with masked key display
- SettingsUpdate validation with optional fields
- Defaults: llmBaseUrl "https://openrouter.ai/api/v1", llmModel "anthropic/claude-sonnet-4.5"
- Encrypted storage with AES-256-GCM for llm_key
- getSetting() export for future route groups
- Comprehensive test coverage: defaults, updates, masking, encryption at rest, validation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 14:14:53 -07:00
marcuspaico
0b8e897e31 feat(auth): setup/login/logout, argon2id, sessions, login rate limit 2026-08-17 14:11:00 -07:00
marcuspaico
3c796da649 feat(crypto): boot-generated key + AES-256-GCM sealed values 2026-08-17 14:07:50 -07:00
marcuspaico
0f96d5e3b8 feat(db): drizzle schema (settings, sessions) + boot migrations 2026-08-17 14:04:56 -07:00
marcuspaico
caa9af2f40 fix: typecheck without emit, placeholder test, drop build artifacts
Removes composite project references in favor of simpler -p checking.
Adds noEmit to tsconfig so tsc is typecheck-only.
Adds smoke test to unblock CI test gate.
Ignores tsbuildinfo artifacts from git.
2026-08-17 14:02:12 -07:00
marcuspaico
fe9be9597c chore: monorepo scaffold, CI, MIT license
Bun workspaces (server/web/shared), typecheck+test gates, Gitea CI.
Refs PAI-90.
2026-08-17 13:55:39 -07:00
marcuspaico
e55de20573 docs: M1 scaffold implementation plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 13:52:00 -07:00
marcuspaico
cf5fd5a552 docs: Helios design spec
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-17 13:45:56 -07:00