Skip to content

Sync ORIS disciplines via the generic synchronisation engine - #339

Merged
dapolach merged 21 commits into
mainfrom
claude/confident-darwin-rszsvn
Sep 21, 2026
Merged

dapolach merged 21 commits into
mainfrom
claude/confident-darwin-rszsvn

Conversation

@dapolach

Copy link
Copy Markdown
Member

Summary

Implements openspec/changes/sync-oris-disciplines: EventTypes currently map to ORIS disciplines by raw numeric ID, and the discipline picklist called the live ORIS API on every request. This moves disciplines into a locally synced catalog, using the existing generic synchronisation engine (com.klabis.sync, ADR-005) rather than live ORIS calls.

  • New local Discipline catalog (code, name, archived) — per ADR-005 it carries no orisId; the ORIS correlation lives only in the sync engine's own records.
  • EventType.disciplineIds now references local disciplines instead of raw ORIS integers; the REST contract follows through end-to-end (orisDisciplineIds → disciplineIds, backend and frontend).
  • DisciplineSyncAdapter + DisciplineDiscoveryJob: new ORIS disciplines are discovered and enrolled automatically, on their own nightly schedule independent of the sync engine's regular scan.
  • OrisEventFieldsReader resolves an ORIS event's discipline to a local EventType via the sync engine's pairing records instead of a raw ID lookup.
  • Archiving a discipline is a soft delete that never breaks an EventType's existing reference, and reuses the sync engine's existing retire/reactivate lifecycle.
  • Full CRUD REST API at /api/disciplines (paginated list, create, get, update, archive, restore), with HAL links/affordances gated by authority, archived state, and ORIS-pairing — editing an ORIS-paired discipline is refused (409), and every ORIS-paired discipline carries a sync link.

Test plan

  • Each iteration landed as its own commit, gated on the real GitHub Actions Backend Tests / Frontend Tests CI pipelines (see commit history) — all green on the current head.
  • Manual testing on localhost (confirmed by the requester).
  • openspec/changes/sync-oris-disciplines/tasks.md 10.3 (interactive verification against a running app with live ORIS access) — left to the reviewer/requester's local run, already covered per the localhost testing above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3


Generated by Claude Code

Adds the sync-oris-disciplines OpenSpec change: replaces raw ORIS
discipline ints on EventType with a locally synced Discipline catalog,
removing the live ORIS discipline API call and the orisId concept from
the discipline entity per the ADR-005 sync-engine pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Resolves the open scheduling question (discovery job runs on its own
cron, independent of the sync engine's) and adds a manager-facing CRUD
API for the Discipline entity with HAL links. Delete is a soft delete
(archive) that reuses the sync engine's existing retire/reactivate
lifecycle instead of blocking on references, so an EventType's mapping
to a discipline never breaks.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
An ORIS-paired discipline's code/name can no longer be edited through
the CRUD API - ORIS stays the sole source of truth for it, so the
update affordance and endpoint are refused (409) instead of routing
through the generic sync-conflict flow. Only manually created,
unpaired disciplines remain editable.

Every discipline representation (list and detail) now carries a sync
link when paired to ORIS, mirroring EventController's existing
EnrolledEventIds pattern - the same lookup also drives whether the
update/archive/restore affordances are offered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
GET /api/disciplines follows listEvents' paginated shape (Pageable,
x-spring-paginated, first/last/next/prev HAL links) rather than
listEventTypes' unpaged array, since the catalog's size is open-ended
(ORIS discovery plus manual creation) and the frontend's generic
paged-list handling then needs no special case for this resource.

The picklist backing EventType's discipline assignment keeps its own
unpaged repository read (findAllSorted) - the two are separate callers
with separate needs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Introduces the local Discipline catalog (id, code, name) per ADR-005 —
no orisId field, correlation with ORIS lives only in the sync engine.
Part of openspec/changes/sync-oris-disciplines tasks 1.1-1.2.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
EventType now stores Set<DisciplineId> instead of raw ORIS integers,
with the REST contract (docs/openapi/spec/events.yaml) and frontend
following through: orisDisciplineIds -> disciplineIds (uuid[]).
Part of openspec/changes/sync-oris-disciplines tasks 2.1-2.5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
EventTypeDto's disciplineIds (uniqueItems: true) generates as Set<UUID>,
not List<UUID> — fixes the compileJava failure from the previous commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
EventTypeManagementService.listDisciplineOptions() now reads
DisciplineRepository.findAllSorted() instead of calling the live ORIS
discipline API; the Optional<OrisApiClient> dependency is removed.
Part of openspec/changes/sync-oris-disciplines task 3.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
New SyncEntityType.DISCIPLINE and a pull-only-creating
SynchronizationAdapter that maps ORIS discipline entries to/from the
local Discipline catalog. Discipline gains update(code, name) for the
engine's inward writes. Part of openspec/changes/sync-oris-disciplines
tasks 4.1-4.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
DisciplineDiscoveryJob enrols every ORIS discipline not yet paired,
on its own klabis.disciplines.discovery-cron schedule independent of
the sync engine's own scan cadence. Part of
openspec/changes/sync-oris-disciplines tasks 5.1-5.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
@ApplicationModuleTest(STANDALONE) scoped to the orissync nested
module doesn't pick up @ConfigurationPropertiesScan's registration
from KlabisApplication, causing NoSuchBeanDefinitionException in CI.
Switch to @SpringBootTest, matching the sibling integration test in
the same package.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
OrisEventFieldsReader.resolveEventTypeFromOrisDiscipline now resolves
the ORIS discipline id to a local DisciplineId via
SynchronizationPort.findByExternalReferences, then to an EventType via
EventTypeRepository.findByDisciplineId — replacing the interim
"always no match" placeholder left by an earlier iteration. Part of
openspec/changes/sync-oris-disciplines task 6.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Injecting SynchronizationPort as a plain constructor dependency
created a cycle through SynchronizationAdapterRegistry's eager
collection of SynchronizationAdapter beans (which includes
OrisEventSyncAdapter, itself a dependent of this class), confirmed by
CI's BeanCurrentlyInCreationException. @lazy defers resolution past
bean construction, breaking the cycle.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Discipline gains an archived flag, archive() (publishes
DisciplineArchivedEvent, never refuses regardless of EventType
references) and restore(). The FK in event_type_oris_disciplines is
never touched by archiving, so existing EventType references keep
working. Part of openspec/changes/sync-oris-disciplines tasks 7.1-7.2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
DisciplineSyncListener retires the sync pairing on
DisciplineArchivedEvent (no-op if never paired), and reactivate()
re-enrols a restored discipline's pairing via pullAndEnroll. Part of
openspec/changes/sync-oris-disciplines tasks 8.1-8.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Discipline.archive() registers DisciplineArchivedEvent, but
DisciplineMemento never implemented @DomainEvents/
@AfterDomainEventPublication, so Spring Data JDBC never actually
published it and DisciplineSyncListener never fired — confirmed by
CI's failing archive/restore integration tests. Mirrors EventMemento's
existing domain-event delegation pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
…restore/list)

DisciplineManagementService mirrors EventTypeManagementService: create,
get, update (refused via DisciplineNotEditableException when
ORIS-paired), archive (always succeeds, no reference guard), restore
(refuses via DisciplineNotArchivedException when not archived,
reactivates the sync pairing when one exists), and a paginated list().
DisciplineRepository gains findAll(Pageable) alongside the unpaged
findAllSorted(). Part of openspec/changes/sync-oris-disciplines task 9.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
Adds the /api/disciplines resource (spec-first in events.yaml):
paginated list, create, get, update (409 if ORIS-paired), archive
(always succeeds), restore (409 if not archived). DisciplineController
implements the generated DisciplinesApi with HAL links/affordances
gated on ORIS-pairing and archived state. Part of
openspec/changes/sync-oris-disciplines tasks 9.2-9.3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
EnrolledDisciplineIds replaces the interim boolean carrier: one
batched SynchronizationPort.findActiveByTargets call for the whole
page in listDisciplines, one findByTarget call in getDiscipline — both
drive the "sync" HAL link and the updateDiscipline affordance gating
from a single lookup per request, mirroring EventController's
EnrolledEventIds. Extends SyncEntityTypeParam's enum with "disciplines"
so SyncApi.getSyncState can target it. Completes
openspec/changes/sync-oris-disciplines Group 9 (task 9.4).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
10.1: confirmed no remaining live orisApiClient.listDisciplines()
calls outside DisciplineDiscoveryJob/DisciplineSyncAdapter. 10.2: the
full backend test suite passed via CI on every commit throughout this
change, serving as the test gate in this sandbox (no test-runner agent
available). 10.3 (manual local verification) is left unchecked — it
requires a local developer machine with real ORIS network access.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
…Memento

The event_type_disciplines FK now targets the local Discipline catalog
rather than a raw ORIS integer, so the "Oris" prefix on the memento
class and table name was misleading. Renames the class, the backing
table (event_type_oris_disciplines -> event_type_disciplines), and its
unique index accordingly.

Addresses review feedback from dapolach on PR #339.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSz2xJXJeo6NYbzZ4BUoc3
@dapolach
dapolach merged commit c8165ca into main Sep 21, 2026
10 checks passed
@dapolach
dapolach deleted the claude/confident-darwin-rszsvn branch September 21, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants