dsh-env-manager-plugin 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +249 -2
- package/client.js +2581 -0
- package/cordis.patch.yml +4 -0
- package/docs/00-project/current-state.md +246 -0
- package/docs/00-project/glossary.md +215 -0
- package/docs/00-project/project-brief.md +114 -0
- package/docs/00-project/roadmap.md +89 -0
- package/docs/01-architecture/DSH_Environment_Service_Memory_Skill_Brainstorm.md +493 -0
- package/docs/01-architecture/api-verification.md +416 -0
- package/docs/01-architecture/architecture-overview.md +222 -0
- package/docs/01-architecture/data-model.md +930 -0
- package/docs/01-architecture/decisions/ADR-0001-plugin-as-bundle.md +29 -0
- package/docs/01-architecture/decisions/ADR-0002-file-based-storage.md +25 -0
- package/docs/01-architecture/decisions/ADR-0003-skill-router-over-search.md +25 -0
- package/docs/01-architecture/decisions/ADR-0004-typescript-host-js-client.md +31 -0
- package/docs/01-architecture/decisions/ADR-0005-layered-rule-resolution.md +36 -0
- package/docs/01-architecture/decisions/ADR-0006-credentials-as-references.md +30 -0
- package/docs/01-architecture/decisions/ADR-0007-session-scoped-default-environment.md +28 -0
- package/docs/01-architecture/decisions/ADR-0008-memory-bridge-not-second-store.md +31 -0
- package/docs/01-architecture/decisions/ADR-0009-client-host-http-transport.md +87 -0
- package/docs/01-architecture/decisions/ADR-0010-environment-plane-model.md +87 -0
- package/docs/01-architecture/decisions/ADR-0011-k8s-node-execution-model.md +92 -0
- package/docs/01-architecture/design.md +666 -0
- package/docs/01-architecture/detailed-design.md +961 -0
- package/docs/01-architecture/implementation-plan.md +323 -0
- package/docs/01-architecture/module-boundaries.md +306 -0
- package/docs/01-architecture/security-model.md +227 -0
- package/docs/01-architecture/storage-contract.md +224 -0
- package/docs/01-architecture/tool-contract.md +438 -0
- package/docs/01-architecture/ui-design.md +587 -0
- package/docs/02-harness/agent-behavior.md +36 -0
- package/docs/02-harness/change-snapshot-policy.md +29 -0
- package/docs/02-harness/client-ui-integration.md +110 -0
- package/docs/02-harness/context-policy.md +43 -0
- package/docs/02-harness/harness-standard.md +91 -0
- package/docs/02-harness/task-execution-policy.md +26 -0
- package/docs/02-harness/verification-policy.md +97 -0
- package/docs/03-openspec/archive-rule.md +25 -0
- package/docs/03-openspec/bugfix-rule.md +30 -0
- package/docs/03-openspec/design-rule.md +33 -0
- package/docs/03-openspec/openspec-usage-guide.md +31 -0
- package/docs/03-openspec/proposal-rule.md +70 -0
- package/docs/03-openspec/spec-rule.md +33 -0
- package/docs/03-openspec/tasks-rule.md +31 -0
- package/docs/04-development/coding-style.md +67 -0
- package/docs/04-development/dependency-policy.md +43 -0
- package/docs/04-development/development-guide.md +85 -0
- package/docs/04-development/directory-convention.md +105 -0
- package/docs/04-development/local-run-guide.md +55 -0
- package/docs/04-development/testing-guide.md +102 -0
- package/docs/05-domain/deployment-template.md +76 -0
- package/docs/05-domain/environment-template.md +165 -0
- package/docs/05-domain/operation-binding-template.md +55 -0
- package/docs/05-domain/operation-record-template.md +55 -0
- package/docs/05-domain/prompt-template.md +90 -0
- package/docs/05-domain/rule-template.md +73 -0
- package/docs/05-domain/service-template.md +39 -0
- package/docs/06-operations/deployment-guide.md +54 -0
- package/docs/06-operations/release.md +188 -0
- package/docs/06-operations/security-policy.md +53 -0
- package/docs/06-operations/troubleshooting.md +91 -0
- package/docs/07-templates/adr-template.md +21 -0
- package/docs/07-templates/bugfix-template.md +11 -0
- package/docs/07-templates/case-template.md +15 -0
- package/docs/07-templates/design-template.md +29 -0
- package/docs/07-templates/proposal-template.md +19 -0
- package/docs/07-templates/spec-template.md +15 -0
- package/docs/07-templates/tasks-template.md +12 -0
- package/docs/99-archive/README.md +13 -0
- package/docs/README.md +142 -0
- package/icon.svg +4 -0
- package/index.js +8 -0
- package/lib/app/app.d.ts +67 -0
- package/lib/app/app.js +494 -0
- package/lib/app/app.js.map +1 -0
- package/lib/config.d.ts +55 -0
- package/lib/config.js +42 -0
- package/lib/config.js.map +1 -0
- package/lib/domain/constants.d.ts +21 -0
- package/lib/domain/constants.js +59 -0
- package/lib/domain/constants.js.map +1 -0
- package/lib/domain/errors.d.ts +20 -0
- package/lib/domain/errors.js +22 -0
- package/lib/domain/errors.js.map +1 -0
- package/lib/domain/schemas.d.ts +28 -0
- package/lib/domain/schemas.js +445 -0
- package/lib/domain/schemas.js.map +1 -0
- package/lib/domain/types.d.ts +412 -0
- package/lib/domain/types.js +2 -0
- package/lib/domain/types.js.map +1 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +89 -0
- package/lib/index.js.map +1 -0
- package/lib/logger.d.ts +8 -0
- package/lib/logger.js +13 -0
- package/lib/logger.js.map +1 -0
- package/lib/memory/bridge.d.ts +18 -0
- package/lib/memory/bridge.js +13 -0
- package/lib/memory/bridge.js.map +1 -0
- package/lib/registry/integrity.d.ts +10 -0
- package/lib/registry/integrity.js +204 -0
- package/lib/registry/integrity.js.map +1 -0
- package/lib/registry/registry.d.ts +14 -0
- package/lib/registry/registry.js +182 -0
- package/lib/registry/registry.js.map +1 -0
- package/lib/resolver/merge.d.ts +23 -0
- package/lib/resolver/merge.js +90 -0
- package/lib/resolver/merge.js.map +1 -0
- package/lib/resolver/resolve.d.ts +18 -0
- package/lib/resolver/resolve.js +288 -0
- package/lib/resolver/resolve.js.map +1 -0
- package/lib/resolver/template.d.ts +10 -0
- package/lib/resolver/template.js +42 -0
- package/lib/resolver/template.js.map +1 -0
- package/lib/router/params.d.ts +3 -0
- package/lib/router/params.js +83 -0
- package/lib/router/params.js.map +1 -0
- package/lib/router/router.d.ts +11 -0
- package/lib/router/router.js +204 -0
- package/lib/router/router.js.map +1 -0
- package/lib/session/session.d.ts +16 -0
- package/lib/session/session.js +64 -0
- package/lib/session/session.js.map +1 -0
- package/lib/skill.d.ts +17 -0
- package/lib/skill.js +56 -0
- package/lib/skill.js.map +1 -0
- package/lib/storage/atomic.d.ts +10 -0
- package/lib/storage/atomic.js +107 -0
- package/lib/storage/atomic.js.map +1 -0
- package/lib/storage/paths.d.ts +10 -0
- package/lib/storage/paths.js +50 -0
- package/lib/storage/paths.js.map +1 -0
- package/lib/storage/records.d.ts +16 -0
- package/lib/storage/records.js +112 -0
- package/lib/storage/records.js.map +1 -0
- package/lib/storage/sessions.d.ts +17 -0
- package/lib/storage/sessions.js +65 -0
- package/lib/storage/sessions.js.map +1 -0
- package/lib/tools/tools.d.ts +14 -0
- package/lib/tools/tools.js +472 -0
- package/lib/tools/tools.js.map +1 -0
- package/lib/transport/http.d.ts +3 -0
- package/lib/transport/http.js +217 -0
- package/lib/transport/http.js.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +97 -4
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
# Data Model (V1)
|
|
2
|
+
|
|
3
|
+
Status: normative for V1 implementation.
|
|
4
|
+
|
|
5
|
+
Schema version: `1`.
|
|
6
|
+
|
|
7
|
+
This document defines the records stored by DSH Environment Manager, the validation rules applied to them, and the structured results returned by tools.
|
|
8
|
+
|
|
9
|
+
TypeScript declarations below are the source of truth for the Host implementation. YAML examples illustrate the on-disk form.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Conventions
|
|
14
|
+
|
|
15
|
+
### 1.1 Identifiers
|
|
16
|
+
|
|
17
|
+
- Pattern: `^[a-z0-9][a-z0-9-]{0,62}$` (1–63 characters).
|
|
18
|
+
- One namespace per record type: environment ids, service ids, deployment ids, rule profile ids and operation binding ids are unique within their own type.
|
|
19
|
+
- A record's `id` must equal its file name without extension.
|
|
20
|
+
|
|
21
|
+
### 1.2 Timestamps
|
|
22
|
+
|
|
23
|
+
- ISO 8601 with timezone, stored as UTC (`2026-11-01T03:00:00.000Z`).
|
|
24
|
+
- `createdAt` is set once; `updatedAt` changes on every write.
|
|
25
|
+
|
|
26
|
+
### 1.3 Record envelope
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
interface RecordMeta {
|
|
30
|
+
id: string;
|
|
31
|
+
schemaVersion: 1;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
updatedAt: string;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 1.4 Validation policy
|
|
38
|
+
|
|
39
|
+
- Unknown fields are rejected on write (`RECORD_INVALID`).
|
|
40
|
+
- On read, unknown fields are preserved and reported as `UNKNOWN_FIELD` warnings, so a
|
|
41
|
+
hand-edited file is inspectable before it is fixed.
|
|
42
|
+
- Records with an unknown `schemaVersion` are skipped on read with a warning diagnostic.
|
|
43
|
+
- Records failing schema validation are skipped on read with a warning; they are not deleted.
|
|
44
|
+
- No field may contain a plain-text secret. `credentialRef` is a reference only.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2. Enumerations
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
type EnvironmentCategory = 'dev' | 'test' | 'staging' | 'production' | 'custom';
|
|
52
|
+
|
|
53
|
+
type RuntimeMode = 'native' | 'docker' | 'kubernetes';
|
|
54
|
+
|
|
55
|
+
type HostKind = 'vm' | 'physical' | 'k8s-all-in-one' | 'k8s-worker' | 'pod-host';
|
|
56
|
+
|
|
57
|
+
type HostAccessMethod = 'ssh' | 'kubectl' | 'docker';
|
|
58
|
+
|
|
59
|
+
type ClusterTopology = 'all-in-one' | 'separate' | 'external';
|
|
60
|
+
|
|
61
|
+
type Status = 'configured-not-verified' | 'partially-verified' | 'verified';
|
|
62
|
+
|
|
63
|
+
type MatchLevel =
|
|
64
|
+
| 'deployment'
|
|
65
|
+
| 'environment-service'
|
|
66
|
+
| 'service'
|
|
67
|
+
| 'category-mode'
|
|
68
|
+
| 'global';
|
|
69
|
+
|
|
70
|
+
type Severity = 'info' | 'warning' | 'error';
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`EnvironmentCategory` drives the production policy check. `custom` never implies production.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 3. Environment
|
|
78
|
+
|
|
79
|
+
An environment is the unit of topology: roles, hosts, clusters and access endpoints travel together in one file. Hosts and clusters are embedded entities rather than top-level records; this keeps co-location edits atomic. Cross-environment host sharing is out of V1 scope (see §14).
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
interface LogicalRole {
|
|
83
|
+
id: string; // e.g. management, business, extension
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface Host {
|
|
89
|
+
id: string;
|
|
90
|
+
name: string; // hostname
|
|
91
|
+
kind?: HostKind; // defaults to vm; a machine, not a Pod object
|
|
92
|
+
os?: string;
|
|
93
|
+
platform?: string; // e.g. Linux / x86_64
|
|
94
|
+
ip?: string;
|
|
95
|
+
publicIp?: string;
|
|
96
|
+
sshPort?: number; // default 22
|
|
97
|
+
roles: string[]; // at least one declared LogicalRole id; many-to-many is expected
|
|
98
|
+
access?: HostAccessMethod[]; // command surfaces available on this machine
|
|
99
|
+
clusterRef?: string; // Cluster.id in this environment
|
|
100
|
+
adminUser?: string;
|
|
101
|
+
isActive?: boolean;
|
|
102
|
+
credentialRef?: string;
|
|
103
|
+
accounts?: HostAccount[]; // max 50 per host
|
|
104
|
+
note?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type LoginProtocol = 'ssh' | 'rdp' | 'web' | 'k8s-api' | 'docker' | 'custom';
|
|
108
|
+
type LoginAuthType = 'password' | 'private-key' | 'agent' | 'token' | 'other';
|
|
109
|
+
|
|
110
|
+
interface HostAccount {
|
|
111
|
+
id: string;
|
|
112
|
+
username: string;
|
|
113
|
+
purpose?: string;
|
|
114
|
+
protocol?: LoginProtocol; // defaults to ssh
|
|
115
|
+
loginUrl?: string; // web console entry URL; never a secret
|
|
116
|
+
port?: number;
|
|
117
|
+
authType?: LoginAuthType;
|
|
118
|
+
credentialRef?: string; // reference only; never a secret value
|
|
119
|
+
lastLoginAt?: string; // ISO-8601
|
|
120
|
+
lastLoginFrom?: string; // source IP or hostname
|
|
121
|
+
note?: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface Cluster {
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
kind: 'kubernetes' | 'docker';
|
|
128
|
+
topology?: ClusterTopology; // descriptive hint; per-host kind is authoritative
|
|
129
|
+
apiEndpoint?: string; // https://...
|
|
130
|
+
namespaces?: string[];
|
|
131
|
+
credentialRef?: string;
|
|
132
|
+
note?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface AccessEndpoint {
|
|
136
|
+
id: string;
|
|
137
|
+
kind: 'ssh' | 'web' | 'k8s-api' | 'docker' | 'custom';
|
|
138
|
+
hostRef?: string; // Host.id inside this environment
|
|
139
|
+
clusterRef?: string; // Cluster.id inside this environment
|
|
140
|
+
url?: string;
|
|
141
|
+
port?: number;
|
|
142
|
+
credentialRef?: string;
|
|
143
|
+
note?: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface LoginRecord {
|
|
147
|
+
id: string;
|
|
148
|
+
at: string; // ISO-8601 login time
|
|
149
|
+
username: string;
|
|
150
|
+
hostRef?: string; // Host.id inside this environment
|
|
151
|
+
accountId?: string; // HostAccount.id on hostRef
|
|
152
|
+
protocol?: LoginProtocol;
|
|
153
|
+
sourceIp?: string; // client / jump-host address
|
|
154
|
+
result: 'success' | 'failure';
|
|
155
|
+
note?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
interface Environment extends RecordMeta {
|
|
159
|
+
name: string;
|
|
160
|
+
aliases: string[];
|
|
161
|
+
category: EnvironmentCategory;
|
|
162
|
+
description?: string;
|
|
163
|
+
roles: LogicalRole[]; // at least one; standard four are seeded on create
|
|
164
|
+
hosts: Host[];
|
|
165
|
+
clusters: Cluster[];
|
|
166
|
+
endpoints: AccessEndpoint[];
|
|
167
|
+
loginRecords?: LoginRecord[]; // max 200; latest-first is a UI convention, not a schema rule
|
|
168
|
+
ruleOverrides?: RuleOverride; // see §7
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 3.1 Environment, plane and node
|
|
173
|
+
|
|
174
|
+
An **environment** is one complete network and one configuration page. Its management, business
|
|
175
|
+
and database planes are **role views over the same `hosts[]`**, not separate environments and not
|
|
176
|
+
duplicated host records. An environment may also have an expansion / other plane.
|
|
177
|
+
|
|
178
|
+
```text
|
|
179
|
+
Environment (one network, one YAML document)
|
|
180
|
+
├── LogicalRole: management
|
|
181
|
+
├── LogicalRole: business
|
|
182
|
+
├── LogicalRole: database
|
|
183
|
+
├── LogicalRole: extension
|
|
184
|
+
├── Host: management-plane-01 roles: [management]
|
|
185
|
+
├── Host: app-db-01 roles: [business, database]
|
|
186
|
+
└── Host: app-01 roles: [business]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Consequences:
|
|
190
|
+
|
|
191
|
+
- A host that serves both the management plane and the business plane is **one** `Host` with
|
|
192
|
+
`roles: [management, business]`.
|
|
193
|
+
- A host may belong to any number of declared roles; co-location is represented once.
|
|
194
|
+
- The UI renders one tab per role by filtering `hosts[]`; the same node therefore appears in every
|
|
195
|
+
plane it serves. No role-specific copy of the node exists.
|
|
196
|
+
- A plane with no nodes is valid (for example, a network without a database tier is valid). A node
|
|
197
|
+
with no role is **not** valid.
|
|
198
|
+
|
|
199
|
+
Standard role ids are `management`, `business`, `database` and `extension`. They are seeded on
|
|
200
|
+
environment creation so the four plane pages exist before the first node is added. Custom role ids
|
|
201
|
+
are allowed, but `roles` must contain at least one entry and every host must reference at least one
|
|
202
|
+
declared role.
|
|
203
|
+
|
|
204
|
+
### 3.2 Node kind, command surface and cluster topology
|
|
205
|
+
|
|
206
|
+
`Host.kind` classifies the **machine**; it is not a logical plane. `Host.access` records which
|
|
207
|
+
command surfaces are available on that machine; it is capability metadata, not authorization.
|
|
208
|
+
`Host.clusterRef` links a Kubernetes or Pod-hosting machine to a `Cluster` in the same
|
|
209
|
+
environment. A Pod is a workload, not a `Host`; the `pod-host` kind is the machine that runs
|
|
210
|
+
Pods and is operated with `docker exec`.
|
|
211
|
+
|
|
212
|
+
| `Host.kind` | Meaning | `clusterRef` | Form default access |
|
|
213
|
+
| --- | --- | --- | --- |
|
|
214
|
+
| `vm` | Generic virtual machine | optional | `[ssh]` |
|
|
215
|
+
| `physical` | Generic physical machine | optional | `[ssh]` |
|
|
216
|
+
| `k8s-all-in-one` | Control plane and worker on the same machine | required | `[ssh, kubectl, docker]` |
|
|
217
|
+
| `k8s-worker` | Worker node in a Kubernetes cluster | required | `[ssh, kubectl]` |
|
|
218
|
+
| `pod-host` | Machine running Pods, operated through `docker exec` | optional but recommended | `[ssh, docker]` |
|
|
219
|
+
|
|
220
|
+
Rules:
|
|
221
|
+
|
|
222
|
+
- `Environment.hosts[].kind` defaults to `vm` when omitted, so existing records remain readable.
|
|
223
|
+
- `Environment.hosts[].access` may be omitted for legacy records; the UI then shows no command
|
|
224
|
+
surface. The form pre-fills the defaults above, but the operator can change them.
|
|
225
|
+
- A `k8s-all-in-one` or `k8s-worker` host must reference an existing `Cluster.id`; a missing or
|
|
226
|
+
unknown `clusterRef` is a validation error.
|
|
227
|
+
- A `pod-host` may omit `clusterRef` when it is a Docker-only machine outside this environment's
|
|
228
|
+
Kubernetes cluster. When it does reference a cluster, the reference must exist.
|
|
229
|
+
- `access` values are `ssh`, `kubectl` and `docker`. The kind does not restrict access: a worker
|
|
230
|
+
may also have Docker, and a VM may have `kubectl`. Duplicate access values are rejected.
|
|
231
|
+
- `Cluster.topology` is optional and descriptive: `all-in-one`, `separate` or `external`. It does
|
|
232
|
+
not replace per-host `kind`; a legacy cluster without topology is shown as `—`. The schema does
|
|
233
|
+
not enforce a particular mix of node kinds, because environments are often migrated incrementally.
|
|
234
|
+
- The UI can add and edit clusters, but cluster deletion remains a YAML operation so a referenced
|
|
235
|
+
cluster cannot disappear by accident.
|
|
236
|
+
|
|
237
|
+
```yaml
|
|
238
|
+
hosts:
|
|
239
|
+
- id: k8s-aio-01
|
|
240
|
+
name: k8s-aio-01
|
|
241
|
+
kind: k8s-all-in-one
|
|
242
|
+
clusterRef: k8s-prod
|
|
243
|
+
access: [ssh, kubectl, docker]
|
|
244
|
+
roles: [management, business]
|
|
245
|
+
ip: 10.0.1.20
|
|
246
|
+
- id: pod-host-01
|
|
247
|
+
name: pod-host-01
|
|
248
|
+
kind: pod-host
|
|
249
|
+
clusterRef: k8s-prod
|
|
250
|
+
access: [ssh, docker]
|
|
251
|
+
roles: [business]
|
|
252
|
+
ip: 10.0.1.21
|
|
253
|
+
- id: worker-01
|
|
254
|
+
name: worker-01
|
|
255
|
+
kind: k8s-worker
|
|
256
|
+
clusterRef: k8s-prod
|
|
257
|
+
access: [ssh, kubectl]
|
|
258
|
+
roles: [business]
|
|
259
|
+
ip: 10.0.1.22
|
|
260
|
+
|
|
261
|
+
clusters:
|
|
262
|
+
- id: k8s-prod
|
|
263
|
+
name: production-k8s
|
|
264
|
+
kind: kubernetes
|
|
265
|
+
topology: all-in-one
|
|
266
|
+
apiEndpoint: https://10.0.1.20:6443
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### 3.3 Co-location rule
|
|
270
|
+
|
|
271
|
+
A host that serves both the management plane and the business plane is **one** `Host` with `roles: [management, business]`.
|
|
272
|
+
|
|
273
|
+
Forbidden: two host entries with the same hostname or IP that differ only by role.
|
|
274
|
+
|
|
275
|
+
Validation must reject duplicate host ids within an environment and duplicate `ip` values within
|
|
276
|
+
an environment. A host must declare at least one role, and every declared role id must exist in
|
|
277
|
+
`Environment.roles`.
|
|
278
|
+
|
|
279
|
+
### 3.4 Accounts and login audit
|
|
280
|
+
|
|
281
|
+
`Host.accounts` records the identities used to access a host. Each account carries a purpose,
|
|
282
|
+
protocol, authentication type, credential reference, and the last observed login time/source.
|
|
283
|
+
For a web console, `loginUrl` stores the entry URL and `username` stores the login name; a
|
|
284
|
+
web account is still attached to the machine or business host that exposes the console.
|
|
285
|
+
`loginUrl` is optional but must be an absolute `http(s)` URL.
|
|
286
|
+
Accounts never carry password, key, token, or kubeconfig material; only `credentialRef` is stored.
|
|
287
|
+
When the node form or account form receives a password, the Client writes it through the DSH
|
|
288
|
+
credential service and stores the generated reference; the password is not written to YAML.
|
|
289
|
+
|
|
290
|
+
`Environment.loginRecords` records login observations, including failed attempts. Records are
|
|
291
|
+
bounded (200 per environment) and may reference a host and one of its accounts. Entries are
|
|
292
|
+
stored in the environment YAML rather than a separate record kind so a topology change and its
|
|
293
|
+
audit context remain one atomic write. The UI sorts them newest-first.
|
|
294
|
+
|
|
295
|
+
These fields are intentionally lightweight and optional. They are not a replacement for a
|
|
296
|
+
privileged-access-management (PAM) or SIEM system; they are context for DSH sessions.
|
|
297
|
+
|
|
298
|
+
### 3.5 YAML example
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
id: test-a
|
|
302
|
+
schemaVersion: 1
|
|
303
|
+
name: Test Environment A
|
|
304
|
+
aliases: [ta, test-a]
|
|
305
|
+
category: test
|
|
306
|
+
description: Main test environment for aicoservice
|
|
307
|
+
roles:
|
|
308
|
+
- id: management
|
|
309
|
+
name: Management Plane
|
|
310
|
+
- id: business
|
|
311
|
+
name: Business Plane
|
|
312
|
+
hosts:
|
|
313
|
+
- id: host-001
|
|
314
|
+
name: test-a-node-01
|
|
315
|
+
kind: vm
|
|
316
|
+
os: ubuntu-22.04
|
|
317
|
+
platform: Linux / x86_64
|
|
318
|
+
ip: 10.0.1.10
|
|
319
|
+
sshPort: 22
|
|
320
|
+
roles: [management, business]
|
|
321
|
+
access: [ssh]
|
|
322
|
+
adminUser: root
|
|
323
|
+
isActive: true
|
|
324
|
+
credentialRef: cred-test-a-root
|
|
325
|
+
accounts:
|
|
326
|
+
- id: root
|
|
327
|
+
username: root
|
|
328
|
+
purpose: system administration
|
|
329
|
+
protocol: ssh
|
|
330
|
+
port: 22
|
|
331
|
+
authType: private-key
|
|
332
|
+
credentialRef: cred-test-a-root
|
|
333
|
+
lastLoginAt: 2026-10-31T08:12:00.000Z
|
|
334
|
+
lastLoginFrom: 10.0.8.21
|
|
335
|
+
- id: console
|
|
336
|
+
username: ops
|
|
337
|
+
purpose: business console
|
|
338
|
+
protocol: web
|
|
339
|
+
loginUrl: https://test-a.example.internal/login
|
|
340
|
+
authType: password
|
|
341
|
+
credentialRef: env-manager/test-a/console
|
|
342
|
+
clusters:
|
|
343
|
+
- id: k8s-01
|
|
344
|
+
name: test-a-k8s
|
|
345
|
+
kind: kubernetes
|
|
346
|
+
topology: separate
|
|
347
|
+
apiEndpoint: https://10.0.1.20:6443
|
|
348
|
+
namespaces: [dv-sop]
|
|
349
|
+
credentialRef: cred-test-a-kubeconfig
|
|
350
|
+
endpoints:
|
|
351
|
+
- id: ep-ssh-01
|
|
352
|
+
kind: ssh
|
|
353
|
+
hostRef: host-001
|
|
354
|
+
port: 22
|
|
355
|
+
credentialRef: cred-test-a-root
|
|
356
|
+
- id: ep-web-01
|
|
357
|
+
kind: web
|
|
358
|
+
url: https://test-a.example.internal
|
|
359
|
+
loginRecords:
|
|
360
|
+
- id: login-0001
|
|
361
|
+
at: 2026-10-31T08:12:00.000Z
|
|
362
|
+
username: root
|
|
363
|
+
hostRef: host-001
|
|
364
|
+
accountId: root
|
|
365
|
+
protocol: ssh
|
|
366
|
+
sourceIp: 10.0.8.21
|
|
367
|
+
result: success
|
|
368
|
+
note: jump-host session
|
|
369
|
+
createdAt: 2026-11-01T03:00:00.000Z
|
|
370
|
+
updatedAt: 2026-11-01T03:00:00.000Z
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 4. Service
|
|
376
|
+
|
|
377
|
+
A service is environment-independent. Deployment placement lives in §5.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
interface GitInfo {
|
|
381
|
+
url: string;
|
|
382
|
+
defaultBranch: string; // default main
|
|
383
|
+
modulePath?: string; // monorepo subpath
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
interface BuildInfo {
|
|
387
|
+
tool: 'maven' | 'gradle' | 'npm' | 'custom';
|
|
388
|
+
command?: string;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
interface ArtifactInfo {
|
|
392
|
+
name: string; // e.g. aicoservice.jar
|
|
393
|
+
path?: string; // template allowed
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
interface Service extends RecordMeta {
|
|
397
|
+
name: string;
|
|
398
|
+
aliases: string[];
|
|
399
|
+
category: string; // free-form service category, e.g. backend
|
|
400
|
+
description?: string;
|
|
401
|
+
git: GitInfo;
|
|
402
|
+
build?: BuildInfo;
|
|
403
|
+
artifact?: ArtifactInfo;
|
|
404
|
+
deployModes: RuntimeMode[]; // at least one
|
|
405
|
+
ruleProfileRefs: string[]; // ordered; later profiles win on field conflicts
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 4.1 YAML example
|
|
410
|
+
|
|
411
|
+
```yaml
|
|
412
|
+
id: aicoservice
|
|
413
|
+
schemaVersion: 1
|
|
414
|
+
name: Aico Service
|
|
415
|
+
aliases: [aicoservice]
|
|
416
|
+
category: backend
|
|
417
|
+
description: Core AI coordination service
|
|
418
|
+
git:
|
|
419
|
+
url: https://git.example.internal/team/aicoservice.git
|
|
420
|
+
defaultBranch: main
|
|
421
|
+
build:
|
|
422
|
+
tool: maven
|
|
423
|
+
artifact:
|
|
424
|
+
name: aicoservice.jar
|
|
425
|
+
deployModes: [native, docker, kubernetes]
|
|
426
|
+
ruleProfileRefs: [java-common, company-default]
|
|
427
|
+
createdAt: 2026-11-01T03:00:00.000Z
|
|
428
|
+
updatedAt: 2026-11-01T03:00:00.000Z
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## 5. Deployment Binding
|
|
434
|
+
|
|
435
|
+
A deployment binding states which service runs where. A service may have several bindings in the same environment; `(environmentId, serviceId)` is therefore not unique.
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
interface NativeLocation {
|
|
439
|
+
kind: 'native';
|
|
440
|
+
hostRef: string; // Host.id in the referenced environment
|
|
441
|
+
installDir?: string;
|
|
442
|
+
serviceName?: string; // systemd unit or Windows service name
|
|
443
|
+
processMatch?: string; // process command-line fragment
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
interface DockerLocation {
|
|
447
|
+
kind: 'docker';
|
|
448
|
+
hostRef: string;
|
|
449
|
+
containerName?: string;
|
|
450
|
+
image?: string;
|
|
451
|
+
volumes?: string[];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
interface KubernetesLocation {
|
|
455
|
+
kind: 'kubernetes';
|
|
456
|
+
clusterRef: string; // Cluster.id in the referenced environment
|
|
457
|
+
namespace: string;
|
|
458
|
+
workload: {
|
|
459
|
+
kind: 'Deployment' | 'StatefulSet' | 'DaemonSet' | 'CronJob' | 'Pod';
|
|
460
|
+
name: string;
|
|
461
|
+
};
|
|
462
|
+
container?: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
type DeploymentLocation =
|
|
466
|
+
| NativeLocation
|
|
467
|
+
| DockerLocation
|
|
468
|
+
| KubernetesLocation;
|
|
469
|
+
|
|
470
|
+
interface DeploymentBinding extends RecordMeta {
|
|
471
|
+
name?: string;
|
|
472
|
+
environmentId: string;
|
|
473
|
+
serviceId: string;
|
|
474
|
+
runtimeMode: RuntimeMode;
|
|
475
|
+
location: DeploymentLocation;
|
|
476
|
+
variables?: Record<string, string | number | boolean>;
|
|
477
|
+
ruleOverrides?: RuleOverride;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Validation: `runtimeMode` must equal `location.kind`; the referenced host/cluster must exist in the referenced environment; the service must list `runtimeMode` in `deployModes`.
|
|
482
|
+
|
|
483
|
+
### 5.1 YAML example
|
|
484
|
+
|
|
485
|
+
```yaml
|
|
486
|
+
id: deploy-001
|
|
487
|
+
schemaVersion: 1
|
|
488
|
+
name: aicoservice-test-a
|
|
489
|
+
environmentId: test-a
|
|
490
|
+
serviceId: aicoservice
|
|
491
|
+
runtimeMode: kubernetes
|
|
492
|
+
location:
|
|
493
|
+
kind: kubernetes
|
|
494
|
+
clusterRef: k8s-01
|
|
495
|
+
namespace: dv-sop
|
|
496
|
+
workload:
|
|
497
|
+
kind: Deployment
|
|
498
|
+
name: aicoservice
|
|
499
|
+
container: aicoservice
|
|
500
|
+
variables:
|
|
501
|
+
replicaCount: 2
|
|
502
|
+
createdAt: 2026-11-01T03:00:00.000Z
|
|
503
|
+
updatedAt: 2026-11-01T03:00:00.000Z
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 6. Rule Profile
|
|
509
|
+
|
|
510
|
+
A rule profile is a shared template bundle. Overrides live on the environment (§3) and the deployment (§5).
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
interface LogSourceFile {
|
|
514
|
+
kind: 'file';
|
|
515
|
+
path: string; // template allowed
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
interface LogSourceContainer {
|
|
519
|
+
kind: 'container-stdout';
|
|
520
|
+
container: string; // template allowed
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
interface LogSourcePod {
|
|
524
|
+
kind: 'pod-logs';
|
|
525
|
+
namespace: string; // template allowed
|
|
526
|
+
workload: string; // template allowed
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
type LogSource = LogSourceFile | LogSourceContainer | LogSourcePod;
|
|
530
|
+
|
|
531
|
+
interface ClassLocation {
|
|
532
|
+
kind: 'build-output' | 'deployed-dir' | 'jar-entry';
|
|
533
|
+
path: string; // directory, or jar path for jar-entry
|
|
534
|
+
entryPrefix?: string; // jar-internal class prefix
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
interface RuleOverride {
|
|
538
|
+
variables?: Record<string, string | number | boolean>;
|
|
539
|
+
paths?: Record<string, string>;
|
|
540
|
+
logSources?: Partial<Record<RuntimeMode, LogSource>>;
|
|
541
|
+
classLocations?: Partial<Record<RuntimeMode, ClassLocation>>;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
interface RuleProfile extends RecordMeta {
|
|
545
|
+
name?: string;
|
|
546
|
+
description?: string;
|
|
547
|
+
variables?: Record<string, string | number | boolean>;
|
|
548
|
+
paths?: Record<string, string>;
|
|
549
|
+
logSources?: Partial<Record<RuntimeMode, LogSource>>;
|
|
550
|
+
classLocations?: Partial<Record<RuntimeMode, ClassLocation>>;
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
`classLocation.kind` distinguishes the three real sources: build output (`target/classes`), a deployed directory of classes, or an entry inside a JAR. V1 never collapses these into one path.
|
|
555
|
+
|
|
556
|
+
### 6.1 YAML example
|
|
557
|
+
|
|
558
|
+
```yaml
|
|
559
|
+
id: java-common
|
|
560
|
+
schemaVersion: 1
|
|
561
|
+
description: Common Java service paths and log sources
|
|
562
|
+
variables:
|
|
563
|
+
installRoot: /opt/company/services
|
|
564
|
+
paths:
|
|
565
|
+
serviceHome: ${installRoot}/${service.id}
|
|
566
|
+
jar: ${paths.serviceHome}/lib/${service.artifact.name}
|
|
567
|
+
logDir: ${paths.serviceHome}/logs
|
|
568
|
+
applicationLog: ${paths.logDir}/${service.id}.log
|
|
569
|
+
logSources:
|
|
570
|
+
native:
|
|
571
|
+
kind: file
|
|
572
|
+
path: ${paths.applicationLog}
|
|
573
|
+
docker:
|
|
574
|
+
kind: container-stdout
|
|
575
|
+
container: ${deployment.location.containerName}
|
|
576
|
+
kubernetes:
|
|
577
|
+
kind: pod-logs
|
|
578
|
+
namespace: ${deployment.location.namespace}
|
|
579
|
+
workload: ${deployment.location.workload.name}
|
|
580
|
+
classLocations:
|
|
581
|
+
native:
|
|
582
|
+
kind: deployed-dir
|
|
583
|
+
path: ${paths.serviceHome}/classes
|
|
584
|
+
kubernetes:
|
|
585
|
+
kind: jar-entry
|
|
586
|
+
path: ${paths.jar}
|
|
587
|
+
entryPrefix: com/company/aico
|
|
588
|
+
createdAt: 2026-11-01T03:00:00.000Z
|
|
589
|
+
updatedAt: 2026-11-01T03:00:00.000Z
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## 7. Operation Binding
|
|
595
|
+
|
|
596
|
+
```ts
|
|
597
|
+
interface OperationMatch {
|
|
598
|
+
environmentId?: string;
|
|
599
|
+
serviceId?: string;
|
|
600
|
+
deploymentId?: string;
|
|
601
|
+
deploymentMode?: RuntimeMode;
|
|
602
|
+
serviceCategory?: string;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
interface SkillReference {
|
|
606
|
+
name: string;
|
|
607
|
+
expectedSource?: string | null; // SkillSummary.source, e.g. project-dsh
|
|
608
|
+
expectedProvider?: string | null; // SkillSummary.provider
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
interface OperationPolicy {
|
|
612
|
+
requiresApproval: boolean;
|
|
613
|
+
allowProduction: boolean;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
interface OperationBinding extends RecordMeta {
|
|
617
|
+
operation: string; // standard id or custom id
|
|
618
|
+
match: OperationMatch; // empty object means global
|
|
619
|
+
skill: SkillReference;
|
|
620
|
+
parameters?: Record<string, string>;
|
|
621
|
+
policy: OperationPolicy;
|
|
622
|
+
enabled: boolean;
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`match` must be one of five shapes; any other combination is `RECORD_INVALID`:
|
|
627
|
+
|
|
628
|
+
| Shape | Level |
|
|
629
|
+
|---|---|
|
|
630
|
+
| `{ deploymentId }` plus optional `environmentId`/`serviceId` | `deployment` |
|
|
631
|
+
| `{ environmentId, serviceId }` | `environment-service` |
|
|
632
|
+
| `{ serviceId }` | `service` |
|
|
633
|
+
| `{ serviceCategory, deploymentMode }` | `category-mode` |
|
|
634
|
+
| `{}` | `global` |
|
|
635
|
+
|
|
636
|
+
`environmentId` alone, `deploymentMode` alone, or `environmentId` + `deploymentMode` are
|
|
637
|
+
invalid: they would create a sixth level with no defined precedence.
|
|
638
|
+
|
|
639
|
+
### 7.1 Standard operation ids
|
|
640
|
+
|
|
641
|
+
```text
|
|
642
|
+
install, deploy, redeploy, replace, restart, upgrade, rollback, uninstall,
|
|
643
|
+
collect-logs, health-check, inspect, upload, download
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Any other id is treated as a custom operation and must appear explicitly in a binding.
|
|
647
|
+
|
|
648
|
+
### 7.2 Write operations
|
|
649
|
+
|
|
650
|
+
`install`, `deploy`, `redeploy`, `replace`, `restart`, `upgrade`, `rollback`, `uninstall`, `upload` are write operations. For them, `policy.requiresApproval` defaults to `true` when the field is omitted, and a binding cannot implicitly grant production access.
|
|
651
|
+
|
|
652
|
+
### 7.3 YAML example
|
|
653
|
+
|
|
654
|
+
```yaml
|
|
655
|
+
id: binding-001
|
|
656
|
+
schemaVersion: 1
|
|
657
|
+
operation: redeploy
|
|
658
|
+
match:
|
|
659
|
+
environmentId: test-a
|
|
660
|
+
serviceId: aicoservice
|
|
661
|
+
skill:
|
|
662
|
+
name: redeploy-k8s-service
|
|
663
|
+
expectedSource: null
|
|
664
|
+
expectedProvider: null
|
|
665
|
+
parameters:
|
|
666
|
+
namespace: ${deployment.location.namespace}
|
|
667
|
+
workload: ${deployment.location.workload.name}
|
|
668
|
+
cluster: ${deployment.location.clusterRef}
|
|
669
|
+
policy:
|
|
670
|
+
requiresApproval: true
|
|
671
|
+
allowProduction: false
|
|
672
|
+
enabled: true
|
|
673
|
+
createdAt: 2026-11-01T03:00:00.000Z
|
|
674
|
+
updatedAt: 2026-11-01T03:00:00.000Z
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 8. Resolved results
|
|
680
|
+
|
|
681
|
+
### 8.1 Rule source and provenance
|
|
682
|
+
|
|
683
|
+
```ts
|
|
684
|
+
type RuleLayer = 'global' | 'service' | 'environment' | 'deployment';
|
|
685
|
+
|
|
686
|
+
interface RuleSource {
|
|
687
|
+
layer: RuleLayer;
|
|
688
|
+
ruleProfileId?: string;
|
|
689
|
+
recordId?: string; // environmentId / deploymentId / serviceId
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
interface ProvenanceInput {
|
|
693
|
+
name: string; // e.g. paths.logDir
|
|
694
|
+
source: RuleSource;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
interface ProvenanceEntry {
|
|
698
|
+
field: string; // e.g. paths.applicationLog, logSources.kubernetes.namespace
|
|
699
|
+
definedBy: RuleSource; // layer that supplied the winning template
|
|
700
|
+
inputs: ProvenanceInput[]; // referenced fields and their winning sources
|
|
701
|
+
shadowed: RuleSource[]; // lower-priority definitions that lost
|
|
702
|
+
value: string | number | boolean;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
interface ResolvedField<T> {
|
|
706
|
+
value: T;
|
|
707
|
+
provenance: ProvenanceEntry;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
interface ResolvedStructured<T> {
|
|
711
|
+
value: T;
|
|
712
|
+
provenance: ProvenanceEntry[]; // one entry per property
|
|
713
|
+
}
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
`definedBy` answers "where did this template come from". `inputs` answers "what influenced the final value". Both are required: an override of `paths.logDir` changes `paths.applicationLog` even though the latter was defined at the service layer.
|
|
717
|
+
|
|
718
|
+
### 8.2 Service Context
|
|
719
|
+
|
|
720
|
+
```ts
|
|
721
|
+
interface CredentialStatus {
|
|
722
|
+
ref: string;
|
|
723
|
+
configured: boolean;
|
|
724
|
+
source?: string; // where the value lives, e.g. profile-vault
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
interface ServiceContext {
|
|
728
|
+
environmentId: string;
|
|
729
|
+
environmentName: string;
|
|
730
|
+
environmentCategory: EnvironmentCategory;
|
|
731
|
+
serviceId: string;
|
|
732
|
+
serviceName: string;
|
|
733
|
+
serviceCategory: string;
|
|
734
|
+
deploymentId: string;
|
|
735
|
+
deploymentName?: string;
|
|
736
|
+
runtimeMode: RuntimeMode;
|
|
737
|
+
source: {
|
|
738
|
+
gitUrl?: string;
|
|
739
|
+
defaultBranch?: string;
|
|
740
|
+
modulePath?: string;
|
|
741
|
+
};
|
|
742
|
+
artifact?: {
|
|
743
|
+
name: string;
|
|
744
|
+
path?: string;
|
|
745
|
+
};
|
|
746
|
+
location: DeploymentLocation;
|
|
747
|
+
variables: Record<string, ResolvedField<string | number | boolean>>;
|
|
748
|
+
paths: Record<string, ResolvedField<string>>;
|
|
749
|
+
logSource?: ResolvedStructured<LogSource>;
|
|
750
|
+
classLocation?: ResolvedStructured<ClassLocation>;
|
|
751
|
+
ruleProfiles: string[];
|
|
752
|
+
status: Status;
|
|
753
|
+
credentials: CredentialStatus[];
|
|
754
|
+
diagnostics: Diagnostic[];
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
`status` starts as `configured-not-verified`. Without an online check, V1 must not claim `verified`.
|
|
759
|
+
|
|
760
|
+
### 8.3 Operation Resolution
|
|
761
|
+
|
|
762
|
+
```ts
|
|
763
|
+
interface Diagnostic {
|
|
764
|
+
code: string;
|
|
765
|
+
severity: Severity;
|
|
766
|
+
message: string;
|
|
767
|
+
file?: string; // registry-relative file name
|
|
768
|
+
path?: string; // field path, e.g. ruleProfileRefs[0]
|
|
769
|
+
details?: Record<string, unknown>;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
interface ResolvedSkillReference {
|
|
773
|
+
name: string;
|
|
774
|
+
source?: string | null;
|
|
775
|
+
provider?: string | null;
|
|
776
|
+
available: boolean | 'unknown';
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
interface OperationResolution {
|
|
780
|
+
ok: boolean;
|
|
781
|
+
operation: string;
|
|
782
|
+
requested: {
|
|
783
|
+
environmentId: string;
|
|
784
|
+
serviceId?: string;
|
|
785
|
+
deploymentId?: string;
|
|
786
|
+
};
|
|
787
|
+
matchedBindingId?: string;
|
|
788
|
+
matchLevel?: MatchLevel;
|
|
789
|
+
skill?: ResolvedSkillReference;
|
|
790
|
+
parameters?: Record<string, string>;
|
|
791
|
+
policy?: OperationPolicy;
|
|
792
|
+
diagnostics: Diagnostic[];
|
|
793
|
+
}
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
---
|
|
797
|
+
|
|
798
|
+
## 9. Session context
|
|
799
|
+
|
|
800
|
+
```ts
|
|
801
|
+
interface SessionContext {
|
|
802
|
+
sessionId: string;
|
|
803
|
+
defaultEnvironmentId: string | null;
|
|
804
|
+
persisted: boolean;
|
|
805
|
+
updatedAt: string;
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Session context is per session and never mutates registry records. It is stored under `sessions/` in the plugin data directory, keyed by session id.
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## 10. Operation record (P1 boundary)
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
interface OperationRecord {
|
|
817
|
+
id: string;
|
|
818
|
+
sessionId: string;
|
|
819
|
+
environmentId: string;
|
|
820
|
+
serviceId?: string;
|
|
821
|
+
deploymentId?: string;
|
|
822
|
+
operation: string;
|
|
823
|
+
skillRef?: string;
|
|
824
|
+
startedAt: string;
|
|
825
|
+
finishedAt?: string;
|
|
826
|
+
outcome: 'success' | 'failure' | 'unknown';
|
|
827
|
+
evidenceRefs: string[]; // opaque, sanitized references
|
|
828
|
+
summary: string;
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
V1 defines the type and a no-op bridge; it does not persist operation records itself.
|
|
833
|
+
|
|
834
|
+
---
|
|
835
|
+
|
|
836
|
+
## 11. Diagnostics catalogue
|
|
837
|
+
|
|
838
|
+
| Code | Severity | Meaning |
|
|
839
|
+
|---|---|---|
|
|
840
|
+
| `ENV_NOT_FOUND` | error | Environment id does not exist |
|
|
841
|
+
| `SERVICE_NOT_FOUND` | error | Service id does not exist |
|
|
842
|
+
| `DEPLOYMENT_NOT_FOUND` | error | Deployment id does not exist |
|
|
843
|
+
| `HOST_NOT_FOUND` | error | Deployment host reference, or a login record's host reference, is missing |
|
|
844
|
+
| `CLUSTER_NOT_FOUND` | error | Deployment cluster reference is missing in the environment |
|
|
845
|
+
| `DUPLICATE_HOST_ID` | warning | Two hosts in one environment share an id |
|
|
846
|
+
| `DUPLICATE_HOST_IP` | warning | Two hosts in one environment share an IP |
|
|
847
|
+
| `DUPLICATE_ACCOUNT_ID` | warning | Two accounts on one host share an id |
|
|
848
|
+
| `ACCOUNT_NOT_FOUND` | warning | A login record references an account that is not on its host |
|
|
849
|
+
| `RULE_PROFILE_NOT_FOUND` | error | `ruleProfileRefs` names a missing profile |
|
|
850
|
+
| `DEPLOYMENT_AMBIGUOUS` | error | No deployment id given and several bindings match |
|
|
851
|
+
| `DUPLICATE_ID` | warning | Two files declare the same id; the later file is skipped |
|
|
852
|
+
| `BINDING_NOT_FOUND` | error | No operation binding matches the context |
|
|
853
|
+
| `RECORD_INVALID` | error | Record failed schema validation |
|
|
854
|
+
| `RECORD_IN_USE` | error | Record is still referenced and cannot be deleted |
|
|
855
|
+
| `RECORD_LIMIT_EXCEEDED` | error | Record count would exceed `maxRecords` |
|
|
856
|
+
| `RECORD_TOO_LARGE` | error | Serialized record exceeds 1 MiB |
|
|
857
|
+
| `INVALID_ARGUMENT` | error | Tool or request input failed validation |
|
|
858
|
+
| `FORBIDDEN` | error | Request rejected by transport policy |
|
|
859
|
+
| `STORAGE_UNWRITABLE` | error | Data directory cannot be written |
|
|
860
|
+
| `SCHEMA_UNSUPPORTED` | warning | Unknown `schemaVersion`, record skipped |
|
|
861
|
+
| `UNKNOWN_FIELD` | warning | Read-only: a field outside the schema is present |
|
|
862
|
+
| `RULE_TEMPLATE_INVALID` | error | Malformed `${...}` or identifier |
|
|
863
|
+
| `RULE_UNDEFINED_VARIABLE` | error | Template references an unknown field |
|
|
864
|
+
| `RULE_CYCLE` | error | Template variable cycle |
|
|
865
|
+
| `RULE_PATH_TRAVERSAL` | error | Resolved path contains `..` |
|
|
866
|
+
| `RULE_TYPE_MISMATCH` | error | Template used where a non-string value is required |
|
|
867
|
+
| `OPERATION_UNKNOWN` | error | Operation is neither standard nor bound |
|
|
868
|
+
| `BINDING_CONFLICT` | error | Multiple bindings at the highest match level |
|
|
869
|
+
| `SKILL_UNAVAILABLE` | error | Skill not registered, or source/provider mismatch |
|
|
870
|
+
| `SKILL_REGISTRY_UNAVAILABLE` | warning | Skill registry cannot be queried; availability unknown |
|
|
871
|
+
| `POLICY_DENIED` | error | Production policy or approval policy rejects the operation |
|
|
872
|
+
| `PARAMETER_RESOLUTION_FAILED` | error | Parameter template failed to resolve |
|
|
873
|
+
| `CREDENTIAL_MISSING` | warning | `credentialRef` has no configured value |
|
|
874
|
+
| `MEMORY_BRIDGE_UNAVAILABLE` | warning | Memory plugin not installed |
|
|
875
|
+
| `SESSION_CONTEXT_UNAVAILABLE` | error | A session-scoped tool ran without an owning agent |
|
|
876
|
+
| `SESSION_CONTEXT_UNPERSISTED` | warning | Session state could not be written |
|
|
877
|
+
| `INTERNAL_ERROR` | error | Unexpected failure, message redacted |
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
## 12. Referential integrity
|
|
882
|
+
|
|
883
|
+
Validated on every write and by `env_validate`:
|
|
884
|
+
|
|
885
|
+
1. `DeploymentBinding.environmentId` exists.
|
|
886
|
+
2. `DeploymentBinding.serviceId` exists.
|
|
887
|
+
3. `DeploymentBinding.location.hostRef` exists in that environment (native/docker).
|
|
888
|
+
4. `DeploymentBinding.location.clusterRef` exists in that environment (kubernetes).
|
|
889
|
+
5. `Service.ruleProfileRefs` all exist.
|
|
890
|
+
6. `Environment.hosts[].roles` reference declared roles.
|
|
891
|
+
7. `Environment.endpoints[].hostRef` / `clusterRef` exist in that environment.
|
|
892
|
+
8. `Environment.hosts[].accounts[].id` are unique within their host.
|
|
893
|
+
9. `Environment.loginRecords[].hostRef` exists in that environment.
|
|
894
|
+
10. `Environment.loginRecords[].accountId` exists on the referenced host when both are set.
|
|
895
|
+
11. `OperationBinding` match references (when present) exist.
|
|
896
|
+
12. `OperationBinding.skill.name` is non-empty.
|
|
897
|
+
|
|
898
|
+
Duplicate host ids and duplicate host IPs are also reported as integrity errors.
|
|
899
|
+
Schema validation additionally rejects an environment whose `roles` is empty, and a host whose
|
|
900
|
+
`roles` is empty. A host role that is not declared in `Environment.roles` is reported as
|
|
901
|
+
`ROLE_NOT_FOUND` by integrity validation.
|
|
902
|
+
|
|
903
|
+
Broken references are reported, never silently repaired.
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## 13. Identity and cardinality
|
|
908
|
+
|
|
909
|
+
```text
|
|
910
|
+
Environment 1 ── N Host
|
|
911
|
+
Environment 1 ── N Cluster
|
|
912
|
+
Environment 1 ── N Role
|
|
913
|
+
Host N ── N Role (via Host.roles)
|
|
914
|
+
Host N ── N Environment (out of V1; hosts are embedded)
|
|
915
|
+
Service 1 ── N DeploymentBinding
|
|
916
|
+
Environment 1 ── N DeploymentBinding
|
|
917
|
+
Service 1 ── N RuleProfile (ordered ruleProfileRefs)
|
|
918
|
+
OperationBinding N ── predicate over Environment/Service/Deployment
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
---
|
|
922
|
+
|
|
923
|
+
## 14. Out-of-scope model changes
|
|
924
|
+
|
|
925
|
+
The following are deliberately postponed:
|
|
926
|
+
|
|
927
|
+
- Extracting hosts into a top-level registry for cross-environment sharing.
|
|
928
|
+
- Storing discovered assets separately from declared assets.
|
|
929
|
+
- Vector or embedding-based retrieval for services.
|
|
930
|
+
- Per-user permissions inside one DSH profile.
|