@elinpf/dsh-ops-access 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/README.i18n.yaml +4 -0
- package/README.md +42 -0
- package/README.zh.md +42 -0
- package/cordis.patch.yml +1 -0
- package/lib/index.d.ts +75 -0
- package/lib/index.js +1008 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.js +28 -0
- package/lib/mention.d.ts +59 -0
- package/lib/mention.js +93 -0
- package/lib/types.d.ts +268 -0
- package/lib/types.js +10 -0
- package/package.json +73 -0
package/README.i18n.yaml
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @elinpf/dsh-ops-access
|
|
2
|
+
|
|
3
|
+
The ops access capability seam — owns the YAML credential registry (default `~/.dsh-ops/access.yaml`) and exposes `ctx.opsAccess` (resolve / list / register) to provider plugins and consumer tools.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Single registry file, zero cache**: every `resolve`/`list`/`writeEntry` re-reads, re-parses, and re-validates the YAML — edits take effect immediately, no restart.
|
|
8
|
+
- **Tiered entries**: each profile carries an `ro` tier (agent-readable default) and an `rw` tier (served only through a registered broker grant).
|
|
9
|
+
- **Provider seam**: one provider per credential kind (`k8s`/`ceph`/`ssh` packages) supplies only a zod schema plus field processing (`~` expansion, content validation, capability probe). Providers register via `registerAccessProvider(ctx, provider)` — never hand-write `ctx.inject` for sibling services, it deadlocks the loader.
|
|
10
|
+
- **`register_access` tool**: the agent's self-service path for writing the ro tier (rw stays human-managed via the admin HTTP routes).
|
|
11
|
+
- **Mention support**: `@[kind/name](dsh-access:<payload>)` mentions are parsed on `agent/pre-step` and rewritten to readable references with envelope context; `GET /ops-access/list` feeds the browser's `@` picker. The encoding lives in the `./mention` subpath.
|
|
12
|
+
- **Admin routes**: `GET /ops-access/admin/list`, `GET /ops-access/admin/kinds`, `GET|POST|DELETE /ops-access/admin/entry` — envelope + validation status only, never field values.
|
|
13
|
+
|
|
14
|
+
## Design notes
|
|
15
|
+
|
|
16
|
+
- **Secrets never cross**: profiles carry only file paths and connection parameters — logs, errors, and model context cannot contain secret material. File-field content is write-only after save (`getEntry` withholds even the stored path).
|
|
17
|
+
- **Why the seam is split**: core owns the registry file and the service; providers own per-kind field knowledge; consumer tools (`ops-tool-kubectl` & co.) own command building. Each side changes independently.
|
|
18
|
+
- **Broker, not gatekeeper-in-core**: policy (who may read rw) lives in a registered `AccessBroker` — a pure decision function consulted on every resolve. Without one, resolve serves ro byte-for-byte as before.
|
|
19
|
+
- **Everything is an effect**: tool, routes, provider and broker registrations are all tied to the cordis effect lifecycle, so fiber disposal / HMR unload removes them cleanly.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
- id: ops-access
|
|
25
|
+
name: '@elinpf/dsh-ops-access'
|
|
26
|
+
registryFile: ~/.dsh-ops/access.yaml # default
|
|
27
|
+
credentialsDir: ~/.dsh-ops/credentials # default; managed credential content files (0600)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm run build # tsc → lib/
|
|
34
|
+
npx vitest run # specs drive the real plugin through a mock context
|
|
35
|
+
# against a real tmp-dir registry file
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Known Limitations and Deferred Work
|
|
39
|
+
|
|
40
|
+
- No caching means a stat+parse per call — fine at ops-scale registries.
|
|
41
|
+
- `ssh` providers cannot capability-probe (no read-only shell to test) — their tiers stay unprobed.
|
|
42
|
+
- The registry file is human-editable by design; there is no lock against concurrent writers.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @elinpf/dsh-ops-access
|
|
2
|
+
|
|
3
|
+
运维访问能力缝(capability seam)— 持有 YAML 凭据注册表(默认 `~/.dsh-ops/access.yaml`),向 provider 插件和消费工具暴露 `ctx.opsAccess`(resolve / list / register)。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- **单注册表文件、零缓存**:每次 `resolve`/`list`/`writeEntry` 都重新读取、解析、校验 YAML — 改文件立即生效,无需重启。
|
|
8
|
+
- **分层条目**:每个 profile 携带 `ro` 层(agent 默认可读)和 `rw` 层(只有注册了 broker 授权后才发放)。
|
|
9
|
+
- **Provider 缝**:每种凭据类型一个 provider(`k8s`/`ceph`/`ssh` 包),只提供 zod schema 加字段处理(`~` 展开、内容校验、能力探测)。provider 通过 `registerAccessProvider(ctx, provider)` 注册 — 绝不要手写 `ctx.inject` 依赖兄弟服务,会死锁 loader。
|
|
10
|
+
- **`register_access` 工具**:agent 自助写入 ro 层的路径(rw 层始终由人通过 admin HTTP 路由管理)。
|
|
11
|
+
- **Mention 支持**:`@[kind/name](dsh-access:<payload>)` mention 在 `agent/pre-step` 上被解析、重写为可读引用并注入 envelope 上下文;`GET /ops-access/list` 给浏览器的 `@` 选择器供数。编码在 `./mention` 子路径。
|
|
12
|
+
- **Admin 路由**:`GET /ops-access/admin/list`、`GET /ops-access/admin/kinds`、`GET|POST|DELETE /ops-access/admin/entry` — 只出 envelope + 校验状态,绝不出字段值。
|
|
13
|
+
|
|
14
|
+
## 设计要点
|
|
15
|
+
|
|
16
|
+
- **秘密不过境**:profile 只携带文件路径和连接参数 — 日志、报错、模型上下文都不可能含秘密材料。文件字段保存后只写不读(`getEntry` 连存储路径都不返回)。
|
|
17
|
+
- **为什么这样拆**:core 持有注册表文件和服务;provider 持有各类型的字段知识;消费工具(`ops-tool-kubectl` 等)持有命令拼装。三方各自独立演进。
|
|
18
|
+
- **broker 而非内置守门**:策略(谁能读 rw)住在注册的 `AccessBroker` 里 — 一个纯决策函数,每次 resolve 都咨询。没有 broker 时 resolve 与之前逐字节一致地发 ro。
|
|
19
|
+
- **一切皆 effect**:工具、路由、provider 和 broker 注册都绑在 cordis effect 生命周期上,fiber 销毁 / HMR 卸载会干净移除。
|
|
20
|
+
|
|
21
|
+
## 配置
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
- id: ops-access
|
|
25
|
+
name: '@elinpf/dsh-ops-access'
|
|
26
|
+
registryFile: ~/.dsh-ops/access.yaml # 默认值
|
|
27
|
+
credentialsDir: ~/.dsh-ops/credentials # 默认值;托管凭据内容文件(0600)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 测试
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
npm run build # tsc → lib/
|
|
34
|
+
npx vitest run # spec 用 mock context 驱动真实插件,
|
|
35
|
+
# 注册表是真实的 tmp 目录文件
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 已知限制和待办事项
|
|
39
|
+
|
|
40
|
+
- 无缓存意味着每次调用一次 stat+解析 — 运维规模的注册表下没问题。
|
|
41
|
+
- `ssh` provider 无法做能力探测(没有只读 shell 可测)— 其分层保持未探测状态。
|
|
42
|
+
- 注册表文件按设计可人工编辑;没有并发写锁。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ops access capability seam.
|
|
3
|
+
*
|
|
4
|
+
* Owns the YAML credential registry file (default `~/.dsh-ops/access.yaml`)
|
|
5
|
+
* and exposes `ctx.opsAccess`: a generic `resolve(kind, name)` / `list()`
|
|
6
|
+
* entry plus a `register(provider)` surface for provider plugins. Providers
|
|
7
|
+
* (one per credential kind) supply the zod schema for their entry shape and
|
|
8
|
+
* an optional `process` step (e.g. `~` expansion); secret material never
|
|
9
|
+
* leaves the filesystem — profiles carry only paths and connection params.
|
|
10
|
+
*
|
|
11
|
+
* The registry file is re-read, re-parsed, and re-validated on every call —
|
|
12
|
+
* edits take effect immediately, nothing is cached.
|
|
13
|
+
*
|
|
14
|
+
* Also registers the `register_access` tool: the agent's self-service path
|
|
15
|
+
* for writing the ro tier of a profile (rw tiers stay human-managed via the
|
|
16
|
+
* admin HTTP routes below).
|
|
17
|
+
*
|
|
18
|
+
* Registry format:
|
|
19
|
+
*
|
|
20
|
+
* ```yaml
|
|
21
|
+
* version: 1
|
|
22
|
+
* k8s:
|
|
23
|
+
* prod:
|
|
24
|
+
* description: 生产集群
|
|
25
|
+
* environment: prod
|
|
26
|
+
* ro:
|
|
27
|
+
* kubeconfig: ~/.dsh-ops/credentials/k8s/prod/ro/kubeconfig
|
|
28
|
+
* rw:
|
|
29
|
+
* kubeconfig: ~/.dsh-ops/credentials/k8s/prod/rw/kubeconfig
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Every top-level section besides `version` is a kind; keys inside a section
|
|
33
|
+
* are profile names. `description` and `environment` are envelope fields
|
|
34
|
+
* on the entry; `ro` and `rw` are tier sub-objects holding the
|
|
35
|
+
* provider-specific fields for that tier.
|
|
36
|
+
*
|
|
37
|
+
* @module @elinpf/dsh-ops-access
|
|
38
|
+
*/
|
|
39
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
40
|
+
import z from '@deepseek-ai/schemastery';
|
|
41
|
+
import type { AccessProvider, AccessBroker, OpsAccess } from './types.js';
|
|
42
|
+
export declare const name = "ops-access";
|
|
43
|
+
export declare const inject: string[];
|
|
44
|
+
export interface Config {
|
|
45
|
+
/** Path to the YAML access registry; a leading `~` expands to $HOME. */
|
|
46
|
+
registryFile: string;
|
|
47
|
+
/** Root directory for managed credential content files; a leading `~` expands to $HOME. */
|
|
48
|
+
credentialsDir: string;
|
|
49
|
+
}
|
|
50
|
+
export declare const Config: z<Config>;
|
|
51
|
+
export type { AccessProvider, AccessProfile, EntryEnvelope, ProbeState, AdminTierStatus, AdminEntry, KindDescriptor, AccessAgent, AccessBrokerDecision, AccessBroker, OpsAccess, } from './types.js';
|
|
52
|
+
declare module '@deepseek-ai/cordis' {
|
|
53
|
+
interface Context {
|
|
54
|
+
opsAccess?: OpsAccess;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Register a provider into the seam from a provider plugin's `apply()`. The
|
|
59
|
+
* preset mounts sibling rows concurrently, so a static inject on 'opsAccess'
|
|
60
|
+
* can deadlock the loader against the definition row — this defers through
|
|
61
|
+
* `ctx.inject` and ties the registration to the plugin's effect lifecycle.
|
|
62
|
+
* Provider packages should call this and nothing else.
|
|
63
|
+
*/
|
|
64
|
+
export declare function registerAccessProvider(ctx: Context, provider: AccessProvider): void;
|
|
65
|
+
/**
|
|
66
|
+
* Register an access broker (the gate) from the gate plugin's `apply()`. Same
|
|
67
|
+
* deferred-mount discipline as {@link registerAccessProvider}: the preset
|
|
68
|
+
* mounts sibling rows concurrently, so a static inject on 'opsAccess' can
|
|
69
|
+
* deadlock the loader against the definition row — this defers through
|
|
70
|
+
* `ctx.inject` and ties the registration to the plugin's effect lifecycle.
|
|
71
|
+
*/
|
|
72
|
+
export declare function registerAccessBroker(ctx: Context, broker: AccessBroker): void;
|
|
73
|
+
/** Expand a leading `~` (or `~/`) to the user's home directory. */
|
|
74
|
+
export declare function expandHome(p: string): string;
|
|
75
|
+
export declare function apply(ctx: Context, config: Config): void;
|