@kontextmind/kxm 0.7.103 → 0.7.104
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +27 -8
- package/docs/concepts/data-and-storage.md +1 -1
- package/docs/contributing/test-matrix.md +7 -5
- package/docs/operations/backup-and-restore.md +59 -27
- package/docs/operations/deploy.md +1 -1
- package/docs/reference/cli-reference.md +31 -20
- package/docs/reference/config-reference.md +1 -1
- package/docs/start/quickstart-claude-code.md +1 -1
- package/docs/templates/runbook.md +2 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +788 -649
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +321 -291
- package/plugins/kxm/dist/runtime.js +424 -348
- package/plugins/kxm/dist/server.js +78 -78
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +14 -7
- package/plugins/kxm/src/cli/project.ts +66 -12
- package/plugins/kxm/src/cli.ts +7 -5
- package/plugins/kxm/src/database.ts +193 -65
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/runtime-paths.ts +50 -0
- package/plugins/kxm/src/runtime-store.ts +22 -53
- package/plugins/kxm/src/runtime-supervisor.ts +38 -17
- package/schemas/backup-manifest.schema.json +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -349,14 +349,33 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
349
349
|
and admits those two routes. One-shot production no longer falls through to an
|
|
350
350
|
unadmitted `claude-3-7-sonnet`.
|
|
351
351
|
|
|
352
|
-
- **`kxm backup` includes
|
|
353
|
-
copies
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
352
|
+
- **`kxm backup` includes this project's Runtime event store, and never another
|
|
353
|
+
project's unless asked.** Discovery copies the checkout's own
|
|
354
|
+
`$S/runtime/projects/<projectKey>/run-events.db`, with the key derived from the
|
|
355
|
+
canonical checkout path as the Runtime derives it, plus its
|
|
356
|
+
`run-events.db.run-prompts.json` sidecar. `kxm backup` and `kxm restore` act for the
|
|
357
|
+
checkout the Runtime would use (the Git root holding `.kxm/project.yaml`), so running
|
|
358
|
+
them from a subdirectory finds the same stores. The shared `$S/runtime/registry.db` and
|
|
359
|
+
every other project's event store are copied only with `kxm backup --all-projects`, and
|
|
360
|
+
a restore of a backup that holds them is refused with `restore_requires_all_projects`
|
|
361
|
+
unless `kxm restore --all-projects` is given; a project restore therefore never rolls
|
|
362
|
+
back another project's runs, receipts or sync outbox, or the registry. A project
|
|
363
|
+
restore writes the event store where the Runtime looks for the checkout being
|
|
364
|
+
restored, under the current `KXM_STATE_HOME`; `--all-projects` rebases the shared
|
|
365
|
+
stores onto the current user state root. The manifest records `scope`, `stateRoot`
|
|
366
|
+
and `runtimeProjectKey`. A copy that misses a discovered store is `complete: false`:
|
|
367
|
+
`kxm backup` exits 1 with `ok: false`, and restore refuses that manifest.
|
|
368
|
+
`kxm backup --help` no longer says Runtime stores are left out.
|
|
369
|
+
|
|
370
|
+
- **`kxm restore` refuses while the Runtime supervisor or a hub is running.** Before any
|
|
371
|
+
write, and under `--dry-run` too, it fails with `restore_runtime_running` when the
|
|
372
|
+
supervisor is live by the same test `kxm runtime status` uses (running state, fresh
|
|
373
|
+
heartbeat, live PID), read through a read-only open of the registry, and with
|
|
374
|
+
`restore_hub_running` when a live `hub.pid` claim sits beside a hub store it would
|
|
375
|
+
overwrite. A registry too broken to read fails closed with
|
|
376
|
+
`restore_runtime_unverified`, which says to stop the Runtime and move the registry
|
|
377
|
+
aside. Replacing a SQLite file under an open writer could lose commits or corrupt the
|
|
378
|
+
store.
|
|
360
379
|
|
|
361
380
|
- **Signed webhooks cannot be replayed.** KXM's own webhook senders now sign the
|
|
362
381
|
timestamp, delivery ID, definition, run and signal key along with the body
|
|
@@ -169,7 +169,7 @@ Every store opens in WAL mode with a 5-second busy timeout, `synchronous=NORMAL`
|
|
|
169
169
|
## Reset a store
|
|
170
170
|
|
|
171
171
|
> [!CAUTION]
|
|
172
|
-
> Deleting a store erases its history for good. Back it up first if you might need it. `kxm backup` copies the hub store and
|
|
172
|
+
> Deleting a store erases its history for good. Back it up first if you might need it. `kxm backup` copies the hub store and this project's Runtime event store; `kxm backup --all-projects` adds the Runtime registry and every project's event store.
|
|
173
173
|
|
|
174
174
|
Stop every writer first, then delete each database together with its sidecars:
|
|
175
175
|
|
|
@@ -136,17 +136,19 @@ The context suites in detail:
|
|
|
136
136
|
| Metadata-only dashboard: ops mode, presence-only fallback, observer filtering, keys, body-free local projection | `tui.test.ts`, `hub-api.test.ts` |
|
|
137
137
|
| Restricted loader, deterministic bundle hash, init classification, atomic creation, three-way repair, crash resumption | `project-config.test.ts`, `cli.test.ts`, `package-install.test.ts` |
|
|
138
138
|
| Legacy `.kxm/config` JSON is refused with `legacy_state_unsupported`; `kxm migrate` is unknown; older stores are refused | `project-config.test.ts`, `cli.test.ts`, `e6-backup-restore-migrations.test.ts` |
|
|
139
|
-
| `kxm backup` and `kxm restore` round-trip the hub store and Runtime stores seeded under `.kxm/runtime/`;
|
|
139
|
+
| `kxm backup` and `kxm restore` round-trip the hub store and Runtime stores seeded under `.kxm/runtime/`; a project backup under `KXM_STATE_HOME` holds only its own event store and sidecar, and its restore leaves another project and the registry alone; `--all-projects` holds and restores every project and the registry, and restore refuses it without the flag; restore refuses while the supervisor or a hub is live, `--dry-run` included; tampered, newer or incomplete (`complete: false`) backups are refused | `e6-backup-restore-migrations.test.ts` |
|
|
140
140
|
| Permission-diff trust: authority lattice, prose neutrality, Git base shadowing, CLI diff and check | `permission.test.ts`, `cli.test.ts`, `contracts.test.ts`, `package-install.test.ts` |
|
|
141
141
|
| KXM schemas, restricted YAML fixtures, cross-resource semantics and sync-safe rejection | `contracts.test.ts`, `restricted-yaml.test.ts` |
|
|
142
142
|
| Harness detection, auth and dispatch for the built-in catalog, including Windows launch rules | `harness.test.ts` |
|
|
143
143
|
| `kxm update`: `update.yaml` validation, GitHub or npm version checks, install-kind detection, `kxm-<v>.tgz` asset selection | `kxm-update.test.ts`, `kxm-update-cli.test.ts`, `kxm-install-kind.test.ts` |
|
|
144
144
|
|
|
145
145
|
The round trip seeds its Runtime stores in a project-local `.kxm/runtime/`
|
|
146
|
-
layout that the Runtime never writes.
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
146
|
+
layout that the Runtime never writes. Separate tests seed two projects' event
|
|
147
|
+
stores, their prompt sidecars and a shared registry under a temporary
|
|
148
|
+
`KXM_STATE_HOME`, at the paths the Runtime derives, and check each scope and
|
|
149
|
+
that a partial manifest is refused. The supervisor-liveness test fakes a live
|
|
150
|
+
supervisor with a fresh registry record naming the test's own PID; no test backs
|
|
151
|
+
up or restores stores a running supervisor wrote.
|
|
150
152
|
|
|
151
153
|
## Packaging, release and repository gates
|
|
152
154
|
|
|
@@ -59,17 +59,16 @@ Record every override with the backup. A restore that lands where the running se
|
|
|
59
59
|
|
|
60
60
|
## Know what each backup covers
|
|
61
61
|
|
|
62
|
-
`kxm backup` copies the SQLite stores it
|
|
62
|
+
`kxm backup` copies the SQLite stores that belong to the checkout it runs in: the hub store under the checkout, and the checkout's own Runtime event store and its prompt sidecar under the user state root. It finds that event store the way the Runtime does, by a key derived from the canonical checkout path. Everything else needs `--all-projects` or the stopped-state copy described below.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
> The Runtime stores are shared by every project on the machine. `kxm backup` copies `$S/runtime/registry.db` and the `run-events.db` of every project under `$S/runtime/projects/`, not only the project you run it from, and `kxm restore` writes all of them back. Restoring one project's backup returns every project's runs to the moment of that backup.
|
|
64
|
+
The Runtime registry and the other projects' event stores are shared by every project on the machine, so a project backup leaves them out, and a project restore never rolls them back. The registry holds the supervisor's identity and each project's registration, including the home Runtime id that every one of that project's runs records. A project restore relies on the registry that is already on the machine. `kxm backup --all-projects` adds the registry and every project's event store for a whole-machine copy, and only `kxm restore --all-projects` restores one.
|
|
66
65
|
|
|
67
66
|
| Path | Holds | In `kxm backup` |
|
|
68
67
|
|---|---|---|
|
|
69
|
-
| `$W/kxm.db` | Hub store: agents, messages, workflow runs, journals, context items, leases, synced run facts | Yes, at `<
|
|
70
|
-
| `$S/runtime/
|
|
71
|
-
| `$S/runtime/projects/<key>/run-events.db` |
|
|
72
|
-
| `$S/runtime/
|
|
68
|
+
| `$W/kxm.db` | Hub store: agents, messages, workflow runs, journals, context items, leases, synced run facts | Yes, at `<checkout>/.kxm/state/kxm.db` only |
|
|
69
|
+
| `$S/runtime/projects/<key>/run-events.db` | Event-sourced runs, drive receipts, gate evidence, the sync outbox | This checkout's (`events:<key>`); every project's with `--all-projects` |
|
|
70
|
+
| `$S/runtime/projects/<key>/run-events.db.run-prompts.json` | Run prompt text; restoring a store without it loses every prompt | With its event store, as a plain file (`events:<key>:run-prompts`) |
|
|
71
|
+
| `$S/runtime/registry.db` | Runtime registry: projects, their roots and home Runtime, the supervisor identity and claim | Only with `--all-projects` (`registry`) |
|
|
73
72
|
| `$S/projects/<hash>/repository-bindings.json`, `$S/update.yaml` | Member repository paths; updater settings | No |
|
|
74
73
|
| `$S/hub-env.json`, `$S/hub-binding.json`, `$C/session.token` | Credentials and the machine's hub binding | No; prefer regenerating secrets to copying them |
|
|
75
74
|
| `$R/.kxm/` definition files and durable records | Project, roles, routes, prices, roster, memory, skills, goals, tasks, candidates | No; commit them to Git or copy the checkout |
|
|
@@ -84,7 +83,7 @@ These files are disposable and need no backup: `hub.pid`, `hub.stop`, `worker-*.
|
|
|
84
83
|
|
|
85
84
|
## Back up the SQLite stores with `kxm backup`
|
|
86
85
|
|
|
87
|
-
Run it
|
|
86
|
+
Run it inside the checkout. It acts for the checkout the Runtime would use: the Git root that holds `.kxm/project.yaml`, or the current directory outside one. It finds the hub store under that checkout and ignores `--workspace`, `KXM_WORKDIR`, `KXM_STATE_DIR` and `KXM_DATA_PATH`, so a relocated hub database is not found. It finds the checkout's event store under the user state root, including one moved with `KXM_STATE_HOME`.
|
|
88
87
|
|
|
89
88
|
Preview first. A dry run lists what it would write, opens no store and writes nothing:
|
|
90
89
|
|
|
@@ -96,9 +95,8 @@ kxm backup --dry-run
|
|
|
96
95
|
Expected output:
|
|
97
96
|
|
|
98
97
|
```text
|
|
99
|
-
dry run: back up
|
|
98
|
+
dry run: back up 2 store(s) and 1 file(s) (project scope) to /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z (sources are not opened, so their WAL is not checkpointed)
|
|
100
99
|
would write /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z/kxm.db
|
|
101
|
-
would write /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z/registry.db
|
|
102
100
|
would write /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z/run-events.db
|
|
103
101
|
would write /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z/run-events.db.run-prompts.json
|
|
104
102
|
would write /srv/kxm/product/.kxm/backups/backup-2026-09-23T18-29-09-107Z/manifest.json
|
|
@@ -113,14 +111,13 @@ kxm backup --out /backups/kxm/2026-09-23/sqlite
|
|
|
113
111
|
Expected output:
|
|
114
112
|
|
|
115
113
|
```text
|
|
116
|
-
Created SQLite backup with
|
|
114
|
+
Created SQLite backup with 2 store(s) (project scope):
|
|
117
115
|
- hub-store: /srv/kxm/product/.kxm/state/kxm.db -> kxm.db (schema v5, 110592 bytes, sha256 sha256:86549...)
|
|
118
|
-
- registry: /home/kxm/.local/state/kxm/runtime/registry.db -> registry.db (schema v1, 20480 bytes, sha256 sha256:a5bde...)
|
|
119
116
|
- events:38ed26cb8eeaa297f3b0b452: /home/kxm/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db -> run-events.db (schema v7, 348160 bytes, sha256 sha256:27c13...)
|
|
120
117
|
Manifest: /backups/kxm/2026-09-23/sqlite/manifest.json
|
|
121
118
|
```
|
|
122
119
|
|
|
123
|
-
The summary lists stores only; the prompt sidecar is in the manifest's `files`.
|
|
120
|
+
The summary lists stores only; the prompt sidecar is in the manifest's `files`. The manifest also records its `scope` (`project` or `all-projects`), the user state root it copied from (`stateRoot`), and this checkout's store key (`runtimeProjectKey`).
|
|
124
121
|
|
|
125
122
|
For each store, `kxm backup` checkpoints the write-ahead log, runs `PRAGMA integrity_check`, copies the database with `VACUUM INTO`, checks the copy's integrity, sets mode `0600`, and records its schema version and SHA-256 in `manifest.json` (`kxm.backup-manifest.v1`). `VACUUM INTO` reads one consistent snapshot, so the hub and the Runtime can keep running during this step. Each prompt sidecar is copied as a regular file with mode `0600` and its SHA-256 recorded.
|
|
126
123
|
|
|
@@ -129,7 +126,17 @@ A backup is complete only when it copied everything it found. If a store or side
|
|
|
129
126
|
> [!NOTE]
|
|
130
127
|
> Without `--out`, backups go to `.kxm/backups/` inside the checkout. Keep that directory out of Git, or always pass `--out`.
|
|
131
128
|
|
|
132
|
-
It fails with `backup_no_stores` when it finds no store at all, neither `.kxm/state/kxm.db` under the
|
|
129
|
+
It fails with `backup_no_stores` when it finds no store at all, neither `.kxm/state/kxm.db` under the checkout nor the checkout's event store under the user state root, and with `database_corrupted` when an integrity check fails.
|
|
130
|
+
|
|
131
|
+
### Back up every project on the machine
|
|
132
|
+
|
|
133
|
+
`--all-projects` adds the Runtime registry and the event store and prompt sidecar of every project under `$S/runtime/projects/`:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
kxm backup --all-projects --out /backups/kxm/2026-09-23/sqlite
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use it for a whole-machine copy, together with the stopped-state copy below. When two projects' event stores share a file name, the second copy is prefixed with its store id. Restoring this backup takes `kxm restore --all-projects` and rolls back every project on the machine.
|
|
133
140
|
|
|
134
141
|
## Back up everything else
|
|
135
142
|
|
|
@@ -151,11 +158,11 @@ Copy the remaining state with both services stopped. SQLite runs in write-ahead-
|
|
|
151
158
|
S="${KXM_STATE_HOME:-$HOME/.local/state/kxm}" # macOS: "$HOME/Library/Application Support/KXM"
|
|
152
159
|
B=/backups/kxm/2026-09-23
|
|
153
160
|
mkdir -p "$B"
|
|
154
|
-
kxm backup --out "$B/sqlite"
|
|
161
|
+
kxm backup --all-projects --out "$B/sqlite"
|
|
155
162
|
tar -C "$S" --exclude 'supervisor.token' --exclude 'hub-env.json' -czf "$B/user-state.tgz" .
|
|
156
163
|
```
|
|
157
164
|
|
|
158
|
-
`kxm backup` already holds the
|
|
165
|
+
`kxm backup --all-projects` already holds the registry and every project's event store and sidecar. The archive holds them again, as files, together with what `kxm backup` does not copy: the repository bindings, the hub binding and `update.yaml`. It leaves out the credential file; keep tokens in your secret store, or include `hub-env.json` and protect the archive as a secret.
|
|
159
166
|
|
|
160
167
|
If `KXM_STATE_DIR` or `KXM_DATA_PATH` moved the hub database, `kxm backup` fails with `backup_no_stores`. With both services stopped, copy that database file and any `-wal` and `-shm` files instead.
|
|
161
168
|
4. Copy the other roots your recovery needs: the checkout's untracked `.kxm/` records, `$D/assets/`, `$W/worker-*.json` (and `$W/pi-sessions/` only if your policy keeps model history), and `$C`.
|
|
@@ -166,9 +173,15 @@ Keep at least one previous backup, and bound retention: run events and prompt si
|
|
|
166
173
|
|
|
167
174
|
## Restore with `kxm restore`
|
|
168
175
|
|
|
169
|
-
`kxm restore` overwrites
|
|
176
|
+
`kxm restore` overwrites the live databases it restores and deletes their `-wal` and `-shm` files. Before it writes anything, it refuses:
|
|
177
|
+
|
|
178
|
+
- While the Runtime supervisor is running (`restore_runtime_running`), judged as `kxm runtime status` judges it. The supervisor holds the registry and every project's event store open.
|
|
179
|
+
- While a hub holds the hub store it would overwrite (`restore_hub_running`), seen as a live `hub.pid` claim in that store's directory.
|
|
180
|
+
- A backup that holds the registry or another project's event store (`restore_requires_all_projects`), unless you pass `--all-projects`.
|
|
181
|
+
|
|
182
|
+
Stop the hub and the Runtime first and keep them stopped, as in the backup procedure. Move the current state aside rather than deleting it.
|
|
170
183
|
|
|
171
|
-
Preview the restore. The dry run performs every check
|
|
184
|
+
Preview the restore. The dry run performs every check on this page, including the supervisor and hub checks, and lists what it would overwrite:
|
|
172
185
|
|
|
173
186
|
```bash
|
|
174
187
|
cd /srv/kxm/product
|
|
@@ -178,11 +191,10 @@ kxm restore /backups/kxm/2026-09-23/sqlite/manifest.json --dry-run
|
|
|
178
191
|
Expected output:
|
|
179
192
|
|
|
180
193
|
```text
|
|
181
|
-
dry run: restore
|
|
194
|
+
dry run: restore 2 store(s) and 1 file(s) from /backups/kxm/2026-09-23/sqlite/manifest.json; digests verified against the manifest
|
|
182
195
|
would write /srv/kxm/product/.kxm/state/kxm.db
|
|
183
196
|
would delete /srv/kxm/product/.kxm/state/kxm.db-wal
|
|
184
197
|
would delete /srv/kxm/product/.kxm/state/kxm.db-shm
|
|
185
|
-
would write /home/kxm/.local/state/kxm/runtime/registry.db
|
|
186
198
|
would write /home/kxm/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db
|
|
187
199
|
would write /home/kxm/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db.run-prompts.json
|
|
188
200
|
```
|
|
@@ -196,15 +208,32 @@ kxm restore /backups/kxm/2026-09-23/sqlite/manifest.json
|
|
|
196
208
|
Expected output:
|
|
197
209
|
|
|
198
210
|
```text
|
|
199
|
-
Restored
|
|
211
|
+
Restored 2 SQLite store(s) from /backups/kxm/2026-09-23/sqlite/manifest.json:
|
|
200
212
|
- hub-store: -> /srv/kxm/product/.kxm/state/kxm.db (schema v5, integrity ok)
|
|
201
|
-
- registry: -> /home/kxm/.local/state/kxm/runtime/registry.db (schema v1, integrity ok)
|
|
202
213
|
- events:38ed26cb8eeaa297f3b0b452: -> /home/kxm/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db (schema v7, integrity ok)
|
|
203
214
|
```
|
|
204
215
|
|
|
205
216
|
Before it overwrites anything, `kxm restore` checks that the manifest is a `kxm.backup-manifest.v1` document that does not record `complete: false` (`restore_incomplete`), that every listed file exists, that each file's SHA-256 matches the manifest, and that no store is newer than this build supports. The manifest's own `manifestSha256` is not checked. A manifest from an older build that has no `complete` field still restores.
|
|
206
217
|
|
|
207
|
-
It then checks each backup's integrity and schema version again, copies it into place with mode `0600`, checks the result, and then copies the prompt sidecars back.
|
|
218
|
+
It then checks each backup's integrity and schema version again, copies it into place with mode `0600`, checks the result, and then copies the prompt sidecars back. Each target is worked out from the checkout you restore into:
|
|
219
|
+
|
|
220
|
+
- A store under the manifest's project root is rebased onto this checkout.
|
|
221
|
+
- The backed-up checkout's own event store and its sidecar go to the store the Runtime derives for this checkout, under the current user state root. A restore under a moved `KXM_STATE_HOME` lands where the Runtime looks.
|
|
222
|
+
- With `--all-projects`, the registry and every other event store are rebased from the manifest's `stateRoot` onto the current user state root.
|
|
223
|
+
- A manifest written before backups were scoped has no `stateRoot`. Its Runtime stores are bare absolute paths, count as machine-wide, and restore only with `--all-projects`, to those paths.
|
|
224
|
+
|
|
225
|
+
A project restore leaves the registry alone. The restored runs record the home Runtime id that the registry already holds for this project, so the Runtime keeps owning them. If the registry itself was lost, a project restore cannot bring it back: restore an `--all-projects` backup or the archive.
|
|
226
|
+
|
|
227
|
+
### Restore every project
|
|
228
|
+
|
|
229
|
+
Restore a backup taken with `--all-projects` only when rolling every project back to the backup's moment is what you want:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
kxm restore /backups/kxm/2026-09-23/sqlite/manifest.json --all-projects --dry-run
|
|
233
|
+
kxm restore /backups/kxm/2026-09-23/sqlite/manifest.json --all-projects
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
It restores the registry and every project's event store and sidecar. Every checkout must still be at the absolute path the registry records.
|
|
208
237
|
|
|
209
238
|
### Restore ceilings
|
|
210
239
|
|
|
@@ -221,7 +250,7 @@ The ceilings come from `KXM_BACKUP_CEILINGS` in `plugins/kxm/src/database.ts` an
|
|
|
221
250
|
|
|
222
251
|
### Restore the Runtime stores by hand
|
|
223
252
|
|
|
224
|
-
`kxm restore`
|
|
253
|
+
Use the archive instead of `kxm restore` when you also need the bindings and `update.yaml`, or when a backup holds only the SQLite copies you no longer trust.
|
|
225
254
|
|
|
226
255
|
1. Stop the Runtime and the hub, as in the backup procedure.
|
|
227
256
|
2. Move `$S/runtime/registry.db` and `$S/runtime/projects/` aside.
|
|
@@ -248,11 +277,14 @@ Test a full restore on a spare machine before you rely on it, and repeat the tes
|
|
|
248
277
|
|
|
249
278
|
| Symptom | Cause | Fix |
|
|
250
279
|
|---|---|---|
|
|
251
|
-
| `backup_no_stores` | No `.kxm/state/kxm.db` under the
|
|
280
|
+
| `backup_no_stores` | No `.kxm/state/kxm.db` under the checkout (or it was moved with `KXM_STATE_DIR` or `KXM_DATA_PATH`) and no event store for this checkout under the user state root | Run inside the checkout; copy a relocated database with the stopped-state procedure |
|
|
252
281
|
| `Backup is incomplete (<n> omitted); not ok:`, exit 1 | A store or sidecar could not be copied, or appeared while the backup ran | Fix the source named after `omitted`, then run `kxm backup` again |
|
|
253
282
|
| `restore_incomplete` | The manifest records `complete: false` | Restore a complete backup; the partial one is not restorable |
|
|
254
|
-
| Runs are missing after a restore | The checkout moved to another absolute path,
|
|
255
|
-
|
|
|
283
|
+
| Runs are missing, or the Runtime refuses the project with `project_home_conflict`, after a restore | The checkout moved to another absolute path, and the registry still binds the project to the old one | Keep the checkout at its original path and restore there |
|
|
284
|
+
| `restore_requires_all_projects` | The backup holds the Runtime registry or another project's event store: it was taken with `--all-projects`, or by a build from before backups were scoped | Pass `--all-projects` if rolling back every project is what you want; otherwise restore a backup taken without it |
|
|
285
|
+
| `restore_runtime_running` | The Runtime supervisor is running | Run `kxm runtime stop`, pause whatever restarts it, then restore |
|
|
286
|
+
| `restore_runtime_unverified` | `$S/runtime/registry.db` cannot be read, so restore cannot tell whether the supervisor is running | Stop the Runtime, move the registry aside, then restore; an `--all-projects` backup brings the registry back |
|
|
287
|
+
| `restore_hub_running` | A live `hub.pid` claim sits beside the hub store the restore would overwrite | Run `kxm hub stop`, then restore. If no hub is running and the claim's PID now belongs to another process, delete the stale `hub.pid`; it is disposable |
|
|
256
288
|
| `restore_manifest_digest_mismatch` | A backup file changed after the manifest was written | Use another backup; do not edit files in a backup set |
|
|
257
289
|
| `restore_file_missing` | A file listed in the manifest is not beside it | Copy the whole backup directory, not only `manifest.json` |
|
|
258
290
|
| `runtime_schema_mismatch` | A backup file's schema version differs from the one its manifest records | Use another backup set; never mix files between sets |
|
|
@@ -237,7 +237,7 @@ flowchart LR
|
|
|
237
237
|
Hold these invariants on every tenant box:
|
|
238
238
|
|
|
239
239
|
1. **A service account, not root.** Session isolation routes model context; it is not a sandbox against a hostile process under the same account. Give untrusted workers separate accounts or containers.
|
|
240
|
-
2. **Explicit, stable paths.** Set `KXM_WORKSPACE_DIR`, `KXM_STATE_DIR`, `KXM_DATA_PATH`, `KXM_LOG_PATH` and `KXM_STATE_HOME` instead of inheriting a home directory, and pin `KXM_HOST=127.0.0.1`. `kxm backup` ignores `KXM_STATE_DIR` and `KXM_DATA_PATH`, so on such a box it finds no hub store: it copies only the Runtime
|
|
240
|
+
2. **Explicit, stable paths.** Set `KXM_WORKSPACE_DIR`, `KXM_STATE_DIR`, `KXM_DATA_PATH`, `KXM_LOG_PATH` and `KXM_STATE_HOME` instead of inheriting a home directory, and pin `KXM_HOST=127.0.0.1`. `kxm backup` ignores `KXM_STATE_DIR` and `KXM_DATA_PATH`, so on such a box it finds no hub store: it copies only the project's Runtime event store under `KXM_STATE_HOME`, or fails with `backup_no_stores` when there is none. Copy the hub database with the [stopped-state backup](backup-and-restore.md#back-up-everything-else).
|
|
241
241
|
3. **Loopback listeners only.** Neither the hub nor the supervisor has a public port, and nothing is load-balanced across hubs.
|
|
242
242
|
4. **Edge authentication.** The proxy authenticates browsers (for example with Authentik forward auth) on the portal's routes only. The hub never interprets browser identity; see [ADR-0004](../adr/ADR-0004-edge-identity-authentik.md).
|
|
243
243
|
5. **Server-side machine credentials.** The portal backend keeps the hub credentials and calls the hub itself. `kxm tenant status`, which uses the admin token, gives it one labelled view of hub metadata and Runtime runs.
|
|
@@ -3340,17 +3340,18 @@ price catalog stamped 2026-09-23 (list estimate only; vendor rates were not fetc
|
|
|
3340
3340
|
## `kxm backup`
|
|
3341
3341
|
|
|
3342
3342
|
```text
|
|
3343
|
-
kxm backup [--out <dir>]
|
|
3343
|
+
kxm backup [--out <dir>] [--all-projects]
|
|
3344
3344
|
```
|
|
3345
3345
|
|
|
3346
|
-
Creates a verified SQLite backup of
|
|
3346
|
+
Creates a verified SQLite backup of this checkout's hub store and Runtime event store, with a hashed `kxm.backup-manifest.v1` manifest. It acts for the checkout the Runtime would use: the Git root that holds `.kxm/project.yaml`, or the current directory outside one. Under that checkout it discovers the hub store `.kxm/state/kxm.db`, and any `registry.db`, `bindings.db`, and `events/*.db` under `.kxm/runtime/`. Under the user state root (`KXM_STATE_HOME` or the platform default) it discovers the checkout's own `runtime/projects/<key>/run-events.db`, with `<key>` derived from the canonical checkout path as the Runtime derives it, and that store's `run-events.db.run-prompts.json` prompt sidecar, which it copies as a plain file. The shared `runtime/registry.db` and other projects' event stores are left out unless you pass `--all-projects`. It ignores `--workspace` and `KXM_DATA_PATH`. Bindings, `update.yaml` and the other state roots are not included; see [Backup and restore](../operations/backup-and-restore.md).
|
|
3347
3347
|
|
|
3348
3348
|
| Option | Argument | Default | Description |
|
|
3349
3349
|
|---|---|---|---|
|
|
3350
3350
|
| `--out` | `<dir>` | `.kxm/backups/backup-<timestamp>` | Directory to write backup and manifest |
|
|
3351
|
+
| `--all-projects` | | Off | Also back up the Runtime registry and the event store and prompt sidecar of every project under `runtime/projects/`. Restoring that backup needs `kxm restore --all-projects` |
|
|
3351
3352
|
|
|
3352
|
-
- Writes a copy of each store and sidecar and `manifest.json`. `--dry-run` lists the stores and files it found and the files it would write without opening any store, so no WAL is checkpointed (JSON: `outDir`, `stores` with `storeId`, `sourcePath`, `backupFile`, `files` with `id`, `sourcePath`, `backupFile`, plus `dryRun` and `planned`).
|
|
3353
|
-
- JSON keys: `ok`, `backupId`, `outDir`, `manifest` (`schema`, `backupId`, `createdAt`, `projectRoot`, `stores` with `storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `sha256`, `bytes`, `integrity`; `files` with `id`, `sourcePath`, `backupFile`, `sha256`, `bytes`; `complete`; `omitted`; `manifestSha256`).
|
|
3353
|
+
- Writes a copy of each store and sidecar and `manifest.json`. `--dry-run` lists the stores and files it found and the files it would write without opening any store, so no WAL is checkpointed (JSON: `scope`, `outDir`, `stores` with `storeId`, `sourcePath`, `backupFile`, `files` with `id`, `sourcePath`, `backupFile`, plus `dryRun` and `planned`).
|
|
3354
|
+
- JSON keys: `ok`, `backupId`, `outDir`, `manifest` (`schema`, `backupId`, `createdAt`, `projectRoot`, `scope` (`project` or `all-projects`), `stateRoot`, `runtimeProjectKey`, `stores` with `storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `sha256`, `bytes`, `integrity`; `files` with `id`, `sourcePath`, `backupFile`, `sha256`, `bytes`; `complete`; `omitted`; `manifestSha256`).
|
|
3354
3355
|
- A store or sidecar that cannot be copied, or that appears while the backup runs, is listed in `omitted` and the manifest records `complete: false`. The command then prints `Backup is incomplete (<n> omitted); not ok:`, sets `ok: false`, and exits 1; `kxm restore` refuses that manifest.
|
|
3355
3356
|
- Exit 1 with `backup_failed`; `issues` carry codes such as `backup_no_stores` (no store found, or none could be copied) and `database_corrupted`.
|
|
3356
3357
|
|
|
@@ -3359,31 +3360,29 @@ kxm backup --out ../bk
|
|
|
3359
3360
|
```
|
|
3360
3361
|
|
|
3361
3362
|
```text
|
|
3362
|
-
Created SQLite backup with
|
|
3363
|
+
Created SQLite backup with 2 store(s) (project scope):
|
|
3363
3364
|
- hub-store: /work/proj/.kxm/state/kxm.db -> kxm.db (schema v5, 110592 bytes, sha256 sha256:56c6d...)
|
|
3364
|
-
- registry: /home/me/.local/state/kxm/runtime/registry.db -> registry.db (schema v1, 20480 bytes, sha256 sha256:a5bde...)
|
|
3365
3365
|
- events:38ed26cb8eeaa297f3b0b452: /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db -> run-events.db (schema v7, 348160 bytes, sha256 sha256:27c13...)
|
|
3366
3366
|
Manifest: /work/bk/manifest.json
|
|
3367
3367
|
```
|
|
3368
3368
|
|
|
3369
3369
|
The summary lists stores; the prompt sidecar appears under `files` in the manifest.
|
|
3370
3370
|
|
|
3371
|
-
In a project with a hub store and
|
|
3371
|
+
In a project with a hub store and a Runtime event store:
|
|
3372
3372
|
|
|
3373
3373
|
```bash
|
|
3374
3374
|
kxm backup --dry-run
|
|
3375
3375
|
```
|
|
3376
3376
|
|
|
3377
3377
|
```text
|
|
3378
|
-
dry run: back up
|
|
3378
|
+
dry run: back up 2 store(s) and 1 file(s) (project scope) to /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z (sources are not opened, so their WAL is not checkpointed)
|
|
3379
3379
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/kxm.db
|
|
3380
|
-
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/registry.db
|
|
3381
3380
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/run-events.db
|
|
3382
3381
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/run-events.db.run-prompts.json
|
|
3383
3382
|
would write /work/proj/.kxm/backups/backup-2026-09-23T17-44-51-277Z/manifest.json
|
|
3384
3383
|
```
|
|
3385
3384
|
|
|
3386
|
-
On a machine with no hub store and no Runtime
|
|
3385
|
+
On a machine with no hub store and no Runtime event store for this checkout yet:
|
|
3387
3386
|
|
|
3388
3387
|
```bash
|
|
3389
3388
|
kxm backup --json
|
|
@@ -3396,17 +3395,20 @@ kxm backup --json
|
|
|
3396
3395
|
## `kxm restore`
|
|
3397
3396
|
|
|
3398
3397
|
```text
|
|
3399
|
-
kxm restore <manifest>
|
|
3398
|
+
kxm restore <manifest> [--all-projects]
|
|
3400
3399
|
```
|
|
3401
3400
|
|
|
3402
|
-
Restores SQLite stores and prompt sidecars from a verified backup manifest
|
|
3401
|
+
Restores SQLite stores and prompt sidecars from a verified backup manifest into the checkout the Runtime would use, found as for `kxm backup`. It checks the manifest schema, refuses a manifest that records `complete: false` (`restore_incomplete`), checks that every backup file is present and each file's digest, and works out each target. A path under the manifest's project root is rebased onto this checkout. The backed-up checkout's own event store and its sidecar go to the store the Runtime derives for this checkout under the current user state root. Anything else under the user state root, such as the registry or another project's event store, is refused with `restore_requires_all_projects` unless you pass `--all-projects`, which rebases it onto the current user state root. A manifest written before backups were scoped records its Runtime stores as bare absolute paths; they count as machine-wide too, and `--all-projects` writes them back to those paths. A manifest without a `complete` field, from an older build, still restores.
|
|
3403
3402
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
-
|
|
3407
|
-
|
|
3408
|
-
-
|
|
3409
|
-
-
|
|
3403
|
+
| Option | Argument | Default | Description |
|
|
3404
|
+
|---|---|---|---|
|
|
3405
|
+
| `--all-projects` | | Off | Also restore the Runtime registry and other projects' event stores the backup holds, rolling back every project on the machine |
|
|
3406
|
+
|
|
3407
|
+
- Arguments: `<manifest>`, path to `manifest.json` or the directory that holds it.
|
|
3408
|
+
- Refuses before writing anything while the Runtime supervisor is running (`restore_runtime_running`), judged as `kxm runtime status` judges it but through a read-only open of the registry, and while a live `hub.pid` claim sits beside a hub store it would overwrite (`restore_hub_running`). Stop both first.
|
|
3409
|
+
- Before overwriting anything, a restore checks every store's recorded schema version against the ceiling for that store, so a store newer than this build is refused (`runtime_schema_newer`) before the first file is replaced. `--dry-run` runs the same manifest, file, digest, schema, scope, supervisor and hub checks and plans each target it would overwrite (and any `-wal` or `-shm` sidecar it would delete) without touching them. Dry-run JSON keys: `backupId`, `manifestPath`, `scope`, `stores` (`storeId`, `targetPath`, `schemaVersion`), `files` (`id`, `targetPath`), `dryRun`, `planned`.
|
|
3410
|
+
- JSON keys: `backupId`, `manifestPath`, `scope`, `restoredStores` (`storeId`, `sourcePath`, `backupFile`, `schemaVersion`, `integrity`). `scope` is absent for a manifest written before backups were scoped.
|
|
3411
|
+
- Exit 1 with `restore_failed`; `issues` carry codes such as `runtime_path_invalid`, `restore_manifest_invalid`, `restore_incomplete`, `restore_file_missing`, `restore_manifest_digest_mismatch`, `runtime_schema_newer`, `restore_requires_all_projects`, `restore_runtime_running`, `restore_runtime_unverified` (the registry could not be read to check the supervisor), and `restore_hub_running`.
|
|
3410
3412
|
- Two more checks run per store while restoring, after the plan checks: a backup file whose schema version differs from the version the manifest records is refused with `runtime_schema_mismatch`, and one that fails its SQLite integrity check with `database_corrupted`. In a multi-store restore, stores restored before the refused one stay restored.
|
|
3411
3413
|
|
|
3412
3414
|
```bash
|
|
@@ -3414,13 +3416,22 @@ kxm restore ../bk/manifest.json --dry-run
|
|
|
3414
3416
|
```
|
|
3415
3417
|
|
|
3416
3418
|
```text
|
|
3417
|
-
dry run: restore
|
|
3419
|
+
dry run: restore 2 store(s) and 1 file(s) from /work/bk/manifest.json; digests verified against the manifest
|
|
3418
3420
|
would write /work/proj/.kxm/state/kxm.db
|
|
3419
|
-
would write /home/me/.local/state/kxm/runtime/registry.db
|
|
3420
3421
|
would write /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db
|
|
3421
3422
|
would write /home/me/.local/state/kxm/runtime/projects/38ed26cb8eeaa297f3b0b452/run-events.db.run-prompts.json
|
|
3422
3423
|
```
|
|
3423
3424
|
|
|
3425
|
+
While the Runtime supervisor is running:
|
|
3426
|
+
|
|
3427
|
+
```bash
|
|
3428
|
+
kxm restore ../bk/manifest.json
|
|
3429
|
+
```
|
|
3430
|
+
|
|
3431
|
+
```text
|
|
3432
|
+
restore failed: /home/me/.local/state/kxm/runtime/registry.db: restore_runtime_running: the Runtime supervisor is running (pid 48213); stop it with `kxm runtime stop` and keep it stopped until the restore finishes
|
|
3433
|
+
```
|
|
3434
|
+
|
|
3424
3435
|
A missing manifest:
|
|
3425
3436
|
|
|
3426
3437
|
```bash
|
|
@@ -1731,7 +1731,7 @@ Everything under `.kxm/` at the project root falls into one of three groups.
|
|
|
1731
1731
|
| `logs/` | Ignored runtime logs | The hub and workers |
|
|
1732
1732
|
| `state/` | Ignored restart state: the hub database `kxm.db`, Pi sessions, worker manifests | The hub and workers |
|
|
1733
1733
|
| `run/` | Ignored sockets (`run/ssh-sockets/`) | `kxm ssh` |
|
|
1734
|
-
| `backups/` | Ignored; each `backup-<time>/` holds copies of the hub database,
|
|
1734
|
+
| `backups/` | Ignored; each `backup-<time>/` holds copies of the hub database, this project's Runtime event store and prompt sidecar (every project's, and the registry, with `--all-projects`), and `manifest.json` | `kxm backup` (without `--out`) |
|
|
1735
1735
|
| `config/` | Legacy: its JSON files make the project unloadable | Nothing current |
|
|
1736
1736
|
| `.kxm-init-transaction/` (sibling of `.kxm/` at the Git root) | Ignored; interrupted `kxm init` state | `kxm init` |
|
|
1737
1737
|
|
|
@@ -331,7 +331,7 @@ kxm runtime stop
|
|
|
331
331
|
`kxm backup` writes a verified copy and a hashed manifest to `.kxm/backups/backup-<timestamp>/`.
|
|
332
332
|
|
|
333
333
|
> [!WARNING]
|
|
334
|
-
> `kxm backup` copies
|
|
334
|
+
> `kxm backup` copies this project's hub store and its Runtime run event store. The Runtime registry and other projects' run stores are shared by every project on this machine, so they are copied only with `--all-projects`, and a later `kxm restore` refuses to roll them back without the same flag. It does not copy bindings, `update.yaml` or the other state roots; [Back up everything else](../operations/backup-and-restore.md#back-up-everything-else) covers those.
|
|
335
335
|
|
|
336
336
|
[Back up and restore KXM](../operations/backup-and-restore.md) and [Upgrade KXM](../operations/upgrade.md) cover restores and rollback.
|
|
337
337
|
|
|
@@ -77,5 +77,6 @@ flowchart TD
|
|
|
77
77
|
## Rollback and escalation
|
|
78
78
|
|
|
79
79
|
- **Rollback:** restore the last verified backup with
|
|
80
|
-
`kxm restore <manifest>` while the hub
|
|
80
|
+
`kxm restore <manifest>` while the hub and the Runtime are stopped; it
|
|
81
|
+
refuses while either is running.
|
|
81
82
|
- **Escalation:** <primary on-call or human operator contact>
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.104",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|