Skip to content

feat(push): PR-1b — Rust-owned registration against the push server - #467

Merged
grunch merged 3 commits into
mainfrom
feat/push-1b-reconcile
Sep 13, 2026
Merged

grunch merged 3 commits into
mainfrom
feat/push-1b-reconcile

Conversation

@grunch

@grunch grunch commented Sep 13, 2026

Copy link
Copy Markdown
Member

Summary

Phase 1, task T1.2 of docs/PUSH_NOTIFICATIONS.md (§7.1, §8.2). Stacked on PR-1a (#466), whose pure rules this wires to the trade rows, the settings store and the push server at https://mostro-push-server.fly.dev.

Nothing in Dart calls this yet (PR-1c hands the token over), so the app's behavior is unchanged: without a token nothing is registered and nothing is asked of the server.

  • PushServer trait with the reqwest implementation: 10 s timeout on every platform, lowercase hex, mostro_pubkey on every register, Retry-After on 429, 403 recorded as a refusal of that node. Only the JSON bodies of §3.1 are sent: no auth, no request id, no sender.
  • reconcile_core applies the rules to the persisted state against a server, and is what the tests exercise with a fake. reconcile_push is single-flight with a dirty flag, so a request that arrives mid-pass runs it again rather than in parallel.
  • Triggers, coalesced 2 s: every persist_trade_row, every trade update emission (so wipes and terminal outcomes count), every claim upsert, the restore, a 6 h timer started when the pool comes up, and resync() on resume.
  • Bridge surface: set_push_token, clear_push_token, set_push_enabled (off unregisters everything the persisted map knows; on clears every node refusal), get_push_status, on_push_status_changed. Types PushPlatform { Android, Ios, Web } and PushStatus.
  • delete_identity unregisters first. Selecting a node clears that node's refusal.
  • Issuing node on every row. create_order and the restore placeholder now seed creator_pubkey with the active node; an empty legacy value reads as the active node.
  • DM filter authors now include the issuing node of every live trade row, so a registered key is always one the filter can hear after a node switch.
  • JSON mirror (push_mirror.json next to the database, native only) with the token and the accepted registrations, for the OS-scheduled refresh of T1.5. Removed when there is nothing to refresh.
  • Under cargo test every production path talks to an unreachable stub: the trade-row tests trigger reconciles as a side effect and none may reach the real server.
  • Host default https://mostro-push-server.fly.dev, overridable at build time with PUSH_SERVER_URL.

Test plan

  • 11 new tests in api/push.rs against a scripted fake server: keys registered under their node and recorded; a second pass over an unchanged world makes no request; disabled unregisters everything the state knows; no token means nothing asked; a failure backs off and retries past it; 429 waits out Retry-After; 403 refuses the node, skips its keys, and clears on "try again"; a rotated token re-registers every key and a grace ends in unregister; status classification; the status report; the URL default and override.
  • cargo test (659), cargo clippy --all-targets (no new warnings), cargo check --target wasm32-unknown-unknown, ./scripts/frb-generate.sh (bindings committed), flutter analyze, all in the pre-commit hook.

Manual testing

  1. Check out the branch, run ./scripts/frb-generate.sh, then flutter run -d linux. Trade normally. Expect log lines [push] reconcile: wanted=N registered=0 +0 -0 failed=0 refused=0: no token, so nothing is sent.
  2. From a Dart debug hook, call push_api.setPushToken(token: 'test-token', platform: PushPlatform.android) while holding one open trade. Expect [push] reconcile: … +1 … and a push_mirror.json next to mostro.db naming that trade's pubkey.
  3. Read push_registrations from the settings table and confirm one entry with registered_at, token_hash (64 hex, not the token) and mostro_pubkey equal to the active node.
  4. Call push_api.setPushEnabled(enabled: false). Expect … -1 … and the mirror file gone. Call it with true and expect +1 again.
  5. Stop the network and call push_api.reconcilePush(). Expect failed=1, then a next_attempt_at 60 s ahead in the registration, and no further attempt until then.
  6. Complete or cancel the trade. Expect the registration to gain unwanted_since, and to stay registered for 24 h before an unregister.
  7. Switch to a second node and back. Expect no unregister on the switch, and the DM filter log to list both nodes as authors while the trade is live.

api/push.rs: the PushServer trait with its reqwest implementation (10 s
timeout, lowercase hex, mostro_pubkey, Retry-After, 403 as a per-node
refusal), reconcile_core over the persisted state, reconcile_push as
single-flight with coalesced triggers (trade row writes, status
emissions, claim upserts, restore, a 6 h timer, resync), the bridge
surface (set_push_token, clear_push_token, set_push_enabled,
get_push_status, on_push_status_changed), and the JSON mirror the
OS-scheduled refresh will read. delete_identity unregisters first;
selecting a node clears its refusal. Maker and restored rows now carry
the issuing node in creator_pubkey, and the kind-14 filter's authors
include the issuing node of every live trade row. Host:
https://mostro-push-server.fly.dev, PUSH_SERVER_URL at build time.
Under cargo test every production path talks to an unreachable stub.

docs/PUSH_NOTIFICATIONS.md Phase 1, T1.2.
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • develop

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 56bad01e-275e-43be-af65-f70066a21f80

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-13T20:33:23.045657Z d152ca4 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: d152ca496f

ℹ️ 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".

Comment thread rust/src/api/disputes.rs Outdated
Comment thread rust/src/api/push.rs Outdated
The wanted set no longer consults the in-memory dispute record: a live dispute's row reads Dispute, which is not terminal, and an admin outcome ends both, so a record never marked resolved cannot keep a finished trade's key registered. Clearing a node refusal runs under the reconcile lock and touches the refusals only, never the registration map a reconcile in flight owns.
Base automatically changed from feat/push-1a-rules to main September 13, 2026 21:14
@grunch
grunch merged commit f3358b0 into main Sep 13, 2026
4 checks passed
@grunch
grunch deleted the feat/push-1b-reconcile branch September 13, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant