APIDossierEntry now includes initials, color, age, dob, sex, is_self —
computed server-side using the same avatar color palette and age logic
as the web portal dashboard. Self entry now includes name (was empty).
Relation field now uses proper name from RBAC relation int.
Native mobile family switcher UI can display rich profile cards without
any additional API calls.
After lab trend charts render, a POST to /dossier/{id}/labs/commentary
sends compact series summaries (first→last value, % change, dates,
reference range) to Claude Haiku, which returns one sentence per metric.
Commentary appears below each chart as an italic insight, e.g.:
'Cholesterol dropped 15% from 210→178 mg/dL since March — now within range.'
Implementation:
- New handleLabCommentary() in dossier_sections.go — auth-gated, POST only,
gracefully skips if no Anthropic key configured
- Route: POST /dossier/{id}/labs/commentary (registered before /labs in mux)
- renderFilterChart() now wraps each SVG in .lab-chart-wrap with a
.lab-commentary placeholder showing a pulsing '…' while loading
- fetchLabCommentary() fires async after chart render, patches commentary
divs by data-abbr attribute when response arrives
- CSS: .lab-chart-wrap, .lab-commentary, .lab-commentary-text,
.lab-commentary-loading with pulse animation
The decompressDICOM path used gdcmconv which wasn't installed on the
server, silently failing all compressed DICOM imports.
Fix:
- Install libgdcm-tools (gdcmconv 3.0.22) on forge
- Expand isCompressedTransferSyntax to explicitly cover:
- JPEG 2000 Lossless (1.2.840.10008.1.2.4.90)
- JPEG 2000 Lossy (1.2.840.10008.1.2.4.91)
- JPEG 2000 Multi-component (1.2.840.10008.1.2.4.92/.93)
- RLE Lossless (1.2.840.10008.1.2.5)
- Deflated Explicit VR (1.2.840.10008.1.2.1.99)
- JPEG-LS already covered by 1.2.840.10008.1.2.4 prefix
- gdcmconv -w handles all of these natively
gdcmconv is now available at /usr/bin/gdcmconv (v3.0.22).
JPEG 2000 lossless DICOMs from any scanner will now import correctly.
Adds compareStudies() to viewer.js:
- Switches to 2-panel layout
- Loads most recent study (studies[0]) in left panel
- Loads prior study (studies[1]) in right panel
- Auto-matches same orientation series (AX > SAG > COR)
- Enables sync scroll automatically
- Jumps both panels to middle slice
Adds 'Compare' button to viewer toolbar (viewer/main.go) next to
1/2/3 Panel and 3D buttons.
Sync scroll already worked across studies via slice_location matching —
this just makes the workflow one click.
The old findTag scanned raw bytes for the 4-byte tag pattern, which
caused false matches inside Siemens CSA private OB blobs (e.g. the
large 0029,1020 Series Header). This corrupted body_part and other
fields on Siemens MAGNETOM Sola MRIs because findTag(0x0018, 0x0015)
hit a matching byte sequence inside the binary payload before reaching
the real BodyPartExamined element.
Fix: walkToTag() walks the DICOM element stream sequentially, reading
VR and length fields to skip element values entirely. Falls back to
byte-scan only on corrupt/truncated length fields. findLastTag updated
to use the same walker.
- Add html.EscapeString() to series_desc when building Series struct
- Prevents JavaScript injection via malicious DICOM metadata
Security impact: XSS payloads in series descriptions now render as harmless text.
- Store session tokens server-side on login (was: generated but not stored)
- Add /api/v1/auth/logout endpoint for server-side session invalidation
- Delete old sessions on login to prevent session fixation attacks
- Add Cache-Control: no-store headers to all auth responses
Security fixes:
1. Session identifiers now rotated on login (old sessions deleted)
2. Logout properly invalidates server-side session
3. Auth responses include anti-caching headers
Add --daemon and --interval flags to import-renpho for continuous polling.
Daemon handles SIGTERM/SIGINT gracefully, logs errors per account without
exiting. Systemd user service at ~/.config/systemd/user/renpho-poller.service.
Remove health-poller/ (Python stub that never wrote data).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- CR-001: Remove auth backdoor code 250365 (lib/dbcore.go)
High fixes:
- HI-001: Fix CORS wildcard to use origin allowlist (portal/api_mobile.go, portal/mcp_http.go)
- HI-002: Fix LOINC skip logic - skip only if BOTH SearchKey2 AND LOINC are set (lib/normalize.go)
Also added:
- Full code review report at docs/CODE-REVIEW-2026-02-28.md
14 issues found: 2 critical, 4 high, 5 medium, 3 low
3 fixes applied, remaining are documented for follow-up
- Add import-renpho: Go binary to sync Renpho body composition data
into inou vitals via direct EntryWrite (AES-128/ECB Renpho API client,
13 body metrics, dedup, auto RBAC grants, -setup/-discover modes)
- Add POST /api/v2/readings endpoint for batch vital ingest
- Fix dashboard lab chip: filter by lab_order not lab_report
- Portal: upload handler, dossier page rework, dashboard updates
- Remove tools/fix-lang (replaced by toolkit translate)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix lab chart reference bands: parse DOB in DossierFromEntry, generate
deterministic ref_ids in import-caliper (was collapsing 4363 rows to 1)
- Consolidate DossierFromEntry into lib/dbcore.go (eliminate portal duplicate)
- Add Import field to entries for batch undo (NextImportID, all import paths)
- MyChart direct JSON parsing (skip Gemini for structured lab data)
- Multi-order extraction from markdown/text tables
- Normalize progress callback for UI feedback
- DICOM import, genome import, API, portal, MCP, translation updates
- Remove test DICOM data from repo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
api-docs.txt: token is NOT the dossier ID — it's a time-limited encrypted
string. Point users to /connect for token generation.
install_public.tmpl: Grok prompt now points to /connect (Grok tab) for
the token, not /dashboard which doesn't show it.
- Remove inou_bridge_win/darwin download links (fully web-based MCP now)
- Mirror English connect.tmpl structure exactly
- Proper Dutch and Russian translations throughout
- Claude Desktop, Grok, ChatGPT, and Other AI tabs all translated
- Functional prompts (custom instructions, Grok API prompt) kept in English
Reference data simplification (choke point pattern):
- Remove RefSave/RefDelete from lib (import-time only, not runtime)
- Remove LabTestSave*, LabRefSave* from lib/lab_reference.go
- Remove PopulateReferences (LLM-based ref generation)
- Keep only RefQuery() for runtime reads
- Import tools handle their own SQL inserts
Rate limiting removed:
- Delete new_signups table and all rate limit code
- Solved via different approach (not in codebase)
Database consolidation (on staging):
- Moved genotypes table (30K SNPs) to reference.db
- Deleted empty DBs: portal.db, rate_limit.db, snpedia.db, ratelimit.db
Net -293 lines. Runtime code now only reads reference data via RefQuery().
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Simplify access control from 500+ lines to ~50 lines of core logic:
- New permission bitmask (PermRead/Write/Delete/Manage)
- Hierarchical access (dossier → category → entry)
- Single choke points: CheckAccess(), EntryQuery(), DossierQuery()
- All data access now enforced through lib RBAC layer
- Removed complex role templates and permission caching
Also improved NewID() to use UUID v4 + SHA-256 hash for better
randomness distribution (was limited to 0-7 hex start).
Net -210 lines across 28 files. Ready for staging deployment.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
list_dossiers, list_studies, list_series, list_slices, query_entries,
get_categories, query_genome — all now call lib directly with
AccessContext{AccessorID: dossierID}. No more HTTP roundtrip to the
internal API with its separate auth path.
Image and journal tools still use API (image rendering logic lives
there, and the API already enforces RBAC via lib.CheckAccess).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename Query→dbQuery, Save→dbSave, Load→dbLoad, Delete→dbDelete,
Count→dbCount in lib/db_queries.go. Go compiler now prevents any code
outside lib/ from bypassing RBAC checks.
All external callers migrated to RBAC-checked functions:
- EntryCategoryCounts, EntryCount, EntryListByDossier (new)
- LabTestList, LabEntryListForIndex, LabRefLookupAll (new)
- GenomeQuery now requires AccessContext
- EntryDeleteByCategory/EntryDeleteTree now require AccessContext
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace N separate SQL queries with single IN clause for rsids and genes
- Dedup results by rsid, merging categories from multiple tiers
- Add repute filter (Good/Bad/Clear) to genome queries
- Expose limit/offset as MCP parameters
- Add genotype to search check
- Fix category filter in genomeEntriesToResult
- Remove deprecated api/api_categories.go and api/api_genome.go
- Change GenomeMatch to use Categories []string instead of Category+Subcategory
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move GenomeQuery logic from api/api_genome.go to lib/v2.go so MCP
handler calls lib directly instead of HTTP round-trip (fixes 403 on
genome queries via Claude.ai MCP - was hitting RBAC table mismatch)
- Generate CategoryFromString from categoryNames in init() (single
source of truth, removes 9 unused aliases)
- Redact sensitive variants (Bad repute, magnitude >4) in targeted
queries: genotype/summary replaced with "hidden" + hint to use
include_hidden=true. Broad queries still suppress entirely.
- API handler is now a thin wrapper parsing query params
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed v1Entries and v1Entry to return category names ('genome', 'upload')
instead of keys ('category004', 'category005'). This makes the API consistent
and prevents MCP from passing back the wrong format.
Removed category004 format parsing since API no longer returns it.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace generic 'no genome data' message with specific error codes:
- GENOME_NO_EXTRACTION: extraction entry not found
- GENOME_VARIANT_QUERY_FAILED: variant query failed
Makes debugging MCP issues much faster by pinpointing exact failure point.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Two bugs fixed:
1. genome query RBAC: Someone removed sysCtx and changed back to user ctx after Saturday's fix, causing RBAC to block genome data access. Restored system context usage with dossier access check first.
2. query_entries category filter: API expected category names like 'genome' but MCP returns 'category004' format. Now supports both formats.
Fixes:
- api/api_genome.go: Restore system context for GenomeGetExtraction, GenomeGetTiers, GenomeGetVariants
- api/api_v1.go: Parse both 'category004' and 'genome' formats in v1Entries
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: Sophia's genome data was stored in legacy flat format where
all variants were top-level category 4 entries with rsID as the type
field. The API expected new hierarchical format (extraction → tier →
variant), causing "no genome data" errors.
Changes:
- GenomeGetExtraction(): Detect legacy data, return synthetic extraction
- GenomeGetTiers(): Return synthetic "all" tier for legacy format
- GenomeGetVariants(): Query top-level entries for legacy format
Test results:
- Sophia's 4960 genome variants now accessible via /api/genome
- Gene queries work: ?gene=MTHFR,COMT,MTR,MTRR returns 5 matches
- MCP tools (query_genome) now function correctly
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace specific DPO name with generic privacy email across all legal templates
- Update DPA to clarify third-party services vs sub-processors distinction
- Add privacy policy and DPA cross-references in Terms
- Add intellectual property section to Terms
- Improve prompts UI with Yes/No buttons, section headers, and better visual hierarchy
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add /legal/terms with comprehensive ToS content
- Add terms link to footer navigation
- Add /legal/terms to defense.go whitelist for external access
- Update privacy policy and DPA templates with improved styling
- Refactor RBAC editor template formatting
- Add prompts AI setup documentation
- Include database migration scripts
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed the backward compatibility fallback that checked the old
dossier_access table from CanManageDossier/CanAccessDossier - it was
a security risk (hidden path that bypassed the new RBAC system).
Instead, added MigrateOldAccess() that converts existing dossier_access
entries to proper access grants on startup (idempotent - skips existing).
Migration rules:
- Self-references (accessor == target) skipped (owner access is automatic)
- can_edit = 1 → "rwdm" root grant
- can_edit = 0 → "r" root grant
- Role set to "Migrated" for tracking
Result: 12 grants migrated from old table.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
RBAC editor was failing with 403 Forbidden when trying to edit permissions
for users who have access via the old dossier_access table but not the new
access grants table.
Added fallback logic to CanManageDossier and CanAccessDossier:
1. Check new RBAC system (access table) first
2. If no grant found, check old dossier_access table
3. For manage: check can_edit = 1
4. For access: check status = 1
This allows existing access relationships to work with the new RBAC editor
while we migrate data from old to new system.
Fixes: "Forbidden" error when editing permissions for legacy access grants
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated "Edit" button in dossier Privacy section to open new RBAC editor
instead of old access editor. Users can now access granular per-category
permissions directly from the dossier page.
Location: Privacy section → Edit button next to each person with access
Route changed: /dossier/{id}/access/{grantee_id} → /dossier/{id}/rbac/{grantee_id}
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>