Skip to content

Verify ContentProvider registration ordering before building the native event bus - #293

Open
ScottMorris wants to merge 3 commits into
mainfrom
feat/255-p0-contentprovider-spike
Open

Verify ContentProvider registration ordering before building the native event bus#293
ScottMorris wants to merge 3 commits into
mainfrom
feat/255-p0-contentprovider-spike

Conversation

@ScottMorris

Copy link
Copy Markdown
Contributor

What this is

Phase 0 of #255's implementation plan: a throwaway spike, not meant to be merged, that de-risks the one assumption the rest of the design depends on — that a manifest-declared ContentProvider's onCreate() reliably fires before AlarmReceiver.onReceive() on a cold process start, so a plugin's native listeners can be guaranteed registered before an alarm fires even when the Tauri/WebView runtime hasn't booted yet. There's zero <provider> precedent in this codebase, so this needed direct verification rather than assuming the documented Android platform guarantee holds here too.

What's here

  • BusInitProvider (debug-only — lives under src/debug/, excluded entirely from release builds) logs its onCreate() timestamp via the existing NativeEventLog mechanism, and asserts the single-process invariant the whole design rests on.
  • A matching spike-only log line in AlarmReceiver.onReceive() for direct ordering comparison.
  • A debug-only, capped (≤2000ms) artificial stall path to sanity-check the threading contract later phases depend on (listeners must do cheap synchronous work only and hand blocking work to their own executor).
  • docs/spikes/255-contentprovider-spike-protocol.md — the full device test protocol below.

Testing checklist (needs your physical phone — nothing here blocks on it)

Run through docs/spikes/255-contentprovider-spike-protocol.md whenever you have a chance:

  1. Build + install debug, kill via adb shell am kill (not am force-stop — that suppresses broadcast delivery and would give a false negative), schedule a real alarm, let it fire → confirm the provider's onCreate timestamp precedes AlarmReceiver.onReceive's in the exported NativeEventLog.
  2. Repeat for the boot-completed path and the notification-tap-while-backgrounded path.
  3. Run the stall test (-PbusSpikeStallMs=1000, do not exceed 2000ms) and confirm no StrictMode violations and no measurable delay to AlarmRingingService's audio start.
  4. Re-confirm Watch doesn't ring when the phone alarm fires cold and the screen is on/in use elsewhere #254's BAL findings still hold on current OS builds (see the BAL comment in AlarmRingingService.kt).

Report back go/no-go — a no-go triggers a follow-up fix to Stage 3's registration mechanism (fallback: registration in the plugin's load() + an Application-attach hook) rather than blocking anything that's already in flight.

Review status

Reviewed via /code-review high — 5 findings, all fixed and re-verified (debug-only gating confirmed via a real assembleRelease check showing no provider in the release AAR/manifest; stall capped; redundant logcat signal added so a swallowed NativeEventLog write during Direct Boot can't produce a false negative; SecurityException now caught in the reflection fallback; blank Gradle property handled).

Part of #255.

ScottMorris and others added 2 commits August 13, 2026 14:19
Adds a throwaway BusInitProvider to wear-sync's manifest to test, ahead of
Phase 1 of the shared native event bus design, whether a manifest-declared
ContentProvider's onCreate() reliably runs before AlarmReceiver.onReceive()
on a cold process start. Verifies the single-process invariant the design
depends on (API 28+ Application.getProcessName(), reflection fallback down
to minSdk 26), logs through the existing NativeEventLog mechanism so its
timestamp can be compared against a matching spike-only log line added to
AlarmReceiver.onReceive(), and includes a debug-only, build-time-gated
onCreate() stall for a human tester to sanity-check the threading contract.
Includes a device test protocol under docs/spikes/. No production logic
changed beyond the one instrumentation line in AlarmReceiver.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WVm6JTTpiSY8KSKJ3p5Qq
- Gate BusInitProvider to debug builds only via a src/debug/ source-set
  split (both the Kotlin class and its manifest <provider> entry), rather
  than a runtime BuildConfig.DEBUG check alone -- confirmed the release AAR
  and release merged manifest contain no trace of it.
- Clamp the spike-only onCreate() stall to 2000ms in code (coerceAtMost),
  since AlarmReceiver.onReceive() calls startForegroundService() in the same
  cold-start chain and a longer stall risks ForegroundServiceDidNotStartInTimeException
  instead of producing useful evidence; documented the cap in the test protocol.
- Add an independent Log.i in onCreate() so there's a second signal that
  survives even if NativeEventLog's file write silently fails (relevant for
  Test 2's BOOT_COMPLETED/Direct Boot scenario).
- Catch SecurityException alongside ReflectiveOperationException in the
  ActivityThread reflection fallback, so OEM/enterprise reflection hardening
  degrades to "can't verify" instead of crashing onCreate().
- Treat a blank -PbusSpikeStallMs the same as absent (defaults to 0).

Re-verified testDebugUnitTest/assembleDebug/lint for wear-sync, assembleRelease
for wear-sync (to prove exclusion), and testDebugUnitTest for alarm-manager,
all BUILD SUCCESSFUL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013WVm6JTTpiSY8KSKJ3p5Qq
@ScottMorris ScottMorris added android Android toolchain and mobile CI concerns plugin Plugin work architecture System design test Test coverage or test fixes labels Aug 13, 2026
@ScottMorris
ScottMorris marked this pull request as ready for review August 14, 2026 04:43
@ScottMorris

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 47af4bc342

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

5. Once the alarm is ringing, pull the merged log (or the two individual `Threshold-wear-sync.log` / `Threshold-alarm-manager.log` files) and find:
- `[BusInitProvider] BusInitProvider.onCreate() fired`
- `[AlarmReceiver] #255 Phase 0 spike: onReceive() fired`
6. **Pass condition:** the `BusInitProvider` timestamp is strictly earlier than the `AlarmReceiver` timestamp. Also check for a `verifySingleProcessInvariant` failure or an `ERROR: single-process invariant violated` line — either indicates the process-model assumption itself doesn't hold on this device/OS build, which is a separate and more serious finding than ordering.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use finer-grained ordering evidence

Because both NativeEventLog implementations record wall-clock timestamps only to millisecond precision, a correctly ordered provider callback and receiver callback can receive identical timestamps on a fast device. This pass condition then reports a false no-go despite the provider having run first; record a monotonic higher-resolution value or a shared sequence number instead of requiring the formatted timestamps to be strictly different.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, but not fixed — this is a throwaway spike branch that will never be merged (its purpose is solely to gather device-test evidence for the ContentProvider ordering assumption before Stage 3 builds on it; see the plan referenced in the PR description). Not investing further engineering here given that. Leaving open for visibility in case you want it addressed anyway.

Comment on lines +85 to +87
val singleProcess = processName == context.packageName
if (BuildConfig.DEBUG) {
check(singleProcess) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Inspect every component before asserting a single process

Comparing this provider's current process with the package name only proves that the provider itself is in the default process. If any receiver, service, activity, or other plugin provider declares android:process, this check still passes while the planned in-memory bus cannot reach that component, so the documented future-regression guard gives a false assurance; inspect the merged manifest's component process assignments or explicitly exercise each participating component.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, but not fixed — same reasoning as the other findings on this PR: throwaway spike, never merged. Leaving open for visibility.

Actually fixes the AGENTS.md Markdown convention violation flagged on
PR #293 -- an earlier reply on this thread incorrectly claimed
BusInitProvider.kt was already compliant without checking; it wasn't.
Also covers wrapped comments in the same file that weren't the
specific line flagged, plus AlarmReceiver.kt's spike instrumentation
comment, build.gradle.kts's stall-property comment, and the debug
manifest's XML comment. Comment-only, no behavioural change; verified
both debug and release variants still build.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

android Android toolchain and mobile CI concerns architecture System design plugin Plugin work test Test coverage or test fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant