@x12i/static-memorix-explorer-api 1.0.0 → 1.1.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 +103 -12
- package/dist/engine/identity.js +11 -9
- package/dist/engine/write.js +48 -4
- package/dist/routes/index.js +57 -7
- package/dist/server.js +7 -2
- package/guides/demo-app.md +524 -0
- package/guides/managing-json-files.md +310 -0
- package/guides/running-the-service.md +136 -0
- package/guides/using-the-api.md +265 -0
- package/mocks/data/admin/snapshots.json +52 -0
- package/mocks/data/assets/analysis.json +14 -0
- package/mocks/data/assets/decisions.json +14 -0
- package/mocks/data/assets/events.json +4 -0
- package/mocks/data/assets/snapshots.json +49 -0
- package/mocks/data/findings/analysis.json +4 -0
- package/mocks/data/findings/decisions.json +4 -0
- package/mocks/data/findings/events.json +3 -0
- package/mocks/data/findings/snapshots.json +21 -0
- package/mocks/data/marketing/snapshots.json +75 -0
- package/mocks/data/memory/memory.json +31 -0
- package/mocks/data/product/snapshots.json +84 -0
- package/mocks/data/system/inventory.json +8 -0
- package/mocks/data/users/snapshots.json +5 -0
- package/mocks/demo.html +828 -0
- package/mocks/metadata/agents.json +7 -0
- package/mocks/metadata/lists/assets-default.json +22 -0
- package/mocks/metadata/lists/backlog.json +10 -0
- package/mocks/metadata/lists/findings-default.json +9 -0
- package/mocks/metadata/lists/my-plate.json +10 -0
- package/mocks/metadata/lists/this-week.json +10 -0
- package/mocks/metadata/narratives/admin.json +7 -0
- package/mocks/metadata/narratives/assets.json +17 -0
- package/mocks/metadata/narratives/findings.json +7 -0
- package/mocks/metadata/narratives/marketing.json +17 -0
- package/mocks/metadata/narratives/product.json +12 -0
- package/mocks/metadata/object-types/admin.json +6 -0
- package/mocks/metadata/object-types/assets.json +9 -0
- package/mocks/metadata/object-types/findings.json +8 -0
- package/mocks/metadata/object-types/marketing.json +6 -0
- package/mocks/metadata/object-types/memory.json +6 -0
- package/mocks/metadata/object-types/product.json +6 -0
- package/mocks/metadata/object-types/users.json +13 -0
- package/mocks/metadata/write-descriptors/admin-task-write.json +20 -0
- package/mocks/metadata/write-descriptors/assets-analysis-write.json +14 -0
- package/mocks/metadata/write-descriptors/marketing-task-write.json +20 -0
- package/mocks/metadata/write-descriptors/memory-write.json +14 -0
- package/mocks/metadata/write-descriptors/product-task-write.json +20 -0
- 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,136 @@
|
|
|
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
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
All configuration is via environment variables. Defaults work out of the box.
|
|
55
|
+
|
|
56
|
+
### Core
|
|
57
|
+
|
|
58
|
+
| Variable | Default | Description |
|
|
59
|
+
| --------- | ----------- | ------------------------------ |
|
|
60
|
+
| `PORT` | `4300` | HTTP listen port |
|
|
61
|
+
| `HOST` | `0.0.0.0` | HTTP listen host |
|
|
62
|
+
| `MOCKS_DIR` | `<project>/mocks` | Root directory for JSON fixtures |
|
|
63
|
+
|
|
64
|
+
### DB Router Simulation
|
|
65
|
+
|
|
66
|
+
These variables control the fake database routing strings returned by the
|
|
67
|
+
health endpoint. The frontend uses these to verify it is connected to the
|
|
68
|
+
correct tenant.
|
|
69
|
+
|
|
70
|
+
| Variable | Default | Description |
|
|
71
|
+
| --------------------------- | ----------------- | ---------------------------------------- |
|
|
72
|
+
| `MEMORIX_ORG_ID` | `memorix` | Organization prefix |
|
|
73
|
+
| `MEMORIX_AGENT_ID` | `default-agent` | Agent prefix for Catalox DB routing |
|
|
74
|
+
| `MEMORIX_DEPLOYMENT_PROFILE`| `default` | When `ebook`, forces org/agent to `ebooks`|
|
|
75
|
+
|
|
76
|
+
The health endpoint returns:
|
|
77
|
+
- `memorixDb`: `"<orgId>-memorix-entities + <orgId>-memorix-events"`
|
|
78
|
+
- `cataloxDb`: `"<agentId>-memorix-catalox"`
|
|
79
|
+
|
|
80
|
+
### Feature flags
|
|
81
|
+
|
|
82
|
+
Mutations (POST/PATCH/PUT/DELETE) on lists, narratives, and records are gated
|
|
83
|
+
behind feature flags. All default to `false`.
|
|
84
|
+
|
|
85
|
+
| Variable | Controls |
|
|
86
|
+
| ------------------------------------------- | --------------------- |
|
|
87
|
+
| `MEMORIX_EXPLORER_ENABLE_METADATA_WRITES` | List & narrative CRUD |
|
|
88
|
+
| `MEMORIX_EXPLORER_ENABLE_PIPELINE_WRITES` | Pipeline writes |
|
|
89
|
+
| `MEMORIX_EXPLORER_ENABLE_REGISTRY_WRITES` | Registry writes |
|
|
90
|
+
|
|
91
|
+
Set to `true` or `1` to enable:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true mock-memorix-explorer-api
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Disk flush
|
|
98
|
+
|
|
99
|
+
| Variable | Default | Description |
|
|
100
|
+
| -------------------------- | ------- | --------------------------------------- |
|
|
101
|
+
| `DISK_FLUSH_DEBOUNCE_MS` | `300` | Milliseconds to debounce before writing dirty files to disk |
|
|
102
|
+
|
|
103
|
+
### Example: full custom configuration
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
PORT=8080 \
|
|
107
|
+
HOST=127.0.0.1 \
|
|
108
|
+
MOCKS_DIR=/path/to/my/fixtures \
|
|
109
|
+
MEMORIX_ORG_ID=neo \
|
|
110
|
+
MEMORIX_AGENT_ID=neo-agent \
|
|
111
|
+
MEMORIX_EXPLORER_ENABLE_METADATA_WRITES=true \
|
|
112
|
+
mock-memorix-explorer-api
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Health checks
|
|
116
|
+
|
|
117
|
+
Once running, verify the server is up:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
curl localhost:4300/health
|
|
121
|
+
# {"ok":true}
|
|
122
|
+
|
|
123
|
+
curl "localhost:4300/api/explorer/health?includeInventory=1"
|
|
124
|
+
# {"ok":true,"mongoUriConfigured":true,"discoverySample":["assets","findings","memory"],...}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Graceful shutdown
|
|
128
|
+
|
|
129
|
+
The server handles `SIGINT` and `SIGTERM`. On shutdown it:
|
|
130
|
+
1. Flushes all dirty in-memory state to disk.
|
|
131
|
+
2. Closes the Fastify listener.
|
|
132
|
+
3. Exits with code 0.
|
|
133
|
+
|
|
134
|
+
This means mutations made via the API are always persisted as long as the
|
|
135
|
+
server shuts down cleanly. If the process is killed with `SIGKILL`, unsaved
|
|
136
|
+
state is lost.
|