Consume octi-web's published wire-format fixtures#318
Merged
Conversation
Introduces a v2 multi-source fixture-lock.json at repo root pinning d4rken-org/octi-web at a known commit + manifest sha256. Per-module consumer tests under modules-{meta,clipboard,files}/src/test parse each octi-web payloadJson through the production decoder and assert field-by-field against octi-web's canonical inputs.
Shared sync infrastructure lives in app-common-test/src/main/java/testhelpers/interop/ — InteropFixtureSync (HttpURLConnection-based fetch+verify with size caps, idempotent across test classes), SyncRefResolver (SOURCE_PATHS allowlist + INTEROP_FIXTURE_OVERRIDES env merge with strict validation), and the wire schemas.
Pairs with octi-web#26 (the producer).
This was referenced May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
No user-facing behavior change. Internal: app-main now consumes octi-web's published wire-format fixtures and asserts every payload decodes to the canonical shape octi-web emits. If a future octi-web change drops a required field, retypes a value, or renames an enum entry, app-main's CI catches it here — same gate the existing Tink fixture verify provides for the encryption layer, applied to the per-module JSON payloads.
This is the first consumer half of Phase B in the cross-repo wire-format gate. The matching upstream-gating workflow on octi-web (which would run app-main + octi-desktop tests against an octi-web PR's HEAD via
INTEROP_FIXTURE_OVERRIDES) lands in a follow-up once octi-desktop is also consuming these same fixtures.Technical Context
fixture-lock.jsonat repo root uses aschemaVersion: 2+sources: { "<owner>/<repo>": { ref, manifest_sha256 } }schema. App-main is starting fresh on this — it had no prior v1 lockfile to migrate. octi-web and octi-desktop already ship single-source v1 lockfiles; they migrate to v2 in their own follow-ups (B3/B4) so the cross-repo trust shape doesn't have to converge in one atomic step.app-common-test/src/main/java/testhelpers/interop/. modules-meta, modules-clipboard, modules-files all depend on sync-core — consumer tests can't live in sync-core because they need to import the per-module*Infomodel classes. The shared helpers (InteropFixturesschemas,SyncRefResolvervalidation,InteropFixtureSyncfetcher) sit one layer above so each module'ssrc/testcan call into them through the existingtestImplementation(project(":app-common-test"))wiring.HttpURLConnection, notjava.net.http.HttpClient. This object lives insrc/mainof an Android library module;java.net.httpis API 33+ on Android even though tests run on the JVM.HttpURLConnectionskips the NewApi gate cleanly. Streaming read with a hard byte cap (64 KiB manifest, 256 KiB per fixture file, 32 files max per manifest) so a malformed or hostile upstream can't OOM the test JVM.INTEROP_FIXTURE_OVERRIDESenv var is declared as a Gradle task input. Otherwise an overridden run could be UP-TO-DATE skipped against a prior cached result. Same reasoning asfixture-lock.jsonitself being an input.{"d4rken-org/octi-desktop": "<sha>"}before app-main's lock actually contains that source gets rejected at parse time — silent fallback would let the gate report green against a lock that doesn't actually verify the implied wire.sha256+byteLengthon every PublishedVector are re-verified at read time. octi-web's B1 self-check pins them at generate time; this consumer side double-checks against the actualpayloadJsonbytes so a hand-edited fixture (with manifest untouched) fails here, not silently as a green decode.Files.size()pre-checks to mirror the on-fetch size caps. A locally-tampered cache file can't bypass them on the second run.Review guidance
tools/generate-fixtures.ts— adding a new field there without updating the matching mirror here makes the test fail with a useful expected/actual diff.app-common-test/.../SyncRefResolver.kt; cross-reference with the same-named files atocti-web/src/__interop__/sync-ref-resolver.tsandocti-desktop/.../SyncRefResolver.kt. SOURCE_PATHS is the per-repo cross-repo trust boundary — adding a fourth source requires a code change in each repo (deliberate friction).Test plan
./gradlew :sync-core:testDebugUnitTest :modules-meta:testDebugUnitTest :modules-clipboard:testDebugUnitTest :modules-files:testDebugUnitTestgreen on a cold cache (fetched fixtures from octi-web@84b9e57)SyncRefResolverTestcovering validateLock, parseOverrides, resolveAll (incl. the new "override targets a source not present in the lock" check)WebMetaInteropTest, 4 inWebClipboardInteropTest, 7 inWebFilesInteropTest— every field of every vector assertedSister PRs (the trio of producer↔consumer pairs):