@ai-matrx/messaging 0.11.8 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.cts CHANGED
@@ -510,6 +510,45 @@ interface Outbox {
510
510
  }
511
511
  declare function createOutbox(options: OutboxOptions): Outbox;
512
512
 
513
+ /**
514
+ * ONE error classification for the whole package (C22).
515
+ *
516
+ * The banned alternative is the host catching a PostgREST error and deciding
517
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
518
+ * which is how the same three-branch `if` ends up copy-pasted into every
519
+ * consumer and drifts. Everything this package throws is a `MessagingError`
520
+ * with a stable `code`, and the two that hosts genuinely treat differently
521
+ * (`session-unavailable`, `forbidden`) say so in the type.
522
+ *
523
+ * The incident behind `session-unavailable`: a DM reader mounted before the
524
+ * access token existed, every RPC came back `42501`, and the app captured 909
525
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
526
+ * warns and retries; it is never a red error.
527
+ */
528
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
529
+ declare class MessagingError extends Error {
530
+ readonly code: MessagingErrorCode;
531
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
532
+ readonly remedy: string;
533
+ readonly cause?: unknown;
534
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
535
+ /** True when re-running the same call is the fix. */
536
+ get isRetryable(): boolean;
537
+ }
538
+ interface PostgrestErrorLike {
539
+ message: string;
540
+ code?: string | undefined;
541
+ details?: string | null | undefined;
542
+ }
543
+ /**
544
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
545
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
546
+ * error, and no consumer should ever have to.
547
+ */
548
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
549
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
550
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
551
+
513
552
  /**
514
553
  * THE STRUCTURAL SUPABASE SHAPE.
515
554
  *
@@ -612,6 +651,95 @@ interface MessagingSupabaseClient {
612
651
  /** The internal client contract — what the repository and the manager see. */
613
652
  type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
614
653
 
654
+ /**
655
+ * WHO IS ACTING — read from the session, never handed in (DD-241).
656
+ *
657
+ * THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
658
+ * 0.11.x this package took the acting user's id from whoever built it:
659
+ * `<MessagingProvider userId>` for the React path, and
660
+ * `createMessagingRepository({ identity: { userId } })` for the framework-free
661
+ * one. Both ends got their id the same way in the only host that exists —
662
+ * matrx-frontend's Redux `selectUserId` — and that copy is written once, when
663
+ * the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
664
+ * to another account, when the person signs out in another tab, or when a
665
+ * refresh returns a different subject.
666
+ *
667
+ * Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
668
+ *
669
+ * `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
670
+ * → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
671
+ * authenticated user"
672
+ *
673
+ * Messaging's version reaches further, because here the id is not only a read
674
+ * argument — it is a WRITE argument. `identity.userId` lands on the wire as
675
+ * `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
676
+ * FROM), `created_by` and `updated_by` (the audit columns), and
677
+ * `.eq("user_id", …)` (whose participant row is being read and updated). A
678
+ * stale one does not merely get refused: where RLS lets it through it writes a
679
+ * row attributed to somebody the tab cannot prove it is.
680
+ *
681
+ * So there is ONE source now, and it is the client's own session. The same
682
+ * client that mints the JWT on every PostgREST request answers "who is this?",
683
+ * so the id in the argument and the id in the token cannot disagree — not
684
+ * because they are kept in step, but because they are the same fact read once.
685
+ *
686
+ * WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
687
+ * user id, a subscription that fires when it changes, and a `resolving` state
688
+ * that is honest about the moment before the first `getSession()` answers.
689
+ *
690
+ * - It is `resolving` until the first read lands. Nothing user-scoped is
691
+ * issued in that window, which is what a page-load race used to spend
692
+ * issuing reads and writes as a user the tab could not prove it was.
693
+ * - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
694
+ * `useSyncExternalStore` source that returns a fresh object per call is
695
+ * "Maximum update depth exceeded", not a warning.
696
+ * - It NEVER refuses anything on its own authority. It reports who the session
697
+ * says you are; the database remains the only thing that decides what that
698
+ * is allowed to read or write.
699
+ * - A client with no `auth` at all is a real, named failure with a remedy —
700
+ * never a silent fall back to "nobody".
701
+ *
702
+ * THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
703
+ * public prop type is still `MessagingSupabaseClient`, which does not describe
704
+ * `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
705
+ * place, exactly as `createMessagingRepository` narrows to
706
+ * `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
707
+ * proven at compile time in `supabase-shape.test.ts`.
708
+ */
709
+
710
+ interface SessionIdentitySnapshot {
711
+ /**
712
+ * `resolving` until the first `getSession()` answers. A user-scoped read or
713
+ * write issued in this window would name a guess.
714
+ */
715
+ readonly state: "resolving" | "resolved";
716
+ /** Who the session says is acting, or `null` for nobody. */
717
+ readonly userId: UserId | null;
718
+ }
719
+ interface SessionIdentity {
720
+ /**
721
+ * False when the client handed to us has no auth source at all — a
722
+ * misconfiguration, not "nobody is signed in", and the two get different
723
+ * sentences.
724
+ */
725
+ readonly hasAuthSource: boolean;
726
+ /** Stable reference until the acting user actually changes. */
727
+ snapshot(): SessionIdentitySnapshot;
728
+ subscribe(listener: () => void): () => void;
729
+ dispose(): void;
730
+ }
731
+ interface SessionIdentityOptions {
732
+ readonly client: MessagingSupabaseClient;
733
+ readonly onDiagnostic?: ((event: {
734
+ level: "info" | "warn" | "error";
735
+ message: string;
736
+ remedy?: string | undefined;
737
+ }) => void) | undefined;
738
+ }
739
+ /** The snapshot every server render sees: nobody is acting on a server. */
740
+ declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
741
+ declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
742
+
615
743
  /**
616
744
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
617
745
  *
@@ -688,7 +816,23 @@ interface RepositoryOptions {
688
816
  * the provider does.
689
817
  */
690
818
  client: MessagingSupabaseClient;
691
- identity: MessagingIdentity;
819
+ /**
820
+ * The active organization. Every conversation and message write carries it
821
+ * explicitly (R5).
822
+ *
823
+ * 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
824
+ * acting is read from `client`'s own session — see `session-identity.ts` for
825
+ * the class that closed and why a handed-in id could not be made safe by
826
+ * convention. A caller that still passes `identity` is IGNORED and told so.
827
+ */
828
+ organizationId: string;
829
+ /**
830
+ * INTERNAL. The React provider already runs one session reader for its own
831
+ * render loop and hands it here so the two cannot answer differently and so
832
+ * one `onAuthStateChange` subscription serves both. A framework-free caller
833
+ * omits it and the repository builds its own from `client`.
834
+ */
835
+ sessionIdentity?: SessionIdentity | undefined;
692
836
  /** Called once before a retry when a read fails with a missing session. */
693
837
  resolveSession?: SessionResolver | undefined;
694
838
  /**
@@ -702,7 +846,14 @@ interface RepositoryOptions {
702
846
  now?: (() => number) | undefined;
703
847
  }
704
848
  interface MessagingRepository {
705
- readonly identity: MessagingIdentity;
849
+ /** The organization every write here is stamped with. */
850
+ readonly organizationId: OrganizationId;
851
+ /**
852
+ * Who the session says is acting — awaited, because the first `getSession()`
853
+ * is a real read. Rejects with `session-unavailable` when nobody is signed
854
+ * in, which is the code the retry loop already knows how to heal.
855
+ */
856
+ actingUserId(): Promise<UserId>;
706
857
  listConversations(args?: {
707
858
  limit?: number;
708
859
  cursor?: ConversationCursor | null;
@@ -980,11 +1131,19 @@ declare function messagingClientId(): string;
980
1131
  * package error, retrying a package call, or branching on a package quirk in
981
1132
  * host code, that logic belongs in here (C22).
982
1133
  *
983
- * 🚨 THE PROVIDER'S LIFETIME IS `client` + `userId` + `organizationId`, AND
984
- * NOTHING ELSE. Hosts pass fresh object literals for `agents`, `actions`, and
985
- * `onDiagnostic` on every render; keying the engine on those identities would
986
- * tear down and rebuild every channel on every parent re-render — a permanent
987
- * resubscribe loop that looks exactly like "realtime is broken".
1134
+ * 🚨 THE PROVIDER'S LIFETIME IS `client` + THE SESSION'S USER + `organizationId`,
1135
+ * AND NOTHING ELSE. Hosts pass fresh object literals for `agents`, `actions`,
1136
+ * and `onDiagnostic` on every render; keying the engine on those identities
1137
+ * would tear down and rebuild every channel on every parent re-render — a
1138
+ * permanent resubscribe loop that looks exactly like "realtime is broken".
1139
+ *
1140
+ * 🚨 THERE IS NO `userId` PROP (DD-241, 0.12.0). Who is acting is read from the
1141
+ * session of the `client` you pass — see `core/session-identity.ts` for the
1142
+ * class that closed. The provider is INERT until that first read lands, an
1143
+ * account switch rebuilds the runtime under the open tab, and a sign-out makes
1144
+ * it inert again. A `userId` still passed at run time (a stale bundle, a
1145
+ * JavaScript consumer) is ignored and reported through `onDiagnostic` with the
1146
+ * remedy — never taken, and never dropped in silence.
988
1147
  */
989
1148
 
990
1149
  /**
@@ -1080,10 +1239,14 @@ interface MessagingHost {
1080
1239
  readonly wrapConversationRow: ComponentType<ConversationRowWrapperProps> | null;
1081
1240
  }
1082
1241
  interface MessagingProviderProps {
1083
- /** Any Supabase client. `null` while the host is still resolving one. */
1242
+ /**
1243
+ * Any Supabase client. `null` while the host is still resolving one.
1244
+ *
1245
+ * 🚨 IT IS ALSO WHO YOU ARE. Messaging reads the acting user from THIS
1246
+ * client's session, so pass the same browser client your app signs in with
1247
+ * (DD-241). There is no `userId` prop, on purpose.
1248
+ */
1084
1249
  client: MessagingSupabaseClient | null | undefined;
1085
- /** The signed-in user. `null` while auth is still resolving. */
1086
- userId: string | null | undefined;
1087
1250
  /** The active organization. Every write carries it (R5). */
1088
1251
  organizationId: string | null | undefined;
1089
1252
  /**
@@ -1512,45 +1675,6 @@ declare const MESSAGING_EVENTS: {
1512
1675
  readonly actionSettled: "mx.action.settled";
1513
1676
  };
1514
1677
 
1515
- /**
1516
- * ONE error classification for the whole package (C22).
1517
- *
1518
- * The banned alternative is the host catching a PostgREST error and deciding
1519
- * for itself whether it was an auth blip, a permission denial, or a real bug —
1520
- * which is how the same three-branch `if` ends up copy-pasted into every
1521
- * consumer and drifts. Everything this package throws is a `MessagingError`
1522
- * with a stable `code`, and the two that hosts genuinely treat differently
1523
- * (`session-unavailable`, `forbidden`) say so in the type.
1524
- *
1525
- * The incident behind `session-unavailable`: a DM reader mounted before the
1526
- * access token existed, every RPC came back `42501`, and the app captured 909
1527
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
1528
- * warns and retries; it is never a red error.
1529
- */
1530
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
1531
- declare class MessagingError extends Error {
1532
- readonly code: MessagingErrorCode;
1533
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
1534
- readonly remedy: string;
1535
- readonly cause?: unknown;
1536
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
1537
- /** True when re-running the same call is the fix. */
1538
- get isRetryable(): boolean;
1539
- }
1540
- interface PostgrestErrorLike {
1541
- message: string;
1542
- code?: string | undefined;
1543
- details?: string | null | undefined;
1544
- }
1545
- /**
1546
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
1547
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
1548
- * error, and no consumer should ever have to.
1549
- */
1550
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
1551
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
1552
- declare function invalidResponse(operation: string, detail: string): MessagingError;
1553
-
1554
1678
  /**
1555
1679
  * Display formatting. In the package because every consumer otherwise rebuilds
1556
1680
  * it slightly differently and the inbox and the thread disagree about what
@@ -1681,4 +1805,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1681
1805
  /** Serialize picked references into a fence the platform's other readers accept. */
1682
1806
  declare function composeFence(references: readonly MatrxReference[]): string;
1683
1807
 
1684
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
1808
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, SearchIcon, SendIcon, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
package/dist/react.d.ts CHANGED
@@ -510,6 +510,45 @@ interface Outbox {
510
510
  }
511
511
  declare function createOutbox(options: OutboxOptions): Outbox;
512
512
 
513
+ /**
514
+ * ONE error classification for the whole package (C22).
515
+ *
516
+ * The banned alternative is the host catching a PostgREST error and deciding
517
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
518
+ * which is how the same three-branch `if` ends up copy-pasted into every
519
+ * consumer and drifts. Everything this package throws is a `MessagingError`
520
+ * with a stable `code`, and the two that hosts genuinely treat differently
521
+ * (`session-unavailable`, `forbidden`) say so in the type.
522
+ *
523
+ * The incident behind `session-unavailable`: a DM reader mounted before the
524
+ * access token existed, every RPC came back `42501`, and the app captured 909
525
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
526
+ * warns and retries; it is never a red error.
527
+ */
528
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
529
+ declare class MessagingError extends Error {
530
+ readonly code: MessagingErrorCode;
531
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
532
+ readonly remedy: string;
533
+ readonly cause?: unknown;
534
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
535
+ /** True when re-running the same call is the fix. */
536
+ get isRetryable(): boolean;
537
+ }
538
+ interface PostgrestErrorLike {
539
+ message: string;
540
+ code?: string | undefined;
541
+ details?: string | null | undefined;
542
+ }
543
+ /**
544
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
545
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
546
+ * error, and no consumer should ever have to.
547
+ */
548
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
549
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
550
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
551
+
513
552
  /**
514
553
  * THE STRUCTURAL SUPABASE SHAPE.
515
554
  *
@@ -612,6 +651,95 @@ interface MessagingSupabaseClient {
612
651
  /** The internal client contract — what the repository and the manager see. */
613
652
  type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
614
653
 
654
+ /**
655
+ * WHO IS ACTING — read from the session, never handed in (DD-241).
656
+ *
657
+ * THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
658
+ * 0.11.x this package took the acting user's id from whoever built it:
659
+ * `<MessagingProvider userId>` for the React path, and
660
+ * `createMessagingRepository({ identity: { userId } })` for the framework-free
661
+ * one. Both ends got their id the same way in the only host that exists —
662
+ * matrx-frontend's Redux `selectUserId` — and that copy is written once, when
663
+ * the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
664
+ * to another account, when the person signs out in another tab, or when a
665
+ * refresh returns a different subject.
666
+ *
667
+ * Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
668
+ *
669
+ * `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
670
+ * → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
671
+ * authenticated user"
672
+ *
673
+ * Messaging's version reaches further, because here the id is not only a read
674
+ * argument — it is a WRITE argument. `identity.userId` lands on the wire as
675
+ * `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
676
+ * FROM), `created_by` and `updated_by` (the audit columns), and
677
+ * `.eq("user_id", …)` (whose participant row is being read and updated). A
678
+ * stale one does not merely get refused: where RLS lets it through it writes a
679
+ * row attributed to somebody the tab cannot prove it is.
680
+ *
681
+ * So there is ONE source now, and it is the client's own session. The same
682
+ * client that mints the JWT on every PostgREST request answers "who is this?",
683
+ * so the id in the argument and the id in the token cannot disagree — not
684
+ * because they are kept in step, but because they are the same fact read once.
685
+ *
686
+ * WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
687
+ * user id, a subscription that fires when it changes, and a `resolving` state
688
+ * that is honest about the moment before the first `getSession()` answers.
689
+ *
690
+ * - It is `resolving` until the first read lands. Nothing user-scoped is
691
+ * issued in that window, which is what a page-load race used to spend
692
+ * issuing reads and writes as a user the tab could not prove it was.
693
+ * - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
694
+ * `useSyncExternalStore` source that returns a fresh object per call is
695
+ * "Maximum update depth exceeded", not a warning.
696
+ * - It NEVER refuses anything on its own authority. It reports who the session
697
+ * says you are; the database remains the only thing that decides what that
698
+ * is allowed to read or write.
699
+ * - A client with no `auth` at all is a real, named failure with a remedy —
700
+ * never a silent fall back to "nobody".
701
+ *
702
+ * THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
703
+ * public prop type is still `MessagingSupabaseClient`, which does not describe
704
+ * `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
705
+ * place, exactly as `createMessagingRepository` narrows to
706
+ * `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
707
+ * proven at compile time in `supabase-shape.test.ts`.
708
+ */
709
+
710
+ interface SessionIdentitySnapshot {
711
+ /**
712
+ * `resolving` until the first `getSession()` answers. A user-scoped read or
713
+ * write issued in this window would name a guess.
714
+ */
715
+ readonly state: "resolving" | "resolved";
716
+ /** Who the session says is acting, or `null` for nobody. */
717
+ readonly userId: UserId | null;
718
+ }
719
+ interface SessionIdentity {
720
+ /**
721
+ * False when the client handed to us has no auth source at all — a
722
+ * misconfiguration, not "nobody is signed in", and the two get different
723
+ * sentences.
724
+ */
725
+ readonly hasAuthSource: boolean;
726
+ /** Stable reference until the acting user actually changes. */
727
+ snapshot(): SessionIdentitySnapshot;
728
+ subscribe(listener: () => void): () => void;
729
+ dispose(): void;
730
+ }
731
+ interface SessionIdentityOptions {
732
+ readonly client: MessagingSupabaseClient;
733
+ readonly onDiagnostic?: ((event: {
734
+ level: "info" | "warn" | "error";
735
+ message: string;
736
+ remedy?: string | undefined;
737
+ }) => void) | undefined;
738
+ }
739
+ /** The snapshot every server render sees: nobody is acting on a server. */
740
+ declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
741
+ declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
742
+
615
743
  /**
616
744
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
617
745
  *
@@ -688,7 +816,23 @@ interface RepositoryOptions {
688
816
  * the provider does.
689
817
  */
690
818
  client: MessagingSupabaseClient;
691
- identity: MessagingIdentity;
819
+ /**
820
+ * The active organization. Every conversation and message write carries it
821
+ * explicitly (R5).
822
+ *
823
+ * 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
824
+ * acting is read from `client`'s own session — see `session-identity.ts` for
825
+ * the class that closed and why a handed-in id could not be made safe by
826
+ * convention. A caller that still passes `identity` is IGNORED and told so.
827
+ */
828
+ organizationId: string;
829
+ /**
830
+ * INTERNAL. The React provider already runs one session reader for its own
831
+ * render loop and hands it here so the two cannot answer differently and so
832
+ * one `onAuthStateChange` subscription serves both. A framework-free caller
833
+ * omits it and the repository builds its own from `client`.
834
+ */
835
+ sessionIdentity?: SessionIdentity | undefined;
692
836
  /** Called once before a retry when a read fails with a missing session. */
693
837
  resolveSession?: SessionResolver | undefined;
694
838
  /**
@@ -702,7 +846,14 @@ interface RepositoryOptions {
702
846
  now?: (() => number) | undefined;
703
847
  }
704
848
  interface MessagingRepository {
705
- readonly identity: MessagingIdentity;
849
+ /** The organization every write here is stamped with. */
850
+ readonly organizationId: OrganizationId;
851
+ /**
852
+ * Who the session says is acting — awaited, because the first `getSession()`
853
+ * is a real read. Rejects with `session-unavailable` when nobody is signed
854
+ * in, which is the code the retry loop already knows how to heal.
855
+ */
856
+ actingUserId(): Promise<UserId>;
706
857
  listConversations(args?: {
707
858
  limit?: number;
708
859
  cursor?: ConversationCursor | null;
@@ -980,11 +1131,19 @@ declare function messagingClientId(): string;
980
1131
  * package error, retrying a package call, or branching on a package quirk in
981
1132
  * host code, that logic belongs in here (C22).
982
1133
  *
983
- * 🚨 THE PROVIDER'S LIFETIME IS `client` + `userId` + `organizationId`, AND
984
- * NOTHING ELSE. Hosts pass fresh object literals for `agents`, `actions`, and
985
- * `onDiagnostic` on every render; keying the engine on those identities would
986
- * tear down and rebuild every channel on every parent re-render — a permanent
987
- * resubscribe loop that looks exactly like "realtime is broken".
1134
+ * 🚨 THE PROVIDER'S LIFETIME IS `client` + THE SESSION'S USER + `organizationId`,
1135
+ * AND NOTHING ELSE. Hosts pass fresh object literals for `agents`, `actions`,
1136
+ * and `onDiagnostic` on every render; keying the engine on those identities
1137
+ * would tear down and rebuild every channel on every parent re-render — a
1138
+ * permanent resubscribe loop that looks exactly like "realtime is broken".
1139
+ *
1140
+ * 🚨 THERE IS NO `userId` PROP (DD-241, 0.12.0). Who is acting is read from the
1141
+ * session of the `client` you pass — see `core/session-identity.ts` for the
1142
+ * class that closed. The provider is INERT until that first read lands, an
1143
+ * account switch rebuilds the runtime under the open tab, and a sign-out makes
1144
+ * it inert again. A `userId` still passed at run time (a stale bundle, a
1145
+ * JavaScript consumer) is ignored and reported through `onDiagnostic` with the
1146
+ * remedy — never taken, and never dropped in silence.
988
1147
  */
989
1148
 
990
1149
  /**
@@ -1080,10 +1239,14 @@ interface MessagingHost {
1080
1239
  readonly wrapConversationRow: ComponentType<ConversationRowWrapperProps> | null;
1081
1240
  }
1082
1241
  interface MessagingProviderProps {
1083
- /** Any Supabase client. `null` while the host is still resolving one. */
1242
+ /**
1243
+ * Any Supabase client. `null` while the host is still resolving one.
1244
+ *
1245
+ * 🚨 IT IS ALSO WHO YOU ARE. Messaging reads the acting user from THIS
1246
+ * client's session, so pass the same browser client your app signs in with
1247
+ * (DD-241). There is no `userId` prop, on purpose.
1248
+ */
1084
1249
  client: MessagingSupabaseClient | null | undefined;
1085
- /** The signed-in user. `null` while auth is still resolving. */
1086
- userId: string | null | undefined;
1087
1250
  /** The active organization. Every write carries it (R5). */
1088
1251
  organizationId: string | null | undefined;
1089
1252
  /**
@@ -1512,45 +1675,6 @@ declare const MESSAGING_EVENTS: {
1512
1675
  readonly actionSettled: "mx.action.settled";
1513
1676
  };
1514
1677
 
1515
- /**
1516
- * ONE error classification for the whole package (C22).
1517
- *
1518
- * The banned alternative is the host catching a PostgREST error and deciding
1519
- * for itself whether it was an auth blip, a permission denial, or a real bug —
1520
- * which is how the same three-branch `if` ends up copy-pasted into every
1521
- * consumer and drifts. Everything this package throws is a `MessagingError`
1522
- * with a stable `code`, and the two that hosts genuinely treat differently
1523
- * (`session-unavailable`, `forbidden`) say so in the type.
1524
- *
1525
- * The incident behind `session-unavailable`: a DM reader mounted before the
1526
- * access token existed, every RPC came back `42501`, and the app captured 909
1527
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
1528
- * warns and retries; it is never a red error.
1529
- */
1530
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
1531
- declare class MessagingError extends Error {
1532
- readonly code: MessagingErrorCode;
1533
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
1534
- readonly remedy: string;
1535
- readonly cause?: unknown;
1536
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
1537
- /** True when re-running the same call is the fix. */
1538
- get isRetryable(): boolean;
1539
- }
1540
- interface PostgrestErrorLike {
1541
- message: string;
1542
- code?: string | undefined;
1543
- details?: string | null | undefined;
1544
- }
1545
- /**
1546
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
1547
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
1548
- * error, and no consumer should ever have to.
1549
- */
1550
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
1551
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
1552
- declare function invalidResponse(operation: string, detail: string): MessagingError;
1553
-
1554
1678
  /**
1555
1679
  * Display formatting. In the package because every consumer otherwise rebuilds
1556
1680
  * it slightly differently and the inbox and the thread disagree about what
@@ -1681,4 +1805,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1681
1805
  /** Serialize picked references into a fence the platform's other readers accept. */
1682
1806
  declare function composeFence(references: readonly MatrxReference[]): string;
1683
1807
 
1684
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, type SchemaLike, SearchIcon, SendIcon, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };
1808
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, AgentTag, type AiCallArgs, type AiCapability, type AiResult, AlertIcon, type Attachment, Avatar, BotIcon, CheckIcon, ChevronLeftIcon, type ClientMessageId, ClockIcon, CloseIcon, Composer, type ComposerInputRenderProps, type Conversation, type ConversationCursor, type ConversationId, ConversationList, type ConversationListProps, type ConversationRowWrapperProps, ConversationSkeleton, type ConversationSummary, type ConversationThread, type ConversationType, ConversationView, type ConversationViewProps, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, DeliveryTick, DoubleCheckIcon, type DraftMessage, EmptyState, type EngineDiagnostic, type IncomingMessageContext, type JsonObject, type JsonValue, LinkIcon, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, MessageActionChips, type MessageActionRenderProps, type MessageActionRenderer, MessageBubble, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessageWrapperProps, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingHost, type MessagingIdentity, MessagingInbox, type MessagingInboxProps, MessagingProvider, type MessagingProviderProps, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, PaperclipIcon, type Participant, type ParticipantRole, PlusIcon, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, ReferenceCard, type ReferenceRenderer, ReplyIcon, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, SearchIcon, SendIcon, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, SparklesIcon, type SupabaseLike, TABLES, type TextSegment, TypingDots, type UseComposerResult, type UseConversationResult, type UseConversationsResult, type UseMessageActionResult, type UseMessagingAiResult, type UseTypistsResult, type UserId, type UserSummary, UsersIcon, asClientMessageId, asConversationId, asMessageId, asOrganizationId, asUserId, composeFence, conversationTopic, createActionRegistry, createMemoryOutboxStorage, createMessagingAi, createMessagingEngine, createMessagingRepository, createMessagingStore, createOutbox, createReadCache, createSessionIdentity, createWebOutboxStorage, effectiveActorKey, extractReferences, formatConversationTime, formatDateSeparator, formatLastSeen, formatMessageTime, formatTypists, groupMessages, inboxTopic, invalidResponse, isSameDay, messagingClientId, normalizeMessagingError, optimisticMessage, participantNames, projectConversationSummary, projectMessage, projectMessageAction, projectParticipantRole, projectUserSummary, resolveActor, splitText, summarizeText, toMessagingArchiveFilter, unreadCutoff, useComposer, useConversation, useConversations, useMessageAction, useMessagingAi, useMessagingHost, useMessagingSnapshot, useOnlineUserIds, useRequiredMessagingHost, useTypists };