@x12i/static-memorix 2.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/dist/cli.d.ts +17 -0
  4. package/dist/cli.js +278 -0
  5. package/dist/config.d.ts +39 -0
  6. package/dist/config.js +109 -0
  7. package/dist/engine/aliases.d.ts +32 -0
  8. package/dist/engine/aliases.js +74 -0
  9. package/dist/engine/associations.d.ts +26 -0
  10. package/dist/engine/associations.js +60 -0
  11. package/dist/engine/identity.d.ts +22 -0
  12. package/dist/engine/identity.js +50 -0
  13. package/dist/engine/inventory.d.ts +29 -0
  14. package/dist/engine/inventory.js +133 -0
  15. package/dist/engine/lists.d.ts +18 -0
  16. package/dist/engine/lists.js +62 -0
  17. package/dist/engine/narratives.d.ts +16 -0
  18. package/dist/engine/narratives.js +67 -0
  19. package/dist/engine/objectTypes.d.ts +11 -0
  20. package/dist/engine/objectTypes.js +71 -0
  21. package/dist/engine/query.d.ts +38 -0
  22. package/dist/engine/query.js +191 -0
  23. package/dist/engine/records.d.ts +23 -0
  24. package/dist/engine/records.js +152 -0
  25. package/dist/engine/snapshots.d.ts +17 -0
  26. package/dist/engine/snapshots.js +110 -0
  27. package/dist/engine/write.d.ts +30 -0
  28. package/dist/engine/write.js +163 -0
  29. package/dist/routes/helpers.d.ts +4 -0
  30. package/dist/routes/helpers.js +29 -0
  31. package/dist/routes/index.d.ts +2 -0
  32. package/dist/routes/index.js +402 -0
  33. package/dist/server.d.ts +15 -0
  34. package/dist/server.js +130 -0
  35. package/dist/storage/InMemoryStore.d.ts +31 -0
  36. package/dist/storage/InMemoryStore.js +242 -0
  37. package/dist/storage/fs.d.ts +6 -0
  38. package/dist/storage/fs.js +54 -0
  39. package/dist/types.d.ts +88 -0
  40. package/dist/types.js +37 -0
  41. package/guides/demo-app.md +527 -0
  42. package/guides/managing-json-files.md +384 -0
  43. package/guides/running-the-service.md +269 -0
  44. package/guides/using-the-api.md +265 -0
  45. package/mocks/data/admin/snapshots.json +52 -0
  46. package/mocks/data/assets/analysis.json +14 -0
  47. package/mocks/data/assets/decisions.json +14 -0
  48. package/mocks/data/assets/events.json +4 -0
  49. package/mocks/data/assets/snapshots.json +49 -0
  50. package/mocks/data/findings/analysis.json +4 -0
  51. package/mocks/data/findings/decisions.json +4 -0
  52. package/mocks/data/findings/events.json +3 -0
  53. package/mocks/data/findings/snapshots.json +21 -0
  54. package/mocks/data/marketing/snapshots.json +75 -0
  55. package/mocks/data/memory/memory.json +31 -0
  56. package/mocks/data/product/snapshots.json +84 -0
  57. package/mocks/data/system/inventory.json +8 -0
  58. package/mocks/data/users/snapshots.json +5 -0
  59. package/mocks/demo.html +828 -0
  60. package/mocks/metadata/agents.json +7 -0
  61. package/mocks/metadata/lists/assets-default.json +22 -0
  62. package/mocks/metadata/lists/backlog.json +10 -0
  63. package/mocks/metadata/lists/findings-default.json +9 -0
  64. package/mocks/metadata/lists/my-plate.json +10 -0
  65. package/mocks/metadata/lists/this-week.json +10 -0
  66. package/mocks/metadata/narratives/admin.json +7 -0
  67. package/mocks/metadata/narratives/assets.json +17 -0
  68. package/mocks/metadata/narratives/findings.json +7 -0
  69. package/mocks/metadata/narratives/marketing.json +17 -0
  70. package/mocks/metadata/narratives/product.json +12 -0
  71. package/mocks/metadata/object-types/admin.json +6 -0
  72. package/mocks/metadata/object-types/assets.json +9 -0
  73. package/mocks/metadata/object-types/findings.json +8 -0
  74. package/mocks/metadata/object-types/marketing.json +6 -0
  75. package/mocks/metadata/object-types/memory.json +6 -0
  76. package/mocks/metadata/object-types/product.json +6 -0
  77. package/mocks/metadata/object-types/users.json +13 -0
  78. package/mocks/metadata/write-descriptors/admin-task-write.json +20 -0
  79. package/mocks/metadata/write-descriptors/assets-analysis-write.json +14 -0
  80. package/mocks/metadata/write-descriptors/marketing-task-write.json +20 -0
  81. package/mocks/metadata/write-descriptors/memory-write.json +14 -0
  82. package/mocks/metadata/write-descriptors/product-task-write.json +20 -0
  83. package/package.json +67 -0
@@ -0,0 +1,384 @@
1
+ # Managing JSON Files
2
+
3
+ ## File-backed database routing
4
+
5
+ The static server has no database, so database routing is represented by a
6
+ reserved filename prefix followed by `--`.
7
+
8
+ With:
9
+
10
+ ```bash
11
+ MEMORIX_ORG_ID=neo
12
+ MEMORIX_AGENT_ID=neo-agent
13
+ ```
14
+
15
+ the routing matrix is:
16
+
17
+ | Data kind | Filename prefix | Example |
18
+ | --- | --- | --- |
19
+ | Snapshots, analysis, decisions | `neo-memorix-entities--` | `data/product/neo-memorix-entities--snapshots.json` |
20
+ | Events | `neo-memorix-events--` | `data/product/neo-memorix-events--events.json` |
21
+ | Memory | `neo-agent-memorix-catalox--` | `data/memory/neo-agent-memorix-catalox--memory.json` |
22
+ | Metadata | `neo-agent-memorix-catalox--` | `metadata/lists/neo-agent-memorix-catalox--my-plate.json` |
23
+
24
+ ### Read and write precedence
25
+
26
+ 1. The server first looks for the routed, prefixed file.
27
+ 2. If it does not exist, it reads the matching unprefixed file as seed data.
28
+ 3. Any mutation writes the complete resulting collection to the routed file.
29
+ 4. Unprefixed seed files are never overwritten while routed mode is active.
30
+ 5. Files belonging to a different prefix are ignored by the active process.
31
+
32
+ Deleting a metadata document writes JSON `null` as a routed tombstone. This is
33
+ intentional: removing only the routed file would cause the unprefixed seed to
34
+ reappear on the next restart.
35
+
36
+ This makes onboarding migration-safe: existing files such as
37
+ `product/snapshots.json` continue to seed a new tenant, while the first write
38
+ creates `product/neo-memorix-entities--snapshots.json`. Starting with another
39
+ organization ID selects a different file and therefore different state.
40
+
41
+ The delimiter `--` is reserved. Do not use it inside ordinary, unprefixed
42
+ fixture basenames.
43
+
44
+ Routing IDs are validated before any files are opened. Organization IDs,
45
+ agent IDs, object types, and metadata names may contain letters, numbers,
46
+ dot, underscore, and hyphen, but cannot contain path separators, `..`, or the
47
+ reserved `--` delimiter.
48
+
49
+ Malformed or empty JSON stops startup with the affected path in the error;
50
+ it is never silently converted to an empty collection. Writes use a temporary
51
+ file and same-directory rename so readers never observe a partially written
52
+ JSON document.
53
+
54
+ Routing is process-scoped. Run one server process per organization/agent route,
55
+ and use only one writer process for a given prefix. Two processes writing the
56
+ same routed file concurrently use last-writer-wins semantics.
57
+
58
+ ### Inspect the active route
59
+
60
+ ```bash
61
+ curl http://localhost:5030/api/explorer/health
62
+ ```
63
+
64
+ The response includes:
65
+
66
+ ```json
67
+ {
68
+ "fileRouting": {
69
+ "entitiesPrefix": "neo-memorix-entities",
70
+ "eventsPrefix": "neo-memorix-events",
71
+ "memoryPrefix": "neo-agent-memorix-catalox",
72
+ "metadataPrefix": "neo-agent-memorix-catalox"
73
+ }
74
+ }
75
+ ```
76
+
77
+ The mock server stores all state in JSON files under the `mocks/` directory. At
78
+ startup, every file is loaded into an in-memory store. Mutations happen in
79
+ memory and are debounced-flushed back to disk automatically.
80
+
81
+ ## Directory structure
82
+
83
+ ```
84
+ mocks/
85
+ metadata/ # schema, config, descriptors
86
+ agents.json
87
+ object-types/
88
+ assets.json
89
+ findings.json
90
+ memory.json
91
+ product.json
92
+ marketing.json
93
+ admin.json
94
+ users.json
95
+ lists/
96
+ assets-default.json
97
+ findings-default.json
98
+ my-plate.json
99
+ this-week.json
100
+ backlog.json
101
+ narratives/
102
+ assets.json
103
+ findings.json
104
+ product.json
105
+ marketing.json
106
+ admin.json
107
+ write-descriptors/
108
+ assets-analysis-write.json
109
+ memory-write.json
110
+ product-task-write.json
111
+ marketing-task-write.json
112
+ admin-task-write.json
113
+ data/ # record collections
114
+ assets/
115
+ snapshots.json
116
+ analysis.json
117
+ decisions.json
118
+ events.json
119
+ findings/
120
+ snapshots.json
121
+ analysis.json
122
+ decisions.json
123
+ events.json
124
+ product/
125
+ snapshots.json
126
+ marketing/
127
+ snapshots.json
128
+ admin/
129
+ snapshots.json
130
+ users/
131
+ snapshots.json
132
+ memory/
133
+ memory.json
134
+ system/
135
+ inventory.json
136
+ ```
137
+
138
+ ## Metadata files
139
+
140
+ Metadata files define the shape of the system: what object types exist, how
141
+ lists are queried, what narratives are authored, and how writes are validated.
142
+
143
+ ### agents.json
144
+
145
+ Registry of agents that participate in the pipeline.
146
+
147
+ ```json
148
+ {
149
+ "agents": [
150
+ { "id": "agent-1", "name": "Discovery Agent", "role": "ingestion" },
151
+ { "id": "agent-2", "name": "Analysis Agent", "role": "enrichment" },
152
+ { "id": "agent-3", "name": "Decision Agent", "role": "remediation" }
153
+ ]
154
+ }
155
+ ```
156
+
157
+ ### object-types/\*.json
158
+
159
+ Each file defines one object type. The `catalogRelations` array declares
160
+ relationships between objects. The `rootPropertyCatalog` is computed at runtime
161
+ by the `POST .../root-property-catalog/compute` endpoint.
162
+
163
+ ```json
164
+ {
165
+ "name": "assets",
166
+ "summary": "Infrastructure assets discovered and analyzed by Memorix.",
167
+ "catalogRelations": [
168
+ {
169
+ "sourceProperty": "linkedAssetId",
170
+ "targetObject": "assets",
171
+ "targetProperty": "recordId",
172
+ "relationType": "self-link"
173
+ },
174
+ {
175
+ "sourceProperty": "ownerId",
176
+ "targetObject": "agents",
177
+ "targetProperty": "id",
178
+ "relationType": "ownership"
179
+ }
180
+ ],
181
+ "rootPropertyCatalog": []
182
+ }
183
+ ```
184
+
185
+ To add a new object type: create a new `.json` file under
186
+ `mocks/metadata/object-types/` and add a matching directory under
187
+ `mocks/data/` with the content type files (at minimum `snapshots.json`).
188
+
189
+ ### lists/\*.json
190
+
191
+ List descriptors define saved queries: which entity they target, which fields
192
+ to project, what extensions to join, and default sort order.
193
+
194
+ ```json
195
+ {
196
+ "id": "assets-default",
197
+ "entity": "assets",
198
+ "entityName": "assets",
199
+ "baseContentType": "snapshots",
200
+ "fields": ["recordId", "entityId", "severity", "status"],
201
+ "extensions": ["analysis", "decisions"],
202
+ "sort": { "severity": -1, "entityId": 1 },
203
+ "query": {},
204
+ "listId": "assets-default"
205
+ }
206
+ ```
207
+
208
+ - `fields` controls which properties appear in the projected result.
209
+ - `extensions` names additional content types to join (e.g. `analysis`,
210
+ `decisions`) via the snapshot alias pipeline.
211
+ - `sort` is a mingo-compatible sort document (`1` = asc, `-1` = desc).
212
+
213
+ ### narratives/\*.json
214
+
215
+ Authored narratives are key-value maps. Each key becomes a virtual tag that
216
+ records can carry.
217
+
218
+ ```json
219
+ {
220
+ "has-vulns": {
221
+ "key": "has-vulns",
222
+ "label": "Has Vulnerabilities",
223
+ "description": "Asset carries at least one known vulnerability."
224
+ },
225
+ "exposed-public": {
226
+ "key": "exposed-public",
227
+ "label": "Exposed Publicly",
228
+ "description": "Asset is reachable from the public internet."
229
+ }
230
+ }
231
+ ```
232
+
233
+ Records reference narratives by setting `narratives` on the document:
234
+
235
+ ```json
236
+ {
237
+ "recordId": "asset-1",
238
+ "narratives": { "has-vulns": true, "exposed-public": true }
239
+ }
240
+ ```
241
+
242
+ ### write-descriptors/\*.json
243
+
244
+ Write descriptors define the JSON Schema for write operations and the target
245
+ collection.
246
+
247
+ ```json
248
+ {
249
+ "writeDescriptorId": "assets-analysis-write",
250
+ "targetCollection": "assets/analysis",
251
+ "schema": {
252
+ "type": "object",
253
+ "required": ["recordId"],
254
+ "properties": {
255
+ "recordId": { "type": "string" },
256
+ "analysisType": { "type": "string" },
257
+ "score": { "type": "number" },
258
+ "notes": { "type": "array" }
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ - `targetCollection` is an `objectType/contentType` pair (e.g. `assets/analysis`).
265
+ - `schema` follows a subset of JSON Schema: `required` array and `properties`
266
+ map with `type` constraints. The write engine validates input against this
267
+ before executing the operation.
268
+ - **Required semantics:** `required` is enforced fully only for `add` /
269
+ `replace`. For `upsert` / `patch` / `delete`, only the identity field
270
+ (`recordId`, or `memoryId` on the memory tier) is mandatory. This lets the
271
+ UI send partial updates like `{recordId, status}` without re-supplying every
272
+ required field.
273
+
274
+ The demo app's task descriptors (`product-task-write.json`,
275
+ `marketing-task-write.json`, `admin-task-write.json`) all use this pattern.
276
+ See [demo-app.md](./demo-app.md) for the full walkthrough.
277
+
278
+ ## Data files
279
+
280
+ Data files hold the actual record collections. Each object type gets its own
281
+ directory, and within it, one file per content type.
282
+
283
+ ### Content types
284
+
285
+ | Content type | File | Identity key | Purpose |
286
+ | ------------- | ------------------- | -------------- | ------------------------------ |
287
+ | `snapshots` | `snapshots.json` | `recordId` | Entity snapshots (primary tier)|
288
+ | `analysis` | `analysis.json` | `recordId` | Knowledge/analysis records |
289
+ | `decisions` | `decisions.json` | `recordId` | Decision records |
290
+ | `events` | `events.json` | `eventId` | Event records |
291
+ | `memory` | `memory.json` | `memoryId` | Fourth tier (standalone) |
292
+
293
+ ### Record structure
294
+
295
+ Records are plain JSON objects. Every record must carry exactly one identity
296
+ key. Records can include narrative tags and associated data buckets.
297
+
298
+ ```json
299
+ [
300
+ {
301
+ "recordId": "asset-1",
302
+ "entityId": "web-prod-01",
303
+ "severity": "critical",
304
+ "status": "open",
305
+ "region": "us-east-1",
306
+ "ownerId": "agent-1",
307
+ "narratives": { "has-vulns": true, "exposed-public": true },
308
+ "associatedData": [
309
+ { "kind": "tag", "value": "prod" },
310
+ { "kind": "tag", "value": "web" }
311
+ ],
312
+ "associatedInferred": [
313
+ { "kind": "discovery", "value": "public-ip" }
314
+ ],
315
+ "associatedAnalysis": [
316
+ { "kind": "analysis", "value": "cve-score-9" }
317
+ ]
318
+ }
319
+ ]
320
+ ```
321
+
322
+ ### Associated data buckets
323
+
324
+ Records carry associated data in prefixed fields. The API translates these
325
+ between storage format and the normalized `associated` bucket returned to
326
+ clients.
327
+
328
+ | Storage field | Client alias | Description |
329
+ | ----------------------- | --------------- | ---------------------- |
330
+ | `associatedData` | `data` | Explicit tags |
331
+ | `associatedDiscovery` | `discovery` | Discovery-phase data |
332
+ | `associatedInferred` | `discovery` | Inferred discovery |
333
+ | `associatedAnalysis` | `analysis` | Analysis-phase data |
334
+ | `associated{Custom}` | `{custom}` | Custom bucket |
335
+
336
+ ### Memory tier
337
+
338
+ Memory is the fourth data tier. It lives under `mocks/data/memory/memory.json`
339
+ and uses `memoryId` as its identity key (instead of `recordId`).
340
+
341
+ ```json
342
+ [
343
+ {
344
+ "memoryId": "mem-1",
345
+ "entityId": "web-prod-01",
346
+ "kind": "conversation-summary",
347
+ "summary": "Discussed upgrade path for web-prod-01 from v2 to v3.",
348
+ "createdAt": "2026-07-10T08:00:00Z"
349
+ }
350
+ ]
351
+ ```
352
+
353
+ ### System inventory
354
+
355
+ `mocks/data/system/inventory.json` is a special file used by the inventory
356
+ engine for policy configuration.
357
+
358
+ ## How the store works
359
+
360
+ 1. On startup, `InMemoryStore.load()` reads all metadata and data JSON files
361
+ into JavaScript `Map` objects.
362
+ 2. All API reads serve from the in-memory maps (no disk I/O per request).
363
+ 3. Writes mutate the in-memory maps and mark files as "dirty".
364
+ 4. Dirty files are flushed to disk after a debounce (default 300ms, configured
365
+ via `DISK_FLUSH_DEBOUNCE_MS`).
366
+ 5. On `SIGINT`/`SIGTERM`, all dirty files are flushed immediately before
367
+ shutdown.
368
+
369
+ You can modify JSON files on disk while the server is running, but changes
370
+ won't be picked up until the next restart (the store loads once at boot). If
371
+ you need to persist changes made via the API, they are written back
372
+ automatically.
373
+
374
+ ## Adding a new object type
375
+
376
+ 1. Create `mocks/metadata/object-types/<name>.json` following the schema above.
377
+ 2. Create `mocks/data/<name>/` directory.
378
+ 3. Add at least `mocks/data/<name>/snapshots.json` with an empty array `[]`.
379
+ 4. Optionally add `analysis.json`, `decisions.json`, `events.json`.
380
+ 5. Create `mocks/metadata/lists/<name>-default.json` for a default list view.
381
+ 6. Create `mocks/metadata/narratives/<name>.json` if the object type has
382
+ authored narratives.
383
+ 7. Restart the server. The new object type appears in
384
+ `GET /api/explorer/object-types` and the inventory.
@@ -0,0 +1,269 @@
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
13
+ ```
14
+
15
+ This makes the `static-memorix` command available globally.
16
+ `memorix-explorer` remains available as a short, friendly alias.
17
+
18
+ ### From source (git clone)
19
+
20
+ ```bash
21
+ git clone https://github.com/x12i/static-memorix.git
22
+ cd static-memorix
23
+ npm install
24
+ ```
25
+
26
+ ## Starting the server
27
+
28
+ ### Production mode (build then run)
29
+
30
+ ```bash
31
+ npm run build
32
+ npm start
33
+ ```
34
+
35
+ ### Development mode (no build step)
36
+
37
+ ```bash
38
+ npm run dev
39
+ ```
40
+
41
+ Uses Node's `--experimental-strip-types` to run TypeScript directly. Faster
42
+ iteration, but not suitable for distribution.
43
+
44
+ ### As a global CLI
45
+
46
+ ```bash
47
+ static-memorix
48
+ ```
49
+
50
+ After `npm install -g`, the CLI starts the server with default settings. The
51
+ mocks directory defaults to the bundled fixtures, and the CLI prints the URLs
52
+ to open:
53
+
54
+ ```text
55
+ Memorix Explorer is ready
56
+ Demo: http://localhost:5030/demo
57
+ API: http://localhost:5030/api/explorer
58
+ Health: http://localhost:5030/health
59
+ ```
60
+
61
+ ### Create a writable demo
62
+
63
+ The bundled fixtures may live inside a global npm installation. Create a
64
+ user-owned copy before experimenting with create/edit/delete:
65
+
66
+ ```bash
67
+ static-memorix create-demo
68
+ ```
69
+
70
+ The command creates `./memorix-demo` and prints the exact next step:
71
+
72
+ ```text
73
+ Demo created:
74
+ ./memorix-demo
75
+
76
+ Start the server with:
77
+ static-memorix --mocks-dir "./memorix-demo"
78
+
79
+ Then open:
80
+ http://localhost:5030/demo
81
+ ```
82
+
83
+ The generated directory contains `demo.html`, `data/`, `metadata/`, and a
84
+ README. It is safe to edit and suitable for source control. The command refuses
85
+ to overwrite an existing file or directory.
86
+
87
+ Use a custom folder or port:
88
+
89
+ ```bash
90
+ static-memorix create-demo --dir ./fixtures
91
+ static-memorix --mocks-dir ./fixtures --port 8080
92
+ ```
93
+
94
+ ### Stop the server
95
+
96
+ Press Ctrl+C in the terminal where the server is running, or stop a managed
97
+ instance from another terminal:
98
+
99
+ ```bash
100
+ static-memorix stop
101
+ static-memorix stop --port 8080
102
+ ```
103
+
104
+ The CLI tracks only server processes it started and refuses to terminate a PID
105
+ that does not identify as Memorix Explorer. Stale tracking files are removed
106
+ automatically. If startup finds an occupied port, the error prints the matching
107
+ stop command and suggests the next port.
108
+
109
+ ### With `npx` (no global install)
110
+
111
+ ```bash
112
+ npx @x12i/static-memorix
113
+ ```
114
+
115
+ Choose a port with a CLI option or environment variable. CLI options take
116
+ precedence:
117
+
118
+ ```bash
119
+ static-memorix --port 5030 --host 127.0.0.1
120
+ PORT=5030 HOST=127.0.0.1 static-memorix
121
+ ```
122
+
123
+ Built-in help and version output do not start the server:
124
+
125
+ ```bash
126
+ static-memorix --help
127
+ static-memorix --version
128
+ ```
129
+
130
+ ### From application code
131
+
132
+ Importing the package does not open a port. Call `startServer()` explicitly:
133
+
134
+ ```js
135
+ import { startServer } from "@x12i/static-memorix";
136
+
137
+ const app = await startServer({
138
+ port: 5030,
139
+ host: "127.0.0.1",
140
+ installSignalHandlers: false,
141
+ });
142
+
143
+ // Later, for example during test teardown:
144
+ await app.close();
145
+ ```
146
+
147
+ For tests that do not need a real TCP listener, build the Fastify app and use
148
+ its injection API:
149
+
150
+ ```js
151
+ import { buildServer } from "@x12i/static-memorix";
152
+
153
+ const app = await buildServer();
154
+ const response = await app.inject({ method: "GET", url: "/health" });
155
+ console.log(response.json()); // { ok: true }
156
+ await app.close();
157
+ ```
158
+
159
+ ## Configuration
160
+
161
+ All configuration is via environment variables. Defaults work out of the box.
162
+
163
+ ### Core
164
+
165
+ | Variable | Default | Description |
166
+ | --------- | ----------- | ------------------------------ |
167
+ | `PORT` | `5030` | HTTP listen port |
168
+ | `HOST` | `0.0.0.0` | HTTP listen host |
169
+ | `MOCKS_DIR` | bundled `<package>/mocks` | Root directory for JSON fixtures |
170
+
171
+ `MOCKS_DIR` must be writable if you use mutation endpoints. For installed or
172
+ containerized deployments, point it at a persistent application-owned volume
173
+ rather than relying on the package's bundled seed directory:
174
+
175
+ ```bash
176
+ MOCKS_DIR=/var/lib/my-app/memorix-mocks static-memorix --port 5030
177
+ ```
178
+
179
+ Configuration is resolved when the package is imported. In programmatic usage,
180
+ set `process.env.MOCKS_DIR`, `MEMORIX_ORG_ID`, and `MEMORIX_AGENT_ID` before the
181
+ first dynamic import, and run separate processes for separate routing contexts.
182
+
183
+ ### DB Router Simulation
184
+
185
+ These variables control the fake database routing strings returned by the
186
+ health endpoint. The frontend uses these to verify it is connected to the
187
+ correct tenant.
188
+
189
+ | Variable | Default | Description |
190
+ | --------------------------- | ----------------- | ---------------------------------------- |
191
+ | `MEMORIX_ORG_ID` | `memorix` | Organization prefix |
192
+ | `MEMORIX_AGENT_ID` | `default-agent` | Agent prefix for Catalox DB routing |
193
+ | `MEMORIX_DEPLOYMENT_PROFILE`| `default` | When `ebook`, forces org/agent to `ebooks`|
194
+
195
+ The health endpoint returns:
196
+ - `memorixDb`: `"<orgId>-memorix-entities + <orgId>-memorix-events"`
197
+ - `cataloxDb`: `"<agentId>-memorix-catalox"`
198
+ - `fileRouting`: the concrete entity, event, memory, and metadata filename
199
+ prefixes selected for this process
200
+
201
+ These are functional routes, not display-only labels. For example:
202
+
203
+ ```bash
204
+ MEMORIX_ORG_ID=neo MEMORIX_AGENT_ID=neo-agent npm start
205
+ ```
206
+
207
+ selects filenames beginning with `neo-memorix-entities--`,
208
+ `neo-memorix-events--`, and `neo-agent-memorix-catalox--`. See
209
+ [managing-json-files.md](./managing-json-files.md#file-backed-database-routing)
210
+ for exact examples and fallback behavior.
211
+
212
+ ### Feature flags
213
+
214
+ Metadata mutations on lists and narratives are gated behind feature flags.
215
+ Record mutations are governed by write descriptors and are available by
216
+ default.
217
+
218
+ | Variable | Controls |
219
+ | ------------------------------------------- | --------------------- |
220
+ | `MEMORIX_EXPLORER_ENABLE_METADATA_WRITES` | List & narrative CRUD |
221
+ | `MEMORIX_EXPLORER_ENABLE_PIPELINE_WRITES` | Pipeline writes |
222
+ | `MEMORIX_EXPLORER_ENABLE_REGISTRY_WRITES` | Registry writes |
223
+
224
+ Set to `true` or `1` to enable:
225
+
226
+ ```bash
227
+ MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true static-memorix
228
+ ```
229
+
230
+ ### Disk flush
231
+
232
+ | Variable | Default | Description |
233
+ | -------------------------- | ------- | --------------------------------------- |
234
+ | `DISK_FLUSH_DEBOUNCE_MS` | `300` | Milliseconds to debounce before writing dirty files to disk |
235
+
236
+ ### Example: full custom configuration
237
+
238
+ ```bash
239
+ PORT=8080 \
240
+ HOST=127.0.0.1 \
241
+ MOCKS_DIR=/path/to/my/fixtures \
242
+ MEMORIX_ORG_ID=neo \
243
+ MEMORIX_AGENT_ID=neo-agent \
244
+ MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true \
245
+ static-memorix
246
+ ```
247
+
248
+ ## Health checks
249
+
250
+ Once running, verify the server is up:
251
+
252
+ ```bash
253
+ curl localhost:5030/health
254
+ # {"ok":true}
255
+
256
+ curl "localhost:5030/api/explorer/health?includeInventory=1"
257
+ # {"ok":true,"mongoUriConfigured":true,"discoverySample":["assets","findings","memory"],...}
258
+ ```
259
+
260
+ ## Graceful shutdown
261
+
262
+ The server handles `SIGINT` and `SIGTERM`. On shutdown it:
263
+ 1. Flushes all dirty in-memory state to disk.
264
+ 2. Closes the Fastify listener.
265
+ 3. Exits with code 0.
266
+
267
+ This means mutations made via the API are always persisted as long as the
268
+ server shuts down cleanly. If the process is killed with `SIGKILL`, unsaved
269
+ state is lost.