← Foundation · All seats

draftAndroid path not deployed08.08.2026proposed by Claude & @rinat-enikeevsource on GitHub →

Onym Moderation ↔ Device Mark: Google Play Integrity Device Recall Implementation

Implementation profile draft 0.2 — August 2026

This profile maps the abstract device-mark rail onto Play Integrity's device recall feature: three per-device values scoped to a Google Play developer account, written server-side and read inside integrity verdicts, surviving app reinstallation and device reset for a bounded retention term.

This document is a concrete implementation of Moderation.md §5.7 (device marks and enforcement binding) for Android. The abstract contract remains authoritative for mandates, reports, cases, verdicts, obligations, and invariants. This document defines only the Google platform mapping and records where it falls short of the abstract rail.

The sibling DeviceCheck profile is the iOS counterpart. The two profiles deliberately share consent, verdict validation, gate-state, reconciliation, and enforcement-backend requirements where the platforms permit it.

The document distinguishes:

  • platform behavior, which Google Play Integrity provides;
  • profile requirements, which a conforming interface vendor's app and enforcement backend must implement; and
  • gaps, where the platform or current code cannot yet meet the abstract contract and a declared mitigation applies.

The concrete Authority-side v1 wire and implemented lifecycle follow merged onym-moderation main (through PR #10). No current Onym Android code implements this profile. The existing Android application has no moderation domain, authenticated manifest review, Play Integrity token client, enforcement backend transport, gate, or device recall integration. Every implementation statement below is therefore a requirement, not a claim about deployed behavior; §9 records that boundary.

1. Conformance declaration

Abstract concept Device recall mapping
Enforcement profile onym:moderation-enforcement-profile:google-device-recall-v1, version 1
Gate notice schema onym-moderation-google-device-recall-case-notice-v1
Device-mark platform Google Play Integrity API, device recall (beta)
Mark scope (per interface vendor) Three values are per device per Google Play developer account. Every app in that account can access the same values; they are not isolated by package name or linked Cloud project
case-open mark bitFirst
banned mark bitSecond
Reserved bitThird, left unchanged by this profile
Mark write path Vendor backend → v1/{packageName}/deviceRecall:write, authenticated through the app's linked Google Cloud project and targeting the device represented by a verified integrity token
Mark read path deviceRecall.values and writeDates inside a decoded integrity verdict after device recall is enabled for the app
deviceBinding (mandate/verdict field) An opaque vendor-local enrollment identifier; not an integrity token or a platform device identifier
Device attestation of the request A standard or classic integrity token generated by the app and decoded and validated by the backend; standard requests bind the gate request through requestHash
Mark persistence Google documents recall across app reinstall and device reset; values are retained for three years after the last read or write, so this is durable leased state, not permanent storage

The table instantiates this signed enforcement-profile object:

{
  "profileVersion": 1,
  "profileId": "onym:moderation-enforcement-profile:google-device-recall-v1",
  "platform": "google-device-recall",
  "bindings": {
    "enroll-device": {"requestSchema": "onym-moderation-google-device-recall-enrollment-request-v1", "resultSchema": "onym-moderation-google-device-recall-device-enrollment-v1"},
    "countersign-mandate": {"requestSchema": "onym-moderation-mandate-v1", "resultSchema": "onym-moderation-google-device-recall-interface-signature-v1"},
    "register-mandate": {"requestSchema": "onym-moderation-mandate-v1", "resultSchema": "onym-moderation-mandate-receipt-v1"},
    "gate-check": {"requestSchema": "onym-moderation-google-device-recall-gate-request-v1", "resultSchema": "onym-moderation-google-device-recall-gate-result-v1"},
    "deliver-verdict": {"requestSchema": "onym-moderation-verdict-submission-v1", "resultSchema": "onym-moderation-verdict-receipt-v1"}
  },
  "caseNoticeSchema": "onym-moderation-google-device-recall-case-notice-v1",
  "markBindings": {"case-open": "bitFirst", "banned": "bitSecond"},
  "specification": "<content-address-of-this-profile-specification>"
}

Sections 4–6 define the registered schema and mark meanings. The Interface directory authenticates this object as Moderation.md §6.4 requires, and the signed mandate pins its ID/version pair. Reissuing that pair with different bindings is nonconforming. bitThird remains outside markBindings and this profile never writes it.

The account-wide scope is broader than one app. To preserve the abstract contract's exclusive interface-vendor write boundary, a conforming deployment must either dedicate its Play developer account to interfaces that share this exact bit contract or prove that no other app or backend in the account can write these values. A package-scoped Cloud project alone does not provide that separation. An app transfer moves the app to the new developer account's value namespace; it does not carry the old account's device marks with it.

2. Ownership mapping

Component Owner Holds
Android interface app Interface vendor Integrity token requests, request-hash construction, gate and ban-state UX
Enforcement backend Interface vendor Linked Google Cloud service-account credentials, token decoding and validation, verdict validation, reconciliation, and the mark write path
Google Play Integrity Google Recall-value storage, integrity verdict production, and token validation; judges nothing
Moderation authority Independent operator Verdict signing keys; no Google credentials and no write path

The vendor's Play developer account and linked Cloud credentials physically route writes through the interface vendor. The profile turns that constraint into the abstract separation: the authority signs, the vendor validates and executes, and Google stores. Because sibling apps in one developer account can reach the same values, the vendor must enforce the account-level isolation rule in §1 rather than claiming package-level isolation it does not have.

3. Physical topology

┌──────────────────────┐  token + requestHash          ┌──────────────────────┐
│ Android interface app│──────────────────────────────>│ Enforcement backend  │
│ case / ban / appeal  │<──────────────────────────────│ interface vendor     │
└──────────────────────┘  gate result + state          └──────┬───────▲───────┘
                                               decode / write │       │ signed
                                                              v       │ Verdict
                                        ┌──────────────────────────┐  │
                                        │ Google Play Integrity    │  │
                                        │ verdicts · device recall │  │
                                        └──────────────────────────┘  │
                                                      ┌───────────────┴──────┐
                                                      │ Moderation authority │
                                                      │ (no Google access)   │
                                                      └──────────────────────┘

4. Value mapping

Value Abstract mark Set when Cleared when
bitFirst case-open Backend validates an interim open-case verdict against this device's enrollment Dismissal, superseding ban, decision-deadline default, or designation revocation
bitSecond banned Backend validates a ban verdict whose executeAfter has arrived; a non-suspensive ban may execute before final becomes true banExpires, reversal/new-holder remedy, or designation revocation where no forum survives
bitThird

This profile neither sets nor clears bitThird; every write omits it, which preserves its account-wide value.

Profile requirements:

  1. values change only inside verdict execution and declared deadline/expiry/revocation reconciliation; no administrative, support, sibling-app, or store-pressure path may reach deviceRecall:write;
  2. every write is logged against the authorizing verdict hash or declared clearing rule, and the log is auditable by the audit seat;
  3. writes specify only values this transition changes. In particular, bitThird is omitted rather than reset, because unspecified values remain unchanged and all apps in the developer account share it;
  4. writeDates are month-and-year consistency signals, never authorization, execution, or expiry sources. Setting a value true updates its corresponding yyyymm* field; setting it false removes that field. Real timing lives in the verdict and backend log; and
  5. the backend accounts for Google's documented write-to-read propagation delay of up to 30 seconds and never treats an immediately stale read as permission to undo or bypass a just-executed verdict.

5.1 Reviewed terms and enrollment

Consent follows the abstract one-artifact transaction. A conforming Android repository:

  1. fetches the authority listing, exact manifest bytes, and detached signature once for human review;
  2. verifies the directory-pinned operator key, signature over the exact bytes, component/operator bindings, consent-time conditions, and lowercase SHA-256 hash inside that repository boundary;
  3. returns an opaque reviewed artifact whose constructor is unavailable to application callers, so decoded fields cannot be paired with different raw bytes; and
  4. after agreement, accepts only that same artifact, re-runs time-sensitive validity checks, and does not refetch. The signed mandate's classes and hash and the retained display bytes all derive from exactly what the user saw.

Enrollment then proceeds:

  1. the app prepares a standard integrity token provider and constructs an enrollment request containing the user key, UTC timestamp, and a fresh backend-issued challenge;
  2. it hashes a stable serialization of every security-relevant enrollment field into Play Integrity's requestHash, obtains a token, and sends the fields, token, and identity signature to the backend;
  3. the backend decodes the token through Google, verifies the package, certificate, licensing and device verdicts required by the deployment, timestamp/freshness, identity signature, and exact requestHash binding, and reads the current recall values;
  4. after resolving the gate outcome, the backend returns an opaque vendor-local deviceBinding only when enrollment may proceed;
  5. the client builds and signs the mandate from its retained reviewed artifact and that binding, including this enforcement-profile ID/version;
  6. the backend returns only its detached countersignature. It cannot replace mandate fields or ask the client to persist a rebuilt mandate; and
  7. the interface registers the exact finalized two-signature mandate with the named authority as Moderation.md §6.2 requires. Registration is idempotent by mandateRef; failure leaves authority enrollment incomplete. The signed consent artifact remains immutable, but the gate fails closed and retries only transient failure. If the Authority's published manifest changed, the app presents the new authenticated artifact for review and obtains fresh consent/signatures rather than retrying forever or substituting terms.

Steps 1–4 bind enroll-device, steps 5–6 bind countersign-mandate, and step 7 binds register-mandate; the next section binds gate-check. Signed interim and terminal verdicts enter the backend through deliver-verdict, and the gate derives the notice or ban state returned to the app. Enrollment, countersigning, and gating belong to the independently versioned interface-enforcement profile; registration and delivery are cross-owner authority-profile facets.

The cross-language canonical serialization, signature domains, precise integrity-verdict policy, and freshness window must be frozen in published fixtures before implementation. They are not inherited accidentally from the current Swift implementation. At minimum, enrollment and gate requests use different signature domains and bind every transmitted security-relevant field.

5.2 Gate checks

At launch, on foreground, on explicit retry, and at least once per the declared interval while the app runs:

  1. the app obtains an execution slot from its throttled gate scheduler, reuses a prepared standard token provider when valid, constructs a GateCheckRequest containing the user key, active mandateRef, timestamp, and fresh backend challenge, and binds the stable request serialization through requestHash;
  2. the backend decodes and validates the token and request as in enrollment, reads deviceRecall.values, resolves the enrollment where possible, and reconciles platform state with its verdict records before responding;
  3. bitSecond set → it returns banned even if the enrollment cannot yet be resolved. A resolved enrollment supplies the verdict reference, authority contact, expiry, ordinary appeal, and new-holder path. An unresolved one still refuses operation and displays the designated authority plus its new-holder/re-identification procedure; successful re-identification then supplies the governing verdict or clears the value. A silent checkRequired brick is nonconforming;
  4. bitSecond clear and bitFirst set → it returns caseOpen with notices. The app operates normally and displays the procedural case state;
  5. a present deviceRecall object whose values are false or absent → it returns clear only after the prerequisite verdict tuple below and reconciliation succeed; and
  6. a missing deviceRecall object, failed prerequisite, invalid request, exhausted offline grace, or other untrustworthy unmarked answer → checkRequired, which blocks operation.

The concrete evaluation rule is deliberately stricter than testing whether values is empty. Before interpreting any recall value, the backend requires:

  1. matching and fresh requestDetails, including package name and requestHash;
  2. appRecognitionVerdict == PLAY_RECOGNIZED, with the expected package and signing-certificate digest;
  3. appLicensingVerdict == LICENSED;
  4. deviceRecognitionVerdict containing MEETS_DEVICE_INTEGRITY; and
  5. the deviceIntegrity.deviceRecall object to be present.

Failure of any condition is checkRequired, regardless of the contents of values. If all five pass, a present deviceRecall object with empty maps is the profile's clean never-written state; absent bit fields are false. Google does not expose a separate "device recall evaluated" boolean and also documents an unavailable recall value as an object with empty values and writeDates. A technically unavailable result that nevertheless satisfies all five classifier conditions — including the present but empty object — is therefore indistinguishable from a clean device. The profile cannot honestly claim to fail closed across that final ambiguity; §8 records the limitation and required monitoring.

On an otherwise clean device, absence of an active mandate is a consent-gate condition, not clear. A set mark takes precedence and follows the caseOpen or banned path above even when mandate storage is missing. Losing local mandate storage therefore leads to a fresh mark check before re-consent, never into an unmoderated app.

The backend associates the enrollment with the mandate's deviceBinding at first validated token. Integrity tokens are request artifacts, not stable identifiers. The linkage model is:

  • Reading is platform-state-first. The recall values are the sole durable device state the refusal consults after reinstall: a set bitSecond refuses service even when the backend cannot initially resolve an enrollment.
  • Writing and reconciliation are session-mediated. The identity signature and token presented together in one session are the only link from the current device request to a vendor-local enrollment. The backend never tries to derive an identifier from the token.
  • Unresolvable marked state routes to re-identification. A marked device whose session identity resolves no active verdict is not treated as clean. It receives the manifest's new-holder/re-identification procedure; the outcome is a reversal that clears values or a confirmation that restores linkage.

Gate checks run at a declared interval (default P1D). An unreachable backend may serve the last successfully reconciled state for a declared offline grace window (default P3D), then degrades to checkRequired. The client persists the result and success time, rejects clock rollback, prevents an older concurrent result from replacing a newer one, and explicitly checks on foreground rather than relying only on a background timer. Backend identity refusal remains in force independently of the device gate.

Launch, foreground, retry, and interval triggers feed one scheduler; they do not each force a provider preparation or token request. The scheduler coalesces concurrent triggers and holds one provider. It prepares on cold start, after INTEGRITY_TOKEN_PROVIDER_INVALID, and after a recall write when the next eligible read must observe refreshed values, while enforcing Google's limit of at most five preparations per app instance per minute. Token requests are likewise coalesced, exponentially backed off on TOO_MANY_REQUESTS, and metered against a declared budget below the deployment's daily quota, with usage alerts enabled. A throttled trigger serves the last reconciled result only within offline grace and schedules the next eligible attempt; without history or after grace it returns checkRequired. Foreground or retry thrash must never create unbounded Play calls or discard a still-valid known state.

For standard requests, device recall is refreshed during provider preparation. After a write, the app must prepare again before relying on a subsequent token to observe the new value. The backend carries the just-executed state across Google's propagation window rather than accepting a cached pre-write value as authoritative.

6. Verdict execution and reconciliation

On receiving a verdict from the designated authority, the backend:

  1. validates the authority signature against the key in the mandate's exact consented manifest; manifest hash, authority, mandate reference, accused keys, deviceBinding, and class bindings; mandatory reasoning; disposition and marks; appealDeadline; executeAfter against the consented appeal effect; and banExpires against the consented ban term. A valid ban before executeAfter is stored, not executed;
  2. resolves deviceBinding to an enrollment and selects an already decoded, verified token from the target device's authenticated session. Google permits a verified token to authorize a write for up to 14 days, but this profile uses the current execution/reconciliation session where possible and never treats token age alone as identity-to-device linkage;
  3. if there is no usable target-device session, queues the mark write until the next session presenting a valid token with an identity named by the mandate. Backend identity refusal applies immediately, so the queue does not restore access under the banned key;
  4. calls deviceRecall:write with only the values this transition changes and records the response against the authorizing object; and
  5. schedules and lazily reconciles clearing actions authorized by expiry, dismissal/reversal, decision-deadline default, or the abstract revocation forum rule.

The case-open write normally executes during notice service, which already requires a connected accused session. Clearing and delayed writes require a usable token from the target device. When the device returns, the backend reconciles before answering its gate. A resolvable enrollment applies expiry, dismissal/reversal, decision defaults, and revocation directly; an unresolvable marked holder enters the re-identification path. A stale value on a device that never returns is inert because no conforming gate acts on it without reconciliation.

Google retains values for three years after the last read or write. Ordinary gate reads refresh that lease for active devices. If a marked device stays away longer than the retention term, its values may disappear; a returning mandated identity can be re-marked from backend verdict state, but a new identity on expired platform state cannot be recognized as the old device. This limitation is disclosed in §8 and means the profile cannot promise unconditional device enforcement after more than three years of platform inactivity.

7. Error mapping

Abstract error Device recall condition Backend behavior
mark_write_failed Write rejected, token unusable, API failure, or rate limit Retry in the next valid target-device session; verdict and identity refusal remain in force
verdict_invalid Pre-platform validation failure Refuse execution; never call Google
Clean platform state deviceRecall present with values absent or false, and the full request/app/licensing/device prerequisite tuple passes Reconcile against the resolved enrollment before returning clear
Token/request invalid Decode failure, replay-cleared verdict, bad package/certificate/verdict, stale request, identity signature failure, or requestHash mismatch Reject and re-request; repeated failure becomes checkRequired
Recall prerequisites fail deviceRecall missing, app not PLAY_RECOGNIZED, account not LICENSED, or device missing MEETS_DEVICE_INTEGRITY checkRequired; never treat as clean
Recall object present but empty All prerequisite verdicts pass and both maps are empty Treat as never-written clean state, subject to Google's documented evaluation ambiguity in §8
No supported Play environment Play Store/services absent or outdated, unsupported device, emulator checkRequired; never unmoderated operation
Token/provider throttled TOO_MANY_REQUESTS, daily token quota, or provider-prepare limit Coalesce and back off; serve the last reconciled result within grace, otherwise checkRequired; schedule the next eligible attempt
Propagation/cache lag A post-write token still carries old values Preserve just-executed backend state, re-prepare provider, and retry after the propagation window
new_holder_claim Holder asserts transfer at ban UX or marked state cannot resolve Route to the manifest's procedure and execute the resulting reversal or confirmation normally

8. Known gaps

  1. Beta availability. Device recall remains a Play Integrity beta that requires approval and Play Console opt-in. Quotas, availability, and terms can change; every deployment must re-verify them before claiming conformance.
  2. The values are developer-account-wide. All apps in one Play developer account see the same three values. Package endpoints and Cloud credentials do not create separate bit namespaces. A dedicated account or demonstrably common bit contract is required; otherwise a sibling app can corrupt or act on moderation state outside the consenting surface.
  3. Retention is bounded. Values expire after three years without a read or write. Active gate checks refresh the lease, but a device absent beyond the horizon can return with clean platform state. Permanent sanctions therefore retain backend identity refusal but cannot promise a permanent device mark.
  4. The values survive the person. Google documents persistence through reinstall and reset, so resale and refurbishment can carry a sanction to a new holder. The reference Authority's unauthenticated, bounded new-holder claim path offers human review but is not proof of transfer and can be exhausted. Reset and transfer behavior must still be exercised on deployment devices.
  5. The rail excludes valid Android environments. Device recall requires recent enabled Play Store and Play services, a Play-licensed account, and a supported physical device; emulators and de-Googled or otherwise unsupported distributions fail to checkRequired. The interface manifest must disclose this reach limitation.
  6. Google exposes no definitive evaluated/clean discriminator. Both a never-written device and an unavailable recall value can produce a present deviceRecall object with empty values and writeDates. The profile requires the request/app/licensing/device tuple in §5.2 and fails closed when the object or any prerequisite is missing, but a technical recall failure with every outer prerequisite still passing is indistinguishable from clean state. Deployments must monitor empty-result anomalies and disclose this residual fail-open platform ambiguity; no implementation may claim a stronger signal than Google returns.
  7. Three values and coarse dates are not a verdict store. Verdict references, classes, expiries, and appeal state live in the vendor backend. bitThird remains reserved. Losing backend state makes the values uninterpretable and cannot be repaired from month/year write dates.
  8. A queued write can be outrun. A verdict received without a usable target-device token must wait for another session. Wiping the app before that session and never returning under a mandated identity can evade the device write, though backend refusal of the named identity remains. Standard-request caching and up-to-30-second propagation also require explicit handling.
  9. No third-party proof of faithful writes exists. Google attests the device and stores values, not the vendor's obedience. Signed write logs and audit-seat attestation are the declared substitute and remain a paper control until a deployed auditor verifies them.
  10. Google is a silent party. Developer-account or Cloud-project failure, app transfer, quota policy, beta change, or feature deprecation can alter or orphan state outside this contract. The dependency and its failure behavior must be disclosed.
  11. No Onym Android implementation or frozen wire contract exists. The current app contains none of the client or backend components in this profile. Cross-language consent, request, mandate, verdict, and signature fixtures must be published before implementation; passing the iOS package's tests would not by itself establish Android or backend conformance.

9. Current Onym Android implementation

The current onym-android repository contains no moderation or Play Integrity device recall slice:

Contract/profile responsibility Current Android status
Authority directory and manifest authenticity Not implemented
One-snapshot reviewed consent artifact Not implemented
Mandate enrollment and signature-only countersigning Not implemented
Standard integrity token provider and request-hash binding Not implemented
Enforcement backend transport Not implemented
Launch/foreground/interval gate and offline grace Not implemented
Verdict validation and execution timing Not implemented
Recall reads, writes, reconciliation, and write logs Not implemented
Case, ban, ordinary appeal, and new-holder UX Not implemented

This table is descriptive and deliberately narrower than the profile's acceptance criteria. The abstract contract and the implemented iOS domain slice are design inputs, not evidence that Android or Google-backed enforcement exists.

10. Conformance tests

Fixtures must cover:

  • exact-byte manifest authenticity, raw-bytes/decoded-fields mismatch, and a hosted manifest changing between review and agreement without changing the signed mandate artifact;
  • standard token preparation and decoding; package, certificate, licensing, device, timestamp, replay, identity-signature, and requestHash validation;
  • the exact recall classifier: every failed request/app/licensing/device prerequisite, missing deviceRecall, present-but-empty maps, and populated false values, including the documented clean/unavailable ambiguity;
  • verdict-to-value execution for every disposition, including suspensive/non-suspensive timing and refusal of early writes;
  • writes that preserve unspecified bitThird, removal of the corresponding yyyymm* field on clear, separate rate limiting, propagation delay, and mandatory provider refresh;
  • queued writes and their execution only in a valid target-device session;
  • lazy reconciliation for expiry, dismissal/reversal, deadline default, and revocation;
  • re-linking after reinstall and routing unresolved marked devices to re-identification/new-holder review;
  • developer-account sibling-app visibility and app-transfer namespace change;
  • the three-year retention boundary, reset/reinstall behavior, and device transfer; and
  • interval/grace enforcement, clock rollback, stale concurrent completions, coalesced foreground/retry thrash, the five-per-minute provider-prepare cap, daily token quota exhaustion, missing Play services, unlicensed accounts, unsupported devices, and emulators.

11. Acceptance criteria

This profile is successfully implemented when:

  1. an authenticated manifest is reviewed and the exact retained artifact—not a second fetch or caller-built byte/field pair—supplies the signed mandate;
  2. a ban verdict against an enrolled device sets bitSecond only at or after its valid executeAfter, and the app refuses service with complete ban and appeal UX using only the vendor's authorized backend path;
  3. reinstalling or resetting the device restores refusal from recall state within Google's declared retention and platform-support bounds, without a backend account lookup deciding whether a set banned value should gate;
  4. expiry, dismissal/reversal, deadline default, and applicable revocation clear values mechanically in a subsequent valid device session;
  5. no path outside validated verdict execution and declared reconciliation can write bitFirst or bitSecond, no profile path touches bitThird, and the log accounts for every call;
  6. cached reads, propagation lag, rate limits, a missing deviceRecall object, failed prerequisite verdicts, and unsupported Play environments cannot produce clear, while the irreducible present-empty ambiguity is tested, monitored, and disclosed;
  7. the deployment satisfies and tests the developer-account-wide ownership rule instead of claiming false package isolation; and
  8. the shared object and validation fixtures let independent Android, iOS, and backend authors implement the abstract contract without private coordination.

References

  1. Onym moderation contract boundary: Moderation.md
  2. Sibling iOS profile: Moderation-DeviceCheck.md
  3. Google Play Integrity device recall: https://developer.android.com/google/play/integrity/device-recall
  4. Standard Play Integrity requests: https://developer.android.com/google/play/integrity/standard
  5. Play Integrity verdicts: https://developer.android.com/google/play/integrity/verdicts
  6. Play Integrity terms and data safety: https://developer.android.com/google/play/integrity/terms
  7. Onym audit seat (write-log attestation): ../audit/Audit.md
  8. Current Onym Android application: https://github.com/onymchat/onym-android

This document is maintained in the public onym-system repository and rendered here from a pinned commit. The repository is the authoritative source — latest version on main →