@kontextmind/kxm 0.7.103 → 0.7.105
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 +39 -8
- package/docs/concepts/data-and-storage.md +1 -1
- package/docs/contributing/test-matrix.md +11 -6
- package/docs/operations/backup-and-restore.md +59 -27
- package/docs/operations/deploy.md +1 -1
- package/docs/reference/cli-reference.md +73 -55
- package/docs/reference/config-reference.md +1 -1
- package/docs/reference/workflow-definitions.md +4 -4
- package/docs/start/first-workflow.md +6 -2
- package/docs/start/quickstart-claude-code.md +11 -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 +1802 -1011
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +322 -292
- package/plugins/kxm/dist/runtime.js +427 -351
- 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 +132 -30
- package/plugins/kxm/src/cli/system.ts +19 -1
- package/plugins/kxm/src/cli/tasks.ts +55 -31
- package/plugins/kxm/src/cli/workflows.ts +52 -54
- package/plugins/kxm/src/cli.ts +8 -6
- package/plugins/kxm/src/database.ts +193 -65
- package/plugins/kxm/src/engine.ts +79 -3
- package/plugins/kxm/src/harness.ts +6 -5
- 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/plugins/kxm/src/suggest.ts +132 -79
- package/plugins/kxm/src/workflow-manager.ts +42 -19
- package/schemas/backup-manifest.schema.json +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,18 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
9
|
+
- **Claude-only workflow recommendations now fail honestly when execution is unavailable.**
|
|
10
|
+
`suggest` honors explicit harness constraints, uses flat installable IDs and verified
|
|
11
|
+
capability-appropriate routes, refuses unchecked existing definitions, and never substitutes a
|
|
12
|
+
different writer. `workflow add` validates IDs and runner-compatible YAML before
|
|
13
|
+
writing; picked/imported dry runs leave configuration untouched. `gate validate
|
|
14
|
+
--file` accepts local YAML without changing webhook environment-source validation.
|
|
15
|
+
Run output distinguishes creation from execution and names live prerequisites and
|
|
16
|
+
`runs drive/status/receipt`; incompatible `task run` requests leave tasks unchanged.
|
|
17
|
+
Harness inventory reflects the project default, and initialization explains the
|
|
18
|
+
generic Pi/npm starter settings. Claude's one-shot profile remains read-only; Pi/Grok
|
|
19
|
+
writer recommendations require audited writer admission. See the [CLI reference](docs/reference/cli-reference.md#kxm-suggest).
|
|
20
|
+
|
|
9
21
|
- **`kxm workflow add --template <name>` writes a valid first workflow.**
|
|
10
22
|
`implement-and-verify` (the `implementer` agent, then the project's `test` gate; a
|
|
11
23
|
failing gate sends the work back to `implement` at most twice), `dual-critic-review` (two
|
|
@@ -349,14 +361,33 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
349
361
|
and admits those two routes. One-shot production no longer falls through to an
|
|
350
362
|
unadmitted `claude-3-7-sonnet`.
|
|
351
363
|
|
|
352
|
-
- **`kxm backup` includes
|
|
353
|
-
copies
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
364
|
+
- **`kxm backup` includes this project's Runtime event store, and never another
|
|
365
|
+
project's unless asked.** Discovery copies the checkout's own
|
|
366
|
+
`$S/runtime/projects/<projectKey>/run-events.db`, with the key derived from the
|
|
367
|
+
canonical checkout path as the Runtime derives it, plus its
|
|
368
|
+
`run-events.db.run-prompts.json` sidecar. `kxm backup` and `kxm restore` act for the
|
|
369
|
+
checkout the Runtime would use (the Git root holding `.kxm/project.yaml`), so running
|
|
370
|
+
them from a subdirectory finds the same stores. The shared `$S/runtime/registry.db` and
|
|
371
|
+
every other project's event store are copied only with `kxm backup --all-projects`, and
|
|
372
|
+
a restore of a backup that holds them is refused with `restore_requires_all_projects`
|
|
373
|
+
unless `kxm restore --all-projects` is given; a project restore therefore never rolls
|
|
374
|
+
back another project's runs, receipts or sync outbox, or the registry. A project
|
|
375
|
+
restore writes the event store where the Runtime looks for the checkout being
|
|
376
|
+
restored, under the current `KXM_STATE_HOME`; `--all-projects` rebases the shared
|
|
377
|
+
stores onto the current user state root. The manifest records `scope`, `stateRoot`
|
|
378
|
+
and `runtimeProjectKey`. A copy that misses a discovered store is `complete: false`:
|
|
379
|
+
`kxm backup` exits 1 with `ok: false`, and restore refuses that manifest.
|
|
380
|
+
`kxm backup --help` no longer says Runtime stores are left out.
|
|
381
|
+
|
|
382
|
+
- **`kxm restore` refuses while the Runtime supervisor or a hub is running.** Before any
|
|
383
|
+
write, and under `--dry-run` too, it fails with `restore_runtime_running` when the
|
|
384
|
+
supervisor is live by the same test `kxm runtime status` uses (running state, fresh
|
|
385
|
+
heartbeat, live PID), read through a read-only open of the registry, and with
|
|
386
|
+
`restore_hub_running` when a live `hub.pid` claim sits beside a hub store it would
|
|
387
|
+
overwrite. A registry too broken to read fails closed with
|
|
388
|
+
`restore_runtime_unverified`, which says to stop the Runtime and move the registry
|
|
389
|
+
aside. Replacing a SQLite file under an open writer could lose commits or corrupt the
|
|
390
|
+
store.
|
|
360
391
|
|
|
361
392
|
- **Signed webhooks cannot be replayed.** KXM's own webhook senders now sign the
|
|
362
393
|
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
|
|
|
@@ -60,8 +60,11 @@ to run one file is in [Develop KXM](development.md#run-one-file-or-one-test).
|
|
|
60
60
|
| Settlement from a structured result only; prose outcomes and undeclared outcomes end as `outcome_unknown` | `pi-producer.test.ts`, `engine.test.ts` |
|
|
61
61
|
| Routing records carry `workflowId`, `askSha256`, `objectiveSha256` and `stepWrites`, and settle only `blocked` or `failed` | `route-admission.test.ts` |
|
|
62
62
|
| Dispatch context: only committed, pinned memory and hash-verified promoted skills reach an agent; the rest is a `dispatch_context_*` gap | `engine.test.ts` ("dispatch context: agents receive only committed, pinned memory and verified skills; anything else is withheld with a gap and the step still completes") |
|
|
63
|
-
|
|
|
63
|
+
| Run creation reports no execution and concrete live prerequisites; task refusal leaves task/Runtime unchanged; project harness default is honored | `cli.test.ts`, `engine.test.ts`, `harness.test.ts` |
|
|
64
64
|
| `kxm workflow add --template` writes workflows that validate and plan; an impossible gate outcome is refused | `cli-experience.test.ts` ("workflow add templates validate and plan a run, and a gate outcome the step can never produce is refused") |
|
|
65
|
+
| Flat workflow IDs and schema/compiler validation precede mutations; imported/picked local/global dry runs write nothing | `role-and-workflow-manager.test.ts`, `cli-experience.test.ts` |
|
|
66
|
+
| Claude-only suggestions honor detected/authenticated routes, require audited writer profiles, refuse existing unchecked definitions, and quote shell arguments literally | `suggest.test.ts`, `cli-experience.test.ts` |
|
|
67
|
+
| Explicit local YAML validates with runner schema/transitions; webhook environment sources retain JSON/secret checks | `gate-validation.test.ts` |
|
|
65
68
|
| `kxm workflow add` writes a local workflow only where the loader reads it and only if the project still loads; outside a project it refuses | `role-and-workflow-manager.test.ts` ("workflow add writes only what the project loader accepts, at the project root, and loadKxmProject still loads") |
|
|
66
69
|
| `kxm workflow add --pick <global-id>` copies the global definition into the project, not the scaffold, and the loader check refuses one that does not fit | `role-and-workflow-manager.test.ts` ("workflow add --pick <global-id> copies that global definition into the project, and refuses one the project loader rejects") |
|
|
67
70
|
| `default.yaml` and the 13-step `fix.yaml` compile deterministically; back edges need budgets | `engine-compile.test.ts` |
|
|
@@ -136,17 +139,19 @@ The context suites in detail:
|
|
|
136
139
|
| Metadata-only dashboard: ops mode, presence-only fallback, observer filtering, keys, body-free local projection | `tui.test.ts`, `hub-api.test.ts` |
|
|
137
140
|
| 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
141
|
| 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/`;
|
|
142
|
+
| `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
143
|
| 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
144
|
| KXM schemas, restricted YAML fixtures, cross-resource semantics and sync-safe rejection | `contracts.test.ts`, `restricted-yaml.test.ts` |
|
|
142
145
|
| Harness detection, auth and dispatch for the built-in catalog, including Windows launch rules | `harness.test.ts` |
|
|
143
146
|
| `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
147
|
|
|
145
148
|
The round trip seeds its Runtime stores in a project-local `.kxm/runtime/`
|
|
146
|
-
layout that the Runtime never writes.
|
|
147
|
-
|
|
148
|
-
`
|
|
149
|
-
|
|
149
|
+
layout that the Runtime never writes. Separate tests seed two projects' event
|
|
150
|
+
stores, their prompt sidecars and a shared registry under a temporary
|
|
151
|
+
`KXM_STATE_HOME`, at the paths the Runtime derives, and check each scope and
|
|
152
|
+
that a partial manifest is refused. The supervisor-liveness test fakes a live
|
|
153
|
+
supervisor with a fresh registry record naming the test's own PID; no test backs
|
|
154
|
+
up or restores stores a running supervisor wrote.
|
|
150
155
|
|
|
151
156
|
## Packaging, release and repository gates
|
|
152
157
|
|
|
@@ -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.
|