@fgv/ts-agent-memory 5.1.0-37 → 5.1.0-39

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 (197) hide show
  1. package/.rush/temp/{6dfc331ddf74dc2db0d4483d0c96ea971f351c83.tar.log → b82cf6bdece20481260e6bab946179eeec9d7b46.tar.log} +116 -2
  2. package/.rush/temp/chunked-rush-logs/ts-agent-memory.build.chunks.jsonl +4 -4
  3. package/.rush/temp/operation/build/all.log +4 -4
  4. package/.rush/temp/operation/build/log-chunks.jsonl +4 -4
  5. package/.rush/temp/operation/build/state.json +1 -1
  6. package/.rush/temp/shrinkwrap-deps.json +222 -221
  7. package/config/jest.config.json +1 -1
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/packlets/ingest/cycleGuard.js +111 -0
  11. package/dist/packlets/ingest/cycleGuard.js.map +1 -0
  12. package/dist/packlets/ingest/hostStages.js +6 -0
  13. package/dist/packlets/ingest/hostStages.js.map +1 -0
  14. package/dist/packlets/ingest/index.js +9 -0
  15. package/dist/packlets/ingest/index.js.map +1 -0
  16. package/dist/packlets/ingest/model.js +6 -0
  17. package/dist/packlets/ingest/model.js.map +1 -0
  18. package/dist/packlets/ingest/orchestrator.js +438 -0
  19. package/dist/packlets/ingest/orchestrator.js.map +1 -0
  20. package/dist/packlets/retrieve/index.js +1 -0
  21. package/dist/packlets/retrieve/index.js.map +1 -1
  22. package/dist/packlets/retrieve/temporalRetrievers.js +172 -0
  23. package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -0
  24. package/dist/packlets/store/fileTreeMemoryStore.js +323 -41
  25. package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
  26. package/dist/packlets/tools/index.js +6 -0
  27. package/dist/packlets/tools/index.js.map +1 -0
  28. package/dist/packlets/tools/memoryTools.js +337 -0
  29. package/dist/packlets/tools/memoryTools.js.map +1 -0
  30. package/dist/packlets/types/identityCodec.js +115 -0
  31. package/dist/packlets/types/identityCodec.js.map +1 -1
  32. package/dist/packlets/types/index.js +1 -0
  33. package/dist/packlets/types/index.js.map +1 -1
  34. package/dist/packlets/types/temporal.js +85 -0
  35. package/dist/packlets/types/temporal.js.map +1 -0
  36. package/dist/packlets/types/writePolicy.js +96 -0
  37. package/dist/packlets/types/writePolicy.js.map +1 -1
  38. package/dist/test/unit/converters/antagonistRoundTrip.test.js +95 -0
  39. package/dist/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
  40. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js +273 -0
  41. package/dist/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
  42. package/dist/test/unit/ingest/cycleGuard.test.js +54 -0
  43. package/dist/test/unit/ingest/cycleGuard.test.js.map +1 -0
  44. package/dist/test/unit/ingest/orchestrator.test.js +913 -0
  45. package/dist/test/unit/ingest/orchestrator.test.js.map +1 -0
  46. package/dist/test/unit/retrieve/temporalRetrievers.test.js +182 -0
  47. package/dist/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
  48. package/dist/test/unit/store/antagonistTemporalBoundary.test.js +120 -0
  49. package/dist/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
  50. package/dist/test/unit/store/fileTreeMemoryStore.test.js +74 -7
  51. package/dist/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  52. package/dist/test/unit/store/temporalStore.test.js +398 -0
  53. package/dist/test/unit/store/temporalStore.test.js.map +1 -0
  54. package/dist/test/unit/tools/memoryTools.test.js +572 -0
  55. package/dist/test/unit/tools/memoryTools.test.js.map +1 -0
  56. package/dist/test/unit/types/temporalCodec.test.js +203 -0
  57. package/dist/test/unit/types/temporalCodec.test.js.map +1 -0
  58. package/dist/test/unit/types/temporalPolicy.test.js +62 -0
  59. package/dist/test/unit/types/temporalPolicy.test.js.map +1 -0
  60. package/dist/ts-agent-memory.d.ts +1042 -10
  61. package/dist/tsdoc-metadata.json +1 -1
  62. package/etc/ts-agent-memory.api.md +284 -0
  63. package/lib/index.d.ts +2 -0
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js +2 -0
  66. package/lib/index.js.map +1 -1
  67. package/lib/packlets/ingest/cycleGuard.d.ts +41 -0
  68. package/lib/packlets/ingest/cycleGuard.d.ts.map +1 -0
  69. package/lib/packlets/ingest/cycleGuard.js +115 -0
  70. package/lib/packlets/ingest/cycleGuard.js.map +1 -0
  71. package/lib/packlets/ingest/hostStages.d.ts +88 -0
  72. package/lib/packlets/ingest/hostStages.d.ts.map +1 -0
  73. package/lib/packlets/ingest/hostStages.js +7 -0
  74. package/lib/packlets/ingest/hostStages.js.map +1 -0
  75. package/lib/packlets/ingest/index.d.ts +5 -0
  76. package/lib/packlets/ingest/index.d.ts.map +1 -0
  77. package/lib/packlets/ingest/index.js +25 -0
  78. package/lib/packlets/ingest/index.js.map +1 -0
  79. package/lib/packlets/ingest/model.d.ts +177 -0
  80. package/lib/packlets/ingest/model.d.ts.map +1 -0
  81. package/lib/packlets/ingest/model.js +7 -0
  82. package/lib/packlets/ingest/model.js.map +1 -0
  83. package/lib/packlets/ingest/orchestrator.d.ts +206 -0
  84. package/lib/packlets/ingest/orchestrator.d.ts.map +1 -0
  85. package/lib/packlets/ingest/orchestrator.js +442 -0
  86. package/lib/packlets/ingest/orchestrator.js.map +1 -0
  87. package/lib/packlets/retrieve/index.d.ts +1 -0
  88. package/lib/packlets/retrieve/index.d.ts.map +1 -1
  89. package/lib/packlets/retrieve/index.js +1 -0
  90. package/lib/packlets/retrieve/index.js.map +1 -1
  91. package/lib/packlets/retrieve/temporalRetrievers.d.ts +78 -0
  92. package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -0
  93. package/lib/packlets/retrieve/temporalRetrievers.js +178 -0
  94. package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -0
  95. package/lib/packlets/store/fileTreeMemoryStore.d.ts +118 -9
  96. package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
  97. package/lib/packlets/store/fileTreeMemoryStore.js +322 -40
  98. package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
  99. package/lib/packlets/tools/index.d.ts +2 -0
  100. package/lib/packlets/tools/index.d.ts.map +1 -0
  101. package/lib/packlets/tools/index.js +22 -0
  102. package/lib/packlets/tools/index.js.map +1 -0
  103. package/lib/packlets/tools/memoryTools.d.ts +139 -0
  104. package/lib/packlets/tools/memoryTools.d.ts.map +1 -0
  105. package/lib/packlets/tools/memoryTools.js +341 -0
  106. package/lib/packlets/tools/memoryTools.js.map +1 -0
  107. package/lib/packlets/types/identityCodec.d.ts +86 -0
  108. package/lib/packlets/types/identityCodec.d.ts.map +1 -1
  109. package/lib/packlets/types/identityCodec.js +118 -1
  110. package/lib/packlets/types/identityCodec.js.map +1 -1
  111. package/lib/packlets/types/index.d.ts +1 -0
  112. package/lib/packlets/types/index.d.ts.map +1 -1
  113. package/lib/packlets/types/index.js +1 -0
  114. package/lib/packlets/types/index.js.map +1 -1
  115. package/lib/packlets/types/temporal.d.ts +40 -0
  116. package/lib/packlets/types/temporal.d.ts.map +1 -0
  117. package/lib/packlets/types/temporal.js +92 -0
  118. package/lib/packlets/types/temporal.js.map +1 -0
  119. package/lib/packlets/types/writePolicy.d.ts +49 -0
  120. package/lib/packlets/types/writePolicy.d.ts.map +1 -1
  121. package/lib/packlets/types/writePolicy.js +98 -1
  122. package/lib/packlets/types/writePolicy.js.map +1 -1
  123. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts +10 -0
  124. package/lib/test/unit/converters/antagonistRoundTrip.test.d.ts.map +1 -0
  125. package/lib/test/unit/converters/antagonistRoundTrip.test.js +97 -0
  126. package/lib/test/unit/converters/antagonistRoundTrip.test.js.map +1 -0
  127. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts +9 -0
  128. package/lib/test/unit/ingest/antagonistCycleAndParity.test.d.ts.map +1 -0
  129. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js +275 -0
  130. package/lib/test/unit/ingest/antagonistCycleAndParity.test.js.map +1 -0
  131. package/lib/test/unit/ingest/cycleGuard.test.d.ts +2 -0
  132. package/lib/test/unit/ingest/cycleGuard.test.d.ts.map +1 -0
  133. package/lib/test/unit/ingest/cycleGuard.test.js +56 -0
  134. package/lib/test/unit/ingest/cycleGuard.test.js.map +1 -0
  135. package/lib/test/unit/ingest/orchestrator.test.d.ts +2 -0
  136. package/lib/test/unit/ingest/orchestrator.test.d.ts.map +1 -0
  137. package/lib/test/unit/ingest/orchestrator.test.js +915 -0
  138. package/lib/test/unit/ingest/orchestrator.test.js.map +1 -0
  139. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts +2 -0
  140. package/lib/test/unit/retrieve/temporalRetrievers.test.d.ts.map +1 -0
  141. package/lib/test/unit/retrieve/temporalRetrievers.test.js +184 -0
  142. package/lib/test/unit/retrieve/temporalRetrievers.test.js.map +1 -0
  143. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts +9 -0
  144. package/lib/test/unit/store/antagonistTemporalBoundary.test.d.ts.map +1 -0
  145. package/lib/test/unit/store/antagonistTemporalBoundary.test.js +122 -0
  146. package/lib/test/unit/store/antagonistTemporalBoundary.test.js.map +1 -0
  147. package/lib/test/unit/store/fileTreeMemoryStore.test.js +74 -7
  148. package/lib/test/unit/store/fileTreeMemoryStore.test.js.map +1 -1
  149. package/lib/test/unit/store/temporalStore.test.d.ts +2 -0
  150. package/lib/test/unit/store/temporalStore.test.d.ts.map +1 -0
  151. package/lib/test/unit/store/temporalStore.test.js +400 -0
  152. package/lib/test/unit/store/temporalStore.test.js.map +1 -0
  153. package/lib/test/unit/tools/memoryTools.test.d.ts +2 -0
  154. package/lib/test/unit/tools/memoryTools.test.d.ts.map +1 -0
  155. package/lib/test/unit/tools/memoryTools.test.js +574 -0
  156. package/lib/test/unit/tools/memoryTools.test.js.map +1 -0
  157. package/lib/test/unit/types/temporalCodec.test.d.ts +2 -0
  158. package/lib/test/unit/types/temporalCodec.test.d.ts.map +1 -0
  159. package/lib/test/unit/types/temporalCodec.test.js +205 -0
  160. package/lib/test/unit/types/temporalCodec.test.js.map +1 -0
  161. package/lib/test/unit/types/temporalPolicy.test.d.ts +2 -0
  162. package/lib/test/unit/types/temporalPolicy.test.d.ts.map +1 -0
  163. package/lib/test/unit/types/temporalPolicy.test.js +64 -0
  164. package/lib/test/unit/types/temporalPolicy.test.js.map +1 -0
  165. package/package.json +7 -7
  166. package/rush-logs/ts-agent-memory.build.cache.log +1 -1
  167. package/rush-logs/ts-agent-memory.build.log +4 -4
  168. package/src/index.ts +2 -0
  169. package/src/packlets/ingest/cycleGuard.ts +142 -0
  170. package/src/packlets/ingest/hostStages.ts +111 -0
  171. package/src/packlets/ingest/index.ts +9 -0
  172. package/src/packlets/ingest/model.ts +184 -0
  173. package/src/packlets/ingest/orchestrator.ts +797 -0
  174. package/src/packlets/retrieve/index.ts +1 -0
  175. package/src/packlets/retrieve/temporalRetrievers.ts +210 -0
  176. package/src/packlets/store/fileTreeMemoryStore.ts +460 -66
  177. package/src/packlets/tools/index.ts +6 -0
  178. package/src/packlets/tools/memoryTools.ts +579 -0
  179. package/src/packlets/types/identityCodec.ts +184 -0
  180. package/src/packlets/types/index.ts +1 -0
  181. package/src/packlets/types/temporal.ts +96 -0
  182. package/src/packlets/types/writePolicy.ts +127 -0
  183. package/src/test/unit/converters/antagonistRoundTrip.test.ts +110 -0
  184. package/src/test/unit/ingest/antagonistCycleAndParity.test.ts +362 -0
  185. package/src/test/unit/ingest/cycleGuard.test.ts +68 -0
  186. package/src/test/unit/ingest/orchestrator.test.ts +1158 -0
  187. package/src/test/unit/retrieve/temporalRetrievers.test.ts +226 -0
  188. package/src/test/unit/store/antagonistTemporalBoundary.test.ts +158 -0
  189. package/src/test/unit/store/fileTreeMemoryStore.test.ts +98 -7
  190. package/src/test/unit/store/temporalStore.test.ts +469 -0
  191. package/src/test/unit/tools/memoryTools.test.ts +771 -0
  192. package/src/test/unit/types/temporalCodec.test.ts +259 -0
  193. package/src/test/unit/types/temporalPolicy.test.ts +96 -0
  194. package/temp/build/lint/_eslint-5eVG3S6w.json +85 -9
  195. package/temp/build/typescript/ts_8nwakTlr.json +1 -1
  196. package/temp/ts-agent-memory.api.json +11984 -6314
  197. package/temp/ts-agent-memory.api.md +284 -0
@@ -0,0 +1,259 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import '@fgv/ts-utils-jest';
7
+ import {
8
+ EntityId,
9
+ IIdentityCodec,
10
+ IMemoryRecord,
11
+ KnowledgeIdentityCodec,
12
+ MemoryScopeKey,
13
+ TemporalIdentityCodec,
14
+ isTemporalIdentityCodec,
15
+ isTemporalRecord,
16
+ isVersionCurrent,
17
+ isVersionValidAt,
18
+ selectCurrentVersion,
19
+ selectVersionAsOf
20
+ } from '../../../packlets/types';
21
+ import { envelopeConverter } from '../../../packlets/converters';
22
+
23
+ const codec: TemporalIdentityCodec = TemporalIdentityCodec.create('facts').orThrow();
24
+
25
+ describe('TemporalIdentityCodec', () => {
26
+ describe('create', () => {
27
+ test('rejects a non-portable base scope', () => {
28
+ expect(TemporalIdentityCodec.create('has/slash')).toFailWith(/baseScope/i);
29
+ });
30
+ });
31
+
32
+ describe('encode', () => {
33
+ test('maps an entityId to the entity subtree, versioned', () => {
34
+ expect(codec.encode('fact-1' as EntityId)).toSucceedWith({
35
+ scope: 'facts/entities/fact-1' as MemoryScopeKey,
36
+ idStem: 'fact-1',
37
+ isVersioned: true
38
+ });
39
+ });
40
+
41
+ test('rejects a non-portable entityId', () => {
42
+ expect(codec.encode('bad/id' as EntityId)).toFailWith(/entityId/i);
43
+ });
44
+ });
45
+
46
+ describe('encodeVersion', () => {
47
+ test('forms the <entityId>-v<seq> stem', () => {
48
+ expect(codec.encodeVersion('fact-1' as EntityId, 7)).toSucceedWith('fact-1-v7');
49
+ });
50
+
51
+ test('rejects a negative or non-integer seq', () => {
52
+ expect(codec.encodeVersion('fact-1' as EntityId, -1)).toFailWith(/non-negative integer/i);
53
+ expect(codec.encodeVersion('fact-1' as EntityId, 1.5)).toFailWith(/non-negative integer/i);
54
+ });
55
+
56
+ test('rejects a non-portable entityId', () => {
57
+ expect(codec.encodeVersion('bad/id' as EntityId, 1)).toFailWith(/entityId/i);
58
+ });
59
+ });
60
+
61
+ describe('decode / decodeVersion', () => {
62
+ test('recovers the entityId and seq from a (scope, version stem)', () => {
63
+ expect(codec.decodeVersion('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-v3')).toSucceedWith({
64
+ entityId: 'fact-1' as EntityId,
65
+ seq: 3
66
+ });
67
+ expect(codec.decode('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-v3')).toSucceedWith(
68
+ 'fact-1' as EntityId
69
+ );
70
+ });
71
+
72
+ test('disambiguates an entityId that itself ends in -v<digits> via the scope', () => {
73
+ // The subtree scope is authoritative for the entityId, so a stem whose
74
+ // entityId is `rev-v2` parses correctly.
75
+ expect(codec.decodeVersion('facts/entities/rev-v2' as MemoryScopeKey, 'rev-v2-v5')).toSucceedWith({
76
+ entityId: 'rev-v2' as EntityId,
77
+ seq: 5
78
+ });
79
+ });
80
+
81
+ test('rejects a scope that is not <base>/entities/<entityId>', () => {
82
+ expect(codec.decodeVersion('facts/fact-1' as MemoryScopeKey, 'fact-1-v0')).toFailWith(
83
+ /must be 'facts\/entities\/<entityId>'/i
84
+ );
85
+ expect(codec.decodeVersion('other/entities/fact-1' as MemoryScopeKey, 'fact-1-v0')).toFailWith(
86
+ /must be 'facts\/entities\/<entityId>'/i
87
+ );
88
+ });
89
+
90
+ test('rejects a non-portable entityId embedded in the scope', () => {
91
+ expect(codec.decodeVersion('facts/entities/ ' as MemoryScopeKey, ' -v0')).toFailWith(/entityId/i);
92
+ });
93
+
94
+ test('rejects a stem that does not carry the entity prefix', () => {
95
+ expect(codec.decodeVersion('facts/entities/fact-1' as MemoryScopeKey, 'other-v0')).toFailWith(
96
+ /must begin with 'fact-1-v'/i
97
+ );
98
+ });
99
+
100
+ test('rejects a stem with a non-integer version suffix', () => {
101
+ expect(codec.decodeVersion('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-vX')).toFailWith(
102
+ /non-integer version suffix/i
103
+ );
104
+ });
105
+
106
+ test('rejects a version suffix outside the safe integer range (corruption/tamper guard)', () => {
107
+ // A very long digit run passes the regex but overflows Number.MAX_SAFE_INTEGER;
108
+ // parseInt would silently lose precision, so decode must fail rather than corrupt seq.
109
+ expect(
110
+ codec.decodeVersion('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-v99999999999999999999')
111
+ ).toFailWith(/outside the safe integer range/i);
112
+ });
113
+ });
114
+
115
+ describe('verifyRoundTrip', () => {
116
+ test('succeeds for a well-formed (scope, version stem)', () => {
117
+ expect(codec.verifyRoundTrip('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-v9')).toSucceedWith(
118
+ true
119
+ );
120
+ });
121
+
122
+ test('fails when the stem cannot be decoded', () => {
123
+ expect(codec.verifyRoundTrip('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-vZ')).toFailWith(
124
+ /non-integer version suffix/i
125
+ );
126
+ });
127
+
128
+ test('fails on a non-canonical (zero-padded) seq stem', () => {
129
+ // `-v007` decodes to seq 7 but re-encodes to `-v7`, so it is not canonical.
130
+ expect(codec.verifyRoundTrip('facts/entities/fact-1' as MemoryScopeKey, 'fact-1-v007')).toFailWith(
131
+ /round-trip mismatch/i
132
+ );
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('isTemporalIdentityCodec', () => {
138
+ test('is true for a temporal codec', () => {
139
+ expect(isTemporalIdentityCodec(codec)).toBe(true);
140
+ });
141
+
142
+ test('is false for a flat codec', () => {
143
+ const flat: IIdentityCodec = new KnowledgeIdentityCodec();
144
+ expect(isTemporalIdentityCodec(flat)).toBe(false);
145
+ });
146
+ });
147
+
148
+ /** Build a temporal version record for the selection-helper tests. */
149
+ // eslint-disable-next-line @rushstack/no-new-null -- invalid_at null is the meaningful still-valid sentinel
150
+ function versionRecord(seq: number, validAt: number, invalidAt?: number | null): IMemoryRecord<unknown> {
151
+ const temporal: Record<string, unknown> = { valid_at: validAt };
152
+ if (invalidAt !== undefined) {
153
+ temporal.invalid_at = invalidAt;
154
+ }
155
+ return {
156
+ envelope: envelopeConverter
157
+ .convert({
158
+ id: `fact-1-v${seq}`,
159
+ entityId: 'fact-1',
160
+ kind: 'fact',
161
+ tags: [],
162
+ links: [],
163
+ created: validAt,
164
+ updated: validAt,
165
+ seq,
166
+ contentHash: `h${seq}`,
167
+ provenance: { source: 'agent' },
168
+ temporal
169
+ })
170
+ .orThrow(),
171
+ body: `body v${seq}`
172
+ };
173
+ }
174
+
175
+ describe('temporal selection helpers', () => {
176
+ const v1 = versionRecord(1, 100, 200);
177
+ const v2 = versionRecord(2, 200);
178
+ const atemporal: IMemoryRecord<unknown> = {
179
+ envelope: envelopeConverter
180
+ .convert({
181
+ id: 'doc',
182
+ entityId: 'doc',
183
+ kind: 'knowledge',
184
+ tags: [],
185
+ links: [],
186
+ created: 0,
187
+ updated: 0,
188
+ seq: 0,
189
+ contentHash: 'h',
190
+ provenance: { source: 'agent' }
191
+ })
192
+ .orThrow(),
193
+ body: 'flat'
194
+ };
195
+
196
+ test('isTemporalRecord keys off the temporal block', () => {
197
+ expect(isTemporalRecord(v1)).toBe(true);
198
+ expect(isTemporalRecord(atemporal)).toBe(false);
199
+ });
200
+
201
+ test('isVersionCurrent is true only for a null/absent invalid_at', () => {
202
+ expect(isVersionCurrent(v1)).toBe(false);
203
+ expect(isVersionCurrent(v2)).toBe(true);
204
+ expect(isVersionCurrent(versionRecord(3, 300, null))).toBe(true);
205
+ expect(isVersionCurrent(atemporal)).toBe(false);
206
+ });
207
+
208
+ test('isVersionValidAt bounds by valid_at and invalid_at', () => {
209
+ expect(isVersionValidAt(v1, 50)).toBe(false); // before valid_at
210
+ expect(isVersionValidAt(v1, 150)).toBe(true); // within [100, 200)
211
+ expect(isVersionValidAt(v1, 200)).toBe(false); // at invalid_at (exclusive)
212
+ expect(isVersionValidAt(v2, 500)).toBe(true); // still valid
213
+ expect(isVersionValidAt(atemporal, 500)).toBe(false);
214
+ });
215
+
216
+ test('isVersionValidAt defaults valid_at to created when absent', () => {
217
+ const noValidAt: IMemoryRecord<unknown> = {
218
+ envelope: envelopeConverter
219
+ .convert({
220
+ id: 'fact-1-v4',
221
+ entityId: 'fact-1',
222
+ kind: 'fact',
223
+ tags: [],
224
+ links: [],
225
+ created: 400,
226
+ updated: 400,
227
+ seq: 4,
228
+ contentHash: 'h4',
229
+ provenance: { source: 'agent' },
230
+ temporal: {}
231
+ })
232
+ .orThrow(),
233
+ body: 'v4'
234
+ };
235
+ expect(isVersionValidAt(noValidAt, 399)).toBe(false);
236
+ expect(isVersionValidAt(noValidAt, 400)).toBe(true);
237
+ });
238
+
239
+ test('selectCurrentVersion returns the highest-seq current version', () => {
240
+ expect(selectCurrentVersion([v1, v2])?.envelope.id).toBe('fact-1-v2');
241
+ // A fully-invalidated entity has no current version.
242
+ expect(selectCurrentVersion([v1])).toBeUndefined();
243
+ expect(selectCurrentVersion([])).toBeUndefined();
244
+ });
245
+
246
+ test('selectCurrentVersion is order-independent (highest seq wins on an unsorted input)', () => {
247
+ const v3 = versionRecord(3, 300);
248
+ // Descending input: the first candidate is already the max, so a later
249
+ // candidate must NOT displace it.
250
+ expect(selectCurrentVersion([v3, v2])?.envelope.id).toBe('fact-1-v3');
251
+ expect(selectCurrentVersion([v2, v3])?.envelope.id).toBe('fact-1-v3');
252
+ });
253
+
254
+ test('selectVersionAsOf returns the highest-seq version valid at the instant', () => {
255
+ expect(selectVersionAsOf([v1, v2], 150)?.envelope.id).toBe('fact-1-v1');
256
+ expect(selectVersionAsOf([v1, v2], 250)?.envelope.id).toBe('fact-1-v2');
257
+ expect(selectVersionAsOf([v1, v2], 50)).toBeUndefined();
258
+ });
259
+ });
@@ -0,0 +1,96 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import '@fgv/ts-utils-jest';
7
+ import {
8
+ EntityId,
9
+ IMemoryEnvelope,
10
+ IMemoryRecord,
11
+ IProvenance,
12
+ Kind,
13
+ MemoryId,
14
+ Tag,
15
+ TemporalVersionedPolicy
16
+ } from '../../../packlets/types';
17
+
18
+ function makeRecord(overrides?: Partial<IMemoryEnvelope>, body?: unknown): IMemoryRecord<unknown> {
19
+ const envelope: IMemoryEnvelope = {
20
+ id: 'fact-1-v1' as MemoryId,
21
+ entityId: 'fact-1' as EntityId,
22
+ kind: 'fact' as Kind,
23
+ tags: ['sky'] as Tag[],
24
+ links: [],
25
+ created: 1000,
26
+ updated: 1000,
27
+ seq: 1,
28
+ contentHash: 'hash-1',
29
+ provenance: { source: 'agent' } as IProvenance,
30
+ temporal: { valid_at: 1000 },
31
+ ...overrides
32
+ };
33
+ return { envelope, body: body ?? 'the sky is blue' };
34
+ }
35
+
36
+ describe('TemporalVersionedPolicy', () => {
37
+ let policy: TemporalVersionedPolicy;
38
+
39
+ beforeEach(() => {
40
+ policy = TemporalVersionedPolicy.create().orThrow();
41
+ });
42
+
43
+ test('exposes the temporal mutable surface and entity dedup', () => {
44
+ expect(policy.mutableFields).toEqual(['body', 'tags', 'links', 'provenance', 'embeddingRef']);
45
+ expect(policy.dedupScope).toBe('entity');
46
+ });
47
+
48
+ describe('admit', () => {
49
+ test("always accepts (invalidate-don't-delete: never culls)", () => {
50
+ expect(policy.admit(makeRecord(), [])).toSucceedWith({ decision: 'accept' });
51
+ expect(policy.admit(makeRecord(), [makeRecord(), makeRecord()])).toSucceedWith({
52
+ decision: 'accept'
53
+ });
54
+ });
55
+ });
56
+
57
+ describe('applyUpdate (forms the new version content)', () => {
58
+ test('replaces a string body wholesale', () => {
59
+ const existing = makeRecord(undefined, 'the sky is blue');
60
+ expect(policy.applyUpdate(existing, { body: 'the sky is grey' })).toSucceedAndSatisfy((updated) => {
61
+ expect(updated.body).toBe('the sky is grey');
62
+ });
63
+ });
64
+
65
+ test('merges tags / provenance and preserves untouched envelope metadata', () => {
66
+ const existing = makeRecord({ provenance: { source: 'agent', by: 'curator' } as IProvenance });
67
+ expect(
68
+ policy.applyUpdate(existing, { tags: ['sky', 'weather'], provenance: { model: 'gpt' } })
69
+ ).toSucceedAndSatisfy((updated) => {
70
+ expect(updated.envelope.tags).toEqual(['sky', 'weather']);
71
+ expect(updated.envelope.provenance).toEqual({ source: 'agent', by: 'curator', model: 'gpt' });
72
+ // Identity/transaction-time fields are carried verbatim (the store restamps them).
73
+ expect(updated.envelope.entityId).toBe('fact-1');
74
+ });
75
+ });
76
+
77
+ test('rejects a patch that deletes a required field', () => {
78
+ expect(policy.applyUpdate(makeRecord(), { body: null })).toFailWith(/may not delete required field/i);
79
+ });
80
+
81
+ test('restores embeddingRef as absent when a null patch drops it (hash-stable)', () => {
82
+ const existing = makeRecord({ embeddingRef: 'vec-1' });
83
+ expect(policy.applyUpdate(existing, { embeddingRef: null })).toSucceedAndSatisfy((updated) => {
84
+ expect(updated.envelope.embeddingRef).toBeUndefined();
85
+ });
86
+ });
87
+
88
+ test('carries an untouched embeddingRef through a body-only patch', () => {
89
+ const existing = makeRecord({ embeddingRef: 'vec-1' }, 'the sky is blue');
90
+ expect(policy.applyUpdate(existing, { body: 'the sky is grey' })).toSucceedAndSatisfy((updated) => {
91
+ expect(updated.body).toBe('the sky is grey');
92
+ expect(updated.envelope.embeddingRef).toBe('vec-1');
93
+ });
94
+ });
95
+ });
96
+ });
@@ -1,5 +1,5 @@
1
1
  {
2
- "cacheVersion": "9.39.4_v22.22.0",
2
+ "cacheVersion": "9.39.5_v24.15.0",
3
3
  "fileVersions": [
4
4
  [
5
5
  "packlets/types/filenameSafety.ts",
@@ -15,15 +15,19 @@
15
15
  ],
16
16
  [
17
17
  "packlets/types/identityCodec.ts",
18
- "280d27a02a8ac914fa8fdfbcc0624a913a145178edca6ecd5c5d90e318fdb10b_5FxNr7n+qjkV58wA2j2YZUqKABE="
18
+ "dfd6399be63cb55f38a37e1bee02d33458496d8fc16d879ce6f64386fb665f3e_5FxNr7n+qjkV58wA2j2YZUqKABE="
19
+ ],
20
+ [
21
+ "packlets/types/temporal.ts",
22
+ "c466e8c492ea8770d3c6045edf9fafb6df0e4be7ef94b6fa37f1825904289f65_5FxNr7n+qjkV58wA2j2YZUqKABE="
19
23
  ],
20
24
  [
21
25
  "packlets/types/writePolicy.ts",
22
- "511e86a9b7112468ad162597e555da7e009c8bcc415408c73b9e028ee4ccfaf4_5FxNr7n+qjkV58wA2j2YZUqKABE="
26
+ "0b914ac826cd126c34f73156a8b98efbbab44ef86dc476342a5d1c0afba485e6_5FxNr7n+qjkV58wA2j2YZUqKABE="
23
27
  ],
24
28
  [
25
29
  "packlets/types/index.ts",
26
- "e74d947932429f3cfbb08cc84668da6ddac280ee2d984bb9aa59570b88a7a1e9_5FxNr7n+qjkV58wA2j2YZUqKABE="
30
+ "21552730a5f27a53d1cdee798c4a22860f23b3d4290c3acdde08dcd7734adab2_5FxNr7n+qjkV58wA2j2YZUqKABE="
27
31
  ],
28
32
  [
29
33
  "packlets/converters/bodyConverterRegistry.ts",
@@ -75,7 +79,7 @@
75
79
  ],
76
80
  [
77
81
  "packlets/store/fileTreeMemoryStore.ts",
78
- "a7e4c896fd306508b97933c108d7ae006e4393ee921717a7e3ac0bfd38a0ff98_5FxNr7n+qjkV58wA2j2YZUqKABE="
82
+ "75746b65d0cde19462c3f6809a59fd60985864bf5fa1a66c7a59795115b49b8b_5FxNr7n+qjkV58wA2j2YZUqKABE="
79
83
  ],
80
84
  [
81
85
  "packlets/store/index.ts",
@@ -105,17 +109,53 @@
105
109
  "packlets/retrieve/semanticRetriever.ts",
106
110
  "30b52f854d7ccf395e03278e3889f7512ea825a489d31966736cef9a143bf991_5FxNr7n+qjkV58wA2j2YZUqKABE="
107
111
  ],
112
+ [
113
+ "packlets/retrieve/temporalRetrievers.ts",
114
+ "0131f13f335eca903bd94e2e8c793ca7af52e0cf60d63d83a6c12365ea530472_5FxNr7n+qjkV58wA2j2YZUqKABE="
115
+ ],
108
116
  [
109
117
  "packlets/retrieve/hybridRetriever.ts",
110
118
  "a3a6deccf2f8fff2a1863b31835780d8b1fa2a85af5db6639c38b2a5bc34a806_5FxNr7n+qjkV58wA2j2YZUqKABE="
111
119
  ],
112
120
  [
113
121
  "packlets/retrieve/index.ts",
114
- "39e1c225410ab3441a15326f90de9cc078fc3f72fdb8b58bde4a5047a5e65d6d_5FxNr7n+qjkV58wA2j2YZUqKABE="
122
+ "69aedb66b96e404cb8a20b1762605bb3646620e8d69ce520052a0d28715c7b54_5FxNr7n+qjkV58wA2j2YZUqKABE="
123
+ ],
124
+ [
125
+ "packlets/tools/memoryTools.ts",
126
+ "5593b178cee4f05bae54292130f5bc0bbb6858d7b10e5a4602ebbc22e29b8cbc_5FxNr7n+qjkV58wA2j2YZUqKABE="
127
+ ],
128
+ [
129
+ "packlets/tools/index.ts",
130
+ "450d52ceb27041fdace7ece7e49b747187deecbc5a3f5fafc6049a5809e576a2_5FxNr7n+qjkV58wA2j2YZUqKABE="
131
+ ],
132
+ [
133
+ "packlets/ingest/model.ts",
134
+ "666b14530d8ffcd51cd1d13ad46d1616c87f75e4ce28fd0db8ebacc434714cfe_5FxNr7n+qjkV58wA2j2YZUqKABE="
135
+ ],
136
+ [
137
+ "packlets/ingest/hostStages.ts",
138
+ "2b848556ea7d94a9f9247a6780428585e56e5d281364e16ff9cefad465ff05e1_5FxNr7n+qjkV58wA2j2YZUqKABE="
139
+ ],
140
+ [
141
+ "packlets/ingest/cycleGuard.ts",
142
+ "c2c969319313740b5a861ffede25512e843cdc4a62d2ed81d6ada3bfea3c6bcf_5FxNr7n+qjkV58wA2j2YZUqKABE="
143
+ ],
144
+ [
145
+ "packlets/ingest/orchestrator.ts",
146
+ "f821fe8bc363157124a991a4dff62a2c96c1fefe14db54993147a00f5b522e8b_5FxNr7n+qjkV58wA2j2YZUqKABE="
147
+ ],
148
+ [
149
+ "packlets/ingest/index.ts",
150
+ "95098b11f231af90b75e9b8ea81b4a2f7be28679930e6470206eda8723b2530d_5FxNr7n+qjkV58wA2j2YZUqKABE="
115
151
  ],
116
152
  [
117
153
  "index.ts",
118
- "99fdcf995285072710d5a6239d578f46446eadb35c6bd5e6255d1b72394fb1c4_5FxNr7n+qjkV58wA2j2YZUqKABE="
154
+ "31af89fc4f15e6e2ad7542569401f39c29319d6f740c2f8be8478e2c0a266f4e_5FxNr7n+qjkV58wA2j2YZUqKABE="
155
+ ],
156
+ [
157
+ "test/unit/converters/antagonistRoundTrip.test.ts",
158
+ "716259d8068e73fd8444a306835881f9cff709e043dde61578bb2a061decdfb7_fnTnCS8rja7DE6jB0bKRna1pZAg="
119
159
  ],
120
160
  [
121
161
  "test/unit/converters/bodyConverterRegistry.test.ts",
@@ -129,6 +169,18 @@
129
169
  "test/unit/index/memoryIndex.test.ts",
130
170
  "a9f1894d8e8ab4430709fcd6addb003ecd6ff98baebf855ace88f39427e30593_fnTnCS8rja7DE6jB0bKRna1pZAg="
131
171
  ],
172
+ [
173
+ "test/unit/ingest/antagonistCycleAndParity.test.ts",
174
+ "af7b769809b92543b0a9cd32ddcb1de8bcb4db102d7997b71be7a84d9b1fbb09_fnTnCS8rja7DE6jB0bKRna1pZAg="
175
+ ],
176
+ [
177
+ "test/unit/ingest/cycleGuard.test.ts",
178
+ "ffb8024c01a769482c7abcc057c2e4ad798ffc50f010081a9283f1cf1209d56e_fnTnCS8rja7DE6jB0bKRna1pZAg="
179
+ ],
180
+ [
181
+ "test/unit/ingest/orchestrator.test.ts",
182
+ "945eb0d45fa417bcb4a88eb7bfb5ee8b51fbd16099925b9dc1888c69fc02d236_fnTnCS8rja7DE6jB0bKRna1pZAg="
183
+ ],
132
184
  [
133
185
  "test/unit/observe/memoryObservationStore.test.ts",
134
186
  "675e6b09c107c74fe254a0f208be0d8177ace1971fb8f1a7ae0a3c3c56f38357_fnTnCS8rja7DE6jB0bKRna1pZAg="
@@ -141,13 +193,21 @@
141
193
  "test/unit/retrieve/retrievers.test.ts",
142
194
  "e4a7ba604ec7c481d2a491358d1c42d9e1eecdb77fa3e3a804c10fe72e5506df_fnTnCS8rja7DE6jB0bKRna1pZAg="
143
195
  ],
196
+ [
197
+ "test/unit/retrieve/temporalRetrievers.test.ts",
198
+ "e068283b7d75482efe6116e0396c6b7a6865b20bb57e4efec9abd8b811f5fc9e_fnTnCS8rja7DE6jB0bKRna1pZAg="
199
+ ],
200
+ [
201
+ "test/unit/store/antagonistTemporalBoundary.test.ts",
202
+ "ab04a6b3292ce201a3ad9f4528aea460ecdf9b832fcff5799aa7608d2d8f05ca_fnTnCS8rja7DE6jB0bKRna1pZAg="
203
+ ],
144
204
  [
145
205
  "test/unit/store/embedOnWrite.test.ts",
146
206
  "9bdab98d1e0cb73f16a5d18daf0bd7e4da02db28d05e82424d1c42e9fc0b2c45_fnTnCS8rja7DE6jB0bKRna1pZAg="
147
207
  ],
148
208
  [
149
209
  "test/unit/store/fileTreeMemoryStore.test.ts",
150
- "4768a91f3d0117105b77168d9fbf9184b95f2af7bfb1f802eef9f078394a38fa_fnTnCS8rja7DE6jB0bKRna1pZAg="
210
+ "06407be1a2dc1b914cecefe63766ea4a597c132cc04e87d8b2a373d582be435f_fnTnCS8rja7DE6jB0bKRna1pZAg="
151
211
  ],
152
212
  [
153
213
  "test/unit/store/observations.test.ts",
@@ -157,6 +217,14 @@
157
217
  "test/unit/store/scopeEncoding.test.ts",
158
218
  "5ea0364489c7424bbfcae0b85f71a49aa069202e307282f808ea54449a927c3b_fnTnCS8rja7DE6jB0bKRna1pZAg="
159
219
  ],
220
+ [
221
+ "test/unit/store/temporalStore.test.ts",
222
+ "351187294694c4f616dae51b837cc0abe346da028c08986e77a9faad4a372731_fnTnCS8rja7DE6jB0bKRna1pZAg="
223
+ ],
224
+ [
225
+ "test/unit/tools/memoryTools.test.ts",
226
+ "ed1b29228843e28ce103a46705426eb63b5e8b4d0f76e66c63747b259c2cd8f0_fnTnCS8rja7DE6jB0bKRna1pZAg="
227
+ ],
160
228
  [
161
229
  "test/unit/types/identityCodec.test.ts",
162
230
  "0bdce7a05231458604750150fc806f1fc64b159949edc3af2f5a20d5d9bfceee_fnTnCS8rja7DE6jB0bKRna1pZAg="
@@ -165,6 +233,14 @@
165
233
  "test/unit/types/ids.test.ts",
166
234
  "30b7ac5c0daa99f32913eefdd6825d4e73e7e91d66c5863c5a5b8d8666635fc8_fnTnCS8rja7DE6jB0bKRna1pZAg="
167
235
  ],
236
+ [
237
+ "test/unit/types/temporalCodec.test.ts",
238
+ "2a7963436cf9d7518be5dabb98a7bcf8091857ddfcc1068d21a67002b564875a_fnTnCS8rja7DE6jB0bKRna1pZAg="
239
+ ],
240
+ [
241
+ "test/unit/types/temporalPolicy.test.ts",
242
+ "f6ff83abe5e9e418ee0445f54eda869f1400d49cd11529da9be06c69be7f7c70_fnTnCS8rja7DE6jB0bKRna1pZAg="
243
+ ],
168
244
  [
169
245
  "test/unit/types/writePolicy.test.ts",
170
246
  "b1dce7dcab42c012d504d2d108626e62ce024463654f118b5bb78adb2faa7dac_fnTnCS8rja7DE6jB0bKRna1pZAg="
@@ -178,5 +254,5 @@
178
254
  "fb1b4366bcd6b06174b82a3604cd6ee09692cb89a6c38b90866281ba204efc90_fnTnCS8rja7DE6jB0bKRna1pZAg="
179
255
  ]
180
256
  ],
181
- "filesHash": "6mhWoAAG3IozXRRuSjvrbA"
257
+ "filesHash": "41qqBVkA2UlzsTYEJ5_czQ"
182
258
  }