dsh-env-manager-plugin 0.1.0 → 0.1.2
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 -21
- package/README.md +274 -250
- package/client.js +2581 -2581
- package/cordis.patch.yml +4 -4
- package/docs/00-project/current-state.md +32 -6
- package/docs/00-project/glossary.md +215 -215
- package/docs/00-project/project-brief.md +114 -114
- package/docs/00-project/roadmap.md +89 -89
- package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -493
- package/docs/01-architecture/api-verification.md +416 -416
- package/docs/01-architecture/architecture-overview.md +222 -222
- package/docs/01-architecture/data-model.md +930 -930
- package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -29
- package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -25
- package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -25
- package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -31
- package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -36
- package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -30
- package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -28
- package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -31
- package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -87
- package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -87
- package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -92
- package/docs/01-architecture/design.md +666 -666
- package/docs/01-architecture/detailed-design.md +961 -961
- package/docs/01-architecture/implementation-plan.md +323 -323
- package/docs/01-architecture/module-boundaries.md +306 -306
- package/docs/01-architecture/security-model.md +227 -227
- package/docs/01-architecture/storage-contract.md +224 -224
- package/docs/01-architecture/tool-contract.md +438 -438
- package/docs/01-architecture/ui-design.md +587 -587
- package/docs/02-harness/agent-behavior.md +36 -36
- package/docs/02-harness/change-snapshot-policy.md +29 -29
- package/docs/02-harness/client-ui-integration.md +110 -110
- package/docs/02-harness/context-policy.md +43 -43
- package/docs/02-harness/harness-standard.md +91 -91
- package/docs/02-harness/task-execution-policy.md +26 -26
- package/docs/02-harness/verification-policy.md +97 -97
- package/docs/03-openspec/archive-rule.md +25 -25
- package/docs/03-openspec/bugfix-rule.md +30 -30
- package/docs/03-openspec/design-rule.md +33 -33
- package/docs/03-openspec/openspec-usage-guide.md +31 -31
- package/docs/03-openspec/proposal-rule.md +70 -70
- package/docs/03-openspec/spec-rule.md +33 -33
- package/docs/03-openspec/tasks-rule.md +31 -31
- package/docs/04-development/coding-style.md +67 -67
- package/docs/04-development/dependency-policy.md +43 -43
- package/docs/04-development/development-guide.md +85 -85
- package/docs/04-development/directory-convention.md +105 -105
- package/docs/04-development/local-run-guide.md +61 -55
- package/docs/04-development/testing-guide.md +102 -102
- package/docs/05-domain/deployment-template.md +76 -76
- package/docs/05-domain/environment-template.md +165 -165
- package/docs/05-domain/operation-binding-template.md +55 -55
- package/docs/05-domain/operation-record-template.md +55 -55
- package/docs/05-domain/prompt-template.md +90 -90
- package/docs/05-domain/rule-template.md +73 -73
- package/docs/05-domain/service-template.md +39 -39
- package/docs/06-operations/deployment-guide.md +54 -54
- package/docs/06-operations/release.md +198 -188
- package/docs/06-operations/security-policy.md +53 -53
- package/docs/06-operations/troubleshooting.md +141 -91
- package/docs/07-templates/adr-template.md +21 -21
- package/docs/07-templates/bugfix-template.md +11 -11
- package/docs/07-templates/case-template.md +15 -15
- package/docs/07-templates/design-template.md +29 -29
- package/docs/07-templates/proposal-template.md +19 -19
- package/docs/07-templates/spec-template.md +15 -15
- package/docs/07-templates/tasks-template.md +12 -12
- package/docs/99-archive/README.md +13 -13
- package/docs/README.md +142 -142
- package/icon.svg +4 -4
- package/index.js +31 -8
- package/lib/app/app.d.ts +1 -1
- package/lib/app/app.js +4 -1
- package/lib/app/app.js.map +1 -1
- package/lib/skill.js +37 -37
- package/locale/en.json +6 -6
- package/locale/zh.json +6 -6
- package/package.json +101 -99
- package/scripts/repair-profile.mjs +141 -0
|
@@ -1,666 +1,666 @@
|
|
|
1
|
-
# V1 Design
|
|
2
|
-
|
|
3
|
-
Status: V1 design, derived from
|
|
4
|
-
[DSH_Environment_Service_Memory_Skill_Brainstorm.md](./DSH_Environment_Service_Memory_Skill_Brainstorm.md)
|
|
5
|
-
and verified against the DSH runtime in [api-verification.md](./api-verification.md).
|
|
6
|
-
|
|
7
|
-
Scope of this document: **V1 = P0** from the brainstorm, plus the contracts needed to start P1.
|
|
8
|
-
|
|
9
|
-
Normative companions:
|
|
10
|
-
|
|
11
|
-
- [detailed-design.md](./detailed-design.md) — module contracts, algorithms, tool and HTTP specs
|
|
12
|
-
- [data-model.md](./data-model.md) — record schemas and result types
|
|
13
|
-
- [tool-contract.md](./tool-contract.md) — model-facing tool surface
|
|
14
|
-
- [storage-contract.md](./storage-contract.md) — persistence rules
|
|
15
|
-
- [module-boundaries.md](./module-boundaries.md) — module ownership
|
|
16
|
-
- [security-model.md](./security-model.md) — credential, policy and redaction rules
|
|
17
|
-
- [ui-design.md](./ui-design.md) — Client panels and transport
|
|
18
|
-
- [implementation-plan.md](./implementation-plan.md) — ordered tasks
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## 1. Goals and non-goals
|
|
23
|
-
|
|
24
|
-
### 1.1 V1 goal
|
|
25
|
-
|
|
26
|
-
Give every DSH session a shared, queryable environment context, and make the path from "agent knows the environment and service" to "the right Skill with the right parameters" explicit, deterministic and auditable.
|
|
27
|
-
|
|
28
|
-
### 1.2 V1 delivers
|
|
29
|
-
|
|
30
|
-
1. A DSH bundle with a Host half and a Client half.
|
|
31
|
-
2. File-based registry storage under the plugin data directory.
|
|
32
|
-
3. Manual management of environments, services, deployment bindings, rule profiles and operation bindings.
|
|
33
|
-
4. Layered rule resolution with provenance and validation.
|
|
34
|
-
5. Deterministic operation-to-Skill routing with conflict detection, availability checks and approval policy evaluation.
|
|
35
|
-
6. Read-only Agent tools usable from any session in the profile.
|
|
36
|
-
7. Session-scoped default environment.
|
|
37
|
-
8. A minimal Web/Desktop configuration UI; file editing remains a supported baseline.
|
|
38
|
-
|
|
39
|
-
### 1.3 V1 does not deliver
|
|
40
|
-
|
|
41
|
-
- SSH/SFTP, Docker, Kubernetes or browser execution adapters.
|
|
42
|
-
- Automatic deployment, rollback or self-healing.
|
|
43
|
-
- Automatic extraction of service facts from Git, Maven, Dockerfile or Helm.
|
|
44
|
-
- Automatic rule or Skill modification without review.
|
|
45
|
-
- A persistent memory store of its own.
|
|
46
|
-
- Guarantees that a derived path exists. Only an online check may mark a value verified.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 2. Design principles
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
Registry files are the source of truth.
|
|
54
|
-
Derived values are labelled as derived.
|
|
55
|
-
A Skill reference is discovery, not execution authorization.
|
|
56
|
-
Credentials are references; values never enter the registry or tool output.
|
|
57
|
-
Conflicts fail loudly; nothing is chosen silently.
|
|
58
|
-
Facts, derived values and verified observations are different things.
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
These principles are expanded into module rules in
|
|
62
|
-
[module-boundaries.md](./module-boundaries.md) and hard constraints in
|
|
63
|
-
[AGENTS.md](../../AGENTS.md) §3.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## 3. Architecture
|
|
68
|
-
|
|
69
|
-
```text
|
|
70
|
-
DSH Web / Desktop
|
|
71
|
-
└── Client half (configuration panels, context preview)
|
|
72
|
-
│ POST /env-manager/api
|
|
73
|
-
▼
|
|
74
|
-
DSH Host process
|
|
75
|
-
├── transport header/origin checks, op dispatch
|
|
76
|
-
├── tools thin request/response layer
|
|
77
|
-
├── app shared use cases
|
|
78
|
-
├── storage atomic YAML read/write
|
|
79
|
-
├── registry validated per-request snapshot of storage
|
|
80
|
-
├── resolver pure context + rule resolution
|
|
81
|
-
├── router pure operation→Skill matching
|
|
82
|
-
├── session per-session default environment
|
|
83
|
-
└── memory-bridge optional P1 contract
|
|
84
|
-
│
|
|
85
|
-
▼
|
|
86
|
-
Plugin data directory
|
|
87
|
-
├── environments/ services/ deployments/ rules/ operations/
|
|
88
|
-
└── sessions/
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### 3.1 Host and Client split
|
|
92
|
-
|
|
93
|
-
- **Host** owns all business logic, validation and persistence.
|
|
94
|
-
- **Client** renders and calls the plugin HTTP API; it never reads registry files directly and never re-implements resolution.
|
|
95
|
-
- The Client plugin remains a plain JavaScript module in V1. The Host is TypeScript compiled to ESM (see [ADR-0004](./decisions/ADR-0004-typescript-host-js-client.md)).
|
|
96
|
-
|
|
97
|
-
### 3.2 Host service dependencies
|
|
98
|
-
|
|
99
|
-
These DSH services are verified in [api-verification.md](./api-verification.md):
|
|
100
|
-
|
|
101
|
-
| Service | Use |
|
|
102
|
-
|---|---|
|
|
103
|
-
| `ctx.tools` | register Agent tools (F2–F5) |
|
|
104
|
-
| `ctx.credentials` | `describe(ref)` for status only; `resolve` is never called (F8) |
|
|
105
|
-
| `ctx.approval` | request confirmation for management writes that affect production (F7) |
|
|
106
|
-
| `ctx.skills` | `snapshot()` for Skill availability; `register()` for the bundled discovery Skill (F20–F21) |
|
|
107
|
-
| `ctx.webServer` | register the plugin HTTP route for the Client (F12) |
|
|
108
|
-
| `profileContext` | `dir` for the default data directory (F10) |
|
|
109
|
-
| Node `fs` | atomic writes and deletes under `dataDir`; `ctx.fs` lacks mkdir/unlink (F9) |
|
|
110
|
-
|
|
111
|
-
Skill availability degrades to `available: 'unknown'` with `SKILL_REGISTRY_UNAVAILABLE`
|
|
112
|
-
when the catalog is absent or incomplete, rather than silently passing.
|
|
113
|
-
|
|
114
|
-
### 3.3 Bundled discovery Skill
|
|
115
|
-
|
|
116
|
-
The Host registers one runtime Skill named `dsh-env-manager` through
|
|
117
|
-
`ctx.skills.register()`. It is **not** an execution Skill and **not** an operation
|
|
118
|
-
binding:
|
|
119
|
-
|
|
120
|
-
- The registration lives in the plugin's unscoped `apply()` context, so it enters the
|
|
121
|
-
global Skill layer and is visible to every workspace in the same DSH profile. No
|
|
122
|
-
workspace `SKILL.md` is required.
|
|
123
|
-
- Its catalog entry is the discovery surface: name, description and `whenToUse`. The full
|
|
124
|
-
workflow body is loaded only when the model or user invokes the Skill.
|
|
125
|
-
- The workflow walks `env_find` → `env_set_context` → `env_resolve` →
|
|
126
|
-
`env_resolve_operation`; it does not execute anything.
|
|
127
|
-
- The registration is disposed with the plugin fiber, so disabling or uninstalling the
|
|
128
|
-
plugin also removes the Skill.
|
|
129
|
-
- `config.registerSkill` (default `true`) can suppress the entry for installations that
|
|
130
|
-
do not want it.
|
|
131
|
-
- The plugin does not inject a global system-prompt section. Discovery stays in the
|
|
132
|
-
Skill catalog to avoid imposing environment-manager instructions on unrelated
|
|
133
|
-
sessions.
|
|
134
|
-
- `ctx.skills` may be unavailable in a minimal Host; the plugin keeps its tools and panel
|
|
135
|
-
and simply skips Skill registration.
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## 4. Domain model
|
|
140
|
-
|
|
141
|
-
See [data-model.md](./data-model.md).
|
|
142
|
-
|
|
143
|
-
Key points that shape the design:
|
|
144
|
-
|
|
145
|
-
- **Co-location** is expressed with one Host and several roles, not several hosts.
|
|
146
|
-
- **Node kind and command access are machine properties**, not planes: `Host.kind`,
|
|
147
|
-
`Host.access` and `Host.clusterRef` record whether a machine is a VM, physical, K8s
|
|
148
|
-
all-in-one, K8s worker or Pod host and whether it exposes SSH, `kubectl` or `docker`.
|
|
149
|
-
Pods are workloads in deployment bindings, not Host records.
|
|
150
|
-
- **Deployment bindings are many**: a service may run several times in one environment, so deployment ids are first-class.
|
|
151
|
-
- **Rule profiles are shared**; environment and deployment carry field-level overrides.
|
|
152
|
-
- **Operation bindings are predicates**, not a fixed matrix of every combination.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## 5. Storage
|
|
157
|
-
|
|
158
|
-
Full rules in [storage-contract.md](./storage-contract.md). Summary:
|
|
159
|
-
|
|
160
|
-
```text
|
|
161
|
-
<dataDir>/
|
|
162
|
-
├── environments/{id}.yaml
|
|
163
|
-
├── services/{id}.yaml
|
|
164
|
-
├── deployments/{id}.yaml
|
|
165
|
-
├── rules/{id}.yaml
|
|
166
|
-
├── operations/{id}.yaml
|
|
167
|
-
├── sessions/{sessionId}.json
|
|
168
|
-
└── .backups/{kind}/{id}/{timestamp}.yaml
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
- `<dataDir>` defaults to `<profile>/data/dsh-env-manager` and is configurable.
|
|
172
|
-
- Reads and writes go through the storage module; no other module touches files.
|
|
173
|
-
- Writes are atomic (temp file + fsync + rename) with a rolling backup.
|
|
174
|
-
- Invalid or unsupported records are skipped with a diagnostic, never deleted.
|
|
175
|
-
- V1 has no index files. A snapshot is loaded per application request and reloaded after a write.
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## 6. Rule resolution
|
|
180
|
-
|
|
181
|
-
### 6.1 Template language
|
|
182
|
-
|
|
183
|
-
Only interpolation is supported:
|
|
184
|
-
|
|
185
|
-
```text
|
|
186
|
-
${name}
|
|
187
|
-
${name.path}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Rules:
|
|
191
|
-
|
|
192
|
-
- Identifier segments: `[A-Za-z_][A-Za-z0-9_]*`, separated by `.`.
|
|
193
|
-
- No function calls, operators, conditionals, environment variables or command substitution.
|
|
194
|
-
- No escaping. A lone `$` is literal; a malformed `${…}` (missing `}` or invalid
|
|
195
|
-
identifier) is `RULE_TEMPLATE_INVALID`.
|
|
196
|
-
- Interpolation is allowed only in string-valued fields (`paths`, `logSources`, `classLocations`, binding parameters).
|
|
197
|
-
|
|
198
|
-
### 6.2 Available namespaces
|
|
199
|
-
|
|
200
|
-
```text
|
|
201
|
-
env.id, env.name, env.category
|
|
202
|
-
service.id, service.name, service.category
|
|
203
|
-
service.artifact.name
|
|
204
|
-
deployment.id, deployment.name, deployment.runtimeMode
|
|
205
|
-
deployment.location.* (runtime-specific: see data-model §5)
|
|
206
|
-
<user variables from rule profiles and overrides>
|
|
207
|
-
paths.* (resolved path fields)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
`env`, `service` and `deployment` are read-only. `paths` is itself resolved, so a path may reference another path.
|
|
211
|
-
|
|
212
|
-
### 6.3 Layers and precedence
|
|
213
|
-
|
|
214
|
-
From lowest to highest:
|
|
215
|
-
|
|
216
|
-
1. Built-in global defaults (empty in V1).
|
|
217
|
-
2. Service `ruleProfileRefs`, in list order; later profiles win on field conflicts.
|
|
218
|
-
3. Environment `ruleOverrides`.
|
|
219
|
-
4. Deployment `variables` (variables only), then deployment `ruleOverrides` (all field kinds; wins over `variables` for the same variable).
|
|
220
|
-
|
|
221
|
-
Merging is **field-level**, not whole-object replacement: overriding `paths.logDir` leaves `paths.jar` intact.
|
|
222
|
-
|
|
223
|
-
### 6.4 Resolution algorithm
|
|
224
|
-
|
|
225
|
-
```text
|
|
226
|
-
1. Collect ordered layers.
|
|
227
|
-
2. Merge variables, paths, logSources[per runtime], classLocations[per runtime]
|
|
228
|
-
into a raw map, recording the winning source and the shadowed sources per field.
|
|
229
|
-
3. Build a dependency graph from ${...} references in raw string values.
|
|
230
|
-
4. Detect cycles (DFS with an active stack) and report RULE_CYCLE with the cycle path.
|
|
231
|
-
5. Reject references to unknown fields with RULE_UNDEFINED_VARIABLE.
|
|
232
|
-
6. Resolve in dependency order; each substitution is recorded as a provenance input.
|
|
233
|
-
7. Validate resolved paths: reject any `..` segment (RULE_PATH_TRAVERSAL).
|
|
234
|
-
8. Select the log source and class location for the deployment's runtimeMode.
|
|
235
|
-
9. Emit provenance for every field that was requested by the caller.
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Resolution is pure: same inputs, same outputs, no I/O.
|
|
239
|
-
|
|
240
|
-
### 6.5 Which fields are resolved eagerly
|
|
241
|
-
|
|
242
|
-
`env_resolve` returns only the fields used by the runtime:
|
|
243
|
-
|
|
244
|
-
- all `paths` fields defined by any layer (bounded, typically under 20),
|
|
245
|
-
- the `logSources[runtimeMode]` entry,
|
|
246
|
-
- the `classLocations[runtimeMode]` entry,
|
|
247
|
-
- merged `variables`.
|
|
248
|
-
|
|
249
|
-
Only defined fields are evaluated; the resolver never guesses a missing path. A defined
|
|
250
|
-
template that references a name no layer defines is still `RULE_UNDEFINED_VARIABLE`.
|
|
251
|
-
|
|
252
|
-
### 6.6 Provenance semantics
|
|
253
|
-
|
|
254
|
-
For each returned field, the response includes:
|
|
255
|
-
|
|
256
|
-
- `definedBy` — the winning layer and, when applicable, the rule profile id.
|
|
257
|
-
- `inputs` — every referenced field with the layer that supplied it.
|
|
258
|
-
- `shadowed` — lower-priority definitions that were overridden.
|
|
259
|
-
|
|
260
|
-
This makes AC-04 explainable: overriding `paths.logDir` at the environment layer changes `paths.applicationLog` while its `definedBy` remains the service rule profile. Both facts are visible.
|
|
261
|
-
|
|
262
|
-
### 6.7 Log sources
|
|
263
|
-
|
|
264
|
-
Log sources are structured, not collapsed into a file path:
|
|
265
|
-
|
|
266
|
-
| Runtime | Source kind | Example fields |
|
|
267
|
-
|---|---|---|
|
|
268
|
-
| native | `file` | `path` |
|
|
269
|
-
| docker | `container-stdout` | `container` |
|
|
270
|
-
| kubernetes | `pod-logs` | `namespace`, `workload` |
|
|
271
|
-
|
|
272
|
-
### 6.8 Class locations
|
|
273
|
-
|
|
274
|
-
Three distinct kinds are preserved:
|
|
275
|
-
|
|
276
|
-
- `build-output` — e.g. `target/classes`
|
|
277
|
-
- `deployed-dir` — an exploded class directory on the host
|
|
278
|
-
- `jar-entry` — a jar path plus the class entry prefix
|
|
279
|
-
|
|
280
|
-
### 6.9 Worked example
|
|
281
|
-
|
|
282
|
-
Service `aicoservice` references `java-common`; environment `test-a` overrides `paths.logDir`; deployment `deploy-001` is Kubernetes.
|
|
283
|
-
|
|
284
|
-
```text
|
|
285
|
-
paths.serviceHome /opt/company/services/aicoservice service/java-common
|
|
286
|
-
paths.jar /opt/company/services/aicoservice/lib/aicoservice.jar
|
|
287
|
-
(input: serviceHome)
|
|
288
|
-
paths.logDir /data/logs/aicoservice environment (shadowed service)
|
|
289
|
-
paths.applicationLog /data/logs/aicoservice/aicoservice.log service/java-common
|
|
290
|
-
(input: logDir → environment)
|
|
291
|
-
logSource pod-logs dv-sop / aicoservice service/java-common
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
The `applicationLog` value reflects the environment override even though its template was defined at the service layer.
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## 7. Operation Skill routing
|
|
299
|
-
|
|
300
|
-
### 7.1 Match levels
|
|
301
|
-
|
|
302
|
-
Bindings are grouped by how specific their `match` predicate is:
|
|
303
|
-
|
|
304
|
-
| Level | Predicate | Priority |
|
|
305
|
-
|---|---|---|
|
|
306
|
-
| `deployment` | `match.deploymentId` set | 1 (highest) |
|
|
307
|
-
| `environment-service` | `match.environmentId` + `match.serviceId` | 2 |
|
|
308
|
-
| `service` | `match.serviceId` only | 3 |
|
|
309
|
-
| `category-mode` | `match.serviceCategory` + `match.deploymentMode` | 4 |
|
|
310
|
-
| `global` | empty match object | 5 (lowest) |
|
|
311
|
-
|
|
312
|
-
A predicate is eligible only if **every** field it declares matches the resolved context. `deploymentMode` compares against `runtimeMode`; `serviceCategory` compares against the service category.
|
|
313
|
-
|
|
314
|
-
A `match` that is not one of these five shapes (for example `environmentId` alone) is rejected at record validation; it never reaches routing.
|
|
315
|
-
|
|
316
|
-
### 7.2 Conflict rule
|
|
317
|
-
|
|
318
|
-
If more than one enabled binding is eligible at the highest level, the result is `BINDING_CONFLICT` listing every candidate id, level and skill name. No binding is selected.
|
|
319
|
-
|
|
320
|
-
If exactly one binding is eligible at the highest level, lower levels are ignored entirely, even if they also match.
|
|
321
|
-
|
|
322
|
-
### 7.3 Skill availability
|
|
323
|
-
|
|
324
|
-
Checks, in order, against `ctx.skills.snapshot({ scope, cwd, signal })`:
|
|
325
|
-
|
|
326
|
-
1. The catalog is complete; an incomplete snapshot means the check is inconclusive.
|
|
327
|
-
2. The Skill name is registered.
|
|
328
|
-
3. If `expectedSource` is set, the registered `source` matches.
|
|
329
|
-
4. If `expectedProvider` is set, the registered `provider` matches.
|
|
330
|
-
|
|
331
|
-
Outcomes:
|
|
332
|
-
|
|
333
|
-
- pass → `available: true`
|
|
334
|
-
- fail → `SKILL_UNAVAILABLE`, `ok: false`
|
|
335
|
-
- registry absent or incomplete → `available: 'unknown'` plus `SKILL_REGISTRY_UNAVAILABLE` warning, `ok: true` (routing still useful) but the caller is told the check was incomplete
|
|
336
|
-
|
|
337
|
-
Digest pinning is not supported: the DSH catalog exposes no digest. The router never
|
|
338
|
-
substitutes a different Skill.
|
|
339
|
-
|
|
340
|
-
### 7.4 Approval policy
|
|
341
|
-
|
|
342
|
-
Evaluation order:
|
|
343
|
-
|
|
344
|
-
1. Write operation and `policy.requiresApproval !== false` → `requiresApproval: true`.
|
|
345
|
-
2. `environmentCategory === 'production'` and `policy.allowProduction !== true` → `POLICY_DENIED`.
|
|
346
|
-
3. Otherwise the policy is returned as declared.
|
|
347
|
-
|
|
348
|
-
V1 does not execute the operation. The resolved policy is returned to the caller; enforcement happens in the executing Skill or in the future executor.
|
|
349
|
-
|
|
350
|
-
### 7.5 Parameter mapping
|
|
351
|
-
|
|
352
|
-
- Binding `parameters` values are templates resolved against the same namespaces as rules, plus `paths.*`.
|
|
353
|
-
- Result values must be `string`, `number` or `boolean`; numbers and booleans are stringified.
|
|
354
|
-
- Any unresolved reference or non-scalar value fails with `PARAMETER_RESOLUTION_FAILED`.
|
|
355
|
-
- Parameters are not a command line. They are structured inputs for the target Skill.
|
|
356
|
-
|
|
357
|
-
### 7.6 Worked example
|
|
358
|
-
|
|
359
|
-
```text
|
|
360
|
-
operation: redeploy
|
|
361
|
-
context: test-a / aicoservice / deploy-001 (kubernetes)
|
|
362
|
-
|
|
363
|
-
eligible:
|
|
364
|
-
binding-001 level=environment-service skill=redeploy-k8s-service
|
|
365
|
-
selected: binding-001
|
|
366
|
-
parameters: namespace=dv-sop workload=aicoservice cluster=k8s-01
|
|
367
|
-
policy: requiresApproval=true allowProduction=false
|
|
368
|
-
skill: available=true
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
If `redeploy-k8s-service` is missing, the result is an error with `SKILL_UNAVAILABLE`; it does not fall through to `binding-002`.
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
## 8. Session context
|
|
376
|
-
|
|
377
|
-
- `env_set_context` writes `sessions/{sessionId}.json` with `defaultEnvironmentId`.
|
|
378
|
-
- Passing `null` clears the default.
|
|
379
|
-
- `env_get_context` returns the current session's default, if any.
|
|
380
|
-
- DSH exposes `sessionProjections`, but it requires declared session event types and a
|
|
381
|
-
closed projection schema, so a standalone bundle cannot use it. V1 writes
|
|
382
|
-
`sessions/{sessionId}.json`; a projection-based store is a later refinement
|
|
383
|
-
(detailed-design §9).
|
|
384
|
-
- Session context is isolated: it cannot modify registry files or another session's default.
|
|
385
|
-
|
|
386
|
-
Tools that need an environment accept an explicit `environmentId`. When omitted, they fall back to the session default and otherwise return a validation error naming both options.
|
|
387
|
-
|
|
388
|
-
---
|
|
389
|
-
|
|
390
|
-
## 9. Tool surface
|
|
391
|
-
|
|
392
|
-
Full schemas in [tool-contract.md](./tool-contract.md); the `defineTool` definitions
|
|
393
|
-
are specified in [detailed-design.md](./detailed-design.md) §11.
|
|
394
|
-
|
|
395
|
-
Every tool is registered with `defineTool`:
|
|
396
|
-
|
|
397
|
-
```ts
|
|
398
|
-
ctx.tools.register(defineTool({
|
|
399
|
-
name: 'env_list',
|
|
400
|
-
description: '...',
|
|
401
|
-
parameters: { kind: { type: 'string', enum: [...] } },
|
|
402
|
-
output: { schema: { ... }, render: (args, value) => [{ type: 'text', text: '...' }] },
|
|
403
|
-
async execute(args, exec) { /* returns the declared JSON value */ },
|
|
404
|
-
}))
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
Read tools:
|
|
408
|
-
|
|
409
|
-
```text
|
|
410
|
-
env_list, env_get, env_find, env_resolve, env_resolve_operation,
|
|
411
|
-
env_get_context, env_set_context
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
Management tools (state-changing):
|
|
415
|
-
|
|
416
|
-
```text
|
|
417
|
-
env_save, env_delete, env_validate
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
Results:
|
|
421
|
-
|
|
422
|
-
- Success returns the declared JSON value, validated by DSH against
|
|
423
|
-
`output.schema`.
|
|
424
|
-
- Expected failures throw `EnvManagerError extends HarnessError`, which carries a
|
|
425
|
-
stable `code` (`ENV_NOT_FOUND`, `BINDING_CONFLICT`, `POLICY_DENIED`, …) and a
|
|
426
|
-
message safe to show. There is no `{ ok: false }` value.
|
|
427
|
-
- `env_delete` and production `env_save` ask `ctx.approval`; any outcome other than
|
|
428
|
-
`allowed-once` denies.
|
|
429
|
-
- Errors never include credential values, file contents or stack traces.
|
|
430
|
-
|
|
431
|
-
---
|
|
432
|
-
|
|
433
|
-
## 10. Client UI
|
|
434
|
-
|
|
435
|
-
### 10.1 V1 baseline
|
|
436
|
-
|
|
437
|
-
Configuration is possible without the UI:
|
|
438
|
-
|
|
439
|
-
- edit YAML files under `dataDir` directly;
|
|
440
|
-
- call `env_validate` to check the registry;
|
|
441
|
-
- call `env_resolve` / `env_resolve_operation` to inspect behavior.
|
|
442
|
-
|
|
443
|
-
### 10.2 V1 UI
|
|
444
|
-
|
|
445
|
-
A dedicated Environment Manager surface in the DSH Web/Desktop client, registered as a
|
|
446
|
-
`sidebar.panellist` entry (id `env-manager`) plus the `main` panel keyed
|
|
447
|
-
`env-manager`:
|
|
448
|
-
|
|
449
|
-
| Section | Content |
|
|
450
|
-
|---|---|
|
|
451
|
-
| Environments (default) | list; detail split into overview, all nodes, management/business/database/expansion plane views, login records, Kubernetes/clusters, endpoints and YAML; shell-only New environment |
|
|
452
|
-
| Overview | counts, dataDir, warnings, validate |
|
|
453
|
-
| Records | Services, Deployments, Rule profiles and Operation bindings as YAML editors |
|
|
454
|
-
| Context preview | pick environment + service (+ deployment) and inspect the Service Context |
|
|
455
|
-
| Operation preview | operation → selected binding, skill availability, policy, parameters |
|
|
456
|
-
|
|
457
|
-
Rules for the UI:
|
|
458
|
-
|
|
459
|
-
- All reads/writes go through the plugin's same-origin HTTP API
|
|
460
|
-
(`POST /env-manager/api`, ADR-0009); no direct file access and no second business
|
|
461
|
-
path.
|
|
462
|
-
- The UI cannot read or write session context in V1; that stays with the agent tools.
|
|
463
|
-
- `New environment` creates only the environment shell and the four standard role
|
|
464
|
-
definitions; nodes are added inside the relevant plane. Plane tabs are filters over the
|
|
465
|
-
same `hosts[]`, never separate records. Environment detail and YAML are editable.
|
|
466
|
-
- A node also carries a machine kind (`vm`, `physical`, `k8s-all-in-one`, `k8s-worker`,
|
|
467
|
-
`pod-host`) and its command surfaces (`ssh`, `kubectl`, `docker`). Kubernetes and Pod-host
|
|
468
|
-
nodes reference a cluster. Individual Pods are not modeled as hosts; `pod-host` is the
|
|
469
|
-
machine reached with `docker exec`. The detail page has a Kubernetes/clusters tab for
|
|
470
|
-
cluster metadata. See ADR-0011.
|
|
471
|
-
- Service, deployment, rule and binding records are edited as YAML, validated
|
|
472
|
-
server-side before writing.
|
|
473
|
-
- Text is routed through the client locale service; `en` and `zh` provided.
|
|
474
|
-
- The UI must show provenance for derived values, not just the final string.
|
|
475
|
-
- Production-category destructive actions are visually distinct and require
|
|
476
|
-
confirmation.
|
|
477
|
-
|
|
478
|
-
Details: [ui-design.md](./ui-design.md). The implemented `client.js` registers the
|
|
479
|
-
sidebar entry and the `main` panel; the panel is the configuration surface described
|
|
480
|
-
above.
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
## 11. Memory bridge (P1 boundary)
|
|
485
|
-
|
|
486
|
-
V1 defines the contract and ships a null adapter:
|
|
487
|
-
|
|
488
|
-
```ts
|
|
489
|
-
interface MemoryBridge {
|
|
490
|
-
emitOperationRecord(record: OperationRecord): Promise<void>;
|
|
491
|
-
queryMemory(scope: MemoryScope): Promise<MemoryQueryResult[]>;
|
|
492
|
-
proposeChange(proposal: RuleOrSkillProposal): Promise<{ id: string }>;
|
|
493
|
-
}
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
- Null adapter returns `MEMORY_BRIDGE_UNAVAILABLE` as a warning; it never throws into a tool.
|
|
497
|
-
- `EvidenceRef` values are opaque strings, sanitized before emission.
|
|
498
|
-
- The bridge never writes registry files and never applies proposals.
|
|
499
|
-
- P1 wires the adapter to the memory plugin's public contract once that contract is verified.
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
## 12. Security
|
|
504
|
-
|
|
505
|
-
Summary; details in [security-model.md](./security-model.md).
|
|
506
|
-
|
|
507
|
-
- `credentialRef` only in registry records; status via `ctx.credentials.describe`, never
|
|
508
|
-
`resolve`. The add/edit node form may call `ctx.credentials.set` once with an optional
|
|
509
|
-
login password and never persists or returns the value; shell creation and batch import
|
|
510
|
-
collect no password.
|
|
511
|
-
- Production write operations denied unless `allowProduction: true`.
|
|
512
|
-
- Tool management writes affecting production records require `ctx.approval`.
|
|
513
|
-
- UI writes run as actor `'ui'` and are denied for production unless
|
|
514
|
-
`writePolicy.allowUiProductionWrites` is enabled.
|
|
515
|
-
- The `/env-manager/api` route requires a custom header and a same-origin `Origin`;
|
|
516
|
-
no CORS headers are sent.
|
|
517
|
-
- Rule resolution rejects cycles, undefined variables and path traversal.
|
|
518
|
-
- Diagnostics and logs must be redacted before emission.
|
|
519
|
-
- Fail closed on conflicts, missing Skills and broken references.
|
|
520
|
-
|
|
521
|
-
---
|
|
522
|
-
|
|
523
|
-
## 13. Error handling
|
|
524
|
-
|
|
525
|
-
- Tool failures throw `EnvManagerError extends HarnessError`; DSH records the stable
|
|
526
|
-
`{ name, code }` and shows the message. A bare string is never thrown.
|
|
527
|
-
- HTTP failures return `{ ok: false, error: { code, message, details? } }` with a
|
|
528
|
-
status from [detailed-design.md](./detailed-design.md) §12.2.
|
|
529
|
-
- Registry load failures produce diagnostics and keep serving the records that are valid.
|
|
530
|
-
- Resolution failures name the field and the offending variable.
|
|
531
|
-
- Router failures list candidate bindings.
|
|
532
|
-
- All error paths are covered by tests listed in
|
|
533
|
-
[verification-policy.md](../02-harness/verification-policy.md).
|
|
534
|
-
|
|
535
|
-
---
|
|
536
|
-
|
|
537
|
-
## 14. Acceptance criteria
|
|
538
|
-
|
|
539
|
-
| ID | Scenario | V1 | Verification |
|
|
540
|
-
|---|---|---|---|
|
|
541
|
-
| AC-01 | One host carries management + business roles with no duplicate host entry | yes | `env_save` validation test |
|
|
542
|
-
| AC-02 | K8s cluster/workload and Docker instance are queryable through deployment bindings | yes | resolver test |
|
|
543
|
-
| AC-03 | Service with Git URL and artifact is visible to a second session | yes | registry reload test |
|
|
544
|
-
| AC-04 | Java common rules plus environment override compute paths with provenance | yes | resolver + provenance test |
|
|
545
|
-
| AC-05 | `env_resolve_operation` returns the bound Skill and mapped parameters for test-a redeploy | yes | router test |
|
|
546
|
-
| AC-06 | Two same-level bindings produce a conflict error | yes | router conflict test |
|
|
547
|
-
| AC-07 | Two sessions share data but keep different defaults | yes | session store test |
|
|
548
|
-
| AC-08 | Credential query returns ref, configured and source only | yes | tool output test |
|
|
549
|
-
| AC-09 | A completed log check stores a sanitized summary and evidence refs | P1 | memory bridge contract test |
|
|
550
|
-
| AC-10 | A rule change derived from experience becomes a pending proposal | P1 | proposal queue test |
|
|
551
|
-
| AC-11 | Environment detail shows host accounts with protocol/auth/last-login source, without secret values | yes | app test + UI checklist |
|
|
552
|
-
| AC-12 | Login records are stored in the environment YAML, reference existing hosts/accounts and render newest-first | yes | app integrity test + UI checklist |
|
|
553
|
-
| AC-13 | Environments is the default tab, `New environment` is reachable from the header, and Records excludes environments | yes | UI checklist |
|
|
554
|
-
| AC-14 | `New environment` creates only the shell with four standard roles, and a plane tab filters the same `hosts[]` without copying nodes | yes | app schema test + UI checklist |
|
|
555
|
-
| AC-15 | A node can carry multiple roles, is editable, is batch-importable without a password, and is searchable by id/name/IP/role/kind/access/cluster/account | yes | UI checklist + live verification |
|
|
556
|
-
| AC-16 | A host records machine kind and command access; K8s kinds require an existing cluster; a Pod host is one machine, not one entry per Pod | yes | schema test + UI checklist + live verification |
|
|
557
|
-
| AC-17 | Cluster metadata includes optional topology and a node count derived from `clusterRef`; cluster create/edit round-trips in the panel | yes | app test + UI checklist |
|
|
558
|
-
| AC-18 | A host account can store a web login URL and username; a password is written to the credential service and only `credentialRef` remains; the account can be edited and searched by URL | yes | app test + UI checklist + live verification |
|
|
559
|
-
|
|
560
|
-
---
|
|
561
|
-
|
|
562
|
-
## 15. Build and packaging
|
|
563
|
-
|
|
564
|
-
### 15.1 Host
|
|
565
|
-
|
|
566
|
-
- TypeScript sources in `src/`.
|
|
567
|
-
- `tsconfig.json`: `target ES2022`, `module NodeNext`, `strict`, `outDir lib`.
|
|
568
|
-
- `npm run build` → `tsc -p tsconfig.json`.
|
|
569
|
-
- `package.json` exports `.` → `./index.js`; `index.js` is a shim that re-exports `./lib/index.js`.
|
|
570
|
-
|
|
571
|
-
### 15.2 Client
|
|
572
|
-
|
|
573
|
-
- Plain JavaScript `client.js` at the repository root, in the DSH module-loader format.
|
|
574
|
-
- No build step in V1; no JSX. Components use `React.createElement`.
|
|
575
|
-
- If the UI later requires TypeScript or JSX, a client bundling step is added with its own ADR.
|
|
576
|
-
|
|
577
|
-
### 15.3 Bundle layout
|
|
578
|
-
|
|
579
|
-
```text
|
|
580
|
-
dsh-env-manager/
|
|
581
|
-
├── index.js # host entry shim: export * from './lib/index.js'
|
|
582
|
-
├── client.js # client module
|
|
583
|
-
├── cordis.patch.yml
|
|
584
|
-
├── package.json
|
|
585
|
-
├── tsconfig.json
|
|
586
|
-
├── src/ # TypeScript host sources
|
|
587
|
-
├── lib/ # build output (git-ignored)
|
|
588
|
-
├── locale/
|
|
589
|
-
├── icon.svg
|
|
590
|
-
├── docs/
|
|
591
|
-
└── tests/
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
`files` in `package.json` includes `index.js`, `lib/`, `client.js`, `cordis.patch.yml`, `locale/`, `icon.svg`, `README.md`, `LICENSE`.
|
|
595
|
-
|
|
596
|
-
Manifest contract:
|
|
597
|
-
|
|
598
|
-
- `dsh.bundle.patch: ./cordis.patch.yml` registers the Host plugin.
|
|
599
|
-
- `dsh.client: { platform: 'web', immediately: true }` loads the Client half in Web and
|
|
600
|
-
Desktop. It declares no `inject` dependency on a conversation UI package, because the
|
|
601
|
-
panel lives in the shell's sidebar/main slots, not the composer dock.
|
|
602
|
-
|
|
603
|
-
### 15.4 Dependencies
|
|
604
|
-
|
|
605
|
-
| Package | Purpose | Scope |
|
|
606
|
-
|---|---|---|
|
|
607
|
-
| `zod` | record validation | runtime |
|
|
608
|
-
| `yaml` | YAML parse/serialize | runtime |
|
|
609
|
-
| `@deepseek-ai/schemastery` | `Config` schema | runtime |
|
|
610
|
-
| `@deepseek-ai/dsh-tools` | `defineTool`, tool types | peer |
|
|
611
|
-
| `@deepseek-ai/dsh-llm` | `HarnessError` | peer |
|
|
612
|
-
| `@deepseek-ai/cordis` | `Context` type (type-only) | peer, provided by the Host |
|
|
613
|
-
| `typescript` | build | dev |
|
|
614
|
-
| `vitest` | tests | dev |
|
|
615
|
-
|
|
616
|
-
Peer packages are provided by the DSH profile; the plugin must not bundle them.
|
|
617
|
-
Version alignment is pinned to the runtime release (`0.2.1-rc.5` at design time) and
|
|
618
|
-
checked during T01. Adding any other dependency follows
|
|
619
|
-
`docs/04-development/dependency-policy.md`.
|
|
620
|
-
|
|
621
|
-
---
|
|
622
|
-
|
|
623
|
-
## 16. Open questions
|
|
624
|
-
|
|
625
|
-
The eight integration questions from the first draft are resolved in
|
|
626
|
-
[api-verification.md](./api-verification.md):
|
|
627
|
-
|
|
628
|
-
| # | Question | Resolution |
|
|
629
|
-
|---|---|---|
|
|
630
|
-
| 1 | Plugin data directory | `profileContext.dir` → `<dir>/data/dsh-env-manager` (F10) |
|
|
631
|
-
| 2 | `ctx.fs` for data paths | unusable for create/delete; use Node fs under `dataDir` (F9) |
|
|
632
|
-
| 3 | Skill registry API | `ctx.skills.snapshot`; source/provider checks, no digest (F20) |
|
|
633
|
-
| 4 | Durable session metadata | V1 file store; projection is a later refinement (F11) |
|
|
634
|
-
| 5 | Client slot for a panel | `sidebar.panellist` + `main` keyed `env-manager` (F15) |
|
|
635
|
-
| 6 | Client→Host calls | plugin-owned HTTP route, ADR-0009 (F14) |
|
|
636
|
-
| 7 | Memory plugin contract | still owned by the memory plugin; null adapter until P1 |
|
|
637
|
-
| 8 | `credentials.describe` coverage | presence/source/writability, no value (F8) |
|
|
638
|
-
|
|
639
|
-
Remaining, none blocking V1:
|
|
640
|
-
|
|
641
|
-
- The memory plugin's public contract (P1).
|
|
642
|
-
- Cross-process write locking for a shared `dataDir`.
|
|
643
|
-
- A session-event projection instead of the session file.
|
|
644
|
-
- Client bundling if the UI outgrows a single plain-JS module.
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
## 17. Traceability to the brainstorm
|
|
649
|
-
|
|
650
|
-
| Brainstorm | Design section |
|
|
651
|
-
|---|---|
|
|
652
|
-
| §3.1 environment types and co-location | §4, data-model §3 |
|
|
653
|
-
| §3.2 common operations | §7, data-model §7 |
|
|
654
|
-
| §3.3 ideal interaction | §7, §9 |
|
|
655
|
-
| §4.1 environment registry | data-model §3 |
|
|
656
|
-
| §4.2 service catalog | data-model §4 |
|
|
657
|
-
| §4.3 deployment binding | data-model §5 |
|
|
658
|
-
| §4.4 rule catalog / resolver | §6, data-model §6 |
|
|
659
|
-
| §5 operation skill registry | §7, data-model §7 |
|
|
660
|
-
| §6 session context | §8 |
|
|
661
|
-
| §7 memory bridge | §11 |
|
|
662
|
-
| §8 security and governance | §12, security-model |
|
|
663
|
-
| §9 P0 scope | §1 |
|
|
664
|
-
| §10 module split | module-boundaries |
|
|
665
|
-
| §11 acceptance scenarios | §14 |
|
|
666
|
-
| §12 open questions | §16 |
|
|
1
|
+
# V1 Design
|
|
2
|
+
|
|
3
|
+
Status: V1 design, derived from
|
|
4
|
+
[DSH_Environment_Service_Memory_Skill_Brainstorm.md](./DSH_Environment_Service_Memory_Skill_Brainstorm.md)
|
|
5
|
+
and verified against the DSH runtime in [api-verification.md](./api-verification.md).
|
|
6
|
+
|
|
7
|
+
Scope of this document: **V1 = P0** from the brainstorm, plus the contracts needed to start P1.
|
|
8
|
+
|
|
9
|
+
Normative companions:
|
|
10
|
+
|
|
11
|
+
- [detailed-design.md](./detailed-design.md) — module contracts, algorithms, tool and HTTP specs
|
|
12
|
+
- [data-model.md](./data-model.md) — record schemas and result types
|
|
13
|
+
- [tool-contract.md](./tool-contract.md) — model-facing tool surface
|
|
14
|
+
- [storage-contract.md](./storage-contract.md) — persistence rules
|
|
15
|
+
- [module-boundaries.md](./module-boundaries.md) — module ownership
|
|
16
|
+
- [security-model.md](./security-model.md) — credential, policy and redaction rules
|
|
17
|
+
- [ui-design.md](./ui-design.md) — Client panels and transport
|
|
18
|
+
- [implementation-plan.md](./implementation-plan.md) — ordered tasks
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Goals and non-goals
|
|
23
|
+
|
|
24
|
+
### 1.1 V1 goal
|
|
25
|
+
|
|
26
|
+
Give every DSH session a shared, queryable environment context, and make the path from "agent knows the environment and service" to "the right Skill with the right parameters" explicit, deterministic and auditable.
|
|
27
|
+
|
|
28
|
+
### 1.2 V1 delivers
|
|
29
|
+
|
|
30
|
+
1. A DSH bundle with a Host half and a Client half.
|
|
31
|
+
2. File-based registry storage under the plugin data directory.
|
|
32
|
+
3. Manual management of environments, services, deployment bindings, rule profiles and operation bindings.
|
|
33
|
+
4. Layered rule resolution with provenance and validation.
|
|
34
|
+
5. Deterministic operation-to-Skill routing with conflict detection, availability checks and approval policy evaluation.
|
|
35
|
+
6. Read-only Agent tools usable from any session in the profile.
|
|
36
|
+
7. Session-scoped default environment.
|
|
37
|
+
8. A minimal Web/Desktop configuration UI; file editing remains a supported baseline.
|
|
38
|
+
|
|
39
|
+
### 1.3 V1 does not deliver
|
|
40
|
+
|
|
41
|
+
- SSH/SFTP, Docker, Kubernetes or browser execution adapters.
|
|
42
|
+
- Automatic deployment, rollback or self-healing.
|
|
43
|
+
- Automatic extraction of service facts from Git, Maven, Dockerfile or Helm.
|
|
44
|
+
- Automatic rule or Skill modification without review.
|
|
45
|
+
- A persistent memory store of its own.
|
|
46
|
+
- Guarantees that a derived path exists. Only an online check may mark a value verified.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Design principles
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
Registry files are the source of truth.
|
|
54
|
+
Derived values are labelled as derived.
|
|
55
|
+
A Skill reference is discovery, not execution authorization.
|
|
56
|
+
Credentials are references; values never enter the registry or tool output.
|
|
57
|
+
Conflicts fail loudly; nothing is chosen silently.
|
|
58
|
+
Facts, derived values and verified observations are different things.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
These principles are expanded into module rules in
|
|
62
|
+
[module-boundaries.md](./module-boundaries.md) and hard constraints in
|
|
63
|
+
[AGENTS.md](../../AGENTS.md) §3.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Architecture
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
DSH Web / Desktop
|
|
71
|
+
└── Client half (configuration panels, context preview)
|
|
72
|
+
│ POST /env-manager/api
|
|
73
|
+
▼
|
|
74
|
+
DSH Host process
|
|
75
|
+
├── transport header/origin checks, op dispatch
|
|
76
|
+
├── tools thin request/response layer
|
|
77
|
+
├── app shared use cases
|
|
78
|
+
├── storage atomic YAML read/write
|
|
79
|
+
├── registry validated per-request snapshot of storage
|
|
80
|
+
├── resolver pure context + rule resolution
|
|
81
|
+
├── router pure operation→Skill matching
|
|
82
|
+
├── session per-session default environment
|
|
83
|
+
└── memory-bridge optional P1 contract
|
|
84
|
+
│
|
|
85
|
+
▼
|
|
86
|
+
Plugin data directory
|
|
87
|
+
├── environments/ services/ deployments/ rules/ operations/
|
|
88
|
+
└── sessions/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3.1 Host and Client split
|
|
92
|
+
|
|
93
|
+
- **Host** owns all business logic, validation and persistence.
|
|
94
|
+
- **Client** renders and calls the plugin HTTP API; it never reads registry files directly and never re-implements resolution.
|
|
95
|
+
- The Client plugin remains a plain JavaScript module in V1. The Host is TypeScript compiled to ESM (see [ADR-0004](./decisions/ADR-0004-typescript-host-js-client.md)).
|
|
96
|
+
|
|
97
|
+
### 3.2 Host service dependencies
|
|
98
|
+
|
|
99
|
+
These DSH services are verified in [api-verification.md](./api-verification.md):
|
|
100
|
+
|
|
101
|
+
| Service | Use |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `ctx.tools` | register Agent tools (F2–F5) |
|
|
104
|
+
| `ctx.credentials` | `describe(ref)` for status only; `resolve` is never called (F8) |
|
|
105
|
+
| `ctx.approval` | request confirmation for management writes that affect production (F7) |
|
|
106
|
+
| `ctx.skills` | `snapshot()` for Skill availability; `register()` for the bundled discovery Skill (F20–F21) |
|
|
107
|
+
| `ctx.webServer` | register the plugin HTTP route for the Client (F12) |
|
|
108
|
+
| `profileContext` | `dir` for the default data directory (F10) |
|
|
109
|
+
| Node `fs` | atomic writes and deletes under `dataDir`; `ctx.fs` lacks mkdir/unlink (F9) |
|
|
110
|
+
|
|
111
|
+
Skill availability degrades to `available: 'unknown'` with `SKILL_REGISTRY_UNAVAILABLE`
|
|
112
|
+
when the catalog is absent or incomplete, rather than silently passing.
|
|
113
|
+
|
|
114
|
+
### 3.3 Bundled discovery Skill
|
|
115
|
+
|
|
116
|
+
The Host registers one runtime Skill named `dsh-env-manager` through
|
|
117
|
+
`ctx.skills.register()`. It is **not** an execution Skill and **not** an operation
|
|
118
|
+
binding:
|
|
119
|
+
|
|
120
|
+
- The registration lives in the plugin's unscoped `apply()` context, so it enters the
|
|
121
|
+
global Skill layer and is visible to every workspace in the same DSH profile. No
|
|
122
|
+
workspace `SKILL.md` is required.
|
|
123
|
+
- Its catalog entry is the discovery surface: name, description and `whenToUse`. The full
|
|
124
|
+
workflow body is loaded only when the model or user invokes the Skill.
|
|
125
|
+
- The workflow walks `env_find` → `env_set_context` → `env_resolve` →
|
|
126
|
+
`env_resolve_operation`; it does not execute anything.
|
|
127
|
+
- The registration is disposed with the plugin fiber, so disabling or uninstalling the
|
|
128
|
+
plugin also removes the Skill.
|
|
129
|
+
- `config.registerSkill` (default `true`) can suppress the entry for installations that
|
|
130
|
+
do not want it.
|
|
131
|
+
- The plugin does not inject a global system-prompt section. Discovery stays in the
|
|
132
|
+
Skill catalog to avoid imposing environment-manager instructions on unrelated
|
|
133
|
+
sessions.
|
|
134
|
+
- `ctx.skills` may be unavailable in a minimal Host; the plugin keeps its tools and panel
|
|
135
|
+
and simply skips Skill registration.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4. Domain model
|
|
140
|
+
|
|
141
|
+
See [data-model.md](./data-model.md).
|
|
142
|
+
|
|
143
|
+
Key points that shape the design:
|
|
144
|
+
|
|
145
|
+
- **Co-location** is expressed with one Host and several roles, not several hosts.
|
|
146
|
+
- **Node kind and command access are machine properties**, not planes: `Host.kind`,
|
|
147
|
+
`Host.access` and `Host.clusterRef` record whether a machine is a VM, physical, K8s
|
|
148
|
+
all-in-one, K8s worker or Pod host and whether it exposes SSH, `kubectl` or `docker`.
|
|
149
|
+
Pods are workloads in deployment bindings, not Host records.
|
|
150
|
+
- **Deployment bindings are many**: a service may run several times in one environment, so deployment ids are first-class.
|
|
151
|
+
- **Rule profiles are shared**; environment and deployment carry field-level overrides.
|
|
152
|
+
- **Operation bindings are predicates**, not a fixed matrix of every combination.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 5. Storage
|
|
157
|
+
|
|
158
|
+
Full rules in [storage-contract.md](./storage-contract.md). Summary:
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
<dataDir>/
|
|
162
|
+
├── environments/{id}.yaml
|
|
163
|
+
├── services/{id}.yaml
|
|
164
|
+
├── deployments/{id}.yaml
|
|
165
|
+
├── rules/{id}.yaml
|
|
166
|
+
├── operations/{id}.yaml
|
|
167
|
+
├── sessions/{sessionId}.json
|
|
168
|
+
└── .backups/{kind}/{id}/{timestamp}.yaml
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- `<dataDir>` defaults to `<profile>/data/dsh-env-manager` and is configurable.
|
|
172
|
+
- Reads and writes go through the storage module; no other module touches files.
|
|
173
|
+
- Writes are atomic (temp file + fsync + rename) with a rolling backup.
|
|
174
|
+
- Invalid or unsupported records are skipped with a diagnostic, never deleted.
|
|
175
|
+
- V1 has no index files. A snapshot is loaded per application request and reloaded after a write.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 6. Rule resolution
|
|
180
|
+
|
|
181
|
+
### 6.1 Template language
|
|
182
|
+
|
|
183
|
+
Only interpolation is supported:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
${name}
|
|
187
|
+
${name.path}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Rules:
|
|
191
|
+
|
|
192
|
+
- Identifier segments: `[A-Za-z_][A-Za-z0-9_]*`, separated by `.`.
|
|
193
|
+
- No function calls, operators, conditionals, environment variables or command substitution.
|
|
194
|
+
- No escaping. A lone `$` is literal; a malformed `${…}` (missing `}` or invalid
|
|
195
|
+
identifier) is `RULE_TEMPLATE_INVALID`.
|
|
196
|
+
- Interpolation is allowed only in string-valued fields (`paths`, `logSources`, `classLocations`, binding parameters).
|
|
197
|
+
|
|
198
|
+
### 6.2 Available namespaces
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
env.id, env.name, env.category
|
|
202
|
+
service.id, service.name, service.category
|
|
203
|
+
service.artifact.name
|
|
204
|
+
deployment.id, deployment.name, deployment.runtimeMode
|
|
205
|
+
deployment.location.* (runtime-specific: see data-model §5)
|
|
206
|
+
<user variables from rule profiles and overrides>
|
|
207
|
+
paths.* (resolved path fields)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`env`, `service` and `deployment` are read-only. `paths` is itself resolved, so a path may reference another path.
|
|
211
|
+
|
|
212
|
+
### 6.3 Layers and precedence
|
|
213
|
+
|
|
214
|
+
From lowest to highest:
|
|
215
|
+
|
|
216
|
+
1. Built-in global defaults (empty in V1).
|
|
217
|
+
2. Service `ruleProfileRefs`, in list order; later profiles win on field conflicts.
|
|
218
|
+
3. Environment `ruleOverrides`.
|
|
219
|
+
4. Deployment `variables` (variables only), then deployment `ruleOverrides` (all field kinds; wins over `variables` for the same variable).
|
|
220
|
+
|
|
221
|
+
Merging is **field-level**, not whole-object replacement: overriding `paths.logDir` leaves `paths.jar` intact.
|
|
222
|
+
|
|
223
|
+
### 6.4 Resolution algorithm
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
1. Collect ordered layers.
|
|
227
|
+
2. Merge variables, paths, logSources[per runtime], classLocations[per runtime]
|
|
228
|
+
into a raw map, recording the winning source and the shadowed sources per field.
|
|
229
|
+
3. Build a dependency graph from ${...} references in raw string values.
|
|
230
|
+
4. Detect cycles (DFS with an active stack) and report RULE_CYCLE with the cycle path.
|
|
231
|
+
5. Reject references to unknown fields with RULE_UNDEFINED_VARIABLE.
|
|
232
|
+
6. Resolve in dependency order; each substitution is recorded as a provenance input.
|
|
233
|
+
7. Validate resolved paths: reject any `..` segment (RULE_PATH_TRAVERSAL).
|
|
234
|
+
8. Select the log source and class location for the deployment's runtimeMode.
|
|
235
|
+
9. Emit provenance for every field that was requested by the caller.
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Resolution is pure: same inputs, same outputs, no I/O.
|
|
239
|
+
|
|
240
|
+
### 6.5 Which fields are resolved eagerly
|
|
241
|
+
|
|
242
|
+
`env_resolve` returns only the fields used by the runtime:
|
|
243
|
+
|
|
244
|
+
- all `paths` fields defined by any layer (bounded, typically under 20),
|
|
245
|
+
- the `logSources[runtimeMode]` entry,
|
|
246
|
+
- the `classLocations[runtimeMode]` entry,
|
|
247
|
+
- merged `variables`.
|
|
248
|
+
|
|
249
|
+
Only defined fields are evaluated; the resolver never guesses a missing path. A defined
|
|
250
|
+
template that references a name no layer defines is still `RULE_UNDEFINED_VARIABLE`.
|
|
251
|
+
|
|
252
|
+
### 6.6 Provenance semantics
|
|
253
|
+
|
|
254
|
+
For each returned field, the response includes:
|
|
255
|
+
|
|
256
|
+
- `definedBy` — the winning layer and, when applicable, the rule profile id.
|
|
257
|
+
- `inputs` — every referenced field with the layer that supplied it.
|
|
258
|
+
- `shadowed` — lower-priority definitions that were overridden.
|
|
259
|
+
|
|
260
|
+
This makes AC-04 explainable: overriding `paths.logDir` at the environment layer changes `paths.applicationLog` while its `definedBy` remains the service rule profile. Both facts are visible.
|
|
261
|
+
|
|
262
|
+
### 6.7 Log sources
|
|
263
|
+
|
|
264
|
+
Log sources are structured, not collapsed into a file path:
|
|
265
|
+
|
|
266
|
+
| Runtime | Source kind | Example fields |
|
|
267
|
+
|---|---|---|
|
|
268
|
+
| native | `file` | `path` |
|
|
269
|
+
| docker | `container-stdout` | `container` |
|
|
270
|
+
| kubernetes | `pod-logs` | `namespace`, `workload` |
|
|
271
|
+
|
|
272
|
+
### 6.8 Class locations
|
|
273
|
+
|
|
274
|
+
Three distinct kinds are preserved:
|
|
275
|
+
|
|
276
|
+
- `build-output` — e.g. `target/classes`
|
|
277
|
+
- `deployed-dir` — an exploded class directory on the host
|
|
278
|
+
- `jar-entry` — a jar path plus the class entry prefix
|
|
279
|
+
|
|
280
|
+
### 6.9 Worked example
|
|
281
|
+
|
|
282
|
+
Service `aicoservice` references `java-common`; environment `test-a` overrides `paths.logDir`; deployment `deploy-001` is Kubernetes.
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
paths.serviceHome /opt/company/services/aicoservice service/java-common
|
|
286
|
+
paths.jar /opt/company/services/aicoservice/lib/aicoservice.jar
|
|
287
|
+
(input: serviceHome)
|
|
288
|
+
paths.logDir /data/logs/aicoservice environment (shadowed service)
|
|
289
|
+
paths.applicationLog /data/logs/aicoservice/aicoservice.log service/java-common
|
|
290
|
+
(input: logDir → environment)
|
|
291
|
+
logSource pod-logs dv-sop / aicoservice service/java-common
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The `applicationLog` value reflects the environment override even though its template was defined at the service layer.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## 7. Operation Skill routing
|
|
299
|
+
|
|
300
|
+
### 7.1 Match levels
|
|
301
|
+
|
|
302
|
+
Bindings are grouped by how specific their `match` predicate is:
|
|
303
|
+
|
|
304
|
+
| Level | Predicate | Priority |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `deployment` | `match.deploymentId` set | 1 (highest) |
|
|
307
|
+
| `environment-service` | `match.environmentId` + `match.serviceId` | 2 |
|
|
308
|
+
| `service` | `match.serviceId` only | 3 |
|
|
309
|
+
| `category-mode` | `match.serviceCategory` + `match.deploymentMode` | 4 |
|
|
310
|
+
| `global` | empty match object | 5 (lowest) |
|
|
311
|
+
|
|
312
|
+
A predicate is eligible only if **every** field it declares matches the resolved context. `deploymentMode` compares against `runtimeMode`; `serviceCategory` compares against the service category.
|
|
313
|
+
|
|
314
|
+
A `match` that is not one of these five shapes (for example `environmentId` alone) is rejected at record validation; it never reaches routing.
|
|
315
|
+
|
|
316
|
+
### 7.2 Conflict rule
|
|
317
|
+
|
|
318
|
+
If more than one enabled binding is eligible at the highest level, the result is `BINDING_CONFLICT` listing every candidate id, level and skill name. No binding is selected.
|
|
319
|
+
|
|
320
|
+
If exactly one binding is eligible at the highest level, lower levels are ignored entirely, even if they also match.
|
|
321
|
+
|
|
322
|
+
### 7.3 Skill availability
|
|
323
|
+
|
|
324
|
+
Checks, in order, against `ctx.skills.snapshot({ scope, cwd, signal })`:
|
|
325
|
+
|
|
326
|
+
1. The catalog is complete; an incomplete snapshot means the check is inconclusive.
|
|
327
|
+
2. The Skill name is registered.
|
|
328
|
+
3. If `expectedSource` is set, the registered `source` matches.
|
|
329
|
+
4. If `expectedProvider` is set, the registered `provider` matches.
|
|
330
|
+
|
|
331
|
+
Outcomes:
|
|
332
|
+
|
|
333
|
+
- pass → `available: true`
|
|
334
|
+
- fail → `SKILL_UNAVAILABLE`, `ok: false`
|
|
335
|
+
- registry absent or incomplete → `available: 'unknown'` plus `SKILL_REGISTRY_UNAVAILABLE` warning, `ok: true` (routing still useful) but the caller is told the check was incomplete
|
|
336
|
+
|
|
337
|
+
Digest pinning is not supported: the DSH catalog exposes no digest. The router never
|
|
338
|
+
substitutes a different Skill.
|
|
339
|
+
|
|
340
|
+
### 7.4 Approval policy
|
|
341
|
+
|
|
342
|
+
Evaluation order:
|
|
343
|
+
|
|
344
|
+
1. Write operation and `policy.requiresApproval !== false` → `requiresApproval: true`.
|
|
345
|
+
2. `environmentCategory === 'production'` and `policy.allowProduction !== true` → `POLICY_DENIED`.
|
|
346
|
+
3. Otherwise the policy is returned as declared.
|
|
347
|
+
|
|
348
|
+
V1 does not execute the operation. The resolved policy is returned to the caller; enforcement happens in the executing Skill or in the future executor.
|
|
349
|
+
|
|
350
|
+
### 7.5 Parameter mapping
|
|
351
|
+
|
|
352
|
+
- Binding `parameters` values are templates resolved against the same namespaces as rules, plus `paths.*`.
|
|
353
|
+
- Result values must be `string`, `number` or `boolean`; numbers and booleans are stringified.
|
|
354
|
+
- Any unresolved reference or non-scalar value fails with `PARAMETER_RESOLUTION_FAILED`.
|
|
355
|
+
- Parameters are not a command line. They are structured inputs for the target Skill.
|
|
356
|
+
|
|
357
|
+
### 7.6 Worked example
|
|
358
|
+
|
|
359
|
+
```text
|
|
360
|
+
operation: redeploy
|
|
361
|
+
context: test-a / aicoservice / deploy-001 (kubernetes)
|
|
362
|
+
|
|
363
|
+
eligible:
|
|
364
|
+
binding-001 level=environment-service skill=redeploy-k8s-service
|
|
365
|
+
selected: binding-001
|
|
366
|
+
parameters: namespace=dv-sop workload=aicoservice cluster=k8s-01
|
|
367
|
+
policy: requiresApproval=true allowProduction=false
|
|
368
|
+
skill: available=true
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
If `redeploy-k8s-service` is missing, the result is an error with `SKILL_UNAVAILABLE`; it does not fall through to `binding-002`.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 8. Session context
|
|
376
|
+
|
|
377
|
+
- `env_set_context` writes `sessions/{sessionId}.json` with `defaultEnvironmentId`.
|
|
378
|
+
- Passing `null` clears the default.
|
|
379
|
+
- `env_get_context` returns the current session's default, if any.
|
|
380
|
+
- DSH exposes `sessionProjections`, but it requires declared session event types and a
|
|
381
|
+
closed projection schema, so a standalone bundle cannot use it. V1 writes
|
|
382
|
+
`sessions/{sessionId}.json`; a projection-based store is a later refinement
|
|
383
|
+
(detailed-design §9).
|
|
384
|
+
- Session context is isolated: it cannot modify registry files or another session's default.
|
|
385
|
+
|
|
386
|
+
Tools that need an environment accept an explicit `environmentId`. When omitted, they fall back to the session default and otherwise return a validation error naming both options.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 9. Tool surface
|
|
391
|
+
|
|
392
|
+
Full schemas in [tool-contract.md](./tool-contract.md); the `defineTool` definitions
|
|
393
|
+
are specified in [detailed-design.md](./detailed-design.md) §11.
|
|
394
|
+
|
|
395
|
+
Every tool is registered with `defineTool`:
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
ctx.tools.register(defineTool({
|
|
399
|
+
name: 'env_list',
|
|
400
|
+
description: '...',
|
|
401
|
+
parameters: { kind: { type: 'string', enum: [...] } },
|
|
402
|
+
output: { schema: { ... }, render: (args, value) => [{ type: 'text', text: '...' }] },
|
|
403
|
+
async execute(args, exec) { /* returns the declared JSON value */ },
|
|
404
|
+
}))
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Read tools:
|
|
408
|
+
|
|
409
|
+
```text
|
|
410
|
+
env_list, env_get, env_find, env_resolve, env_resolve_operation,
|
|
411
|
+
env_get_context, env_set_context
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Management tools (state-changing):
|
|
415
|
+
|
|
416
|
+
```text
|
|
417
|
+
env_save, env_delete, env_validate
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Results:
|
|
421
|
+
|
|
422
|
+
- Success returns the declared JSON value, validated by DSH against
|
|
423
|
+
`output.schema`.
|
|
424
|
+
- Expected failures throw `EnvManagerError extends HarnessError`, which carries a
|
|
425
|
+
stable `code` (`ENV_NOT_FOUND`, `BINDING_CONFLICT`, `POLICY_DENIED`, …) and a
|
|
426
|
+
message safe to show. There is no `{ ok: false }` value.
|
|
427
|
+
- `env_delete` and production `env_save` ask `ctx.approval`; any outcome other than
|
|
428
|
+
`allowed-once` denies.
|
|
429
|
+
- Errors never include credential values, file contents or stack traces.
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 10. Client UI
|
|
434
|
+
|
|
435
|
+
### 10.1 V1 baseline
|
|
436
|
+
|
|
437
|
+
Configuration is possible without the UI:
|
|
438
|
+
|
|
439
|
+
- edit YAML files under `dataDir` directly;
|
|
440
|
+
- call `env_validate` to check the registry;
|
|
441
|
+
- call `env_resolve` / `env_resolve_operation` to inspect behavior.
|
|
442
|
+
|
|
443
|
+
### 10.2 V1 UI
|
|
444
|
+
|
|
445
|
+
A dedicated Environment Manager surface in the DSH Web/Desktop client, registered as a
|
|
446
|
+
`sidebar.panellist` entry (id `env-manager`) plus the `main` panel keyed
|
|
447
|
+
`env-manager`:
|
|
448
|
+
|
|
449
|
+
| Section | Content |
|
|
450
|
+
|---|---|
|
|
451
|
+
| Environments (default) | list; detail split into overview, all nodes, management/business/database/expansion plane views, login records, Kubernetes/clusters, endpoints and YAML; shell-only New environment |
|
|
452
|
+
| Overview | counts, dataDir, warnings, validate |
|
|
453
|
+
| Records | Services, Deployments, Rule profiles and Operation bindings as YAML editors |
|
|
454
|
+
| Context preview | pick environment + service (+ deployment) and inspect the Service Context |
|
|
455
|
+
| Operation preview | operation → selected binding, skill availability, policy, parameters |
|
|
456
|
+
|
|
457
|
+
Rules for the UI:
|
|
458
|
+
|
|
459
|
+
- All reads/writes go through the plugin's same-origin HTTP API
|
|
460
|
+
(`POST /env-manager/api`, ADR-0009); no direct file access and no second business
|
|
461
|
+
path.
|
|
462
|
+
- The UI cannot read or write session context in V1; that stays with the agent tools.
|
|
463
|
+
- `New environment` creates only the environment shell and the four standard role
|
|
464
|
+
definitions; nodes are added inside the relevant plane. Plane tabs are filters over the
|
|
465
|
+
same `hosts[]`, never separate records. Environment detail and YAML are editable.
|
|
466
|
+
- A node also carries a machine kind (`vm`, `physical`, `k8s-all-in-one`, `k8s-worker`,
|
|
467
|
+
`pod-host`) and its command surfaces (`ssh`, `kubectl`, `docker`). Kubernetes and Pod-host
|
|
468
|
+
nodes reference a cluster. Individual Pods are not modeled as hosts; `pod-host` is the
|
|
469
|
+
machine reached with `docker exec`. The detail page has a Kubernetes/clusters tab for
|
|
470
|
+
cluster metadata. See ADR-0011.
|
|
471
|
+
- Service, deployment, rule and binding records are edited as YAML, validated
|
|
472
|
+
server-side before writing.
|
|
473
|
+
- Text is routed through the client locale service; `en` and `zh` provided.
|
|
474
|
+
- The UI must show provenance for derived values, not just the final string.
|
|
475
|
+
- Production-category destructive actions are visually distinct and require
|
|
476
|
+
confirmation.
|
|
477
|
+
|
|
478
|
+
Details: [ui-design.md](./ui-design.md). The implemented `client.js` registers the
|
|
479
|
+
sidebar entry and the `main` panel; the panel is the configuration surface described
|
|
480
|
+
above.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## 11. Memory bridge (P1 boundary)
|
|
485
|
+
|
|
486
|
+
V1 defines the contract and ships a null adapter:
|
|
487
|
+
|
|
488
|
+
```ts
|
|
489
|
+
interface MemoryBridge {
|
|
490
|
+
emitOperationRecord(record: OperationRecord): Promise<void>;
|
|
491
|
+
queryMemory(scope: MemoryScope): Promise<MemoryQueryResult[]>;
|
|
492
|
+
proposeChange(proposal: RuleOrSkillProposal): Promise<{ id: string }>;
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
- Null adapter returns `MEMORY_BRIDGE_UNAVAILABLE` as a warning; it never throws into a tool.
|
|
497
|
+
- `EvidenceRef` values are opaque strings, sanitized before emission.
|
|
498
|
+
- The bridge never writes registry files and never applies proposals.
|
|
499
|
+
- P1 wires the adapter to the memory plugin's public contract once that contract is verified.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 12. Security
|
|
504
|
+
|
|
505
|
+
Summary; details in [security-model.md](./security-model.md).
|
|
506
|
+
|
|
507
|
+
- `credentialRef` only in registry records; status via `ctx.credentials.describe`, never
|
|
508
|
+
`resolve`. The add/edit node form may call `ctx.credentials.set` once with an optional
|
|
509
|
+
login password and never persists or returns the value; shell creation and batch import
|
|
510
|
+
collect no password.
|
|
511
|
+
- Production write operations denied unless `allowProduction: true`.
|
|
512
|
+
- Tool management writes affecting production records require `ctx.approval`.
|
|
513
|
+
- UI writes run as actor `'ui'` and are denied for production unless
|
|
514
|
+
`writePolicy.allowUiProductionWrites` is enabled.
|
|
515
|
+
- The `/env-manager/api` route requires a custom header and a same-origin `Origin`;
|
|
516
|
+
no CORS headers are sent.
|
|
517
|
+
- Rule resolution rejects cycles, undefined variables and path traversal.
|
|
518
|
+
- Diagnostics and logs must be redacted before emission.
|
|
519
|
+
- Fail closed on conflicts, missing Skills and broken references.
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
## 13. Error handling
|
|
524
|
+
|
|
525
|
+
- Tool failures throw `EnvManagerError extends HarnessError`; DSH records the stable
|
|
526
|
+
`{ name, code }` and shows the message. A bare string is never thrown.
|
|
527
|
+
- HTTP failures return `{ ok: false, error: { code, message, details? } }` with a
|
|
528
|
+
status from [detailed-design.md](./detailed-design.md) §12.2.
|
|
529
|
+
- Registry load failures produce diagnostics and keep serving the records that are valid.
|
|
530
|
+
- Resolution failures name the field and the offending variable.
|
|
531
|
+
- Router failures list candidate bindings.
|
|
532
|
+
- All error paths are covered by tests listed in
|
|
533
|
+
[verification-policy.md](../02-harness/verification-policy.md).
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## 14. Acceptance criteria
|
|
538
|
+
|
|
539
|
+
| ID | Scenario | V1 | Verification |
|
|
540
|
+
|---|---|---|---|
|
|
541
|
+
| AC-01 | One host carries management + business roles with no duplicate host entry | yes | `env_save` validation test |
|
|
542
|
+
| AC-02 | K8s cluster/workload and Docker instance are queryable through deployment bindings | yes | resolver test |
|
|
543
|
+
| AC-03 | Service with Git URL and artifact is visible to a second session | yes | registry reload test |
|
|
544
|
+
| AC-04 | Java common rules plus environment override compute paths with provenance | yes | resolver + provenance test |
|
|
545
|
+
| AC-05 | `env_resolve_operation` returns the bound Skill and mapped parameters for test-a redeploy | yes | router test |
|
|
546
|
+
| AC-06 | Two same-level bindings produce a conflict error | yes | router conflict test |
|
|
547
|
+
| AC-07 | Two sessions share data but keep different defaults | yes | session store test |
|
|
548
|
+
| AC-08 | Credential query returns ref, configured and source only | yes | tool output test |
|
|
549
|
+
| AC-09 | A completed log check stores a sanitized summary and evidence refs | P1 | memory bridge contract test |
|
|
550
|
+
| AC-10 | A rule change derived from experience becomes a pending proposal | P1 | proposal queue test |
|
|
551
|
+
| AC-11 | Environment detail shows host accounts with protocol/auth/last-login source, without secret values | yes | app test + UI checklist |
|
|
552
|
+
| AC-12 | Login records are stored in the environment YAML, reference existing hosts/accounts and render newest-first | yes | app integrity test + UI checklist |
|
|
553
|
+
| AC-13 | Environments is the default tab, `New environment` is reachable from the header, and Records excludes environments | yes | UI checklist |
|
|
554
|
+
| AC-14 | `New environment` creates only the shell with four standard roles, and a plane tab filters the same `hosts[]` without copying nodes | yes | app schema test + UI checklist |
|
|
555
|
+
| AC-15 | A node can carry multiple roles, is editable, is batch-importable without a password, and is searchable by id/name/IP/role/kind/access/cluster/account | yes | UI checklist + live verification |
|
|
556
|
+
| AC-16 | A host records machine kind and command access; K8s kinds require an existing cluster; a Pod host is one machine, not one entry per Pod | yes | schema test + UI checklist + live verification |
|
|
557
|
+
| AC-17 | Cluster metadata includes optional topology and a node count derived from `clusterRef`; cluster create/edit round-trips in the panel | yes | app test + UI checklist |
|
|
558
|
+
| AC-18 | A host account can store a web login URL and username; a password is written to the credential service and only `credentialRef` remains; the account can be edited and searched by URL | yes | app test + UI checklist + live verification |
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## 15. Build and packaging
|
|
563
|
+
|
|
564
|
+
### 15.1 Host
|
|
565
|
+
|
|
566
|
+
- TypeScript sources in `src/`.
|
|
567
|
+
- `tsconfig.json`: `target ES2022`, `module NodeNext`, `strict`, `outDir lib`.
|
|
568
|
+
- `npm run build` → `tsc -p tsconfig.json`.
|
|
569
|
+
- `package.json` exports `.` → `./index.js`; `index.js` is a shim that re-exports `./lib/index.js`.
|
|
570
|
+
|
|
571
|
+
### 15.2 Client
|
|
572
|
+
|
|
573
|
+
- Plain JavaScript `client.js` at the repository root, in the DSH module-loader format.
|
|
574
|
+
- No build step in V1; no JSX. Components use `React.createElement`.
|
|
575
|
+
- If the UI later requires TypeScript or JSX, a client bundling step is added with its own ADR.
|
|
576
|
+
|
|
577
|
+
### 15.3 Bundle layout
|
|
578
|
+
|
|
579
|
+
```text
|
|
580
|
+
dsh-env-manager/
|
|
581
|
+
├── index.js # host entry shim: export * from './lib/index.js'
|
|
582
|
+
├── client.js # client module
|
|
583
|
+
├── cordis.patch.yml
|
|
584
|
+
├── package.json
|
|
585
|
+
├── tsconfig.json
|
|
586
|
+
├── src/ # TypeScript host sources
|
|
587
|
+
├── lib/ # build output (git-ignored)
|
|
588
|
+
├── locale/
|
|
589
|
+
├── icon.svg
|
|
590
|
+
├── docs/
|
|
591
|
+
└── tests/
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
`files` in `package.json` includes `index.js`, `lib/`, `client.js`, `cordis.patch.yml`, `locale/`, `icon.svg`, `README.md`, `LICENSE`.
|
|
595
|
+
|
|
596
|
+
Manifest contract:
|
|
597
|
+
|
|
598
|
+
- `dsh.bundle.patch: ./cordis.patch.yml` registers the Host plugin.
|
|
599
|
+
- `dsh.client: { platform: 'web', immediately: true }` loads the Client half in Web and
|
|
600
|
+
Desktop. It declares no `inject` dependency on a conversation UI package, because the
|
|
601
|
+
panel lives in the shell's sidebar/main slots, not the composer dock.
|
|
602
|
+
|
|
603
|
+
### 15.4 Dependencies
|
|
604
|
+
|
|
605
|
+
| Package | Purpose | Scope |
|
|
606
|
+
|---|---|---|
|
|
607
|
+
| `zod` | record validation | runtime |
|
|
608
|
+
| `yaml` | YAML parse/serialize | runtime |
|
|
609
|
+
| `@deepseek-ai/schemastery` | `Config` schema | runtime |
|
|
610
|
+
| `@deepseek-ai/dsh-tools` | `defineTool`, tool types | peer |
|
|
611
|
+
| `@deepseek-ai/dsh-llm` | `HarnessError` | peer |
|
|
612
|
+
| `@deepseek-ai/cordis` | `Context` type (type-only) | peer, provided by the Host |
|
|
613
|
+
| `typescript` | build | dev |
|
|
614
|
+
| `vitest` | tests | dev |
|
|
615
|
+
|
|
616
|
+
Peer packages are provided by the DSH profile; the plugin must not bundle them.
|
|
617
|
+
Version alignment is pinned to the runtime release (`0.2.1-rc.5` at design time) and
|
|
618
|
+
checked during T01. Adding any other dependency follows
|
|
619
|
+
`docs/04-development/dependency-policy.md`.
|
|
620
|
+
|
|
621
|
+
---
|
|
622
|
+
|
|
623
|
+
## 16. Open questions
|
|
624
|
+
|
|
625
|
+
The eight integration questions from the first draft are resolved in
|
|
626
|
+
[api-verification.md](./api-verification.md):
|
|
627
|
+
|
|
628
|
+
| # | Question | Resolution |
|
|
629
|
+
|---|---|---|
|
|
630
|
+
| 1 | Plugin data directory | `profileContext.dir` → `<dir>/data/dsh-env-manager` (F10) |
|
|
631
|
+
| 2 | `ctx.fs` for data paths | unusable for create/delete; use Node fs under `dataDir` (F9) |
|
|
632
|
+
| 3 | Skill registry API | `ctx.skills.snapshot`; source/provider checks, no digest (F20) |
|
|
633
|
+
| 4 | Durable session metadata | V1 file store; projection is a later refinement (F11) |
|
|
634
|
+
| 5 | Client slot for a panel | `sidebar.panellist` + `main` keyed `env-manager` (F15) |
|
|
635
|
+
| 6 | Client→Host calls | plugin-owned HTTP route, ADR-0009 (F14) |
|
|
636
|
+
| 7 | Memory plugin contract | still owned by the memory plugin; null adapter until P1 |
|
|
637
|
+
| 8 | `credentials.describe` coverage | presence/source/writability, no value (F8) |
|
|
638
|
+
|
|
639
|
+
Remaining, none blocking V1:
|
|
640
|
+
|
|
641
|
+
- The memory plugin's public contract (P1).
|
|
642
|
+
- Cross-process write locking for a shared `dataDir`.
|
|
643
|
+
- A session-event projection instead of the session file.
|
|
644
|
+
- Client bundling if the UI outgrows a single plain-JS module.
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## 17. Traceability to the brainstorm
|
|
649
|
+
|
|
650
|
+
| Brainstorm | Design section |
|
|
651
|
+
|---|---|
|
|
652
|
+
| §3.1 environment types and co-location | §4, data-model §3 |
|
|
653
|
+
| §3.2 common operations | §7, data-model §7 |
|
|
654
|
+
| §3.3 ideal interaction | §7, §9 |
|
|
655
|
+
| §4.1 environment registry | data-model §3 |
|
|
656
|
+
| §4.2 service catalog | data-model §4 |
|
|
657
|
+
| §4.3 deployment binding | data-model §5 |
|
|
658
|
+
| §4.4 rule catalog / resolver | §6, data-model §6 |
|
|
659
|
+
| §5 operation skill registry | §7, data-model §7 |
|
|
660
|
+
| §6 session context | §8 |
|
|
661
|
+
| §7 memory bridge | §11 |
|
|
662
|
+
| §8 security and governance | §12, security-model |
|
|
663
|
+
| §9 P0 scope | §1 |
|
|
664
|
+
| §10 module split | module-boundaries |
|
|
665
|
+
| §11 acceptance scenarios | §14 |
|
|
666
|
+
| §12 open questions | §16 |
|