@x12i/static-memorix-explorer-api 1.0.0 → 1.1.1

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 (49) hide show
  1. package/README.md +150 -16
  2. package/dist/config.js +1 -1
  3. package/dist/engine/identity.js +11 -9
  4. package/dist/engine/write.js +48 -4
  5. package/dist/routes/index.js +57 -7
  6. package/dist/server.js +42 -20
  7. package/guides/demo-app.md +524 -0
  8. package/guides/managing-json-files.md +310 -0
  9. package/guides/running-the-service.md +171 -0
  10. package/guides/using-the-api.md +265 -0
  11. package/mocks/data/admin/snapshots.json +52 -0
  12. package/mocks/data/assets/analysis.json +14 -0
  13. package/mocks/data/assets/decisions.json +14 -0
  14. package/mocks/data/assets/events.json +4 -0
  15. package/mocks/data/assets/snapshots.json +49 -0
  16. package/mocks/data/findings/analysis.json +4 -0
  17. package/mocks/data/findings/decisions.json +4 -0
  18. package/mocks/data/findings/events.json +3 -0
  19. package/mocks/data/findings/snapshots.json +21 -0
  20. package/mocks/data/marketing/snapshots.json +75 -0
  21. package/mocks/data/memory/memory.json +31 -0
  22. package/mocks/data/product/snapshots.json +84 -0
  23. package/mocks/data/system/inventory.json +8 -0
  24. package/mocks/data/users/snapshots.json +5 -0
  25. package/mocks/demo.html +828 -0
  26. package/mocks/metadata/agents.json +7 -0
  27. package/mocks/metadata/lists/assets-default.json +22 -0
  28. package/mocks/metadata/lists/backlog.json +10 -0
  29. package/mocks/metadata/lists/findings-default.json +9 -0
  30. package/mocks/metadata/lists/my-plate.json +10 -0
  31. package/mocks/metadata/lists/this-week.json +10 -0
  32. package/mocks/metadata/narratives/admin.json +7 -0
  33. package/mocks/metadata/narratives/assets.json +17 -0
  34. package/mocks/metadata/narratives/findings.json +7 -0
  35. package/mocks/metadata/narratives/marketing.json +17 -0
  36. package/mocks/metadata/narratives/product.json +12 -0
  37. package/mocks/metadata/object-types/admin.json +6 -0
  38. package/mocks/metadata/object-types/assets.json +9 -0
  39. package/mocks/metadata/object-types/findings.json +8 -0
  40. package/mocks/metadata/object-types/marketing.json +6 -0
  41. package/mocks/metadata/object-types/memory.json +6 -0
  42. package/mocks/metadata/object-types/product.json +6 -0
  43. package/mocks/metadata/object-types/users.json +13 -0
  44. package/mocks/metadata/write-descriptors/admin-task-write.json +20 -0
  45. package/mocks/metadata/write-descriptors/assets-analysis-write.json +14 -0
  46. package/mocks/metadata/write-descriptors/marketing-task-write.json +20 -0
  47. package/mocks/metadata/write-descriptors/memory-write.json +14 -0
  48. package/mocks/metadata/write-descriptors/product-task-write.json +20 -0
  49. package/package.json +5 -2
@@ -0,0 +1,310 @@
1
+ # Managing JSON Files
2
+
3
+ The mock server stores all state in JSON files under the `mocks/` directory. At
4
+ startup, every file is loaded into an in-memory store. Mutations happen in
5
+ memory and are debounced-flushed back to disk automatically.
6
+
7
+ ## Directory structure
8
+
9
+ ```
10
+ mocks/
11
+ metadata/ # schema, config, descriptors
12
+ agents.json
13
+ object-types/
14
+ assets.json
15
+ findings.json
16
+ memory.json
17
+ product.json
18
+ marketing.json
19
+ admin.json
20
+ users.json
21
+ lists/
22
+ assets-default.json
23
+ findings-default.json
24
+ my-plate.json
25
+ this-week.json
26
+ backlog.json
27
+ narratives/
28
+ assets.json
29
+ findings.json
30
+ product.json
31
+ marketing.json
32
+ admin.json
33
+ write-descriptors/
34
+ assets-analysis-write.json
35
+ memory-write.json
36
+ product-task-write.json
37
+ marketing-task-write.json
38
+ admin-task-write.json
39
+ data/ # record collections
40
+ assets/
41
+ snapshots.json
42
+ analysis.json
43
+ decisions.json
44
+ events.json
45
+ findings/
46
+ snapshots.json
47
+ analysis.json
48
+ decisions.json
49
+ events.json
50
+ product/
51
+ snapshots.json
52
+ marketing/
53
+ snapshots.json
54
+ admin/
55
+ snapshots.json
56
+ users/
57
+ snapshots.json
58
+ memory/
59
+ memory.json
60
+ system/
61
+ inventory.json
62
+ ```
63
+
64
+ ## Metadata files
65
+
66
+ Metadata files define the shape of the system: what object types exist, how
67
+ lists are queried, what narratives are authored, and how writes are validated.
68
+
69
+ ### agents.json
70
+
71
+ Registry of agents that participate in the pipeline.
72
+
73
+ ```json
74
+ {
75
+ "agents": [
76
+ { "id": "agent-1", "name": "Discovery Agent", "role": "ingestion" },
77
+ { "id": "agent-2", "name": "Analysis Agent", "role": "enrichment" },
78
+ { "id": "agent-3", "name": "Decision Agent", "role": "remediation" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ ### object-types/\*.json
84
+
85
+ Each file defines one object type. The `catalogRelations` array declares
86
+ relationships between objects. The `rootPropertyCatalog` is computed at runtime
87
+ by the `POST .../root-property-catalog/compute` endpoint.
88
+
89
+ ```json
90
+ {
91
+ "name": "assets",
92
+ "summary": "Infrastructure assets discovered and analyzed by Memorix.",
93
+ "catalogRelations": [
94
+ {
95
+ "sourceProperty": "linkedAssetId",
96
+ "targetObject": "assets",
97
+ "targetProperty": "recordId",
98
+ "relationType": "self-link"
99
+ },
100
+ {
101
+ "sourceProperty": "ownerId",
102
+ "targetObject": "agents",
103
+ "targetProperty": "id",
104
+ "relationType": "ownership"
105
+ }
106
+ ],
107
+ "rootPropertyCatalog": []
108
+ }
109
+ ```
110
+
111
+ To add a new object type: create a new `.json` file under
112
+ `mocks/metadata/object-types/` and add a matching directory under
113
+ `mocks/data/` with the content type files (at minimum `snapshots.json`).
114
+
115
+ ### lists/\*.json
116
+
117
+ List descriptors define saved queries: which entity they target, which fields
118
+ to project, what extensions to join, and default sort order.
119
+
120
+ ```json
121
+ {
122
+ "id": "assets-default",
123
+ "entity": "assets",
124
+ "entityName": "assets",
125
+ "baseContentType": "snapshots",
126
+ "fields": ["recordId", "entityId", "severity", "status"],
127
+ "extensions": ["analysis", "decisions"],
128
+ "sort": { "severity": -1, "entityId": 1 },
129
+ "query": {},
130
+ "listId": "assets-default"
131
+ }
132
+ ```
133
+
134
+ - `fields` controls which properties appear in the projected result.
135
+ - `extensions` names additional content types to join (e.g. `analysis`,
136
+ `decisions`) via the snapshot alias pipeline.
137
+ - `sort` is a mingo-compatible sort document (`1` = asc, `-1` = desc).
138
+
139
+ ### narratives/\*.json
140
+
141
+ Authored narratives are key-value maps. Each key becomes a virtual tag that
142
+ records can carry.
143
+
144
+ ```json
145
+ {
146
+ "has-vulns": {
147
+ "key": "has-vulns",
148
+ "label": "Has Vulnerabilities",
149
+ "description": "Asset carries at least one known vulnerability."
150
+ },
151
+ "exposed-public": {
152
+ "key": "exposed-public",
153
+ "label": "Exposed Publicly",
154
+ "description": "Asset is reachable from the public internet."
155
+ }
156
+ }
157
+ ```
158
+
159
+ Records reference narratives by setting `narratives` on the document:
160
+
161
+ ```json
162
+ {
163
+ "recordId": "asset-1",
164
+ "narratives": { "has-vulns": true, "exposed-public": true }
165
+ }
166
+ ```
167
+
168
+ ### write-descriptors/\*.json
169
+
170
+ Write descriptors define the JSON Schema for write operations and the target
171
+ collection.
172
+
173
+ ```json
174
+ {
175
+ "writeDescriptorId": "assets-analysis-write",
176
+ "targetCollection": "assets/analysis",
177
+ "schema": {
178
+ "type": "object",
179
+ "required": ["recordId"],
180
+ "properties": {
181
+ "recordId": { "type": "string" },
182
+ "analysisType": { "type": "string" },
183
+ "score": { "type": "number" },
184
+ "notes": { "type": "array" }
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ - `targetCollection` is an `objectType/contentType` pair (e.g. `assets/analysis`).
191
+ - `schema` follows a subset of JSON Schema: `required` array and `properties`
192
+ map with `type` constraints. The write engine validates input against this
193
+ before executing the operation.
194
+ - **Required semantics:** `required` is enforced fully only for `add` /
195
+ `replace`. For `upsert` / `patch` / `delete`, only the identity field
196
+ (`recordId`, or `memoryId` on the memory tier) is mandatory. This lets the
197
+ UI send partial updates like `{recordId, status}` without re-supplying every
198
+ required field.
199
+
200
+ The demo app's task descriptors (`product-task-write.json`,
201
+ `marketing-task-write.json`, `admin-task-write.json`) all use this pattern.
202
+ See [demo-app.md](./demo-app.md) for the full walkthrough.
203
+
204
+ ## Data files
205
+
206
+ Data files hold the actual record collections. Each object type gets its own
207
+ directory, and within it, one file per content type.
208
+
209
+ ### Content types
210
+
211
+ | Content type | File | Identity key | Purpose |
212
+ | ------------- | ------------------- | -------------- | ------------------------------ |
213
+ | `snapshots` | `snapshots.json` | `recordId` | Entity snapshots (primary tier)|
214
+ | `analysis` | `analysis.json` | `recordId` | Knowledge/analysis records |
215
+ | `decisions` | `decisions.json` | `recordId` | Decision records |
216
+ | `events` | `events.json` | `eventId` | Event records |
217
+ | `memory` | `memory.json` | `memoryId` | Fourth tier (standalone) |
218
+
219
+ ### Record structure
220
+
221
+ Records are plain JSON objects. Every record must carry exactly one identity
222
+ key. Records can include narrative tags and associated data buckets.
223
+
224
+ ```json
225
+ [
226
+ {
227
+ "recordId": "asset-1",
228
+ "entityId": "web-prod-01",
229
+ "severity": "critical",
230
+ "status": "open",
231
+ "region": "us-east-1",
232
+ "ownerId": "agent-1",
233
+ "narratives": { "has-vulns": true, "exposed-public": true },
234
+ "associatedData": [
235
+ { "kind": "tag", "value": "prod" },
236
+ { "kind": "tag", "value": "web" }
237
+ ],
238
+ "associatedInferred": [
239
+ { "kind": "discovery", "value": "public-ip" }
240
+ ],
241
+ "associatedAnalysis": [
242
+ { "kind": "analysis", "value": "cve-score-9" }
243
+ ]
244
+ }
245
+ ]
246
+ ```
247
+
248
+ ### Associated data buckets
249
+
250
+ Records carry associated data in prefixed fields. The API translates these
251
+ between storage format and the normalized `associated` bucket returned to
252
+ clients.
253
+
254
+ | Storage field | Client alias | Description |
255
+ | ----------------------- | --------------- | ---------------------- |
256
+ | `associatedData` | `data` | Explicit tags |
257
+ | `associatedDiscovery` | `discovery` | Discovery-phase data |
258
+ | `associatedInferred` | `discovery` | Inferred discovery |
259
+ | `associatedAnalysis` | `analysis` | Analysis-phase data |
260
+ | `associated{Custom}` | `{custom}` | Custom bucket |
261
+
262
+ ### Memory tier
263
+
264
+ Memory is the fourth data tier. It lives under `mocks/data/memory/memory.json`
265
+ and uses `memoryId` as its identity key (instead of `recordId`).
266
+
267
+ ```json
268
+ [
269
+ {
270
+ "memoryId": "mem-1",
271
+ "entityId": "web-prod-01",
272
+ "kind": "conversation-summary",
273
+ "summary": "Discussed upgrade path for web-prod-01 from v2 to v3.",
274
+ "createdAt": "2026-07-10T08:00:00Z"
275
+ }
276
+ ]
277
+ ```
278
+
279
+ ### System inventory
280
+
281
+ `mocks/data/system/inventory.json` is a special file used by the inventory
282
+ engine for policy configuration.
283
+
284
+ ## How the store works
285
+
286
+ 1. On startup, `InMemoryStore.load()` reads all metadata and data JSON files
287
+ into JavaScript `Map` objects.
288
+ 2. All API reads serve from the in-memory maps (no disk I/O per request).
289
+ 3. Writes mutate the in-memory maps and mark files as "dirty".
290
+ 4. Dirty files are flushed to disk after a debounce (default 300ms, configured
291
+ via `DISK_FLUSH_DEBOUNCE_MS`).
292
+ 5. On `SIGINT`/`SIGTERM`, all dirty files are flushed immediately before
293
+ shutdown.
294
+
295
+ You can modify JSON files on disk while the server is running, but changes
296
+ won't be picked up until the next restart (the store loads once at boot). If
297
+ you need to persist changes made via the API, they are written back
298
+ automatically.
299
+
300
+ ## Adding a new object type
301
+
302
+ 1. Create `mocks/metadata/object-types/<name>.json` following the schema above.
303
+ 2. Create `mocks/data/<name>/` directory.
304
+ 3. Add at least `mocks/data/<name>/snapshots.json` with an empty array `[]`.
305
+ 4. Optionally add `analysis.json`, `decisions.json`, `events.json`.
306
+ 5. Create `mocks/metadata/lists/<name>-default.json` for a default list view.
307
+ 6. Create `mocks/metadata/narratives/<name>.json` if the object type has
308
+ authored narratives.
309
+ 7. Restart the server. The new object type appears in
310
+ `GET /api/explorer/object-types` and the inventory.
@@ -0,0 +1,171 @@
1
+ # Running the Service
2
+
3
+ The mock server runs as a CLI tool or as a development server. It listens for
4
+ HTTP requests on a configurable port and serves the full Memorix Explorer API
5
+ from in-memory state backed by JSON files on disk.
6
+
7
+ ## Installation
8
+
9
+ ### From npm (global)
10
+
11
+ ```bash
12
+ npm install -g @x12i/static-memorix-explorer-api
13
+ ```
14
+
15
+ This makes the `mock-memorix-explorer-api` command available globally.
16
+
17
+ ### From source (git clone)
18
+
19
+ ```bash
20
+ git clone https://github.com/x12i/static-memorix-explorer-api.git
21
+ cd static-memorix-explorer-api
22
+ npm install
23
+ ```
24
+
25
+ ## Starting the server
26
+
27
+ ### Production mode (build then run)
28
+
29
+ ```bash
30
+ npm run build
31
+ npm start
32
+ ```
33
+
34
+ ### Development mode (no build step)
35
+
36
+ ```bash
37
+ npm run dev
38
+ ```
39
+
40
+ Uses Node's `--experimental-strip-types` to run TypeScript directly. Faster
41
+ iteration, but not suitable for distribution.
42
+
43
+ ### As a global CLI
44
+
45
+ ```bash
46
+ mock-memorix-explorer-api
47
+ ```
48
+
49
+ After `npm install -g`, the CLI starts the server with default settings. The
50
+ mocks directory defaults to a `mocks/` folder relative to the installed package.
51
+
52
+ ### With `npx` (no global install)
53
+
54
+ ```bash
55
+ npx @x12i/static-memorix-explorer-api
56
+ ```
57
+
58
+ ### From application code
59
+
60
+ Importing the package does not open a port. Call `startServer()` explicitly:
61
+
62
+ ```js
63
+ import { startServer } from "@x12i/static-memorix-explorer-api";
64
+
65
+ const app = await startServer({
66
+ port: 5030,
67
+ host: "127.0.0.1",
68
+ installSignalHandlers: false,
69
+ });
70
+
71
+ // Later, for example during test teardown:
72
+ await app.close();
73
+ ```
74
+
75
+ For tests that do not need a real TCP listener, build the Fastify app and use
76
+ its injection API:
77
+
78
+ ```js
79
+ import { buildServer } from "@x12i/static-memorix-explorer-api";
80
+
81
+ const app = await buildServer();
82
+ const response = await app.inject({ method: "GET", url: "/health" });
83
+ console.log(response.json()); // { ok: true }
84
+ await app.close();
85
+ ```
86
+
87
+ ## Configuration
88
+
89
+ All configuration is via environment variables. Defaults work out of the box.
90
+
91
+ ### Core
92
+
93
+ | Variable | Default | Description |
94
+ | --------- | ----------- | ------------------------------ |
95
+ | `PORT` | `5030` | HTTP listen port |
96
+ | `HOST` | `0.0.0.0` | HTTP listen host |
97
+ | `MOCKS_DIR` | `<project>/mocks` | Root directory for JSON fixtures |
98
+
99
+ ### DB Router Simulation
100
+
101
+ These variables control the fake database routing strings returned by the
102
+ health endpoint. The frontend uses these to verify it is connected to the
103
+ correct tenant.
104
+
105
+ | Variable | Default | Description |
106
+ | --------------------------- | ----------------- | ---------------------------------------- |
107
+ | `MEMORIX_ORG_ID` | `memorix` | Organization prefix |
108
+ | `MEMORIX_AGENT_ID` | `default-agent` | Agent prefix for Catalox DB routing |
109
+ | `MEMORIX_DEPLOYMENT_PROFILE`| `default` | When `ebook`, forces org/agent to `ebooks`|
110
+
111
+ The health endpoint returns:
112
+ - `memorixDb`: `"<orgId>-memorix-entities + <orgId>-memorix-events"`
113
+ - `cataloxDb`: `"<agentId>-memorix-catalox"`
114
+
115
+ ### Feature flags
116
+
117
+ Mutations (POST/PATCH/PUT/DELETE) on lists, narratives, and records are gated
118
+ behind feature flags. All default to `false`.
119
+
120
+ | Variable | Controls |
121
+ | ------------------------------------------- | --------------------- |
122
+ | `MEMORIX_EXPLORER_ENABLE_METADATA_WRITES` | List & narrative CRUD |
123
+ | `MEMORIX_EXPLORER_ENABLE_PIPELINE_WRITES` | Pipeline writes |
124
+ | `MEMORIX_EXPLORER_ENABLE_REGISTRY_WRITES` | Registry writes |
125
+
126
+ Set to `true` or `1` to enable:
127
+
128
+ ```bash
129
+ MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true mock-memorix-explorer-api
130
+ ```
131
+
132
+ ### Disk flush
133
+
134
+ | Variable | Default | Description |
135
+ | -------------------------- | ------- | --------------------------------------- |
136
+ | `DISK_FLUSH_DEBOUNCE_MS` | `300` | Milliseconds to debounce before writing dirty files to disk |
137
+
138
+ ### Example: full custom configuration
139
+
140
+ ```bash
141
+ PORT=8080 \
142
+ HOST=127.0.0.1 \
143
+ MOCKS_DIR=/path/to/my/fixtures \
144
+ MEMORIX_ORG_ID=neo \
145
+ MEMORIX_AGENT_ID=neo-agent \
146
+ MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true \
147
+ mock-memorix-explorer-api
148
+ ```
149
+
150
+ ## Health checks
151
+
152
+ Once running, verify the server is up:
153
+
154
+ ```bash
155
+ curl localhost:5030/health
156
+ # {"ok":true}
157
+
158
+ curl "localhost:5030/api/explorer/health?includeInventory=1"
159
+ # {"ok":true,"mongoUriConfigured":true,"discoverySample":["assets","findings","memory"],...}
160
+ ```
161
+
162
+ ## Graceful shutdown
163
+
164
+ The server handles `SIGINT` and `SIGTERM`. On shutdown it:
165
+ 1. Flushes all dirty in-memory state to disk.
166
+ 2. Closes the Fastify listener.
167
+ 3. Exits with code 0.
168
+
169
+ This means mutations made via the API are always persisted as long as the
170
+ server shuts down cleanly. If the process is killed with `SIGKILL`, unsaved
171
+ state is lost.