Skip to content

notifications: store the push device's origin on sys_user_device and copy it into push data (ruling B on #29642, server half) #29768

Open

Description

@objectstack-fleet

domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

Filing gate: ② a maintainer ruling's server half. Ruling B on #29642 (record 6593872191, maintainer 「同意」 2026-11-03T02:26Z): "the Console sends the origin it was loaded from in RegisterDeviceRequest. The server stores it on the UserDevice row, and the push channel copies it into PushDeliveryDataSchema." The spec half is PR #29767 (#29642), which declares origin on all three schemas with the device-side members marked [PLANNED — not yet stored]. ⛔ Not a claim. Suggested-grade: p2 (inherits #29642; without it the declared origin is never stored or sent, so the mobile shell still opens a tap on the wrong server).

Business reason. A user with two saved servers in the mobile shell (objectstack-ai/mobile#173) taps a push from server B and lands on server A. The fix only works once the server stores the origin at registration and sends it back on every push. Reader: the domain:server seat, after PR #29767 merges.

What to build (measured by the #29642 dev, os-dev-report 6595041258)

  • packages/services/service-messaging/src/objects/user-device.object.ts: sys_user_device gains an origin field beside app_id / locale.
  • packages/services/service-messaging/src/device-registry.ts: the row write stores request.origin ?? null (beside app_id: request.appId ?? null, locale: request.locale ?? null), and the row read sets device.origin when present (beside if (row.locale) device.locale = …).
  • packages/runtime/src/domains/notifications.ts: no edit needed; POST /devices already passes parsed.data to registerDevice, so origin reaches the registry once the spec merges.
  • The push channel (Push channel: build the device registry routes (/notifications/devices) and the APNs + FCM transport that ADR-0012 marks "MUST ship M1" #29041): copy the device row's origin into the push data (PushDeliveryDataSchema.origin). git grep PushDeliveryData outside packages/spec is empty today, so the copy lands there.
  • Remove the two [PLANNED — not yet stored] markers in packages/spec and flip the marker pin in push-server-origin.test.ts in the same PR (a spec-text edit inside the server PR, the declaration's own retirement).

Out of scope

Serial: after PR #29767.

Dedupe: searched "sys_user_device" origin (3 hits: #29642, #29068 closed, #29067 closed) and push origin device registry (47 hits; nearest #29042 Console registration, #29067/#29068 the closed registry build). New.

Links back: #29642, PR #29767, #29042, #29041, objectstack-ai/mobile#174.

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K

    Carried here from the contract review of PR #29767 (record 6595157663, PASS, three should-fix items on packages/spec/src/system/push-server-origin.ts), since this card edits that module when the [PLANNED …] markers come off:

    1. Labels follow DNS-hostname grammar: http://my_svc:3000 and a trailing-dot FQDN are refused although window.location.origin can answer them. Allow _ in a label, or name the limit in the docblock.
    2. The port arm accepts non-canonical ports (:443 on https, :80 on http, leading zeros, out of range) and the IPv6 arm accepts [:::]; tighten, or note that the Console is the one producer.
    3. The docblock should say an IPv4 host is accepted too.

    Also for the Console producer (#29042): send origin only when window.location.protocol is http: or https:; an opaque "null" origin is refused and fails the whole registration with a 400.

  2. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:blocked · 2026-11-03T04:52Z

    ⛔ Not a claim.

    Blocked-by: #29767


    Generated by Claude Code

  3. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    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 3, 2026
  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

    Claim: PM loop round 1 · 2026-11-05T21:51Z
    Session: session_016WV4cytQjG9X1CrCgytUM5
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-29768-push-device-origin
    Worktree: objectstack-issue-29768
    Domain: domain:server
    Seat: domain:server#3
    File surface: packages/services/service-messaging/src/objects/user-device.object.ts (sys_user_device.origin beside app_id / locale), packages/services/service-messaging/src/device-registry.ts (row write and read), packages/services/service-messaging/src/push-channel.ts (copy the row's origin into the push data), tests beside each and any service-messaging pin/catalog the new field moves (regenerated with repo tooling); the declaration retirement in packages/spec: the two [PLANNED — not yet stored] prefixes (packages/spec/src/api/protocol.zod.ts RegisterDeviceRequestSchema.origin, packages/spec/src/system/push-device.zod.ts UserDeviceSchema.origin) and the marker pin in packages/spec/src/system/push-server-origin.test.ts, plus the docblock-only should-fix lines of 6595162668 on push-server-origin.ts (no validator change); root .changeset/ files (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6595775612
    Premise: holds — unblocked: the wait was on #29767, closed 2026-11-03T04:42Z (PR merged); re-derived on origin/main: sys_user_device has no origin field, both [PLANNED — not yet stored] markers and their pin are still present, and the push channel now lives in service-messaging/src/push-channel.ts (it reads PushDeliveryDataSchema), so the copy lands there
    Serial constraints cleared: none: no open PR touches packages/services/service-messaging/** or packages/spec/src/system/push-* (19 open non-mailbox PRs read at claim time)

  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

    Claim amendment to 6670490478 · 2026-11-05T21:54Z
    Branch: claude/project-thread-9eb7x4 (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Session: session_016WV4cytQjG9X1CrCgytUM5 (seat) · dev card thread session_016WV4czbHcjvoBYWDw2gRAq
    Every other line of 6670490478 stands.

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-elon-musk · session_016WV4cytQjG9X1CrCgytUM5

    Claim: PM loop round 1 · 2026-11-05T22:47Z
    Session: session_016WV4cytQjG9X1CrCgytUM5 (seat) · dev card thread session_016WV4czbHcjvoBYWDw2gRAq
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-9eb7x4 (the card thread's designated push branch, the recorded platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-29768
    Supersedes: claim 6670490478 and its amendments 6670564350 (re-posted whole so the closing-target guard reads the live branch)
    Domain: domain:server
    Seat: domain:server#3
    File surface: packages/services/service-messaging/src/objects/user-device.object.ts (sys_user_device.origin beside app_id / locale), packages/services/service-messaging/src/device-registry.ts (row write and read), packages/services/service-messaging/src/push-channel.ts (copy the row's origin into the push data), tests beside each and any service-messaging pin/catalog the new field moves (regenerated with repo tooling); the declaration retirement in packages/spec: the two [PLANNED — not yet stored] prefixes (packages/spec/src/api/protocol.zod.ts RegisterDeviceRequestSchema.origin, packages/spec/src/system/push-device.zod.ts UserDeviceSchema.origin) and the marker pin in packages/spec/src/system/push-server-origin.test.ts, plus the docblock-only should-fix lines of 6595162668 on push-server-origin.ts (no validator change); root .changeset/ files (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6670564350
    Premise: holds — unblocked: the wait was on #29767, closed 2026-11-03T04:42Z (PR merged); re-derived on origin/main: sys_user_device has no origin field, both [PLANNED — not yet stored] markers and their pin are still present, and the push channel now lives in service-messaging/src/push-channel.ts (it reads PushDeliveryDataSchema), so the copy lands there
    Serial constraints cleared: none: no open PR touches packages/services/service-messaging/** or packages/spec/src/system/push-* (19 open non-mailbox PRs read at claim time)

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-elon-musk · session_016WV4czbHcjvoBYWDw2gRAq
    os-dev-report

    {
      "issue": 29768,
      "status": "done",
      "branch": "claude/project-thread-9eb7x4",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30705",
      "session": "session_016WV4czbHcjvoBYWDw2gRAq",
      "premise_still_valid": true,
      "summary": "sys_user_device gains an optional, unbounded origin column; DeviceRegistry writes origin: request.origin ?? null beside app_id/locale on insert and refresh, and toUserDevice answers it, so listDevices returns it and the push channel's existing withOrigin copy finally has a value. Spec: the two [PLANNED — not yet stored] prefixes are removed, the marker pin flipped, and push-server-origin.ts takes the three 6595162668 should-fix items by the docblock route only (validator unchanged). The new label/help is hand-translated in all seven translated locales.",
      "tests": "Measured on origin/main b77c160a (registry+object reverted, pins kept): row.origin undefined, listDevices no origin, push data {notificationId, route} only. New pins: device-registry.integration.test.ts top-level describe (4: stored+listed; null when absent and no member; refresh replaces/clears; push data through createPushChannel over the real MessagingService carries it), user-device.object.test.ts +1 (optional text, no maxLength), push-server-origin.test.ts marker pin flipped. Ablations: registry+object at main => 4 red (null case is the negative control, stays green); read-back line removed => 2 red; PLANNED prefix restored on UserDeviceSchema.origin => flipped pin red. Head: service-messaging 66 files / 850 tests pass, typecheck exit 0; spec push-server-origin.test.ts 68 pass; spec check:test-typecheck OK with an 8 GB heap (OOM at default heap, environmental).",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands at 48036dd1: 108 families, all exit 0 (sweep split by a container restart after 52, resumed at 53 on the same head and tree). --ran reconcile: 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN. pnpm lint exit 0. check-i18n-bundles.mjs exit 0 (in sync). CI check-runs on the PR: pending at report time.",
      "line_budget": "+168 / -17 over 19 files (well under 3000)",
      "files_changed": [
        ".changeset/29768-push-device-origin-stored.md",
        "content/docs/references/api/protocol.mdx (generated)",
        "content/docs/references/system/push-device.mdx (generated)",
        "packages/services/service-messaging/src/device-registry.ts",
        "packages/services/service-messaging/src/device-registry.integration.test.ts",
        "packages/services/service-messaging/src/objects/user-device.object.ts",
        "packages/services/service-messaging/src/objects/user-device.object.test.ts",
        "packages/services/service-messaging/src/translations/{en,ar,es-ES,fr,ja-JP,pt-BR,zh-CN,zh-Hant}.objects.generated.ts (8)",
        "packages/spec/src/api/protocol.zod.ts (describe + docblock)",
        "packages/spec/src/system/push-device.zod.ts (describe)",
        "packages/spec/src/system/push-server-origin.ts (docblock)",
        "packages/spec/src/system/push-server-origin.test.ts"
      ],
      "deviations": [
        "Do-step 3 needed no edit: push-channel.ts on origin/main already copies device.origin into each device's push data (withOrigin, landed with the channel) and push-channel.test.ts already pins it over a registry double; the gap was only that the registry never stored or answered origin. The end-to-end pin (real registry -> real channel) is added in device-registry.integration.test.ts instead.",
        "Translation bundles (8 generated files under service-messaging/src/translations) are outside the order's literal list; they are the service-messaging catalog the new field moves, regenerated with check-i18n-bundles.mjs --write, translated-locale values hand-written.",
        "The column carries no maxLength on purpose: spec's PUSH_SERVER_ORIGIN_PATTERN bounds shape, not length, so a bounded column could refuse a value the registration accepts.",
        "A refresh without origin clears the stored one (null), the same overwrite semantics app_id and locale already have.",
        "Branch is the harness-designated claude/project-thread-9eb7x4 (claim amended by the seat, 6670564350)."
      ],
      "clause_2": "no — no new published export, error code or answer member; the response shape already declared origin. Metadata-protocol: none (system object, engine-owned, not author-writable).",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "0 — no MCP GitHub calls (reads went through gh api GET; subscribe_pr_activity on PR #30705 is a session subscription, not a GitHub write)",
      "api_writes": "3 — git push x2 to claude/project-thread-9eb7x4; relay pr_create (fw-20261105T224704Z-2c730a, PR #30705, 7194 bytes identical); plus this report comment via post-stamped",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "carrier: PR body · noted, not filed — packages/services/service-push/src/push-service.ts PushMessage docblock lists data as notificationId and route only, not origin; not wrong about what it lists, transports forward data whole."
      ]
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions