@kontextmind/kxm 0.7.181 → 0.7.182
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 +14 -0
- package/docs/architecture/authentication.md +11 -2
- package/docs/architecture/hosted-direction.md +7 -5
- package/docs/architecture/inventory.md +4 -2
- package/docs/architecture/platform.md +18 -4
- package/docs/contributing/ci-and-release.md +18 -9
- package/docs/contributing/decisions.md +31 -0
- package/docs/contributing/development.md +39 -1
- package/docs/reference/configuration.md +22 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +45 -0
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/studio-layout.ts +50 -0
- package/scripts/ci-postgres-17.sh +144 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
9
|
+
- **The witnessed MG1 Python control-plane foundation is now reproducible from this repository.**
|
|
10
|
+
`python/kxm-control` provides the pinned Python 3.13 uv workspace, fixed
|
|
11
|
+
`kxmd` Postgres/Temporal caller, Authentik user and runner verification
|
|
12
|
+
that requires token expiry, tenant/project/repository registration,
|
|
13
|
+
owner/admin tenant rename, revision and artifact APIs, and a probe entry
|
|
14
|
+
point. OpenAPI and the Studio path inventory are derived from the FastAPI
|
|
15
|
+
app. The checked generator emits the revision helper. Studio exposes the
|
|
16
|
+
bounded `/api/control/account` relay with a three-second bound and explicit
|
|
17
|
+
failure states. Removing registration leaves source state untouched and
|
|
18
|
+
later registry calls return `registration_unavailable` until the schema is
|
|
19
|
+
installed again. The package does not change host or guest installs. The
|
|
20
|
+
required CI aggregate runs the Python contracts against PostgreSQL 17. This
|
|
21
|
+
does not move the hub or Runtime from their existing Node and SQLite owners.
|
|
22
|
+
|
|
9
23
|
- **KXM configuration now has one owner-validated terminal editor.**
|
|
10
24
|
`kxm config edit --scope project|user` and OMP/Pi `/kxm config` publish the
|
|
11
25
|
same `kxm.config.v1` surface. Accepted writes are saved and re-read; rejected
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Authentication
|
|
2
2
|
|
|
3
|
-
Read from `plugins/kxm/src/harness.ts
|
|
4
|
-
|
|
3
|
+
Read from `plugins/kxm/src/harness.ts`, `plugins/kxm/src/cli.ts`, and
|
|
4
|
+
`python/kxm-control/src/kxm_control/auth.py`.
|
|
5
5
|
`kxm harness list` runs `probeHarnesses` (`cmdHarnessList` in
|
|
6
6
|
`plugins/kxm/src/cli/project.ts`). For each detected harness that has
|
|
7
7
|
`authArgs`, `probeEntry` runs that command and `interpretAuth` reads the
|
|
@@ -23,6 +23,15 @@ the harness command's own stdout.
|
|
|
23
23
|
issues, or clears the local session token file. That token is a KXM session
|
|
24
24
|
token, not a provider key. Provider logins stay in each harness.
|
|
25
25
|
|
|
26
|
+
The MG1 Python control API uses a separate Authentik boundary. It accepts only
|
|
27
|
+
RS256 tokens whose issuer and audience match its configured values, whose
|
|
28
|
+
`exp` is present and unexpired, and whose `kxm_actor` is `user` or `runner`.
|
|
29
|
+
A token without `exp` is rejected.
|
|
30
|
+
The signed subject and actor kind must match
|
|
31
|
+
an explicit tenant membership; a user membership never authorizes a runner
|
|
32
|
+
token with the same subject. Only a user can create a tenant. The API does not
|
|
33
|
+
store the bearer token.
|
|
34
|
+
|
|
26
35
|
## Related
|
|
27
36
|
|
|
28
37
|
- [Access](access.md)
|
|
@@ -52,11 +52,13 @@ roadmap question. The tracker wins.
|
|
|
52
52
|
proposal is not in this repository. Its delivery position is unchanged:
|
|
53
53
|
none of the delivery is scheduled there.
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
Today the Node hub and Runtime still write local SQLite and local files. The
|
|
56
|
+
checked-in MG1 Python control API is the narrow exception: it owns only central
|
|
57
|
+
registration and bounded tenant artifacts in the fixed `kxmd` PostgreSQL
|
|
58
|
+
account. Temporal is provisioned for `kxmd`, but no production workflow cutover
|
|
59
|
+
is selected. Redis Streams, NATS, raw WebSockets, the A2A Python SDK, and S3
|
|
60
|
+
remain backlog. The rest of this page remains a target, not a live deployment
|
|
61
|
+
description.
|
|
60
62
|
|
|
61
63
|
## Related
|
|
62
64
|
|
|
@@ -6,7 +6,8 @@ Read from `plugins/kxm/src/cli.ts`, `plugins/kxm/src/config-editor.ts`,
|
|
|
6
6
|
`plugins/kxm/src/runtime-paths.ts`, `plugins/kxm/src/mcp-server.ts`,
|
|
7
7
|
`plugins/kxm/src/oneshot-producer.ts`, `plugins/kxm/src/studio-layout.ts`,
|
|
8
8
|
`plugins/kxm/src/cli/types.ts` (`workspaceDirs`),
|
|
9
|
-
`plugins/kxm/src/local-snapshot.ts`,
|
|
9
|
+
`plugins/kxm/src/local-snapshot.ts`, `scripts/kxm-worker.mjs`, and
|
|
10
|
+
`python/kxm-control/src/kxm_control/{api,registry}.py`.
|
|
10
11
|
|
|
11
12
|
| Surface | Started by | Listener or path | Store it owns | Defining file |
|
|
12
13
|
| --- | --- | --- | --- | --- |
|
|
@@ -21,7 +22,8 @@ Read from `plugins/kxm/src/cli.ts`, `plugins/kxm/src/config-editor.ts`,
|
|
|
21
22
|
| MCP server | Claude plugin or stdio launch | stdio | none | `plugins/kxm/src/mcp-server.ts` |
|
|
22
23
|
| One-shot harness | supervisor producer | child process, no listener | none | `plugins/kxm/src/oneshot-producer.ts` |
|
|
23
24
|
| Pi worker | `kxm agent worker` | no HTTP listener in `scripts/kxm-worker.mjs` | none of its own | `scripts/kxm-worker.mjs` |
|
|
24
|
-
| Studio | `kxm studio serve` | `127.0.0.1:4242` | none | `plugins/kxm/src/studio-layout.ts` |
|
|
25
|
+
| Studio | `kxm studio serve` | `127.0.0.1:4242` | none; `/api/control/account` is a bounded relay to the configured control API | `plugins/kxm/src/studio-layout.ts` |
|
|
26
|
+
| Python control API | ASGI server loading `kxm_control.api:app` | deployment-selected; the package declares no network default | `kxm_registry` in the fixed `kxmd` PostgreSQL database and the configured tenant artifact root | `python/kxm-control/src/kxm_control/api.py` |
|
|
25
27
|
| Workspace config | `workspaceDirs` | `.kxm/config`, or `KXM_CONFIG_DIR` | files in that directory | `plugins/kxm/src/cli/types.ts` |
|
|
26
28
|
| Workspace logs | `workspaceDirs` | `.kxm/logs`, or `KXM_LOGS_DIR` | log files | `plugins/kxm/src/cli/types.ts` |
|
|
27
29
|
| Workspace assets | `workspaceDirs` | `.kxm/assets`, or `KXM_ASSETS_DIR` | asset files | `plugins/kxm/src/cli/types.ts` |
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Read from `plugins/kxm/src/cli.ts`, `plugins/kxm/src/runtime-supervisor.ts`,
|
|
4
4
|
`scripts/kxm-hub.mjs`, `plugins/kxm/src/hub.ts`, `plugins/kxm/src/mcp-server.ts`,
|
|
5
|
-
|
|
5
|
+
`plugins/kxm/src/oneshot-producer.ts`, and
|
|
6
|
+
`python/kxm-control/src/kxm_control/api.py`.
|
|
6
7
|
|
|
7
8
|
KXM on this checkout is local-first. The `kxm` CLI is the operator entry
|
|
8
9
|
(`plugins/kxm/src/cli.ts`, launched through `scripts/kxm.mjs`). The hub is an
|
|
@@ -18,17 +19,29 @@ requested. The MCP server (`plugins/kxm/src/mcp-server.ts`) uses
|
|
|
18
19
|
spawned by the producer registered in `plugins/kxm/src/oneshot-producer.ts`.
|
|
19
20
|
That child process is not a listener.
|
|
20
21
|
|
|
22
|
+
The checked-in MG1 Python control API is additive. It owns central registration
|
|
23
|
+
and bounded tenant artifacts through the fixed `kxmd` account. Only an owner
|
|
24
|
+
or admin membership can rename a tenant. Removing the registration schema
|
|
25
|
+
leaves source state in place, and later registry routes return
|
|
26
|
+
`registration_unavailable` until the schema is installed again. The API does
|
|
27
|
+
not install or change host or guest software. It does not replace the Node
|
|
28
|
+
hub, Runtime, CLI, or their SQLite stores. Studio can relay
|
|
29
|
+
`GET /api/control/account` to this service when `KXM_CONTROL_URL` is configured.
|
|
30
|
+
|
|
21
31
|
```mermaid
|
|
22
32
|
flowchart LR
|
|
23
33
|
cli["kxm CLI"]
|
|
24
|
-
hub["hub"]
|
|
25
|
-
sup["Runtime supervisor"]
|
|
34
|
+
hub["Node hub"]
|
|
35
|
+
sup["Node Runtime supervisor"]
|
|
26
36
|
mcp["MCP server"]
|
|
27
37
|
shot["one-shot harness"]
|
|
38
|
+
studio["Studio"]
|
|
39
|
+
control["MG1 Python control API"]
|
|
28
40
|
cli -->|"kxm hub start"| hub
|
|
29
41
|
mcp -->|"HTTP to hub"| hub
|
|
30
42
|
cli -->|"kxm run"| sup
|
|
31
43
|
sup -->|"spawn producer"| shot
|
|
44
|
+
studio -->|"bounded account read"| control
|
|
32
45
|
```
|
|
33
46
|
|
|
34
47
|
## Listeners
|
|
@@ -39,7 +52,8 @@ flowchart LR
|
|
|
39
52
|
| Supervisor HTTP | `127.0.0.1` and an ephemeral port when the requested port is `0` |
|
|
40
53
|
| MCP | stdio, no TCP port |
|
|
41
54
|
| CLI and one-shot harness | no listener |
|
|
42
|
-
| Studio, when served | `127.0.0.1:4242` from `createStudioServer` in `plugins/kxm/src/studio-layout.ts` |
|
|
55
|
+
| Studio, when served | `127.0.0.1:4242` from `createStudioServer` in `plugins/kxm/src/studio-layout.ts`; its optional control relay targets `KXM_CONTROL_URL` |
|
|
56
|
+
| Python control API | ASGI listener selected by deployment; the package declares no host or port default |
|
|
43
57
|
| Antigravity OAuth callback | loopback port `51121` only during login (`plugins/kxm/src/providers/antigravity/auth/oauth.ts`) |
|
|
44
58
|
|
|
45
59
|
## Related
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# CI and release
|
|
2
2
|
|
|
3
|
-
Pull requests run lint, typecheck,
|
|
4
|
-
Pushes to `main` also run
|
|
3
|
+
Pull requests run lint, typecheck, the Node unit suite on Linux Node 24, and
|
|
4
|
+
the Python control-plane suite against PostgreSQL 17. Pushes to `main` also run
|
|
5
|
+
the Node suite and the full OS × Node matrix.
|
|
5
6
|
The complete suite with coverage floors runs nightly, every merge to `main`
|
|
6
7
|
cuts a patch release, and every release is verified before it reaches npm.
|
|
7
8
|
This page explains which checks run where, how a merge becomes a published
|
|
@@ -15,7 +16,7 @@ release and an npm publish; the nightly job adds the slower suites.
|
|
|
15
16
|
|
|
16
17
|
```mermaid
|
|
17
18
|
flowchart LR
|
|
18
|
-
PR[Pull request] -->|"
|
|
19
|
+
PR[Pull request] -->|"Node shards, Python/Postgres, docs lint, plugin validation"| REQ["CI / required"]
|
|
19
20
|
REQ --> MERGE{Merged?}
|
|
20
21
|
MERGE -->|yes| MAIN[main]
|
|
21
22
|
MAIN -->|"unit shards plus validate:pr matrix"| PUSH[Push CI]
|
|
@@ -40,6 +41,7 @@ together.
|
|
|
40
41
|
| Pull request, push to `main`, or manual | `ci.yml` (`required`) | Aggregates the lanes below into one pass/fail check named `CI / required` |
|
|
41
42
|
| Pull request and push | `ci.yml` (Docs lint) | `lint:docs` and `check:versions`, always |
|
|
42
43
|
| Code pull request and code push | `ci.yml` (Unit engine and Unit, Linux Node 24) | `engine.test.ts` split by test name; `permission.test.ts`, `runtime.test.ts`, and `package-install.test.ts` one file at a time; every other unit file in two light shards. Typecheck and `check-generated` run on `light-1` |
|
|
44
|
+
| Code pull request and code push | `ci.yml` (Python control plane) | Python 3.13 `unittest` contracts from the frozen uv workspace against PostgreSQL 17 started by `scripts/ci-postgres-17.sh` |
|
|
43
45
|
| Platform-sensitive pull request | `ci.yml` (Unit, Windows Node 24) | The same unit lanes on `windows-latest` |
|
|
44
46
|
| Push to `main`, or manual | `ci.yml` (Validate matrix) | `validate:pr` on Linux and Windows for Node 22.19.0 and Node 24; not on a pull request |
|
|
45
47
|
| Code pull request and code push | `ci.yml` (Plugin validation) | `claude plugin validate --strict` on the marketplace and the plugin |
|
|
@@ -73,7 +75,8 @@ What moved off the pull-request lane, and what did not:
|
|
|
73
75
|
runs `validate:pr` on Windows.
|
|
74
76
|
- Coverage, `test/simulations`, and `npm pack --dry-run` stay in the nightly
|
|
75
77
|
complete suite. They were not part of pull-request CI before this split.
|
|
76
|
-
- Plugin validation still
|
|
78
|
+
- Python control-plane validation and Plugin validation still run on code pull
|
|
79
|
+
requests and code pushes.
|
|
77
80
|
|
|
78
81
|
Run `npm run verify` locally before every push. A nightly failure is a release
|
|
79
82
|
blocker: it is the only place the simulation suite and coverage floors run.
|
|
@@ -82,8 +85,13 @@ blocker: it is the only place the simulation suite and coverage floors run.
|
|
|
82
85
|
|
|
83
86
|
Code pull requests run Docs lint, two Linux Node 24 engine shards, a serial
|
|
84
87
|
lane (`permission.test.ts`, then `runtime.test.ts`, then
|
|
85
|
-
`package-install.test.ts`), two light shards for every other unit file,
|
|
86
|
-
|
|
88
|
+
`package-install.test.ts`), two light shards for every other Node unit file,
|
|
89
|
+
the Python 3.13 control-plane suite against PostgreSQL 17, and Plugin
|
|
90
|
+
validation. The Python lane stays on `kontextmind-doks`. That scale set has
|
|
91
|
+
no container-engine socket, so the job starts PostgreSQL 17 with
|
|
92
|
+
`scripts/ci-postgres-17.sh` on `127.0.0.1:5432` and uses the same admin and
|
|
93
|
+
test DSNs as the contributor procedure in [Develop KXM](development.md).
|
|
94
|
+
`light-1` typechecks and checks generated bundles.
|
|
87
95
|
`engine.test.ts` is split by test name because that file alone was 174
|
|
88
96
|
seconds; the serial lane keeps the next longest files off the light pool,
|
|
89
97
|
which was 268 seconds when every non-engine file shared one job.
|
|
@@ -165,9 +173,10 @@ workaround.
|
|
|
165
173
|
The first job, Classify changes, lists the changed paths and sets `code=false`
|
|
166
174
|
when every path matches `*.md` (including `plans/**/*.md`), `docs/**`,
|
|
167
175
|
`.kxm/assets/**`, `LICENSE`, the issue and PR templates, or `dependabot.yml`.
|
|
168
|
-
A non-markdown file under `plans/` is code. Unit lanes,
|
|
169
|
-
and the Validate matrix are skipped. Docs lint runs,
|
|
170
|
-
passes. `scripts/ci-classify.mjs` and
|
|
176
|
+
A non-markdown file under `plans/` is code. Unit lanes, the Python control-plane
|
|
177
|
+
lane, Plugin validation, and the Validate matrix are skipped. Docs lint runs,
|
|
178
|
+
and `CI / required` passes. `scripts/ci-classify.mjs` and
|
|
179
|
+
`ci-contract.test.ts` pin this behavior.
|
|
171
180
|
|
|
172
181
|
`platform=true` when a path is a package manifest, the lockfile, a file under
|
|
173
182
|
`scripts/` or `.github/workflows/`, or a filename that names path, process,
|
|
@@ -198,3 +198,34 @@ Evidence: Evidence: `git diff HEAD origin/main` on the stale branch showed upstr
|
|
|
198
198
|
- Interim recommendation: use the opt-in installer until OMP exposes a
|
|
199
199
|
first-class transactional status-preset API.
|
|
200
200
|
Evidence: `plugins/kxm/src/cli/plugins.ts`, `plugins/kxm/src/session-work.ts`, and `test/core/cli.test.ts`.
|
|
201
|
+
|
|
202
|
+
## 2026-10-11 — Bring the witnessed MG1 Python foundation onto main (migration)
|
|
203
|
+
|
|
204
|
+
- Context: the tracker records MG1 as passed off-repo while the operator's
|
|
205
|
+
primary checkout holds the matching untracked Python workspace, MG0 evidence,
|
|
206
|
+
and Studio control-account proxy. Leaving the implementation outside Git
|
|
207
|
+
makes the recorded gate irreproducible and blocks MG2 from a reviewed base.
|
|
208
|
+
- Options:
|
|
209
|
+
- Keep MG1 off-repo and start the roadmap's role-authority work — weight 0.2.
|
|
210
|
+
Avoids a CI change, but preserves the tracker/source contradiction and risks
|
|
211
|
+
losing the only implementation matching the deployed witness.
|
|
212
|
+
- Import MG1 at `python/kxm-control`, keep Node as the current product owner,
|
|
213
|
+
and add a required Python/PostgreSQL contract lane — weight 0.9. Makes the
|
|
214
|
+
accepted foundation reproducible without claiming a Python cutover.
|
|
215
|
+
- Replace the Node CLI and Runtime with the Python package now — weight 0.1.
|
|
216
|
+
MG2 through MG8 are still open, so this would skip import, bridge, runner,
|
|
217
|
+
parity, and retirement gates.
|
|
218
|
+
- Selected: import the bounded MG1 foundation and its evidence, harden the
|
|
219
|
+
Studio proxy, and make the Python unit/contract suite part of `CI / required`.
|
|
220
|
+
SQLite and the Node Runtime remain authoritative. OpenAPI and the Studio
|
|
221
|
+
path inventory are derived from the FastAPI app; the checked generator emits
|
|
222
|
+
the revision helper. The package does not change host or guest installs.
|
|
223
|
+
After registration removal, registry routes return `registration_unavailable`
|
|
224
|
+
until the schema is installed again.
|
|
225
|
+
- Solid recommendation: keep every accepted migration wave in Git with its
|
|
226
|
+
generated contracts and a required service-backed test lane.
|
|
227
|
+
- Interim recommendation: if the service lane is unavailable, do not merge the
|
|
228
|
+
Python source; retain the off-repo tracker wording rather than claiming an
|
|
229
|
+
unverified gate.
|
|
230
|
+
Evidence: `plans/implementation-plan.md`, `plans/plan-python-migration.md`,
|
|
231
|
+
`python/kxm-control/tests/test_mg1.py`, and `.github/workflows/ci.yml`.
|
|
@@ -23,7 +23,10 @@ extension, the Claude Code plugin, or the bundled skills.
|
|
|
23
23
|
`kxm lane run <unit> --brief <file> --workflow implement-only`.
|
|
24
24
|
The read-only critic workflows on the same command are
|
|
25
25
|
`review-arch-only` and `review-cli-only`.
|
|
26
|
-
- Docker for the clean-container install smoke
|
|
26
|
+
- Docker for the clean-container install smoke and the Python control-plane
|
|
27
|
+
contract suite.
|
|
28
|
+
- Python 3.13 and [`uv`](https://docs.astral.sh/uv/) for the Python
|
|
29
|
+
control-plane contract suite.
|
|
27
30
|
|
|
28
31
|
## Set up a checkout
|
|
29
32
|
|
|
@@ -255,6 +258,41 @@ It runs three scripts in order:
|
|
|
255
258
|
| 2 | `npm run check` | `tsc --noEmit`, `markdownlint-cli2`, and version surfaces |
|
|
256
259
|
| 3 | `npm run check:generated` | Generated artifacts are tracked and match the staged copy |
|
|
257
260
|
|
|
261
|
+
Changes to the Python control plane also run its frozen-workspace contract
|
|
262
|
+
suite before the npm gate. The suite drops and recreates `kxm_mg1_test`, so
|
|
263
|
+
the admin DSN account needs database create and drop privilege. The `postgres`
|
|
264
|
+
superuser in the container below is that account. Run PostgreSQL 17 in a
|
|
265
|
+
disposable container published only on loopback `127.0.0.1:5432`, with
|
|
266
|
+
`POSTGRES_PASSWORD=postgres`. Leave port `5432` on loopback free. Wait until
|
|
267
|
+
`pg_isready` succeeds, export the two DSNs CI uses, run the frozen suite, and
|
|
268
|
+
remove the container afterward:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
docker rm -f kxm-mg1-pg >/dev/null 2>&1 || true
|
|
272
|
+
docker run --detach --name kxm-mg1-pg \
|
|
273
|
+
--publish 127.0.0.1:5432:5432 \
|
|
274
|
+
--env POSTGRES_PASSWORD=postgres \
|
|
275
|
+
postgres:17
|
|
276
|
+
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do
|
|
277
|
+
docker exec kxm-mg1-pg pg_isready -U postgres && break
|
|
278
|
+
sleep 1
|
|
279
|
+
done
|
|
280
|
+
docker exec kxm-mg1-pg pg_isready -U postgres
|
|
281
|
+
export KXM_MG1_ADMIN_DSN='host=127.0.0.1 port=5432 dbname=postgres user=postgres password=postgres'
|
|
282
|
+
export KXM_MG1_TEST_DSN='host=127.0.0.1 port=5432 dbname=kxm_mg1_test user=postgres password=postgres'
|
|
283
|
+
uv run --project python --frozen python -m unittest discover -s python/kxm-control/tests
|
|
284
|
+
suite_status=$?
|
|
285
|
+
docker rm -f kxm-mg1-pg
|
|
286
|
+
(exit "$suite_status")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The required CI aggregate repeats that suite against PostgreSQL 17 with the
|
|
290
|
+
same two DSNs and the same `postgres` superuser. On the `kontextmind-doks`
|
|
291
|
+
runners, `scripts/ci-postgres-17.sh` starts that server on `127.0.0.1:5432`
|
|
292
|
+
from a pinned PostgreSQL 17 binary. Contributors use the `postgres:17`
|
|
293
|
+
container procedure above, which publishes the same port and DSNs. The suite
|
|
294
|
+
remains a CI lane rather than a third root gate script.
|
|
295
|
+
|
|
258
296
|
Every suite script passes `--test-force-exit`, and `scripts/run-bounded.mjs` stops the run if the suite is still going after its wall-clock limit (20 minutes, or 40 for the complete and coverage suites). The bound is that wall clock per script. A per-test timeout is not used because under `node --test` each file is itself a test, so a per-test timeout also bounds each file.
|
|
259
297
|
|
|
260
298
|
Other scripts you will use:
|
|
@@ -30,6 +30,7 @@ Names that start with `KXM_` are not all operator settings. This map covers ever
|
|
|
30
30
|
| Agents | `KXM_SERVER_URL`, `KXM_AUTH_TOKEN`, `KXM_PROJECT`, `KXM_AGENT_NAME`, `KXM_AGENT_PURPOSE` | [Agent settings](#agent-settings) |
|
|
31
31
|
| Long-lived Pi workers | `KXM_PI_COMMAND`, `KXM_WORKER_*` (operator settings), `KXM_AGENT_LOG_PATH` | [Long-lived worker settings](#long-lived-worker-settings) |
|
|
32
32
|
| Runtime supervisor | `KXM_STATE_HOME`, `KXM_RUNTIME_SYNC_INTERVAL_MS`, `KXM_RUNTIME_STOP_GRACE_MS` | [Runtime supervisor settings](#runtime-supervisor-settings) |
|
|
33
|
+
| Studio and Python control plane | `KXM_CONTROL_URL`, `KXM_KXMD_*`, `KXM_AUTHENTIK_*`, `KXM_POSTGRES_*`, `KXM_DATABASE`, `KXM_DB_USER`, `KXM_TEMPORAL_*` | [Studio and Python control-plane settings](#studio-and-python-control-plane-settings) |
|
|
33
34
|
| Operator CLI and sessions | `KXM_USER_CONFIG_DIR`, `KXM_USER_TELEMETRY_DIR`, `KXM_SESSION_TOKEN`, `KXM_SESSION_BRIEF`, `KXM_WORKFLOW_*`, `GITHUB_TOKEN`, and others | [CLI and session settings](#cli-and-session-settings) |
|
|
34
35
|
| Nous model providers | `KXM_NOUS_PROVIDERS`, `KXM_NOUS_PROXY_URL`, `KXM_NOUS_DISCOVERY_TIMEOUT_MS`, `KXM_NOUS_CATALOG_FILE`, `NOUS_API_KEY` | [Nous providers](../guides/nous-providers.md) |
|
|
35
36
|
| Browser automation | `KXM_BROWSER`, `OBSCURA_CDP_URL`, `OBSCURA_PORT`, `STEEL_API_URL`, `STEEL_UI_URL`, `STEEL_AUTH_HEADER`, `STEEL_AUTH_BASIC`, `STEEL_AUTH_USER`, `STEEL_AUTH_TOKEN`, `STEEL_API_KEY` (legacy), `USE_PASS_CLI` | [Browser settings](#browser-automation) |
|
|
@@ -187,6 +188,27 @@ The Runtime supervisor runs `kxm run` workflows and syncs their summaries to the
|
|
|
187
188
|
| `KXM_RUNTIME_SYNC_INTERVAL_MS` | `10000` | How often the supervisor heartbeats and pushes its outbox, clamped to `250` through `60000` |
|
|
188
189
|
| `KXM_RUNTIME_STOP_GRACE_MS` | `30000` | How long a stopping supervisor waits for open drives, `0` to `600000`; an invalid value warns and uses the default |
|
|
189
190
|
|
|
191
|
+
## Studio and Python control-plane settings
|
|
192
|
+
|
|
193
|
+
The MG1 Python service is additive: these values do not redirect the Node hub
|
|
194
|
+
or Runtime from SQLite. Requests cannot override its database host, database,
|
|
195
|
+
role, Temporal target, or namespace.
|
|
196
|
+
|
|
197
|
+
| Variable | Default | Effect |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| `KXM_CONTROL_URL` | Unset | Python control API base URL used by Studio's `GET /api/control/account` relay. The relay returns `control_unconfigured` when absent, and aborts a nonresponding upstream after three seconds with `control_unreachable` |
|
|
200
|
+
| `KXM_KXMD_PASSWORD` | None; required | PostgreSQL password for the fixed `kxmd` role. Keep it in the deployment secret manager |
|
|
201
|
+
| `KXM_POSTGRES_HOST` | `127.0.0.1` | PostgreSQL host selected by the service deployment, including an operator-owned tunnel |
|
|
202
|
+
| `KXM_POSTGRES_PORT` | `5432` | PostgreSQL port, from 1 through 65535 |
|
|
203
|
+
| `KXM_DATABASE` | `kxmd` | Fixed database assertion. Any other value refuses startup-time account resolution |
|
|
204
|
+
| `KXM_DB_USER` | `kxmd` | Fixed PostgreSQL role assertion. Any other value is refused |
|
|
205
|
+
| `KXM_TEMPORAL_TARGET` | `127.0.0.1:7233` | Temporal endpoint selected by the service deployment |
|
|
206
|
+
| `KXM_TEMPORAL_NAMESPACE` | `kxmd` | Fixed Temporal namespace assertion. Any other value is refused |
|
|
207
|
+
| `KXM_KXMD_ARTIFACTS` | `/var/lib/kxm/artifacts/kxmd` | Root for bounded per-tenant artifacts |
|
|
208
|
+
| `KXM_AUTHENTIK_ISSUER` | None; required for authenticated routes | Exact token issuer |
|
|
209
|
+
| `KXM_AUTHENTIK_AUDIENCE` | None; required for authenticated routes | Exact token audience |
|
|
210
|
+
| `KXM_AUTHENTIK_JWKS_URL` | None; required for authenticated routes | JWKS endpoint used to verify RS256 user and runner tokens |
|
|
211
|
+
|
|
190
212
|
## CLI and session settings
|
|
191
213
|
|
|
192
214
|
| Variable | Default | Effect |
|
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.182",
|
|
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",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -38995,6 +38995,11 @@ function generateStudioLayout(plan, state) {
|
|
|
38995
38995
|
};
|
|
38996
38996
|
}
|
|
38997
38997
|
var DEFAULT_STUDIO_PORT = 4242;
|
|
38998
|
+
function isKxmdAccountView(value) {
|
|
38999
|
+
if (typeof value !== "object" || value === null) return false;
|
|
39000
|
+
const account = value;
|
|
39001
|
+
return account.account === "kxmd" && account.database === "kxmd" && account.namespace === "kxmd" && account.role === "kxmd";
|
|
39002
|
+
}
|
|
38998
39003
|
function createStudioServer(options = {}) {
|
|
38999
39004
|
const targetPort = options.port ?? DEFAULT_STUDIO_PORT;
|
|
39000
39005
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -39013,6 +39018,46 @@ function createStudioServer(options = {}) {
|
|
|
39013
39018
|
res.end(JSON.stringify({ ok: true, studio: true, port: targetPort }));
|
|
39014
39019
|
return;
|
|
39015
39020
|
}
|
|
39021
|
+
if (url.pathname === "/api/control/account" && req.method === "GET") {
|
|
39022
|
+
const base = options.controlUrl ?? process.env.KXM_CONTROL_URL;
|
|
39023
|
+
if (!base) {
|
|
39024
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
39025
|
+
res.end(JSON.stringify({ ok: false, error: "control_unconfigured" }));
|
|
39026
|
+
return;
|
|
39027
|
+
}
|
|
39028
|
+
let upstream;
|
|
39029
|
+
try {
|
|
39030
|
+
upstream = await fetch(new URL("/v1/account", base), {
|
|
39031
|
+
signal: AbortSignal.timeout(3e3)
|
|
39032
|
+
});
|
|
39033
|
+
} catch {
|
|
39034
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
39035
|
+
res.end(JSON.stringify({ ok: false, error: "control_unreachable" }));
|
|
39036
|
+
return;
|
|
39037
|
+
}
|
|
39038
|
+
const contentType = upstream.headers.get("content-type");
|
|
39039
|
+
if (!contentType?.toLowerCase().startsWith("application/json")) {
|
|
39040
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
39041
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
39042
|
+
return;
|
|
39043
|
+
}
|
|
39044
|
+
let body;
|
|
39045
|
+
try {
|
|
39046
|
+
body = await upstream.json();
|
|
39047
|
+
} catch {
|
|
39048
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
39049
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
39050
|
+
return;
|
|
39051
|
+
}
|
|
39052
|
+
if (upstream.ok && !isKxmdAccountView(body)) {
|
|
39053
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
39054
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
39055
|
+
return;
|
|
39056
|
+
}
|
|
39057
|
+
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
39058
|
+
res.end(JSON.stringify(body));
|
|
39059
|
+
return;
|
|
39060
|
+
}
|
|
39016
39061
|
if (url.pathname === "/api/layout" && req.method === "GET") {
|
|
39017
39062
|
const plan = options.planProvider?.();
|
|
39018
39063
|
if (!plan) {
|
|
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25462
25462
|
}
|
|
25463
25463
|
|
|
25464
25464
|
// plugins/kxm/src/mcp-server.ts
|
|
25465
|
-
var VERSION = "0.7.
|
|
25465
|
+
var VERSION = "0.7.182";
|
|
25466
25466
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25467
25467
|
var inbox = /* @__PURE__ */ new Map();
|
|
25468
25468
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
package/plugins/kxm/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
|
|
|
11
11
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
12
12
|
import { sessionTokenFixHint } from "./session-token-hint.ts";
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.7.
|
|
14
|
+
const VERSION = "0.7.182";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -290,6 +290,7 @@ export interface StudioServerOptions {
|
|
|
290
290
|
planProvider?: (() => KxmCompiledPlan | undefined) | undefined;
|
|
291
291
|
stateProvider?: (() => KxmRunState | undefined) | undefined;
|
|
292
292
|
onMutation?: ((command: string, args: Record<string, unknown>) => Promise<{ ok: boolean; result?: unknown; error?: string }> | { ok: boolean; result?: unknown; error?: string }) | undefined;
|
|
293
|
+
controlUrl?: string | undefined;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
export interface StudioServerHandle {
|
|
@@ -299,6 +300,15 @@ export interface StudioServerHandle {
|
|
|
299
300
|
close: () => Promise<void>;
|
|
300
301
|
}
|
|
301
302
|
|
|
303
|
+
function isKxmdAccountView(value: unknown): boolean {
|
|
304
|
+
if (typeof value !== "object" || value === null) return false;
|
|
305
|
+
const account = value as Record<string, unknown>;
|
|
306
|
+
return account.account === "kxmd"
|
|
307
|
+
&& account.database === "kxmd"
|
|
308
|
+
&& account.namespace === "kxmd"
|
|
309
|
+
&& account.role === "kxmd";
|
|
310
|
+
}
|
|
311
|
+
|
|
302
312
|
/**
|
|
303
313
|
* Embedded Web Studio Local Server (Decision Q8 & D14).
|
|
304
314
|
* Serves the visual DAG, form stepper, and Temporal swimlane dashboards on http://localhost:4242.
|
|
@@ -328,6 +338,46 @@ export function createStudioServer(options: StudioServerOptions = {}): StudioSer
|
|
|
328
338
|
return;
|
|
329
339
|
}
|
|
330
340
|
|
|
341
|
+
if (url.pathname === "/api/control/account" && req.method === "GET") {
|
|
342
|
+
const base = options.controlUrl ?? process.env.KXM_CONTROL_URL;
|
|
343
|
+
if (!base) {
|
|
344
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
345
|
+
res.end(JSON.stringify({ ok: false, error: "control_unconfigured" }));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
let upstream: Response;
|
|
349
|
+
try {
|
|
350
|
+
upstream = await fetch(new URL("/v1/account", base), {
|
|
351
|
+
signal: AbortSignal.timeout(3_000),
|
|
352
|
+
});
|
|
353
|
+
} catch {
|
|
354
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
355
|
+
res.end(JSON.stringify({ ok: false, error: "control_unreachable" }));
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const contentType = upstream.headers.get("content-type");
|
|
359
|
+
if (!contentType?.toLowerCase().startsWith("application/json")) {
|
|
360
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
361
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
let body: unknown;
|
|
365
|
+
try {
|
|
366
|
+
body = await upstream.json();
|
|
367
|
+
} catch {
|
|
368
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
369
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (upstream.ok && !isKxmdAccountView(body)) {
|
|
373
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
374
|
+
res.end(JSON.stringify({ ok: false, error: "control_response_invalid" }));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
378
|
+
res.end(JSON.stringify(body));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
331
381
|
if (url.pathname === "/api/layout" && req.method === "GET") {
|
|
332
382
|
const plan = options.planProvider?.();
|
|
333
383
|
if (!plan) {
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Start PostgreSQL 17 on 127.0.0.1:5432 for the Python MG1 lane.
|
|
3
|
+
# The kontextmind-doks scale set has a client binary and no engine socket, so
|
|
4
|
+
# this job provisions the server from a pinned archive instead of a workflow
|
|
5
|
+
# service. A second call in the same job returns once that server is ready.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
version=17.11.0
|
|
10
|
+
triple=x86_64-unknown-linux-gnu
|
|
11
|
+
sha256=b7a1ba6bae6499d8296e3e81b0171eecfd1766ca9aaa0057e41ad3e844e5e2e0
|
|
12
|
+
archive_name="postgresql-${version}-${triple}.tar.gz"
|
|
13
|
+
url="https://github.com/theseus-rs/postgresql-binaries/releases/download/${version}/${archive_name}"
|
|
14
|
+
|
|
15
|
+
if [[ "$(uname -s)" != "Linux" || "$(uname -m)" != "x86_64" ]]; then
|
|
16
|
+
echo "PostgreSQL 17 CI archive is linux/x86_64 only (got $(uname -s)/$(uname -m))" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
work="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/kxm-mg1-postgres"
|
|
21
|
+
prefix="${work}/postgresql-${version}-${triple}"
|
|
22
|
+
pgdata="${work}/data"
|
|
23
|
+
pglog="${work}/postgres.log"
|
|
24
|
+
pwfile="${work}/pwfile"
|
|
25
|
+
mkdir -p "$work"
|
|
26
|
+
|
|
27
|
+
package_for_library() {
|
|
28
|
+
case "$1" in
|
|
29
|
+
libzstd.so.*) echo libzstd1 ;;
|
|
30
|
+
liblz4.so.*) echo liblz4-1 ;;
|
|
31
|
+
libxml2.so.*) echo libxml2 ;;
|
|
32
|
+
liblzma.so.*) echo liblzma5 ;;
|
|
33
|
+
libssl.so.3 | libcrypto.so.3) echo libssl3 ;;
|
|
34
|
+
libicuuc.so.74 | libicui18n.so.74 | libicudata.so.74) echo libicu74 ;;
|
|
35
|
+
libgssapi_krb5.so.*) echo libgssapi-krb5-2 ;;
|
|
36
|
+
libkrb5.so.* | libk5crypto.so.* | libkrb5support.so.*) echo libkrb5-3 ;;
|
|
37
|
+
libcom_err.so.*) echo libcom-err2 ;;
|
|
38
|
+
libkeyutils.so.*) echo libkeyutils1 ;;
|
|
39
|
+
libstdc++.so.*) echo libstdc++6 ;;
|
|
40
|
+
libgcc_s.so.*) echo libgcc-s1 ;;
|
|
41
|
+
libz.so.*) echo zlib1g ;;
|
|
42
|
+
*) return 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
server_version_num() {
|
|
47
|
+
PGPASSWORD=postgres "${prefix}/bin/psql" \
|
|
48
|
+
-h 127.0.0.1 -p 5432 -U postgres -d postgres \
|
|
49
|
+
-Atqc "SHOW server_version_num"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
postgres_17_ready() {
|
|
53
|
+
[[ -x "${prefix}/bin/pg_isready" ]] || return 1
|
|
54
|
+
"${prefix}/bin/pg_isready" -h 127.0.0.1 -p 5432 -U postgres >/dev/null 2>&1 || return 1
|
|
55
|
+
local num
|
|
56
|
+
num="$(server_version_num 2>/dev/null)" || return 1
|
|
57
|
+
[[ "$num" -ge 170000 && "$num" -lt 180000 ]]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
install_missing_libraries() {
|
|
61
|
+
local -a missing=()
|
|
62
|
+
local line
|
|
63
|
+
while IFS= read -r line; do
|
|
64
|
+
[[ -n "$line" ]] && missing+=("$line")
|
|
65
|
+
done < <(ldd "${prefix}/bin/postgres" | awk '/not found$/ { print $1 }')
|
|
66
|
+
if [[ ${#missing[@]} -eq 0 ]]; then
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
local -a packages=()
|
|
71
|
+
local lib pkg
|
|
72
|
+
for lib in "${missing[@]}"; do
|
|
73
|
+
if ! pkg="$(package_for_library "$lib")"; then
|
|
74
|
+
echo "PostgreSQL 17 needs ${lib}, which has no package mapping on this runner" >&2
|
|
75
|
+
ldd "${prefix}/bin/postgres" >&2 || true
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
packages+=("$pkg")
|
|
79
|
+
done
|
|
80
|
+
mapfile -t packages < <(printf '%s\n' "${packages[@]}" | awk '!seen[$0]++')
|
|
81
|
+
echo "Installing shared libraries for PostgreSQL 17: ${packages[*]}"
|
|
82
|
+
sudo -n apt-get update
|
|
83
|
+
sudo -n DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${packages[@]}"
|
|
84
|
+
|
|
85
|
+
local -a still=()
|
|
86
|
+
while IFS= read -r line; do
|
|
87
|
+
[[ -n "$line" ]] && still+=("$line")
|
|
88
|
+
done < <(ldd "${prefix}/bin/postgres" | awk '/not found$/ { print $1 }')
|
|
89
|
+
if [[ ${#still[@]} -gt 0 ]]; then
|
|
90
|
+
echo "PostgreSQL 17 still needs: ${still[*]}" >&2
|
|
91
|
+
exit 1
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if postgres_17_ready; then
|
|
96
|
+
echo "PostgreSQL 17 already ready on 127.0.0.1:5432 (server_version_num=$(server_version_num))"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if [[ ! -x "${prefix}/bin/postgres" ]]; then
|
|
101
|
+
echo "Downloading PostgreSQL ${version}"
|
|
102
|
+
curl --fail --silent --show-error --location --retry 5 --retry-delay 2 \
|
|
103
|
+
--output "${work}/${archive_name}" \
|
|
104
|
+
"$url"
|
|
105
|
+
echo "${sha256} ${work}/${archive_name}" | sha256sum -c -
|
|
106
|
+
tar -xzf "${work}/${archive_name}" -C "$work"
|
|
107
|
+
fi
|
|
108
|
+
install_missing_libraries
|
|
109
|
+
|
|
110
|
+
if [[ ! -s "${pgdata}/PG_VERSION" ]]; then
|
|
111
|
+
printf '%s\n' postgres >"$pwfile"
|
|
112
|
+
chmod 600 "$pwfile"
|
|
113
|
+
"${prefix}/bin/initdb" \
|
|
114
|
+
--username=postgres \
|
|
115
|
+
--pwfile="$pwfile" \
|
|
116
|
+
--auth-host=scram-sha-256 \
|
|
117
|
+
--auth-local=trust \
|
|
118
|
+
--encoding=UTF8 \
|
|
119
|
+
--locale=C \
|
|
120
|
+
--set listen_addresses=127.0.0.1 \
|
|
121
|
+
--set port=5432 \
|
|
122
|
+
--set shared_buffers=16MB \
|
|
123
|
+
--set max_connections=20 \
|
|
124
|
+
--set shared_memory_type=mmap \
|
|
125
|
+
--set dynamic_shared_memory_type=mmap \
|
|
126
|
+
--set huge_pages=off \
|
|
127
|
+
--set fsync=off \
|
|
128
|
+
--set synchronous_commit=off \
|
|
129
|
+
--set full_page_writes=off \
|
|
130
|
+
--pgdata="$pgdata"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
if ! "${prefix}/bin/pg_ctl" -D "$pgdata" -l "$pglog" -w -t 30 start; then
|
|
134
|
+
echo "PostgreSQL 17 failed to start" >&2
|
|
135
|
+
cat "$pglog" >&2 || true
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
num="$(server_version_num)"
|
|
140
|
+
if [[ "$num" -lt 170000 || "$num" -ge 180000 ]]; then
|
|
141
|
+
echo "PostgreSQL 17 required, server_version_num=${num}" >&2
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
echo "PostgreSQL 17 ready on 127.0.0.1:5432 (server_version_num=${num})"
|