Skip to content

Push taps route to the current connection, even when the notification came from another saved server #174

Open

Description

@objectstack-fleet

repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

Blocked-by: objectstack-ai/objectstack#29642

Filing category ①. This is a user-visible defect with a named producer. It becomes reachable once saved connections (#152, PR #173) land.

The defect

hooks/usePushRouting.ts takes a notification's route (routeFromPushData) and opens it on the current connection's Console. It never checks which server sent the notification.

With several saved servers, every server registers the same device token. When a user taps a notification from server B while server A is current, B's route opens on A's Console. The page is wrong, and the record may even exist on A.

reach: named producer, hooks/usePushRouting.ts (route taken without a server check). PR #173 documents it as a Known limit and lists it in its Acceptance notes.

Ask

Who acts

The repo:mobile seat dispatches it after #152 lands.

Duplicate check (2026-11-02)

The open and closed mobile cards were read in full (seat enumeration): no card on push routing per connection.

Activity

  1. added
    enhancementNew feature or request
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

    Unblocked: Blocked-by #152 merged (678fbe9).

    Claim: PM loop round 1
    Session: session_01QSeKYnW1Xf6HDSWz1eGAqv
    Account: os-justin
    Branch: claude/issue-174-push-per-connection
    Worktree: mobile-issue-174
    Domain: n/a — single-lane repo (repo:mobile)
    Seat: repo:mobile#1
    File surface: hooks/usePushRouting.ts, lib/push-notifications.ts, app/console.tsx (switch before routing), lib/connections/** (lookup by host only), __tests__/**, docs/HYBRID-SHELL.md, docs/PUSH-NOTIFICATIONS.md, .changeset/174-push-per-connection.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default tier (opus) — dispatch-gates --tier --repo objectstack-ai/mobile: no path-derived mandate, effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds. usePushRouting(serverUrl, …) at hooks/usePushRouting.ts:29 routes on the current connection with no server check.
    Serial constraints cleared: #157 may touch lib/connections/** (ordinary concurrency). #155 may touch app/console.tsx (excursion bar host): the PR that lands second reconciles.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
    os-dev-report

    {
      "issue": 174,
      "status": "blocked",
      "branch": "claude/issue-174-push-per-connection",
      "pr": null,
      "session": "session_01QSeKYnW1Xf6HDSWz1eGAqv",
      "premise_still_valid": false,
      "summary": "Stopped on the card's own stop condition: the platform push payload carries no server identity, so no PR. The mobile defect itself is real (hooks/usePushRouting.ts:29 routes every tap on the current serverUrl), but there is nothing in the payload to route by. Measured on objectstack origin/main aca58cdb: no push channel exists (service-messaging/src has email, inbox, sms and http/webhook channels only; channel.ts:7 names APNs/FCM in prose), and packages/spec/src/api/push-delivery.zod.ts is ABSENT (control leg: packages/spec/src/system/email-config.zod.ts present on the same ref). The only push payload contract is in objectstack-ai/objectstack#29218, an open draft PR (head aff751de, spec slice of objectstack-ai/objectstack#29067 / objectstack-ai/objectstack#29041): push-delivery.zod.ts:90-99 PushDeliveryDataSchema = z.object with exactly two keys, notificationId (line 91, required) and route (line 92, optional, PushRouteSchema lines 72-85 = same-server relative path only). No host, origin, server URL or tenant key; ADR-0012 line 426 gives the push renderer output as title, body, data, sound, badge with no identity either. APNs and FCM tell the app nothing about the sender, and every saved server registers the same device token, so the shell cannot derive the sender locally; per the ruling no route-shape heuristic was attempted. The platform side is a new objectstack card for the seat to file (options in open_questions); the cheapest moment is before objectstack-ai/objectstack#29218 merges. Branch was pushed empty as the probe (push succeeded, no 403) and carries no commits; worktree removed.",
      "tests": "none run: no code written (stop condition). Evidence reads only: git show origin/main and git cat-file -e on objectstack (shallow checkout; absent-file reading paired with a present control file on the same ref), git show aff751de:packages/spec/src/api/push-delivery.zod.ts, REST reads of objectstack PR 29218 and issues 29041, 29067, 29068.",
      "gates": "NOT MEASURED: tsc, eslint, jest, reason: no diff (stop condition, no PR). pnpm install in the worktree exit 0 (it was started backgrounded with an ampersand, a deviation from the foreground rule; it finished before any later step).",
      "dogfood_battery": "n/a: no PR",
      "mcp_calls": "0",
      "api_writes": "1: POST repos/objectstack-ai/mobile/issues/174/comments (this report, via post-stamped.mjs); git push of the empty branch is not a REST write",
      "open_questions": [
        {
          "question": "How should a push name the server that sent it, so the shell can match it against saved connections by host (lib/connections/model.ts normalizes to host[:port])? This is a public contract name in packages/spec, so it is the spec seat's and maintainer's call, filed as an objectstack card by the mobile seat.",
          "options": [
            "A: the push channel adds a server-origin key to PushDeliveryDataSchema from the server's configured public URL. Cost: one optional key in objectstack-ai/objectstack#29218 plus one line in the objectstack-ai/objectstack#29041 channel; a server reachable under several hostnames or behind a rewriting proxy emits an origin the shell did not save, so the tap is dropped (safe, but the feature silently fails for those deployments).",
            "B: the Console sends the origin it was loaded from (window.location.origin, the host the shell saved) in RegisterDeviceRequest, the server stores it on the UserDevice row, and the push channel echoes that row's value into the push data. Cost: one optional member each on RegisterDeviceRequestSchema, UserDeviceSchema and PushDeliveryDataSchema, all in objectstack-ai/objectstack#29218 before merge, plus the Console bridge registration call; matches exactly the host the shell knows, independent of proxy config.",
            "C: no platform change; the shell keeps routing on the current connection and documents the limit. Cost: zero; the card's defect stays."
          ],
          "recommendation": "B, because it echoes the exact host the shell saved for that device row, so matching needs no knowledge of the server's public URL, and all three members are widenings that fit inside objectstack-ai/objectstack#29218 before it merges (one PR instead of a second widening later). Then mobile #174 resumes with a host lookup and the #152 switch path."
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: objectstack-ai/objectstack#29041 · noted, not filed: the planned contract (objectstack-ai/objectstack#29218 push-delivery.zod.ts:35-48) says the shell never opens route verbatim and resolves app-relative values like /opportunity/123 through resolveNotificationTarget, while mobile lib/push-notifications.ts routeFromPushData opens route verbatim after safeConsoleRoute and docs/PUSH-NOTIFICATIONS.md documents a url key the planned contract does not carry. Observation only: the contract is unmerged and no producer exists. Dedupe words: push route resolve, actionUrl app-relative, routeFromPushData verbatim, push payload url key"
      ]
    }
  4. added
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Nov 2, 2026
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv

    Release: claim 6581414167 (session session_01QSeKYnW1Xf6HDSWz1eGAqv, branch claude/issue-174-push-per-connection, which has no commits).

    The premise is falsified, the card's own stop condition. The dev's report is comment 6581547684, re-checked by the seat:

    The platform decision is filed as objectstack-ai/objectstack#29642 (recommendation B: round-trip the Console's loaded origin through device registration).

    State: pm:dispatched → pm:blocked, assignee cleared. Resume from the existing branch once #29642 is ruled and its contract change is installable from a platform release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpm:blockedBlocked by another issue/PR — body carries Blocked-by: #N

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions