diff --git a/agent-roadmap/current.md b/agent-roadmap/current.md index bc1032f9..1399bce4 100644 --- a/agent-roadmap/current.md +++ b/agent-roadmap/current.md @@ -7,7 +7,7 @@ ## 활성 Milestone -- [진행중] 메시징 계약 표준화 +- [검토중] 메시징 계약 표준화 - Phase: `agent-roadmap/phase/messaging-runtime/PHASE.md` - 경로: `agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md` diff --git a/agent-roadmap/phase/messaging-runtime/PHASE.md b/agent-roadmap/phase/messaging-runtime/PHASE.md index 38ec93d7..61a3183c 100644 --- a/agent-roadmap/phase/messaging-runtime/PHASE.md +++ b/agent-roadmap/phase/messaging-runtime/PHASE.md @@ -16,7 +16,7 @@ Mattermost server/webapp/push-proxy는 upstream-followable runtime으로 두고, 완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다. 스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다. -- [진행중] 메시징 계약 표준화 +- [검토중] 메시징 계약 표준화 - 경로: `agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md` - 요약: 서버와 Flutter 플러그인이 공유할 메시징/알림 API, 이벤트, 저장 계약을 정리한다. - [계획] 알림 파이프라인 고도화 diff --git a/agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md b/agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md index c5325156..88371d25 100644 --- a/agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md +++ b/agent-roadmap/phase/messaging-runtime/milestones/messaging-contract.md @@ -12,7 +12,7 @@ upstream-followable server/push runtime과 nexo-owned Flutter SDK가 주고받 ## 상태 -[진행중] +[검토중] ## 승격 조건 @@ -41,28 +41,32 @@ upstream-followable server/push runtime과 nexo-owned Flutter SDK가 주고받 앱이 의존할 수 있는 최소 메시징/알림 API 표면을 정리한다. - [x] [dart-api] Dart public API에서 유지할 이름, deprecated 후보, nexo wrapper 후보를 분리한다. -- [ ] [event-shape] notification event, opened event, token event의 payload shape와 backward compatibility 기준을 문서화한다. -- [ ] [token-flow] device token 저장, auth token 저장, signing key 저장 책임과 호출 순서를 정리한다. -- [ ] [sdk-runtime-boundary] Flutter SDK가 server/push contract를 추적하되 Mattermost mobile implementation을 팔로잉하지 않는 경계를 문서화한다. +- [x] [event-shape] notification event, opened event, token event의 payload shape와 backward compatibility 기준을 문서화한다. +- [x] [token-flow] device token 저장, auth token 저장, signing key 저장 책임과 호출 순서를 정리한다. +- [x] [sdk-runtime-boundary] Flutter SDK가 server/push contract를 추적하되 Mattermost mobile implementation을 팔로잉하지 않는 경계를 문서화한다. ### Epic: [server-contract] Server contract 서버 core와 플러그인이 공유하는 메시징/알림 계약을 확인 가능한 형태로 만든다. -- [ ] [push-payload] push payload 필드와 검증 기준을 서버/플러그인 양쪽 문서에서 연결한다. -- [ ] [ack-contract] ACK 요청 형식과 실패 처리 기준을 정리한다. -- [ ] [routing-contract] channel/thread navigation event가 client router로 전달되는 기준을 정리한다. -- [ ] [front-compat] Mattermost webapp/reference front에서 동작하는 기본 메시징 기능과 Flutter SDK embedded 흐름이 충돌하지 않는 기준을 정리한다. +- [x] [push-payload] push payload 필드와 검증 기준을 서버/플러그인 양쪽 문서에서 연결한다. +- [x] [ack-contract] ACK 요청 형식과 실패 처리 기준을 정리한다. +- [x] [routing-contract] channel/thread navigation event가 client router로 전달되는 기준을 정리한다. +- [x] [front-compat] Mattermost webapp/reference front에서 동작하는 기본 메시징 기능과 Flutter SDK embedded 흐름이 충돌하지 않는 기준을 정리한다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 없음 +- 상태: 요청됨 +- 요청일: 2026-05-30 +- 완료 근거: + - `packages/messaging_flutter/README.md`에 plugin-side public API, event shape, token/credential flow, push payload, ACK, routing, reference front compatibility 기준을 정리했다. + - `services/core/compose/README.md`에 server-side source anchors, payload fields, ACK endpoint, webapp/reference front compatibility 기준을 연결했다. + - 모든 기능 Task가 문서/테스트/source anchor 근거와 함께 완료 후보가 되었다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 + - [ ] Flutter 앱 우선 노출 범위가 현재 Public API Contract 기준으로 충분한지 확인했다 - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 +- 리뷰 코멘트: 모든 기능 Task는 완료 후보이며, 남은 판단은 사용자 최종 확인과 archive 승인이다. ## 범위 제외 @@ -74,7 +78,10 @@ upstream-followable server/push runtime과 nexo-owned Flutter SDK가 주고받 - 관련 경로: `packages/messaging_flutter/lib/`, `packages/messaging_flutter/android/`, `services/core/server/`, `services/core/webapp/`, `services/core/compose/`, `apps/flutter-test/` - 표준선(선택): SDK public API는 nexo-owned wrapper로 유지하고, server/webapp/push-proxy compatibility는 얇은 layer로 흡수한다 -- 진행 근거: 2026-05-30 `packages/messaging_flutter/README.md`에 Public API Contract 섹션을 추가해 유지 API, compatibility/deprecated 후보, nexo wrapper 후보를 분리했다. +- 진행 근거: + - 2026-05-30 `packages/messaging_flutter/README.md`에 Public API Contract 섹션을 추가해 유지 API, compatibility/deprecated 후보, nexo wrapper 후보를 분리했다. + - 2026-05-30 `packages/messaging_flutter/README.md`의 Event Shape Contract, Token And Credential Flow, SDK Ownership Boundary, Runtime Tracking Boundary 섹션과 `packages/messaging_flutter/test/nexo_messaging_test.dart` 근거로 event shape, token flow, SDK/runtime boundary, routing contract를 완료로 판정했다. + - 2026-05-30 `packages/messaging_flutter/README.md`와 `services/core/compose/README.md`를 상호 연결해 push payload, ACK, reference front compatibility 계약 기준을 완료 후보로 정리했다. - 선행 작업: 정체성 기준선, 업스트림 팔로잉 기준선, 클라이언트 검증 기준선 - 후속 작업: 알림 파이프라인 고도화, 멀티앱 채널 모델 - 확인 필요: Flutter 앱 우선 노출 범위 diff --git a/packages/messaging_flutter/README.md b/packages/messaging_flutter/README.md index 449eb45b..ba1ca003 100644 --- a/packages/messaging_flutter/README.md +++ b/packages/messaging_flutter/README.md @@ -278,6 +278,104 @@ The Dart API currently recognizes these native event types: `onNavigateToThread`; otherwise it calls `onNavigateToChannel` when a channel is available. +## Server And Push Runtime Contract + +This package consumes the Mattermost-compatible push runtime documented from the +server side in +[`services/core/compose/README.md`](../../services/core/compose/README.md#sdk-contract-anchors). +The plugin keeps compatibility with those fields, while typed app-facing API +stays in the Dart surface above. + +### Push Payload Fields + +Server-side source anchor: +`services/core/server/public/model/push_notification.go`. + +Plugin-side source anchors: +`android/src/main/kotlin/com/tokilabs/nexo/messaging/NexoFirebaseMessagingService.kt`, +`android/src/main/java/com/tokilabs/nexo/messaging/helpers/CustomPushNotificationHelper.java`, +and `android/src/main/kotlin/com/tokilabs/nexo/messaging/LaunchIntentHelper.kt`. + +| Field | Required for | Plugin handling | +| --- | --- | --- | +| `type` | Message, clear, session dispatch | Must match the native `PushNotificationType` constants. Unknown types are forwarded only as raw compatibility events. | +| `ack_id` | ACK and signature validation | Sent back to `/api/v4/notifications/ack` and required by signed push JWT validation when a signature is present. | +| `server_url` | ACK/reply delivery, signature lookup, routing | Preferred server identity. If omitted, Android may resolve it from `server_id` or the single stored server URL. | +| `server_id` | Server URL fallback | Resolved through the identifier stored by `setAuthToken(serverUrl, token, identifier: ...)`. | +| `channel_id` | Notification grouping and channel routing | Used for notification grouping, clear handling, and `onNavigateToChannel`. | +| `root_id` | CRT/thread routing | When CRT is enabled, this groups thread notifications and drives `onNavigateToThread`. | +| `is_crt_enabled` | Thread behavior | The string value `"true"` enables thread routing; other values are treated as false. | +| `post_id` | ACK id-loaded response and notification identity | Included in ACK requests and used as the Android notification id source. | +| `signature` | Payload verification | Verified against the server signing key and stored device token. Missing signatures are accepted for backward compatibility. | +| `id_loaded` | ACK response enrichment | The string value `"true"` asks the server to return post data that Android can merge into the notification bundle. | +| `category` | Inline reply affordance | `CAN_REPLY` enables the Android inline reply action when `post_id` and `server_url` are present. | + +Validation criteria: + +- A signed payload passes only when the JWT contains the expected `ack_id` and + raw device token, and the signing key resolves from the server database or the + `setSigningKey()` fallback. +- A payload without `server_url` is valid only when `server_id` or the one-server + fallback resolves to a server URL before ACK, reply, or signature lookup. +- Payload fields not listed here may pass through raw `onNotification` maps for + diagnostics, but consuming apps should not treat them as stable API. + +### ACK Contract + +Plugin-side source anchor: +`android/src/main/java/com/tokilabs/nexo/messaging/ReceiptDelivery.java`. + +Server-side source anchors: +`services/core/server/public/model/push_notification.go` and +`services/core/server/channels/api4/system.go`. + +The plugin POSTs ACKs to `/api/v4/notifications/ack` with: + +| Request field | Value | +| --- | --- | +| `id` | Push payload `ack_id`. | +| `received_at` | Client receipt timestamp in milliseconds. | +| `platform` | `android`. | +| `type` | Push payload `type`. | +| `post_id` | Push payload `post_id`, when present. | +| `is_id_loaded` | Boolean derived from payload `id_loaded == "true"`. | + +The server returns `OK` for ordinary ACKs. When `is_id_loaded` is true and the +ACK references a message post, the server may return message fields such as +`post_id`, `root_id`, `category`, `message`, `team_id`, `channel_id`, +`channel_name`, `type`, `sender_id`, `sender_name`, and `version`; Android merges +those fields into the notification bundle before rendering. + +Failure handling criteria: + +- If ACK delivery fails, Android logs the failure and continues signature + validation and notification processing with the original payload. +- If an id-loaded ACK response cannot be parsed, Android treats it as absent and + continues with the original payload. +- If the server rejects the ACK because push notifications are disabled, + unauthenticated, or the post is unavailable, that is a runtime/server state + failure, not a Dart API contract change. + +### Reference Front Compatibility + +The Mattermost webapp under `services/core/webapp` remains the reference front +for browser messaging features such as posts, replies, post acknowledgements, +channel navigation, and thread state. The Flutter SDK embedded flow must not +replace or fork that webapp behavior inside this repository. + +Compatibility criteria: + +- Webapp routes, Redux state, and product UI stay upstream-followable in + `services/core/webapp`. +- Flutter consuming apps own their own screens and routers; this plugin emits + opened events and optional channel/thread callbacks only. +- Server compatibility belongs to push payload fields, ACK/reply endpoints, + signing-key verification, and device-token format, not to Mattermost mobile or + webapp UI implementation details. +- If a future Flutter app needs product UI parity with the reference webapp, that + work belongs in the consuming app or a separate roadmap item, not in this + plugin contract milestone. + ## Android Notes The Android manifest declares the plugin's own diff --git a/services/core/compose/README.md b/services/core/compose/README.md index ea32b3c5..d610f581 100644 --- a/services/core/compose/README.md +++ b/services/core/compose/README.md @@ -59,6 +59,42 @@ docker compose config | rg --fixed-strings \ FCM/APNS credential validation is not part of the default smoke. Run it only when a real push-proxy config is present outside git. +## SDK Contract Anchors + +The Flutter SDK side of this contract is documented in +[`packages/messaging_flutter/README.md`](../../../packages/messaging_flutter/README.md#server-and-push-runtime-contract). +This compose runtime keeps the Mattermost-compatible server and push-proxy shape +that the SDK consumes. + +Server source anchors: + +- Push payload model: `services/core/server/public/model/push_notification.go` +- ACK endpoint route and handler: `services/core/server/channels/api4/system.go` +- Push signing and push-proxy dispatch: `services/core/server/channels/app/notification_push.go` +- Reference webapp client behavior: `services/core/webapp/platform/client/src/client4.ts`, + `services/core/webapp/platform/client/src/websocket.ts`, and + `services/core/webapp/channels/src/` + +SDK-facing payload fields are `type`, `ack_id`, `server_id`, `device_id`, +`post_id`, `category`, `team_id`, `channel_id`, `root_id`, `channel_name`, +`message`, `sender_id`, `sender_name`, `version`, `is_crt_enabled`, +`is_id_loaded`, and `signature`. The server signs push notifications with the +`ack_id` and raw device token after splitting the stored device id into platform +and token parts. The Android SDK stores device tokens with the +`android_rn-v2:` compatibility prefix and validates the raw token portion. + +ACK delivery uses `POST /api/v4/notifications/ack` with `id`, `received_at`, +`platform`, `type`, `post_id`, and `is_id_loaded`. When id-loaded ACK is enabled +for a message notification, the server may return post and routing fields that +the SDK merges into the notification bundle. + +The webapp remains the reference browser front. Compose and server updates must +keep webapp messaging, replies, post acknowledgements, channel navigation, and +thread behavior upstream-followable. Flutter embedded SDK compatibility is +limited to payload fields, ACK/reply endpoints, signing-key verification, +device-token format, and opened channel/thread routing events; product UI parity +belongs to consuming apps or a separate roadmap item. + ## Remote Parity The remote development copy lives under `~/docker/services/nexo/compose`. Local