Skip to content

feat(reporting): share the report reason vocabulary, and sign the target - #1519

Merged
bmc08gt merged 1 commit into
code/cashfrom
feat/reporting-shared-vocabulary
Sep 21, 2026
Merged

bmc08gt merged 1 commit into
code/cashfrom
feat/reporting-shared-vocabulary

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Both apps could already send a report and neither had a way to ask for one. This is the half of that work that is shared: the reason vocabulary, the Swift facade over it, and a signing bug that would have shipped with the first caller.

Design: docs/superpowers/specs/2026-09-21-reporting-design.md in the orchestrator repo (under review as code-payments/flipcash-client-orchestrator#16).

The vocabulary is shared code, not a convention

flipcash.reporting.v1.ReportRequest has no category field. description is a free-form string and the only place a reason can go, so the reason has to be encoded into it. Encoding it separately on each platform is the thing that drifts, and a vocabulary that drifts gives moderation two spellings of the same report.

New :libs:reporting holds the six tokens and the assembly rule — token on line one, the reporter's own words from line two on — and is exported through :kmp:shared-core on the api(...) and export(...) lists. ReportReason.swift re-exports the Kotlin enum under a local name rather than restating it in Swift; a Swift enum with its own six cases would be a second copy of exactly what this module exists to make single.

ReportDescriptionTest writes every expected string out as a literal, so a renamed token or a dropped newline fails the build instead of quietly changing what moderation receives. ReportReasonTests covers the bridge rather than the behaviour: that the wrap does not reorder or drop anything, that the cap crosses as a number, and that displayOrder is exhaustive — a seventh reason added in Kotlin compiles fine on both sides and would otherwise be silently missing from the iOS sheet.

One note on the facade for whoever writes the iOS UI: Kotlin/Native lowercases the whole enum case, so the Swift spellings are .scamorfraud and .sexualcontent, not the camel-case you would expect from the Kotlin.

The signature covered the wrong bytes

ReportingApi set its auth block before the target:

.setDescription(description)
.apply { setAuth(authenticate(owner)) }
when (target) { /* builder.setUserId(...) etc. */ }

authenticate() signs this.buildPartial() — a snapshot of the builder at the moment it is called — so every report Android sends is signed over its description and nothing else. iOS builds target-first and signs both.

That makes the rule positional for every API in that package, not a quirk of reporting: any field set after authenticate() is outside the signature. ReportingApi is the only current violator, and BlocklistApi gets the ordering right, which is what makes this a slip rather than a convention. The fix moves the auth block below the when, with a comment at the call site saying why the position matters, since nothing in the type system enforces it.

This fix is unproven. GetBlobsRequestSigningTest covers the same bug class for GetBlobs without needing a signing key, but it works because getBlobsRequest takes its authenticator as a parameter; ReportingApi.report calls authenticate(owner) inline and has no such seam. Adding one means extracting a reportRequest(target, description, authenticate:) builder, which is more than the positional fix this change is scoped to. The first real check is the first live report sent from the Android UI.

Why the UI is not here

The Android surfaces are a separate branch. Nothing in them is a dependency of the flipcash-shared-core-spm publish, and bundling them would make iOS wait for a UI review it has no stake in.

Whoever merges this: the next move is yours. iOS is publish-gated — FLIPCASH_SHARED_CORE_LOCAL exists in this repo's spm/Package.swift but not in code-ios-app, where all three manifests carry an unconditional .upToNextMinor(from: "0.7.0"). So flipcash-shared-core-spm needs a 0.8.0 publish and the tag moved before the iOS work can start.

Registrations

A new KMP module is picked up by nothing automatically. Two of the four registrations here fail loudly if forgotten — include(":libs:reporting") and its kmpUnitTestModules membership, without which the aggregate calls a testDebugUnitTest the module does not have. The other two fail silently: libs/reporting/** in the shared-core-tests.yml paths: filter, without which the macOS lane never triggers on a change to the module, and :libs:reporting:iosSimulatorArm64Test in its task list, without which the Kotlin/Native half never runs. Neither omission produces a red check.

`ReportRequest` has no category field -- only a free-form `description` --
so a report's reason has to be encoded into that string. Encoding it twice,
once per platform, gives moderation two spellings of the same report.

New `:libs:reporting` holds the six tokens and the rule that assembles them:
the token on the first line, the reporter's own words from the second on.
It rides the existing `SharedCore` framework, and `ReportReason.swift`
re-exports the Kotlin enum rather than restating it, so the tokens are
written down once for both apps.

`ReportingApi` also set its auth block before the target. `authenticate()`
signs `buildPartial()`, a snapshot of the builder at that moment, so every
report was signed over its description alone. The auth block now goes last,
which is how `BlocklistApi` has always built its requests.
@bmc08gt
bmc08gt requested a review from jeffyanta as a code owner September 21, 2026 19:32
@bmc08gt bmc08gt self-assigned this Sep 21, 2026
@github-actions github-actions Bot added area: network gRPC, connectivity, API, exchange rates area: build-system Gradle, convention plugins, build-logic type: feature New functionality labels Sep 21, 2026
@bmc08gt
bmc08gt merged commit e77da66 into code/cash Sep 21, 2026
4 checks passed
@bmc08gt
bmc08gt deleted the feat/reporting-shared-vocabulary branch September 23, 2026 16:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build-system Gradle, convention plugins, build-logic area: network gRPC, connectivity, API, exchange rates type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant