Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TEDY backend — validation/review brief

A self-contained task for an independent review agent. Read this end to end, then execute the Validation objectives. Work read-only unless asked to fix; report findings ranked by severity with file:line evidence. The branch is claude/terminology-editor-helex-migration-c79310; all TEDY backend work lives under modules/tedy/backend/ and migration/tedy-migrate/.


1. Initial task (what was asked)

Migrate the terminology-editor backend from a reference terminology server into helex-tx, on the Helex platform libraries. Hard requirements from the original brief:

  1. Create a backend under modules/tedy based on an emr module template.
  2. Add run_frontend.sh tedy / run_backend.sh tedy update scripts.
  3. Use helex-commons instead of the reference commons; no reference-product or vendor names (“termx”/“kodality”) may remain in committed code, comments, commits, or PRs (real URLs only in a gitignored .env).
  4. Use Forge (FHIR model lib) instead of the reference’s Zmei.
  5. Analyze the DB, rewrite changesets removing vendor references; every changeset must be idempotent (create if not exists, idempotent DML); new schema is tedy; ship a separate jar that migrates the legacy DB → tedy, run manually before install; improve the model (a redundant table, an over-long table name).
  6. (Phase 2) FHIR mapping between TEDY resources and FHIR + EMR FIS — out of scope for this review.
  7. Do not use Kefhir; recreate needed functionality in TEDY.
  8. Port all tests from the reference server. 9/10. Ensure endpoints work; note optimizations vs the reference.
  9. Be extensible for OAuth + user permissions.

Full plan: ~/.claude/plans/the-goal-is-migrate-spicy-shore.md.

2. Plan / decisions (locked with the user)

  • Template: emr modules/tx/backend (org.helex.tx) — the terminology-domain module — not generic mdm.
  • Build model: start standalone — the backend builds independently against published artifacts org.helex.emr:commons-* (0.1.0-SNAPSHOT) and org.helex.forge:* (0.20.0), no EMR checkout required; structured to also composite-build inside EMR later.
  • Package namespace: org.helex.tx.tedy (emr’s tx module is uniquely org.helex.tx).
  • Auth: TEDY-owned (a pluggable SessionProvider chain + @Authorized interceptor) because core-backend is not published. ABAC ACL deferred until commons-tenant is published (a one-line emr change was made: it was added to publishableCoreLibraries in emr/repo/build.gradle.kts, but the publish itself is a pending user action). Code Systems etc. are ACL-free for now (like emr’s global mdm.practitioner).
  • DB idempotency: the tedy module changelog is idempotent (CREATE TABLE IF NOT EXISTS, guarded create_table_metadata, insert … on conflict) so a fresh install and a migrated legacy DB converge. It first includes the shared core schema from the published commons-db-core.
  • Data-model renames (agreed): entity_propertycode_system_property, entity_property_valueconcept_property, defined_entity_propertydefined_property, entity_version_code_system_version_membershipconcept_version_membership, map_set*concept_map* (Java package mapsetconceptmap, models MapSet*ConceptMap*; REST path stays /ts/map-sets). code_system.permissions is dropped (ACL replaces it). code_system_entity kept (polymorphic supertype of concept + code_system_association).
  • Tests: two-stage (strengthen the reference suite first, then port to JUnit) — the reference-side strengthening (Stage A) and the full port (Stage B) are not yet done; TEDY ships its own JUnit Testcontainers ITs per slice instead.
  • Jackson: commons-db is Jackson 2; Spring MVC (Boot 4) is Jackson 3 — loosely-structured fields are Object/collections to cross that boundary.

3. Implemented part (what to review)

Commits on the branch (newest first):

CommitContent
104cbf7migration extended to Value Sets + Spaces
6a0c13arun/contract-verification scripts
44f51a2tedy-migrate standalone migration jar
66989f5Spaces slice
5f059e1Naming Systems + Defined Properties
a184461Map Set slice (concept_map)
3c9b3e5Value Set slice (compose + expansion)
4e8c41bconcept associations
3da59dfscaffold + auth core + Code System + concepts

Backend (modules/tedy/backend, org.helex.tx.tedy)

  • Standalone Gradle (JDK 25, Spring Boot 4.0.0); build.gradle.kts, application.yml, application-dev.yml.
  • Auth core (auth/): SessionProvider chain → SessionFilterSessionStore; @Authorized + AuthorizationInterceptor; SecurityConfig (stateless, authz via the chain); GuestSessionProvider grants *.*.* in dev (config-gated). Real OAuth/JWT provider is a documented follow-up.
  • Resources — each model/ → repository/ (BaseRepository + forge-anvil) → service/ → api/: Code System (codesystem/, incl. concept/), Value Set (valueset/), Map Set (conceptmap/), Naming Systems (namingsystem/), Defined Properties (definedproperty/), Spaces (space/).
  • Changelog (src/main/resources/tedy/db/changelog/): master includes core-db then the idempotent tedy DDL 01…07.
  • REST: /ts/code-systems (+ /concepts, associations, versions, spaceId filter), /ts/value-sets (+ /expand), /ts/map-sets (+ /associations), /ts/naming-systems, /ts/defined-properties, /spaces (+ /packages).
  • Optimization vs reference: Value Set compose ruleSet and Map Set scope stored as one JSONB column instead of the reference’s normalized rule tables; expansion computed on demand.

Migration jar (migration/tedy-migrate)

  • Standalone runnable jar. TedyMigrator runs ordered idempotent INSERT … WHERE NOT EXISTS scripts (src/main/resources/migrate/*.sql) in one transaction, disabling the sys-column trigger to preserve sys_* verbatim, applying every rename + the defined_property_id backfill + a core.seq_id fixup. Covers CS-family, associations, naming systems, concept maps, value sets, and spaces (sys.*tedy.*).

Scripts (scripts/)

  • run_backend.sh tedy (Docker Postgres + dev profile Liquibase), run_frontend.sh tedy (VITE_TX_TARGET → local backend, delegates to dev.sh), verify-tedy.sh (curl smoke test), check-naming.sh (prohibited-term gate).

How it was verified (reproduce these)

  • Backend ITs (Testcontainers Postgres, run Liquibase, hit HTTP): CodeSystemControllerIT, ValueSetControllerIT, ConceptMapControllerIT, RegistryControllerIT, SpaceControllerITcd modules/tedy/backend && ./gradlew test (7 tests, all green).
  • Migration IT: cd migration/tedy-migrate && ./gradlew test (2 tests: renames+backfill, idempotency).
  • Live smoke: ./scripts/run_backend.sh tedy then ./scripts/verify-tedy.sh — all endpoints 200/204.
  • Naming gate: ./scripts/check-naming.sh — clean.

Known gaps / deferred (do NOT report as defects; confirm they’re correctly scoped out)

ABAC ACL wiring (pending commons-tenant publish); FHIR facade + FIS (Phase 2); the reference-suite test port (Stage A/B); concept tree-search / cross-CS /ts/concepts / /entity-versions; Spaces GitHub/DevOps sync + async diff; Map Set property-values / statistics / automap.


4. Validation objectives (the review task)

Assess correctness, faithfulness, and safety. Prioritize:

  1. Contract fidelity — do the controllers match the endpoint paths, params, and JSON shapes the frontend clients expect? Cross-check modules/tedy/backend/.../api/* against libs/tedy/src/lib/client/*.ts and libs/tedy/src/lib/types/*.ts (paths, SearchResult = {data,meta}, field names like mapSet/designationType/entityProperty, the /transaction bodies).
  2. Persistence correctness — repositories: JSONB/text[]/Forge-datatype column mappings (AnvilBeanProcessor/AnvilSqlBuilder), the client-supplied-text-id upsert, optimistic-lock/soft-delete handling, and the spaceId join. Look for SQL injection (all use bound ?), N+1 decoration, and Jackson 2/3 boundary bugs.
  3. Renames & data model — confirm every agreed rename is applied consistently across DDL, models, repos, and the migration; confirm permissions is dropped and code_system_entity is retained as the supertype.
  4. Migration safety — are all scripts truly idempotent (re-run = no change)? Are sys_* preserved (trigger disabled)? Is the defined_property_id backfill correct and collision-free? Does the sequence fixup prevent future id collisions? Any source column referenced that wouldn’t exist in the real legacy schema? (The IT setup approximates it — flag mismatches.)
  5. Auth extensibility — is the SessionProvider chain genuinely pluggable, and is the guest *.*.* default safe (dev-gated, not on in prod)? Is @Authorized enforced on every mutating endpoint?
  6. Naming hygiene — independently grep committed sources for the prohibited terms.
  7. Idempotency of the module changelog — would a fresh install and a migrated DB converge? Any create_table_metadata/index that isn’t guarded?
  8. Build/verify reproducibility — do the ITs and the live smoke test actually pass on a clean checkout?

Deliver: a ranked findings list (severity, file:line, failure scenario), plus a short verdict on whether the Phase-1 backend is faithful to the plan and safe to build on.