@x12i/memorix-format 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/associated.d.ts +38 -0
- package/dist/associated.d.ts.map +1 -0
- package/dist/associated.js +59 -0
- package/dist/associated.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +199 -0
- package/dist/cli.js.map +1 -0
- package/dist/concept.d.ts +16 -0
- package/dist/concept.d.ts.map +1 -0
- package/dist/concept.js +75 -0
- package/dist/concept.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +49 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +4 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +51 -0
- package/dist/inspect.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +23 -0
- package/dist/utils.js.map +1 -0
- package/dist/validate.d.ts +14 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +211 -0
- package/dist/validate.js.map +1 -0
- package/docs/association-format.md +466 -0
- package/docs/memorix-format-additions.md +956 -0
- package/docs/memorix-format-for-dummies-association-corrections.md +113 -0
- package/docs/memorix-format.md +282 -0
- package/docs/memorix-workflow-format.md +114 -0
- package/examples/invalid-record.json +11 -0
- package/examples/valid-snapshot.json +38 -0
- package/package.json +67 -0
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
# Appendix: Extended Memorix Record Parts
|
|
2
|
+
|
|
3
|
+
> Shipped with `@x12i/memorix-format`. Examples may show the older `identity` envelope for illustration; the **canonical** clean envelope is [`memorix-format.md`](./memorix-format.md).
|
|
4
|
+
|
|
5
|
+
## A. Why this appendix exists
|
|
6
|
+
|
|
7
|
+
The main Memorix 2.0 format defines the clean structure of a record:
|
|
8
|
+
|
|
9
|
+
```txt id="v3yvcs"
|
|
10
|
+
identity
|
|
11
|
+
lifecycle
|
|
12
|
+
concept
|
|
13
|
+
data
|
|
14
|
+
index
|
|
15
|
+
sourceFacts
|
|
16
|
+
_system
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This appendix adds the missing higher-level distinction between four different kinds of information that are often mixed together:
|
|
20
|
+
|
|
21
|
+
```txt id="dw987j"
|
|
22
|
+
native facts
|
|
23
|
+
associated context
|
|
24
|
+
numeric analytics
|
|
25
|
+
interpretive insights
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The distinction matters because Memorix records need to be easy to consume, but also semantically clean.
|
|
29
|
+
|
|
30
|
+
The existing Memorix contract already says that domain payload belongs in `data`, while system and provenance fields should stay outside `data`. The same idea should now be extended to associated context, analytics, and insights.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## B. Full snapshot record shape
|
|
35
|
+
|
|
36
|
+
The full-scale Memorix record shape is intended mainly for the `snapshot` content type of an object type.
|
|
37
|
+
|
|
38
|
+
```ts id="km4sqn"
|
|
39
|
+
type MemorixSnapshotRecord = {
|
|
40
|
+
_id?: string;
|
|
41
|
+
|
|
42
|
+
identity: MemorixIdentity;
|
|
43
|
+
lifecycle: MemorixLifecycle;
|
|
44
|
+
concept: MemorixConcept;
|
|
45
|
+
|
|
46
|
+
data: JsonObject;
|
|
47
|
+
|
|
48
|
+
associatedData?: JsonObject;
|
|
49
|
+
analytics?: MemorixAnalytics;
|
|
50
|
+
insights?: MemorixInsights;
|
|
51
|
+
|
|
52
|
+
index?: MemorixIndexBlock;
|
|
53
|
+
sourceFacts?: MemorixSourceFacts;
|
|
54
|
+
|
|
55
|
+
_system?: MemorixSystemBlock;
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This is the rich, readable, consumption-friendly version of the object.
|
|
60
|
+
|
|
61
|
+
Other content types under the same object type do not need to carry all of these blocks. Memorix already stores records by object type and content type, for example `assets-snapshots` or `vulnerabilities-snapshots`.
|
|
62
|
+
|
|
63
|
+
So the rule is:
|
|
64
|
+
|
|
65
|
+
```txt id="s9zbmc"
|
|
66
|
+
The full record shape belongs primarily to snapshots.
|
|
67
|
+
Other content types should contain only what makes sense for that content type.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## C. Minimal shape for non-snapshot content types
|
|
73
|
+
|
|
74
|
+
For the product **workflow** content type (`{objectType}-workflow`, format
|
|
75
|
+
`memorix-workflow/1`), see [memorix-workflow-format.md](./memorix-workflow-format.md).
|
|
76
|
+
Workflow is 1:1 product state (owner, status, audit, tasks); catalog-driven
|
|
77
|
+
fields under `data` are defined in Catalox `memorix-workflow-field-schemas`.
|
|
78
|
+
|
|
79
|
+
Not every collection under an object type should look like a full snapshot.
|
|
80
|
+
|
|
81
|
+
For example:
|
|
82
|
+
|
|
83
|
+
```txt id="evq5gl"
|
|
84
|
+
assets-snapshots
|
|
85
|
+
assets-events
|
|
86
|
+
assets-facts
|
|
87
|
+
assets-chunks
|
|
88
|
+
assets-history
|
|
89
|
+
assets-profiles
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Only `assets-snapshots` needs the full record surface.
|
|
93
|
+
|
|
94
|
+
Other content types should have the required Memorix structure, but not unnecessary blocks.
|
|
95
|
+
|
|
96
|
+
Recommended minimal shape:
|
|
97
|
+
|
|
98
|
+
```ts id="uak8ve"
|
|
99
|
+
type MemorixContentRecord = {
|
|
100
|
+
_id?: string;
|
|
101
|
+
|
|
102
|
+
identity: MemorixIdentity;
|
|
103
|
+
lifecycle?: MemorixLifecycle;
|
|
104
|
+
concept?: MemorixConcept;
|
|
105
|
+
|
|
106
|
+
data: JsonObject;
|
|
107
|
+
|
|
108
|
+
index?: MemorixIndexBlock;
|
|
109
|
+
sourceFacts?: MemorixSourceFacts;
|
|
110
|
+
|
|
111
|
+
_system?: MemorixSystemBlock;
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Required minimum:
|
|
116
|
+
|
|
117
|
+
```txt id="rq22ev"
|
|
118
|
+
identity
|
|
119
|
+
data
|
|
120
|
+
whatever metadata/keys Memorix needs for that content type
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Memorix should decide the exact required keys by content type, descriptor, and object behavior.
|
|
124
|
+
|
|
125
|
+
The important point:
|
|
126
|
+
|
|
127
|
+
```txt id="6pdkqx"
|
|
128
|
+
Do not force analytics, insights, or associatedData into every content type.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## D. `data`: native readable facts
|
|
134
|
+
|
|
135
|
+
`data` is the record’s own readable truth.
|
|
136
|
+
|
|
137
|
+
It should contain facts that are directly about the record itself.
|
|
138
|
+
|
|
139
|
+
For a CVE record:
|
|
140
|
+
|
|
141
|
+
```json id="lqeccc"
|
|
142
|
+
{
|
|
143
|
+
"data": {
|
|
144
|
+
"cveId": "CVE-2022-29464",
|
|
145
|
+
"severity": "critical",
|
|
146
|
+
"description": "Unrestricted file upload vulnerability in WSO2 products.",
|
|
147
|
+
"publishedAt": "2022-04-18"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
These are facts about the CVE itself.
|
|
153
|
+
|
|
154
|
+
`data` should not contain unrelated records, source-system noise, enum codes, opaque ids, or product judgments.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## E. `associatedData`: readable related context
|
|
159
|
+
|
|
160
|
+
`associatedData` contains useful readable context attached to the record because of related records.
|
|
161
|
+
|
|
162
|
+
It is not the record’s native truth, but it is very useful for consumption.
|
|
163
|
+
|
|
164
|
+
For example, a CVE record may need to show the assets affected by that CVE.
|
|
165
|
+
|
|
166
|
+
Those assets are not part of the CVE itself, so they should not live in `data`.
|
|
167
|
+
|
|
168
|
+
But consumers still need them.
|
|
169
|
+
|
|
170
|
+
So they belong in `associatedData`.
|
|
171
|
+
|
|
172
|
+
```json id="m7pzc6"
|
|
173
|
+
{
|
|
174
|
+
"associatedData": {
|
|
175
|
+
"affectedAssets": [
|
|
176
|
+
{
|
|
177
|
+
"assetName": "prod-api-01",
|
|
178
|
+
"assetIp": "10.100.18.64",
|
|
179
|
+
"recordId": "entity:asset:10.100.18.64"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"assetName": "prod-db-02",
|
|
183
|
+
"assetIp": "10.100.18.65",
|
|
184
|
+
"recordId": "entity:asset:10.100.18.65"
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
This gives consumers the useful joined view without pretending that affected assets are native CVE fields.
|
|
192
|
+
|
|
193
|
+
### Rule
|
|
194
|
+
|
|
195
|
+
```txt id="cqzqcl"
|
|
196
|
+
associatedData = readable materialized context from related records
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## F. `analytics`: numeric rollups about linked lists
|
|
202
|
+
|
|
203
|
+
`analytics` is not the associated data itself.
|
|
204
|
+
|
|
205
|
+
`analytics` contains numbers about associated lists.
|
|
206
|
+
|
|
207
|
+
It is used when the record has links to lists of other records, and Memorix wants to store useful numeric summaries about those lists.
|
|
208
|
+
|
|
209
|
+
For example:
|
|
210
|
+
|
|
211
|
+
```txt id="omk77f"
|
|
212
|
+
CVE -> affected assets
|
|
213
|
+
asset -> related vulnerabilities
|
|
214
|
+
document -> exposed users
|
|
215
|
+
user -> accessible sensitive files
|
|
216
|
+
group -> member users
|
|
217
|
+
policy -> affected resources
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If a CVE is linked to many affected assets, then `analytics.affectedAssets` contains numbers about that list.
|
|
221
|
+
|
|
222
|
+
```json id="uerbc6"
|
|
223
|
+
{
|
|
224
|
+
"analytics": {
|
|
225
|
+
"affectedAssets": {
|
|
226
|
+
"total": 12,
|
|
227
|
+
"internetExposed": 3,
|
|
228
|
+
"production": 5,
|
|
229
|
+
"withOpenFindings": 9
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The actual affected asset list belongs in `associatedData`.
|
|
236
|
+
|
|
237
|
+
The internal record links belong in `index.recordLinks`.
|
|
238
|
+
|
|
239
|
+
The numeric summaries belong in `analytics`.
|
|
240
|
+
|
|
241
|
+
### What belongs in `analytics`
|
|
242
|
+
|
|
243
|
+
```txt id="fdwu3l"
|
|
244
|
+
counts
|
|
245
|
+
totals
|
|
246
|
+
distributions
|
|
247
|
+
buckets
|
|
248
|
+
aggregations
|
|
249
|
+
list summaries
|
|
250
|
+
numeric rollups
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Examples:
|
|
254
|
+
|
|
255
|
+
```json id="1dwp79"
|
|
256
|
+
{
|
|
257
|
+
"analytics": {
|
|
258
|
+
"affectedAssets": {
|
|
259
|
+
"total": 12,
|
|
260
|
+
"byEnvironment": {
|
|
261
|
+
"production": 5,
|
|
262
|
+
"staging": 4,
|
|
263
|
+
"development": 3
|
|
264
|
+
},
|
|
265
|
+
"internetExposed": 3
|
|
266
|
+
},
|
|
267
|
+
"relatedFindings": {
|
|
268
|
+
"total": 18,
|
|
269
|
+
"open": 14,
|
|
270
|
+
"closed": 4
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### What does not belong in `analytics`
|
|
277
|
+
|
|
278
|
+
Do not put the actual related records in `analytics`.
|
|
279
|
+
|
|
280
|
+
Bad:
|
|
281
|
+
|
|
282
|
+
```json id="8e7c4g"
|
|
283
|
+
{
|
|
284
|
+
"analytics": {
|
|
285
|
+
"affectedAssets": [
|
|
286
|
+
{
|
|
287
|
+
"assetName": "prod-api-01",
|
|
288
|
+
"assetIp": "10.100.18.64"
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Good:
|
|
296
|
+
|
|
297
|
+
```json id="3t9cdo"
|
|
298
|
+
{
|
|
299
|
+
"associatedData": {
|
|
300
|
+
"affectedAssets": [
|
|
301
|
+
{
|
|
302
|
+
"assetName": "prod-api-01",
|
|
303
|
+
"assetIp": "10.100.18.64"
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
"analytics": {
|
|
308
|
+
"affectedAssets": {
|
|
309
|
+
"total": 1
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Rule
|
|
316
|
+
|
|
317
|
+
```txt id="7wh3kh"
|
|
318
|
+
analytics answers: how many, how much, how distributed, how often.
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## G. `insights`: interpretations, judgments, and decisions
|
|
324
|
+
|
|
325
|
+
`insights` contains interpreted meaning about the record.
|
|
326
|
+
|
|
327
|
+
It is the place for judgments, assessments, analysis, scores, conclusions, recommendations, classifications, confidence, and priority.
|
|
328
|
+
|
|
329
|
+
```json id="2dxvna"
|
|
330
|
+
{
|
|
331
|
+
"insights": {
|
|
332
|
+
"risk": {
|
|
333
|
+
"level": "critical",
|
|
334
|
+
"score": 92,
|
|
335
|
+
"confidence": 0.84,
|
|
336
|
+
"reason": "Critical CVE affecting production and internet-exposed assets."
|
|
337
|
+
},
|
|
338
|
+
"priority": {
|
|
339
|
+
"level": "urgent",
|
|
340
|
+
"reason": "Affected assets include production systems."
|
|
341
|
+
},
|
|
342
|
+
"recommendations": [
|
|
343
|
+
{
|
|
344
|
+
"action": "Patch affected assets",
|
|
345
|
+
"urgency": "high"
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Why this must be separate from `data`
|
|
353
|
+
|
|
354
|
+
A score is not the same as a fact.
|
|
355
|
+
|
|
356
|
+
This is a fact:
|
|
357
|
+
|
|
358
|
+
```json id="vfqhn8"
|
|
359
|
+
{
|
|
360
|
+
"severity": "critical"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
This is an insight:
|
|
365
|
+
|
|
366
|
+
```json id="tdrpzw"
|
|
367
|
+
{
|
|
368
|
+
"riskScore": 92,
|
|
369
|
+
"priority": "urgent",
|
|
370
|
+
"recommendedAction": "Patch affected assets"
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
If insights are placed inside `data`, they can pollute the record’s native meaning.
|
|
375
|
+
|
|
376
|
+
### Rule
|
|
377
|
+
|
|
378
|
+
```txt id="41g5nl"
|
|
379
|
+
insights answers: so what, how bad, what should we do, how confident are we.
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## H. Difference between `associatedData`, `analytics`, and `insights`
|
|
385
|
+
|
|
386
|
+
These three are easy to confuse, so the distinction must be strict.
|
|
387
|
+
|
|
388
|
+
```txt id="d2vm9q"
|
|
389
|
+
associatedData = the related things
|
|
390
|
+
analytics = numbers about the related things
|
|
391
|
+
insights = conclusions from the facts, context, and numbers
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Example:
|
|
395
|
+
|
|
396
|
+
```json id="3n6qlf"
|
|
397
|
+
{
|
|
398
|
+
"data": {
|
|
399
|
+
"cveId": "CVE-2022-29464",
|
|
400
|
+
"severity": "critical"
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
"associatedData": {
|
|
404
|
+
"affectedAssets": [
|
|
405
|
+
{
|
|
406
|
+
"assetName": "prod-api-01",
|
|
407
|
+
"assetIp": "10.100.18.64"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"assetName": "prod-db-02",
|
|
411
|
+
"assetIp": "10.100.18.65"
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
"analytics": {
|
|
417
|
+
"affectedAssets": {
|
|
418
|
+
"total": 2,
|
|
419
|
+
"production": 2,
|
|
420
|
+
"internetExposed": 1
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
|
|
424
|
+
"insights": {
|
|
425
|
+
"risk": {
|
|
426
|
+
"level": "critical",
|
|
427
|
+
"reason": "The CVE affects production assets, including one internet-exposed asset."
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
This keeps the model clean:
|
|
434
|
+
|
|
435
|
+
```txt id="sjw9xv"
|
|
436
|
+
data says what the record is.
|
|
437
|
+
associatedData says what is connected to it.
|
|
438
|
+
analytics says how much or how many.
|
|
439
|
+
insights says what Memorix concludes.
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## I. Relationship with `index.recordLinks`
|
|
445
|
+
|
|
446
|
+
`index.recordLinks` is the internal link/index surface.
|
|
447
|
+
|
|
448
|
+
It is not meant to replace `associatedData`.
|
|
449
|
+
|
|
450
|
+
It is used by Memorix for search, expansion, linking, retrieval, ranking, and internal navigation.
|
|
451
|
+
|
|
452
|
+
The existing Memorix model already treats relations as read-time joins rather than stored graph edges.
|
|
453
|
+
|
|
454
|
+
The Memorix 2.0 model keeps that philosophy but gives links an indexed representation when needed.
|
|
455
|
+
|
|
456
|
+
Example:
|
|
457
|
+
|
|
458
|
+
```json id="6akxmw"
|
|
459
|
+
{
|
|
460
|
+
"index": {
|
|
461
|
+
"recordLinks": [
|
|
462
|
+
{
|
|
463
|
+
"key": "affectedAssets",
|
|
464
|
+
"relation": "affects",
|
|
465
|
+
"direction": "out",
|
|
466
|
+
"target": {
|
|
467
|
+
"recordId": "entity:asset:10.100.18.64",
|
|
468
|
+
"target": "entity",
|
|
469
|
+
"objectType": "assets"
|
|
470
|
+
},
|
|
471
|
+
"display": {
|
|
472
|
+
"name": "prod-api-01"
|
|
473
|
+
},
|
|
474
|
+
"indexed": true
|
|
475
|
+
}
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The same relationship may appear in different ways:
|
|
482
|
+
|
|
483
|
+
```txt id="f0c6wq"
|
|
484
|
+
index.recordLinks = internal link
|
|
485
|
+
associatedData = readable related record view
|
|
486
|
+
analytics = numeric summary of the linked list
|
|
487
|
+
insights = interpretation based on the relationship
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## J. Relationship with `sourceFacts`
|
|
493
|
+
|
|
494
|
+
`sourceFacts` preserves raw values used for resolution and traceability.
|
|
495
|
+
|
|
496
|
+
Example:
|
|
497
|
+
|
|
498
|
+
```json id="x0e556"
|
|
499
|
+
{
|
|
500
|
+
"sourceFacts": {
|
|
501
|
+
"schemaVersion": "sourceFacts.v1",
|
|
502
|
+
"values": {
|
|
503
|
+
"assetId": {
|
|
504
|
+
"rawValue": "a_918271",
|
|
505
|
+
"kind": "foreign-key",
|
|
506
|
+
"resolvedToPath": "associatedData.affectedAssets[0].assetName",
|
|
507
|
+
"resolvedValue": "prod-api-01"
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
The raw value belongs in `sourceFacts`.
|
|
515
|
+
|
|
516
|
+
The readable related object belongs in `associatedData`.
|
|
517
|
+
|
|
518
|
+
The internal link belongs in `index.recordLinks`.
|
|
519
|
+
|
|
520
|
+
The count belongs in `analytics`.
|
|
521
|
+
|
|
522
|
+
The conclusion belongs in `insights`.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## K. Relationship with Memorix-Hippox
|
|
527
|
+
|
|
528
|
+
Memorix-Hippox is the metadata database of Memorix.
|
|
529
|
+
|
|
530
|
+
It remembers how things relate, how values are resolved, and how source-system mess becomes Memorix meaning.
|
|
531
|
+
|
|
532
|
+
For this appendix, Hippox should remember product-level metadata such as:
|
|
533
|
+
|
|
534
|
+
```txt id="y7qppy"
|
|
535
|
+
which object types can be linked
|
|
536
|
+
which links are one-to-one
|
|
537
|
+
which links are one-to-many
|
|
538
|
+
which linked lists should produce analytics
|
|
539
|
+
which linked lists should produce associatedData
|
|
540
|
+
which enum/code values resolve to readable values
|
|
541
|
+
which fields should become insights
|
|
542
|
+
which fields are native data
|
|
543
|
+
which fields are raw source facts
|
|
544
|
+
which sourcePath/targetPath pairs join two object types
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
The record does not need to store the full Hippox metadata format.
|
|
548
|
+
|
|
549
|
+
The record stores the result:
|
|
550
|
+
|
|
551
|
+
```txt id="s0v4wk"
|
|
552
|
+
sourceFacts keeps raw resolver material.
|
|
553
|
+
index.recordLinks keeps internal links.
|
|
554
|
+
associatedData keeps readable related context.
|
|
555
|
+
analytics keeps numeric rollups.
|
|
556
|
+
insights keeps interpreted conclusions.
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### K.1 Three Hippox surfaces on a snapshot record
|
|
560
|
+
|
|
561
|
+
| Layer | Who writes it | Hippox involvement |
|
|
562
|
+
|-------|---------------|-------------------|
|
|
563
|
+
| `index.recordLinks` | ingest / indexer | schema relations; optional Hippox-approved join metadata |
|
|
564
|
+
| `associatedData` | snapshot materializer | readable output from approved context links (`retrieval.type: record` or `list`) |
|
|
565
|
+
| `analytics` / `insights` | downstream jobs | Hippox may define which linked lists feed rollups or conclusions |
|
|
566
|
+
|
|
567
|
+
Context link rules themselves live in Mongo database **`memorix-hippox`** (collection **`context-links`**). Records only carry materialized results.
|
|
568
|
+
|
|
569
|
+
### K.2 Context links vs schema relations
|
|
570
|
+
|
|
571
|
+
Schema relations (entity descriptors) declare durable product semantics and populate `index.recordLinks`.
|
|
572
|
+
|
|
573
|
+
**Context links** (`@x12i/memorix-hippox`) let operators define property joins without changing schema first:
|
|
574
|
+
|
|
575
|
+
```txt
|
|
576
|
+
discover → join-discovery ranks fields from sampled data
|
|
577
|
+
simulate → validates proposed join on samples
|
|
578
|
+
approve → persists reusable link definition
|
|
579
|
+
resolve → bounded runtime context (jobMemory.context)
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Join paths use the same dot-path convention as descriptors: from document root, typically under `data.*`.
|
|
583
|
+
|
|
584
|
+
Example operator flow for `assets → subnets`:
|
|
585
|
+
|
|
586
|
+
```http
|
|
587
|
+
GET /api/hippox/join-discovery?sourceObjectType=assets&targetObjectType=subnets
|
|
588
|
+
POST /api/hippox/links/simulate
|
|
589
|
+
POST /api/hippox/links/:id/approve
|
|
590
|
+
POST /api/hippox/context/resolve
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Discovery seeds from schema relations and previously approved Hippox links so declared joins stay rank 1 even on rediscovery.
|
|
594
|
+
|
|
595
|
+
When a snapshot is fully materialized, readable joined targets belong in **`associatedData`**, not mixed into native `data` fields. Internal pointers stay in **`index.recordLinks`**.
|
|
596
|
+
|
|
597
|
+
### K.3 Snapshot-only materialization rule
|
|
598
|
+
|
|
599
|
+
`associatedData`, `analytics`, and `insights` are snapshot-only by default (see §L). Hippox context resolution at job time produces transient `jobMemory.context`; persisting that output onto a snapshot is an explicit materialization step, not automatic on every resolve call.
|
|
600
|
+
|
|
601
|
+
### K.4 Automated fuzzy mapping (`@x12i/helpers` ≥ 2.0)
|
|
602
|
+
|
|
603
|
+
For scopes **without** entity descriptors or schema relations — migration dumps, external API clusters, audit JSON — Hippox can use the **`@x12i/helpers/record-key-analysis`** pipeline to infer joins before an operator touches the UI.
|
|
604
|
+
|
|
605
|
+
**Prerequisite:** `@x12i/helpers@^2.0.0` with `analyzeSingleRecordKeys`, `analyzeCollectionKeys`, and `mapCrossCollectionRelationships`.
|
|
606
|
+
|
|
607
|
+
**Phase 1 — Ingestion and sampling**
|
|
608
|
+
|
|
609
|
+
Loop all collections in scope. Sample up to **10,000** records per collection (same order of magnitude as Hippox discovery/simulation limits). Hippox reuses its Mongo collection resolver for Memorix object types; raw JSON arrays use the same sample cap in memory.
|
|
610
|
+
|
|
611
|
+
**Phase 2 — Entity profiling (per collection)**
|
|
612
|
+
|
|
613
|
+
1. `analyzeSingleRecordKeys(firstRecord)` — candidate property names with confidence scores.
|
|
614
|
+
2. Keep properties where **`confidence >= 40`** (reduces CPU vs profiling every column).
|
|
615
|
+
3. `analyzeCollectionKeys(fullSample, candidateProperties)` — classify each property:
|
|
616
|
+
- **`PRIMARY`** → entity primary identifiers (candidate join targets)
|
|
617
|
+
- **`FOREIGN`** → unresolved outbound keys (candidate Hippox `sourcePath`)
|
|
618
|
+
- **`ENUM`** → data dictionary entries (future `sourceFacts` resolver seeds)
|
|
619
|
+
|
|
620
|
+
Store results in a central schema registry (in-memory map or JSON artifact).
|
|
621
|
+
|
|
622
|
+
**Phase 3 — Cross-collection relationship mapping**
|
|
623
|
+
|
|
624
|
+
After all collections are profiled:
|
|
625
|
+
|
|
626
|
+
1. Build `targetCollectionsMap`: each collection name → `{ primaryKeys, records }` using Phase 2 `PRIMARY` paths.
|
|
627
|
+
2. For every collection with **`FOREIGN`** keys, call `mapCrossCollectionRelationships(sourceSample, foreignKeyProperty, targetCollectionsMap)`.
|
|
628
|
+
3. Accept edges where **`inclusionRatio > 0.95`** (helpers default threshold).
|
|
629
|
+
4. Map winning edges to Hippox join candidates: `{ sourceObjectType, targetObjectType, join: [{ sourcePath, targetPath, operator: 'eq' }] }`.
|
|
630
|
+
|
|
631
|
+
**Output handling**
|
|
632
|
+
|
|
633
|
+
The orchestration produces a structured link graph of the previously unknown scope. Feed it into:
|
|
634
|
+
|
|
635
|
+
- Hippox **simulate → approve** for durable context links
|
|
636
|
+
- Schema **relation detect** for descriptor promotion
|
|
637
|
+
- Migration validators and agentic schema tools
|
|
638
|
+
|
|
639
|
+
This is **complementary** to `GET /api/hippox/join-discovery`, which assumes named Memorix object types and blends live overlap with schema/approved-link seeds. Fuzzy mapping is the cold-start path; join-discovery is the operator refinement path once types exist.
|
|
640
|
+
|
|
641
|
+
---
|
|
642
|
+
|
|
643
|
+
## L. Snapshot-only full-scale rule
|
|
644
|
+
|
|
645
|
+
The full-scale shape should be expected mainly in snapshot collections.
|
|
646
|
+
|
|
647
|
+
Example:
|
|
648
|
+
|
|
649
|
+
```txt id="ot7c3i"
|
|
650
|
+
vulnerabilities-snapshots
|
|
651
|
+
assets-snapshots
|
|
652
|
+
users-snapshots
|
|
653
|
+
documents-snapshots
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
A snapshot is the best place for:
|
|
657
|
+
|
|
658
|
+
```txt id="kr0jcs"
|
|
659
|
+
concept
|
|
660
|
+
data
|
|
661
|
+
associatedData
|
|
662
|
+
analytics
|
|
663
|
+
insights
|
|
664
|
+
index
|
|
665
|
+
sourceFacts
|
|
666
|
+
_system
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Other content types should stay smaller and purpose-specific.
|
|
670
|
+
|
|
671
|
+
Example:
|
|
672
|
+
|
|
673
|
+
```txt id="i0znxj"
|
|
674
|
+
vulnerabilities-events
|
|
675
|
+
vulnerabilities-facts
|
|
676
|
+
vulnerabilities-chunks
|
|
677
|
+
vulnerabilities-history
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
These may only need:
|
|
681
|
+
|
|
682
|
+
```txt id="nqsr18"
|
|
683
|
+
identity
|
|
684
|
+
lifecycle
|
|
685
|
+
data
|
|
686
|
+
sourceFacts
|
|
687
|
+
_system
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
or whatever Memorix decides is required by descriptor and use case.
|
|
691
|
+
|
|
692
|
+
### Rule
|
|
693
|
+
|
|
694
|
+
```txt id="l97dza"
|
|
695
|
+
Snapshots are the full readable object view.
|
|
696
|
+
Other content types are specialized records.
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## M. Final full-scale snapshot example
|
|
702
|
+
|
|
703
|
+
```json id="eey88y"
|
|
704
|
+
{
|
|
705
|
+
"_id": "667f00000000000000000001",
|
|
706
|
+
|
|
707
|
+
"identity": {
|
|
708
|
+
"recordId": "entity:cve:CVE-2022-29464",
|
|
709
|
+
"target": "entity",
|
|
710
|
+
"targetId": "entity:cve:CVE-2022-29464",
|
|
711
|
+
"objectType": "vulnerabilities",
|
|
712
|
+
"contentType": "snapshots",
|
|
713
|
+
"version": "memorix-record.2.0"
|
|
714
|
+
},
|
|
715
|
+
|
|
716
|
+
"lifecycle": {
|
|
717
|
+
"createdAt": "2026-06-30T10:00:00.000Z",
|
|
718
|
+
"modifiedAt": "2026-06-30T10:00:00.000Z",
|
|
719
|
+
"capturedAt": "2026-06-30T09:55:00.000Z",
|
|
720
|
+
"status": "active"
|
|
721
|
+
},
|
|
722
|
+
|
|
723
|
+
"concept": {
|
|
724
|
+
"kind": "cve",
|
|
725
|
+
"name": "CVE-2022-29464",
|
|
726
|
+
"title": "CVE-2022-29464",
|
|
727
|
+
"summary": "Critical unrestricted file upload vulnerability affecting WSO2 products.",
|
|
728
|
+
"keywords": ["CVE-2022-29464", "WSO2", "critical"]
|
|
729
|
+
},
|
|
730
|
+
|
|
731
|
+
"data": {
|
|
732
|
+
"cveId": "CVE-2022-29464",
|
|
733
|
+
"severity": "critical",
|
|
734
|
+
"description": "Unrestricted file upload vulnerability affecting WSO2 products.",
|
|
735
|
+
"publishedAt": "2022-04-18"
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
"associatedData": {
|
|
739
|
+
"affectedAssets": [
|
|
740
|
+
{
|
|
741
|
+
"assetName": "prod-api-01",
|
|
742
|
+
"assetIp": "10.100.18.64",
|
|
743
|
+
"recordId": "entity:asset:10.100.18.64"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"assetName": "prod-db-02",
|
|
747
|
+
"assetIp": "10.100.18.65",
|
|
748
|
+
"recordId": "entity:asset:10.100.18.65"
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
},
|
|
752
|
+
|
|
753
|
+
"analytics": {
|
|
754
|
+
"affectedAssets": {
|
|
755
|
+
"total": 2,
|
|
756
|
+
"production": 2,
|
|
757
|
+
"internetExposed": 1
|
|
758
|
+
},
|
|
759
|
+
"relatedFindings": {
|
|
760
|
+
"total": 4,
|
|
761
|
+
"open": 3,
|
|
762
|
+
"closed": 1
|
|
763
|
+
}
|
|
764
|
+
},
|
|
765
|
+
|
|
766
|
+
"insights": {
|
|
767
|
+
"risk": {
|
|
768
|
+
"level": "critical",
|
|
769
|
+
"score": 92,
|
|
770
|
+
"confidence": 0.84,
|
|
771
|
+
"reason": "The CVE affects production assets, including one internet-exposed asset."
|
|
772
|
+
},
|
|
773
|
+
"priority": {
|
|
774
|
+
"level": "urgent",
|
|
775
|
+
"reason": "Production exposure and critical severity require fast remediation."
|
|
776
|
+
},
|
|
777
|
+
"recommendations": [
|
|
778
|
+
{
|
|
779
|
+
"action": "Patch affected assets",
|
|
780
|
+
"urgency": "high"
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
},
|
|
784
|
+
|
|
785
|
+
"index": {
|
|
786
|
+
"text": {
|
|
787
|
+
"title": "CVE-2022-29464",
|
|
788
|
+
"summary": "Critical unrestricted file upload vulnerability affecting WSO2 products.",
|
|
789
|
+
"keywords": ["CVE-2022-29464", "critical", "prod-api-01", "prod-db-02"]
|
|
790
|
+
},
|
|
791
|
+
"facets": {
|
|
792
|
+
"severity": "critical",
|
|
793
|
+
"affectedAssetsTotal": 2,
|
|
794
|
+
"internetExposedAssets": 1
|
|
795
|
+
},
|
|
796
|
+
"recordLinks": [
|
|
797
|
+
{
|
|
798
|
+
"key": "affectedAssets",
|
|
799
|
+
"relation": "affects",
|
|
800
|
+
"direction": "out",
|
|
801
|
+
"target": {
|
|
802
|
+
"recordId": "entity:asset:10.100.18.64",
|
|
803
|
+
"target": "entity",
|
|
804
|
+
"objectType": "assets"
|
|
805
|
+
},
|
|
806
|
+
"display": {
|
|
807
|
+
"name": "prod-api-01"
|
|
808
|
+
},
|
|
809
|
+
"indexed": true
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
"key": "affectedAssets",
|
|
813
|
+
"relation": "affects",
|
|
814
|
+
"direction": "out",
|
|
815
|
+
"target": {
|
|
816
|
+
"recordId": "entity:asset:10.100.18.65",
|
|
817
|
+
"target": "entity",
|
|
818
|
+
"objectType": "assets"
|
|
819
|
+
},
|
|
820
|
+
"display": {
|
|
821
|
+
"name": "prod-db-02"
|
|
822
|
+
},
|
|
823
|
+
"indexed": true
|
|
824
|
+
}
|
|
825
|
+
]
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
"sourceFacts": {
|
|
829
|
+
"schemaVersion": "sourceFacts.v1",
|
|
830
|
+
"values": {
|
|
831
|
+
"severityCode": {
|
|
832
|
+
"rawValue": 4,
|
|
833
|
+
"kind": "enum-code",
|
|
834
|
+
"readable": false,
|
|
835
|
+
"resolvedToPath": "data.severity",
|
|
836
|
+
"resolvedValue": "critical"
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
"_system": {
|
|
842
|
+
"memorix": {
|
|
843
|
+
"entity": "vulnerabilities",
|
|
844
|
+
"contentType": "snapshots",
|
|
845
|
+
"operation": "upsert"
|
|
846
|
+
},
|
|
847
|
+
"processing": {
|
|
848
|
+
"graphRuns": {},
|
|
849
|
+
"jobTypeRuns": {}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## N. Final mental model
|
|
858
|
+
|
|
859
|
+
Use this as the lighthouse:
|
|
860
|
+
|
|
861
|
+
```txt id="tbgvry"
|
|
862
|
+
identity = how Memorix identifies the record
|
|
863
|
+
|
|
864
|
+
lifecycle = when the record exists and changed
|
|
865
|
+
|
|
866
|
+
concept = what the record is
|
|
867
|
+
|
|
868
|
+
data = native readable facts about the record
|
|
869
|
+
|
|
870
|
+
associatedData = readable related records/context attached for consumption
|
|
871
|
+
|
|
872
|
+
analytics = numeric rollups about linked lists
|
|
873
|
+
|
|
874
|
+
insights = interpretations, judgments, scores, and recommendations
|
|
875
|
+
|
|
876
|
+
index = internal search/link/index surface
|
|
877
|
+
|
|
878
|
+
sourceFacts = raw source values and resolver inputs
|
|
879
|
+
|
|
880
|
+
_system = Memorix-managed metadata, processing, provenance, and audit
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
The clean separation is:
|
|
884
|
+
|
|
885
|
+
```txt id="blxjhb"
|
|
886
|
+
Facts stay in data.
|
|
887
|
+
Related context goes in associatedData.
|
|
888
|
+
Numbers about related context go in analytics.
|
|
889
|
+
Conclusions go in insights.
|
|
890
|
+
Links go in index.
|
|
891
|
+
Raw source mess goes in sourceFacts.
|
|
892
|
+
System state goes in _system.
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
---------------------------
|
|
897
|
+
|
|
898
|
+
```txt id="m7zj8c"
|
|
899
|
+
The full rich Memorix record shape exists only on snapshot content types / snapshot collections.
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
Meaning these extra consumption/summary layers should be **snapshot-only by default**:
|
|
903
|
+
|
|
904
|
+
```txt id="g8756x"
|
|
905
|
+
associatedData
|
|
906
|
+
analytics
|
|
907
|
+
insights
|
|
908
|
+
full record-level index projections
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
So for example:
|
|
912
|
+
|
|
913
|
+
```txt id="cpyqdi"
|
|
914
|
+
vulnerabilities-snapshots ✅ full record shape
|
|
915
|
+
vulnerabilities-events ❌ not full shape
|
|
916
|
+
vulnerabilities-facts ❌ not full shape
|
|
917
|
+
vulnerabilities-chunks ❌ not full shape
|
|
918
|
+
vulnerabilities-history ❌ not full shape
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
The clean rule:
|
|
922
|
+
|
|
923
|
+
```txt id="0fdcos"
|
|
924
|
+
Snapshots are the full readable object view.
|
|
925
|
+
Other content types are specialized records.
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
For non-snapshot collections, the record should usually have only what the content type actually needs:
|
|
929
|
+
|
|
930
|
+
```txt id="nqyy5v"
|
|
931
|
+
identity
|
|
932
|
+
lifecycle, if relevant
|
|
933
|
+
concept, if useful
|
|
934
|
+
data
|
|
935
|
+
sourceFacts, if raw/resolution material exists
|
|
936
|
+
_system
|
|
937
|
+
minimal index, only if that content type needs indexing
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
So the final model should say:
|
|
941
|
+
|
|
942
|
+
```txt id="8ba054"
|
|
943
|
+
data exists on every content record.
|
|
944
|
+
|
|
945
|
+
associatedData, analytics, and insights belong to snapshots only by default.
|
|
946
|
+
|
|
947
|
+
index may exist outside snapshots only when the content type itself requires indexing, like chunks or searchable facts.
|
|
948
|
+
|
|
949
|
+
_system and sourceFacts can exist anywhere when needed.
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
I would add this sentence to the appendix:
|
|
953
|
+
|
|
954
|
+
```txt id="65cb87"
|
|
955
|
+
Unless a descriptor explicitly opts in, associatedData, analytics, and insights are allowed only on snapshot content types, because they represent the full materialized view of the object, not the minimal record format for every collection.
|
|
956
|
+
```
|