From 2516d8120c4c8535382c79654585b948b29daae2 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Tue, 25 Aug 2026 10:25:15 +0500 Subject: [PATCH 1/4] test(diagnostics): define background work failure contract RED (#191) --- .../diagnostics/BackgroundWorkFailureTest.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 core/common/src/test/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailureTest.kt diff --git a/core/common/src/test/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailureTest.kt b/core/common/src/test/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailureTest.kt new file mode 100644 index 000000000..22f3955db --- /dev/null +++ b/core/common/src/test/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailureTest.kt @@ -0,0 +1,68 @@ +package app.muxtv.common.diagnostics + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class BackgroundWorkFailureTest { + @Test + fun `failure kinds match stable WorkManager callbacks`() { + assertThat(BackgroundWorkFailureKind.entries) + .containsExactly( + BackgroundWorkFailureKind.INITIALIZATION, + BackgroundWorkFailureKind.SCHEDULING, + BackgroundWorkFailureKind.WORKER_INITIALIZATION, + BackgroundWorkFailureKind.WORKER_EXECUTION, + ) + .inOrder() + } + + @Test + fun `worker category is a closed secret-safe vocabulary`() { + assertThat(BackgroundWorkerCategory.entries) + .containsExactly( + BackgroundWorkerCategory.SOURCE_REFRESH, + BackgroundWorkerCategory.EPG_REFRESH, + BackgroundWorkerCategory.UNKNOWN, + ) + .inOrder() + } + + @Test + fun `observation exposes no arbitrary string or throwable payload`() { + val fieldTypes = BackgroundWorkFailureObservation::class.java.declaredFields.map { it.type } + + assertThat(fieldTypes).doesNotContain(String::class.java) + assertThat(fieldTypes).doesNotContain(Throwable::class.java) + } + + @Test + fun `observation equality is deterministic`() { + val first = + BackgroundWorkFailureObservation( + kind = BackgroundWorkFailureKind.WORKER_EXECUTION, + timestampEpochMillis = 1234L, + workerCategory = BackgroundWorkerCategory.SOURCE_REFRESH, + ) + val second = first.copy() + + assertThat(second).isEqualTo(first) + assertThat(second.hashCode()).isEqualTo(first.hashCode()) + } + + @Test + fun `observation rejects negative wall clock timestamp`() { + var thrown: Throwable? = null + + try { + BackgroundWorkFailureObservation( + kind = BackgroundWorkFailureKind.INITIALIZATION, + timestampEpochMillis = -1L, + workerCategory = BackgroundWorkerCategory.UNKNOWN, + ) + } catch (failure: Throwable) { + thrown = failure + } + + assertThat(thrown).isInstanceOf(IllegalArgumentException::class.java) + } +} From 05b9fe47a87eb3f0a6199f5683feeb5cb3d84b81 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Tue, 25 Aug 2026 10:26:15 +0500 Subject: [PATCH 2/4] ci(diagnostics): execute O1 pure-contract RED on hosted runner --- .github/workflows/o1-red-hosted.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/o1-red-hosted.yml diff --git a/.github/workflows/o1-red-hosted.yml b/.github/workflows/o1-red-hosted.yml new file mode 100644 index 000000000..deeb77a3b --- /dev/null +++ b/.github/workflows/o1-red-hosted.yml @@ -0,0 +1,28 @@ +name: O1 WorkManager RED hosted + +on: + push: + branches: + - feat/workmanager-failure-observation-191 + +permissions: + contents: read + +concurrency: + group: o1-red-hosted-${{ github.ref }} + cancel-in-progress: true + +jobs: + core-common-red: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 + with: + distribution: temurin + java-version: '17' + - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 + - name: Observe O1 pure-contract RED + shell: bash + run: ./gradlew :core:common:test --tests "*BackgroundWorkFailureTest" --no-daemon --console=plain --stacktrace From 76df0b7814a7c21aa2787ba769c84663a9b05d5a Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Tue, 25 Aug 2026 10:26:45 +0500 Subject: [PATCH 3/4] ci(diagnostics): remove temporary hosted RED gate --- .github/workflows/o1-red-hosted.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/o1-red-hosted.yml diff --git a/.github/workflows/o1-red-hosted.yml b/.github/workflows/o1-red-hosted.yml deleted file mode 100644 index deeb77a3b..000000000 --- a/.github/workflows/o1-red-hosted.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: O1 WorkManager RED hosted - -on: - push: - branches: - - feat/workmanager-failure-observation-191 - -permissions: - contents: read - -concurrency: - group: o1-red-hosted-${{ github.ref }} - cancel-in-progress: true - -jobs: - core-common-red: - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 - - uses: actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9 - with: - distribution: temurin - java-version: '17' - - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 - - name: Observe O1 pure-contract RED - shell: bash - run: ./gradlew :core:common:test --tests "*BackgroundWorkFailureTest" --no-daemon --console=plain --stacktrace From 24b33931ce4ee09044e5ae467748de696d95a263 Mon Sep 17 00:00:00 2001 From: MrFr3di Date: Wed, 26 Aug 2026 07:22:30 +0500 Subject: [PATCH 4/4] feat(diagnostics): add typed background work failure contract --- .../diagnostics/BackgroundWorkFailure.kt | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 core/common/src/main/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailure.kt diff --git a/core/common/src/main/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailure.kt b/core/common/src/main/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailure.kt new file mode 100644 index 000000000..fdbb192c8 --- /dev/null +++ b/core/common/src/main/kotlin/app/muxtv/common/diagnostics/BackgroundWorkFailure.kt @@ -0,0 +1,24 @@ +package app.muxtv.common.diagnostics + +internal enum class BackgroundWorkFailureKind { + INITIALIZATION, + SCHEDULING, + WORKER_INITIALIZATION, + WORKER_EXECUTION, +} + +internal enum class BackgroundWorkerCategory { + SOURCE_REFRESH, + EPG_REFRESH, + UNKNOWN, +} + +internal data class BackgroundWorkFailureObservation( + val kind: BackgroundWorkFailureKind, + val timestampEpochMillis: Long, + val workerCategory: BackgroundWorkerCategory, +) { + init { + require(timestampEpochMillis >= 0L) { "Timestamp must be non-negative" } + } +}