revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5

=== JpaAdapterComponentsConfig package scan ===
     1	package dev.caskeleton.adapter.outbound.persistence.config;
     2	
     3	import org.springframework.context.annotation.ComponentScan;
     4	import org.springframework.context.annotation.Configuration;
     5	
     6	/**
     7	 * Registers the JPA adapter's scanned components, which nothing registered (JPA-INT-006).
     8	 *
     9	 * <p>The composition root's {@code @ComponentScan} excludes {@code
    10	 * dev.caskeleton.adapter.outbound.persistence.**} by regex, and that exclusion is correct: it is
    11	 * what makes an optional capability optional, so a deployment with JPA off assembles no persistence
    12	 * beans rather than assembling them and hoping each one remembered to carry the switch.
    13	 *
    14	 * <p>What was missing is the other half. Eight classes in this leaf are written as scanned
    15	 * components — {@code SpringTransactionPort}, {@code PersistenceExceptionTranslator}, {@code
    16	 * StandardSqlStateErrorMapping}, {@code DomainContextAuditContextPort}, the idempotency store and
    17	 * its reaper, and the outbox store and its reaper — and once the broad scan stopped reaching them,
    18	 * nothing else did. They are annotated {@code @Component} and {@code @Repository} and were beans in
    19	 * no running application: {@code TransactionPort} in particular had no implementation at all, so
    20	 * every use case that opens a transaction had no port to open it with.
    21	 *
    22	 * <p>It surfaced as an unsatisfied dependency the first time a capability that needs a transaction
    23	 * was actually assembled — the notification orchestrator, in the local-notification-ingest lane —
    24	 * rather than as anything a unit test could see, because each of these classes is constructed
    25	 * directly by its own tests.
    26	 *
    27	 * <p>So the scan is restored, narrowed to the packages it should always have covered and reachable
    28	 * only through {@code PersistenceJpaRootAutoConfiguration}, which carries the JPA master switch.
    29	 * Off is still structural.
    30	 *
    31	 * <p>Two packages are deliberately absent:
    32	 *
    33	 * <ul>
    34	 *   <li>{@code ..persistence.fileserver} — gated on its own capability switch, scanned by {@link
    35	 *       dev.caskeleton.adapter.outbound.persistence.fileserver.FileserverJpaPersistenceConfig};
    36	 *   <li>{@code ..persistence.notification} — assembled explicitly, bean by bean, by {@code
    37	 *       NotificationJpaPersistenceFacade}.
    38	 * </ul>
    39	 *
    40	 * <p>Components under these packages keep their own {@code @ConditionalOnProperty} guards; being
    41	 * scanned makes them candidates, not unconditional beans.
    42	 *
    43	 * <p>{@code ..persistence.lock} is in the list for the same reason and with the same history.
    44	 * {@code DistributedLockPersistenceConfig} owns both lock providers — the in-process registry and
    45	 * the JDBC one — and was registered by nothing but a test calling {@code ctx.register(...)}. So a
    46	 * single-instance deployment had no {@code DistributedLockPort} at all, and a multi-instance one
    47	 * could not start: the composition root's own {@code DistributedLockConfig} asks for a bean
    48	 * qualified {@code jdbcDistributedLock} that only that configuration declares.
    49	 *
    50	 * <p>Each package's {@code @ConfigurationProperties} type is enabled by a configuration inside that
    51	 * same package — {@code JpaTransactionConfig} for {@code JpaTransactionSettings}, {@code
    52	 * DistributedLockPersistenceConfig} for {@code LockSettings} — rather than from here. Enabling them
    53	 * centrally would give {@code config} an edge to {@code lock} and {@code transaction} that the
    54	 * module map does not grant it, and the map is right: this class knows which packages to scan, not
    55	 * what is inside them. They need enabling at all because {@code @ConfigurationPropertiesScan}
    56	 * excludes this tree as deliberately as {@code @ComponentScan} does.
    57	 */
    58	@Configuration(proxyBeanMethods = false)
    59	@ComponentScan(
    60	    basePackages = {
    61	      "dev.caskeleton.adapter.outbound.persistence.audit",
    62	      "dev.caskeleton.adapter.outbound.persistence.failure",
    63	      "dev.caskeleton.adapter.outbound.persistence.idempotency",
    64	      "dev.caskeleton.adapter.outbound.persistence.lock",
    65	      "dev.caskeleton.adapter.outbound.persistence.outbox",
    66	      "dev.caskeleton.adapter.outbound.persistence.transaction"
    67	    })
    68	public class JpaAdapterComponentsConfig {}

=== runtime-role policy production callers ===
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/PostgreSqlRuntimeRoleVerifier.java:57:    policy.requireSafe(verify(dataSource));
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicy.java:19:public record DatabaseRolePolicy(Set<String> allowedRoles, SearchPathPolicy searchPathPolicy) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicy.java:55:    searchPathPolicy.requireSafe(report.searchPath());

=== runtime-role policy all callers ===
src/adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/platform/PostgreSqlSecurityContractTest.java:89:        new DatabaseRolePolicy(
src/adapter/outbound/persistence-jpa/src/postgresqlIntegrationTest/java/dev/caskeleton/adapter/outbound/persistence/platform/PostgreSqlSecurityContractTest.java:92:    new PostgreSqlRuntimeRoleVerifier().requireSafe(runtime, policy);
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:15:      new DatabaseRolePolicy(
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:21:    policy.requireSafe(new DatabasePrivilegeReport("app_runtime", "app, pg_catalog", false, false));
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:29:                policy.requireSafe(new DatabasePrivilegeReport("app_runtime", "app", true, false)))
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:38:            () -> policy.requireSafe(new DatabasePrivilegeReport("postgres", "app", false, false)))
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:48:                policy.requireSafe(
src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicyTest.java:57:    policy.requireSafe(new DatabasePrivilegeReport("app_runtime", "\"$user\", app", false, false));
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/PostgreSqlRuntimeRoleVerifier.java:57:    policy.requireSafe(verify(dataSource));
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicy.java:19:public record DatabaseRolePolicy(Set<String> allowedRoles, SearchPathPolicy searchPathPolicy) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/security/DatabaseRolePolicy.java:55:    searchPathPolicy.requireSafe(report.searchPath());

=== runtime role advertised Stable + report semantics ===
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformAutoConfiguration.java:61:        CapabilitySupport.stable(JpaCapability.RUNTIME_ROLE_VERIFICATION),
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformAutoConfiguration.java:112:      return roleVerifier.verify(dataSource);
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformReport.java:25:    boolean runtimeRoleVerified,
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformReport.java:56:        privileges != null && !privileges.holdsCreatePrivilege(),
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformReport.java:62:    return openInViewDisabled && runtimeRoleVerified;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaPlatformRuntimeAutoConfiguration.java:194:  public InitializingBean jpaPlatformStartupCheck(

=== startup contract docs ===
## Startup verification

`PostgreSqlRuntimeRoleVerifier` asks the *server* what the connection can do:

```sql
select current_user,
       current_setting('search_path'),
       has_schema_privilege(current_user, current_schema(), 'CREATE'),
       has_database_privilege(current_user, current_database(), 'CREATE')
```

Configuration cannot answer this. Effective privileges come from direct grants, inherited role
memberships, `PUBLIC` grants, and schema ownership, and no reading of a deployment manifest
reconstructs that combination reliably.

Startup fails when the runtime role is not on the allowlist, or holds `CREATE` on the schema or the
database.

## search_path

`SearchPathPolicy` is an allowlist. `search_path` decides which schema an unqualified name resolves
to, so a writable untrusted schema on it — classically `public`, where `CREATE` was granted broadly
before PostgreSQL 15 — lets a planted table, function, or operator shadow the real one, and the
application executes it without noticing. `$user` is exempt: only the connected role owns it.

Refusing the runtime role `CREATE` closes the same route from the other side.

### 41.2 search_path

Application role의 `search_path`를 고정하고 untrusted schema의 object resolution을 차단한다. startup verifier가 current_user, current_schema, search_path, schema CREATE privilege를 검사한다.

### 41.3 Injection 방어

```text
JPQL/Native values → parameter binding
Dynamic sort → allowlist
Dynamic table/column → enum/catalog mapping만
Entity → API mass binding 금지
```

### 41.4 Secret

DB password는 secret manager/workload identity에서 주입하고 config·log·metric에 기록하지 않는다.

---

## 42. Spring Boot AutoConfiguration

### 42.1 Properties

```yaml
backend:
  jpa:
    enabled: true
    require-postgresql: true
    open-in-view: false
    schema-management: VALIDATE
    transaction-profiles: {}
    retry-profiles: {}
    observability:
      hibernate-statistics: true
      sql-parameters: false
    security:
      verify-runtime-role: true
      verify-search-path: true
```

### 42.2 Startup Failures

```text
spring.jpa.open-in-view=true
prod ddl-auto != validate/none
unsupported PostgreSQL version
runtime role has DDL privilege
migration checksum mismatch
required transaction profile timeout missing
Experimental module enabled without feature flag
```

=== outbox composition path ===
src/app-bootstrap/src/main/resources/application.yml:662:    # metrics, or store binding at all; relay-enabled below only decides whether the scheduler runs.
src/app-bootstrap/src/main/resources/application.yml:666:    relay-enabled: ${APP_OUTBOX_RELAY_ENABLED:false}
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java:5:import dev.caskeleton.application.outbox.OutboxStorePort;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java:30:  private final OutboxStorePort store;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java:35:  public OutboxMetrics(OutboxStorePort store, ObjectProvider<MeterRegistry> meterRegistryProvider) {
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java:10: * <p>{@code ca-skeleton.outbox.relay-enabled} defaults to true and {@code app.messaging.broker}
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java:23:  private static final String RELAY_KEY = "ca-skeleton.outbox.relay-enabled";
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java:5:import dev.caskeleton.application.outbox.PublishPendingOutboxEventsUseCase;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java:19:  private final PublishPendingOutboxEventsUseCase relayUseCase;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java:24:      PublishPendingOutboxEventsUseCase relayUseCase, OutboxMetrics metrics, Clock clock) {
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:6:import dev.caskeleton.application.outbox.OutboxStorePort;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:7:import dev.caskeleton.application.outbox.PublishPendingOutboxEventsUseCase;
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:24: * <p>{@code ca-skeleton.outbox.enabled} is the capability switch; {@code relay-enabled} only
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:52:      name = "ca-skeleton.outbox.relay-enabled",
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:56:      OutboxStorePort store,
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:64:    PublishPendingOutboxEventsUseCase relayUseCase =
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:65:        new PublishPendingOutboxEventsUseCase(
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java:95:      OutboxStorePort store, ObjectProvider<MeterRegistry> meterRegistryProvider) {
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyValidator.java:50:    boolean relay = isOn(environment, "ca-skeleton.outbox.relay-enabled");
src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyValidator.java:67:          "ca-skeleton.outbox.relay-enabled=true only starts the scheduler for a capability that is "
src/application-core/src/main/java/dev/caskeleton/application/inbox/InboxStorePort.java:22:  InboxTransitionOutcome markDead(InboxOwner owner, Duration retention, OperationId operationId);
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/DefaultCleanupService.java:87:        markFailed(item, "BATCH_BYTE_BUDGET_EXHAUSTED", now);
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/DefaultCleanupService.java:123:      markFailed(item, "CLEANUP_ATTEMPT_FAILED", now);
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/DefaultCleanupService.java:140:      markFailed(item, "ACTIVE_WRITER_LEASE", now);
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/DefaultCleanupService.java:206:  private void markFailed(CleanupItem item, String reasonCode, Instant now) {
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/DefaultCleanupService.java:209:          queue.markFailed(item, reasonCode, now.plus(retryBackoff));
src/application-core/src/main/java/dev/caskeleton/application/fileserver/cleanup/CleanupQueue.java:21:  void markFailed(CleanupItem item, String reasonCode, Instant nextAttemptAt);
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxRelayResult.java:7: * Result returned by {@link PublishPendingOutboxEventsUseCase} after one relay cycle: the number of
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:35:public class PublishPendingOutboxEventsUseCase
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:38:  private final OutboxStorePort store;
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:60:  public PublishPendingOutboxEventsUseCase(
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:61:      OutboxStorePort store,
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:90:    List<OutboxEvent> claimed = tx.inWrite(() -> store.claimBatch(batchSize, now, inFlightTimeout));
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:141:    tx.inWrite(() -> store.markPublished(event.eventId()));
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:159:      tx.inWrite(() -> store.markFailed(event.eventId(), nextAttemptAt));
src/application-core/src/main/java/dev/caskeleton/application/outbox/PublishPendingOutboxEventsUseCase.java:175:    tx.inWrite(() -> store.markDead(event.eventId()));
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxStorePort.java:14:public interface OutboxStorePort {
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxStorePort.java:27:  List<OutboxEvent> claimBatch(int batchSize, Instant now, Duration inFlightTimeout);
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxStorePort.java:35:  void markPublished(String eventId);
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxStorePort.java:45:  void markFailed(String eventId, Instant nextAttemptAt);
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxStorePort.java:54:  void markDead(String eventId);
src/application-core/src/main/java/dev/caskeleton/application/outbox/OutboxEvent.java:7: * Immutable view of an outbox event row returned by {@link OutboxStorePort#claimBatch}.
src/application-core/src/main/java/dev/caskeleton/application/outbox/v2/OutboxPollingDeliveryPortV2.java:9:  List<ClaimedOutboxDelivery> claimBatch(OutboxDeliveryClaimRequest request);
src/application-core/src/main/java/dev/caskeleton/application/outbox/v2/OutboxPollingDeliveryPortV2.java:11:  OutboxDeliveryTransitionOutcome markPublished(OutboxDeliveryTransition transition);
src/application-core/src/main/java/dev/caskeleton/application/outbox/v2/OutboxPollingDeliveryPortV2.java:16:  OutboxDeliveryTransitionOutcome markDead(OutboxDeliveryTransition transition, String errorCode);
src/application-core/src/main/java/dev/caskeleton/application/idempotency/v2/IdempotencyStorePortV2.java:28:  IdempotencyFailOutcome markFailed(
src/application-core/src/main/java/dev/caskeleton/application/idempotency/v2/IdempotencyExecutorV2.java:217:        store.markFailed(
src/application-core/src/main/java/dev/caskeleton/application/idempotency/v2/IdempotencyExecutorV2.java:282:    store.markFailed(
src/application-core/src/main/java/dev/caskeleton/application/notification/platform/callback/ProviderEventLedger.java:26:  void markFailed(ProviderEventRecordId eventId, String errorCode);
src/application-core/src/main/java/dev/caskeleton/application/notification/platform/callback/ProviderEventProjectionService.java:58:      ledger.markFailed(event.id(), "NO_PROJECTOR");
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/fileserver/JpaCleanupQueue.java:145:  public void markFailed(CleanupItem item, String reasonCode, Instant nextAttemptAt) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:8:import dev.caskeleton.application.outbox.OutboxStorePort;
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:18: * JPA-backed {@link OutboxAppendPort} + {@link OutboxStorePort}. {@link #append} and the claim/mark
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:24:public class OutboxStoreAdapter implements OutboxAppendPort, OutboxStorePort {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:29:  public OutboxStoreAdapter(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:53:  public List<OutboxEvent> claimBatch(int batchSize, Instant now, Duration inFlightTimeout) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:72:  public void markPublished(String eventId) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:84:  public void markFailed(String eventId, Instant nextAttemptAt) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/outbox/OutboxStoreAdapter.java:99:  public void markDead(String eventId) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/inbox/PostgreSqlSameStoreInboxAdapter.java:350:  public InboxTransitionOutcome markDead(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/outbox/PostgreSqlPollingDeliveryAdapter.java:297:  public List<ClaimedOutboxDelivery> claimBatch(OutboxDeliveryClaimRequest request) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/outbox/PostgreSqlPollingDeliveryAdapter.java:313:  public OutboxDeliveryTransitionOutcome markPublished(OutboxDeliveryTransition transition) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/outbox/PostgreSqlPollingDeliveryAdapter.java:366:  public OutboxDeliveryTransitionOutcome markDead(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/idempotency/PostgreSqlOwnerSafeIdempotencyStore.java:294:  public IdempotencyFailOutcome markFailed(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/platform/RecipientDeliveryJpaRepository.java:36:  List<Object[]> claimBatch(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/platform/JpaRecipientLeaseStore.java:47:    return recipients.claimBatch(now, limit, workerId, leaseUntil).stream()
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/platform/JpaProviderEventLedger.java:146:  public void markFailed(ProviderEventRecordId eventId, String errorCode) {

=== durable/live adapter construction references ===
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/liveevent/JpaLiveEventReplayAdapter.java:54:  public JpaLiveEventReplayAdapter(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/operation/DurableOperationStoreAdapter.java:39:  public DurableOperationStoreAdapter(DurableOperationJpaRepository repository) {
