@kontextmind/kxm 0.7.146 → 0.7.148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +38 -3
  3. package/docs/README.md +1 -1
  4. package/docs/adr/ADR-0002-browser-automation-steel-doks.md +8 -5
  5. package/docs/adr/ADR-0005-obscura-default-playwright.md +4 -3
  6. package/docs/adr/ADR-0006-machine-account-names.md +90 -0
  7. package/docs/adr/ADR-0007-steel-caddy-authentik.md +97 -0
  8. package/docs/adr/README.md +3 -1
  9. package/docs/contributing/ci-and-release.md +80 -43
  10. package/docs/contributing/operating-rules.md +13 -1
  11. package/docs/guides/agent-skills.md +1 -1
  12. package/docs/guides/browser-automation.md +44 -28
  13. package/docs/kb/how-credentials-retrieved-safely.md +22 -23
  14. package/docs/kb/how-to-connect-playwright-to-steel.md +8 -5
  15. package/docs/kb/how-to-recover-expired-session-or-orphan.md +11 -10
  16. package/docs/kb/why-automation-opened-different-browser.md +4 -3
  17. package/docs/operations/deploy.md +31 -0
  18. package/docs/operations/troubleshooting.md +23 -1
  19. package/docs/prompts/browser-diagnose-recover.md +2 -2
  20. package/docs/prompts/browser-start.md +1 -1
  21. package/docs/reference/configuration.md +9 -7
  22. package/package.json +2 -1
  23. package/plugins/kxm/.claude-plugin/plugin.json +1 -1
  24. package/plugins/kxm/dist/claude-hook.js +1 -1
  25. package/plugins/kxm/dist/cli.js +2 -2
  26. package/plugins/kxm/dist/extension.js +1 -1
  27. package/plugins/kxm/dist/mcp-server.js +1 -1
  28. package/plugins/kxm/dist/runtime.js +3 -3
  29. package/plugins/kxm/package.json +1 -1
  30. package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +11 -12
  31. package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -4
  32. package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +4 -3
  33. package/plugins/kxm/skills/kxm-browser-session/SKILL.md +5 -5
  34. package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
  35. package/plugins/kxm/src/browser.ts +12 -9
  36. package/plugins/kxm/src/mcp-server.ts +1 -1
  37. package/plugins/kxm/src/modes.ts +1 -1
  38. package/plugins/kxm/src/session-work.ts +3 -1
  39. package/scripts/ci-classify.mjs +69 -0
  40. package/scripts/ci-unit-shard.mjs +230 -0
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.146",
14
+ "version": "0.7.148",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -142,6 +142,37 @@ All notable user-facing changes are documented here. The project follows [Semant
142
142
 
143
143
  ### Changed
144
144
 
145
+ - **Pull-request CI runs the unit suite on Linux Node 24, and `CI / required` is the aggregate check.**
146
+ Docs and plan markdown skip the code jobs. `engine.test.ts` is split by
147
+ test name. `permission.test.ts` and `runtime.test.ts` run one file at a
148
+ time, and every other unit file runs in one light lane. Pushes to `main`
149
+ still run `validate:pr` on Linux and Windows for Node 22.19.0 and Node 24.
150
+ A pull request that touches path, process, shell, spawn, package, lockfile,
151
+ or workflow files also runs the unit lanes on Windows. Playwright stays on
152
+ Obscura. See [CI and release](docs/contributing/ci-and-release.md).
153
+
154
+ - **Docs match the 2026-09-27 Steel and machine-account infrastructure.**
155
+ Steel (`steel.kontextmind.com`, alias `steel.theneuro.me`) is reached only
156
+ through Caddy on `kxmd-proxy` (VM 230) and Authentik forward auth. Direct
157
+ LAN, tailnet, and host-forward access is blocked. Sessions return
158
+ `websocketUrl` `wss://steel.kontextmind.com/` (previously
159
+ `ws://steel-browser/`). The CDP path is `/v1/devtools` with an
160
+ `Authorization` header. Allowed groups are `steel-users`, `kxmd-users`,
161
+ `kxmd-admins`, and `kxmd-owners`. `STEEL_API_URL` defaults to
162
+ `https://steel.kontextmind.com`. `STEEL_API_KEY` is deprecated and is not
163
+ enforced by Steel or Caddy. Migrate to `STEEL_AUTH_HEADER`, then
164
+ `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`; those
165
+ override the key. The `svc-steel` credential is 1Password vault
166
+ `kontextmind`, item `Steel (svc-steel)`, field `basic_auth`, read with
167
+ `op read` and never written to disk. Steel requires `kxm` 0.7.135 or
168
+ newer. Playwright stays on Obscura ([ADR-0005](docs/adr/ADR-0005-obscura-default-playwright.md)).
169
+ The Proxmox boot order and VM names are in
170
+ [Deploy KXM](docs/operations/deploy.md#boot-the-kxmd-proxmox-host).
171
+ Machine accounts follow
172
+ [ADR-0006](docs/adr/ADR-0006-machine-account-names.md). The DOKS deployment
173
+ record is superseded by
174
+ [ADR-0007](docs/adr/ADR-0007-steel-caddy-authentik.md).
175
+
145
176
  - **Workforce ids use one convention, and old ids still resolve.**
146
177
  Role ids stay `planner`, `writer`, `reviewer-arch`, and `reviewer-cli`.
147
178
  Agent ids and agent-step ids use those same names. Route ids are
@@ -158,6 +189,7 @@ All notable user-facing changes are documented here. The project follows [Semant
158
189
  `opus` because the justfile recipe review-arch hardcoded that model while
159
190
  `reviewer-arch` listed only `fable-claude`. That recipe is gone; a request
160
191
  for `opus` fails closed.
192
+
161
193
  - **Dispatch reads role and model files, and agents bind a role.**
162
194
  `scripts/roster-policy.mjs` builds the developer policy from
163
195
  `.kxm/models/*.yaml` and `.kxm/roles/*.yaml` at `refs/remotes/origin/main`.
@@ -472,9 +504,10 @@ All notable user-facing changes are documented here. The project follows [Semant
472
504
  into `chromium.connectOverCDP`. Obscura stays the default and sends no Steel
473
505
  headers. `STEEL_AUTH_HEADER` overrides the value. The CDP URL omits the credential when
474
506
  those variables are set. A 302 to the identity provider fails closed and does
475
- not follow the login redirect. `STEEL_API_KEY` still sends the legacy
476
- `x-steel-api-key` header and `apiKey` query parameter for the temporary proxy
477
- shim, and warns once. See
507
+ not follow the login redirect. `STEEL_API_KEY` is deprecated. Steel and
508
+ Caddy do not enforce it. `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then
509
+ `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN` override it. A client that still
510
+ has only the legacy key warns once and is not authenticated. See
478
511
  [Browser automation](docs/guides/browser-automation.md).
479
512
 
480
513
  ### Removed
@@ -495,6 +528,8 @@ All notable user-facing changes are documented here. The project follows [Semant
495
528
 
496
529
  ### Fixed
497
530
 
531
+ - **Dry-run ship status no longer rewrites `.git/index`.** `git status` refreshes the index under an optional lock. The ship-status read passes `--no-optional-locks`, so a dry run leaves the checkout untouched.
532
+
498
533
  - **A committed checkout counts as authored work, and a one-shot outcome must be a standalone JSON object.** The authoring witness includes `HEAD` with porcelain status and both diffs, so a write that commits its edits is `changed` and can stay `passed`. A `rev-parse` failure keeps that empty head term only when the repository has no commits; any other git failure is `unwitnessed`. A one-shot outcome is accepted when the whole reply is one JSON object, or when that object stands alone on the final line. A closing code fence around the final object is allowed. An object followed by prose, a truncated reply, and an ambiguous tail settle `failed`.
499
534
 
500
535
  - **The test suite no longer passes `--test-timeout`.** Under `node --test` that flag bounds each file, so coverage on CI timed out `test/core/engine.test.ts` at three minutes. The wall clock in `scripts/run-bounded.mjs` still bounds each script.
package/docs/README.md CHANGED
@@ -76,7 +76,7 @@ KXM connects coding agents through a durable, authenticated [hub](glossary.md#hu
76
76
  | [Architecture](concepts/architecture.md) | Integrators, maintainers | The components, message and workflow lifecycles, and KXM's limits |
77
77
  | [Trust model](concepts/trust-model.md) | Operators, security reviewers | Who holds which credential, project boundaries, and what provenance proves |
78
78
  | [Data and storage](concepts/data-and-storage.md) | Operators, security reviewers | What each store holds, where it lives and how long it is kept |
79
- | [Architecture decision records](adr/README.md) | Maintainers | The decision records: [browser automation](adr/ADR-0002-browser-automation-steel-doks.md), [Obscura for Playwright](adr/ADR-0005-obscura-default-playwright.md), [SQLite-only store](adr/ADR-0003-sqlite-only-store.md), [edge identity](adr/ADR-0004-edge-identity-authentik.md) |
79
+ | [Architecture decision records](adr/README.md) | Maintainers | The decision records: [browser automation](adr/ADR-0002-browser-automation-steel-doks.md), [Steel through Caddy](adr/ADR-0007-steel-caddy-authentik.md), [Obscura for Playwright](adr/ADR-0005-obscura-default-playwright.md), [machine account names](adr/ADR-0006-machine-account-names.md), [SQLite-only store](adr/ADR-0003-sqlite-only-store.md), [edge identity](adr/ADR-0004-edge-identity-authentik.md) |
80
80
  | [KXM contract package](contracts/README.md) | Maintainers, reviewers | The normative specifications for the local-first architecture, listed below |
81
81
 
82
82
  ### Contracts
@@ -4,13 +4,13 @@ id: "ADR-0002"
4
4
  type: "adr"
5
5
  title: "Self-hosted Steel on DOKS for reusable browser automation and human takeover"
6
6
  project: "kxm"
7
- status: "accepted"
7
+ status: "superseded"
8
8
  owner: "@operator"
9
9
  created: "2026-09-14"
10
10
  updated: "2026-09-27"
11
11
  authority: "decision"
12
12
  confidence: "verified"
13
- summary: "Adopt self-hosted Steel on DigitalOcean Kubernetes (DOKS) with agent-browser and Playwright as KXM's primary browser automation infrastructure."
13
+ summary: "The 2026-09-14 choice of self-hosted Steel. The DOKS deployment in this record was superseded on 2026-09-27 by ADR-0007. Obscura is the Playwright default (ADR-0005)."
14
14
  tags: ["architecture", "decision", "browser", "steel", "doks", "playwright"]
15
15
  related: ["docs/guides/browser-automation.md", "docs/guides/agent-skills.md", "docs/adr/ADR-0005-obscura-default-playwright.md"]
16
16
  details:
@@ -20,11 +20,15 @@ details:
20
20
  - "Provide dual exploratory (agent-browser) and regression (Playwright) interfaces"
21
21
  - "Enforce strict credential isolation via pass-cli"
22
22
  supersedes: null
23
- superseded_by: null
23
+ superseded_by: "ADR-0007"
24
24
  ---
25
25
 
26
26
  # ADR-0002: Self-hosted Steel on DOKS for reusable browser automation
27
27
 
28
+ ## Status
29
+
30
+ Superseded on 2026-09-27 by [ADR-0007](ADR-0007-steel-caddy-authentik.md) for where Steel runs and how clients authenticate. [ADR-0005](ADR-0005-obscura-default-playwright.md) keeps Obscura as the Playwright default. The sections below are the 2026-09-14 decision. They are not the current deployment. Steel and Caddy do not enforce `STEEL_API_KEY`.
31
+
28
32
  ## Context and problem statement
29
33
 
30
34
  AI coding agents and orchestration workflows in KXM require browser interaction for UI exploration, DOM mapping, bug reproduction, and end-to-end regression testing. Existing approaches suffered from three core issues:
@@ -100,8 +104,7 @@ AI coding agents and orchestration workflows in KXM require browser interaction
100
104
 
101
105
  - **Verification**: Health endpoint `$STEEL_API_URL/v1/health` verified with HTTP 200 and Let's Encrypt TLS.
102
106
  - **Integration Test**: `test/core/browser.test.ts` validates session lifecycle, CDP endpoint formatting, takeover transitions, and secret redaction.
103
- - **Security Check**: `pass-cli` verified as the authoritative store for Steel credentials in the operators' password manager.
104
- - **Edge auth (2026-09-27)**: `steel.kontextmind.com` and `steel.theneuro.me`, including the CDP WebSocket, are behind Authentik forward auth. Steel does not check `STEEL_API_KEY`. Clients send `Authorization: Basic`. Unauthenticated requests are redirected to `id.kxmd.dev`. The legacy `x-steel-api-key` header and `apiKey` query parameter remain a temporary proxy shim.
107
+ - **Security Check**: `pass-cli` was the credential store named in this 2026-09-14 decision. Current clients read the `svc-steel` credential with `op read`, as [ADR-0007](ADR-0007-steel-caddy-authentik.md) records.
105
108
 
106
109
  ## Related
107
110
 
@@ -12,7 +12,7 @@ authority: "decision"
12
12
  confidence: "verified"
13
13
  summary: "Playwright testing and verification connect to pinned Obscura v0.2.3 over CDP. Steel stays the browser for human takeover, MFA, and the live session viewer."
14
14
  tags: ["architecture", "decision", "browser", "obscura", "playwright", "cdp"]
15
- related: ["docs/adr/ADR-0002-browser-automation-steel-doks.md", "docs/guides/browser-automation.md", "docs/kb/how-to-connect-playwright-to-obscura.md"]
15
+ related: ["docs/adr/ADR-0002-browser-automation-steel-doks.md", "docs/adr/ADR-0007-steel-caddy-authentik.md", "docs/guides/browser-automation.md", "docs/kb/how-to-connect-playwright-to-obscura.md"]
16
16
  details:
17
17
  decision_drivers:
18
18
  - "Playwright tests must run without a Steel cluster or a Playwright-managed browser download"
@@ -26,7 +26,7 @@ details:
26
26
 
27
27
  ## Status
28
28
 
29
- Accepted on 2026-09-27. This record does not supersede [ADR-0002](ADR-0002-browser-automation-steel-doks.md). Steel remains the browser for human takeover, MFA, and the live session viewer.
29
+ Accepted on 2026-09-27. This record does not supersede [ADR-0002](ADR-0002-browser-automation-steel-doks.md). [ADR-0007](ADR-0007-steel-caddy-authentik.md) is the current Steel deployment. Steel remains the browser for remote and hosted sessions, human takeover, MFA, and the live session viewer.
30
30
 
31
31
  ## Context
32
32
 
@@ -79,7 +79,8 @@ Obscura v0.2.3 is a headless Chromium build that speaks the Chrome DevTools Prot
79
79
 
80
80
  ## Related
81
81
 
82
- - [ADR-0002: Self-hosted Steel on DOKS](ADR-0002-browser-automation-steel-doks.md)
82
+ - [ADR-0002: Self-hosted Steel on DOKS (superseded deployment)](ADR-0002-browser-automation-steel-doks.md)
83
+ - [ADR-0007: Steel through Caddy and Authentik](ADR-0007-steel-caddy-authentik.md)
83
84
  - [Browser automation](../guides/browser-automation.md)
84
85
  - [How do I connect Playwright to Obscura?](../kb/how-to-connect-playwright-to-obscura.md)
85
86
  - [Environment variables and limits](../reference/configuration.md#browser-automation)
@@ -0,0 +1,90 @@
1
+ ---
2
+ schema: "kxm.doc.v1"
3
+ id: "ADR-0006"
4
+ type: "adr"
5
+ title: "Machine account names"
6
+ project: "kxm"
7
+ status: "accepted"
8
+ owner: "@operator"
9
+ created: "2026-09-27"
10
+ updated: "2026-09-27"
11
+ authority: "decision"
12
+ confidence: "verified"
13
+ summary: "New machine accounts use svc-<system>-<purpose>, or svc-<tenant>-<system>-<purpose> when they belong to one tenant. Test accounts add a test- prefix and stay out of production groups."
14
+ tags: ["architecture", "decision", "authentik", "accounts"]
15
+ related: ["docs/adr/ADR-0004-edge-identity-authentik.md", "docs/operations/deploy.md", "docs/contributing/operating-rules.md"]
16
+ details:
17
+ decision_drivers:
18
+ - "One readable pattern for platform and tenant machine accounts"
19
+ - "Test and witness accounts must be unable to enter production groups"
20
+ - "Existing names stay until an approved inventory authorizes a rename"
21
+ supersedes: null
22
+ superseded_by: null
23
+ ---
24
+
25
+ # ADR-0006: Machine account names
26
+
27
+ ## Status
28
+
29
+ Accepted on 2026-09-27.
30
+
31
+ ## Context
32
+
33
+ Authentik machine accounts were created with local names such as `kxm-agent`, `kxm-witness-*`, `witness9`, `kxmdproof`, `kxm-provisioner`, and `agent-ilo-asus`. Those names do not say whether the account is platform-wide, tenant-scoped, or a test identity. Renaming them without an inventory would drop grants that still point at the old name.
34
+
35
+ ## Decision drivers
36
+
37
+ 1. A reader can tell the scope of an account from its name.
38
+ 2. Test, witness, and proof accounts must not sit in production groups.
39
+ 3. Authentik's own accounts keep the names Authentik assigns.
40
+ 4. A rename of an account that already exists waits for an approved inventory.
41
+
42
+ ## Considered options
43
+
44
+ 1. **`svc-` names, with a `test-` prefix for non-production accounts.**
45
+ 2. **Keep creating ad hoc names.**
46
+ 3. **Put every machine account under Authentik's `ak-*` prefix.**
47
+
48
+ ### Option 1: `svc-` names (chosen)
49
+
50
+ - Good, because platform and tenant scope are visible in the name.
51
+ - Good, because a `test-` prefix can be excluded from production groups.
52
+ - Bad, because accounts that already exist keep their old names until an inventory is approved.
53
+
54
+ ### Option 2: ad hoc names (rejected)
55
+
56
+ - Good, because nothing already issued has to change.
57
+ - Bad, because the next account repeats the same ambiguity.
58
+
59
+ ### Option 3: `ak-*` for every machine account (rejected)
60
+
61
+ - Good, because one prefix would match Authentik-managed users.
62
+ - Bad, because `ak-*` is Authentik's own namespace for outposts and internal users.
63
+
64
+ ## Decision
65
+
66
+ Create machine accounts with these shapes. `<tenant>` is the tenant slug.
67
+
68
+ | Scope | Name |
69
+ |---|---|
70
+ | Platform-wide | `svc-<system>-<purpose>` |
71
+ | Tenant-scoped | `svc-<tenant>-<system>-<purpose>` |
72
+ | Test, witness, or proof | The same shapes with a `test-` prefix |
73
+
74
+ A `test-` account is never a member of a production group.
75
+
76
+ Authentik-managed accounts are exempt. That includes outposts and any account whose name starts with `ak-`.
77
+
78
+ The `svc-steel` credential that reaches the Steel server is the account operators use today. This record does not rename it. Names already recorded in plans and handoffs, including `kxm-agent`, `kxm-witness-*`, `witness9`, `kxmdproof`, `kxm-provisioner`, and `agent-ilo-asus`, stay as written until an approved inventory lists each rename. Do not invent a replacement for a specific account.
79
+
80
+ ## Consequences
81
+
82
+ - New accounts follow the table above.
83
+ - This repository does not assign new names to the recorded accounts.
84
+ - Group names such as `kxmd-owners` are groups, not machine accounts, and this record does not rename them.
85
+
86
+ ## Related
87
+
88
+ - [Edge identity](ADR-0004-edge-identity-authentik.md)
89
+ - [Deploy KXM](../operations/deploy.md#name-machine-accounts)
90
+ - [Operating rules](../contributing/operating-rules.md)
@@ -0,0 +1,97 @@
1
+ ---
2
+ schema: "kxm.doc.v1"
3
+ id: "ADR-0007"
4
+ type: "adr"
5
+ title: "Steel is reached only through Caddy and Authentik"
6
+ project: "kxm"
7
+ status: "accepted"
8
+ owner: "@operator"
9
+ created: "2026-09-27"
10
+ updated: "2026-09-27"
11
+ authority: "decision"
12
+ confidence: "verified"
13
+ summary: "The Steel server steel.kontextmind.com is an LXC behind Caddy and Authentik forward auth. Clients use the svc-steel credential. STEEL_API_KEY is not enforced. Playwright stays on Obscura."
14
+ tags: ["architecture", "decision", "browser", "steel", "authentik"]
15
+ related: ["docs/adr/ADR-0002-browser-automation-steel-doks.md", "docs/adr/ADR-0005-obscura-default-playwright.md", "docs/guides/browser-automation.md", "docs/adr/ADR-0006-machine-account-names.md"]
16
+ details:
17
+ decision_drivers:
18
+ - "Steel must not be reachable on the LAN, the tailnet, or a host forward"
19
+ - "Authentik groups are the access list"
20
+ - "Credentials stay in 1Password and are read into the process only"
21
+ supersedes: "ADR-0002"
22
+ superseded_by: null
23
+ ---
24
+
25
+ # ADR-0007: Steel is reached only through Caddy and Authentik
26
+
27
+ ## Status
28
+
29
+ Accepted on 2026-09-27. This record supersedes the DigitalOcean Kubernetes deployment in [ADR-0002](ADR-0002-browser-automation-steel-doks.md). [ADR-0005](ADR-0005-obscura-default-playwright.md) still makes Obscura the Playwright default. Steel remains the browser for remote and hosted sessions, human takeover, MFA, and the live session viewer.
30
+
31
+ ## Context
32
+
33
+ Steel now runs as LXC 240 on the Proxmox host, at startup order 40. Its public name is `steel.kontextmind.com`. `steel.theneuro.me` is an alias of that same server. Caddy on VM 230 (`kxmd-proxy`) is the only listener, and it forwards authentication to Authentik. Direct LAN, tailnet, and host-forward access is blocked.
34
+
35
+ Sessions return `websocketUrl` `wss://steel.kontextmind.com/`. The previous value was `ws://steel-browser/`. Clients connect Chrome DevTools Protocol at `/v1/devtools` and send `Authorization` on the handshake. The URL does not carry a credential.
36
+
37
+ Steel and Caddy do not enforce `STEEL_API_KEY`. Clients authenticate as the Authentik user `svc-steel`.
38
+
39
+ ## Decision drivers
40
+
41
+ 1. The browser host is not a second network entrance beside the proxy.
42
+ 2. Membership in a known Authentik group is the allow list.
43
+ 3. The credential is read at runtime and is not written to disk.
44
+ 4. Playwright tests keep the Obscura default from ADR-0005.
45
+
46
+ ## Considered options
47
+
48
+ 1. **Caddy plus Authentik forward auth, with direct paths blocked.**
49
+ 2. **Keep the DOKS ingress and a Steel API key.**
50
+ 3. **Publish the LXC on the tailnet or a host forward.**
51
+
52
+ ### Option 1: Caddy and Authentik only (chosen)
53
+
54
+ - Good, because one proxy terminates TLS and applies the group check.
55
+ - Good, because a client that still sends `STEEL_API_KEY` does not get a session.
56
+ - Bad, because clients older than `kxm` 0.7.135 still expect `ws://steel-browser/`.
57
+
58
+ ### Option 2: DOKS and `STEEL_API_KEY` (rejected)
59
+
60
+ - Good, because existing scripts that sent `x-steel-api-key` would keep working.
61
+ - Bad, because that cluster is no longer where Steel runs, and neither Steel nor Caddy checks the key.
62
+
63
+ ### Option 3: tailnet or host-forward access (rejected)
64
+
65
+ - Good, because an operator on the tailnet could open the API without the proxy.
66
+ - Bad, because it bypasses Authentik and the group check.
67
+
68
+ ## Decision
69
+
70
+ Reach Steel only at `https://steel.kontextmind.com` (alias `steel.theneuro.me`) through Caddy and Authentik forward auth. `STEEL_API_URL` defaults to `https://steel.kontextmind.com`.
71
+
72
+ Authenticate with the `svc-steel` credential. Precedence is `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` together with `STEEL_AUTH_TOKEN`. Those variables override `STEEL_API_KEY`. `STEEL_API_KEY` is deprecated: Steel and Caddy do not enforce it, and a value in the URL is not accepted. Migrate by setting one of the three Authentik variables and dropping the key from the environment.
73
+
74
+ Read the credential from 1Password at runtime. The vault is `kontextmind`, the item is `Steel (svc-steel)`, and the field is `basic_auth`. Use `op read`. Do not write the value to disk.
75
+
76
+ ```bash
77
+ export STEEL_AUTH_BASIC="$(op read 'op://kontextmind/Steel (svc-steel)/basic_auth')"
78
+ ```
79
+
80
+ The CDP path is `/v1/devtools` with an `Authorization` header.
81
+
82
+ These Authentik groups may use the server: `steel-users`, `kxmd-users`, `kxmd-admins`, and `kxmd-owners`.
83
+
84
+ `kxm` 0.7.135 or newer is required. Playwright testing stays on Obscura unless `KXM_BROWSER=steel`.
85
+
86
+ ## Consequences
87
+
88
+ - A connection to the LXC by LAN address, tailnet address, or host forward fails.
89
+ - A client that only sets `STEEL_API_KEY` is not authenticated.
90
+ - The `svc-steel` name is the live account. [ADR-0006](ADR-0006-machine-account-names.md) does not rename it ahead of an approved inventory.
91
+
92
+ ## Related
93
+
94
+ - [ADR-0002](ADR-0002-browser-automation-steel-doks.md)
95
+ - [ADR-0005](ADR-0005-obscura-default-playwright.md)
96
+ - [Browser automation](../guides/browser-automation.md)
97
+ - [Deploy KXM](../operations/deploy.md#boot-the-kxmd-proxmox-host)
@@ -7,10 +7,12 @@ An architecture decision record (ADR) captures one significant decision about KX
7
7
  | ADR | Decision | Status | Date |
8
8
  |---|---|---|---|
9
9
  | [ADR-001](../contracts/architecture.md) | Local Runtime, project authority, and aggregate hub | Accepted target | — |
10
- | [ADR-0002](ADR-0002-browser-automation-steel-doks.md) | Self-hosted Steel for reusable browser automation and human takeover | Accepted | 2026-09-14 |
10
+ | [ADR-0002](ADR-0002-browser-automation-steel-doks.md) | Self-hosted Steel on DOKS (deployment superseded) | Superseded by ADR-0007 | 2026-09-14 |
11
11
  | [ADR-0003](ADR-0003-sqlite-only-store.md) | SQLite as the only store | Accepted | 2026-09-17 |
12
12
  | [ADR-0004](ADR-0004-edge-identity-authentik.md) | Edge identity with Authentik; the hub owns no browser identity | Accepted | 2026-09-20 |
13
13
  | [ADR-0005](ADR-0005-obscura-default-playwright.md) | Obscura is the default browser for Playwright; Steel stays for takeover | Accepted | 2026-09-27 |
14
+ | [ADR-0006](ADR-0006-machine-account-names.md) | Machine account names (`svc-` shapes, `test-` prefix, Authentik exemptions) | Accepted | 2026-09-27 |
15
+ | [ADR-0007](ADR-0007-steel-caddy-authentik.md) | Steel is reached only through Caddy and Authentik | Accepted | 2026-09-27 |
14
16
 
15
17
  ADR-001 is the original decision record for the local Runtime. It lives with the contracts in [`docs/contracts/architecture.md`](../contracts/architecture.md) because it is the root of those contracts, and it keeps its original three-digit number. Records in this directory continue the sequence from 0002. There is no ADR-0001.
16
18
 
@@ -1,11 +1,12 @@
1
1
  # CI and release
2
2
 
3
- Every pull request and every push to `main` that changes code runs a
4
- three-minute merge-safety gate, the complete suite with coverage floors runs
5
- nightly, every merge to `main` cuts a patch release, and every release is
6
- verified before it reaches npm. This
7
- page explains which checks run where, how a merge becomes a published version,
8
- and which smoke tests stay manual. It is for contributors and maintainers.
3
+ Pull requests run lint, typecheck, and the unit suite on Linux Node 24.
4
+ Pushes to `main` also run that suite and the full OS × Node matrix.
5
+ The complete suite with coverage floors runs nightly, every merge to `main`
6
+ cuts a patch release, and every release is verified before it reaches npm.
7
+ This page explains which checks run where, how a merge becomes a published
8
+ version, and which smoke tests stay manual. It is for contributors and
9
+ maintainers.
9
10
 
10
11
  ## The pipeline at a glance
11
12
 
@@ -14,9 +15,10 @@ release and an npm publish; the nightly job adds the slower suites.
14
15
 
15
16
  ```mermaid
16
17
  flowchart LR
17
- PR[Pull request] -->|"validate:pr, docs lint, plugin validation"| MERGE{Merged?}
18
+ PR[Pull request] -->|"unit shards, docs lint, plugin validation"| REQ["CI / required"]
19
+ REQ --> MERGE{Merged?}
18
20
  MERGE -->|yes| MAIN[main]
19
- MAIN -->|"same three-minute validate:pr"| PUSH[Push CI]
21
+ MAIN -->|"unit shards plus validate:pr matrix"| PUSH[Push CI]
20
22
  MAIN -->|"Auto-Release: next patch tag"| TAG[Tag vX.Y.Z]
21
23
  TAG -->|dispatch| REL[Release workflow]
22
24
  REL -->|"verify, stamp version, pack"| GH[GitHub release<br/>kxm-X.Y.Z.tgz]
@@ -35,46 +37,75 @@ together.
35
37
  | Trigger | Workflow (job) | What it runs |
36
38
  |---|---|---|
37
39
  | Before you push | Local | `npm run verify` |
38
- | Pull request and push to `main` | `ci.yml` (Validate, two Node legs) | `validate:pr`, the three-minute gate; skipped for documentation-only changes |
40
+ | Pull request, push to `main`, or manual | `ci.yml` (`required`) | Aggregates the lanes below into one pass/fail check named `CI / required` |
39
41
  | Pull request and push | `ci.yml` (Docs lint) | `lint:docs` and `check:versions`, always |
40
- | Pull request and push | `ci.yml` (Plugin validation) | `claude plugin validate --strict` on the marketplace and the plugin; skipped for documentation-only changes |
42
+ | 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` and `runtime.test.ts` one file at a time; every other unit file together. Typecheck and `check-generated` run on the light lane |
43
+ | Platform-sensitive pull request | `ci.yml` (Unit, Windows Node 24) | The same four unit lanes on `windows-latest` |
44
+ | 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
+ | Code pull request and code push | `ci.yml` (Plugin validation) | `claude plugin validate --strict` on the marketplace and the plugin |
41
46
  | Daily at 04:00 UTC, or manual | `nightly.yml` | `test:coverage:complete`, `check`, `check:generated`, `npm pack --dry-run` |
42
47
  | Merged pull request | `auto-release.yml` | Tags the merge commit and dispatches `release.yml` |
43
48
  | Tag push or dispatch | `release.yml` | Verifies, packs and publishes (see [Release flow](#release-flow)) |
44
49
  | Manual only | `smoke.yml` | Real Pi smoke, currently disabled (see [Smoke tests](#smoke-tests)) |
45
- | Pull request, or manual | `e2e.yml` | `npm run e2e` on `ubuntu-latest`: Obscura v0.2.3 plus the Playwright smoke test. This workflow does not enable CI, Nightly, or Real Pi smoke |
50
+ | Pull request, or manual | `e2e.yml` | `npm run e2e` on `ubuntu-latest`: Obscura v0.2.3 plus the Playwright smoke test. Separate from `ci.yml` |
46
51
 
47
52
  The npm scripts behind those rows:
48
53
 
49
54
  | Script | Composition |
50
55
  |---|---|
51
56
  | `verify` | `npm test` (core and package unit tests), `check`, `check:generated` |
57
+ | `test:ci-shard` | One unit lane: build, then `engine <index> <total>`, `serial`, or `light` over `test/core/*.test.ts` and `packages/core/*/tests/unit/*.test.ts` |
52
58
  | `validate:pr` | `build`, `typecheck`, a compact contract and smoke set of nine `test/core` files, `check:versions`, and the generated-`dist` check |
53
- | `validate:ci` | `test:coverage` (core and package tests, 91/80/92 floors), `check`, `npm pack --dry-run`; not run by CI today, available locally |
59
+ | `validate:ci` | `test:coverage` (core and package tests, 91/80/92 floors), `check`, `npm pack --dry-run`; the Release workflow runs it, and it stays available locally |
54
60
  | `test:coverage:complete` | Core, simulation and package tests with 93/80/93 floors |
55
61
  | `check` | `typecheck`, `lint:docs`, `check:versions` |
56
62
 
57
- Three differences matter when a check fails on one side only:
58
-
59
- - CI runs a compact contract and smoke set, not the core suite. The full core
60
- suite and the package unit tests under `packages/core/*/tests` run in your
61
- local `npm run verify`; run it before every push.
62
- - Coverage and `test/simulations` run only in the nightly complete suite, never
63
- on a pull request or a push to `main`.
64
- - A regression the compact set misses can reach `main` and show up in the
65
- nightly run, so treat a nightly failure as a release blocker.
66
-
67
- ### Validate matrix and required checks
68
-
69
- The Validate job runs on Node 22.19.0 and Node 24, on Linux and Windows. Linux
70
- uses the ARC scale set `kontextmind-doks` with a three-minute job timeout.
71
- Windows uses GitHub-hosted `windows-latest` with a fifteen-minute timeout so
72
- `npm ci` can finish. The branch ruleset requires the job names
73
- `Validate (linux, Node 22.19.0)` and `Validate (linux, Node 24)` only; the
74
- Windows names are reported but not required, so a Windows-only failure does not
75
- block merge. Renaming a required Linux job or the matrix means updating the
76
- ruleset in the same change. A newer push cancels an older pull request run;
77
- runs on `main` are never cancelled.
63
+ What moved off the pull-request lane, and what did not:
64
+
65
+ - The four `validate:pr` cells — Linux and Windows, Node 22.19.0 and Node 24 —
66
+ run on a push to `main` and on `workflow_dispatch`. They do not run on a
67
+ pull request. The nine files inside `validate:pr` still run on a code pull
68
+ request, because they are part of the Linux Node 24 unit suite.
69
+ - Node 22.19.0 does not run the unit suite on a pull request. It runs
70
+ `validate:pr` on `main`.
71
+ - Windows runs the unit suite on a pull request only when the classifier marks
72
+ the change platform-sensitive. Every push to `main` that changes code still
73
+ runs `validate:pr` on Windows.
74
+ - Coverage, `test/simulations`, and `npm pack --dry-run` stay in the nightly
75
+ complete suite. They were not part of pull-request CI before this split.
76
+ - Plugin validation still runs on code pull requests and code pushes.
77
+
78
+ Run `npm run verify` locally before every push. A nightly failure is a release
79
+ blocker: it is the only place the simulation suite and coverage floors run.
80
+
81
+ ### Lanes and the required check
82
+
83
+ Code pull requests run Docs lint, two Linux Node 24 engine shards, a serial
84
+ lane (`permission.test.ts` then `runtime.test.ts`), a light lane for every
85
+ other unit file, and Plugin validation. The light lane typechecks and checks
86
+ generated bundles. `engine.test.ts` is split by test name because that file
87
+ alone was 174 seconds; the serial lane keeps the next two longest files off
88
+ the light pool, which was 268 seconds when every non-engine file shared one
89
+ job. Linux jobs use the npm cache from
90
+ `actions/setup-node`. Restoring a `node_modules` tarball was slower than
91
+ `npm ci` on the Linux runners (about 24s versus 17s on 2026-09-24), so that
92
+ cache stays on the Windows jobs, where `npm ci` is the slow step.
93
+
94
+ The job `required` always runs. Its check name is `CI / required`. It fails
95
+ when a lane fails or is cancelled, and it passes when a lane was skipped
96
+ because the change did not need it. Add `CI / required` as a required status
97
+ check in the `protect-main` ruleset. Skipped matrix legs are not required
98
+ names, so they do not block auto-merge. This repository change does not edit
99
+ that ruleset.
100
+
101
+ A newer push cancels an older pull request run. Runs on `main` are never
102
+ cancelled. Unit and Validate matrices use `fail-fast`.
103
+
104
+ The Validate matrix still runs on Node 22.19.0 and Node 24, on Linux and
105
+ Windows, for pushes to `main` and for `workflow_dispatch`. Linux uses the ARC
106
+ scale set `kontextmind-doks` with a three-minute job timeout. Windows uses
107
+ GitHub-hosted `windows-latest` with a fifteen-minute timeout so `npm ci` can
108
+ finish. Those four job names are not the required check anymore.
78
109
 
79
110
  ### CI jobs stay queued while a runner is online
80
111
 
@@ -111,20 +142,26 @@ autoscaler. Do not relabel `km-gh-rn01` or push an empty commit as a routing
111
142
  workaround.
112
143
 
113
144
  > [!NOTE]
114
- > Windows Validate uses GitHub-hosted `windows-latest`, not a self-hosted
115
- > homelab runner and not the ARC scale set. Do not add a `kontextmind-doks`
116
- > label to a Windows runner. Nightly complete coverage and release stay on
117
- > Linux. Windows-specific fixtures (for example the `pi.cmd` worker launch in
118
- > `test/core/worker.test.ts`) also run in the hosted Windows Validate legs.
145
+ > Windows jobs use GitHub-hosted `windows-latest`, not a self-hosted homelab
146
+ > runner and not the ARC scale set. Do not add a `kontextmind-doks` label to a
147
+ > Windows runner. Nightly complete coverage and release stay on Linux.
148
+ > Platform-sensitive pull requests run the unit suite on Windows, which
149
+ > includes `test/core/worker.test.ts`. The main Validate legs run the compact
150
+ > `validate:pr` gate.
119
151
 
120
152
  ### The docs-only classifier
121
153
 
122
154
  The first job, Classify changes, lists the changed paths and sets `code=false`
123
- when every path matches `*.md`, `docs/*`, `.kxm/assets/*`, `LICENSE`, the issue
124
- and PR templates, or `dependabot.yml`. Validate and Plugin validation read it:
125
- for a documentation-only change they report success without checking out the
126
- code, so the required job names still pass. Docs lint always runs.
127
- `ci-contract.test.ts` pins this behavior.
155
+ when every path matches `*.md` (including `plans/**/*.md`), `docs/**`,
156
+ `.kxm/assets/**`, `LICENSE`, the issue and PR templates, or `dependabot.yml`.
157
+ A non-markdown file under `plans/` is code. Unit lanes, Plugin validation,
158
+ and the Validate matrix are skipped. Docs lint runs, and `CI / required`
159
+ passes. `scripts/ci-classify.mjs` and `ci-contract.test.ts` pin this behavior.
160
+
161
+ `platform=true` when a path is a package manifest, the lockfile, a file under
162
+ `scripts/` or `.github/workflows/`, or a filename that names path, process,
163
+ shell, spawn, worker, supervisor, repo-root, or ssh-remote behavior. A
164
+ platform-sensitive pull request also runs the Windows Node 24 unit lanes.
128
165
 
129
166
  > [!WARNING]
130
167
  > Several tests and code paths read documentation files by path. A pull request
@@ -2,7 +2,7 @@
2
2
  title: "Operating rules"
3
3
  description: "Standing instructions from the operator that every agent session on this repository follows, with the date each was given. Read before planning, dispatching, landing, or editing the roadmap."
4
4
  audience: "agents and maintainers"
5
- updated: "2026-09-26"
5
+ updated: "2026-09-27"
6
6
  ---
7
7
 
8
8
  # Operating rules
@@ -58,6 +58,18 @@ replacement.
58
58
  `impl-bg`) retire last, after one real unit has run through the one-step
59
59
  workflows. (2026-09-26.)
60
60
 
61
+ ## Accounts
62
+
63
+ - **Machine account names follow [ADR-0006](../adr/ADR-0006-machine-account-names.md).**
64
+ Platform-wide accounts are `svc-<system>-<purpose>`. Tenant-scoped accounts
65
+ are `svc-<tenant>-<system>-<purpose>`, where `<tenant>` is the tenant slug.
66
+ Test, witness, and proof accounts use those shapes with a `test-` prefix
67
+ and are never members of production groups. Authentik-managed accounts
68
+ (outposts and `ak-*`) are exempt. Recorded names such as `kxm-agent`,
69
+ `kxm-witness-*`, `witness9`, and `kxmdproof` stay until an approved
70
+ inventory. Do not invent a replacement for a specific account.
71
+ (2026-09-27.)
72
+
61
73
  ## Decisions and debt
62
74
 
63
75
  - **Every option comes with pros and cons and two recommendations**, the
@@ -68,7 +68,7 @@ Tools map the same way: peer tools to `kxm-peer`, workflow tools to `kxm-workflo
68
68
 
69
69
  ## Browser automation skills
70
70
 
71
- Playwright testing and verification use Obscura ([ADR-0005](../adr/ADR-0005-obscura-default-playwright.md)). The Steel skills cover human takeover, MFA, and the live session viewer. Those hosts sit behind Authentik forward auth: send `Authorization: Basic` (`STEEL_AUTH_BASIC`, or `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`) and keep the credential out of URLs. `kxm-browser-verify` owns `vision`. The other browser skills own no `kxm` command. See [Browser automation](browser-automation.md) and [ADR-0002](../adr/ADR-0002-browser-automation-steel-doks.md).
71
+ Playwright testing and verification use Obscura ([ADR-0005](../adr/ADR-0005-obscura-default-playwright.md)). The Steel skills cover remote and hosted browsing, human takeover, MFA, and the live session viewer. Steel is `https://steel.kontextmind.com`, reached only through Caddy and Authentik ([ADR-0007](../adr/ADR-0007-steel-caddy-authentik.md)). Send `Authorization: Basic` (`STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`) and keep the credential out of URLs. `STEEL_API_KEY` is deprecated and is not enforced. `kxm` 0.7.135 or newer is required for Steel. `kxm-browser-verify` owns `vision`. The other browser skills own no `kxm` command. See [Browser automation](browser-automation.md).
72
72
 
73
73
  | Skill | Use it to |
74
74
  |---|---|