@ai-matrx/messaging 0.11.9 → 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/index.d.cts CHANGED
@@ -613,6 +613,45 @@ interface Outbox {
613
613
  }
614
614
  declare function createOutbox(options: OutboxOptions): Outbox;
615
615
 
616
+ /**
617
+ * ONE error classification for the whole package (C22).
618
+ *
619
+ * The banned alternative is the host catching a PostgREST error and deciding
620
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
621
+ * which is how the same three-branch `if` ends up copy-pasted into every
622
+ * consumer and drifts. Everything this package throws is a `MessagingError`
623
+ * with a stable `code`, and the two that hosts genuinely treat differently
624
+ * (`session-unavailable`, `forbidden`) say so in the type.
625
+ *
626
+ * The incident behind `session-unavailable`: a DM reader mounted before the
627
+ * access token existed, every RPC came back `42501`, and the app captured 909
628
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
629
+ * warns and retries; it is never a red error.
630
+ */
631
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
632
+ declare class MessagingError extends Error {
633
+ readonly code: MessagingErrorCode;
634
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
635
+ readonly remedy: string;
636
+ readonly cause?: unknown;
637
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
638
+ /** True when re-running the same call is the fix. */
639
+ get isRetryable(): boolean;
640
+ }
641
+ interface PostgrestErrorLike {
642
+ message: string;
643
+ code?: string | undefined;
644
+ details?: string | null | undefined;
645
+ }
646
+ /**
647
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
648
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
649
+ * error, and no consumer should ever have to.
650
+ */
651
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
652
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
653
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
654
+
616
655
  /**
617
656
  * THE STRUCTURAL SUPABASE SHAPE.
618
657
  *
@@ -715,6 +754,95 @@ interface MessagingSupabaseClient {
715
754
  /** The internal client contract — what the repository and the manager see. */
716
755
  type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
717
756
 
757
+ /**
758
+ * WHO IS ACTING — read from the session, never handed in (DD-241).
759
+ *
760
+ * THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
761
+ * 0.11.x this package took the acting user's id from whoever built it:
762
+ * `<MessagingProvider userId>` for the React path, and
763
+ * `createMessagingRepository({ identity: { userId } })` for the framework-free
764
+ * one. Both ends got their id the same way in the only host that exists —
765
+ * matrx-frontend's Redux `selectUserId` — and that copy is written once, when
766
+ * the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
767
+ * to another account, when the person signs out in another tab, or when a
768
+ * refresh returns a different subject.
769
+ *
770
+ * Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
771
+ *
772
+ * `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
773
+ * → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
774
+ * authenticated user"
775
+ *
776
+ * Messaging's version reaches further, because here the id is not only a read
777
+ * argument — it is a WRITE argument. `identity.userId` lands on the wire as
778
+ * `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
779
+ * FROM), `created_by` and `updated_by` (the audit columns), and
780
+ * `.eq("user_id", …)` (whose participant row is being read and updated). A
781
+ * stale one does not merely get refused: where RLS lets it through it writes a
782
+ * row attributed to somebody the tab cannot prove it is.
783
+ *
784
+ * So there is ONE source now, and it is the client's own session. The same
785
+ * client that mints the JWT on every PostgREST request answers "who is this?",
786
+ * so the id in the argument and the id in the token cannot disagree — not
787
+ * because they are kept in step, but because they are the same fact read once.
788
+ *
789
+ * WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
790
+ * user id, a subscription that fires when it changes, and a `resolving` state
791
+ * that is honest about the moment before the first `getSession()` answers.
792
+ *
793
+ * - It is `resolving` until the first read lands. Nothing user-scoped is
794
+ * issued in that window, which is what a page-load race used to spend
795
+ * issuing reads and writes as a user the tab could not prove it was.
796
+ * - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
797
+ * `useSyncExternalStore` source that returns a fresh object per call is
798
+ * "Maximum update depth exceeded", not a warning.
799
+ * - It NEVER refuses anything on its own authority. It reports who the session
800
+ * says you are; the database remains the only thing that decides what that
801
+ * is allowed to read or write.
802
+ * - A client with no `auth` at all is a real, named failure with a remedy —
803
+ * never a silent fall back to "nobody".
804
+ *
805
+ * THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
806
+ * public prop type is still `MessagingSupabaseClient`, which does not describe
807
+ * `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
808
+ * place, exactly as `createMessagingRepository` narrows to
809
+ * `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
810
+ * proven at compile time in `supabase-shape.test.ts`.
811
+ */
812
+
813
+ interface SessionIdentitySnapshot {
814
+ /**
815
+ * `resolving` until the first `getSession()` answers. A user-scoped read or
816
+ * write issued in this window would name a guess.
817
+ */
818
+ readonly state: "resolving" | "resolved";
819
+ /** Who the session says is acting, or `null` for nobody. */
820
+ readonly userId: UserId | null;
821
+ }
822
+ interface SessionIdentity {
823
+ /**
824
+ * False when the client handed to us has no auth source at all — a
825
+ * misconfiguration, not "nobody is signed in", and the two get different
826
+ * sentences.
827
+ */
828
+ readonly hasAuthSource: boolean;
829
+ /** Stable reference until the acting user actually changes. */
830
+ snapshot(): SessionIdentitySnapshot;
831
+ subscribe(listener: () => void): () => void;
832
+ dispose(): void;
833
+ }
834
+ interface SessionIdentityOptions {
835
+ readonly client: MessagingSupabaseClient;
836
+ readonly onDiagnostic?: ((event: {
837
+ level: "info" | "warn" | "error";
838
+ message: string;
839
+ remedy?: string | undefined;
840
+ }) => void) | undefined;
841
+ }
842
+ /** The snapshot every server render sees: nobody is acting on a server. */
843
+ declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
844
+ declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
845
+
718
846
  /**
719
847
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
720
848
  *
@@ -791,7 +919,23 @@ interface RepositoryOptions {
791
919
  * the provider does.
792
920
  */
793
921
  client: MessagingSupabaseClient;
794
- identity: MessagingIdentity;
922
+ /**
923
+ * The active organization. Every conversation and message write carries it
924
+ * explicitly (R5).
925
+ *
926
+ * 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
927
+ * acting is read from `client`'s own session — see `session-identity.ts` for
928
+ * the class that closed and why a handed-in id could not be made safe by
929
+ * convention. A caller that still passes `identity` is IGNORED and told so.
930
+ */
931
+ organizationId: string;
932
+ /**
933
+ * INTERNAL. The React provider already runs one session reader for its own
934
+ * render loop and hands it here so the two cannot answer differently and so
935
+ * one `onAuthStateChange` subscription serves both. A framework-free caller
936
+ * omits it and the repository builds its own from `client`.
937
+ */
938
+ sessionIdentity?: SessionIdentity | undefined;
795
939
  /** Called once before a retry when a read fails with a missing session. */
796
940
  resolveSession?: SessionResolver | undefined;
797
941
  /**
@@ -805,7 +949,14 @@ interface RepositoryOptions {
805
949
  now?: (() => number) | undefined;
806
950
  }
807
951
  interface MessagingRepository {
808
- readonly identity: MessagingIdentity;
952
+ /** The organization every write here is stamped with. */
953
+ readonly organizationId: OrganizationId;
954
+ /**
955
+ * Who the session says is acting — awaited, because the first `getSession()`
956
+ * is a real read. Rejects with `session-unavailable` when nobody is signed
957
+ * in, which is the code the retry loop already knows how to heal.
958
+ */
959
+ actingUserId(): Promise<UserId>;
809
960
  listConversations(args?: {
810
961
  limit?: number;
811
962
  cursor?: ConversationCursor | null;
@@ -1068,45 +1219,6 @@ declare function createMessagingEngine(options: MessagingEngineOptions): Messagi
1068
1219
  /** The realtime client session id, exposed for diagnostics surfaces. */
1069
1220
  declare function messagingClientId(): string;
1070
1221
 
1071
- /**
1072
- * ONE error classification for the whole package (C22).
1073
- *
1074
- * The banned alternative is the host catching a PostgREST error and deciding
1075
- * for itself whether it was an auth blip, a permission denial, or a real bug —
1076
- * which is how the same three-branch `if` ends up copy-pasted into every
1077
- * consumer and drifts. Everything this package throws is a `MessagingError`
1078
- * with a stable `code`, and the two that hosts genuinely treat differently
1079
- * (`session-unavailable`, `forbidden`) say so in the type.
1080
- *
1081
- * The incident behind `session-unavailable`: a DM reader mounted before the
1082
- * access token existed, every RPC came back `42501`, and the app captured 909
1083
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
1084
- * warns and retries; it is never a red error.
1085
- */
1086
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
1087
- declare class MessagingError extends Error {
1088
- readonly code: MessagingErrorCode;
1089
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
1090
- readonly remedy: string;
1091
- readonly cause?: unknown;
1092
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
1093
- /** True when re-running the same call is the fix. */
1094
- get isRetryable(): boolean;
1095
- }
1096
- interface PostgrestErrorLike {
1097
- message: string;
1098
- code?: string | undefined;
1099
- details?: string | null | undefined;
1100
- }
1101
- /**
1102
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
1103
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
1104
- * error, and no consumer should ever have to.
1105
- */
1106
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
1107
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
1108
- declare function invalidResponse(operation: string, detail: string): MessagingError;
1109
-
1110
1222
  /**
1111
1223
  * Display formatting. In the package because every consumer otherwise rebuilds
1112
1224
  * it slightly differently and the inbox and the thread disagree about what
@@ -1237,4 +1349,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1237
1349
  /** Serialize picked references into a fence the platform's other readers accept. */
1238
1350
  declare function composeFence(references: readonly MatrxReference[]): string;
1239
1351
 
1240
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, 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 };
1352
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, 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 };
package/dist/index.d.ts CHANGED
@@ -613,6 +613,45 @@ interface Outbox {
613
613
  }
614
614
  declare function createOutbox(options: OutboxOptions): Outbox;
615
615
 
616
+ /**
617
+ * ONE error classification for the whole package (C22).
618
+ *
619
+ * The banned alternative is the host catching a PostgREST error and deciding
620
+ * for itself whether it was an auth blip, a permission denial, or a real bug —
621
+ * which is how the same three-branch `if` ends up copy-pasted into every
622
+ * consumer and drifts. Everything this package throws is a `MessagingError`
623
+ * with a stable `code`, and the two that hosts genuinely treat differently
624
+ * (`session-unavailable`, `forbidden`) say so in the type.
625
+ *
626
+ * The incident behind `session-unavailable`: a DM reader mounted before the
627
+ * access token existed, every RPC came back `42501`, and the app captured 909
628
+ * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
629
+ * warns and retries; it is never a red error.
630
+ */
631
+ type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
632
+ declare class MessagingError extends Error {
633
+ readonly code: MessagingErrorCode;
634
+ /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
635
+ readonly remedy: string;
636
+ readonly cause?: unknown;
637
+ constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
638
+ /** True when re-running the same call is the fix. */
639
+ get isRetryable(): boolean;
640
+ }
641
+ interface PostgrestErrorLike {
642
+ message: string;
643
+ code?: string | undefined;
644
+ details?: string | null | undefined;
645
+ }
646
+ /**
647
+ * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
648
+ * every boundary in `repository.ts`; nothing else in the package inspects a raw
649
+ * error, and no consumer should ever have to.
650
+ */
651
+ declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
652
+ /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
653
+ declare function invalidResponse(operation: string, detail: string): MessagingError;
654
+
616
655
  /**
617
656
  * THE STRUCTURAL SUPABASE SHAPE.
618
657
  *
@@ -715,6 +754,95 @@ interface MessagingSupabaseClient {
715
754
  /** The internal client contract — what the repository and the manager see. */
716
755
  type MessagingSupabaseInternal = SupabaseLike & RealtimeClientLike;
717
756
 
757
+ /**
758
+ * WHO IS ACTING — read from the session, never handed in (DD-241).
759
+ *
760
+ * THE CLASS THIS CLOSES, and why messaging is the WORSE half of it. Through
761
+ * 0.11.x this package took the acting user's id from whoever built it:
762
+ * `<MessagingProvider userId>` for the React path, and
763
+ * `createMessagingRepository({ identity: { userId } })` for the framework-free
764
+ * one. Both ends got their id the same way in the only host that exists —
765
+ * matrx-frontend's Redux `selectUserId` — and that copy is written once, when
766
+ * the app boots. Nothing rewrites it when the domain-wide auth cookie rotates
767
+ * to another account, when the person signs out in another tab, or when a
768
+ * refresh returns a different subject.
769
+ *
770
+ * Meet's version of this (DD-240, `@ai-matrx/meet` 0.6.0) made READS fail:
771
+ *
772
+ * `communication.meet_pending_call_invites(p_user_id => <the stale copy>)`
773
+ * → 42501 at HTTP 403, "meet: the acting user (4cf62e4e…) is not the
774
+ * authenticated user"
775
+ *
776
+ * Messaging's version reaches further, because here the id is not only a read
777
+ * argument — it is a WRITE argument. `identity.userId` lands on the wire as
778
+ * `p_user1_id` (the direct-conversation pair), `sender_id` (who a message is
779
+ * FROM), `created_by` and `updated_by` (the audit columns), and
780
+ * `.eq("user_id", …)` (whose participant row is being read and updated). A
781
+ * stale one does not merely get refused: where RLS lets it through it writes a
782
+ * row attributed to somebody the tab cannot prove it is.
783
+ *
784
+ * So there is ONE source now, and it is the client's own session. The same
785
+ * client that mints the JWT on every PostgREST request answers "who is this?",
786
+ * so the id in the argument and the id in the token cannot disagree — not
787
+ * because they are kept in step, but because they are the same fact read once.
788
+ *
789
+ * WHAT THIS MODULE IS. A tiny observable over `client.auth`: the current acting
790
+ * user id, a subscription that fires when it changes, and a `resolving` state
791
+ * that is honest about the moment before the first `getSession()` answers.
792
+ *
793
+ * - It is `resolving` until the first read lands. Nothing user-scoped is
794
+ * issued in that window, which is what a page-load race used to spend
795
+ * issuing reads and writes as a user the tab could not prove it was.
796
+ * - `snapshot()` returns THE SAME REFERENCE until the acting user moves — a
797
+ * `useSyncExternalStore` source that returns a fresh object per call is
798
+ * "Maximum update depth exceeded", not a warning.
799
+ * - It NEVER refuses anything on its own authority. It reports who the session
800
+ * says you are; the database remains the only thing that decides what that
801
+ * is allowed to read or write.
802
+ * - A client with no `auth` at all is a real, named failure with a remedy —
803
+ * never a silent fall back to "nobody".
804
+ *
805
+ * THE CLIENT BOUNDARY STAYS SHALLOW (0.2.1, `repository.ts`'s header). The
806
+ * public prop type is still `MessagingSupabaseClient`, which does not describe
807
+ * `auth` at all — so this module narrows to its own minimal `AuthLike` in ONE
808
+ * place, exactly as `createMessagingRepository` narrows to
809
+ * `MessagingSupabaseInternal`. A real `SupabaseClient` satisfying `AuthLike` is
810
+ * proven at compile time in `supabase-shape.test.ts`.
811
+ */
812
+
813
+ interface SessionIdentitySnapshot {
814
+ /**
815
+ * `resolving` until the first `getSession()` answers. A user-scoped read or
816
+ * write issued in this window would name a guess.
817
+ */
818
+ readonly state: "resolving" | "resolved";
819
+ /** Who the session says is acting, or `null` for nobody. */
820
+ readonly userId: UserId | null;
821
+ }
822
+ interface SessionIdentity {
823
+ /**
824
+ * False when the client handed to us has no auth source at all — a
825
+ * misconfiguration, not "nobody is signed in", and the two get different
826
+ * sentences.
827
+ */
828
+ readonly hasAuthSource: boolean;
829
+ /** Stable reference until the acting user actually changes. */
830
+ snapshot(): SessionIdentitySnapshot;
831
+ subscribe(listener: () => void): () => void;
832
+ dispose(): void;
833
+ }
834
+ interface SessionIdentityOptions {
835
+ readonly client: MessagingSupabaseClient;
836
+ readonly onDiagnostic?: ((event: {
837
+ level: "info" | "warn" | "error";
838
+ message: string;
839
+ remedy?: string | undefined;
840
+ }) => void) | undefined;
841
+ }
842
+ /** The snapshot every server render sees: nobody is acting on a server. */
843
+ declare const SERVER_SESSION_IDENTITY: SessionIdentitySnapshot;
844
+ declare function createSessionIdentity(options: SessionIdentityOptions): SessionIdentity;
845
+
718
846
  /**
719
847
  * THE DATA CONTRACT — the ONE place a messaging table or RPC name exists.
720
848
  *
@@ -791,7 +919,23 @@ interface RepositoryOptions {
791
919
  * the provider does.
792
920
  */
793
921
  client: MessagingSupabaseClient;
794
- identity: MessagingIdentity;
922
+ /**
923
+ * The active organization. Every conversation and message write carries it
924
+ * explicitly (R5).
925
+ *
926
+ * 🚨 THERE IS NO `userId` HERE, AND THERE WILL NOT BE ONE (DD-241). Who is
927
+ * acting is read from `client`'s own session — see `session-identity.ts` for
928
+ * the class that closed and why a handed-in id could not be made safe by
929
+ * convention. A caller that still passes `identity` is IGNORED and told so.
930
+ */
931
+ organizationId: string;
932
+ /**
933
+ * INTERNAL. The React provider already runs one session reader for its own
934
+ * render loop and hands it here so the two cannot answer differently and so
935
+ * one `onAuthStateChange` subscription serves both. A framework-free caller
936
+ * omits it and the repository builds its own from `client`.
937
+ */
938
+ sessionIdentity?: SessionIdentity | undefined;
795
939
  /** Called once before a retry when a read fails with a missing session. */
796
940
  resolveSession?: SessionResolver | undefined;
797
941
  /**
@@ -805,7 +949,14 @@ interface RepositoryOptions {
805
949
  now?: (() => number) | undefined;
806
950
  }
807
951
  interface MessagingRepository {
808
- readonly identity: MessagingIdentity;
952
+ /** The organization every write here is stamped with. */
953
+ readonly organizationId: OrganizationId;
954
+ /**
955
+ * Who the session says is acting — awaited, because the first `getSession()`
956
+ * is a real read. Rejects with `session-unavailable` when nobody is signed
957
+ * in, which is the code the retry loop already knows how to heal.
958
+ */
959
+ actingUserId(): Promise<UserId>;
809
960
  listConversations(args?: {
810
961
  limit?: number;
811
962
  cursor?: ConversationCursor | null;
@@ -1068,45 +1219,6 @@ declare function createMessagingEngine(options: MessagingEngineOptions): Messagi
1068
1219
  /** The realtime client session id, exposed for diagnostics surfaces. */
1069
1220
  declare function messagingClientId(): string;
1070
1221
 
1071
- /**
1072
- * ONE error classification for the whole package (C22).
1073
- *
1074
- * The banned alternative is the host catching a PostgREST error and deciding
1075
- * for itself whether it was an auth blip, a permission denial, or a real bug —
1076
- * which is how the same three-branch `if` ends up copy-pasted into every
1077
- * consumer and drifts. Everything this package throws is a `MessagingError`
1078
- * with a stable `code`, and the two that hosts genuinely treat differently
1079
- * (`session-unavailable`, `forbidden`) say so in the type.
1080
- *
1081
- * The incident behind `session-unavailable`: a DM reader mounted before the
1082
- * access token existed, every RPC came back `42501`, and the app captured 909
1083
- * errors in 0.6 seconds. A missing session is a NORMAL lifecycle moment — it
1084
- * warns and retries; it is never a red error.
1085
- */
1086
- type MessagingErrorCode = "session-unavailable" | "contention" | "forbidden" | "not-found" | "conflict" | "transport" | "invalid-response" | "misconfigured" | "unknown";
1087
- declare class MessagingError extends Error {
1088
- readonly code: MessagingErrorCode;
1089
- /** The remedy. Nothing fails silently, and nothing fails without saying what to do. */
1090
- readonly remedy: string;
1091
- readonly cause?: unknown;
1092
- constructor(code: MessagingErrorCode, message: string, remedy: string, cause?: unknown);
1093
- /** True when re-running the same call is the fix. */
1094
- get isRetryable(): boolean;
1095
- }
1096
- interface PostgrestErrorLike {
1097
- message: string;
1098
- code?: string | undefined;
1099
- details?: string | null | undefined;
1100
- }
1101
- /**
1102
- * THE ONE PLACE a raw PostgREST/transport error becomes a typed one. Called at
1103
- * every boundary in `repository.ts`; nothing else in the package inspects a raw
1104
- * error, and no consumer should ever have to.
1105
- */
1106
- declare function normalizeMessagingError(error: PostgrestErrorLike | Error | unknown, operation: string): MessagingError;
1107
- /** A response whose SHAPE is wrong — the ingress boundary, not the wire. */
1108
- declare function invalidResponse(operation: string, detail: string): MessagingError;
1109
-
1110
1222
  /**
1111
1223
  * Display formatting. In the package because every consumer otherwise rebuilds
1112
1224
  * it slightly differently and the inbox and the thread disagree about what
@@ -1237,4 +1349,4 @@ declare function summarizeText(content: string, maxLength?: number): string;
1237
1349
  /** Serialize picked references into a fence the platform's other readers accept. */
1238
1350
  declare function composeFence(references: readonly MatrxReference[]): string;
1239
1351
 
1240
- export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, type SchemaLike, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, 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 };
1352
+ export { type ActionChoice, type ActionContext, type ActionHandler, type ActionOutcome, type ActionReceipt, type ActionRegistry, type ActorPresentation, type AiCallArgs, type AiCapability, type AiResult, type Attachment, type ClientMessageId, type Conversation, type ConversationCursor, type ConversationId, type ConversationSummary, type ConversationThread, type ConversationType, DEFAULT_MESSAGING_ARCHIVE_FILTER, type DeliveryState, type DraftMessage, type EngineDiagnostic, type JsonObject, type JsonValue, MESSAGING_EVENTS, MESSAGING_RPC_SCHEMA, MESSAGING_SCHEMA, type MatrxReference, type Message, type MessageAction, type MessageCursor, type MessageGroup, type MessageId, type MessageKind, type MessagingAgentIdentity, type MessagingAgents, type MessagingAi, type MessagingAiOptions, type MessagingArchiveFilter, type MessagingEngine, type MessagingEngineOptions, MessagingError, type MessagingErrorCode, type MessagingIdentity, type MessagingRepository, type MessagingSnapshot, type MessagingStore, type MessagingSupabaseClient, type MessagingSupabaseInternal, type OrganizationId, type Outbox, type OutboxEntry, type OutboxOptions, type OutboxStorage, type Page, type Participant, type ParticipantRole, type PostgrestFilterLike, type PostgrestLikeResponse, type PostgrestTableLike, RPCS, type ReadCache, type ReadCacheOptions, type RepositoryOptions, SERVER_SESSION_IDENTITY, type SchemaLike, type SessionIdentity, type SessionIdentitySnapshot, type SessionResolver, type SupabaseLike, TABLES, type TextSegment, type UserId, type UserSummary, 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 };