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.
- package/LICENSE +21 -0
- package/README.md +249 -2
- package/client.js +2581 -0
- package/cordis.patch.yml +4 -0
- package/docs/00-project/current-state.md +246 -0
- package/docs/00-project/glossary.md +215 -0
- package/docs/00-project/project-brief.md +114 -0
- package/docs/00-project/roadmap.md +89 -0
- package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -0
- package/docs/01-architecture/api-verification.md +416 -0
- package/docs/01-architecture/architecture-overview.md +222 -0
- package/docs/01-architecture/data-model.md +930 -0
- package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -0
- package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -0
- package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -0
- package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -0
- package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -0
- package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -0
- package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -0
- package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -0
- package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -0
- package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -0
- package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -0
- package/docs/01-architecture/design.md +666 -0
- package/docs/01-architecture/detailed-design.md +961 -0
- package/docs/01-architecture/implementation-plan.md +323 -0
- package/docs/01-architecture/module-boundaries.md +306 -0
- package/docs/01-architecture/security-model.md +227 -0
- package/docs/01-architecture/storage-contract.md +224 -0
- package/docs/01-architecture/tool-contract.md +438 -0
- package/docs/01-architecture/ui-design.md +587 -0
- package/docs/02-harness/agent-behavior.md +36 -0
- package/docs/02-harness/change-snapshot-policy.md +29 -0
- package/docs/02-harness/client-ui-integration.md +110 -0
- package/docs/02-harness/context-policy.md +43 -0
- package/docs/02-harness/harness-standard.md +91 -0
- package/docs/02-harness/task-execution-policy.md +26 -0
- package/docs/02-harness/verification-policy.md +97 -0
- package/docs/03-openspec/archive-rule.md +25 -0
- package/docs/03-openspec/bugfix-rule.md +30 -0
- package/docs/03-openspec/design-rule.md +33 -0
- package/docs/03-openspec/openspec-usage-guide.md +31 -0
- package/docs/03-openspec/proposal-rule.md +70 -0
- package/docs/03-openspec/spec-rule.md +33 -0
- package/docs/03-openspec/tasks-rule.md +31 -0
- package/docs/04-development/coding-style.md +67 -0
- package/docs/04-development/dependency-policy.md +43 -0
- package/docs/04-development/development-guide.md +85 -0
- package/docs/04-development/directory-convention.md +105 -0
- package/docs/04-development/local-run-guide.md +55 -0
- package/docs/04-development/testing-guide.md +102 -0
- package/docs/05-domain/deployment-template.md +76 -0
- package/docs/05-domain/environment-template.md +165 -0
- package/docs/05-domain/operation-binding-template.md +55 -0
- package/docs/05-domain/operation-record-template.md +55 -0
- package/docs/05-domain/prompt-template.md +90 -0
- package/docs/05-domain/rule-template.md +73 -0
- package/docs/05-domain/service-template.md +39 -0
- package/docs/06-operations/deployment-guide.md +54 -0
- package/docs/06-operations/release.md +188 -0
- package/docs/06-operations/security-policy.md +53 -0
- package/docs/06-operations/troubleshooting.md +91 -0
- package/docs/07-templates/adr-template.md +21 -0
- package/docs/07-templates/bugfix-template.md +11 -0
- package/docs/07-templates/case-template.md +15 -0
- package/docs/07-templates/design-template.md +29 -0
- package/docs/07-templates/proposal-template.md +19 -0
- package/docs/07-templates/spec-template.md +15 -0
- package/docs/07-templates/tasks-template.md +12 -0
- package/docs/99-archive/README.md +13 -0
- package/docs/README.md +142 -0
- package/icon.svg +4 -0
- package/index.js +8 -0
- package/lib/app/app.d.ts +67 -0
- package/lib/app/app.js +494 -0
- package/lib/app/app.js.map +1 -0
- package/lib/config.d.ts +55 -0
- package/lib/config.js +42 -0
- package/lib/config.js.map +1 -0
- package/lib/domain/constants.d.ts +21 -0
- package/lib/domain/constants.js +59 -0
- package/lib/domain/constants.js.map +1 -0
- package/lib/domain/errors.d.ts +20 -0
- package/lib/domain/errors.js +22 -0
- package/lib/domain/errors.js.map +1 -0
- package/lib/domain/schemas.d.ts +28 -0
- package/lib/domain/schemas.js +445 -0
- package/lib/domain/schemas.js.map +1 -0
- package/lib/domain/types.d.ts +412 -0
- package/lib/domain/types.js +2 -0
- package/lib/domain/types.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +89 -0
- package/lib/index.js.map +1 -0
- package/lib/logger.d.ts +8 -0
- package/lib/logger.js +13 -0
- package/lib/logger.js.map +1 -0
- package/lib/memory/bridge.d.ts +18 -0
- package/lib/memory/bridge.js +13 -0
- package/lib/memory/bridge.js.map +1 -0
- package/lib/registry/integrity.d.ts +10 -0
- package/lib/registry/integrity.js +204 -0
- package/lib/registry/integrity.js.map +1 -0
- package/lib/registry/registry.d.ts +14 -0
- package/lib/registry/registry.js +182 -0
- package/lib/registry/registry.js.map +1 -0
- package/lib/resolver/merge.d.ts +23 -0
- package/lib/resolver/merge.js +90 -0
- package/lib/resolver/merge.js.map +1 -0
- package/lib/resolver/resolve.d.ts +18 -0
- package/lib/resolver/resolve.js +288 -0
- package/lib/resolver/resolve.js.map +1 -0
- package/lib/resolver/template.d.ts +10 -0
- package/lib/resolver/template.js +42 -0
- package/lib/resolver/template.js.map +1 -0
- package/lib/router/params.d.ts +3 -0
- package/lib/router/params.js +83 -0
- package/lib/router/params.js.map +1 -0
- package/lib/router/router.d.ts +11 -0
- package/lib/router/router.js +204 -0
- package/lib/router/router.js.map +1 -0
- package/lib/session/session.d.ts +16 -0
- package/lib/session/session.js +64 -0
- package/lib/session/session.js.map +1 -0
- package/lib/skill.d.ts +17 -0
- package/lib/skill.js +56 -0
- package/lib/skill.js.map +1 -0
- package/lib/storage/atomic.d.ts +10 -0
- package/lib/storage/atomic.js +107 -0
- package/lib/storage/atomic.js.map +1 -0
- package/lib/storage/paths.d.ts +10 -0
- package/lib/storage/paths.js +50 -0
- package/lib/storage/paths.js.map +1 -0
- package/lib/storage/records.d.ts +16 -0
- package/lib/storage/records.js +112 -0
- package/lib/storage/records.js.map +1 -0
- package/lib/storage/sessions.d.ts +17 -0
- package/lib/storage/sessions.js +65 -0
- package/lib/storage/sessions.js.map +1 -0
- package/lib/tools/tools.d.ts +14 -0
- package/lib/tools/tools.js +472 -0
- package/lib/tools/tools.js.map +1 -0
- package/lib/transport/http.d.ts +3 -0
- package/lib/transport/http.js +217 -0
- package/lib/transport/http.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- 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.
|