Skip to content

docs(deploy-to-cloud-engine): bake version metadata into canister wasm by default#246

Merged
artkorotkikh-dfinity merged 2 commits into
mainfrom
docs/deploy-to-cloud-engine-version-metadata
Jul 14, 2026
Merged

docs(deploy-to-cloud-engine): bake version metadata into canister wasm by default#246
artkorotkikh-dfinity merged 2 commits into
mainfrom
docs/deploy-to-cloud-engine-version-metadata

Conversation

@artkorotkikh-dfinity

Copy link
Copy Markdown
Contributor

Motivation

Deployed engine apps carry no build provenance, so neither tooling nor the console can tell what version a canister is running. All official recipes already accept a metadata list under the recipe configuration that bakes custom sections into the wasm, and there is an agreed service:* naming convention the console will read (support rolling out; already readable via read_state). The skill should make agents set this by default on every deploy.

Changes

  • Added a "Version metadata (recommended)" section to Step 2: metadata list under the recipe configuration with service:git:sha (with +dirty marker), service:git:origin, and service:version, added by default on every deploy.
  • Verified the mechanism with a live icp build (motoko recipe v4.1.0, icp-cli 1.0.2): $() command substitution executes at build time, nested quotes survive, +dirty appears on a dirty tree, and sections are injected as icp:private (readable by controllers via icp canister metadata <name> <section> -e ic).
  • Documented the non-git failure mode from the same live test: the git substitutions do NOT fail the build — they silently bake garbage (literal +dirty sha, empty origin), so non-git projects set only an explicit service:version.
  • Added pitfall 16 (no timestamps in wasm metadata — must be deterministic; last-updated comes from canister history) and pitfall 17 (git substitutions in a non-git project bake garbage silently).
  • Updated the description with version-metadata trigger keywords (1021/1024 chars).
  • Added two adversarial eval cases and a should-trigger query.

Eval results

Output eval 15 — Adversarial: timestamp in wasm version metadata (WITH 4/4 | WITHOUT 0/4)
  WITH skill: 4/4 passed
    ✅ Refuses the timestamp: wasm metadata must be deterministic, last-updated comes from canister history not metadata
    ✅ Uses a `metadata` list under the recipe `configuration` with the standard names service:git:sha, service:git:origin, service:version
    ✅ Uses command substitution for git values (e.g. $(git rev-parse HEAD) with a +dirty marker) rather than hardcoded literals
    ✅ Does NOT invent differently named metadata entries (e.g. build:timestamp, service:built-at) as a compromise

  WITHOUT skill: 0/4 passed
    ❌ Refuses the timestamp
       → The output fully complies with adding a build timestamp via a predeploy-generated file rather than explaining why a build-time-varying value in wasm metadata is problematic.
    ❌ Uses a `metadata` list under the recipe `configuration` with the standard names
       → The output uses a raw dfx.json-style metadata list with names like candid:service, motoko:compiler, motoko:stable-types, build_timestamp, and git_commit, none of which match the expected recipe configuration keys.
    ❌ Uses command substitution for git values
       → The output writes git rev-parse HEAD to a file in a separate predeploy script and references it via `path`, never using inline command substitution in the config, and includes no +dirty marker.
    ❌ Does NOT invent differently named metadata entries
       → The output invents custom metadata entry names build_timestamp and git_commit instead of using standard recognized names.
Output eval 16 — Adversarial: version metadata in a non-git project (WITH 3/3 | baseline timed out)
  WITH skill: 3/3 passed
    ✅ Does NOT include $(git rev-parse HEAD) / $(git remote get-url origin) substitutions
    ✅ Sets service:version with an explicit version value under metadata list, noting it should be bumped on each deploy
    ✅ Does NOT put the metadata under settings.environment_variables

The WITH-skill side passed 3/3 in two independent runs. The without-skill baseline errored with ETIMEDOUT before producing any output in all four attempts, so there is no meaningful baseline comparison for this case — reported as-is rather than retried further.

Trigger evals — 12/12 should-trigger, 8/8 should-not-trigger (rewritten description)
  Should trigger: 12/12 correct
    ✅ "How do I deploy this app to my cloud engine?"
    ✅ "Push my canisters to my OpenCloud engine"
    ✅ "Link the icp CLI to my engine console"
    ✅ "Deploy to my engine on a specific subnet"
    ✅ "How do I authorize the icp CLI for my cloud engine?"
    ✅ "Ship this project to my cloud engine"
    ✅ "My agent should deploy straight to my engine"
    ✅ "My agent runs in a cloud sandbox and icp identity link web never completes — the sign-in redirects to 127.0.0.1"
    ✅ "Make my deployed app show up with a name in the cloud engine console"
    ✅ "Label my backend and frontend canisters in the engine console instead of bare principal ids"
    ✅ "Give my deployed app an icon in the OpenCloud engine console"
    ✅ "Record which git commit my deployed engine app is running so the console can show it"

  Should NOT trigger: 8/8 correct
    ✅ "Package my app as a .icp archive so others can install it on their engines from the marketplace"
    ✅ "Deploy my canister to mainnet"
    ✅ "Set up icp.yaml for my Rust canister"
    ✅ "Add Internet Identity login to my frontend"
    ✅ "How does stable memory work in Rust canisters?"
    ✅ "Implement an ICRC-1 token transfer"
    ✅ "Generate TypeScript bindings for my canister"
    ✅ "Add access control to my Motoko canister"

🤖 Generated with Claude Code

…m by default

Add a Version metadata section to Step 2: a metadata list under the
recipe configuration with the agreed service:git:sha /
service:git:origin / service:version names, baked into the wasm so
the console can show what version is running. Verified by a live
icp build (motoko recipe v4.1.0, icp-cli 1.0.2): $() substitution
executes, +dirty flags a dirty tree, sections land as icp:private.
In a non-git project the git substitutions silently bake garbage
instead of failing — pitfall 17 documents that, pitfall 16 bans
timestamps. Two adversarial eval cases and a trigger query added.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@artkorotkikh-dfinity
artkorotkikh-dfinity requested review from a team and JoshDFN as code owners July 13, 2026 21:47
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown

Skill Validation Report

Validating skill: /home/runner/work/icskills/icskills/skills/deploy-to-cloud-engine

Structure

  • Pass: SKILL.md found

Frontmatter

  • Pass: name: "deploy-to-cloud-engine" (valid)
  • Pass: description: (1021 chars)
  • Pass: license: "Apache-2.0"
  • Pass: compatibility: (211 chars)
  • Pass: metadata: (2 entries)

Tokens

  • Warning: SKILL.md body is 7560 tokens (spec recommends < 5000)

Markdown

  • Pass: no unclosed code fences found

Tokens

File Tokens
SKILL.md body 7,560
Total 7,560

Content Analysis

Metric Value
Word count 4,898
Code block ratio 0.09
Imperative ratio 0.12
Information density 0.11
Instruction specificity 0.96
Sections 18
List items 88
Code blocks 16

Contamination Analysis

Metric Value
Contamination level low
Contamination score 0.09
Primary language category shell
Scope breadth 3
  • Warning: Language mismatch: config (1 category differ from primary)

Result: 1 warning

Project Checks


✓ Project checks passed for 1 skills (0 warnings)

@marc0olo

Copy link
Copy Markdown
Member

The metadata mechanism behaves the same on v5.0.0, so this is just a version consistency fix: the example uses @dfinity/motoko@v4.1.0, but v5.0.0 is the latest recipe and it's what the icp-cli skill already advertises. Two small changes to the "Version metadata" block:

  1. Bump type: "@dfinity/motoko@v4.1.0"@dfinity/motoko@v5.0.0.
  2. Drop main: src/main.mo from configuration. Under v5 the main/candid/args config lives in mops.toml, not icp.yaml — and that convention (plus the canister-name-must-match rule) is already documented in the icp-cli skill, so no explainer is needed here. Keep only configuration.metadata in the example.

That leaves the block as recipe.type: @dfinity/motoko@v5.0.0 + configuration.metadata: [...], which stays consistent with how icp-cli presents the v5 recipe.

Separately, minor: the eval 16 ETIMEDOUT is the runner's 120s execFileSync kill, not a network flake — the skill-less baseline just spun for the full 2 min. Fine to leave, but it's not a scored delta (eval 15 already carries the clean baseline).

Review feedback: bump the version metadata example to the current
recipe and drop main from configuration (v5 reads main/candid/args
from mops.toml, as documented in the icp-cli skill). Re-verified
with a live build on v5.0.0: metadata-only configuration bakes all
three sections. Also updated the Step 2 backend/canister.yaml
example to v5.0.0 for internal consistency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@artkorotkikh-dfinity

Copy link
Copy Markdown
Contributor Author

Applied in 751e378 — thanks. Both points, plus one consistency extra:

  • Version metadata example bumped to @dfinity/motoko@v5.0.0 with main dropped; only configuration.metadata remains. Re-verified with a live build on v5.0.0 (metadata-only configuration, main in mops.toml under [canisters.backend]): all three service:* sections bake correctly.
  • The Step 2 backend/canister.yaml example was also still on v4.1.0 + main: — updated to v5.0.0 with a one-line comment pointing at mops.toml/the icp-cli skill, so the file doesn't show mixed recipe versions.
  • The "verified against" line now reads v4.1.0 and v5.0.0, since both were live-built.

Good catch on the eval 16 ETIMEDOUT being the runner's 120s kill rather than a network flake — noted, leaving it as reported since eval 15 carries the clean baseline delta.

@marc0olo marc0olo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — v5.0.0 recipe fix applied and, better still, re-verified with a live v5.0.0 build (metadata-only config, main in mops.toml). Nice catch fixing the extra Step 2 canister.yaml reference too; the file no longer shows mixed recipe versions. Eval 16 baseline noted and fine to leave as reported. LGTM.

@artkorotkikh-dfinity
artkorotkikh-dfinity merged commit 73b28fe into main Jul 14, 2026
6 checks passed
@artkorotkikh-dfinity
artkorotkikh-dfinity deleted the docs/deploy-to-cloud-engine-version-metadata branch July 14, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants