dsh-env-manager-plugin 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -2
  3. package/client.js +2581 -0
  4. package/cordis.patch.yml +4 -0
  5. package/docs/00-project/current-state.md +246 -0
  6. package/docs/00-project/glossary.md +215 -0
  7. package/docs/00-project/project-brief.md +114 -0
  8. package/docs/00-project/roadmap.md +89 -0
  9. package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -0
  10. package/docs/01-architecture/api-verification.md +416 -0
  11. package/docs/01-architecture/architecture-overview.md +222 -0
  12. package/docs/01-architecture/data-model.md +930 -0
  13. package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -0
  14. package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -0
  15. package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -0
  16. package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -0
  17. package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -0
  18. package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -0
  19. package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -0
  20. package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -0
  21. package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -0
  22. package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -0
  23. package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -0
  24. package/docs/01-architecture/design.md +666 -0
  25. package/docs/01-architecture/detailed-design.md +961 -0
  26. package/docs/01-architecture/implementation-plan.md +323 -0
  27. package/docs/01-architecture/module-boundaries.md +306 -0
  28. package/docs/01-architecture/security-model.md +227 -0
  29. package/docs/01-architecture/storage-contract.md +224 -0
  30. package/docs/01-architecture/tool-contract.md +438 -0
  31. package/docs/01-architecture/ui-design.md +587 -0
  32. package/docs/02-harness/agent-behavior.md +36 -0
  33. package/docs/02-harness/change-snapshot-policy.md +29 -0
  34. package/docs/02-harness/client-ui-integration.md +110 -0
  35. package/docs/02-harness/context-policy.md +43 -0
  36. package/docs/02-harness/harness-standard.md +91 -0
  37. package/docs/02-harness/task-execution-policy.md +26 -0
  38. package/docs/02-harness/verification-policy.md +97 -0
  39. package/docs/03-openspec/archive-rule.md +25 -0
  40. package/docs/03-openspec/bugfix-rule.md +30 -0
  41. package/docs/03-openspec/design-rule.md +33 -0
  42. package/docs/03-openspec/openspec-usage-guide.md +31 -0
  43. package/docs/03-openspec/proposal-rule.md +70 -0
  44. package/docs/03-openspec/spec-rule.md +33 -0
  45. package/docs/03-openspec/tasks-rule.md +31 -0
  46. package/docs/04-development/coding-style.md +67 -0
  47. package/docs/04-development/dependency-policy.md +43 -0
  48. package/docs/04-development/development-guide.md +85 -0
  49. package/docs/04-development/directory-convention.md +105 -0
  50. package/docs/04-development/local-run-guide.md +55 -0
  51. package/docs/04-development/testing-guide.md +102 -0
  52. package/docs/05-domain/deployment-template.md +76 -0
  53. package/docs/05-domain/environment-template.md +165 -0
  54. package/docs/05-domain/operation-binding-template.md +55 -0
  55. package/docs/05-domain/operation-record-template.md +55 -0
  56. package/docs/05-domain/prompt-template.md +90 -0
  57. package/docs/05-domain/rule-template.md +73 -0
  58. package/docs/05-domain/service-template.md +39 -0
  59. package/docs/06-operations/deployment-guide.md +54 -0
  60. package/docs/06-operations/release.md +188 -0
  61. package/docs/06-operations/security-policy.md +53 -0
  62. package/docs/06-operations/troubleshooting.md +91 -0
  63. package/docs/07-templates/adr-template.md +21 -0
  64. package/docs/07-templates/bugfix-template.md +11 -0
  65. package/docs/07-templates/case-template.md +15 -0
  66. package/docs/07-templates/design-template.md +29 -0
  67. package/docs/07-templates/proposal-template.md +19 -0
  68. package/docs/07-templates/spec-template.md +15 -0
  69. package/docs/07-templates/tasks-template.md +12 -0
  70. package/docs/99-archive/README.md +13 -0
  71. package/docs/README.md +142 -0
  72. package/icon.svg +4 -0
  73. package/index.js +8 -0
  74. package/lib/app/app.d.ts +67 -0
  75. package/lib/app/app.js +494 -0
  76. package/lib/app/app.js.map +1 -0
  77. package/lib/config.d.ts +55 -0
  78. package/lib/config.js +42 -0
  79. package/lib/config.js.map +1 -0
  80. package/lib/domain/constants.d.ts +21 -0
  81. package/lib/domain/constants.js +59 -0
  82. package/lib/domain/constants.js.map +1 -0
  83. package/lib/domain/errors.d.ts +20 -0
  84. package/lib/domain/errors.js +22 -0
  85. package/lib/domain/errors.js.map +1 -0
  86. package/lib/domain/schemas.d.ts +28 -0
  87. package/lib/domain/schemas.js +445 -0
  88. package/lib/domain/schemas.js.map +1 -0
  89. package/lib/domain/types.d.ts +412 -0
  90. package/lib/domain/types.js +2 -0
  91. package/lib/domain/types.js.map +1 -0
  92. package/lib/index.d.ts +18 -0
  93. package/lib/index.js +89 -0
  94. package/lib/index.js.map +1 -0
  95. package/lib/logger.d.ts +8 -0
  96. package/lib/logger.js +13 -0
  97. package/lib/logger.js.map +1 -0
  98. package/lib/memory/bridge.d.ts +18 -0
  99. package/lib/memory/bridge.js +13 -0
  100. package/lib/memory/bridge.js.map +1 -0
  101. package/lib/registry/integrity.d.ts +10 -0
  102. package/lib/registry/integrity.js +204 -0
  103. package/lib/registry/integrity.js.map +1 -0
  104. package/lib/registry/registry.d.ts +14 -0
  105. package/lib/registry/registry.js +182 -0
  106. package/lib/registry/registry.js.map +1 -0
  107. package/lib/resolver/merge.d.ts +23 -0
  108. package/lib/resolver/merge.js +90 -0
  109. package/lib/resolver/merge.js.map +1 -0
  110. package/lib/resolver/resolve.d.ts +18 -0
  111. package/lib/resolver/resolve.js +288 -0
  112. package/lib/resolver/resolve.js.map +1 -0
  113. package/lib/resolver/template.d.ts +10 -0
  114. package/lib/resolver/template.js +42 -0
  115. package/lib/resolver/template.js.map +1 -0
  116. package/lib/router/params.d.ts +3 -0
  117. package/lib/router/params.js +83 -0
  118. package/lib/router/params.js.map +1 -0
  119. package/lib/router/router.d.ts +11 -0
  120. package/lib/router/router.js +204 -0
  121. package/lib/router/router.js.map +1 -0
  122. package/lib/session/session.d.ts +16 -0
  123. package/lib/session/session.js +64 -0
  124. package/lib/session/session.js.map +1 -0
  125. package/lib/skill.d.ts +17 -0
  126. package/lib/skill.js +56 -0
  127. package/lib/skill.js.map +1 -0
  128. package/lib/storage/atomic.d.ts +10 -0
  129. package/lib/storage/atomic.js +107 -0
  130. package/lib/storage/atomic.js.map +1 -0
  131. package/lib/storage/paths.d.ts +10 -0
  132. package/lib/storage/paths.js +50 -0
  133. package/lib/storage/paths.js.map +1 -0
  134. package/lib/storage/records.d.ts +16 -0
  135. package/lib/storage/records.js +112 -0
  136. package/lib/storage/records.js.map +1 -0
  137. package/lib/storage/sessions.d.ts +17 -0
  138. package/lib/storage/sessions.js +65 -0
  139. package/lib/storage/sessions.js.map +1 -0
  140. package/lib/tools/tools.d.ts +14 -0
  141. package/lib/tools/tools.js +472 -0
  142. package/lib/tools/tools.js.map +1 -0
  143. package/lib/transport/http.d.ts +3 -0
  144. package/lib/transport/http.js +217 -0
  145. package/lib/transport/http.js.map +1 -0
  146. package/locale/en.json +6 -0
  147. package/locale/zh.json +6 -0
  148. package/package.json +97 -4
@@ -0,0 +1,29 @@
1
+ # ADR-0001: Distribute as a DSH Bundle
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ The Environment Manager must be installable into DSH and share state across sessions in the same profile. We considered several distribution models:
10
+
11
+ 1. Standalone Node.js service
12
+ 2. DSH plugin bundle
13
+ 3. External CLI tool
14
+
15
+ ## Decision
16
+
17
+ We will distribute Environment Manager as a DSH bundle with both Host and Client entries.
18
+
19
+ ## Consequences
20
+
21
+ - Users install the bundle through DSH's `plugin_manager`.
22
+ - Host-side code runs in the DSH host process and can register tools and services.
23
+ - Client-side code runs in the Web/Desktop UI and can provide configuration panels.
24
+ - State is scoped to a DSH profile; all sessions in the profile share the registry.
25
+
26
+ ## Alternatives Considered
27
+
28
+ - **Standalone service**: Adds operational overhead and is not idiomatic for DSH.
29
+ - **External CLI tool**: Loses integration with DSH session context and UI.
@@ -0,0 +1,25 @@
1
+ # ADR-0002: File-based Storage as Source of Truth
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ We need durable storage for registry records. Options include a local JSON/YAML file tree, SQLite, or a database.
10
+
11
+ ## Decision
12
+
13
+ Registry data is stored as YAML files in a directory tree under the plugin workspace.
14
+
15
+ ## Consequences
16
+
17
+ - Human-readable and version-control friendly.
18
+ - Easy to inspect and edit.
19
+ - No external database dependency.
20
+ - Concurrent writes are last-write-wins; higher-level locking may be added later.
21
+
22
+ ## Alternatives Considered
23
+
24
+ - **SQLite**: Adds complexity and a binary artifact; not necessary for V1 scale.
25
+ - **DSH memory plugin**: Memory plugin is for historical knowledge, not authoritative configuration.
@@ -0,0 +1,25 @@
1
+ # ADR-0003: Explicit Operation-to-Skill Binding over Search
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ When an Agent asks to perform an operation (e.g. redeploy a service), the system could either search for a relevant Skill or use an explicit binding.
10
+
11
+ ## Decision
12
+
13
+ We will use explicit Operation Bindings with deterministic scope-based matching.
14
+
15
+ ## Consequences
16
+
17
+ - Predictable routing: the same context always resolves to the same Skill.
18
+ - Conflicts are explicit and fail safely.
19
+ - Parameters are mapped declaratively.
20
+ - Approval policies are attached to bindings.
21
+
22
+ ## Alternatives Considered
23
+
24
+ - **Semantic search over skill descriptions**: Non-deterministic and hard to audit for production operations.
25
+ - **Convention-based naming**: Brittle and does not support fine-grained policy.
@@ -0,0 +1,31 @@
1
+ # ADR-0004: TypeScript Host, Plain-JavaScript Client
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ The Host half contains the domain logic: schemas, layered rule resolution, provenance, conflict detection and policy evaluation. These benefit from static types and exhaustive checks.
10
+
11
+ The Client half is a DSH module-loader artifact. It has no build step today and uses `React.createElement` rather than JSX.
12
+
13
+ The alternative was TypeScript everywhere with a client bundler, or plain JavaScript everywhere.
14
+
15
+ ## Decision
16
+
17
+ - Host: TypeScript compiled to ESM (`src/` → `lib/`).
18
+ - Client: plain JavaScript module at the repository root (`client.js`), no bundler.
19
+ - The current JavaScript host skeleton is replaced during V1 implementation; the bundle entry becomes a thin shim over `lib/index.js`.
20
+
21
+ ## Consequences
22
+
23
+ - Domain invariants are checked at compile time.
24
+ - No client build pipeline in V1; UI code is more verbose.
25
+ - Two languages in one repository; the boundary is explicit: `src/` is Host-only.
26
+ - If the UI later needs TypeScript or JSX, that change gets its own ADR and a bundling step.
27
+
28
+ ## Alternatives Considered
29
+
30
+ - **TypeScript everywhere now**: a bundler and a module-loader wrapper are needed immediately; delays the core value.
31
+ - **JavaScript everywhere**: loses compile-time guarantees exactly where the domain is most intricate.
@@ -0,0 +1,36 @@
1
+ # ADR-0005: Layered Rule Resolution with Provenance
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Rule profiles define paths, log sources and class locations that differ by service and by environment. Users need to override one field without copying an entire profile, and they need to understand why a resolved value is what it is.
10
+
11
+ Alternatives: a single flat profile per deployment, whole-object replacement, or embedding script hooks.
12
+
13
+ ## Decision
14
+
15
+ Resolve rules in four ordered layers:
16
+
17
+ 1. global defaults
18
+ 2. service `ruleProfileRefs`, in list order
19
+ 3. environment `ruleOverrides`
20
+ 4. deployment `variables` and `ruleOverrides`
21
+
22
+ Merging is field-level. Every returned field carries provenance: the winning layer, the referenced inputs with their own sources, and the shadowed definitions.
23
+
24
+ Templates support `${...}` interpolation only. No functions, operators or code.
25
+
26
+ ## Consequences
27
+
28
+ - An environment can override `paths.logDir` without touching `paths.jar`.
29
+ - A field defined at the service layer can still change because an input was overridden; provenance makes that visible.
30
+ - Validation must detect cycles and undefined references before returning anything.
31
+ - Users see derived values as derived; they are not implied to exist.
32
+
33
+ ## Alternatives Considered
34
+
35
+ - **Whole-object replacement**: simple but forces duplication and hides intent.
36
+ - **Script hooks**: powerful but unreviewable in production and outside the safety model.
@@ -0,0 +1,30 @@
1
+ # ADR-0006: Credentials Are References
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ The registry describes production hosts, clusters and services. Operators will want the Agent to "log in" or "use the credential for test-a".
10
+
11
+ Storing values in the registry would make the YAML files a secret store, would leak through tool output and Git history, and would duplicate the DSH credential service.
12
+
13
+ ## Decision
14
+
15
+ 1. Registry records store only `credentialRef` strings.
16
+ 2. Credential status is obtained with `ctx.credentials.describe(ref)` and reduced to
17
+ `{ configured, source? }`.
18
+ 3. The plugin never calls `ctx.credentials.resolve(ref)` and never forwards a credential value.
19
+ 4. Tool and HTTP output expose `{ ref, configured, source? }` only.
20
+
21
+ ## Consequences
22
+
23
+ - Secrets stay in the DSH credential service or an external vault.
24
+ - The plugin cannot authenticate anything itself; execution adapters (future) must obtain credentials through the proper service.
25
+ - A wrong reference cannot be detected beyond `configured: true/false`; operational review remains necessary.
26
+
27
+ ## Alternatives Considered
28
+
29
+ - **Store values in YAML**: unacceptable for Git history and tool output.
30
+ - **Skip references entirely**: loses the link between a host and its credential, which users need.
@@ -0,0 +1,28 @@
1
+ # ADR-0007: Session-Scoped Default Environment
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ Users work in one environment at a time and do not want to repeat "test-a" in every request. A shared global default would let one session silently change another session's target, which is unsafe.
10
+
11
+ ## Decision
12
+
13
+ - The default environment is stored per session.
14
+ - `env_set_context` writes only the current session's value.
15
+ - Tools accept an explicit `environmentId` first; the session default is a fallback.
16
+ - The default is advisory: it never mutates registry records and never changes routing policy.
17
+
18
+ ## Consequences
19
+
20
+ - Two sessions can target different environments concurrently (AC-07).
21
+ - Storage is a small JSON file per session under `<dataDir>/sessions/`; the DSH
22
+ projection store is not usable from a standalone bundle (`api-verification.md` F11).
23
+ - A session without a default must pass `environmentId` explicitly; the error names both options.
24
+
25
+ ## Alternatives Considered
26
+
27
+ - **Profile-global default**: convenient but violates session isolation.
28
+ - **No default at all**: safe but adds friction to every call.
@@ -0,0 +1,31 @@
1
+ # ADR-0008: Memory Bridge Instead of a Second Memory Store
2
+
3
+ ## Status
4
+
5
+ Accepted
6
+
7
+ ## Context
8
+
9
+ The brainstorm originally considered a standalone memory plugin with structured categories. The project already depends on a memory plugin for durable knowledge, decisions, playbooks and open questions.
10
+
11
+ Building a second store would duplicate environment facts and split the place where lessons live.
12
+
13
+ ## Decision
14
+
15
+ - DSH Environment Manager owns environment facts, service catalog, deployment bindings, rules and operation bindings.
16
+ - The memory plugin owns durable knowledge: observations, playbooks, decisions and proposals.
17
+ - The bridge exchanges scoped references, never copies of registry data.
18
+ - V1 ships the contract and a null adapter; P1 implements the adapter.
19
+ - Nothing in the bridge auto-applies a change.
20
+
21
+ ## Consequences
22
+
23
+ - One place for facts, one place for knowledge.
24
+ - Memory entries must carry `environmentId`/`serviceId`/`deploymentId` scope.
25
+ - A single-environment conclusion cannot silently become a global rule.
26
+ - The bridge contract must be versioned when the memory plugin changes.
27
+
28
+ ## Alternatives Considered
29
+
30
+ - **Own memory store**: duplicates scope, evidence and review logic.
31
+ - **No integration**: the improvement loop from the brainstorm cannot close.
@@ -0,0 +1,87 @@
1
+ # ADR-0009: Client–Host transport via a plugin-owned HTTP route
2
+
3
+ - Status: accepted
4
+ - Date: 2026-11-01
5
+ - Supersedes: none
6
+ - Related: ADR-0004 (TypeScript Host, plain-JS Client), `api-verification.md` F9–F14
7
+
8
+ ## Context
9
+
10
+ The V1 panel must list, edit and validate registry records. The data lives in the Host
11
+ process; the Client half runs in the browser page. A transport is therefore required.
12
+
13
+ DSH's native mechanism is the Typert Remote system:
14
+
15
+ - the Host defines a service extending `TypertRemoteService` and marks methods with
16
+ `@Remote` / `@RemoteScope`;
17
+ - a build-time Typert loader emits an `InvocationDescriptor` and a `/remote` artifact;
18
+ - the Client imports that artifact, mounts it through `ctx.remote.$mount()`, and calls
19
+ generated methods;
20
+ - the Gateway rejects Client-supplied fields that lack a strict generated codec.
21
+
22
+ That pipeline assumes the DSH monorepo build toolchain and a matching DSH release.
23
+ This repository is a standalone bundle compiled with plain `tsc`, and the runtime
24
+ provides no API for registering a Remote namespace from plain JavaScript.
25
+
26
+ ## Decision
27
+
28
+ The plugin registers one same-origin HTTP route and the Client calls it:
29
+
30
+ ```text
31
+ POST /env-manager/api
32
+ headers: content-type: application/json
33
+ x-env-manager-request: 1
34
+ body: { "op": "...", "payload": { ... } }
35
+ ```
36
+
37
+ - The route is registered with `ctx.webServer.register({ kind: 'prefix', ... })` and
38
+ disposed with the plugin fiber.
39
+ - The route handler is a thin adapter over the same application service the tools use;
40
+ there is no second business path.
41
+ - Authentication is a required custom header plus an `Origin` equality check against
42
+ `ctx.webServer.getOrigin()`. No cookie or credential is read. No CORS headers are
43
+ sent, so a cross-origin page cannot preflight the custom header.
44
+ - Every request is validated server-side; the Client's TypeScript-free code is never
45
+ trusted for shape.
46
+ - Writes from this route run with actor `'ui'` and are subject to
47
+ `writePolicy.allowUiProductionWrites`; deletes require an explicit `confirm: true`.
48
+
49
+ ## Consequences
50
+
51
+ Positive:
52
+
53
+ - No dependency on the Typert codegen toolchain or on a matching DSH release.
54
+ - One transport for tools and UI, so behavior cannot drift.
55
+ - Simple to test with Node's `http` mocks and `curl`.
56
+ - No streaming requirement in V1, so request/response is sufficient.
57
+
58
+ Negative:
59
+
60
+ - The Client has no generated type contract; shared shapes are maintained by hand and
61
+ checked by tests.
62
+ - A new local HTTP surface exists. It is header-gated, origin-checked, read-only for
63
+ credentials, and cannot be reached cross-origin without a preflight the server
64
+ refuses.
65
+ - No streaming or subscriptions; the UI refreshes explicitly.
66
+ - Session-scoped operations are not exposed to the UI in V1, because the UI has no
67
+ trustworthy session identity.
68
+
69
+ ## Alternatives
70
+
71
+ | Alternative | Why rejected |
72
+ |---|---|
73
+ | Typert Remote namespace | Requires build-time descriptors and DSH monorepo tooling; impossible from plain JS |
74
+ | Client reads registry files directly | The browser has no filesystem access, and it would bypass Host validation |
75
+ | Agent-mediated UI (buttons insert prompts) | Poor UX; every inspection would consume a turn |
76
+ | WebSocket / streaming | V1 has no live-update requirement |
77
+ | `window.__DSH_BOOT__` global injection of data | One-shot snapshot, no writes, stale immediately |
78
+
79
+ ## Review trigger
80
+
81
+ Revisit when either becomes true:
82
+
83
+ 1. DSH exposes a runtime API to register an `InvocationDescriptor` from a bundle, or
84
+ 2. this repository adopts the DSH build toolchain and can emit `/remote` artifacts.
85
+
86
+ At that point the HTTP adapter can be replaced without changing the application service
87
+ or the tools.
@@ -0,0 +1,87 @@
1
+ # ADR-0010: Environment as one network, planes as role views
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-11-01
5
+ - **Supersedes:** the one-step "environment + first host + password" creation flow
6
+ described in earlier V1 UI notes
7
+
8
+ ## Context
9
+
10
+ Operators describe an environment as a complete network set: management nodes, business
11
+ nodes, database nodes (sometimes many), expansion nodes, and 1–100 IPs. They need to work
12
+ one network at a time and switch between planes inside it.
13
+
14
+ The original V1 panel treated the environment as a flat list of hosts plus accounts, and
15
+ its **New environment** form created the environment, first host, account and SSH endpoint
16
+ in one save. That mixed three concerns (network identity, node topology, credentials),
17
+ made every new environment inherit a node it might not need, and hid the plane structure
18
+ inside a generic host table.
19
+
20
+ Two alternatives were considered:
21
+
22
+ 1. **A separate environment per plane.** Easy filtering, but duplicates the network,
23
+ breaks shared nodes and forces the operator to maintain several YAML documents for one
24
+ network.
25
+ 2. **One environment with a flat host list.** Already supported by the schema, but the UI
26
+ had no way to work plane-by-plane, so it did not match the operator's mental model.
27
+
28
+ ## Decision
29
+
30
+ 1. **One environment = one complete network = one YAML document = one configuration
31
+ page.** Management, business, database and expansion are **logical roles**, not
32
+ separate environments.
33
+ 2. **Planes are views over the same `Environment.hosts[]`.** The detail page renders one
34
+ tab per declared role by filtering `hosts[]`. A node serving two planes is one `Host`
35
+ with two role ids and appears in both tabs; it is never copied per plane.
36
+ 3. **`New environment` creates only the shell.** The form collects name, optional id,
37
+ category and description, seeds the four standard roles (`management`, `business`,
38
+ `database`, `extension`) and creates empty `hosts`, `clusters`, `endpoints` and
39
+ `loginRecords`. Nodes are added inside the environment.
40
+ 4. **Roles are mandatory where they carry meaning.**
41
+ - `Environment.roles` must contain at least one entry.
42
+ - Every `Host.roles` must contain at least one entry.
43
+ - Every host role must reference a declared logical role.
44
+ - A plane with zero nodes is valid; a node with zero roles is not.
45
+ 5. **A node supports multiple roles.** Add/edit node uses role checkboxes; opening the form
46
+ from a plane preselects that role. Co-location is represented once.
47
+ 6. **Nodes are first-class in the panel.** Add/edit node, optional account creation,
48
+ expandable account tables, a shared search across id/name/IP/platform/admin/role, and
49
+ batch import in the form `IP,name,roles,port,user` (multiple roles separated by `|` or
50
+ `/`). Batch import never accepts passwords.
51
+ 7. **Credentials remain transient and namespaced.** The add/edit node form may collect a
52
+ login username and password; the password is sent once to `ctx.credentials.set`, the
53
+ record keeps `credentialRef` only. Shell creation and batch import collect no secrets.
54
+ 8. **Database topology is deferred.** Phase 1 treats `database` as a role and keeps
55
+ primary/replica/sharding detail in node notes. Modeling it now would add a schema the
56
+ rest of the UI does not consume.
57
+
58
+ ## Consequences
59
+
60
+ Positive:
61
+
62
+ - One network has one YAML document, one backup unit and one atomic write.
63
+ - A shared management/business/database host is represented once, avoiding duplicate-IP
64
+ errors and split audit context.
65
+ - The UI matches the operator's vocabulary: open the network, switch planes, add nodes.
66
+ - New environments are cheap: four empty plane tabs are immediately usable.
67
+ - Schema validation prevents a node from disappearing from every plane because it has no
68
+ role.
69
+
70
+ Costs / limits:
71
+
72
+ - Every plane view is a filter, so large environments need search and a future
73
+ virtualization strategy; V1 assumes up to a few hundred nodes.
74
+ - Deleting a plane role would affect every node that references it; the panel currently
75
+ edits the role catalog through YAML, not a dedicated role editor.
76
+ - Phase 1 cannot express primary/replica/sharding relationships, read/write split or
77
+ shard ranges; those remain Phase 2.
78
+ - A node with no accounts is valid, but the panel shows `0 accounts` and no credential
79
+ reference until one is added.
80
+
81
+ ## Follow-up
82
+
83
+ - Phase 2: database topology metadata and plane-aware expansion modeling.
84
+ - Add row-level delete for nodes/accounts/login records and credential rotation.
85
+ - Consider an optional JumpServer/PAM import adapter for accounts and login observations.
86
+ - Keep `docs/01-architecture/data-model.md` §3 and `ui-design.md` §6.3 as the normative
87
+ references for this decision.
@@ -0,0 +1,92 @@
1
+ # ADR-0011: Model Kubernetes nodes and Pod hosts as machines with command surfaces
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-11-01
5
+ - **Related:** ADR-0010 (environment plane model), ADR-0006 (credentials as references)
6
+
7
+ ## Context
8
+
9
+ Operators use three Kubernetes-adjacent deployment shapes in the same business:
10
+
11
+ 1. **K8s all-in-one.** The control plane and a worker run on the same machine. Operators
12
+ can run `kubectl` there and, when the runtime is Docker, may also use `docker`.
13
+ 2. **K8s + Pod hosts.** A Kubernetes cluster exists, but business Pods run on separate
14
+ machines. Our team operates those machines with `docker exec`, not by treating each Pod
15
+ as an inventory node. Other departments use standard worker nodes in the same cluster.
16
+ 3. **Generic machines.** VMs and physical machines that are not Kubernetes nodes.
17
+
18
+ The Phase 1 environment model had only generic `Host` records. It could store an IP and a
19
+ role, but it could not say whether a node is an all-in-one control plane, a worker, or a
20
+ Pod-hosting machine, nor whether operators reach it with SSH, `kubectl`, or `docker`.
21
+ Adding a new logical plane was the wrong fix: a node's Kubernetes role is a property of the
22
+ machine and its runtime, while planes remain management/business/database/extension views.
23
+
24
+ ## Decision
25
+
26
+ 1. **A node is a machine, not a Pod.** `Host` represents a VM, physical machine, Kubernetes
27
+ node or Pod-hosting machine. A Pod is a workload; it stays in the deployment model
28
+ (`location.kind: kubernetes`) and is not added to `hosts[]`.
29
+ 2. **Add `Host.kind`.** Allowed values:
30
+ - `vm`
31
+ - `physical`
32
+ - `k8s-all-in-one` — control plane and worker on the same machine
33
+ - `k8s-worker` — worker node in a cluster
34
+ - `pod-host` — machine running Pods, operated through `docker exec`
35
+
36
+ The schema defaults a missing value to `vm` so existing environment YAML remains
37
+ readable. The kind is a classification, not a new logical role.
38
+ 3. **Add `Host.access`.** Allowed values are `ssh`, `kubectl` and `docker`. This records the
39
+ command surfaces that exist on the machine. It is metadata, not an authorization grant
40
+ and not a credential. The registry stores no passwords, kubeconfigs or Docker
41
+ credentials; those remain behind `credentialRef`.
42
+ 4. **Add `Host.clusterRef`.** Kubernetes nodes and Pod hosts may reference a `Cluster` in
43
+ the same environment. `k8s-all-in-one` and `k8s-worker` require an existing cluster
44
+ reference; `pod-host` may omit it for a Docker-only machine outside the cluster.
45
+ 5. **Add `Cluster.topology`.** Allowed values are `all-in-one`, `separate` and `external`.
46
+ Topology is a descriptive cluster hint. Per-host `kind` remains authoritative; the schema
47
+ does not force a particular mix of node kinds while an environment is being migrated.
48
+ 6. **Keep access methods independent of kind.** A worker may have Docker; a VM may have
49
+ `kubectl`; an all-in-one node may have all three. The UI pre-fills defaults but never
50
+ hides an access method based on the kind.
51
+ 7. **Model the operational entry point, not the Pod.** A `pod-host` tells an operator that
52
+ the machine is reached with `docker exec`; it intentionally has no `podName` or
53
+ `containerName` field in V1. Adding Pod/container inventory is a separate future model.
54
+ 8. **Expose the model in the panel.** The node form has kind, cluster and access controls;
55
+ node cards show kind/access badges and cluster references; search covers kind, access and
56
+ cluster name/reference; the detail page has a Kubernetes/clusters tab for adding and
57
+ editing cluster metadata. Batch import still creates generic `vm`/`ssh` nodes and never
58
+ accepts secrets.
59
+
60
+ ## Consequences
61
+
62
+ Positive:
63
+
64
+ - The three deployment shapes operators actually use are representable without inventing a
65
+ Kubernetes plane or duplicating a machine per Pod.
66
+ - The command surface is explicit, so a future execution skill can choose SSH, `kubectl` or
67
+ `docker exec` from declared metadata instead of guessing from the node name.
68
+ - Cluster references and topology make the relationship between all-in-one, worker and Pod
69
+ hosts visible in one environment document.
70
+ - Existing YAML remains valid: missing `kind` reads as `vm`, missing `access` is unknown,
71
+ and missing topology is displayed as `—`.
72
+
73
+ Costs / limits:
74
+
75
+ - `access` states what is available, not whether the current operator is allowed to use it.
76
+ Approval and execution policy remain outside this model.
77
+ - The schema does not prove that a host really has `kubectl` or Docker, nor that a cluster's
78
+ topology matches its nodes. The values are operator-maintained facts.
79
+ - A Pod host is still one record even when it runs many Pods; per-Pod inventory, container
80
+ ids and restart counts are out of scope for V1.
81
+ - Batch import does not yet accept kind/access/cluster columns; those nodes are created as
82
+ generic VMs and must be edited, or imported through YAML.
83
+
84
+ ## Follow-up
85
+
86
+ - Add a future Pod/container inventory only if operations need per-workload targeting; keep
87
+ it separate from `Host`.
88
+ - Add batch-import columns for kind/access/cluster when bulk Kubernetes onboarding becomes a
89
+ real workflow.
90
+ - Consider validating cluster topology consistency as a warning, not a hard error.
91
+ - Keep `docs/01-architecture/data-model.md` §3.2 and `docs/01-architecture/ui-design.md`
92
+ synchronized with this decision.