@elinpf/dsh-ops-tool-ceph 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.
@@ -0,0 +1,4 @@
1
+ title: "@elinpf/dsh-ops-tool-ceph"
2
+ description:
3
+ zh: "ceph 工具 — 经 ops-access 接缝解析 Ceph 集群档案,自动注入凭证路径执行 ceph/rbd/rados"
4
+ en: "ceph tool — resolves Ceph cluster profiles via the ops-access seam and runs ceph/rbd/rados with credential paths injected"
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # @elinpf/dsh-ops-tool-ceph
2
+
3
+ The `ceph` tool for DeepSeek Harness ops mode — resolves a named Ceph cluster profile through the ops-access seam and runs `ceph` / `rbd` / `rados` against it via `ctx.shell`, injecting the credential paths automatically.
4
+
5
+ ## What it does
6
+
7
+ The model calls `ceph` with a `cluster` (profile name) and a `command`. The tool resolves the profile on every call (no caching — credential edits take effect immediately), builds the real command line with the profile's `--conf` / `--keyring` (and `--name` when the profile carries a cephx user) injected as `<id@tier:field>` token references, runs it through the shell service with a 30 s timeout, and returns the standard `{ exitCode, stdout, stderr, command, error? }` result.
8
+
9
+ ## Design notes
10
+
11
+ - **Thin consumer, shared machinery.** This package supplies only four identity pieces — tool name, resolved kind (`ceph`), profile-arg name (`cluster`), and `buildCommand`. Everything else (result shape, output schema, render, resolve-per-call execute template, timeout and signal-death normalization) lives in `@elinpf/dsh-ops-shell-tool`, so the three consumer tools (`kubectl` / `ceph` / `ssh`) cannot drift apart.
12
+ - **First word picks the binary.** `rbd` and `rados` are SEPARATE binaries, not ceph subcommands — `ceph rbd ls` is a mon-side "no valid command found". The command's first word selects from the allowlist `[ceph, rbd, rados]`; a bare word is treated as a ceph subcommand.
13
+ - **Boundary errors over misleading ones.** Host-local ceph-ecosystem binaries (`mount.ceph`, `ceph-fuse`, `ceph-volume`, `rbd-nbd`, …) are explicitly NOT wrapped; such a call fails with a clear message pointing at the `ssh` tool instead of the mon's confusing error.
14
+ - **stderr noise filtering.** The two known "no keyring under /etc/ceph" warnings (pure noise when credentials arrive via injected `--keyring`) are matched exactly and dropped; every other stderr line passes through verbatim.
15
+ - **No secret material in band.** Profiles carry only paths and connection parameters; file paths become `<id@tier:field>` tokens in the assembled command, and the cephx entity name (not secret) stays inline. Read-only enforcement happens at the mon/osd via the credential's caps, never in this tool.
16
+ - **Registration is an effect.** The tool registers via `ctx.effect(() => ctx.tools.register(...))` (inside `registerProfiledShellTool`), so fiber disposal / HMR unloads it cleanly. The `./invariant` subpath registers a no-op invariant companion: the tool is stateless and owns no session events, so there is no runtime invariant to install.
17
+
18
+ ## Configuration
19
+
20
+ Schemastery schema, one option:
21
+
22
+ | Key | Default | Meaning |
23
+ |---|---|---|
24
+ | `timeoutMs` | `30000` | Per-call shell timeout for ceph runs (ms). Slow clusters may need more. |
25
+
26
+ ## Testing
27
+
28
+ ```sh
29
+ npm run build # tsc → lib/ (plugins load lib/, not src/)
30
+ npx vitest run # unit tests against a mock ctx (shell / tools / opsAccess)
31
+ ```
32
+
33
+ The suite covers command assembly (ceph / rbd / rados / explicit-prefix stripping / `--name` injection), the not-wrapped boundary, profile-resolution and shell failure mapping, stderr noise filtering, render purity, export shape (`.` / `./invariant` / `./types` entries), and HMR unload (running every collected effect disposer unregisters the tool).
package/README.zh.md ADDED
@@ -0,0 +1,33 @@
1
+ # @elinpf/dsh-ops-tool-ceph
2
+
3
+ DeepSeek Harness 运维模式下的 `ceph` 工具 — 通过 ops-access 接缝按名字解析 Ceph 集群档案(profile),经由 `ctx.shell` 对集群执行 `ceph` / `rbd` / `rados` 命令,凭证路径自动注入。
4
+
5
+ ## 它做什么
6
+
7
+ 模型用 `cluster`(档案名)和 `command` 调用 `ceph` 工具。工具每次调用都重新解析档案(不缓存,凭证改动立即生效),把档案的 `--conf` / `--keyring`(档案带 cephx 用户时还有 `--name`)以 `<id@tier:field>` 令牌引用的形式拼进真实命令行,经 shell 服务以 30 秒超时执行,返回标准结果 `{ exitCode, stdout, stderr, command, error? }`。
8
+
9
+ ## 设计要点
10
+
11
+ - **薄消费者,共享机制。** 本包只提供四件身份要素:工具名、解析类型(`ceph`)、档案参数名(`cluster`)、`buildCommand`。其余全部(结果形态、输出 schema、渲染、逐次解析的执行模板、超时与信号死亡归一化)都在 `@elinpf/dsh-ops-shell-tool`,保证三个消费工具(`kubectl` / `ceph` / `ssh`)不会各自漂移。
12
+ - **首词选二进制。** `rbd` 和 `rados` 是独立二进制,不是 ceph 子命令 — `ceph rbd ls` 只会在 mon 侧报 "no valid command found"。按命令首词在白名单 `[ceph, rbd, rados]` 中选择;裸词视为 ceph 子命令。
13
+ - **边界错误优于误导错误。** 跑在宿主机本地的 ceph 生态二进制(`mount.ceph`、`ceph-fuse`、`ceph-volume`、`rbd-nbd` 等)明确不包装;这类调用直接报清晰错误并指向 `ssh` 工具,而不是让 mon 的误导性报错带偏排查。
14
+ - **stderr 噪音过滤。** 两条已知的" /etc/ceph 下找不到 keyring "警告(凭证都经注入的 `--keyring` 到达,纯属噪音)按精确模式剔除;其余 stderr 行原样透传。
15
+ - **秘密不经过工具。** 档案只携带路径和连接参数;文件路径在拼装命令中变成 `<id@tier:field>` 令牌,cephx 实体名(非秘密)保持内联。只读强制由凭证 caps 在 mon/osd 侧执行,工具本身不做。
16
+ - **注册即 effect。** 工具经 `ctx.effect(() => ctx.tools.register(...))` 注册(在 `registerProfiledShellTool` 内部),fiber 销毁 / HMR 时干净卸载。`./invariant` 子路径注册一个空实现的 invariant 伴生插件:本工具无状态、不拥有 session 事件,没有需要安装的运行时不变量。
17
+
18
+ ## 配置项
19
+
20
+ schemastery schema,仅一项:
21
+
22
+ | 键 | 默认值 | 含义 |
23
+ |---|---|---|
24
+ | `timeoutMs` | `30000` | 单次 ceph 执行的 shell 超时(毫秒)。慢集群可调大。 |
25
+
26
+ ## 测试方式
27
+
28
+ ```sh
29
+ npm run build # tsc → lib/(插件加载的是 lib/,不是 src/)
30
+ npx vitest run # 基于 mock ctx(shell / tools / opsAccess)的单元测试
31
+ ```
32
+
33
+ 测试覆盖:命令拼装(ceph / rbd / rados / 显式前缀剥离 / `--name` 注入)、不包装边界、档案解析与 shell 失败映射、stderr 噪音过滤、render 纯函数性、导出形态(`.` / `./invariant` / `./types` 三个入口)、以及 HMR 卸载(执行收集到的全部 effect disposer 后工具被注销)。
@@ -0,0 +1 @@
1
+ []
package/lib/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Ops ceph tool consumer.
3
+ *
4
+ * The `ceph` tool: resolves a `ceph` profile through the ops-access seam and
5
+ * runs the command via ctx.shell, injecting the profile's --conf and
6
+ * --keyring paths. The command's first word picks the binary from the
7
+ * allowlist [ceph, rbd, rados] — rbd/rados are SEPARATE binaries, not ceph
8
+ * subcommands (`ceph rbd ls` is a mon-side 'no valid command found', which
9
+ * once sent an agent chasing a phantom permissions problem, 2026-08-27).
10
+ * All shared machinery (result shape, output schema, render, execute
11
+ * template) lives in @elinpf/dsh-ops-shell-tool.
12
+ *
13
+ * @module @elinpf/dsh-ops-tool-ceph
14
+ */
15
+ import type { Context } from '@deepseek-ai/cordis';
16
+ import z from '@deepseek-ai/schemastery';
17
+ import type { CephToolConfig } from './types.js';
18
+ export type { CephToolConfig } from './types.js';
19
+ export declare const name = "ops-tool-ceph";
20
+ export declare const inject: string[];
21
+ export declare const Config: z<Schemastery.ObjectS<{
22
+ /** Per-call shell timeout for ceph runs (ms). Slow clusters may need more. */
23
+ timeoutMs: z<number, number>;
24
+ }>, Schemastery.ObjectT<{
25
+ /** Per-call shell timeout for ceph runs (ms). Slow clusters may need more. */
26
+ timeoutMs: z<number, number>;
27
+ }>>;
28
+ export declare function apply(ctx: Context, config: CephToolConfig): void;
package/lib/index.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Ops ceph tool consumer.
3
+ *
4
+ * The `ceph` tool: resolves a `ceph` profile through the ops-access seam and
5
+ * runs the command via ctx.shell, injecting the profile's --conf and
6
+ * --keyring paths. The command's first word picks the binary from the
7
+ * allowlist [ceph, rbd, rados] — rbd/rados are SEPARATE binaries, not ceph
8
+ * subcommands (`ceph rbd ls` is a mon-side 'no valid command found', which
9
+ * once sent an agent chasing a phantom permissions problem, 2026-08-27).
10
+ * All shared machinery (result shape, output schema, render, execute
11
+ * template) lives in @elinpf/dsh-ops-shell-tool.
12
+ *
13
+ * @module @elinpf/dsh-ops-tool-ceph
14
+ */
15
+ import z from '@deepseek-ai/schemastery';
16
+ import { registerProfiledShellTool } from '@elinpf/dsh-ops-shell-tool';
17
+ // ── Plugin identity ───────────────────────────────────────────────────────────
18
+ export const name = 'ops-tool-ceph';
19
+ export const inject = ['shell', 'tools'];
20
+ // ── Config ───────────────────────────────────────────────────────────────────
21
+ export const Config = z.object({
22
+ /** Per-call shell timeout for ceph runs (ms). Slow clusters may need more. */
23
+ timeoutMs: z.number().default(30000),
24
+ });
25
+ // ── Plugin apply ─────────────────────────────────────────────────────────────
26
+ /**
27
+ * Known ceph-CLI stderr noise inside containers with no default keyring under
28
+ * /etc/ceph. Credentials always arrive via the injected --keyring, so these
29
+ * warnings are pure noise; the patterns match the two known message shapes
30
+ * exactly (tolerating the librados timestamp/entity prefix and the variable
31
+ * path list) — every other stderr line passes through verbatim.
32
+ */
33
+ const STDERR_NOISE = [
34
+ // "unable to find a keyring on /etc/ceph/ceph.client.X.keyring,...: (2) No such file or directory"
35
+ /unable to find a keyring on .*: \(-?\d+\) No such file or directory\s*$/,
36
+ // "no keyring found at /etc/ceph/ceph.client.X.keyring, disabling cephx"
37
+ /no keyring found at .*disabling cephx\s*$/,
38
+ ];
39
+ const CEPH_BINARIES = ['ceph', 'rbd', 'rados'];
40
+ // ceph-ecosystem binaries this tool does NOT wrap — they run locally on a
41
+ // host, not against the mon. Named so a confused call fails with a clear
42
+ // boundary instead of the mon's misleading 'no valid command found'.
43
+ const NOT_WRAPPED = ['mount', 'umount', 'mount.ceph', 'ceph-fuse', 'ceph-volume', 'rclone', 'rbd-nbd', 'rbd-mirror'];
44
+ export function apply(ctx, config) {
45
+ registerProfiledShellTool(ctx, {
46
+ timeoutMs: config.timeoutMs,
47
+ name: 'ceph',
48
+ kind: 'ceph',
49
+ targetParam: 'cluster',
50
+ description: 'Execute a ceph, rbd or rados command on a specified Ceph cluster. The plugin automatically injects the correct --conf and --keyring credentials (shown as <id@tier:field> references). Use list_access to see available cluster names.',
51
+ targetParamDescription: 'Ceph cluster profile name. Use list_access to see options.',
52
+ commandDescription: 'A ceph, rbd or rados command. Bare ceph subcommands work as-is (health detail, osd tree, df). rbd and rados are SEPARATE binaries — include the binary name as the first word (rbd ls -p rbd-pool, rados df); they share the injected --conf/--keyring/--name. Discover pool names with "osd pool ls". Under an ro credential, write verbs (and some reads like rbd ls without class-read caps) fail at the mon/osd with a real permission error — that is the enforcement layer working, not a tool bug.',
53
+ stderrNoise: STDERR_NOISE,
54
+ buildCommand(fields, command, ref) {
55
+ const { name } = fields;
56
+ // --name matters: a non-admin keyring without it still authenticates as
57
+ // client.admin and fails with RADOS permission denied. The cephx entity
58
+ // name is not secret — it stays inline; only the file paths get tokens.
59
+ const nameArg = name ? ` --name ${name}` : '';
60
+ const trimmed = command.trim();
61
+ const first = trimmed.split(/\s+/, 1)[0] ?? '';
62
+ if (NOT_WRAPPED.includes(first)) {
63
+ throw new Error('the ceph tool wraps ceph/rbd/rados against the cluster only — \'' + first + '\' runs locally on a host; use the ssh tool for that');
64
+ }
65
+ // rbd and rados are SEPARATE binaries, not ceph subcommands — select by
66
+ // first word (an explicit 'ceph' prefix is stripped too); a bare word
67
+ // is a ceph subcommand and stays. Read-only-ness is enforced by the
68
+ // credential's caps at the mon/osd, never by the binary name.
69
+ const binary = CEPH_BINARIES.includes(first) ? first : 'ceph';
70
+ const rest = first === binary ? trimmed.slice(first.length).trimStart() : trimmed;
71
+ return `${binary} --conf=${ref('conf')} --keyring=${ref('keyring')}${nameArg} ${rest}`;
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-tool-ceph.
3
+ *
4
+ * @module @elinpf/dsh-ops-tool-ceph/invariant
5
+ */
6
+ /** Cordis companion plugin name. */
7
+ declare const name = "ops-ceph-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Context carrying the invariant service.
13
+ * @returns a promise resolving after registration.
14
+ */
15
+ declare const apply: (ctx: any) => Promise<void>;
16
+ export { apply, inject, name };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Invariant companion for @elinpf/dsh-ops-tool-ceph.
3
+ *
4
+ * @module @elinpf/dsh-ops-tool-ceph/invariant
5
+ */
6
+ const PACKAGE_NAME = '@elinpf/dsh-ops-tool-ceph';
7
+ /** Cordis companion plugin name. */
8
+ const name = 'ops-ceph-invariant';
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ['invariants'];
11
+ /**
12
+ * No runtime invariant: this tool is stateless. It owns no session events and
13
+ * no durable shape — every call re-resolves the credential profile through
14
+ * the ops-access seam and runs one shell command, so there is nothing to
15
+ * fold and nothing to check.
16
+ */
17
+ const install = () => { };
18
+ /**
19
+ * Register this package's invariant companion.
20
+ * @param ctx - Context carrying the invariant service.
21
+ * @returns a promise resolving after registration.
22
+ */
23
+ const apply = async (ctx) => {
24
+ ctx.invariants.register(PACKAGE_NAME, install);
25
+ };
26
+ export { apply, inject, name };
package/lib/types.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Type definitions for the ops-tool-ceph plugin.
3
+ *
4
+ * Types only — no runtime values live here (not even consts), so importing
5
+ * this module never pulls in plugin code.
6
+ *
7
+ * @module @elinpf/dsh-ops-tool-ceph
8
+ */
9
+ /**
10
+ * Resolved configuration of the ops-tool-ceph plugin (post-schema defaults).
11
+ */
12
+ export interface CephToolConfig {
13
+ /** Per-call shell timeout for ceph runs (ms). Slow clusters may need more. */
14
+ timeoutMs: number;
15
+ }
package/lib/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type definitions for the ops-tool-ceph plugin.
3
+ *
4
+ * Types only — no runtime values live here (not even consts), so importing
5
+ * this module never pulls in plugin code.
6
+ *
7
+ * @module @elinpf/dsh-ops-tool-ceph
8
+ */
9
+ export {};
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@elinpf/dsh-ops-tool-ceph",
3
+ "version": "0.1.0",
4
+ "description": "ceph tool consumer of the ops-access seam — resolves ceph profiles and runs commands via ctx.shell, injecting credential paths automatically.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./invariant": {
14
+ "types": "./lib/invariant.d.ts",
15
+ "default": "./lib/invariant.js"
16
+ },
17
+ "./types": {
18
+ "types": "./lib/types.d.ts",
19
+ "default": "./lib/types.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "lib/index.js",
25
+ "lib/invariant.js",
26
+ "lib/types.js",
27
+ "lib/**/*.d.ts",
28
+ "cordis.patch.yml"
29
+ ],
30
+ "dsh": {
31
+ "bundle": {
32
+ "patch": "./cordis.patch.yml"
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "@deepseek-ai/schemastery": "^3.18.1",
37
+ "@elinpf/dsh-ops-shell-tool": "^0.1.0"
38
+ },
39
+ "peerDependencies": {
40
+ "@deepseek-ai/cordis": "^4.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@deepseek-ai/cordis": "4.0.1",
44
+ "@deepseek-ai/dsh-shell": "0.0.1-rc.5",
45
+ "@deepseek-ai/dsh-tools": "0.0.1-rc.1",
46
+ "typescript": "^5.4.0",
47
+ "vitest": "^4.1.11",
48
+ "@elinpf/dsh-ops-access": "0.1.0"
49
+ },
50
+ "license": "MIT",
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc",
56
+ "typecheck": "tsc --noEmit",
57
+ "test": "vitest run"
58
+ }
59
+ }