@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 CHANGED
@@ -5,7 +5,7 @@
5
5
  "files": [
6
6
  {
7
7
  "path": "CHANGELOG.md",
8
- "sha256": "4d6d0e6f47b09b017a32e4f3287c0669821e61569835c397245e5077435f5b15"
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": "2c25bbd9659f1a02e2a7a5413de8673e8735d67e583458fdede75dfd032b6106"
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": "6f4557d97b90936ef153dc90eac4bdceb6846b6d08d8aec45c86baac328cca60"
32
+ "sha256": "32845b9cc1ad64f2f6a8d4eeb8b90bf36710590d25db4234e6813793ef63c5d4"
33
33
  },
34
34
  {
35
35
  "path": "data/tools-classification.json",
36
- "sha256": "9abb3ca6347b0cd6cd51b23649c59e0a0c816e6c12a325f573b392affa350d0b"
36
+ "sha256": "a4bf5a2e76d4f9bd686bec704919ab3380bb22ff46a399b058dc5b3e71a64dd1"
37
37
  },
38
38
  {
39
39
  "path": "data/tools-list-snapshot.json",
40
- "sha256": "dff98bb859dfbbf8ed2fb6f16f17b767444cc2172ecc7684ca3df8188c95707c"
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": "3605797972f9a7c79c13fbcce0cd9ac8b8fb3a8249abbab938f2b1a45f515420"
48
+ "sha256": "00a5c9acc3999c5e07f230ba63bc1a0e202d62c8d15d2204620feca762eece8f"
45
49
  },
46
50
  {
47
51
  "path": "docs/LOCAL-TESTING.md",
48
- "sha256": "de6f709e820fbb831f390e274238b28ba938644670a8db04fd50d1fa0d86da75"
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": "d906ed1871af834274d77dbd3a345b168604456b15360f009a9474778273b11f"
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": "a22f3fcced6e9970e7bed6b935fc03637b6d4b8d3cd6cc65a2685595a6549c1c"
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": "c928db48febfb4a79884ce1830365a8302122a55fb5d6ada8e771c049396ca89"
92
+ "sha256": "90af414aabda1c48c82c9c2ecf0dbe4d7dd4744486efdce0d8367182b6a67a38"
77
93
  },
78
94
  {
79
95
  "path": "src/k8s-approval.js",
80
- "sha256": "014c2effd1e843d870ae1af4f93f5c82faff9972e9f6ca028ed39d47ec33ee3a"
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": "4f95cfb9576c411232503b26053ece6d8d186e4b337beff9bd4afd249f74a014"
112
+ "sha256": "60a5db44db9518835f98f23aa104b86c3621f0b0c030196c2130715f5c19c219"
93
113
  },
94
114
  {
95
115
  "path": "src/toolsets.js",
96
- "sha256": "89229c882b6a3f5bb9ee542b31d1a40374b91879747582a3e82e8aadc72decb3"
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": "026e3031180ce9227b4d31775b9c886b10ad0cb418ed0754aed75b12ded84e31"
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": "d72e4363fd50d0fcc65fd23b40601516bd73368c3659f2a55a394c4d1ed3f85c"
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": "af04d1941fb7aed2cc51b05f65ffed1c440d4d23a7fb26e13261d45e07b0a1f8"
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": "9ad83ba418dee86ef680575a0cc7da4af4be3ae995c00f23f84b336838fbda47"
168
+ "sha256": "b447a8755c67bcbfc3db63c909235f34414721139c7bc7fdf0658ab0638e7980"
145
169
  },
146
170
  {
147
171
  "path": "test/safe-mcp.test.mjs",
148
- "sha256": "45406dabeee0635ede5cda3d2f4144c5c13886d624287fd223b01252d2859b87"
172
+ "sha256": "e99200ccdd8368260af18532478addc08e3b6195986cecf68b669e9dc639d119"
149
173
  }
150
174
  ]
151
175
  },
152
- "signature": "l3G7HKnPVq9k2pUtMFbeaNybS7PUSGnlJAn3dSZvcXRKKQ4TEFmxYfgO4Urjw2spCra9M3UAkRVSJfkUX7zYCw=="
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/@dzhechkov/cloudru-hub
3
+ Site: https://aicoding.space · Source: https://github.com/djd1m/dz-harness/tree/main/packages/cloudru-hub
4
4
 
5
- > **Linux/x64 distribution:** version 0.1.14 installs the exact optional engine
6
- > package `@dzhechkov/cloudru-vm-linux-x64@0.2.6`. The `dz guard` `licence-hold`
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 **144 tools**
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. On Linux/x64, npm installs the exact
19
- optional platform package with the pinned Go engine. Run:
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.14
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, 144 live MCP tools and their
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 78 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 |
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 Snake profile
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 `4463b093…97a97c9b6`, engine version, 144 live tools, full
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 included in the launcher as **opt-in previews**. They require the matching pinned Go binary and temporary credentials in a private local file. On Linux/x64, a normal npm installation supplies that binary through the optional platform package. A static site can be planned from a local HTML file or a bounded ZIP snapshot with `deploy_plan(static_site_source=...)`; the operator checks the source digest shown in the plan and supplies that digest to `approve`. Every included ZIP file is served publicly, so review the file list in the plan and keep credentials out of the artifact. Planning requires an existing available floating IP, whose ID is bound to the approval. Apply creates a dedicated TCP-80 security group, boots the VM from fixed Cloud-init data, and reports the site as verified only after the approved IP is attached to that VM and exact-byte HTTP checks pass. A private run journal keeps resource IDs for interrupted-run recovery and blocks blind replay. The estimate covers compute and disk; a public IP may have a separate charge. The static-site route does not authorize `vm_exec` over SSH. See the two-Snake acceptance procedure in `features/cloudru-cli-vm-deploy/SNAKE-ACCEPTANCE.md` in the private source tree.
85
-
86
- The Kubernetes preview accepts only one reviewed manifest containing a ConfigMap, a non-root single-pod Deployment and a ClusterIP Service in an existing namespace. `k8s-approve` binds exact manifest bytes, cluster, namespace, project and a ten-minute single-use approval. The agent passes only the approval ID; the proxy forwards `create_only=true`, so an existing resource name is a conflict instead of being patched. Kubernetes `create` is not transactional across three documents: inspect and clean up any partially created resources by exact name before trying again. Use a dedicated namespace with no pre-existing resources matching the app label or the three approved names. The profile does not create a cluster, namespace, nodes, NAT, ingress or load balancer. The example, local evidence and four-host live acceptance are in `features/cloudru-k8s-snake-deploy/` in private source. The service account's initial 403 was resolved by granting the Kubernetes role; Claude Code inference remains unavailable for a live host test.
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.6` package is installed
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
File without changes
@@ -1,7 +1,7 @@
1
1
  # Cloud.ru Evolution — указатель (pointer-only таргет)
2
2
 
3
3
  Этот инструмент НЕ несёт на данном таргете ни MCP-тулов Cloud.ru, ни исполняемых
4
- инструкций деплоя. Полная связка (MCP-сервер `cloudru-vm` на 144 тула + скилл +
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": "4463b09323c9dfe0c32d618d335734a947e7fab0a75d07a7bea089b97a97c9b6",
4
- "toolCount": 144,
3
+ "engineSha256": "94fdba4fe4d1a71e684be998c3d2f94f374ff4e4054db2683bcc04d72bfabc03",
4
+ "toolCount": 146,
5
5
  "counts": {
6
6
  "ask": 64,
7
7
  "deny": 2,
8
- "allow": 78
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-20",
3
- "engineVersion": "cloudru-vm 0.2.6-20261020",
4
- "engineSha256": "4463b09323c9dfe0c32d618d335734a947e7fab0a75d07a7bea089b97a97c9b6",
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": 144,
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.
@@ -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 78 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.
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 78 allow-classified tools. The proxy checks the chosen set on every `tools/call`, so a tool hidden from discovery cannot be invoked directly.
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.6` 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.
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`, 12 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` |
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 |
@@ -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.6-20261020` and has SHA-256
23
- `4463b09323c9dfe0c32d618d335734a947e7fab0a75d07a7bea089b97a97c9b6`.
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: 4463b09323c9dfe0c32d618d335734a947e7fab0a75d07a7bea089b97a97c9b6 — MATCHES pinned baseline
37
- ✓ engine-version: cloudru-vm 0.2.6-20261020
38
- ✓ tools-list: 144 tools live (snapshot: 144)
39
- ✓ classification-coverage: every live tool is classified (144 golden entries)
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.14` 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.
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.