-
Notifications
You must be signed in to change notification settings - Fork 2
Copilot instructions update #1598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<T>()` 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. | ||
BurningAXE marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * **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<T>` (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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is in the wrong section, but not sure which one would be more appropriate.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No other suitable one so will leave it here - models will get it anyway.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe under architecture?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's strictly about layers and the bigger picture. |
||
|
|
||
| ## 9. Error Handling | ||
|
|
||
| * Use `ApiResult<T>` 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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
Uh oh!
There was an error while loading. Please reload this page.