fix(observability): ADR-0015 follow-up — null-span + bridge test + recordEventOnly (#332) #7

Merged
hibryda merged 2 commits from feat/identity-service-error-event-followup-332 into main 2026-05-31 02:31:22 +02:00
Owner

Header

identity-service · PR follow-up to the merged ADR-0015 error-event wiring · R1 reviewer findings · #332 OTEL sweep · branch feat/identity-service-error-event-followup-332 off main.

TL;DR

CONDITIONAL_APPROVE candidate — addresses all three R1 MINOR findings on the DORMANT durable-error-event code; full suite GREEN (152 tests), 0 warnings. recordEventOnly wired + tested (it is an error path).

Summary

Scope: three R1 follow-up fixes on observability/OtelErrorEvents.java (null-span trace context, resetForTesting lock scope, recordEventOnly durable wiring) plus two new durable-stream tests and one new bridge-lifecycle test. The ADR-0015 wiring itself is already merged to main; this PR only hardens the dormant (im2be.error-event.enabled=false by default) path. Reviewed commit 05b8a96. The error-event-publisher JPA exclusion in pom.xml is load-bearing for the Redis-only service and was left untouched.

Findings

# Severity Location Title Resolution
1 MINOR OtelErrorEvents.java:buildErrorEvent null-span fell back to Span.current(), risking cross-link of the durable event to an unrelated ambient span trace/span ids now sourced ONLY from the explicitly-passed span; no Span.current() fallback (consistent with the span-event path which no-ops when span == null). Javadoc updated.
2 MINOR OtelErrorEvents.java:resetForTesting publisher clear was OUTSIDE the synchronized block while the counter clear was inside moved errorEventPublisher = null under the same lock as the counter clear
3 MINOR OtelErrorEvents.java:recordEventOnly error-recording path left UNwired by the ADR-0015 PR, breaking D-4 wired (see decision below) + added a wired-publisher test

recordEventOnly decision: WIRED + TESTED

Determination: it IS an error-recording path. recordEventOnly sets span.setStatus(StatusCode.ERROR, errorCode) and emits the same typed error.event span attributes (error.type, error.code, error.recoverable, component) as the other record* families — it is the W3 plane-integration record_event_only mirror for typed errors that have no Throwable in hand (e.g. a validation rejection returning 400 without throwing).

Per ADR-0015 D-4 ("one call site → durable event"), an error-recording path must also thread the durable emit, or the invariant holds for every record* family except this one. Resolution mirrors the other families: the 6-arg form became a delegating overload defaulting to RECOVERY_ACTION_UNSPECIFIED (D-7: action decided at the catch site, never inferred), and a new 7-arg RecoveryAction overload calls emitDurableErrorEvent with error_class = the caller-supplied type string (no Throwable → no FQN available) and message = the stable error code (Rule 01 — no raw exception text). recordEventOnly currently has no production callers (dormant), but wiring it now keeps the D-4 invariant uniform so the first real caller inherits durable emission for free.

Verdict

CONDITIONAL_APPROVE — all three R1 findings resolved; recordEventOnly wired (not excluded) because it is an error-recording path. No production behaviour change while the flag is off. Conditions carried forward: none.

identity-service • R1-followup • 152 tests / 0 failures / 0 warnings • commit 05b8a96 • 2026-05-31

## Header **identity-service** · PR follow-up to the merged ADR-0015 error-event wiring · R1 reviewer findings · `#332` OTEL sweep · branch `feat/identity-service-error-event-followup-332` off `main`. ## TL;DR `CONDITIONAL_APPROVE` candidate — addresses all three R1 MINOR findings on the DORMANT durable-error-event code; full suite GREEN (152 tests), 0 warnings. `recordEventOnly` **wired + tested** (it is an error path). ## Summary Scope: three R1 follow-up fixes on `observability/OtelErrorEvents.java` (null-span trace context, `resetForTesting` lock scope, `recordEventOnly` durable wiring) plus two new durable-stream tests and one new bridge-lifecycle test. The ADR-0015 wiring itself is already merged to `main`; this PR only hardens the dormant (`im2be.error-event.enabled=false` by default) path. Reviewed commit `05b8a96`. The `error-event-publisher` JPA exclusion in `pom.xml` is load-bearing for the Redis-only service and was left untouched. ## Findings | # | Severity | Location | Title | Resolution | |---|----------|----------|-------|------------| | 1 | MINOR | `OtelErrorEvents.java:buildErrorEvent` | null-span fell back to `Span.current()`, risking cross-link of the durable event to an unrelated ambient span | trace/span ids now sourced ONLY from the explicitly-passed span; no `Span.current()` fallback (consistent with the span-event path which no-ops when `span == null`). Javadoc updated. | | 2 | MINOR | `OtelErrorEvents.java:resetForTesting` | publisher clear was OUTSIDE the `synchronized` block while the counter clear was inside | moved `errorEventPublisher = null` under the same lock as the counter clear | | 3 | MINOR | `OtelErrorEvents.java:recordEventOnly` | error-recording path left UNwired by the ADR-0015 PR, breaking D-4 | wired (see decision below) + added a wired-publisher test | ## recordEventOnly decision: WIRED + TESTED **Determination: it IS an error-recording path.** `recordEventOnly` sets `span.setStatus(StatusCode.ERROR, errorCode)` and emits the same typed `error.event` span attributes (`error.type`, `error.code`, `error.recoverable`, `component`) as the other `record*` families — it is the W3 plane-integration `record_event_only` mirror for typed errors that have no `Throwable` in hand (e.g. a validation rejection returning 400 without throwing). Per ADR-0015 D-4 ("one call site → durable event"), an error-recording path must also thread the durable emit, or the invariant holds for every `record*` family except this one. Resolution mirrors the other families: the 6-arg form became a delegating overload defaulting to `RECOVERY_ACTION_UNSPECIFIED` (D-7: action decided at the catch site, never inferred), and a new 7-arg `RecoveryAction` overload calls `emitDurableErrorEvent` with `error_class` = the caller-supplied type string (no `Throwable` → no FQN available) and `message` = the stable error code (Rule 01 — no raw exception text). `recordEventOnly` currently has no production callers (dormant), but wiring it now keeps the D-4 invariant uniform so the first real caller inherits durable emission for free. ## Verdict `CONDITIONAL_APPROVE` — all three R1 findings resolved; `recordEventOnly` wired (not excluded) because it is an error-recording path. No production behaviour change while the flag is off. Conditions carried forward: none. ## Footer identity-service • R1-followup • 152 tests / 0 failures / 0 warnings • commit `05b8a96` • 2026-05-31
R1 verdict findings (kept=3):

(1) MINOR observability/OtelErrorEvents.java:695 — null-span trace-context
    fallback could cross-link the durable ErrorEvent to an unrelated ambient
    span. buildErrorEvent() sourced trace/span ids from
    (span != null ? span : Span.current()), so a null-span catch site emitted
    a durable event stamped with whatever ambient span happened to be in scope.
(2) MINOR observability/OtelErrorEvents.java:132 — resetForTesting() cleared
    errorEventPublisher OUTSIDE the synchronized block while the counter clear
    was inside it; the publisher clear belongs under the same lock as the
    counter clear (consistent publication of both test-reset writes).
(3) MINOR observability/OtelErrorEvents.java:580 — recordEventOnly was an
    error-recording path (sets StatusCode.ERROR + emits the typed error.event
    attributes) but the ADR-0015 wiring PR left it UNwired, breaking D-4
    ("one call site -> durable event") for the no-Throwable path.

Fix:
(1) buildErrorEvent() now sources trace/span context ONLY from the explicitly
    passed span (no Span.current() fallback) — consistent with the span-event
    path which already no-ops when span == null. Javadoc updated.
(2) Moved errorEventPublisher = null INTO the synchronized block in
    resetForTesting().
(3) recordEventOnly: kept the 6-arg form as a delegating overload defaulting to
    RECOVERY_ACTION_UNSPECIFIED, added a 7-arg RecoveryAction overload (matching
    the record / recordPropagated / recordSanitizedEvent families) that threads
    emitDurableErrorEvent through with error_class = the supplied type string
    (no Throwable in hand) and message = the stable error code (Rule 01, no raw
    exception text). The JPA-exclusion on error-event-publisher left untouched
    (load-bearing for the Redis-only service).

Tests:
- OtelErrorEventsDurableStreamTest: +wiredPublisherNullSpanLeavesTraceAndSpanEmpty
  (wired publisher + null span -> captured ErrorEvent has empty traceId AND
  spanId), +recordEventOnlyWiredEmitsTypeAndCode (recordEventOnly durable emit:
  error_class=type string, message=errorCode, action + trace ids mapped). 10 tests.
- NEW OtelErrorEventsBridgeTest in the bridge's own package (destroy() is
  package-private): in-tree installSdk() harness (replicated from
  ConnectProxyServiceImplOtelTest), mock ErrorEventPublisher, new bridge ->
  record() verify(pub).publish(any()) -> bridge.destroy() -> record() again ->
  verifyNoMoreInteractions(pub). 1 test.

Verification:
- export JAVA_HOME=temurin-17; mvn -B compile test-compile -> 0 [WARNING]
- mvn -B test -> 152 tests, 0 failures, 0 errors (GREEN)

Superseded by round 2.

Show previous round

hib-pr-reviewer review — PR #7 (affinity-intelligence-rework/im2be-identity-service)

Round 1 — head 05b8a9674e99, base main, trigger opened

TL;DR: CONDITIONAL_APPROVE — kept 1 unique-to-A info finding (verified: dead sdk static field in bridge test), Reviewer B found nothing; no blocking or major issues.

Summary

Arbitration — Round 1

No prior run history in Memora (first arbitration for this PR). Memora persistence attempted but blocked by tag-allowlist restriction; summary embedded here instead.

Agreement baseline: Reviewer B found 0 issues; Reviewer A found 1 (info). The single unique-to-A finding required verification.

Verification performed: Read OtelErrorEventsBridgeTest.java in full. Confirmed:

  • Line 41: private static OpenTelemetry sdk; is a static field.
  • Line 52-55: assigned inside installSdk() (sdk = OpenTelemetrySdk.builder()…build(); GlobalOpenTelemetry.set(sdk);).
  • sdk is never referenced again after that assignment — not in shutdownSdk(), not in any test method.
  • shutdownSdk() (lines 59-62) calls GlobalOpenTelemetry.resetForTest() directly and never invokes sdk.close() or ((OpenTelemetrySdk) sdk).getSdkTracerProvider().close().

Finding confirmed as stated. Kept at original info severity.

All three production changes verified clean by both reviewers independently: resetForTesting() sync fix, buildErrorEvent ambient-span removal, and recordEventOnly 7-arg overload are all correct; no disagreement to resolve there.

Outcome: Kept 1 verified unique-to-A info finding. 0 dropped. No blocking or major issues.

Blast Radius

The diff touches one static observability utility (OtelErrorEvents.java) and two test files. OtelErrorEvents is a service-wide static helper but the changes are additive (new 7-arg overload) and corrective (sync and ambient-span fixes), not a protocol or API contract change. Blast radius is bounded to the observability layer.

BLAST_SCORE: 3/10

Risk Indicators

Indicator Value
Sensitive functions buildErrorEvent, resetForTesting, recordEventOnly, emitDurableErrorEvent
Migration touched
Test delta
Dependency changes

CI status (head 05b8a9674e99)

No CI checks reported for this commit.

Findings (1)

[INFO] Dead sdk static field: assigned in installSdk() but never read; SDK never explicitly closed

src/test/java/com/aim2be/identity/observability/OtelErrorEventsBridgeTest.java:41

Line 41 declares private static OpenTelemetry sdk;. It is written once in installSdk() (line 52-55: sdk = OpenTelemetrySdk.builder()…build(); GlobalOpenTelemetry.set(sdk);) and never read again — GlobalOpenTelemetry.set(sdk) retains the live reference internally. shutdownSdk() (lines 59-62) calls GlobalOpenTelemetry.resetForTest() directly and never invokes sdk.close(), so the field serves no purpose after the assignment and may attract a static-analysis warning.

Concrete options:

  • Simplest (preferred): Drop the field and keep sdk as a local variable inside installSdk(). shutdownSdk() already works without it.
  • If explicit teardown matters later: Retain the field but add ((OpenTelemetrySdk) sdk).getSdkTracerProvider().close() in shutdownSdk() before GlobalOpenTelemetry.resetForTest(). For the current in-memory tracer with no exporters this is effectively a no-op, but it documents intent and prevents resource-leak warnings from tools like SpotBugs.

Note: the same pattern exists in the pre-existing OtelErrorEventsDurableStreamTest (out of scope for this diff); both classes would benefit from a follow-on tech-debt pass.

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 1 • 1 finding (1i) • 2026-05-31T00:10:26.582Z → 2026-05-31T00:11:37.644Z • posted-as: pr-reviewer-bot • model: auto

<!-- hib-pr-reviewer collapsed --> > _Superseded by round 2._ <details> <summary>Show previous round</summary> <!-- hib-pr-reviewer round:1 --> ## hib-pr-reviewer review — PR #7 (affinity-intelligence-rework/im2be-identity-service) **Round 1** — head `05b8a9674e99`, base `main`, trigger `opened` **TL;DR:** CONDITIONAL_APPROVE — kept 1 unique-to-A info finding (verified: dead `sdk` static field in bridge test), Reviewer B found nothing; no blocking or major issues. ### Summary ## Arbitration — Round 1 No prior run history in Memora (first arbitration for this PR). Memora persistence attempted but blocked by tag-allowlist restriction; summary embedded here instead. **Agreement baseline:** Reviewer B found 0 issues; Reviewer A found 1 (info). The single unique-to-A finding required verification. **Verification performed:** Read `OtelErrorEventsBridgeTest.java` in full. Confirmed: - Line 41: `private static OpenTelemetry sdk;` is a static field. - Line 52-55: assigned inside `installSdk()` (`sdk = OpenTelemetrySdk.builder()…build(); GlobalOpenTelemetry.set(sdk);`). - `sdk` is never referenced again after that assignment — not in `shutdownSdk()`, not in any test method. - `shutdownSdk()` (lines 59-62) calls `GlobalOpenTelemetry.resetForTest()` directly and never invokes `sdk.close()` or `((OpenTelemetrySdk) sdk).getSdkTracerProvider().close()`. Finding confirmed as stated. Kept at original `info` severity. **All three production changes verified clean by both reviewers independently:** `resetForTesting()` sync fix, `buildErrorEvent` ambient-span removal, and `recordEventOnly` 7-arg overload are all correct; no disagreement to resolve there. **Outcome:** Kept 1 verified unique-to-A info finding. 0 dropped. No blocking or major issues. ### Blast Radius The diff touches one static observability utility (`OtelErrorEvents.java`) and two test files. `OtelErrorEvents` is a service-wide static helper but the changes are additive (new 7-arg overload) and corrective (sync and ambient-span fixes), not a protocol or API contract change. Blast radius is bounded to the observability layer. **BLAST_SCORE: 3/10** ### Risk Indicators | Indicator | Value | |---|---| | Sensitive functions | `buildErrorEvent`, `resetForTesting`, `recordEventOnly`, `emitDurableErrorEvent` | | Migration touched | — | | Test delta | — | | Dependency changes | — | ### CI status (head `05b8a9674e99`) _No CI checks reported for this commit._ ### Findings (1) #### **[INFO]** Dead `sdk` static field: assigned in `installSdk()` but never read; SDK never explicitly closed _src/test/java/com/aim2be/identity/observability/OtelErrorEventsBridgeTest.java:41_ Line 41 declares `private static OpenTelemetry sdk;`. It is written once in `installSdk()` (line 52-55: `sdk = OpenTelemetrySdk.builder()…build(); GlobalOpenTelemetry.set(sdk);`) and never read again — `GlobalOpenTelemetry.set(sdk)` retains the live reference internally. `shutdownSdk()` (lines 59-62) calls `GlobalOpenTelemetry.resetForTest()` directly and never invokes `sdk.close()`, so the field serves no purpose after the assignment and may attract a static-analysis warning. **Concrete options:** - **Simplest (preferred):** Drop the field and keep `sdk` as a local variable inside `installSdk()`. `shutdownSdk()` already works without it. - **If explicit teardown matters later:** Retain the field but add `((OpenTelemetrySdk) sdk).getSdkTracerProvider().close()` in `shutdownSdk()` *before* `GlobalOpenTelemetry.resetForTest()`. For the current in-memory tracer with no exporters this is effectively a no-op, but it documents intent and prevents resource-leak warnings from tools like SpotBugs. Note: the same pattern exists in the pre-existing `OtelErrorEventsDurableStreamTest` (out of scope for this diff); both classes would benefit from a follow-on tech-debt pass. ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 1 • 1 finding (1i) • 2026-05-31T00:10:26.582Z → 2026-05-31T00:11:37.644Z • posted-as: pr-reviewer-bot • model: auto</sub> </details>
(1) Bridge test sits in …observability (package-private destroy() access) but
surefire's <includes> was **/unit/**/*Test.java only → it compiled but never ran
(false coverage). Add an explicit include; verified it now executes (Tests run: 1).
(2) R1 info: the static `sdk` field was only used inside installSdk() → made it
a local. 0 warnings, BUILD SUCCESS.

hib-pr-reviewer review — PR #7 (affinity-intelligence-rework/im2be-identity-service)

Round 2 — head a9360c5df83f, base main, trigger synchronize

TL;DR: CONDITIONAL_APPROVE — kept 1 unique-to-A minor finding (verified); B's 0-finding result does not contradict; no blocking issues.

Summary

Arbitration summary

Recalled Memora: no prior runs for this PR found (step 2 returned empty). Created new run-history memory (ID 440) after reconciliation.

Unique-to-A (1 finding) — verified with Read. Line 49 of OtelErrorEventsBridgeTest.java is confirmed as:

SdkTracerProvider tracerProvider = SdkTracerProvider.builder().build();

tracerProvider is a method-local variable in installSdk(). shutdownSdk() (lines 56–60) only calls GlobalOpenTelemetry.resetForTest() and OtelErrorEvents.resetForTesting() — no reference to tracerProvider, so it cannot call close() or shutdown(). Finding is kept as minor.

Reviewer B (0 findings) — B's explanation that the R1 dead-static-field concern was resolved is consistent with HEAD (line 50 shows sdk as a local variable passed immediately to GlobalOpenTelemetry.set()). No contradiction with A's finding — A targets tracerProvider, not sdk.

Kept 1 agreed finding (unique-to-A, verified). No findings dropped.

Blast Radius

The diff touches one observability utility class (OtelErrorEvents.java) and its test harness. The production change (null-span fix + recordEventOnly overload + resetForTesting race fix) is self-contained within the observability package. The test-only finding is in a new test file and does not affect production paths.

BLAST_SCORE: 3/10

Risk Indicators

Indicator Value
Sensitive functions resetForTesting, buildErrorEvent, emitDurableErrorEvent, recordEventOnly
Migration touched
Test delta
Dependency changes

CI status (head a9360c5df83f)

No CI checks reported for this commit.

Findings (1)

[MINOR] SdkTracerProvider is a local variable — unreachable in @AfterAll, cannot be explicitly closed

src/test/java/com/aim2be/identity/observability/OtelErrorEventsBridgeTest.java:49

tracerProvider is created as a method-local variable inside installSdk() and goes out of scope when the method returns. shutdownSdk() (lines 56–60) calls GlobalOpenTelemetry.resetForTest() and OtelErrorEvents.resetForTesting() but has no reference to call tracerProvider.close() / tracerProvider.shutdown(). Today this is low-risk because no span exporter is registered (so no background flush threads are started), but the pattern silently breaks if an exporter is ever added to the builder, leaving SdkTracerProvider's internal resources without a clean lifecycle signal.

Fix: promote to a static field so shutdownSdk() can close it explicitly:

private static SdkTracerProvider tracerProvider;

@BeforeAll
static void installSdk() {
    OtelErrorEvents.resetForTesting();
    GlobalOpenTelemetry.resetForTest();
    tracerProvider = SdkTracerProvider.builder().build();
    final OpenTelemetry sdk = OpenTelemetrySdk.builder()
            .setTracerProvider(tracerProvider).build();
    GlobalOpenTelemetry.set(sdk);
}

@AfterAll
static void shutdownSdk() {
    if (tracerProvider != null) {
        tracerProvider.close();
        tracerProvider = null;
    }
    GlobalOpenTelemetry.resetForTest();
    OtelErrorEvents.resetForTesting();
}

Verdict

CONDITIONAL_APPROVE


hib-pr-reviewer • round 2 • 1 finding (1m) • 2026-05-31T00:29:50.828Z → 2026-05-31T00:31:07.897Z • posted-as: pr-reviewer-bot • model: auto

<!-- hib-pr-reviewer round:2 --> ## hib-pr-reviewer review — PR #7 (affinity-intelligence-rework/im2be-identity-service) **Round 2** — head `a9360c5df83f`, base `main`, trigger `synchronize` **TL;DR:** CONDITIONAL_APPROVE — kept 1 unique-to-A minor finding (verified); B's 0-finding result does not contradict; no blocking issues. ### Summary ## Arbitration summary Recalled Memora: no prior runs for this PR found (step 2 returned empty). Created new run-history memory (ID 440) after reconciliation. **Unique-to-A (1 finding) — verified with `Read`.** Line 49 of `OtelErrorEventsBridgeTest.java` is confirmed as: ```java SdkTracerProvider tracerProvider = SdkTracerProvider.builder().build(); ``` `tracerProvider` is a method-local variable in `installSdk()`. `shutdownSdk()` (lines 56–60) only calls `GlobalOpenTelemetry.resetForTest()` and `OtelErrorEvents.resetForTesting()` — no reference to `tracerProvider`, so it cannot call `close()` or `shutdown()`. Finding is **kept** as minor. **Reviewer B (0 findings)** — B's explanation that the R1 dead-static-field concern was resolved is consistent with HEAD (line 50 shows `sdk` as a local variable passed immediately to `GlobalOpenTelemetry.set()`). No contradiction with A's finding — A targets `tracerProvider`, not `sdk`. Kept 1 agreed finding (unique-to-A, verified). No findings dropped. ### Blast Radius The diff touches one observability utility class (`OtelErrorEvents.java`) and its test harness. The production change (null-span fix + `recordEventOnly` overload + `resetForTesting` race fix) is self-contained within the observability package. The test-only finding is in a new test file and does not affect production paths. **BLAST_SCORE: 3/10** ### Risk Indicators | Indicator | Value | |---|---| | Sensitive functions | `resetForTesting`, `buildErrorEvent`, `emitDurableErrorEvent`, `recordEventOnly` | | Migration touched | — | | Test delta | — | | Dependency changes | — | ### CI status (head `a9360c5df83f`) _No CI checks reported for this commit._ ### Findings (1) #### **[MINOR]** SdkTracerProvider is a local variable — unreachable in @AfterAll, cannot be explicitly closed _src/test/java/com/aim2be/identity/observability/OtelErrorEventsBridgeTest.java:49_ `tracerProvider` is created as a method-local variable inside `installSdk()` and goes out of scope when the method returns. `shutdownSdk()` (lines 56–60) calls `GlobalOpenTelemetry.resetForTest()` and `OtelErrorEvents.resetForTesting()` but has no reference to call `tracerProvider.close()` / `tracerProvider.shutdown()`. Today this is low-risk because no span exporter is registered (so no background flush threads are started), but the pattern silently breaks if an exporter is ever added to the builder, leaving `SdkTracerProvider`'s internal resources without a clean lifecycle signal. **Fix:** promote to a static field so `shutdownSdk()` can close it explicitly: ```java private static SdkTracerProvider tracerProvider; @BeforeAll static void installSdk() { OtelErrorEvents.resetForTesting(); GlobalOpenTelemetry.resetForTest(); tracerProvider = SdkTracerProvider.builder().build(); final OpenTelemetry sdk = OpenTelemetrySdk.builder() .setTracerProvider(tracerProvider).build(); GlobalOpenTelemetry.set(sdk); } @AfterAll static void shutdownSdk() { if (tracerProvider != null) { tracerProvider.close(); tracerProvider = null; } GlobalOpenTelemetry.resetForTest(); OtelErrorEvents.resetForTesting(); } ``` ### Verdict **CONDITIONAL_APPROVE** --- <sub>hib-pr-reviewer • round 2 • 1 finding (1m) • 2026-05-31T00:29:50.828Z → 2026-05-31T00:31:07.897Z • posted-as: pr-reviewer-bot • model: auto</sub>
hibryda deleted branch feat/identity-service-error-event-followup-332 2026-05-31 02:31:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
affinity-intelligence-rework/im2be-identity-service!7
No description provided.