| D-M3-23-1 |
2026-10-21 |
Presence is memory of the process that holds the gateway, so internal/wire wires the gateway's PresenceView, the live routes of notify and the gateway's EphemeralPublisher only in role all; role worker keeps presence.None and the fail-closed routes of M3-19 and logs presence unavailable in this role: hints go by push only once with bothy.server.role; role api wires nothing. With cluster.mode = multi the view sees its own node only. Place: internal/wire/module_notify.go (wirePresence), 13 12 point 4, the notify README. |
Only role all holds both the hub and the consumer push (02 3.2). Separate roles (level 1 of 02 16) and several api nodes (level 2) reach desktops only by push until M7-17 brings a shared presence state and the cross-process path of 02 11.6 (RK-37, D-W-108 point 8). |
| D-M3-23-2 |
2026-10-21 |
A connection counts for presence exactly when its principal is a human with a device, it is in phase live (after catchup.complete) and it is not closing; agent and app connections never count. A connection that never sent focus has an empty focus and is not idle. During the replacement of a device's connection the newer registration wins. Place: internal/gateway/app/presence.go, 13 4.4. |
notification.created is never delivered before catchup.complete (04 7.2 point 6), so a catching-up connection counted as connected would get a gateway route whose frame is dropped; counted as offline it gets its push. 13 4.4 says "connected and not idle", so the absence of an idle report means active. |
| D-M3-23-3 |
2026-10-21 |
A gateway route becomes one job notify.gateway_send per hint and recipient (unique on NotificationID and RecipientID), queue delivery, which after the commit publishes notification.created once with the recipient as audience, reaching every live connection of that person; a connection that ended in between gets nothing and no fallback push. Place: internal/notify/app/liveroutes.go, gatewaysend.go, adapters/jobs, 13 4.6, 02 12.3. |
An ephemeral event is published only after the commit of the transaction that decided it (M2-10 note 8, M2-01 note 2), and Decide gives gateway to every connected device of the person, so one publish to the audience covers them (M3-17 note 8); at most once (04 9 point 4). |
| D-M3-23-4 |
2026-10-21 |
Step 1 of notify.gateway_send checks, in one unit of work and in this order, push.max_age (expired), an active human recipient and push.deliver on the thread with the principal of a human without a request in filter mode, or the recipient rule of R-AZ-30 for security and admin (no_access), through the same function as the send step of notify.push_send (mayDeliver), and reads the thread's current place for the scope. Step 2 outside any transaction: without a publisher no_registration with one Warn line per process, else one publish; a publish error is returned. Exactly once is not promised: a crash after the publish repeats the job with the same nid. Place: internal/notify/app/gatewaysend.go, send.go. |
A revocation committed after the round stops the hint (second check), the hub's admission in log order stops a revocation the tail already applied (third check, T-AZ-03); an in-memory publish cannot join a transaction, and the client replaces by thread (13 6.1). |
| D-M3-23-5 |
2026-10-21 |
A push delayed while a desktop is active is one notify.push_send per registration with ScheduledAt and DueAt at the end of notify.mobile_delay_when_desktop_active and ReleaseSeq = Hint.MessageSeq (the thread_seq of the message: the payload's of message.created, the last agent message of a run, the newest message of a flush; zero without a message). The send reads, after the registration, age and access checks, max(read_states.last_read_seq, threads.unread_floor_seq) and skips read when domain.ReleaseDelayed is false; nothing else is evaluated again, a quiet hint stays quiet. Place: internal/notify/app/liveroutes.go, send.go (released), 13 4.4. |
13 4.4 names only "read" as the reason to drop a delayed push; t0 of 13 9 for the delayed route is the end of the waiting time. |
| D-M3-23-6 |
2026-10-21 |
sync.Admit admits a notification.created with resource user exactly when the connection principal equals the audience and the resource ID, without push.deliver; with resource thread the generic path (audience, then push.deliver on the thread in delivery mode) stays. The generic credential check of personal events (gateway.connect) still runs. Place: internal/sync/app/catchup.go, 13 6.3. |
05 section 6 and R-AZ-30 give a hint without a thread the recipient as its resource and replace push.deliver by the recipient rule, which the job evaluated with the rights of that moment; the connection itself proves an active session of that person. |
| D-M3-23-7 |
2026-10-21 |
NotificationCreatedPayload gains the optional member target_id (the waiting device for security, the grant or group of the reminder for admin) and descriptions of thread_id, message_id and count; absent members are omitted, never null. The server, Dart, Python and TypeScript clients are regenerated. Place: docs/api/openapi.yaml. |
R-AZ-30 says the payload of such a hint names the kind and the target ID, and the schema had no member for it. |
| D-M3-23-8 |
2026-10-21 |
notification.created carries no preview fields until M3-22 adds them with its preview builder; the client shows the hint from the data synced on the same connection. Place: 13 6.3, the notify README. |
The builder needs M3-05, M3-06 and M3-07, none of which is in this ticket's closure; M3-22 depends on M3-23 (PLAN row M3-22, D-W-108 point 8). |
| D-M3-23-9 |
2026-10-23 |
Built on the old integration line and ported to main after M3-19 landed: only the ticket's own commits were brought over. The two commits that ported R-AZ-30 and the catalogue resource notes into the old base and the two test fixture commits for the old stack are dropped, because main already carries them; the additions to the German chapter files of the old base go to their English files on main; wirePresence lives in internal/wire/module_notify.go (PL/28); the hub keeps the attach order of M2-17 next to the new registration order of the presence read; the bundled flush takes the thread sequence through newestMessage; the changelog entry and these rulings are fragments (PL/24). Place: this branch. |
The rules of the landing replace the integration branches; main is the reference. |
| D-M3-23-10 |
2026-10-23 |
The core rule of 05 section 6 (the resource of notification.created without a thread is the recipient, user) and the matching sentence of R-AZ-30 are taken as they stand on main; this ticket changes neither. Place: docs/spec/05-actions.md, docs/spec/actions.yaml. |
The brief relies on that resource (design note 6), which main holds. |
| D-M3-23-11 |
2026-10-21 |
Differences from the brief's names, following the merged code: the read position of the release check uses chat.Threads.Info (UnreadFloorSeq) like the flush of M3-19, not InfoForShare; the kind field of GatewaySend is KindCode (JSON kind), as in PushSend (D-M3-19-18); liveRoutes is built through app.NewLiveRoutes, because internal/wire needs a constructor; Hub.Presence returns the map without an error (the context check is in PresenceView.Connections). The integration tests of the brief run over the push world of M3-19 wired as role all (a test-controlled presence port standing in for the view, a recording publisher) and one end-to-end test against a started process in role all with real gateway connections (TestServeAll_DesktopHintOverGateway); the variants "hub refuses after access.changed", "catch-up held open", the pending-device security hint, notify.mobile_delay_when_desktop_active zero and the two-process role test are covered by the unit tests of gateway/app, sync/app, notify/domain and wire instead. Place: this branch. |
Boundaries of the brief: follow the merged names and list each difference; build the minimum that proves the ticket. |
| D-M3-23-12 |
2026-10-23 |
The brief's pull request and merge steps follow the landing rules: one pull request on main, the full suite runs in its CI. Place: this branch. |
Steering of the landing (2026-10-21). |