@remnic/core 9.3.711 → 9.3.713

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 (166) hide show
  1. package/dist/access-boundary.d.ts +5 -5
  2. package/dist/access-boundary.js +2 -2
  3. package/dist/access-cli.js +5 -5
  4. package/dist/access-http.d.ts +5 -5
  5. package/dist/access-http.js +7 -7
  6. package/dist/access-mcp.d.ts +11 -5
  7. package/dist/access-mcp.js +6 -6
  8. package/dist/access-operations-batch.js +3 -3
  9. package/dist/access-operations.d.ts +5 -5
  10. package/dist/access-operations.js +5 -5
  11. package/dist/access-schema.d.ts +10 -0
  12. package/dist/access-schema.js +1 -1
  13. package/dist/{access-service-CoIA0NrG.d.ts → access-service-DwVmvgBg.d.ts} +11 -4
  14. package/dist/access-service.d.ts +5 -5
  15. package/dist/access-service.js +1 -1
  16. package/dist/access-surface-catalog.d.ts +5 -5
  17. package/dist/action-confidence.d.ts +1 -1
  18. package/dist/active-memory-bridge.d.ts +1 -1
  19. package/dist/active-recall.d.ts +1 -1
  20. package/dist/behavior-learner.d.ts +1 -1
  21. package/dist/behavior-signals.d.ts +1 -1
  22. package/dist/bootstrap.d.ts +4 -4
  23. package/dist/briefing.d.ts +1 -1
  24. package/dist/buffer-surprise-report.d.ts +1 -1
  25. package/dist/buffer.d.ts +1 -1
  26. package/dist/calibration.d.ts +1 -1
  27. package/dist/capabilities.d.ts +1 -1
  28. package/dist/{catalog-DQCZrBjw.d.ts → catalog-CadPvH29.d.ts} +1 -1
  29. package/dist/causal-behavior.d.ts +1 -1
  30. package/dist/causal-consolidation.d.ts +1 -1
  31. package/dist/{chunk-I3BT2IDW.js → chunk-DJNKN3XY.js} +31 -21
  32. package/dist/chunk-DJNKN3XY.js.map +1 -0
  33. package/dist/{chunk-NUWZFSKC.js → chunk-FVZ2ISIX.js} +9 -1
  34. package/dist/chunk-FVZ2ISIX.js.map +1 -0
  35. package/dist/{chunk-5KQCOIPW.js → chunk-JC3MIKR4.js} +4 -4
  36. package/dist/{chunk-ZVDRL6SK.js → chunk-RMT7PAVW.js} +2 -2
  37. package/dist/{chunk-YVWZW37D.js → chunk-UHN2TNSB.js} +25 -2
  38. package/dist/chunk-UHN2TNSB.js.map +1 -0
  39. package/dist/{chunk-Z3B2YW56.js → chunk-VUNI6Y3D.js} +4 -4
  40. package/dist/{chunk-A2R5NV6O.js → chunk-VW24K2Q4.js} +40 -25
  41. package/dist/chunk-VW24K2Q4.js.map +1 -0
  42. package/dist/{chunk-AMXOCZHE.js → chunk-ZDUF7K5V.js} +3 -3
  43. package/dist/{cli-qex-L3GT.d.ts → cli-DfDMJqPP.d.ts} +3 -3
  44. package/dist/cli.d.ts +6 -6
  45. package/dist/cli.js +8 -8
  46. package/dist/compounding/engine.d.ts +1 -1
  47. package/dist/compounding/preference-consolidator.d.ts +1 -1
  48. package/dist/compression-optimizer.d.ts +1 -1
  49. package/dist/config.d.ts +1 -1
  50. package/dist/connectors/codex-materialize-runner.d.ts +1 -1
  51. package/dist/connectors/codex-materialize.d.ts +1 -1
  52. package/dist/connectors/index.d.ts +1 -1
  53. package/dist/consolidation-provenance-check.d.ts +1 -1
  54. package/dist/consolidation-undo.d.ts +1 -1
  55. package/dist/contradiction/index.d.ts +1 -1
  56. package/dist/conversation-index/backend.d.ts +1 -1
  57. package/dist/conversation-index/chunker.d.ts +1 -1
  58. package/dist/conversation-index/faiss-adapter.d.ts +1 -1
  59. package/dist/conversation-index/indexer.d.ts +1 -1
  60. package/dist/conversation-index/search.d.ts +1 -1
  61. package/dist/day-summary.d.ts +1 -1
  62. package/dist/delinearize.d.ts +1 -1
  63. package/dist/direct-answer-wiring.d.ts +1 -1
  64. package/dist/direct-answer.d.ts +1 -1
  65. package/dist/embedding-fallback.d.ts +1 -1
  66. package/dist/enrichment/index.d.ts +1 -1
  67. package/dist/entity-retrieval.d.ts +1 -1
  68. package/dist/entity-schema.d.ts +1 -1
  69. package/dist/explicit-capture.d.ts +4 -4
  70. package/dist/extraction-faithfulness.d.ts +1 -1
  71. package/dist/extraction-judge-telemetry.d.ts +1 -1
  72. package/dist/extraction-judge-training.d.ts +1 -1
  73. package/dist/extraction-judge.d.ts +1 -1
  74. package/dist/extraction.d.ts +1 -1
  75. package/dist/fallback-llm.d.ts +1 -1
  76. package/dist/identity-continuity.d.ts +1 -1
  77. package/dist/importance.d.ts +1 -1
  78. package/dist/index.d.ts +9 -9
  79. package/dist/index.js +8 -8
  80. package/dist/intent.d.ts +1 -1
  81. package/dist/lcm/engine.d.ts +1 -1
  82. package/dist/lcm/index.d.ts +1 -1
  83. package/dist/lcm/tools.d.ts +1 -1
  84. package/dist/lifecycle.d.ts +1 -1
  85. package/dist/live-connectors-runner.d.ts +1 -1
  86. package/dist/local-llm.d.ts +1 -1
  87. package/dist/maintenance/memory-governance.d.ts +1 -1
  88. package/dist/mcp-memory-inspector-app.d.ts +5 -5
  89. package/dist/memory-action-policy.d.ts +1 -1
  90. package/dist/memory-cache.d.ts +1 -1
  91. package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
  92. package/dist/memory-projection-store.d.ts +1 -1
  93. package/dist/memory-provenance.d.ts +1 -1
  94. package/dist/memory-worth-outcomes.d.ts +1 -1
  95. package/dist/models-json.d.ts +1 -1
  96. package/dist/namespaces/migrate.d.ts +2 -2
  97. package/dist/namespaces/principal.d.ts +1 -1
  98. package/dist/namespaces/search.d.ts +1 -1
  99. package/dist/namespaces/storage.d.ts +2 -2
  100. package/dist/native-knowledge.d.ts +1 -1
  101. package/dist/operator-toolkit.d.ts +1 -1
  102. package/dist/orchestration/maintenance.d.ts +2 -2
  103. package/dist/{orchestrator-DsVKLEBk.d.ts → orchestrator-B1iVjEyc.d.ts} +3 -3
  104. package/dist/orchestrator.d.ts +4 -4
  105. package/dist/patterns-cli.d.ts +1 -1
  106. package/dist/policy-runtime.d.ts +1 -1
  107. package/dist/provenance.d.ts +1 -1
  108. package/dist/qmd-recall-cache.d.ts +1 -1
  109. package/dist/qmd.d.ts +1 -1
  110. package/dist/recall-disclosure-escalation.d.ts +1 -1
  111. package/dist/recall-explain-renderer.d.ts +1 -1
  112. package/dist/recall-planner-llm.d.ts +1 -1
  113. package/dist/recall-state.d.ts +1 -1
  114. package/dist/recall-tag-filter.d.ts +1 -1
  115. package/dist/recall-xray-cli.d.ts +1 -1
  116. package/dist/recall-xray-renderer.d.ts +1 -1
  117. package/dist/recall-xray.d.ts +1 -1
  118. package/dist/resolve-auth-token.d.ts +1 -1
  119. package/dist/retrieval-agents.d.ts +1 -1
  120. package/dist/retrieval-tiers.d.ts +1 -1
  121. package/dist/routing/engine.d.ts +1 -1
  122. package/dist/routing/store.d.ts +1 -1
  123. package/dist/search/embed-helper.d.ts +1 -1
  124. package/dist/search/factory.d.ts +1 -1
  125. package/dist/search/index.d.ts +1 -1
  126. package/dist/search/lancedb-backend.d.ts +1 -1
  127. package/dist/search/meilisearch-backend.d.ts +1 -1
  128. package/dist/search/noop-backend.d.ts +1 -1
  129. package/dist/search/orama-backend.d.ts +1 -1
  130. package/dist/search/port.d.ts +1 -1
  131. package/dist/search/remote-backend.d.ts +1 -1
  132. package/dist/{semantic-consolidation-_hVxkTuF.d.ts → semantic-consolidation-CY8uVRNJ.d.ts} +1 -1
  133. package/dist/semantic-consolidation.d.ts +2 -2
  134. package/dist/semantic-rule-verifier.d.ts +1 -1
  135. package/dist/session-observer-bands.d.ts +1 -1
  136. package/dist/session-observer-state.d.ts +1 -1
  137. package/dist/shared-context/manager.d.ts +1 -1
  138. package/dist/signal.d.ts +1 -1
  139. package/dist/storage.d.ts +1 -1
  140. package/dist/summarizer.d.ts +1 -1
  141. package/dist/summary-snapshot.d.ts +1 -1
  142. package/dist/temporal-supersession.d.ts +1 -1
  143. package/dist/temporal-validity.d.ts +1 -1
  144. package/dist/threading.d.ts +1 -1
  145. package/dist/tier-migration.d.ts +1 -1
  146. package/dist/tier-routing.d.ts +1 -1
  147. package/dist/topics.d.ts +1 -1
  148. package/dist/transcript.d.ts +1 -1
  149. package/dist/{types-DUK4vVnN.d.ts → types-Dr0Cw3L9.d.ts} +1 -1
  150. package/dist/types.d.ts +1 -1
  151. package/dist/utility-runtime.d.ts +1 -1
  152. package/package.json +2 -2
  153. package/src/access-http.ts +45 -19
  154. package/src/access-mcp.test.ts +1 -0
  155. package/src/access-mcp.ts +34 -17
  156. package/src/access-schema.ts +8 -0
  157. package/src/access-service-observe-idempotency.test.ts +308 -0
  158. package/src/access-service.ts +63 -3
  159. package/dist/chunk-A2R5NV6O.js.map +0 -1
  160. package/dist/chunk-I3BT2IDW.js.map +0 -1
  161. package/dist/chunk-NUWZFSKC.js.map +0 -1
  162. package/dist/chunk-YVWZW37D.js.map +0 -1
  163. /package/dist/{chunk-5KQCOIPW.js.map → chunk-JC3MIKR4.js.map} +0 -0
  164. /package/dist/{chunk-ZVDRL6SK.js.map → chunk-RMT7PAVW.js.map} +0 -0
  165. /package/dist/{chunk-Z3B2YW56.js.map → chunk-VUNI6Y3D.js.map} +0 -0
  166. /package/dist/{chunk-AMXOCZHE.js.map → chunk-ZDUF7K5V.js.map} +0 -0
@@ -3516,4 +3516,4 @@ interface HourlySummary {
3516
3516
  generatedAt: string;
3517
3517
  }
3518
3518
 
3519
- export { type EntityStructuredSection as $, type ActiveRecallChatType as A, type BehaviorSignalEvent as B, type CalendarSource as C, type SecretRef as D, type EntityFile as E, type FaithfulnessFrontmatter as F, type GatewayConfig as G, type HourlySummary as H, type IdentityInjectionMode as I, type AgentAccessAuthToken as J, type CompressionGuidelineOptimizerEventCounts as K, type LiveConnectorsConfig as L, type MemoryCategory as M, type CompressionGuidelineOptimizerActionSummary as N, type CompressionGuidelineOptimizerRuleUpdate as O, type PluginConfig as P, type QmdSearchResult as Q, type RecallTierExplain as R, type SessionObserverBandConfig as S, type TranscriptEntry as T, type MemoryActionType as U, type VerificationState as V, type MemoryActionEvent as W, type CompressionGuidelineOptimizerState as X, type EntityMention as Y, type EntitySchemaDefinition as Z, type EntitySchemaSectionDefinition as _, type ActiveRecallQueryMode as a, type MemoryObservation as a$, type ExtractionResult as a0, type ConsolidationResult as a1, type DaySummaryResult as a2, type ContinuityIncidentRecord as a3, type ContinuityIncidentCloseInput as a4, type ContinuityIncidentOpenInput as a5, type ContinuityImprovementLoop as a6, type ContinuityLoopReviewInput as a7, type ContinuityLoopUpsertInput as a8, type ImportanceScore as a9, type WearableSourceConnector as aA, type WearablesConfig as aB, type WearableSyncSummary as aC, type WearableSourceStatus as aD, type WearableDayTranscript as aE, type WearableCorrectionRule as aF, type CodingContext as aG, type BootstrapOptions as aH, type BootstrapResult as aI, type ConsolidationObservation as aJ, type EntityTimelineEntry as aK, type ScopeProfileLayerId as aL, type ScopeProfilePromotionTarget as aM, type ScopeProfileConfig as aN, type DreamsStatusResult as aO, type DreamsPhase as aP, type DreamsRunResult as aQ, type CodingModeConfig as aR, type WearableCleanupSettings as aS, type WearableDayTranscriptMeta as aT, type BriefingConfig as aU, type BriefingOpenCommitment as aV, type BriefingWindow as aW, type CodexCompatConfig as aX, DEFAULT_RECALL_DISCLOSURE as aY, type ExtractedFact as aZ, type MemoryActionEligibilitySource as a_, type MemoryIntent as aa, type QmdSearchExplain as ab, type MemoryActionEligibilityContext as ac, type MemoryActionPolicyResult as ad, type MemoryLifecycleEventType as ae, type MemoryLifecycleStateSummary as af, type ModelProviderConfig as ag, type NativeKnowledgeConfig as ah, type ProvenanceSource as ai, type ProvenanceConfig as aj, type RetrievalTier as ak, type SignalScanResult as al, type ConversationThread as am, type TopicScore as an, type MemoryLink as ao, type BufferState as ap, type MetaState as aq, type FileHygieneConfig as ar, type EntityRelationship as as, type EntityActivityEntry as at, type AccessTrackingEntry as au, type MemorySummary as av, type WearableConversation as aw, type WearableSourceSettings as ax, type WearableNativeMemory as ay, type WearableMemoryMode as az, type ActiveRecallPromptStyle as b, type NativeKnowledgeObsidianVaultConfig as b$, type MemoryScope as b0, RECALL_DISCLOSURE_LEVELS as b1, type WearableAuthCheck as b2, type WearableFetchOptions as b3, type WearableFetchPage as b4, type WearableNativeMemoryPage as b5, type WearableTranscriptSegment as b6, isRecallDisclosure as b7, type AgentAccessHttpConfig as b8, type AgentDefaultsConfig as b9, type DreamsPhaseStatus as bA, type DreamsPhasesConfig as bB, type DreamsRemConfig as bC, type EngramTraceEvent as bD, type ExtractedProcedureStep as bE, type ExtractedQuestion as bF, type ExtractedReasoningTrace as bG, type ExtractedReasoningTraceStep as bH, type ExtractedRelationship as bI, type ExtractionPassSource as bJ, type GitHubLiveConnectorConfig as bK, type GmailLiveConnectorConfig as bL, type GoogleDriveLiveConnectorConfig as bM, type HeartbeatConfig as bN, type HeartbeatDetectionMode as bO, type LlmTraceCallback as bP, type LlmTraceEvent as bQ, type MemoryActionOutcome as bR, type MemoryActionPolicyDecision as bS, type MemoryActionStatus as bT, type MemoryLinkType as bU, type MemoryOsPresetName as bV, type ModelApi as bW, type ModelDefinitionConfig as bX, type ModelProviderAuthMode as bY, type NamespacePolicy as bZ, type NativeKnowledgeFolderRuleConfig as b_, type AgentPersona as ba, type BehaviorLoopAdjustment as bb, type BehaviorSignalDirection as bc, type BehaviorSignalType as bd, type BriefingCalendarSourceError as be, type BufferEntryState as bf, type CaptureMode as bg, type CodexCliReasoningEffort as bh, type CodexCompactionFlushMode as bi, type CodexConnectorConfig as bj, type CodingKnowledgeConfig as bk, type CompressionGuidelineActivationState as bl, type CompressionGuidelineOptimizerSourceWindow as bm, type ConfidenceTier as bn, type ConsolidationAction as bo, type ConsolidationItem as bp, type ContinuityIncidentState as bq, type ContinuityLoopCadence as br, type ContinuityLoopStatus as bs, type ContradictionScanConfig as bt, type CronConversationRecallMode as bu, type CronRecallMode as bv, type DreamingConfig as bw, type DreamingNarrativePromptStyle as bx, type DreamsDeepSleepConfig as by, type DreamsLightSleepConfig as bz, type ActiveRecallThinking as c, type NativeKnowledgeOpenClawWorkspaceConfig as c0, type NotionLiveConnectorConfig as c1, type PrincipalFromSessionKeyMode as c2, type PrincipalRule as c3, type ProceduralConfig as c4, type QuestionEntry as c5, type ReasoningEffort as c6, type RecallPipelineConfig as c7, type RecallSectionConfig as c8, type RecallTraceEvent as c9, type RelevanceFeedback as ca, SPECULATIVE_TTL_DAYS as cb, type ScopeProfileAutoPromoteConfig as cc, type ScopeProfileTeamProjectConfig as cd, type ScopeTeamConfig as ce, type ScoredEntity as cf, type SemanticChunkingConfigShape as cg, type SignalLevel as ch, type SlotBehaviorConfig as ci, type SlotMismatchMode as cj, type TriggerMode as ck, confidenceTier as cl, type ActiveRecallModelFallbackPolicy as d, type BehaviorLoopPolicyState as e, type AgentPersonaModelConfig as f, type MemoryFile as g, type MemoryStatus as h, type RecallPlanMode as i, type RecallDisclosure as j, type MemoryFrontmatter as k, type MemoryProjectionCurrentState as l, type MemoryLifecycleEvent as m, type BriefingSections as n, type BriefingFollowup as o, type BriefingFocus as p, type CalendarEvent as q, type BriefingActiveThread as r, type BriefingResult as s, type BriefingRecentEntity as t, type BufferTurn as u, type Checkpoint as v, type LifecycleState as w, type PolicyClass as x, type ImportanceLevel as y, type BufferSurpriseEvent as z };
3519
+ export { type EntityStructuredSection as $, type ActiveRecallChatType as A, type BehaviorSignalEvent as B, type CalendarSource as C, type SecretRef as D, type EntityFile as E, type FaithfulnessFrontmatter as F, type GatewayConfig as G, type HourlySummary as H, type IdentityInjectionMode as I, type AgentAccessAuthToken as J, type CompressionGuidelineOptimizerEventCounts as K, type LiveConnectorsConfig as L, type MemoryCategory as M, type CompressionGuidelineOptimizerActionSummary as N, type CompressionGuidelineOptimizerRuleUpdate as O, type PluginConfig as P, type QmdSearchResult as Q, type RecallTierExplain as R, type SessionObserverBandConfig as S, type TranscriptEntry as T, type MemoryActionType as U, type VerificationState as V, type MemoryActionEvent as W, type CompressionGuidelineOptimizerState as X, type EntityMention as Y, type EntitySchemaDefinition as Z, type EntitySchemaSectionDefinition as _, type ActiveRecallQueryMode as a, type MemoryObservation as a$, type ExtractionResult as a0, type ConsolidationResult as a1, type DaySummaryResult as a2, type ContinuityIncidentRecord as a3, type ContinuityIncidentCloseInput as a4, type ContinuityIncidentOpenInput as a5, type ContinuityImprovementLoop as a6, type ContinuityLoopReviewInput as a7, type ContinuityLoopUpsertInput as a8, type ImportanceScore as a9, type WearableSourceConnector as aA, type WearablesConfig as aB, type WearableSyncSummary as aC, type WearableSourceStatus as aD, type WearableDayTranscript as aE, type WearableCorrectionRule as aF, type CodingContext as aG, type BootstrapOptions as aH, type BootstrapResult as aI, type ConsolidationObservation as aJ, type EntityTimelineEntry as aK, type ScopeProfileLayerId as aL, type ScopeProfilePromotionTarget as aM, type ScopeProfileConfig as aN, type DreamsStatusResult as aO, type DreamsPhase as aP, type DreamsRunResult as aQ, type CodingModeConfig as aR, type WearableCleanupSettings as aS, type WearableDayTranscriptMeta as aT, type BriefingConfig as aU, type BriefingOpenCommitment as aV, type BriefingWindow as aW, type CodexCompatConfig as aX, DEFAULT_RECALL_DISCLOSURE as aY, type ExtractedFact as aZ, type MemoryActionEligibilitySource as a_, type MemoryIntent as aa, type QmdSearchExplain as ab, type MemoryActionEligibilityContext as ac, type MemoryActionPolicyResult as ad, type MemoryLifecycleEventType as ae, type MemoryLifecycleStateSummary as af, type ModelProviderConfig as ag, type NativeKnowledgeConfig as ah, type ProvenanceSource as ai, type ProvenanceConfig as aj, type RetrievalTier as ak, type SignalScanResult as al, type ConversationThread as am, type TopicScore as an, type MemoryLink as ao, type BufferState as ap, type MetaState as aq, type FileHygieneConfig as ar, type EntityRelationship as as, type EntityActivityEntry as at, type AccessTrackingEntry as au, type MemorySummary as av, type WearableConversation as aw, type WearableSourceSettings as ax, type WearableNativeMemory as ay, type WearableMemoryMode as az, type ActiveRecallPromptStyle as b, type NativeKnowledgeObsidianVaultConfig as b$, type MemoryScope as b0, RECALL_DISCLOSURE_LEVELS as b1, type WearableAuthCheck as b2, type WearableFetchOptions as b3, type WearableFetchPage as b4, type WearableNativeMemoryPage as b5, type WearableTranscriptSegment as b6, isRecallDisclosure as b7, type AgentAccessHttpConfig as b8, type AgentDefaultsConfig as b9, type DreamsPhaseStatus as bA, type DreamsPhasesConfig as bB, type DreamsRemConfig as bC, type EngramTraceEvent as bD, type ExtractedProcedureStep as bE, type ExtractedQuestion as bF, type ExtractedReasoningTrace as bG, type ExtractedReasoningTraceStep as bH, type ExtractedRelationship as bI, type ExtractionPassSource as bJ, type GitHubLiveConnectorConfig as bK, type GmailLiveConnectorConfig as bL, type GoogleDriveLiveConnectorConfig as bM, type HeartbeatConfig as bN, type HeartbeatDetectionMode as bO, type LlmTraceCallback as bP, type LlmTraceEvent as bQ, type MemoryActionOutcome as bR, type MemoryActionPolicyDecision as bS, type MemoryActionStatus as bT, type MemoryLinkType as bU, type MemoryOsPresetName as bV, type ModelApi as bW, type ModelDefinitionConfig as bX, type ModelProviderAuthMode as bY, type NamespacePolicy as bZ, type NativeKnowledgeFolderRuleConfig as b_, type AgentPersona as ba, type BehaviorLoopAdjustment as bb, type BehaviorSignalDirection as bc, type BehaviorSignalType as bd, type BriefingCalendarSourceError as be, type BufferEntryState as bf, type CaptureMode as bg, type CodexCliReasoningEffort as bh, type CodexCompactionFlushMode as bi, type CodexConnectorConfig as bj, type CodingKnowledgeConfig as bk, type CompressionGuidelineActivationState as bl, type CompressionGuidelineOptimizerSourceWindow as bm, type ConfidenceTier as bn, type ConsolidationAction as bo, type ConsolidationItem as bp, type ContinuityIncidentState as bq, type ContinuityLoopCadence as br, type ContinuityLoopStatus as bs, type ContradictionScanConfig as bt, type CronConversationRecallMode as bu, type CronRecallMode as bv, type DreamingConfig as bw, type DreamingNarrativePromptStyle as bx, type DreamsDeepSleepConfig as by, type DreamsLightSleepConfig as bz, type ActiveRecallThinking as c, type NativeKnowledgeOpenClawWorkspaceConfig as c0, type NotionLiveConnectorConfig as c1, type PrincipalFromSessionKeyMode as c2, type PrincipalRule as c3, type ProceduralConfig as c4, type QuestionEntry as c5, type ReasoningEffort as c6, type RecallPipelineConfig as c7, type RecallSectionConfig as c8, type RecallTraceEvent as c9, type RelevanceFeedback as ca, SPECULATIVE_TTL_DAYS as cb, type ScopeProfileAutoPromoteConfig as cc, type ScopeProfileTeamProjectConfig as cd, type ScopeTeamConfig as ce, type ScoredEntity as cf, type SemanticChunkingConfigShape as cg, type SignalLevel as ch, type SlotBehaviorConfig as ci, type SlotMismatchMode as cj, type TriggerMode as ck, confidenceTier as cl, type ActiveRecallModelFallbackPolicy as d, type BehaviorLoopPolicyState as e, type MemoryStatus as f, type MemoryFile as g, type RecallPlanMode as h, type RecallDisclosure as i, type AgentPersonaModelConfig as j, type MemoryFrontmatter as k, type MemoryProjectionCurrentState as l, type MemoryLifecycleEvent as m, type BriefingSections as n, type BriefingFollowup as o, type BriefingFocus as p, type CalendarEvent as q, type BriefingActiveThread as r, type BriefingResult as s, type BriefingRecentEntity as t, type BufferTurn as u, type Checkpoint as v, type LifecycleState as w, type PolicyClass as x, type ImportanceLevel as y, type BufferSurpriseEvent as z };
package/dist/types.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import './types-gb_SNtjR.js';
2
2
  import './message-parts/index.js';
3
- export { au as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking, J as AgentAccessAuthToken, b8 as AgentAccessHttpConfig, b9 as AgentDefaultsConfig, ba as AgentPersona, f as AgentPersonaModelConfig, bb as BehaviorLoopAdjustment, e as BehaviorLoopPolicyState, bc as BehaviorSignalDirection, B as BehaviorSignalEvent, bd as BehaviorSignalType, aH as BootstrapOptions, aI as BootstrapResult, r as BriefingActiveThread, be as BriefingCalendarSourceError, aU as BriefingConfig, p as BriefingFocus, o as BriefingFollowup, aV as BriefingOpenCommitment, t as BriefingRecentEntity, s as BriefingResult, n as BriefingSections, aW as BriefingWindow, bf as BufferEntryState, ap as BufferState, z as BufferSurpriseEvent, u as BufferTurn, q as CalendarEvent, C as CalendarSource, bg as CaptureMode, v as Checkpoint, bh as CodexCliReasoningEffort, bi as CodexCompactionFlushMode, aX as CodexCompatConfig, bj as CodexConnectorConfig, aG as CodingContext, bk as CodingKnowledgeConfig, aR as CodingModeConfig, bl as CompressionGuidelineActivationState, N as CompressionGuidelineOptimizerActionSummary, K as CompressionGuidelineOptimizerEventCounts, O as CompressionGuidelineOptimizerRuleUpdate, bm as CompressionGuidelineOptimizerSourceWindow, X as CompressionGuidelineOptimizerState, bn as ConfidenceTier, bo as ConsolidationAction, bp as ConsolidationItem, aJ as ConsolidationObservation, a1 as ConsolidationResult, a6 as ContinuityImprovementLoop, a4 as ContinuityIncidentCloseInput, a5 as ContinuityIncidentOpenInput, a3 as ContinuityIncidentRecord, bq as ContinuityIncidentState, br as ContinuityLoopCadence, a7 as ContinuityLoopReviewInput, bs as ContinuityLoopStatus, a8 as ContinuityLoopUpsertInput, bt as ContradictionScanConfig, am as ConversationThread, bu as CronConversationRecallMode, bv as CronRecallMode, aY as DEFAULT_RECALL_DISCLOSURE, a2 as DaySummaryResult, bw as DreamingConfig, bx as DreamingNarrativePromptStyle, by as DreamsDeepSleepConfig, bz as DreamsLightSleepConfig, aP as DreamsPhase, bA as DreamsPhaseStatus, bB as DreamsPhasesConfig, bC as DreamsRemConfig, aQ as DreamsRunResult, aO as DreamsStatusResult, bD as EngramTraceEvent, at as EntityActivityEntry, E as EntityFile, Y as EntityMention, as as EntityRelationship, Z as EntitySchemaDefinition, _ as EntitySchemaSectionDefinition, $ as EntityStructuredSection, aK as EntityTimelineEntry, aZ as ExtractedFact, bE as ExtractedProcedureStep, bF as ExtractedQuestion, bG as ExtractedReasoningTrace, bH as ExtractedReasoningTraceStep, bI as ExtractedRelationship, bJ as ExtractionPassSource, a0 as ExtractionResult, F as FaithfulnessFrontmatter, ar as FileHygieneConfig, G as GatewayConfig, bK as GitHubLiveConnectorConfig, bL as GmailLiveConnectorConfig, bM as GoogleDriveLiveConnectorConfig, bN as HeartbeatConfig, bO as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, y as ImportanceLevel, a9 as ImportanceScore, w as LifecycleState, L as LiveConnectorsConfig, bP as LlmTraceCallback, bQ as LlmTraceEvent, ac as MemoryActionEligibilityContext, a_ as MemoryActionEligibilitySource, W as MemoryActionEvent, bR as MemoryActionOutcome, bS as MemoryActionPolicyDecision, ad as MemoryActionPolicyResult, bT as MemoryActionStatus, U as MemoryActionType, M as MemoryCategory, g as MemoryFile, k as MemoryFrontmatter, aa as MemoryIntent, m as MemoryLifecycleEvent, ae as MemoryLifecycleEventType, af as MemoryLifecycleStateSummary, ao as MemoryLink, bU as MemoryLinkType, a$ as MemoryObservation, bV as MemoryOsPresetName, l as MemoryProjectionCurrentState, b0 as MemoryScope, h as MemoryStatus, av as MemorySummary, aq as MetaState, bW as ModelApi, bX as ModelDefinitionConfig, bY as ModelProviderAuthMode, ag as ModelProviderConfig, bZ as NamespacePolicy, ah as NativeKnowledgeConfig, b_ as NativeKnowledgeFolderRuleConfig, b$ as NativeKnowledgeObsidianVaultConfig, c0 as NativeKnowledgeOpenClawWorkspaceConfig, c1 as NotionLiveConnectorConfig, P as PluginConfig, x as PolicyClass, c2 as PrincipalFromSessionKeyMode, c3 as PrincipalRule, c4 as ProceduralConfig, aj as ProvenanceConfig, ai as ProvenanceSource, ab as QmdSearchExplain, Q as QmdSearchResult, c5 as QuestionEntry, b1 as RECALL_DISCLOSURE_LEVELS, c6 as ReasoningEffort, j as RecallDisclosure, c7 as RecallPipelineConfig, i as RecallPlanMode, c8 as RecallSectionConfig, R as RecallTierExplain, c9 as RecallTraceEvent, ca as RelevanceFeedback, ak as RetrievalTier, cb as SPECULATIVE_TTL_DAYS, cc as ScopeProfileAutoPromoteConfig, aN as ScopeProfileConfig, aL as ScopeProfileLayerId, aM as ScopeProfilePromotionTarget, cd as ScopeProfileTeamProjectConfig, ce as ScopeTeamConfig, cf as ScoredEntity, D as SecretRef, cg as SemanticChunkingConfigShape, S as SessionObserverBandConfig, ch as SignalLevel, al as SignalScanResult, ci as SlotBehaviorConfig, cj as SlotMismatchMode, an as TopicScore, T as TranscriptEntry, ck as TriggerMode, V as VerificationState, cl as confidenceTier, b7 as isRecallDisclosure } from './types-DUK4vVnN.js';
3
+ export { au as AccessTrackingEntry, A as ActiveRecallChatType, d as ActiveRecallModelFallbackPolicy, b as ActiveRecallPromptStyle, a as ActiveRecallQueryMode, c as ActiveRecallThinking, J as AgentAccessAuthToken, b8 as AgentAccessHttpConfig, b9 as AgentDefaultsConfig, ba as AgentPersona, j as AgentPersonaModelConfig, bb as BehaviorLoopAdjustment, e as BehaviorLoopPolicyState, bc as BehaviorSignalDirection, B as BehaviorSignalEvent, bd as BehaviorSignalType, aH as BootstrapOptions, aI as BootstrapResult, r as BriefingActiveThread, be as BriefingCalendarSourceError, aU as BriefingConfig, p as BriefingFocus, o as BriefingFollowup, aV as BriefingOpenCommitment, t as BriefingRecentEntity, s as BriefingResult, n as BriefingSections, aW as BriefingWindow, bf as BufferEntryState, ap as BufferState, z as BufferSurpriseEvent, u as BufferTurn, q as CalendarEvent, C as CalendarSource, bg as CaptureMode, v as Checkpoint, bh as CodexCliReasoningEffort, bi as CodexCompactionFlushMode, aX as CodexCompatConfig, bj as CodexConnectorConfig, aG as CodingContext, bk as CodingKnowledgeConfig, aR as CodingModeConfig, bl as CompressionGuidelineActivationState, N as CompressionGuidelineOptimizerActionSummary, K as CompressionGuidelineOptimizerEventCounts, O as CompressionGuidelineOptimizerRuleUpdate, bm as CompressionGuidelineOptimizerSourceWindow, X as CompressionGuidelineOptimizerState, bn as ConfidenceTier, bo as ConsolidationAction, bp as ConsolidationItem, aJ as ConsolidationObservation, a1 as ConsolidationResult, a6 as ContinuityImprovementLoop, a4 as ContinuityIncidentCloseInput, a5 as ContinuityIncidentOpenInput, a3 as ContinuityIncidentRecord, bq as ContinuityIncidentState, br as ContinuityLoopCadence, a7 as ContinuityLoopReviewInput, bs as ContinuityLoopStatus, a8 as ContinuityLoopUpsertInput, bt as ContradictionScanConfig, am as ConversationThread, bu as CronConversationRecallMode, bv as CronRecallMode, aY as DEFAULT_RECALL_DISCLOSURE, a2 as DaySummaryResult, bw as DreamingConfig, bx as DreamingNarrativePromptStyle, by as DreamsDeepSleepConfig, bz as DreamsLightSleepConfig, aP as DreamsPhase, bA as DreamsPhaseStatus, bB as DreamsPhasesConfig, bC as DreamsRemConfig, aQ as DreamsRunResult, aO as DreamsStatusResult, bD as EngramTraceEvent, at as EntityActivityEntry, E as EntityFile, Y as EntityMention, as as EntityRelationship, Z as EntitySchemaDefinition, _ as EntitySchemaSectionDefinition, $ as EntityStructuredSection, aK as EntityTimelineEntry, aZ as ExtractedFact, bE as ExtractedProcedureStep, bF as ExtractedQuestion, bG as ExtractedReasoningTrace, bH as ExtractedReasoningTraceStep, bI as ExtractedRelationship, bJ as ExtractionPassSource, a0 as ExtractionResult, F as FaithfulnessFrontmatter, ar as FileHygieneConfig, G as GatewayConfig, bK as GitHubLiveConnectorConfig, bL as GmailLiveConnectorConfig, bM as GoogleDriveLiveConnectorConfig, bN as HeartbeatConfig, bO as HeartbeatDetectionMode, H as HourlySummary, I as IdentityInjectionMode, y as ImportanceLevel, a9 as ImportanceScore, w as LifecycleState, L as LiveConnectorsConfig, bP as LlmTraceCallback, bQ as LlmTraceEvent, ac as MemoryActionEligibilityContext, a_ as MemoryActionEligibilitySource, W as MemoryActionEvent, bR as MemoryActionOutcome, bS as MemoryActionPolicyDecision, ad as MemoryActionPolicyResult, bT as MemoryActionStatus, U as MemoryActionType, M as MemoryCategory, g as MemoryFile, k as MemoryFrontmatter, aa as MemoryIntent, m as MemoryLifecycleEvent, ae as MemoryLifecycleEventType, af as MemoryLifecycleStateSummary, ao as MemoryLink, bU as MemoryLinkType, a$ as MemoryObservation, bV as MemoryOsPresetName, l as MemoryProjectionCurrentState, b0 as MemoryScope, f as MemoryStatus, av as MemorySummary, aq as MetaState, bW as ModelApi, bX as ModelDefinitionConfig, bY as ModelProviderAuthMode, ag as ModelProviderConfig, bZ as NamespacePolicy, ah as NativeKnowledgeConfig, b_ as NativeKnowledgeFolderRuleConfig, b$ as NativeKnowledgeObsidianVaultConfig, c0 as NativeKnowledgeOpenClawWorkspaceConfig, c1 as NotionLiveConnectorConfig, P as PluginConfig, x as PolicyClass, c2 as PrincipalFromSessionKeyMode, c3 as PrincipalRule, c4 as ProceduralConfig, aj as ProvenanceConfig, ai as ProvenanceSource, ab as QmdSearchExplain, Q as QmdSearchResult, c5 as QuestionEntry, b1 as RECALL_DISCLOSURE_LEVELS, c6 as ReasoningEffort, i as RecallDisclosure, c7 as RecallPipelineConfig, h as RecallPlanMode, c8 as RecallSectionConfig, R as RecallTierExplain, c9 as RecallTraceEvent, ca as RelevanceFeedback, ak as RetrievalTier, cb as SPECULATIVE_TTL_DAYS, cc as ScopeProfileAutoPromoteConfig, aN as ScopeProfileConfig, aL as ScopeProfileLayerId, aM as ScopeProfilePromotionTarget, cd as ScopeProfileTeamProjectConfig, ce as ScopeTeamConfig, cf as ScoredEntity, D as SecretRef, cg as SemanticChunkingConfigShape, S as SessionObserverBandConfig, ch as SignalLevel, al as SignalScanResult, ci as SlotBehaviorConfig, cj as SlotMismatchMode, an as TopicScore, T as TranscriptEntry, ck as TriggerMode, V as VerificationState, cl as confidenceTier, b7 as isRecallDisclosure } from './types-Dr0Cw3L9.js';
@@ -1,5 +1,5 @@
1
1
  import { TierRoutingPolicy } from './tier-routing.js';
2
- import './types-DUK4vVnN.js';
2
+ import './types-Dr0Cw3L9.js';
3
3
  import './types-gb_SNtjR.js';
4
4
  import './message-parts/index.js';
5
5
  import './lifecycle.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.3.711",
3
+ "version": "9.3.713",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -2966,7 +2966,7 @@
2966
2966
  "core"
2967
2967
  ],
2968
2968
  "peerDependencies": {
2969
- "@remnic/coding-graph": "^9.3.711"
2969
+ "@remnic/coding-graph": "^9.3.713"
2970
2970
  },
2971
2971
  "peerDependenciesMeta": {
2972
2972
  "@remnic/coding-graph": {
@@ -1257,24 +1257,39 @@ export class EngramAccessHttpServer {
1257
1257
  if (req.method === "POST" && pathname === "/engram/v1/observe") {
1258
1258
  void getOperation("observe"); // boundary dispatch (issue #1525)
1259
1259
  const body = await this.readValidatedBody(req, "observe");
1260
- this.ensureWriteRateLimitAvailable();
1261
- const response = await this.service.observe({
1262
- sessionKey: body.sessionKey,
1263
- messages: body.messages.map((message) => ({
1264
- role: message.role,
1265
- content: message.content,
1266
- sourceFormat: message.sourceFormat ?? undefined,
1267
- rawContent: message.rawContent ?? undefined,
1268
- parts: message.parts ?? undefined,
1269
- })),
1270
- namespace: this.resolveNamespace(req, body.namespace),
1271
- authenticatedPrincipal: this.resolveRequestPrincipal(req),
1272
- skipExtraction: body.skipExtraction === true,
1273
- // Forward cwd/projectTag for auto git-context resolution (issue #569).
1274
- cwd: body.cwd,
1275
- projectTag: body.projectTag,
1276
- });
1277
- this.recordWriteRateLimitHit();
1260
+ const response = await this.service.observe(
1261
+ {
1262
+ sessionKey: body.sessionKey,
1263
+ messages: body.messages.map((message) => ({
1264
+ role: message.role,
1265
+ content: message.content,
1266
+ sourceFormat: message.sourceFormat ?? undefined,
1267
+ rawContent: message.rawContent ?? undefined,
1268
+ parts: message.parts ?? undefined,
1269
+ })),
1270
+ namespace: this.resolveNamespace(req, body.namespace),
1271
+ authenticatedPrincipal: this.resolveRequestPrincipal(req),
1272
+ skipExtraction: body.skipExtraction === true,
1273
+ // Issue #1649: optional server-side dedup key for retried POSTs.
1274
+ idempotencyKey: body.idempotencyKey,
1275
+ // Forward cwd/projectTag for auto git-context resolution (issue #569).
1276
+ cwd: body.cwd,
1277
+ projectTag: body.projectTag,
1278
+ },
1279
+ // Enforce the write-quota INSIDE the service's idempotency lock
1280
+ // (beforeExecute, only on a real miss). A retried observe that hits the
1281
+ // replay path must NOT be rejected with 429 — that is exactly the
1282
+ // response-lost-after-process case the dedup exists for. The original
1283
+ // attempt may have consumed the last quota slot; the retry returns the
1284
+ // cached response without requiring another. Matches memory_store's
1285
+ // hook-in-the-lock pattern (#1434 invariant).
1286
+ { enforceWriteQuota: () => this.ensureWriteRateLimitAvailable() },
1287
+ );
1288
+ // A replayed (deduplicated) observe must not consume a second write-quota
1289
+ // slot — same invariant as memory_store/suggestion_submit (#1434).
1290
+ if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
1291
+ this.recordWriteRateLimitHit();
1292
+ }
1278
1293
  this.respondJson(res, 202, response);
1279
1294
  return;
1280
1295
  }
@@ -2595,7 +2610,15 @@ export class EngramAccessHttpServer {
2595
2610
  codingArchitectureWrite ||
2596
2611
  codegraphWrite
2597
2612
  );
2598
- if (isMcpWrite) {
2613
+ // observe self-enforces quota INSIDE its idempotency lock via the
2614
+ // enforceWriteQuota hook (issue #1649) — mirroring the direct
2615
+ // /engram/v1/observe route — so it must NOT be pre-checked here: a
2616
+ // pre-check would 429 a response-lost replay. It still counts as a write
2617
+ // for post-recording (the replay guard in shouldCountWriteRateLimit skips
2618
+ // the record). Other write tools keep the coarse pre-check.
2619
+ const observeSelfEnforcesQuota =
2620
+ toolName === "engram.observe" || toolName === "remnic.observe";
2621
+ if (isMcpWrite && !observeSelfEnforcesQuota) {
2599
2622
  this.ensureWriteRateLimitAvailable();
2600
2623
  }
2601
2624
 
@@ -2611,6 +2634,9 @@ export class EngramAccessHttpServer {
2611
2634
  sessionKeyOverride: requestIdentity.sessionKey,
2612
2635
  sessionId,
2613
2636
  correlationId: mcpCorrelationId,
2637
+ enforceWriteQuota: observeSelfEnforcesQuota
2638
+ ? () => this.ensureWriteRateLimitAvailable()
2639
+ : undefined,
2614
2640
  });
2615
2641
 
2616
2642
  if (isMcpWrite && response !== null) {
@@ -477,6 +477,7 @@ test("MCP session override is injected only into tools that accept sessionKey",
477
477
  namespace: undefined,
478
478
  authenticatedPrincipal: undefined,
479
479
  skipExtraction: false,
480
+ idempotencyKey: undefined,
480
481
  cwd: undefined,
481
482
  projectTag: undefined,
482
483
  });
package/src/access-mcp.ts CHANGED
@@ -50,6 +50,12 @@ type McpRequestOptions = {
50
50
  sessionKeyOverride?: string;
51
51
  sessionId?: string;
52
52
  correlationId?: string;
53
+ /**
54
+ * Write-quota enforcer forwarded into observe's idempotency lock
55
+ * (issue #1649). Only the MCP-over-HTTP transport supplies this (it owns
56
+ * the write-rate-limit window); the standalone MCP server has no quota.
57
+ */
58
+ enforceWriteQuota?: () => void | Promise<void>;
53
59
  };
54
60
 
55
61
  type McpTool = {
@@ -1156,6 +1162,11 @@ export class EngramMcpServer {
1156
1162
  },
1157
1163
  description: "Conversation messages to observe",
1158
1164
  },
1165
+ idempotencyKey: {
1166
+ type: "string",
1167
+ description:
1168
+ "Optional idempotency key (issue #1649). Deduplicates a retried observe POST server-side so the batch is ingested once even when the HTTP response is lost. Reusing the key with a different payload is rejected.",
1169
+ },
1159
1170
  namespace: { type: "string" },
1160
1171
  skipExtraction: { type: "boolean" },
1161
1172
  cwd: { type: "string", description: "Working directory for auto git-context resolution." },
@@ -2374,7 +2385,7 @@ export class EngramMcpServer {
2374
2385
  argumentsObject = { ...argumentsObject, sessionKey: options.sessionKeyOverride };
2375
2386
  }
2376
2387
  const effectivePrincipal = options?.principalOverride ?? this.authenticatedPrincipal;
2377
- const result = await this.callTool(name, argumentsObject, effectivePrincipal, options?.sessionId);
2388
+ const result = await this.callTool(name, argumentsObject, effectivePrincipal, options?.sessionId, options?.enforceWriteQuota);
2378
2389
  return {
2379
2390
  jsonrpc: "2.0",
2380
2391
  id,
@@ -2562,7 +2573,7 @@ export class EngramMcpServer {
2562
2573
  }));
2563
2574
  }
2564
2575
 
2565
- private async callTool(name: string, args: Record<string, unknown>, effectivePrincipal?: string, mcpSessionId?: string): Promise<unknown> {
2576
+ private async callTool(name: string, args: Record<string, unknown>, effectivePrincipal?: string, mcpSessionId?: string, enforceWriteQuota?: () => void | Promise<void>): Promise<unknown> {
2566
2577
  // Migrated operations dispatch through the access boundary (issue #1525):
2567
2578
  // one registry entry owns schema validation, normalization (rules
2568
2579
  // 17/28/36/48/51), and error mapping for every surface. The switch
@@ -3104,21 +3115,27 @@ export class EngramMcpServer {
3104
3115
  );
3105
3116
  case "engram.observe": {
3106
3117
  const body = parseMcpRequest("observe", args);
3107
- return this.service.observe({
3108
- sessionKey: body.sessionKey,
3109
- messages: body.messages.map((message) => ({
3110
- role: message.role,
3111
- content: message.content,
3112
- parts: message.parts ?? undefined,
3113
- rawContent: message.rawContent ?? undefined,
3114
- sourceFormat: message.sourceFormat ?? undefined,
3115
- })),
3116
- namespace: body.namespace,
3117
- authenticatedPrincipal: effectivePrincipal,
3118
- skipExtraction: body.skipExtraction === true,
3119
- cwd: body.cwd,
3120
- projectTag: body.projectTag,
3121
- });
3118
+ return this.service.observe(
3119
+ {
3120
+ sessionKey: body.sessionKey,
3121
+ messages: body.messages.map((message) => ({
3122
+ role: message.role,
3123
+ content: message.content,
3124
+ parts: message.parts ?? undefined,
3125
+ rawContent: message.rawContent ?? undefined,
3126
+ sourceFormat: message.sourceFormat ?? undefined,
3127
+ })),
3128
+ skipExtraction: body.skipExtraction === true,
3129
+ idempotencyKey: body.idempotencyKey,
3130
+ namespace: body.namespace,
3131
+ authenticatedPrincipal: effectivePrincipal,
3132
+ cwd: body.cwd,
3133
+ projectTag: body.projectTag,
3134
+ },
3135
+ // Forward the transport's write-quota enforcer into observe's
3136
+ // idempotency lock so a replay is never 429'd (issue #1649).
3137
+ enforceWriteQuota ? { enforceWriteQuota } : undefined,
3138
+ );
3122
3139
  }
3123
3140
  case "engram.lcm_search":
3124
3141
  return this.service.lcmSearch({
@@ -216,6 +216,14 @@ export const observeRequestSchema = z.object({
216
216
  messages: z.array(messageSchema).min(1, "messages must be a non-empty array"),
217
217
  namespace: namespaceSchema,
218
218
  skipExtraction: z.boolean().optional(),
219
+ /**
220
+ * Optional idempotency key for server-side dedup of retried observe POSTs
221
+ * (issue #1649). A retry that reaches the daemon after the first attempt
222
+ * already processed is replayed from cache instead of re-ingested. When the
223
+ * key is reused with a DIFFERENT payload the request is rejected as a
224
+ * conflict (same contract as memory_store/suggestion_submit).
225
+ */
226
+ idempotencyKey: idempotencyKeySchema,
219
227
  /** Working directory for auto git-context resolution (issue #569). */
220
228
  cwd: z.string().trim().min(1, "cwd must be non-empty when provided").max(2048).optional(),
221
229
  /**
@@ -0,0 +1,308 @@
1
+ /**
2
+ * #1649: a retried observe POST (same `idempotencyKey`) must be deduplicated
3
+ * server-side — the batch is ingested exactly once even when the HTTP response
4
+ * is lost and the client replays the request. Without the key the daemon
5
+ * re-runs every side effect under `skipDedupeCheck: true` and the turn is
6
+ * queued for extraction twice.
7
+ *
8
+ * Verified here against the `EngramAccessService.observe` path with a stub
9
+ * orchestrator that records every namespace-bearing side effect (LCM enqueue +
10
+ * extraction replay), the same probe shape used by the #1495 observe-scope
11
+ * tests. The idempotency store is filesystem-backed, so `memoryDir` is a real
12
+ * temp directory (not the synthetic path the scope tests use, which never touch
13
+ * the store because they omit the key).
14
+ */
15
+ import assert from "node:assert/strict";
16
+ import { mkdtempSync, rmSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import test from "node:test";
20
+
21
+ import { EngramAccessService } from "./access-service.js";
22
+ import { EngramAccessInputError } from "./access-service.js";
23
+ import { Orchestrator } from "./orchestrator.js";
24
+ import type { EngramAccessObserveRequest } from "./access-service.js";
25
+ import type { CodingContext, PluginConfig } from "./types.js";
26
+
27
+ interface ObserveProbe {
28
+ orch: Orchestrator;
29
+ contexts: Map<string, CodingContext>;
30
+ lcmCalls: Array<{ sessionKey: string }>;
31
+ extractionCalls: Array<{
32
+ sessionKeys: string[];
33
+ writeNamespaceOverride?: string;
34
+ principalOverride?: string;
35
+ }>;
36
+ }
37
+
38
+ function makeObserveProbe(memoryDir: string): ObserveProbe {
39
+ const contexts = new Map<string, CodingContext>();
40
+ const lcmCalls: ObserveProbe["lcmCalls"] = [];
41
+ const extractionCalls: ObserveProbe["extractionCalls"] = [];
42
+
43
+ const config = {
44
+ namespacesEnabled: true,
45
+ defaultNamespace: "default",
46
+ sharedNamespace: "shared",
47
+ namespacePolicies: [
48
+ { name: "pi-geek", readPrincipals: ["pi-geek"], writePrincipals: ["pi-geek"] },
49
+ ],
50
+ codingMode: { projectScope: true },
51
+ memoryDir,
52
+ // Disable objective-state snapshots so the probe doesn't need a writable
53
+ // storage backend — the idempotency contract under test is LCM + extraction.
54
+ objectiveStateMemoryEnabled: false,
55
+ objectiveStateSnapshotWritesEnabled: false,
56
+ principalFromSessionKeyMode: "prefix",
57
+ principalFromSessionKeyRules: [{ match: "pi-geek:", principal: "pi-geek" }],
58
+ recallCrossNamespaceBudgetEnabled: false,
59
+ recallCrossNamespaceBudgetWindowMs: 60_000,
60
+ recallCrossNamespaceBudgetSoftLimit: 10,
61
+ recallCrossNamespaceBudgetHardLimit: 30,
62
+ } as unknown as PluginConfig;
63
+
64
+ const orch = {
65
+ config,
66
+ getCodingContextForSession: (sk: string | undefined) =>
67
+ (sk ? contexts.get(sk) : null) ?? null,
68
+ setCodingContextForSession: (sk: string, ctx: CodingContext | null) => {
69
+ if (ctx === null) contexts.delete(sk);
70
+ else contexts.set(sk, ctx);
71
+ },
72
+ applyCodingNamespaceOverlay: (sk: string | undefined, base: string) =>
73
+ Orchestrator.prototype.applyCodingNamespaceOverlay.call(orch, sk, base),
74
+ getStorage: async (ns: string) => ({ dir: join(memoryDir, "storage", ns) }),
75
+ lcmEngine: {
76
+ enabled: true,
77
+ enqueueObserveMessages: (sessionKey: string) => {
78
+ lcmCalls.push({ sessionKey });
79
+ },
80
+ },
81
+ ingestReplayBatch: async (
82
+ turns: Array<{ sessionKey: string }>,
83
+ options: { writeNamespaceOverride?: string; principalOverride?: string } = {},
84
+ ) => {
85
+ extractionCalls.push({
86
+ sessionKeys: turns.map((t) => t.sessionKey),
87
+ writeNamespaceOverride: options.writeNamespaceOverride,
88
+ principalOverride: options.principalOverride,
89
+ });
90
+ },
91
+ } as unknown as Orchestrator;
92
+
93
+ return { orch, contexts, lcmCalls, extractionCalls };
94
+ }
95
+
96
+ function observeRequest(
97
+ overrides: Partial<EngramAccessObserveRequest>,
98
+ ): EngramAccessObserveRequest {
99
+ return {
100
+ sessionKey: "pi-geek:abc123",
101
+ messages: [
102
+ { role: "user", content: "what database are we using?" },
103
+ { role: "assistant", content: "we use postgres for the primary store" },
104
+ ],
105
+ ...overrides,
106
+ } as EngramAccessObserveRequest;
107
+ }
108
+
109
+ test("#1649 a retried observe with the same idempotencyKey is deduplicated to a single ingest", async () => {
110
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-idem-"));
111
+ try {
112
+ const probe = makeObserveProbe(memoryDir);
113
+ const service = new EngramAccessService(probe.orch);
114
+ const key = "observe-batch-pi-geek-abc123-#1";
115
+
116
+ const first = await service.observe(observeRequest({ idempotencyKey: key }));
117
+ // First attempt is a real ingest.
118
+ assert.equal(first.idempotencyReplay, undefined, "first attempt is not a replay");
119
+ assert.equal(probe.extractionCalls.length, 1, "first attempt ingests once");
120
+ assert.equal(probe.lcmCalls.length, 1, "first attempt archives LCM once");
121
+
122
+ // Retry: same key, same payload — the daemon already processed the body, so
123
+ // this is the response-lost-after-process case the issue describes.
124
+ const second = await service.observe(observeRequest({ idempotencyKey: key }));
125
+ assert.equal(second.idempotencyReplay, true, "retry is served from the cache");
126
+ assert.equal(second.accepted, first.accepted, "retry reports the same accepted count");
127
+ assert.equal(
128
+ second.effectiveNamespace,
129
+ first.effectiveNamespace,
130
+ "retry reports the same effective namespace",
131
+ );
132
+
133
+ // The defining contract: NO second ingest, NO second LCM archive.
134
+ assert.equal(probe.extractionCalls.length, 1, "retry must not queue extraction a second time");
135
+ assert.equal(probe.lcmCalls.length, 1, "retry must not archive LCM a second time");
136
+ } finally {
137
+ rmSync(memoryDir, { recursive: true, force: true });
138
+ }
139
+ });
140
+
141
+ test("#1649 observe without an idempotencyKey is never deduplicated (backward compatible)", async () => {
142
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-nokey-"));
143
+ try {
144
+ const probe = makeObserveProbe(memoryDir);
145
+ const service = new EngramAccessService(probe.orch);
146
+
147
+ await service.observe(observeRequest({}));
148
+ await service.observe(observeRequest({}));
149
+
150
+ assert.equal(probe.extractionCalls.length, 2, "no key ⇒ both calls ingest");
151
+ assert.equal(probe.lcmCalls.length, 2, "no key ⇒ both calls archive LCM");
152
+ } finally {
153
+ rmSync(memoryDir, { recursive: true, force: true });
154
+ }
155
+ });
156
+
157
+ test("#1649 reusing an idempotencyKey with a divergent payload is rejected as a conflict", async () => {
158
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-conflict-"));
159
+ try {
160
+ const probe = makeObserveProbe(memoryDir);
161
+ const service = new EngramAccessService(probe.orch);
162
+ const key = "observe-batch-pi-geek-abc123-#2";
163
+
164
+ await service.observe(observeRequest({ idempotencyKey: key }));
165
+
166
+ // Same key, DIFFERENT messages — a stale key must never silently mask a
167
+ // unrelated batch. Same contract as memory_store/suggestion_submit.
168
+ await assert.rejects(
169
+ service.observe(
170
+ observeRequest({
171
+ idempotencyKey: key,
172
+ messages: [
173
+ { role: "user", content: "completely different question" },
174
+ { role: "assistant", content: "completely different answer" },
175
+ ],
176
+ }),
177
+ ),
178
+ (err: unknown) =>
179
+ err instanceof EngramAccessInputError &&
180
+ /idempotencyKey reuse conflict/.test(err.message),
181
+ "divergent payload under a reused key must throw a conflict",
182
+ );
183
+ } finally {
184
+ rmSync(memoryDir, { recursive: true, force: true });
185
+ }
186
+ });
187
+
188
+ test("#1649 review fix: enforceWriteQuota runs only on a cache miss, never on a replay", async () => {
189
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-quota-hook-"));
190
+ try {
191
+ const probe = makeObserveProbe(memoryDir);
192
+ const service = new EngramAccessService(probe.orch);
193
+ const key = "observe-batch-pi-geek-abc123-#3";
194
+ let quotaChecks = 0;
195
+ const enforceWriteQuota = () => {
196
+ quotaChecks += 1;
197
+ };
198
+
199
+ await service.observe(observeRequest({ idempotencyKey: key }), { enforceWriteQuota });
200
+ assert.equal(quotaChecks, 1, "quota is enforced exactly once on the real ingest");
201
+
202
+ const replay = await service.observe(observeRequest({ idempotencyKey: key }), { enforceWriteQuota });
203
+ assert.equal(replay.idempotencyReplay, true, "second call is a replay");
204
+ assert.equal(quotaChecks, 1, "quota is NOT re-checked on a replay (response-lost retry must not 429)");
205
+ assert.equal(probe.extractionCalls.length, 1, "replay did not re-ingest");
206
+ } finally {
207
+ rmSync(memoryDir, { recursive: true, force: true });
208
+ }
209
+ });
210
+
211
+ test("#1649 review fix: the same idempotencyKey under a different principal is a conflict, not a silent replay", async () => {
212
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-principal-conflict-"));
213
+ try {
214
+ const probe = makeObserveProbe(memoryDir);
215
+ const service = new EngramAccessService(probe.orch);
216
+ const key = "observe-batch-shared-key-#4";
217
+
218
+ // Principal alice ingests under the key.
219
+ await service.observe(
220
+ observeRequest({ idempotencyKey: key, authenticatedPrincipal: "alice" }),
221
+ );
222
+
223
+ // A different principal reusing the same key + same body must NOT replay
224
+ // alice's cached response — the fingerprint folds in authenticatedPrincipal,
225
+ // so this is a conflict (defends against cross-identity replay when the
226
+ // principal is supplied out-of-band via HTTP/MCP auth).
227
+ await assert.rejects(
228
+ service.observe(
229
+ observeRequest({ idempotencyKey: key, authenticatedPrincipal: "bob" }),
230
+ ),
231
+ (err: unknown) =>
232
+ err instanceof EngramAccessInputError &&
233
+ /idempotencyKey reuse conflict/.test(err.message),
234
+ "cross-principal key reuse must throw a conflict",
235
+ );
236
+ } finally {
237
+ rmSync(memoryDir, { recursive: true, force: true });
238
+ }
239
+ });
240
+
241
+ test("#1649 review fix: rebinding the session's ambient coding context makes the same key a conflict", async () => {
242
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-ambient-scope-"));
243
+ try {
244
+ const probe = makeObserveProbe(memoryDir);
245
+ const service = new EngramAccessService(probe.orch);
246
+ const key = "observe-batch-ambient-scope-#5";
247
+ const req = observeRequest({ idempotencyKey: key });
248
+
249
+ // No explicit namespace/cwd/projectTag and no ambient context yet — the
250
+ // scope resolves to the default store. This caches the response.
251
+ await service.observe(req);
252
+ assert.equal(probe.extractionCalls.length, 1, "first observe ingests once");
253
+
254
+ // The session is now rebound to a coding project by an external caller
255
+ // (e.g. a memory_store with cwd, or a direct setCodingContextForSession).
256
+ // The resolved writeNamespace changed, so reusing the same key + same body
257
+ // must NOT silently replay — it is a conflict.
258
+ probe.contexts.set(req.sessionKey, {
259
+ projectId: "tag:remnic",
260
+ branch: null,
261
+ rootPath: "/projects/remnic",
262
+ defaultBranch: null,
263
+ });
264
+
265
+ await assert.rejects(
266
+ service.observe(req),
267
+ (err: unknown) =>
268
+ err instanceof EngramAccessInputError &&
269
+ /idempotencyKey reuse conflict/.test(err.message),
270
+ "key reuse after ambient scope rebind must throw a conflict, not silently replay",
271
+ );
272
+ } finally {
273
+ rmSync(memoryDir, { recursive: true, force: true });
274
+ }
275
+ });
276
+
277
+ test("#1649 review fix: projectTag observe replays correctly despite scope resolution side effects", async () => {
278
+ // The fingerprint folds in the RESOLVED writeNamespace (not the raw ambient
279
+ // context). resolveMemoryScopePlan seeds the session context from projectTag
280
+ // on the first call; the replay reads the SAME seeded context and resolves the
281
+ // SAME writeNamespace. This proves the fingerprint is stable across replays
282
+ // even though the scope resolution mutates session state on the first call.
283
+ const memoryDir = mkdtempSync(join(tmpdir(), "remnic-observe-tag-replay-"));
284
+ try {
285
+ const probe = makeObserveProbe(memoryDir);
286
+ const service = new EngramAccessService(probe.orch);
287
+ const key = "observe-batch-tag-replay-#6";
288
+ const req = observeRequest({ idempotencyKey: key, projectTag: "remnic" });
289
+
290
+ const first = await service.observe(req);
291
+ assert.equal(first.idempotencyReplay, undefined, "first attempt is a real ingest");
292
+
293
+ // The first observe's resolveMemoryScopePlan seeded a coding context for
294
+ // this session. Verify it was actually set (the side effect we depend on).
295
+ assert.ok(
296
+ probe.contexts.get(req.sessionKey),
297
+ "first observe seeded a coding context via resolveMemoryScopePlan",
298
+ );
299
+
300
+ // Retry with the same key + same body + same projectTag — the pre-resolved
301
+ // writeNamespace matches because the seeded context produces the same scope.
302
+ const replay = await service.observe(req);
303
+ assert.equal(replay.idempotencyReplay, true, "projectTag observe replays despite scope-resolution side effects");
304
+ assert.equal(probe.extractionCalls.length, 1, "replay did not re-ingest");
305
+ } finally {
306
+ rmSync(memoryDir, { recursive: true, force: true });
307
+ }
308
+ });