@memberjunction/server 6.1.4 → 6.1.5

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.
Files changed (71) hide show
  1. package/README.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/auth/magicLink/MagicLinkService.d.ts +17 -15
  4. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
  5. package/dist/auth/magicLink/MagicLinkService.js +49 -35
  6. package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
  7. package/dist/auth/magicLink/magicLinkCore.d.ts +14 -33
  8. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
  9. package/dist/auth/magicLink/magicLinkCore.js +20 -52
  10. package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
  11. package/dist/context.d.ts.map +1 -1
  12. package/dist/context.js +11 -23
  13. package/dist/context.js.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +36 -44
  16. package/dist/index.js.map +1 -1
  17. package/dist/postgresqlCompat.d.ts +8 -0
  18. package/dist/postgresqlCompat.d.ts.map +1 -0
  19. package/dist/postgresqlCompat.js +19 -0
  20. package/dist/postgresqlCompat.js.map +1 -0
  21. package/dist/postgresqlPoolSettings.d.ts +108 -0
  22. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  23. package/dist/postgresqlPoolSettings.js +134 -0
  24. package/dist/postgresqlPoolSettings.js.map +1 -0
  25. package/dist/resolvers/AdhocQueryResolver.d.ts +10 -20
  26. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  27. package/dist/resolvers/AdhocQueryResolver.js +54 -174
  28. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  29. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +44 -6
  30. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  31. package/dist/resolvers/RealtimeClientSessionResolver.js +90 -34
  32. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  33. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  34. package/dist/resolvers/RunAIAgentResolver.js +2 -1
  35. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  36. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  37. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  38. package/dist/resolvers/adhocTimeout.js +12 -0
  39. package/dist/resolvers/adhocTimeout.js.map +1 -0
  40. package/dist/services/TaskGraphActionRunner.d.ts +13 -8
  41. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  42. package/dist/services/TaskGraphActionRunner.js +16 -15
  43. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  44. package/package.json +92 -92
  45. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +12 -35
  46. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  47. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  48. package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
  49. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +129 -0
  50. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  51. package/src/__tests__/magicLink.test.ts +15 -76
  52. package/src/__tests__/magicLinkService.consume.test.ts +224 -0
  53. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  54. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  55. package/src/auth/magicLink/MagicLinkService.ts +55 -36
  56. package/src/auth/magicLink/magicLinkCore.ts +19 -56
  57. package/src/context.ts +11 -24
  58. package/src/index.ts +40 -45
  59. package/src/postgresqlCompat.ts +21 -0
  60. package/src/postgresqlPoolSettings.ts +213 -0
  61. package/src/resolvers/AdhocQueryResolver.ts +64 -194
  62. package/src/resolvers/RealtimeClientSessionResolver.ts +93 -35
  63. package/src/resolvers/RunAIAgentResolver.ts +4 -1
  64. package/src/resolvers/adhocTimeout.ts +12 -0
  65. package/src/services/TaskGraphActionRunner.ts +16 -15
  66. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -27
  67. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  68. package/dist/resolvers/adhoc-query-helpers.js +0 -42
  69. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  70. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  71. package/src/resolvers/adhoc-query-helpers.ts +0 -50
@@ -148,6 +148,18 @@ export declare class UploadRealtimeRecordingResult {
148
148
  export declare class RealtimeClientSessionResolver extends ResolverBase {
149
149
  private readonly clientSessionService;
150
150
  private readonly sessionManager;
151
+ /**
152
+ * The reason a `Save()`/`Delete()` returned false, for a log line. `CompleteMessage` renders
153
+ * `Message`, `Error` and every `Errors[]` entry (validation errors included, since `Save()`
154
+ * runs `Validate()` and records its errors there), so there is no separate `Validate()` call.
155
+ * When a subclass refused the write without recording anything, say so explicitly instead of
156
+ * "unknown error", which is what hid MJ#4791.
157
+ *
158
+ * Only a NULL `LatestResult` proves the refusal happened before the provider (nothing was
159
+ * registered at all). A SUCCESS `LatestResult` is an earlier save on the same instance — the
160
+ * refusal registered nothing — so its text must never be reported as this failure's reason.
161
+ */
162
+ private describeSaveFailure;
151
163
  /**
152
164
  * Start a client-direct realtime voice session targeting `targetAgentId`.
153
165
  *
@@ -712,6 +724,14 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
712
724
  * queries (which scan junctions across ALL details) still surface the artifact. This is the
713
725
  * least-invasive correct anchor — no fake visible message, no orphaned artifact.
714
726
  *
727
+ * TWO PRINCIPALS, deliberately: the lookup (`findLatestSessionDetailID`) and the junction row
728
+ * itself (`saveDetailArtifactJunction`, `MJ: Conversation Detail Artifacts`) run as `runUser`
729
+ * (elevated for a scoped-anonymous caller) — that junction entity is outside an anonymous
730
+ * caller's relay grants. The hidden ANCHOR `Conversation Detail` this may need to create runs as
731
+ * `callerUser` instead: like every other `MJ: Conversation Details` write, the System user is
732
+ * refused because it is not the conversation's owner (see {@link persistDirectActionTurn},
733
+ * issue #4791) — only the actual caller can create it.
734
+ *
715
735
  * Strictly best-effort: every failure path logs and returns; a relayed tool call NEVER fails
716
736
  * because history linking did.
717
737
  */
@@ -778,12 +798,6 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
778
798
  * than blocking the start.
779
799
  */
780
800
  private resolveWidgetVoiceCapSeconds;
781
- /**
782
- * Persists a single transcript turn as a `Conversation Detail` stamped with the session's
783
- * conversation, the mapped role, the turn text, the session id, and the owning user.
784
- *
785
- * @returns The boolean save result (logs `CompleteMessage` on failure).
786
- */
787
801
  /**
788
802
  * Stamps `RecordingStartedAt` (the recording `t0` alignment origin) + `RecordingMedia` on a
789
803
  * just-started session when the browser captured WITH consent. Best-effort: a parse/save failure
@@ -794,6 +808,13 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
794
808
  * @param recordingStartedAt ISO-8601 recording start timestamp from the browser.
795
809
  */
796
810
  private stampRecordingStart;
811
+ /**
812
+ * Persists a single transcript turn as a `Conversation Detail` stamped with the session's
813
+ * conversation, the mapped role, the turn text, the session id, and the owning user.
814
+ *
815
+ * @returns The boolean save result. On failure it logs the session, the write user and the
816
+ * reason from {@link describeSaveFailure}.
817
+ */
797
818
  private persistTranscriptTurn;
798
819
  /**
799
820
  * CORRECTION persistence (the client transcript's `ReplacesPrevious` marker — e.g.
@@ -814,6 +835,23 @@ export declare class RealtimeClientSessionResolver extends ResolverBase {
814
835
  * Stored as stable facts (Role: 'AI', HiddenToUser: true, ExternalID: callId, CompletionTime: durationMs,
815
836
  * Message: JSON-serialized tool facts) so that session review can reconstruct the action card dynamically
816
837
  * on load (Option 1) without polluting user-facing speech chat bubbles.
838
+ *
839
+ * Writes as the CALLER, never the elevated run user (issue #4791). Owning the session
840
+ * (`loadOwnedActiveSession`'s gate has already run on every path here) does NOT imply owning
841
+ * the conversation: the conversation id was client-supplied at session start. What authorizes
842
+ * this write is the save itself, evaluated for the caller — the conversation owner gate in
843
+ * `MJConversationDetailEntityExtended`, plus the caller's own entity permission and Create RLS
844
+ * on `MJ: Conversation Details` (for the magic-link role that RLS scopes by the invite's
845
+ * `ScopeResourceID`, and the role belongs to a downstream app, not MJ core). That is why
846
+ * writing as the caller is safe even though the conversation id originated client-side.
847
+ * Elevating to the System user would bypass RLS entirely, and the owner gate refuses it anyway
848
+ * because the System user is not the conversation owner. Measured on MJ#4791 (before the
849
+ * core-entities fix that now records the denial): `Save()` returned `false` with a NULL `LatestResult` and an empty
850
+ * `ResultHistory` — indistinguishable from any other silent failure. Elevating this write is
851
+ * what silently dropped both hidden tool-execution turns of a real magic-link session; the
852
+ * denial itself still happens today, only its silence was fixed separately.
853
+ * `ResolveScopedAnonymousRunUser` elevates AI-**run**-entity writes only (prompt runs, agent
854
+ * runs, the relayed dispatch itself) — never this one.
817
855
  */
818
856
  private persistDirectActionTurn;
819
857
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RealtimeClientSessionResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/RealtimeClientSessionResolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RealtimeClientSessionResolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/RealtimeClientSessionResolver.ts"],"names":[],"mappings":"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"}
@@ -266,6 +266,30 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
266
266
  this.clientSessionService = new RealtimeClientSessionService();
267
267
  this.sessionManager = new SessionManager(this.clientSessionService);
268
268
  }
269
+ /**
270
+ * The reason a `Save()`/`Delete()` returned false, for a log line. `CompleteMessage` renders
271
+ * `Message`, `Error` and every `Errors[]` entry (validation errors included, since `Save()`
272
+ * runs `Validate()` and records its errors there), so there is no separate `Validate()` call.
273
+ * When a subclass refused the write without recording anything, say so explicitly instead of
274
+ * "unknown error", which is what hid MJ#4791.
275
+ *
276
+ * Only a NULL `LatestResult` proves the refusal happened before the provider (nothing was
277
+ * registered at all). A SUCCESS `LatestResult` is an earlier save on the same instance — the
278
+ * refusal registered nothing — so its text must never be reported as this failure's reason.
279
+ */
280
+ describeSaveFailure(entity) {
281
+ const latest = entity.LatestResult;
282
+ if (!latest) {
283
+ return `no failure detail recorded (LatestResult null; ResultHistory length ${entity.ResultHistory.length}) ` +
284
+ '— the write was refused before reaching the provider';
285
+ }
286
+ if (latest.Success) {
287
+ return 'no failure detail recorded (latest result entry is a prior success; ' +
288
+ `ResultHistory length ${entity.ResultHistory.length})`;
289
+ }
290
+ const detail = latest.CompleteMessage?.trim();
291
+ return detail || `no failure detail recorded (LatestResult empty; ResultHistory length ${entity.ResultHistory.length})`;
292
+ }
269
293
  /**
270
294
  * Start a client-direct realtime voice session targeting `targetAgentId`.
271
295
  *
@@ -412,7 +436,9 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
412
436
  // Junction-link any artifacts the delegated run produced into the session's conversation
413
437
  // history (best-effort) — so chat, session review, and resume carryover can all see them.
414
438
  // Runs as `runUser`: the junction entity is not among an anonymous caller's relay grants.
415
- await this.linkDelegatedArtifactsToConversation(session, Artifacts, runUser, provider);
439
+ // The hidden anchor detail it may need to create, though, is written by the CALLER (#4791)
440
+ // — same reason `persistDirectActionTurn` is: the System user is not the conversation owner.
441
+ await this.linkDelegatedArtifactsToConversation(session, Artifacts, runUser, contextUser, provider);
416
442
  await this.sessionManager.Heartbeat(agentSessionId, contextUser, provider);
417
443
  return ResultJson;
418
444
  }
@@ -463,7 +489,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
463
489
  };
464
490
  session.Config_ = JSON.stringify(next);
465
491
  if (!(await session.Save())) {
466
- LogError(`RealtimeClientSessionResolver.updatePendingFeedbackRunID save failed: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`);
492
+ LogError(`RealtimeClientSessionResolver.updatePendingFeedbackRunID save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
467
493
  }
468
494
  }
469
495
  /**
@@ -1367,7 +1393,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1367
1393
  session.Config_ = JSON.stringify(config);
1368
1394
  const saved = await session.Save();
1369
1395
  if (!saved) {
1370
- LogError(`RealtimeClientSessionResolver.persistObservabilityRunIDs save failed: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1396
+ LogError(`RealtimeClientSessionResolver.persistObservabilityRunIDs save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
1371
1397
  }
1372
1398
  }
1373
1399
  /**
@@ -1451,7 +1477,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1451
1477
  const saved = await row.Save();
1452
1478
  if (!saved) {
1453
1479
  LogError(`SaveSessionChannelState: save failed for session ${agentSessionID} / channel ${channelID}: ` +
1454
- `${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1480
+ `${this.describeSaveFailure(row)}`);
1455
1481
  }
1456
1482
  return saved;
1457
1483
  }
@@ -1675,7 +1701,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1675
1701
  artifact.UserID = contextUser.ID;
1676
1702
  artifact.Visibility = 'Always';
1677
1703
  if (!(await artifact.Save())) {
1678
- const message = `SaveSessionChannelArtifact: artifact save failed: ${artifact.LatestResult?.CompleteMessage ?? 'unknown error'}`;
1704
+ const message = `SaveSessionChannelArtifact: artifact save failed for session ${session.ID}: ${this.describeSaveFailure(artifact)}`;
1679
1705
  LogError(message);
1680
1706
  return { Success: false, ErrorMessage: message, ConversationDetailLinked: false };
1681
1707
  }
@@ -1686,7 +1712,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1686
1712
  version.Content = contentJson;
1687
1713
  version.UserID = contextUser.ID;
1688
1714
  if (!(await version.Save())) {
1689
- const message = `SaveSessionChannelArtifact: artifact version save failed: ${version.LatestResult?.CompleteMessage ?? 'unknown error'}`;
1715
+ const message = `SaveSessionChannelArtifact: artifact version save failed for session ${session.ID} (artifact ${artifact.ID}): ${this.describeSaveFailure(version)}`;
1690
1716
  LogError(message);
1691
1717
  return { Success: false, ErrorMessage: message, ArtifactID: artifact.ID, ConversationDetailLinked: false };
1692
1718
  }
@@ -1736,21 +1762,29 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1736
1762
  * queries (which scan junctions across ALL details) still surface the artifact. This is the
1737
1763
  * least-invasive correct anchor — no fake visible message, no orphaned artifact.
1738
1764
  *
1765
+ * TWO PRINCIPALS, deliberately: the lookup (`findLatestSessionDetailID`) and the junction row
1766
+ * itself (`saveDetailArtifactJunction`, `MJ: Conversation Detail Artifacts`) run as `runUser`
1767
+ * (elevated for a scoped-anonymous caller) — that junction entity is outside an anonymous
1768
+ * caller's relay grants. The hidden ANCHOR `Conversation Detail` this may need to create runs as
1769
+ * `callerUser` instead: like every other `MJ: Conversation Details` write, the System user is
1770
+ * refused because it is not the conversation's owner (see {@link persistDirectActionTurn},
1771
+ * issue #4791) — only the actual caller can create it.
1772
+ *
1739
1773
  * Strictly best-effort: every failure path logs and returns; a relayed tool call NEVER fails
1740
1774
  * because history linking did.
1741
1775
  */
1742
- async linkDelegatedArtifactsToConversation(session, artifacts, contextUser, provider) {
1776
+ async linkDelegatedArtifactsToConversation(session, artifacts, runUser, callerUser, provider) {
1743
1777
  if (!artifacts || artifacts.length === 0 || !session.ConversationID) {
1744
1778
  return;
1745
1779
  }
1746
1780
  try {
1747
- const detailID = (await this.findLatestSessionDetailID(session, contextUser, provider)) ??
1748
- (await this.createHiddenSessionAnchorDetail(session, contextUser, provider));
1781
+ const detailID = (await this.findLatestSessionDetailID(session, runUser, provider)) ??
1782
+ (await this.createHiddenSessionAnchorDetail(session, callerUser, provider));
1749
1783
  if (!detailID) {
1750
1784
  return; // anchor unavailable — logged in the helpers
1751
1785
  }
1752
1786
  for (const artifact of artifacts) {
1753
- await this.saveDetailArtifactJunction(detailID, artifact.ArtifactVersionID, contextUser, provider);
1787
+ await this.saveDetailArtifactJunction(detailID, artifact.ArtifactVersionID, runUser, provider);
1754
1788
  }
1755
1789
  }
1756
1790
  catch (error) {
@@ -1781,22 +1815,22 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1781
1815
  * {@link linkDelegatedArtifactsToConversation}). Returns the new detail's id, or `null`
1782
1816
  * (logged) when the save fails.
1783
1817
  */
1784
- async createHiddenSessionAnchorDetail(session, contextUser, provider) {
1785
- const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
1818
+ async createHiddenSessionAnchorDetail(session, callerUser, provider) {
1819
+ const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, callerUser);
1786
1820
  detail.NewRecord();
1787
1821
  detail.ConversationID = session.ConversationID;
1788
1822
  detail.Role = 'AI';
1789
1823
  detail.HiddenToUser = true;
1790
1824
  detail.Message = 'Artifacts produced during a realtime session (system anchor).';
1791
1825
  detail.AgentSessionID = session.ID;
1792
- // Attribute the anchor to the SESSION owner, not the (possibly elevated) writer — identical
1793
- // for every non-elevated caller, whose ownership of the session is already proven.
1826
+ // Attribute to the SESSION owner — the caller writing this row, whose ownership
1827
+ // `loadOwnedActiveSession` already proved.
1794
1828
  detail.UserID = session.UserID;
1795
1829
  if (await detail.Save()) {
1796
1830
  return detail.ID;
1797
1831
  }
1798
1832
  LogError(`ExecuteRealtimeSessionTool: hidden anchor detail save failed for session ${session.ID}: ` +
1799
- `${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1833
+ `${this.describeSaveFailure(detail)}`);
1800
1834
  return null;
1801
1835
  }
1802
1836
  /**
@@ -1812,7 +1846,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1812
1846
  const saved = await junction.Save();
1813
1847
  if (!saved) {
1814
1848
  LogError(`RealtimeClientSessionResolver: artifact junction save failed for detail ${conversationDetailID} / version ${artifactVersionID}: ` +
1815
- `${junction.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1849
+ `${this.describeSaveFailure(junction)}`);
1816
1850
  }
1817
1851
  return saved;
1818
1852
  }
@@ -1841,7 +1875,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1841
1875
  row.LastActiveAt = new Date();
1842
1876
  if (!(await row.Save())) {
1843
1877
  LogError(`SaveSessionChannelArtifact: LastActiveAt stamp failed for session ${agentSessionID} / channel ${channelID}: ` +
1844
- `${row.LatestResult?.CompleteMessage ?? 'unknown error'}`);
1878
+ `${this.describeSaveFailure(row)}`);
1845
1879
  }
1846
1880
  }
1847
1881
  catch (error) {
@@ -1974,12 +2008,6 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
1974
2008
  const minutes = elevation?.widget.VoiceMaxSessionMinutes;
1975
2009
  return minutes && minutes > 0 ? minutes * 60 : undefined;
1976
2010
  }
1977
- /**
1978
- * Persists a single transcript turn as a `Conversation Detail` stamped with the session's
1979
- * conversation, the mapped role, the turn text, the session id, and the owning user.
1980
- *
1981
- * @returns The boolean save result (logs `CompleteMessage` on failure).
1982
- */
1983
2011
  /**
1984
2012
  * Stamps `RecordingStartedAt` (the recording `t0` alignment origin) + `RecordingMedia` on a
1985
2013
  * just-started session when the browser captured WITH consent. Best-effort: a parse/save failure
@@ -2001,9 +2029,16 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2001
2029
  session.RecordingStartedAt = startedAt;
2002
2030
  session.RecordingMedia = 'Audio';
2003
2031
  if (!(await session.Save())) {
2004
- LogError(`RealtimeClientSessionResolver.stampRecordingStart save failed: ${session.LatestResult?.CompleteMessage ?? 'unknown error'}`);
2032
+ LogError(`RealtimeClientSessionResolver.stampRecordingStart save failed for session ${session.ID}: ${this.describeSaveFailure(session)}`);
2005
2033
  }
2006
2034
  }
2035
+ /**
2036
+ * Persists a single transcript turn as a `Conversation Detail` stamped with the session's
2037
+ * conversation, the mapped role, the turn text, the session id, and the owning user.
2038
+ *
2039
+ * @returns The boolean save result. On failure it logs the session, the write user and the
2040
+ * reason from {@link describeSaveFailure}.
2041
+ */
2007
2042
  async persistTranscriptTurn(session, role, text, contextUser, provider, utteranceStartMs, utteranceEndMs) {
2008
2043
  const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
2009
2044
  detail.NewRecord();
@@ -2020,7 +2055,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2020
2055
  }
2021
2056
  const saved = await detail.Save();
2022
2057
  if (!saved) {
2023
- LogError(`RealtimeClientSessionResolver.persistTranscriptTurn save failed: ${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`);
2058
+ LogError(`RealtimeClientSessionResolver.persistTranscriptTurn save failed for session ${session.ID} (write user ${contextUser.ID}): ${this.describeSaveFailure(detail)}`);
2024
2059
  }
2025
2060
  return saved;
2026
2061
  }
@@ -2051,8 +2086,13 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2051
2086
  // ⚠️ ELEVATING INSTEAD WAS TRIED AND IS A TRAP. `ResolveScopedAnonymousRunUser` returns
2052
2087
  // `UserCache.GetSystemUser()`, which on this deployment resolves to the unconfigured
2053
2088
  // placeholder `not.set@nowhere.com` ("Configured provisioning user not found; falling back
2054
- // to an Owner"). Saving as a user that does not exist returns false with a NULL
2055
- // `LatestResult` — indistinguishable from a permission denial, and just as silent.
2089
+ // to an Owner"). That user EXISTS — it is not a missing/invalid principal — and even carries
2090
+ // full CRUD on `MJ: Conversation Details`. It is refused anyway:
2091
+ // `MJConversationDetailEntityExtended`'s owner gate accepts only the conversation's OWNER or
2092
+ // an Edit/Owner Resource Permission grantee, and the System user is neither. Measured on
2093
+ // MJ#4791 (before the core-entities fix that now records the denial): `Save()` → `false`,
2094
+ // `LatestResult` null — indistinguishable from a permission denial, and just as silent. The
2095
+ // denial itself still happens; that fix only made it stop being silent.
2056
2096
  const writeUser = contextUser;
2057
2097
  const rv = RunView.FromMetadataProvider(provider);
2058
2098
  const result = await rv.RunView({
@@ -2070,8 +2110,8 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2070
2110
  //
2071
2111
  // `ResultType: 'entity_object'` hands back hydrated entities, but they do not carry the
2072
2112
  // context user the way `GetEntityObject(entity, user)` does — so `Save()` ran with no
2073
- // principal and failed with an EMPTY message ("unknown error"), which is what the elevation
2074
- // fix above looked like when it was still broken. The insert path beside this one has
2113
+ // principal and failed with an EMPTY message ("unknown error"), which is what an empty-message
2114
+ // failure here looked like before the re-load. The insert path beside this one has
2075
2115
  // always used `GetEntityObject`; this now matches it, which is also why they now succeed
2076
2116
  // and fail for the same reasons.
2077
2117
  const previous = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, writeUser);
@@ -2097,7 +2137,7 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2097
2137
  LogError(`RealtimeClientSessionResolver.replacePreviousTranscriptTurn save failed for session `
2098
2138
  + `${session.ID} — the ${mappedRole} turn keeps its PREVIOUS, shorter text and the `
2099
2139
  + `transcript now understates what was said: `
2100
- + `${previous.LatestResult?.CompleteMessage || JSON.stringify(previous.LatestResult ?? null)}`);
2140
+ + `${this.describeSaveFailure(previous)}`);
2101
2141
  }
2102
2142
  return saved;
2103
2143
  }
@@ -2114,14 +2154,30 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2114
2154
  * Stored as stable facts (Role: 'AI', HiddenToUser: true, ExternalID: callId, CompletionTime: durationMs,
2115
2155
  * Message: JSON-serialized tool facts) so that session review can reconstruct the action card dynamically
2116
2156
  * on load (Option 1) without polluting user-facing speech chat bubbles.
2157
+ *
2158
+ * Writes as the CALLER, never the elevated run user (issue #4791). Owning the session
2159
+ * (`loadOwnedActiveSession`'s gate has already run on every path here) does NOT imply owning
2160
+ * the conversation: the conversation id was client-supplied at session start. What authorizes
2161
+ * this write is the save itself, evaluated for the caller — the conversation owner gate in
2162
+ * `MJConversationDetailEntityExtended`, plus the caller's own entity permission and Create RLS
2163
+ * on `MJ: Conversation Details` (for the magic-link role that RLS scopes by the invite's
2164
+ * `ScopeResourceID`, and the role belongs to a downstream app, not MJ core). That is why
2165
+ * writing as the caller is safe even though the conversation id originated client-side.
2166
+ * Elevating to the System user would bypass RLS entirely, and the owner gate refuses it anyway
2167
+ * because the System user is not the conversation owner. Measured on MJ#4791 (before the
2168
+ * core-entities fix that now records the denial): `Save()` returned `false` with a NULL `LatestResult` and an empty
2169
+ * `ResultHistory` — indistinguishable from any other silent failure. Elevating this write is
2170
+ * what silently dropped both hidden tool-execution turns of a real magic-link session; the
2171
+ * denial itself still happens today, only its silence was fixed separately.
2172
+ * `ResolveScopedAnonymousRunUser` elevates AI-**run**-entity writes only (prompt runs, agent
2173
+ * runs, the relayed dispatch itself) — never this one.
2117
2174
  */
2118
2175
  async persistDirectActionTurn(session, callId, toolName, argsJson, resultJson, durationMs, contextUser, provider) {
2119
2176
  if (!session.ConversationID) {
2120
2177
  return false;
2121
2178
  }
2122
2179
  try {
2123
- const writeUser = ResolveScopedAnonymousRunUser(contextUser);
2124
- const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, writeUser);
2180
+ const detail = await provider.GetEntityObject(CONVERSATION_DETAIL_ENTITY, contextUser);
2125
2181
  detail.NewRecord();
2126
2182
  detail.ConversationID = session.ConversationID;
2127
2183
  detail.Role = 'AI';
@@ -2159,13 +2215,13 @@ let RealtimeClientSessionResolver = class RealtimeClientSessionResolver extends
2159
2215
  detail.Message = JSON.stringify(factPayload);
2160
2216
  const saved = await detail.Save();
2161
2217
  if (!saved) {
2162
- LogError(`RealtimeClientSessionResolver.persistDirectActionTurn save failed: ${detail.LatestResult?.CompleteMessage ?? 'unknown error'}`);
2218
+ LogError(`RealtimeClientSessionResolver.persistDirectActionTurn save failed for session ${session.ID} (tool '${toolName}', call ${callId ?? 'n/a'}, write user ${contextUser.ID}): ${this.describeSaveFailure(detail)}`);
2163
2219
  }
2164
2220
  return saved;
2165
2221
  }
2166
2222
  catch (error) {
2167
2223
  const message = error instanceof Error ? error.message : String(error);
2168
- LogError(`RealtimeClientSessionResolver.persistDirectActionTurn unexpected error: ${message}`);
2224
+ LogError(`RealtimeClientSessionResolver.persistDirectActionTurn unexpected error for session ${session.ID}: ${message}`);
2169
2225
  return false;
2170
2226
  }
2171
2227
  }