@dzhechkov/cloudru-hub 0.1.14 → 0.1.16
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/.dz-manifest.json +43 -19
- package/CHANGELOG.md +32 -0
- package/README.md +71 -15
- package/bin/cloudru-hub.js +0 -0
- package/data/pointer-section.md +1 -1
- package/data/tools-classification.json +13 -3
- package/data/tools-list-snapshot.json +12 -4
- package/docs/COST-COMPARE.md +21 -0
- package/docs/HERMES-CODEX.md +4 -4
- package/docs/LOCAL-TESTING.md +7 -7
- package/docs/WEB-IMAGE.md +57 -0
- package/docs/cost-compare.example.json +84 -0
- package/package.json +9 -9
- package/sbom.json +78 -18
- package/src/cli.js +22 -9
- package/src/cost-compare.js +348 -0
- package/src/install.js +1 -1
- package/src/k8s-approval.js +9 -6
- package/src/k8s-image-manifest.js +75 -0
- package/src/safe-mcp.js +27 -12
- package/src/toolsets.js +1 -1
- package/templates/.claude/hooks/cloudru-ssh-guard.cjs +0 -0
- package/test/classification.test.mjs +5 -5
- package/test/cost-compare.test.mjs +280 -0
- package/test/deploy-approval.test.mjs +12 -0
- package/test/k8s-approval.test.mjs +40 -0
- package/test/resolve.test.mjs +1 -1
- package/test/safe-mcp.test.mjs +20 -0
package/.dz-manifest.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"files": [
|
|
6
6
|
{
|
|
7
7
|
"path": "CHANGELOG.md",
|
|
8
|
-
"sha256": "
|
|
8
|
+
"sha256": "56ba17ee84afe937d3f13c17387b26090e42217b1c8ffd9eee175a5512a14091"
|
|
9
9
|
},
|
|
10
10
|
{
|
|
11
11
|
"path": "LICENSE",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "bfd1c00de39c986cebad2e3fb00fa072ad0b4ecb3cb07f744920bf80474ecb47"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "THIRD_PARTY_NOTICES.md",
|
|
@@ -29,27 +29,39 @@
|
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "data/pointer-section.md",
|
|
32
|
-
"sha256": "
|
|
32
|
+
"sha256": "32845b9cc1ad64f2f6a8d4eeb8b90bf36710590d25db4234e6813793ef63c5d4"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"path": "data/tools-classification.json",
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "a4bf5a2e76d4f9bd686bec704919ab3380bb22ff46a399b058dc5b3e71a64dd1"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"path": "data/tools-list-snapshot.json",
|
|
40
|
-
"sha256": "
|
|
40
|
+
"sha256": "6864bb944ebf780454d266fe7ad47e9cec12969fb66fa9e9a1963b547944e10f"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "docs/COST-COMPARE.md",
|
|
44
|
+
"sha256": "80b4d7ac3868f17d93565274cd6736fe5f2bd7fb059cbed0c2f0afe3f4924317"
|
|
41
45
|
},
|
|
42
46
|
{
|
|
43
47
|
"path": "docs/HERMES-CODEX.md",
|
|
44
|
-
"sha256": "
|
|
48
|
+
"sha256": "00a5c9acc3999c5e07f230ba63bc1a0e202d62c8d15d2204620feca762eece8f"
|
|
45
49
|
},
|
|
46
50
|
{
|
|
47
51
|
"path": "docs/LOCAL-TESTING.md",
|
|
48
|
-
"sha256": "
|
|
52
|
+
"sha256": "7f5cc32d5f089c66246f8349f86fc72dfe6397de7a63edf51692da8583231b41"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"path": "docs/WEB-IMAGE.md",
|
|
56
|
+
"sha256": "690e092bafddc862c20ebd5ff0358a4c57f84881153c235a146e258563459f27"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"path": "docs/cost-compare.example.json",
|
|
60
|
+
"sha256": "7eafb1ff555fb5443ded1a2c578b17eae6e73e3056efb9cbb5129b424c8c5097"
|
|
49
61
|
},
|
|
50
62
|
{
|
|
51
63
|
"path": "package.json",
|
|
52
|
-
"sha256": "
|
|
64
|
+
"sha256": "94247888df4287a6077204c369ccb685a51b1d943635d51c6228dc1f8fe54635"
|
|
53
65
|
},
|
|
54
66
|
{
|
|
55
67
|
"path": "src/classification.js",
|
|
@@ -57,7 +69,11 @@
|
|
|
57
69
|
},
|
|
58
70
|
{
|
|
59
71
|
"path": "src/cli.js",
|
|
60
|
-
"sha256": "
|
|
72
|
+
"sha256": "590265903b3a242b3d9c1367801ce6ce10e23e18abbc28938c5a26f735ea7d75"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"path": "src/cost-compare.js",
|
|
76
|
+
"sha256": "6084ecd7f7fc53bbd441fb9e91dcd7c7bd5c409d1d4e95613fad0fd0735b7b05"
|
|
61
77
|
},
|
|
62
78
|
{
|
|
63
79
|
"path": "src/credentials.js",
|
|
@@ -73,11 +89,15 @@
|
|
|
73
89
|
},
|
|
74
90
|
{
|
|
75
91
|
"path": "src/install.js",
|
|
76
|
-
"sha256": "
|
|
92
|
+
"sha256": "90af414aabda1c48c82c9c2ecf0dbe4d7dd4744486efdce0d8367182b6a67a38"
|
|
77
93
|
},
|
|
78
94
|
{
|
|
79
95
|
"path": "src/k8s-approval.js",
|
|
80
|
-
"sha256": "
|
|
96
|
+
"sha256": "950543b85a7ed2c8e1660b40c2bb50192663e5fe62f8287b9b76ac3f8178c263"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"path": "src/k8s-image-manifest.js",
|
|
100
|
+
"sha256": "8a2c519e9bc6677c867758f4b9b56146f2832ee07a5bfd4fefed3317f0d9cf73"
|
|
81
101
|
},
|
|
82
102
|
{
|
|
83
103
|
"path": "src/probe-child.js",
|
|
@@ -89,11 +109,11 @@
|
|
|
89
109
|
},
|
|
90
110
|
{
|
|
91
111
|
"path": "src/safe-mcp.js",
|
|
92
|
-
"sha256": "
|
|
112
|
+
"sha256": "60a5db44db9518835f98f23aa104b86c3621f0b0c030196c2130715f5c19c219"
|
|
93
113
|
},
|
|
94
114
|
{
|
|
95
115
|
"path": "src/toolsets.js",
|
|
96
|
-
"sha256": "
|
|
116
|
+
"sha256": "0a66e73476d5f5624908f05f621b150efa51217fc9418938ca126dc0dc2b9518"
|
|
97
117
|
},
|
|
98
118
|
{
|
|
99
119
|
"path": "templates/.claude/hooks/cloudru-ssh-guard.cjs",
|
|
@@ -105,11 +125,15 @@
|
|
|
105
125
|
},
|
|
106
126
|
{
|
|
107
127
|
"path": "test/classification.test.mjs",
|
|
108
|
-
"sha256": "
|
|
128
|
+
"sha256": "413ddf6d9a4f709d80bce9011094d95bbe56f12c391f72be235c4482a1b40450"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"path": "test/cost-compare.test.mjs",
|
|
132
|
+
"sha256": "b354913220bf725ec6b8a708b7851e2f5779a3c594cd411732d19acad9001870"
|
|
109
133
|
},
|
|
110
134
|
{
|
|
111
135
|
"path": "test/deploy-approval.test.mjs",
|
|
112
|
-
"sha256": "
|
|
136
|
+
"sha256": "3aaf3cdf27c57be9121d7d1727335d0a97064659b3201d34d1e27f5aed631434"
|
|
113
137
|
},
|
|
114
138
|
{
|
|
115
139
|
"path": "test/dialects.test.mjs",
|
|
@@ -125,7 +149,7 @@
|
|
|
125
149
|
},
|
|
126
150
|
{
|
|
127
151
|
"path": "test/k8s-approval.test.mjs",
|
|
128
|
-
"sha256": "
|
|
152
|
+
"sha256": "acc30164bbafc2d46b404ff8ae87caf42e301e15dd005c424654ee0225af6efd"
|
|
129
153
|
},
|
|
130
154
|
{
|
|
131
155
|
"path": "test/mutation-registry-freshness.test.mjs",
|
|
@@ -141,13 +165,13 @@
|
|
|
141
165
|
},
|
|
142
166
|
{
|
|
143
167
|
"path": "test/resolve.test.mjs",
|
|
144
|
-
"sha256": "
|
|
168
|
+
"sha256": "b447a8755c67bcbfc3db63c909235f34414721139c7bc7fdf0658ab0638e7980"
|
|
145
169
|
},
|
|
146
170
|
{
|
|
147
171
|
"path": "test/safe-mcp.test.mjs",
|
|
148
|
-
"sha256": "
|
|
172
|
+
"sha256": "e99200ccdd8368260af18532478addc08e3b6195986cecf68b669e9dc639d119"
|
|
149
173
|
}
|
|
150
174
|
]
|
|
151
175
|
},
|
|
152
|
-
"signature": "
|
|
176
|
+
"signature": "uCRif8l/EX1KMduryC76GpbzVdYNOCth1Zq9ssM2PoWD6EvqNnFlsK1lDoalJERQi5Rly6hZNmFwixIY0n54Dw=="
|
|
153
177
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,37 @@
|
|
|
1
1
|
# Changelog — @dzhechkov/cloudru-hub
|
|
2
2
|
|
|
3
|
+
## 0.1.16 — 2026-10-29
|
|
4
|
+
|
|
5
|
+
- Pin the matched Linux/x64 engine `0.2.7-20261029` (SHA-256
|
|
6
|
+
`94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03`).
|
|
7
|
+
Kubernetes `k8s_plan(preflight:true)` is included and reports time/project-bound
|
|
8
|
+
observed checks, exact same-project name
|
|
9
|
+
conflicts and explicit unknowns for inventory completeness, create grants,
|
|
10
|
+
quota capacity, integration settings and tariffs. It always reports
|
|
11
|
+
`ready_to_apply:false`; it does not authorize create or promise readiness.
|
|
12
|
+
|
|
13
|
+
- Include placement resolution and durable Kubernetes create/nodepool intent
|
|
14
|
+
receipts in the matched engine. `k8s_get` can observe `receipt_id`,
|
|
15
|
+
`operation_id` and bounded `receipts:true` discovery after a lost reply.
|
|
16
|
+
No automatic retry is unlocked, and this candidate has no new live
|
|
17
|
+
Kubernetes deployment or `web-image` acceptance result.
|
|
18
|
+
|
|
19
|
+
- Add `cost-compare --input <json> [--json]`, an offline VM/Kubernetes cost worksheet
|
|
20
|
+
with explicit topology, dated tariff provenance, exact decimal sums and per-node
|
|
21
|
+
normal/failure capacity bounds. Incomplete prices, insufficient capacity or unknown
|
|
22
|
+
architecture equivalence suppress a winner. The packaged official-rate example
|
|
23
|
+
leaves traffic and equivalence unknown. See `docs/COST-COMPARE.md` for the schema
|
|
24
|
+
and limits.
|
|
25
|
+
|
|
26
|
+
- Retain read-only `deploy_reconcile` and
|
|
27
|
+
`deploy_preflight`. Ordinary VM apply journals intent before create; an
|
|
28
|
+
ambiguous response requires observation before any retry. A new plan cannot
|
|
29
|
+
reuse the project and VM name of an earlier run receipt on sequential submission;
|
|
30
|
+
concurrent distinct VM plans are not serialized.
|
|
31
|
+
- Add a separate `web-image` Kubernetes approval profile for a reviewed,
|
|
32
|
+
digest-pinned one-pod web workload in an existing namespace. Source build,
|
|
33
|
+
registry publication and live host acceptance remain separate steps.
|
|
34
|
+
|
|
3
35
|
## 0.1.14 — 2026-10-20
|
|
4
36
|
|
|
5
37
|
- Prepare an exact optional dependency on `@dzhechkov/cloudru-vm-linux-x64@0.2.6`.
|
package/README.md
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
# @dzhechkov/cloudru-hub
|
|
2
2
|
|
|
3
|
-
Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages
|
|
3
|
+
Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages/cloudru-hub
|
|
4
4
|
|
|
5
|
-
> **Linux/x64
|
|
6
|
-
> package `@dzhechkov/cloudru-vm-linux-x64@0.2.
|
|
5
|
+
> **Linux/x64:** version 0.1.16 names the exact optional engine
|
|
6
|
+
> package `@dzhechkov/cloudru-vm-linux-x64@0.2.7`. The `dz guard` `licence-hold`
|
|
7
7
|
> rule continues to check LICENSE, notices and SPDX on publish.
|
|
8
8
|
|
|
9
9
|
> **Unofficial. NOT affiliated with, endorsed, or supported by Cloud.ru.** "Cloud.ru" is a
|
|
10
10
|
> trademark of its owner; this launcher merely talks to the public Cloud.ru Evolution API
|
|
11
11
|
> through the `cloudru-vm` engine.
|
|
12
12
|
|
|
13
|
-
Thin JS launcher for the **`cloudru-vm` MCP engine** — a Go MCP server with **
|
|
13
|
+
Thin JS launcher for the **`cloudru-vm` MCP engine** — a Go MCP server with **146 tools**
|
|
14
14
|
(MEASURED — live `tools/list` probe, `data/tools-list-snapshot.json`) for Cloud.ru
|
|
15
15
|
Evolution: VMs, k8s, PostgreSQL/Kafka/Redis, DNS, S3, certificates, billing/FinOps, and a
|
|
16
16
|
docker-compose deploy wizard.
|
|
17
17
|
|
|
18
|
-
The launcher archive contains no engine binary.
|
|
19
|
-
|
|
18
|
+
The launcher archive contains no engine binary. npm installs the exact optional
|
|
19
|
+
platform package with the pinned Go engine on Linux/x64. Run:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
npm install -g @dzhechkov/cloudru-hub@0.1.
|
|
22
|
+
npm install -g @dzhechkov/cloudru-hub@0.1.16
|
|
23
23
|
cloudru-hub self-test
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
The self-test verifies the engine SHA-256, version,
|
|
26
|
+
The self-test verifies the engine SHA-256, version, 146 live MCP tools and their
|
|
27
27
|
classification without Cloud.ru credentials. On another platform, or if optional
|
|
28
28
|
dependencies were disabled, provide a trusted binary with the same SHA-256.
|
|
29
29
|
|
|
@@ -38,22 +38,25 @@ The JS launcher remains MIT-licensed; its archive contains no Go engine bytes.
|
|
|
38
38
|
| Piece | Where | What it does |
|
|
39
39
|
|---|---|---|
|
|
40
40
|
| Launcher | `bin/cloudru-hub.js` | resolves the engine binary at **runtime** (never bundled): `$CLOUDRU_VM_BIN` → `~/.cloudru-hub/config.json` `enginePath` → optional platform package `@dzhechkov/cloudru-vm-linux-x64`; verifies sha256 against the pin in `package.json.cloudruHub.binaryHashes` |
|
|
41
|
-
| Paid-step brake (ADR-004) | `src/install.js`, `templates/.claude/hooks/` | emits `permissions` **ask** rules for every mutating-or-shell-reaching tool (64 tools — MEASURED, `data/tools-classification.json` counts), **deny** for `secret_value`/`cert_private_key`, **allow** for
|
|
41
|
+
| Paid-step brake (ADR-004) | `src/install.js`, `templates/.claude/hooks/` | emits `permissions` **ask** rules for every mutating-or-shell-reaching tool (64 tools — MEASURED, `data/tools-classification.json` counts), **deny** for `secret_value`/`cert_private_key`, **allow** for 80 behaviour-audited read-only tools, in BOTH `mcp__cloudru-vm__`/`mcp__cloudru_vm__` spellings (prefix normalization not yet live-probed), plus a PreToolUse veto hook (ssh-guard + the kubectl `rollout` hole) — and **executes** the veto probe before declaring install success |
|
|
42
42
|
| Target integration | `src/install.js`, `src/safe-mcp.js` | Codex, OpenCode and Pi get project MCP configs using a pinned proxy; Hermes gets a guarded `$HERMES_HOME/config.yaml` entry with a verified engine path. Claude Code's general install keeps its permissions and hook probe. The opt-in `vm-deploy` profile requires an exact-plan approval and confines remote commands to the returned VM. Codex, Hermes, OpenCode and Pi each completed both live static-site scenarios on 2026-10-20 with HTTP byte checks and exact resource cleanup. The `k8s-deploy` profile uses a separate exact-manifest approval and create-only Kubernetes operation; the same four hosts completed live Kubernetes deployment, readiness, exact-byte HTTP and cleanup checks. Claude Code has installer/registration checks but no live inference pass. |
|
|
43
43
|
| Skill dialect compiler (ADR-006) | `src/dialects.js`, `data/dialects.json` | generates per-target variants of the canonical skill tree (which is NOT shipped — point `--canonical` at an engine distribution); hard-fails if any Hermes-dialect token survives AND if the engine codename survives in any case in any emitted file (positive invariant, not just the enumerated denylist); unlinks dangling links; enforces the 12 000-char router cap on lossy layouts; byte-deterministic |
|
|
44
44
|
| Golden classification | `data/tools-classification.json` | every engine tool → allow/ask/deny; `allow` requires a BEHAVIOUR audit of the handler (read-only proven at the pinned baseline, engine file:line cited per entry). The previous `stack_status` behavior was unsafe; the imported engine now makes it observational and tests that invariant. A live tool unknown to the classifier is a drift finding. |
|
|
45
|
+
| Offline cost worksheet | `src/cost-compare.js`, `docs/cost-compare.example.json` | compares supplied VM and Kubernetes topology, capacity and dated tariff lines with exact decimal arithmetic; needs no engine, credentials or network access |
|
|
45
46
|
|
|
46
47
|
## Usage
|
|
47
48
|
|
|
48
49
|
```bash
|
|
49
50
|
cloudru-hub resolve # where is the engine, which sha256, is it the pinned baseline
|
|
50
51
|
cloudru-hub self-test # resolve → sha256 → engine version → live tools/list → coverage
|
|
52
|
+
cloudru-hub cost-compare --input docs/cost-compare.example.json --json # offline cost worksheet
|
|
51
53
|
cloudru-hub mcp # run the engine as an MCP stdio server (what .mcp.json points at)
|
|
52
54
|
cloudru-hub mcp-safe --toolsets inventory # pinned MCP proxy with a small allow-classified surface
|
|
53
55
|
cloudru-hub install --target codex --toolsets billing # read-only balance, consumption and FinOps
|
|
54
56
|
cloudru-hub install --target codex --toolsets vm-deploy # guarded VM deployment profile
|
|
55
|
-
cloudru-hub install --target codex --toolsets k8s-deploy # bounded existing-cluster
|
|
57
|
+
cloudru-hub install --target codex --toolsets k8s-deploy # bounded existing-cluster profile
|
|
56
58
|
cloudru-hub k8s-approve --cluster-id <uuid> --namespace snake-pilot --manifest <snake.yaml> --manifest-sha256 <digest>
|
|
59
|
+
cloudru-hub k8s-approve --cluster-id <uuid> --namespace lesson-app --manifest <web.yaml> --manifest-sha256 <digest> --profile web-image
|
|
57
60
|
cloudru-hub approve --plan-id <id> --max-monthly-rub <cap> # one reviewed SSH plan
|
|
58
61
|
cloudru-hub approve --plan-id <id> --max-monthly-rub <cap> --site-sha256 <digest> # reviewed static-site artifact
|
|
59
62
|
cloudru-hub install --target claude-code # emit .mcp.json + permission brake + veto hook, PROBE the veto
|
|
@@ -65,6 +68,12 @@ cloudru-hub install --target agents-md # pointer-only section (no tools, no
|
|
|
65
68
|
cloudru-hub compile-skill --canonical <engine>/skill/cloudru-hub --target claude-code --out ./skill-out
|
|
66
69
|
```
|
|
67
70
|
|
|
71
|
+
The [cost worksheet guide](docs/COST-COMPARE.md) describes the versioned input, supported
|
|
72
|
+
units, exact calculations and limits. The packaged example uses checked official
|
|
73
|
+
VAT-inclusive tariff rows but leaves traffic unpriced and architecture equivalence
|
|
74
|
+
unknown; it therefore reports known subtotals with no winner. Supplied quantities
|
|
75
|
+
and tariff applicability remain operator assumptions, not verified inventory or a live bill.
|
|
76
|
+
|
|
68
77
|
## Using an owner-supplied engine instead
|
|
69
78
|
|
|
70
79
|
```bash
|
|
@@ -73,7 +82,7 @@ node bin/cloudru-hub.js self-test
|
|
|
73
82
|
```
|
|
74
83
|
|
|
75
84
|
Expected: 5 green steps ending `self-test: ALL GREEN` (resolution, sha256 match against
|
|
76
|
-
the pinned baseline `
|
|
85
|
+
the pinned baseline `94fdba4f…bfabc03`, engine version, 146 live tools, full
|
|
77
86
|
classification coverage). Full walkthrough incl. `npm link` and MCP registration:
|
|
78
87
|
[`docs/LOCAL-TESTING.md`](docs/LOCAL-TESTING.md).
|
|
79
88
|
|
|
@@ -81,9 +90,56 @@ Host setup, checks and limits: [`docs/HERMES-CODEX.md`](docs/HERMES-CODEX.md). T
|
|
|
81
90
|
|
|
82
91
|
For billing through `mcp-safe`, add the optional `"agreement_id":"…"` to the owner-only `~/.config/cloudru-hub/credentials.json` alongside `key_id`, `secret` and `project_id`. The proxy forwards it to the Go child as `CLOUDRU_AGREEMENT_ID`. Existing environment-only billing setups remain supported when no credential file is configured. The current consumption tool reports the agreement-wide total; its output should not be presented as a per-project bill. See the [Cloud.ru instructions for locating the agreement ID](https://cloud.ru/docs/billing/ug/topics/faq__api_id_agreement).
|
|
83
92
|
|
|
84
|
-
The `vm-deploy` and `k8s-deploy` profiles are
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
The `vm-deploy` and `k8s-deploy` profiles are opt-in previews requiring a matching pinned Go binary and credentials in a private local file. `deploy_plan` resolves a VM and a compute/disk estimate; `deploy_preflight(plan_id)` observes live name collisions and available quota usage, naming unknown create rights, capacity and billed tariffs. After an exact-plan approval, `deploy_apply` writes a private run intent before VM creation. If the API outcome is uncertain, `deploy_reconcile(plan_id)` reads the receipt and project inventory; it never adopts a same-name VM or authorizes a retry. A prior receipt for the same project and VM name also blocks a new plan, even if the cloud inventory has not caught up; inspect the prior run and choose a new name. The static-site route still snapshots approved HTML/ZIP bytes, uses fixed Cloud-init and a dedicated TCP-80 group, and reports verified only after exact-byte HTTP checks against the approved IP. Every public file must be reviewed; a public IP may incur additional charges. Static sites do not grant `vm_exec` SSH access. See `features/cloudru-cli-vm-deploy/SNAKE-ACCEPTANCE.md` in the private source tree for the earlier live acceptance.
|
|
94
|
+
|
|
95
|
+
Concurrent distinct VM plans for the same project and VM name are not serialized. Avoid launching those plans together; the prior-receipt check protects sequential submissions and is not an atomic name reservation.
|
|
96
|
+
|
|
97
|
+
In the matched `0.2.7-20261029` engine, operator `k8s_plan` resolves a supported zone name or ID to `zone_id`/`zones`, and `k8s_create` explicitly sends that ID for control-plane placement. The VIP subnet must match the same zone. This does not establish why a worker pool may fail to register nodes.
|
|
98
|
+
|
|
99
|
+
The matched engine also accepts `k8s_plan` with
|
|
100
|
+
`{"preflight":true}` to collect read-only observations before a possible paid
|
|
101
|
+
cluster creation. It is separate from VM `deploy_preflight(plan_id)`. The
|
|
102
|
+
versioned report includes its observation time and configured project, with
|
|
103
|
+
`observed_checks`, `observed_conflicts` and `unknowns`. It attempts exact
|
|
104
|
+
zone/subnet resolution, checks returned project cluster rows for an exact name
|
|
105
|
+
collision, and reads project-filtered quota usage. A matching cluster with an
|
|
106
|
+
explicit same-project identity is a conflict; an empty or unmatched response
|
|
107
|
+
does not prove the name is free because inventory completeness is unknown.
|
|
108
|
+
Denied, malformed or unavailable quota responses remain unknown; usage does
|
|
109
|
+
not prove create capacity. Failed probes leave the established findings intact.
|
|
110
|
+
|
|
111
|
+
Every diagnostic report says `ready_to_apply:false`. Authentication, successful
|
|
112
|
+
listing and JWT scope roles do not prove effective caller or cluster service
|
|
113
|
+
account create grants. The [Cloud.ru cluster service-account role guide](https://cloud.ru/docs/kubernetes-evolution/ug/topics/concepts__roles-for-cluster-sa) lists
|
|
114
|
+
`k8s.user`, `nlb.user` and `vpc.user` as base dependencies; `auditlog.writer`, `logaas.writer`
|
|
115
|
+
and `monaas.write` depend on the actual audit, logging and monitoring settings,
|
|
116
|
+
which this report cannot inspect. Verify assignments and settings in IAM before
|
|
117
|
+
a separate paid-operation approval. Exact placement resolution also does not
|
|
118
|
+
prove region-wide availability or runtime readiness. Prospective tariffs are
|
|
119
|
+
not supplied: include compute, control plane, disk, public IP, SNAT, load
|
|
120
|
+
balancer and traffic, then run `cloudru-hub cost-compare --input <json> --json`
|
|
121
|
+
separately with verified tariff inputs. This diagnostic does not create an
|
|
122
|
+
approval or unlock a retry.
|
|
123
|
+
|
|
124
|
+
Kubernetes recovery is included in the matched `0.2.7-20261029` engine.
|
|
125
|
+
The operator flow records a private durable intent before
|
|
126
|
+
`k8s_create` or `k8s_add_nodepool`, then saves known operation/resource IDs.
|
|
127
|
+
After a lost reply, `k8s_get` supports `receipt_id`, `operation_id` and
|
|
128
|
+
bounded `receipts:true` discovery in addition to its existing cluster lookup.
|
|
129
|
+
These observations distinguish provider operation state from resource presence;
|
|
130
|
+
they do not check Ready Nodes or application HTTP. This candidate has no new
|
|
131
|
+
live Kubernetes deployment or `web-image` acceptance result.
|
|
132
|
+
|
|
133
|
+
The first recovery increment keeps the same project/kind/parent/name blocked
|
|
134
|
+
after any submitted intent, including an ambiguous outcome or later deletion.
|
|
135
|
+
Changing a request's size does not permit another submission. There is no
|
|
136
|
+
automatic retry, force flag or receipt-removal procedure. A different name is a
|
|
137
|
+
separate deployment with possible duplicate cost. Diagnostics do not create a
|
|
138
|
+
cluster, and existing guarded profiles still deny infrastructure create tools.
|
|
139
|
+
|
|
140
|
+
The Kubernetes preview has two explicit profiles. The default `snake` profile retains its exact ConfigMap, non-root Deployment and ClusterIP Service. `web-image` accepts one reviewed digest-pinned OCI image in a bounded one-pod Deployment and internal ClusterIP Service. It requires a `/health` endpoint on port 8080, a dedicated namespace, non-root UID 10001, a read-only root filesystem and bounded CPU/RAM. The image must already be built, pushed and pullable by the existing cluster. This package does not build code from a repository or prove that an image digest corresponds to a source commit. `k8s-approve` binds exact manifest bytes, cluster, namespace, project and a ten-minute single-use approval. The proxy uses `create_only=true`; inspect partial resources before retrying because Kubernetes multi-document create is not transactional. Neither profile creates a cluster, namespace, nodes, NAT, ingress or load balancer. The four-host live acceptance in `features/cloudru-k8s-snake-deploy/` covers only Snake; `web-image` currently has deterministic local tests.
|
|
141
|
+
|
|
142
|
+
See the [web-image procedure](docs/WEB-IMAGE.md) for authored code and pinned repository inputs.
|
|
87
143
|
|
|
88
144
|
## Safety properties (all layer-1, discrimination-proven)
|
|
89
145
|
|
|
@@ -126,7 +182,7 @@ protections proven:
|
|
|
126
182
|
|
|
127
183
|
## Platform package status
|
|
128
184
|
|
|
129
|
-
The separate `@dzhechkov/cloudru-vm-linux-x64@0.2.
|
|
185
|
+
The separate `@dzhechkov/cloudru-vm-linux-x64@0.2.7` package is installed
|
|
130
186
|
automatically on Linux/x64 unless optional dependencies are disabled. Its static
|
|
131
187
|
build reproduces the pinned SHA-256 and bundles the license and NOTICE files
|
|
132
188
|
of linked Go modules. `CLOUDRU_VM_BIN` and `~/.cloudru-hub/config.json` remain
|
package/bin/cloudru-hub.js
CHANGED
|
File without changes
|
package/data/pointer-section.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Cloud.ru Evolution — указатель (pointer-only таргет)
|
|
2
2
|
|
|
3
3
|
Этот инструмент НЕ несёт на данном таргете ни MCP-тулов Cloud.ru, ни исполняемых
|
|
4
|
-
инструкций деплоя. Полная связка (MCP-сервер `cloudru-vm` на
|
|
4
|
+
инструкций деплоя. Полная связка (MCP-сервер `cloudru-vm` на 146 тулов + скилл +
|
|
5
5
|
детерминированный тормоз платных шагов) доставляется только на таргеты, где её можно
|
|
6
6
|
поставить честно: Claude Code (полный путь) и codex (после живого veto-probe).
|
|
7
7
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Golden classification of every cloudru-vm engine tool. allow requires a BEHAVIOUR audit of the handler at the pinned engine SHA proving both axes: (1) observational with no cloud mutation, job advancement or secret return (a local plan write is disclosed), and (2) no user-controlled argument reaching shell/exec/SSH. Shell-reaching tools remain ask even when arguments are quoted. Schema shape alone never proves allow. The old baseline had two hazards: stack_status advanced planned jobs into paid actions and logs inserted an unescaped service into a shell command. In the pinned engine, stack_status reads persisted state only; stack_apply explicitly advances jobs, and logs shell-quotes service. The specific regression tests are in go/cloudru-vm/internal/mcp/stack_status_test.go and internal/engine/logs_test.go. Any unknown live tool fails drift checks; deny by default.",
|
|
3
|
-
"engineSha256": "
|
|
4
|
-
"toolCount":
|
|
3
|
+
"engineSha256": "94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03",
|
|
4
|
+
"toolCount": 146,
|
|
5
5
|
"counts": {
|
|
6
6
|
"ask": 64,
|
|
7
7
|
"deny": 2,
|
|
8
|
-
"allow":
|
|
8
|
+
"allow": 80
|
|
9
9
|
},
|
|
10
10
|
"tools": {
|
|
11
11
|
"deploy": {
|
|
@@ -73,6 +73,16 @@
|
|
|
73
73
|
"permission": "ask",
|
|
74
74
|
"why": "engine schema declares confirmed prop (gated upstream; belt-plus-braces per ADR-004 §3)"
|
|
75
75
|
},
|
|
76
|
+
"deploy_reconcile": {
|
|
77
|
+
"effect": "read-only",
|
|
78
|
+
"permission": "allow",
|
|
79
|
+
"why": "behaviour-audited read-only: handleDeployReconcile (tools_deploy.go:259) reads a local receipt and project-scoped VM inventory; ReconcileRun only compares observed identities and never writes state or reaches shell."
|
|
80
|
+
},
|
|
81
|
+
"deploy_preflight": {
|
|
82
|
+
"effect": "read-only",
|
|
83
|
+
"permission": "allow",
|
|
84
|
+
"why": "behaviour-audited read-only: handleDeployPreflight (tools_deploy.go:278) reads a saved plan, VM inventory and quota usage with HTTP GET; EvaluatePreflight does not create cloud resources, write state or reach shell."
|
|
85
|
+
},
|
|
76
86
|
"list_flavors": {
|
|
77
87
|
"effect": "read-only",
|
|
78
88
|
"permission": "allow",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"capturedAt": "2026-10-
|
|
3
|
-
"engineVersion": "cloudru-vm 0.2.
|
|
4
|
-
"engineSha256": "
|
|
2
|
+
"capturedAt": "2026-10-29",
|
|
3
|
+
"engineVersion": "cloudru-vm 0.2.7-20261029",
|
|
4
|
+
"engineSha256": "94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03",
|
|
5
5
|
"method": "JSON-RPC tools/list via src/probe-child.js against locally built private engine",
|
|
6
|
-
"toolCount":
|
|
6
|
+
"toolCount": 146,
|
|
7
7
|
"tools": [
|
|
8
8
|
{
|
|
9
9
|
"name": "deploy",
|
|
@@ -57,6 +57,14 @@
|
|
|
57
57
|
"name": "deploy_apply",
|
|
58
58
|
"gated": true
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
"name": "deploy_reconcile",
|
|
62
|
+
"gated": false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "deploy_preflight",
|
|
66
|
+
"gated": false
|
|
67
|
+
},
|
|
60
68
|
{
|
|
61
69
|
"name": "list_flavors",
|
|
62
70
|
"gated": false
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Offline VM / Kubernetes cost worksheet
|
|
2
|
+
|
|
3
|
+
Run from an installed package or this directory:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cloudru-hub cost-compare --input docs/cost-compare.example.json --json
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The command reads one local JSON file and does not resolve the engine, request credentials, call Cloud.ru, create resources or refresh tariffs. `--json` prints exact line, category and known-subtotal decimal strings plus two-decimal display strings. Without it, the command prints a compact human summary. Invalid input exits nonzero and emits no partial JSON. Only `--input <path>` and one optional `--json` are accepted. The file must be a regular, non-symlink file of at most 128 KiB.
|
|
10
|
+
|
|
11
|
+
The [runnable example](cost-compare.example.json) models 24 active hours. Its listed official tariff rows were checked on 2026-10-21 and are pinned as supplied metadata, not live prices or an applicability guarantee. It calculates known subtotals of **251.6616 RUB for VM** and **466.1376 RUB for Kubernetes**. Both leave traffic unpriced and architecture/HA equivalence unknown, so `winner` and `savingsRubExact` are `null`. The 60GB/80GB SSD allocations, IP counts, one Kubernetes SNAT and absence of load balancers are declared scenario assumptions. The worksheet cannot establish whether root, worker or control-plane volumes are separately charged for a real deployment.
|
|
12
|
+
|
|
13
|
+
The v1 input has `schemaVersion: 1`, one decimal-string `horizonHours` (greater than zero, at most 8760), `currency: "RUB"`, and `vatBasis: "included"`. `workload` supplies integer `replicas`, `survivingReplicas` and `failedNodes`, plus decimal-string `cpuPerReplica` and `ramGBPerReplica`. Both scenarios share those targets. Raw JSON numeric tokens must be canonical non-negative safe integers; fractional, exponent, negative and oversized tokens are rejected before parsing. Decimal values belong in strings. Duplicate object member names are rejected at every depth, including escaped spellings of the same name. `equivalence` is either `{ "status": "unknown", "reason": "...", "assumptions": [...] }` or `{ "status": "assumed", "assumptions": ["..."] }`. An assumed comparison remains conditional; this tool has no verified-equivalence state.
|
|
14
|
+
|
|
15
|
+
Exactly one `vm` and one `k8s` scenario are required, each with a unique `id`. Each declares homogeneous `nodes` (`count`, `cpu`, `ramGB`, `cpuShare`, `reservedCpu`, `reservedRamGB`), `controlPlaneCount`, `resources`, and all seven `categories`. CPU reservations are in guaranteed-core units: usable CPU is `cpu × cpuShare − reservedCpu`. Usable RAM is `ramGB − reservedRamGB`. Per-node replica slots are the smaller of the two exact decimal floors. Normal slots use all worker nodes; survival slots subtract the declared failed worker-node count. Control-plane instances are excluded from workload capacity. This is a numerical homogeneous packing bound, not proof of placement, scheduling, service performance, availability or SLA. Kubernetes requires a positive billed control-plane service count; VM requires zero.
|
|
16
|
+
|
|
17
|
+
`resources` declares `storageGB`, `publicIpCount`, `snatGatewayCount`, `loadBalancerCount` and `trafficGB`. Storage and traffic are decimal strings; IP, SNAT and load-balancer counts are integers. `trafficGB` alone may be `null`, meaning usage is unknown and its category must be `unpriced` with a reason. Zero is an explicit declaration and requires `not-used`, never an implicit free charge. Every other positive or unknown declared quantity must be `priced` or `unpriced` with a reason. Compute cannot be `not-used`; VM control-plane must be `not-used`.
|
|
18
|
+
|
|
19
|
+
Every category is `{ "status": "priced", "items": [...] }`, `{ "status": "not-used", "reason": "..." }`, or `{ "status": "unpriced", "reason": "..." }`. Priced items need a unique `id`, description, positive decimal-string `quantity` and `rateRub`, exact category unit, and tariff provenance `{ "url": "https://...", "version": "...", "asOf": "YYYY-MM-DD", "vatBasis": "included", "pageOrRow": "..." }` with optional lowercase SHA-256. The tariff URL and metadata are user assertions; they are checked for format and consistency, not fetched or certified. Supported units are `instance-hour` for compute/control-plane, `GB-hour` for storage, `IP-hour`, `gateway-hour`, `LB-hour`, and `traffic-GB`. Worker and control-plane instance counts and every other priced category quantity must sum exactly to their declared scenario counts or allocations. Traffic is charged once over the whole horizon; time units multiply by the common horizon. Monthly, tiered, stopped-resource and scheduled rates are outside v1.
|
|
20
|
+
|
|
21
|
+
Exact decimal multiplication and summation precede display rounding. Each input decimal has at most nine fractional places, and file, topology, line-count, quantity, rate and result magnitudes are bounded. `knownSubtotalRubExact` is only a sum of priced lines, not a guaranteed lower bound on an actual bill. A winner appears only when both scenarios have all categories resolved, sufficient normal and declared node-failure capacity, and explicit assumed equivalence. It identifies the lower **modeled infrastructure cost under supplied assumptions**, with a tie reported separately. It is not a deployment recommendation. Discounts, taxes beyond the common VAT-inclusive basis, heterogeneous fleets, autoscaling, live billing, network traffic tariffs and real resource inventory need separate evidence.
|
package/docs/HERMES-CODEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Cloud.ru tools in Codex, Hermes, OpenCode and Pi
|
|
2
2
|
|
|
3
|
-
`cloudru-hub mcp-safe` is the common process boundary. It starts only the binary whose SHA-256 matches the package pin and the classification baseline. Its default profile advertises the
|
|
3
|
+
`cloudru-hub mcp-safe` is the common process boundary. It starts only the binary whose SHA-256 matches the package pin and the classification baseline. Its default profile advertises the 80 golden `allow` tools and rejects `ask`, `deny`, unknown tools and unsupported MCP methods before forwarding a call. Some planning tools may still write local plan files. Filesystem and shell access granted separately to an agent remain outside this MCP boundary.
|
|
4
4
|
|
|
5
|
-
Installers default to the smaller `inventory` toolset (11 tools). Select comma-separated groups with `--toolsets inventory,billing`; the available groups are `inventory`, `compute`, `kubernetes`, `data`, `network`, `storage`, and `billing`. `--toolsets all` exposes all
|
|
5
|
+
Installers default to the smaller `inventory` toolset (11 tools). Select comma-separated groups with `--toolsets inventory,billing`; the available groups are `inventory`, `compute`, `kubernetes`, `data`, `network`, `storage`, and `billing`. `--toolsets all` exposes all 80 allow-classified tools. The proxy checks the chosen set on every `tools/call`, so a tool hidden from discovery cannot be invoked directly.
|
|
6
6
|
|
|
7
7
|
For the read-only billing tools, put `agreement_id` in the same owner-only credential file as `key_id`, `secret` and `project_id`, then select `--toolsets billing` or `inventory,billing`. The safe proxy passes `agreement_id` to the Go engine as `CLOUDRU_AGREEMENT_ID`; Codex installation inherits the credential-file path and billing environment variables. If the file is absent, an existing environment-only setup still works. An explicitly configured but missing or invalid file is an error. `billing_consumption` currently returns an agreement-wide aggregate. The Cloud.ru API documents project filtering, but this adapter has not implemented or live-verified that filter; do not label its total as a project-only charge.
|
|
8
8
|
|
|
@@ -10,11 +10,11 @@ The separate `--toolsets vm-deploy` profile allows one reviewed VM plan and remo
|
|
|
10
10
|
|
|
11
11
|
This local approval is an operator workflow, not a hard security boundary against an agent with unrestricted shell access as the same Unix user: such an agent could invoke `approve` or edit the local files itself. Use process/user isolation and cloud IAM limits if the deployment agent is untrusted. The cost number is an estimate, and the operator cap controls the reviewed plan, not Cloud.ru's final bill.
|
|
12
12
|
|
|
13
|
-
The launcher archive does **not bundle** the engine. A Linux/x64 npm install pulls `@dzhechkov/cloudru-vm-linux-x64@0.2.
|
|
13
|
+
The launcher archive does **not bundle** the engine. A Linux/x64 npm install pulls `@dzhechkov/cloudru-vm-linux-x64@0.2.7` as an exact optional dependency. On other platforms, or when optional dependencies are disabled, set `CLOUDRU_VM_BIN` to a trusted copy whose hash matches `package.json.cloudruHub.binaryHashes`. Run `cloudru-hub self-test --json` first. A missing or changed binary makes `mcp-safe` refuse startup.
|
|
14
14
|
|
|
15
15
|
| Host | Setup | Activation check |
|
|
16
16
|
|---|---|---|
|
|
17
|
-
| Codex | `cloudru-hub install --target codex --dir /trusted/project --toolsets vm-deploy` writes `.codex/config.toml` with `required=true`,
|
|
17
|
+
| Codex | `cloudru-hub install --target codex --dir /trusted/project --toolsets vm-deploy` writes `.codex/config.toml` with `required=true`, 14 enabled tools, an explicit 1200-second timeout and inherited paths for the pinned engine and credentials file | Trust the project; in a fresh session call local-only `status` before planning. The profile requests host `auto`, but `codex exec` with approval policy `never` blocked even `status` in the local probe; `--approve-for-me` allowed it. The proxy still requires a separate plan-bound approval for `deploy_apply` |
|
|
18
18
|
| OpenCode | `cloudru-hub install --target opencode --dir /trusted/project` writes `opencode.json` with a local server | `opencode mcp list`, then inspect tools in a session; installer refuses an existing `opencode.jsonc` to avoid conflicting configs |
|
|
19
19
|
| Pi | `cloudru-hub install --target pi --dir /trusted/project` writes `.pi/mcp.json` | Use a Pi release with built-in MCP, trust the project, run `pi mcp list`, then `/mcp` in a session; an older Pi may require an adapter |
|
|
20
20
|
| Hermes | With a hash-verified engine available, `cloudru-hub install --target hermes` adds a guarded entry to `$HERMES_HOME/config.yaml` with an explicit nonempty include list and `CLOUDRU_VM_BIN` path; `--hermes-home` selects a test directory | Run `hermes mcp test cloudru_hub`; complex YAML and existing Cloud.ru entries are refused for manual review |
|
package/docs/LOCAL-TESTING.md
CHANGED
|
@@ -19,8 +19,8 @@ EOF
|
|
|
19
19
|
# c) no override: the installed Linux/x64 optional platform package
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The pinned binary identifies itself as `cloudru-vm 0.2.
|
|
23
|
-
`
|
|
22
|
+
The pinned binary identifies itself as `cloudru-vm 0.2.7-20261029` and has SHA-256
|
|
23
|
+
`94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03`.
|
|
24
24
|
|
|
25
25
|
## 2. Self-test (end-to-end, no credentials required)
|
|
26
26
|
|
|
@@ -33,10 +33,10 @@ Expected output for the current private build:
|
|
|
33
33
|
|
|
34
34
|
```
|
|
35
35
|
✓ resolve: …/cloudru-vm-linux-x64/native/cloudru-vm (source: env)
|
|
36
|
-
✓ sha256:
|
|
37
|
-
✓ engine-version: cloudru-vm 0.2.
|
|
38
|
-
✓ tools-list:
|
|
39
|
-
✓ classification-coverage: every live tool is classified (
|
|
36
|
+
✓ sha256: 94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03 — MATCHES pinned baseline
|
|
37
|
+
✓ engine-version: cloudru-vm 0.2.7-20261029
|
|
38
|
+
✓ tools-list: 146 tools live (snapshot: 146)
|
|
39
|
+
✓ classification-coverage: every live tool is classified (146 golden entries)
|
|
40
40
|
self-test: ALL GREEN
|
|
41
41
|
```
|
|
42
42
|
|
|
@@ -66,4 +66,4 @@ appears; note which prefix spelling the runtime used.
|
|
|
66
66
|
|
|
67
67
|
## 5. Testing from another server (`npm install` path)
|
|
68
68
|
|
|
69
|
-
On Linux/x64, install `@dzhechkov/cloudru-hub@0.1.
|
|
69
|
+
On Linux/x64, install `@dzhechkov/cloudru-hub@0.1.16` normally and run `cloudru-hub self-test` without setting `CLOUDRU_VM_BIN`. npm installs the exact optional engine package. If optional dependencies were disabled or the platform is unsupported, provide a matching trusted binary explicitly.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Existing-cluster web-image profile
|
|
2
|
+
|
|
3
|
+
This profile applies a reviewed image to an existing Cloud.ru Kubernetes cluster. It does not create a cluster, build an image or prove source-to-image provenance. The `snake` profile remains the default and keeps its previous exact manifest contract.
|
|
4
|
+
|
|
5
|
+
## From authored code or a repository
|
|
6
|
+
|
|
7
|
+
1. For a repository, record its URL and immutable commit SHA. For authored code, record the exact source tree or release artifact used by the build.
|
|
8
|
+
2. Build and publish the image in your trusted CI. Record the resulting registry digest, not a mutable tag. Confirm that the target cluster can pull it. This package does not run an untrusted Dockerfile or store registry credentials.
|
|
9
|
+
3. Prepare a two-document YAML manifest with the image digest. Use a dedicated namespace that already exists, and ensure the app answers `GET /health` on port 8080 as UID 10001 with a read-only root filesystem. Example shape:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
apiVersion: apps/v1
|
|
13
|
+
kind: Deployment
|
|
14
|
+
metadata: {name: lesson-web, namespace: lesson-app}
|
|
15
|
+
spec:
|
|
16
|
+
replicas: 1
|
|
17
|
+
selector: {matchLabels: {app: lesson-web}}
|
|
18
|
+
template:
|
|
19
|
+
metadata: {labels: {app: lesson-web}}
|
|
20
|
+
spec:
|
|
21
|
+
automountServiceAccountToken: false
|
|
22
|
+
securityContext: {runAsNonRoot: true}
|
|
23
|
+
containers:
|
|
24
|
+
- name: lesson-web
|
|
25
|
+
image: registry.example.com/lesson-web@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
|
26
|
+
ports: [{containerPort: 8080}]
|
|
27
|
+
readinessProbe: {httpGet: {path: /health, port: 8080}}
|
|
28
|
+
resources:
|
|
29
|
+
requests: {cpu: 50m, memory: 64Mi}
|
|
30
|
+
limits: {cpu: 200m, memory: 256Mi}
|
|
31
|
+
securityContext:
|
|
32
|
+
allowPrivilegeEscalation: false
|
|
33
|
+
readOnlyRootFilesystem: true
|
|
34
|
+
runAsUser: 10001
|
|
35
|
+
runAsGroup: 10001
|
|
36
|
+
capabilities: {drop: [ALL]}
|
|
37
|
+
---
|
|
38
|
+
apiVersion: v1
|
|
39
|
+
kind: Service
|
|
40
|
+
metadata: {name: lesson-web, namespace: lesson-app}
|
|
41
|
+
spec:
|
|
42
|
+
type: ClusterIP
|
|
43
|
+
selector: {app: lesson-web}
|
|
44
|
+
ports: [{port: 80, targetPort: 8080}]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The registry and digest above are placeholders. Replace them with a real, pullable digest before approval. The exact byte sequence of the final manifest is what the approval binds.
|
|
48
|
+
|
|
49
|
+
4. Compute `sha256sum web.yaml`, review the file and its resource footprint, then run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cloudru-hub k8s-approve --cluster-id <uuid> --namespace lesson-app --manifest web.yaml --manifest-sha256 <sha256> --profile web-image
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
5. The agent calls `k8s_apply` with only the returned `approval_id`. The proxy forwards the approved manifest with `create_only=true`. Use the bounded `k8s_kubectl` queries for pods, deployment, service and rollout. An API success is not evidence of a healthy application; inspect the rollout and workload health.
|
|
56
|
+
|
|
57
|
+
Kubernetes creation across the two documents is not transactional. If one resource is created and the next fails, inspect the exact named resources before cleanup or another attempt. The profile exposes only a ClusterIP; a public endpoint requires a separate, reviewed ingress decision. Cluster and worker charges are outside this approval.
|