From 9d2cb8c801cacda46a568b2e588407504938d699 Mon Sep 17 00:00:00 2001 From: Marinov Date: Thu, 19 Feb 2026 19:44:10 +0200 Subject: [PATCH] Update copilot-instructions.md with expanded knowledge --- .github/copilot-instructions.md | 103 +++++++++++++++++++++++++------- .github/event-system.md | 41 +++++++++++++ 2 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 .github/event-system.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 86201cb89c..631ae8831f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,30 +1,91 @@ -1. Code Style +## 1. Code Style * Code follows general Kotlin code style recommendations. -* Use Android Jetpack ViewModel for managing UI-related data and use viewModelScope for coroutines. -* Use LiveData for observing and updating UI state. Use Custom event wrappers `LiveDataEventWithContent` to handle one-time events. -* Use delegates like `FragmentViewBindingDelegate` to simplify view binding in fragments. +* Use Android Jetpack ViewModel for managing UI-related data and use `viewModelScope` for coroutines. +* Use `LiveData` for observing and updating UI state. Use custom event wrappers `LiveDataEventWithContent` and `LiveDataEvent` (in `infra/core`) to handle one-time events. +* Use `FragmentViewBindingDelegate` (`by viewBinding(...)`) to simplify view binding in fragments. +* All UI is View-based (XML layouts + ViewBinding). +* Use `internal` visibility for module-internal classes (fragments, view models, helpers) to enforce module boundaries. Only `Contract` objects and public API types should be `public`. +* Use `Simber` (in `infra/logging`) for logging instead of `Log` or `println`. -2. Architecture Layers: +## 2. Architecture Layers -* Feature Modules: Manages UI components and ViewModels as well as UI-dependant features. Implemented in `:feature` subfolder. -* Infra Modules: Contains use cases and core business logic. Implemented in `:infra` subfolder. -* Modality Modules: Contain business subsets of feature and infra modules related to a specific biometric modality. Implemented in `:face` - and `:fingerprint` subfolder. -* Each layer is isolated. Feature modules depend on other features and infra modules, infra modules only depend on other infra modules. +* **Feature Modules** (`:feature:*`): UI components, Fragments, ViewModels, and UI-specific use cases. Applied via `simprints.feature` convention plugin. +* **Infra Modules** (`:infra:*`): Core business logic, repositories, use cases, data layer. Applied via `simprints.infra` convention plugin. +* **Modality Modules** (`:face:*`, `:fingerprint:*`): Biometric modality-specific subsets of feature and infra modules. +* **App Module** (`:id`): Application entry point with Firebase, Crashlytics, and Play Store deployment config. +* Each layer is isolated. Feature modules depend on other features and infra modules; infra modules only depend on other infra modules. -3. Dependency Injection: +## 3. Module Communication & Navigation -* Use Dagger Hilt for dependency injection with the `@Inject` constructor as the preferred method of injection. -* Use Singleton scope (`@Singleton`) for critical dependencies. -* ViewModels are injected using Hilt (`@HiltViewModel`). +* Each feature module exposes a public `Contract` object (e.g., `ConsentContract`, `LoginContract`) defining: + * `DESTINATION` — the navigation destination ID. + * `getParams(...)` — a factory method to create type-safe navigation arguments. +* Each feature module has its own navigation graph in `src/main/res/navigation/graph_*.xml`. +* Use Jetpack Navigation with SafeArgs (auto-applied by `simprints.feature` plugin). +* Fragment-level result handling supports cross-navigation-graph communication via `SavedStateHandle` with `FragmentResultListener` fallback for process death resilience. +* The orchestrator (`feature/orchestrator`) acts as a central coordinator, dynamically navigating to feature graphs and handling results based on a step-based state machine. +* Use `navigateSafely()` for navigation, `finishWithResult()` to return results, and `handleResult()` to receive results. All defined in `infra/ui-base/navigation/NavigationResultExt.kt`. -4. Unit testing rules: +## 4. Build System -* Use JUnit 4 for unit testing, MockK for mocking, Google Truth for assertions, AndroidX Test for Android-specific testing. +* Convention plugins are defined in `build-logic/convention/` and applied via plugin IDs like `simprints.feature`, `simprints.infra`, `simprints.library.room`, `simprints.library.hilt`, etc. +* Version catalog is in `gradle/libs.versions.toml`. +* Build types: `debug`, `staging` (minified), `release` (minified, `@StopShip` lint fatal). +* Room modules use SQLCipher encryption controlled by `DB_ENCRYPTION` build config. + +## 5. Dependency Injection + +* Use Dagger Hilt for dependency injection with `@Inject` constructor as the preferred method. +* All Hilt modules use `@InstallIn(SingletonComponent::class)`. Use `@Singleton` for critical dependencies. +* ViewModels are annotated with `@HiltViewModel` and injected via `@Inject constructor`. +* Use `@Binds` (abstract module) for interface-to-implementation bindings. Use `@Provides` (object module) for factory methods. +* Custom dispatcher qualifiers: `@DispatcherIO`, `@DispatcherMain`, `@DispatcherBG` for coroutine scopes. + +## 6. Serialization + +* Use `kotlinx.serialization` with the global `SimJson` instance (in `infra/serialization`). +* Domain models use `@Serializable` annotations with compile-time polymorphic serialization. +* Use `@SerialName` for custom JSON field names. When referencing companion object constants in `@SerialName`, use direct reference (e.g., `BiometricReferenceType.FACE_REFERENCE_KEY`) without `.Companion`. + +## 7. Data Persistence + +* **Room**: Used for events, logs, image metadata, and enrolment records. Modules apply `simprints.library.room` plugin. +* **Realm**: Deprecated database for enrolment record storage (`infra/enrolment-records/realm-store`). Will be removed in a future release. +* **DataStore/Protobuf**: Used for config and auth stores via `simprints.library.protobuf` plugin. + +## 8. Network + +* Retrofit with `kotlinx.serialization` converter, OkHttp client with retry logic (5 attempts default, exponential backoff). +* API calls return `ApiResult` (sealed class with `Success`/`Failure`) from `infra/backend-api`. +* All network calls are `suspend` functions. Use `withContext(Dispatchers.IO)` for network operations. +* `SimCoroutineWorker` (in `infra/core`) is the base class for WorkManager workers, providing structured `retry()`, `fail()`, `success()` result helpers and foreground notification support. + +## 9. Error Handling + +* Use `ApiResult` sealed class for network error handling with `getOrThrow()` and `getOrMapFailure()`. +* Specific exceptions: `BackendMaintenanceException`, `NetworkConnectionException`, `RetryableCloudException`. +* Network-related exceptions (timeout, SSL, unknown host) are downgraded from error to info severity in `Simber` to reduce Crashlytics noise. + +## 10. Coroutines + +* Use `viewModelScope` for ViewModel coroutines. Use `runTest { ... }` for coroutine tests. +* Functions returning `Flow` should **not** be marked `suspend` — expose them as plain functions returning `Flow`. +* Use `StateFlow` and `SharedFlow` for reactive state in ViewModels. + +## 11. Unit Testing + +* Use JUnit 4, MockK for mocking, Google Truth for assertions, AndroidX Test and Robolectric for Android-specific testing. * Test classes use the `*Test` suffix. Test methods use descriptive names with backticks. -* Setup logic is in `@Before` methods. Tests use `@RunWith(AndroidJUnit4::class)` where appropriate. Coroutine tests use `runTest { ... }`. -* Use Given-When-Then structure. Keep tests concise and focused. Use relaxed mocks to reduce boilerplate. -* Use helper methods for mock objects or test data. -* Use lifecycle-aware and coroutine testing where appropriate. -* Isolate Android framework dependencies unless required. +* Setup logic is in `@Before` methods. Tests use `@RunWith(AndroidJUnit4::class)` where appropriate. +* Use Given-When-Then structure. Keep tests concise and focused. Use `@RelaxedMockK` / relaxed mocks to reduce boilerplate. +* Use `TestCoroutineRule` (in `infra/test-tools`) — sets `UnconfinedTestDispatcher` as `Dispatchers.Main`. +* Use `InstantTaskExecutorRule` for synchronous LiveData execution in tests. +* LiveData assertions: `assertEventReceived()`, `assertEventReceivedWithContent()`, `getOrAwaitValue()` from `infra/test-tools`. +* Use `@TestInstallIn` with fake Hilt modules for DI replacement in tests. +* Fragments, Activities, and Hilt Modules are excluded from SonarCloud coverage via project-level settings. Use `@ExcludedFromGeneratedTestCoverageReports("UI class")` to exclude other UI-adjacent classes (adapters, view helpers, etc.). + +## 12. Event System + +* The project uses a custom event system (`infra/events`) to track user actions, biometric captures, matches, and workflow steps as structured, auditable events. +See [event-system.md](.github/event-system.md) for details, including event hierarchy, scopes, persistence, sync, and tokenization. diff --git a/.github/event-system.md b/.github/event-system.md new file mode 100644 index 0000000000..cf9f77fedc --- /dev/null +++ b/.github/event-system.md @@ -0,0 +1,41 @@ +## Event System + +### Event Hierarchy + +* `Event` is a **sealed class** with 60+ concrete subtypes (e.g., `AuthenticationEvent`, `FaceCaptureEvent`, `EnrolmentEventV4`, `OneToOneMatchEvent`). +* Each `Event` has: `id`, `type: EventType` (enum discriminator), `payload: EventPayload`, `scopeId`, and `projectId`. +* `EventPayload` is a sealed base class with: `type`, `eventVersion: Int` (for schema versioning), `createdAt`, `endedAt`, and `toSafeString()` for safe logging. +* Each concrete event defines its own nested `Payload` data class extending `EventPayload`. +* Serialization uses **manual polymorphic dispatch** via `concreteSerializer` instead of Kotlinx's built-in discriminator, to avoid collision with the business `type` field. + +### Event Scopes + +* Events are grouped into **scopes** via `EventScope` — a container with metadata about the session (app version, device info, language, modalities, project config). +* `EventScopeType` enum: `SESSION` (user workflow), `UP_SYNC`, `DOWN_SYNC`, `SAMPLE_UP_SYNC`. +* Scopes have a lifecycle: created with `createEventScope()`, ended with `closeEventScope()` which sets `endedAt` and `endCause` (`WORKFLOW_ENDED` or `NEW_SESSION`). +* Events reference their scope via `scopeId`. + +### Adding Events + +* Use `SessionEventRepository.addOrUpdateEvent(event)` to add an event to the current session (auto-scopes). +* Use `EventRepository.addOrUpdateEvent(scope, event)` for explicit scope association. +* Events are validated before saving (e.g., duplicate GUID checks). +* Use @SessionCoroutineScope for adding or updating events and sessions +* Feature modules usually create events in use cases (e.g., `AddCaptureEventsUseCase`) and call the repository to persist them. + +### Local Storage + +* Events are stored in Room as `DbEvent` with the full event serialized as JSON in `eventJson`. +* Scopes are stored as `DbEventScope` with metadata in `payloadJson`. + +### Event Sync + +* **Upload**: `EventUpSyncTask` loads closed scopes + events, tokenizes sensitive fields, maps domain models to API models (`ApiEvent`, `ApiEventScope`), and uploads in batch. +* **Download**: `SimprintsEventDownSyncTask` fetches remote events and creates local enrolment records. +* API models (`ApiEvent`, `ApiEventScope`, `ApiEventPayloadType`) are separate from domain models; mapping is done via `MapDomainEventScopeToApiUseCase` and `MapDomainEventToApiUseCase`. + +### Tokenization (Sensitive Data) + +* Sensitive fields (e.g., `attendantId`, `moduleId`, `userId`) use `TokenizableString` — a sealed class with `Raw` and `Tokenized` subtypes. +* Events declare tokenizable fields via `getTokenizableFields()` and accept encrypted values via `setTokenizedFields()`. +* `TokenizeEventPayloadFieldsUseCase` encrypts raw fields before upload. The `tokenizedFields` list on `ApiEvent` documents which JSON paths contain encrypted data.