dsh-env-manager-plugin 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +273 -250
  3. package/client.js +2581 -2581
  4. package/cordis.patch.yml +4 -4
  5. package/docs/00-project/current-state.md +32 -6
  6. package/docs/00-project/glossary.md +215 -215
  7. package/docs/00-project/project-brief.md +114 -114
  8. package/docs/00-project/roadmap.md +89 -89
  9. package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -493
  10. package/docs/01-architecture/api-verification.md +416 -416
  11. package/docs/01-architecture/architecture-overview.md +222 -222
  12. package/docs/01-architecture/data-model.md +930 -930
  13. package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -29
  14. package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -25
  15. package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -25
  16. package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -31
  17. package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -36
  18. package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -30
  19. package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -28
  20. package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -31
  21. package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -87
  22. package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -87
  23. package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -92
  24. package/docs/01-architecture/design.md +666 -666
  25. package/docs/01-architecture/detailed-design.md +961 -961
  26. package/docs/01-architecture/implementation-plan.md +323 -323
  27. package/docs/01-architecture/module-boundaries.md +306 -306
  28. package/docs/01-architecture/security-model.md +227 -227
  29. package/docs/01-architecture/storage-contract.md +224 -224
  30. package/docs/01-architecture/tool-contract.md +438 -438
  31. package/docs/01-architecture/ui-design.md +587 -587
  32. package/docs/02-harness/agent-behavior.md +36 -36
  33. package/docs/02-harness/change-snapshot-policy.md +29 -29
  34. package/docs/02-harness/client-ui-integration.md +110 -110
  35. package/docs/02-harness/context-policy.md +43 -43
  36. package/docs/02-harness/harness-standard.md +91 -91
  37. package/docs/02-harness/task-execution-policy.md +26 -26
  38. package/docs/02-harness/verification-policy.md +97 -97
  39. package/docs/03-openspec/archive-rule.md +25 -25
  40. package/docs/03-openspec/bugfix-rule.md +30 -30
  41. package/docs/03-openspec/design-rule.md +33 -33
  42. package/docs/03-openspec/openspec-usage-guide.md +31 -31
  43. package/docs/03-openspec/proposal-rule.md +70 -70
  44. package/docs/03-openspec/spec-rule.md +33 -33
  45. package/docs/03-openspec/tasks-rule.md +31 -31
  46. package/docs/04-development/coding-style.md +67 -67
  47. package/docs/04-development/dependency-policy.md +43 -43
  48. package/docs/04-development/development-guide.md +85 -85
  49. package/docs/04-development/directory-convention.md +105 -105
  50. package/docs/04-development/local-run-guide.md +61 -55
  51. package/docs/04-development/testing-guide.md +102 -102
  52. package/docs/05-domain/deployment-template.md +76 -76
  53. package/docs/05-domain/environment-template.md +165 -165
  54. package/docs/05-domain/operation-binding-template.md +55 -55
  55. package/docs/05-domain/operation-record-template.md +55 -55
  56. package/docs/05-domain/prompt-template.md +90 -90
  57. package/docs/05-domain/rule-template.md +73 -73
  58. package/docs/05-domain/service-template.md +39 -39
  59. package/docs/06-operations/deployment-guide.md +54 -54
  60. package/docs/06-operations/release.md +198 -188
  61. package/docs/06-operations/security-policy.md +53 -53
  62. package/docs/06-operations/troubleshooting.md +141 -91
  63. package/docs/07-templates/adr-template.md +21 -21
  64. package/docs/07-templates/bugfix-template.md +11 -11
  65. package/docs/07-templates/case-template.md +15 -15
  66. package/docs/07-templates/design-template.md +29 -29
  67. package/docs/07-templates/proposal-template.md +19 -19
  68. package/docs/07-templates/spec-template.md +15 -15
  69. package/docs/07-templates/tasks-template.md +12 -12
  70. package/docs/99-archive/README.md +13 -13
  71. package/docs/README.md +142 -142
  72. package/icon.svg +4 -4
  73. package/index.js +31 -8
  74. package/lib/skill.js +37 -37
  75. package/locale/en.json +6 -6
  76. package/locale/zh.json +6 -6
  77. package/package.json +101 -99
  78. 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 |