Skip to content

chore(release): 0.11.0-rc.34 - #3921

Merged
chefsale merged 1 commit into
masterfrom
chore/release-0.11.0-rc.34
Sep 14, 2026
Merged

chefsale merged 1 commit into
masterfrom
chore/release-0.11.0-rc.34

Conversation

@frdomovic

Copy link
Copy Markdown
Member

Warning

Merging this PR is the release. release.yml triggers on a push to master touching Cargo.toml, so there is no separate "cut" step to reconsider at. Merging publishes the binaries, the Docker images and the Homebrew formula, and mints the 0.11.0-rc.34 tag that every downstream repo pins to.

One line, the same shape as every previous cut (#3903 for rc.33, #3821 for rc.32):

 [workspace.metadata.workspaces]
-version = "0.11.0-rc.33"
+version = "0.11.0-rc.34"

What is in it

Eight commits since 0.11.0-rc.33, none of them breaking — no ! commits, so this is a routine additive rc:

Master's state

rc.33's blocker is gone. That cut was held because E2E - Rust Apps had been red on master for four consecutive runs (group-kick-and-readd-deny-list). It is green again — the last eight master runs are seven successes and one cancelled:

success  2026-09-14T10:44  e2971c1b
success  2026-09-13T06:39  70cdf7c1
success  2026-09-11T20:09  6da23c42
cancelled 2026-09-11T09:51 a6f2f55c
success  2026-09-11T09:04  36e6114f
success  2026-09-11T05:38  a22c5b94
success  2026-09-10T14:42  2c0aae4c
success  2026-09-10T14:15  75d2c442

Every other workflow on master's tip is green: CI, Release, ABI Guard, ABI Contract, Wire Contract, Crypto Vectors, App migration e2e, SDK E2E (paired), E2E - Rust Apps (Released Image), Docs CI.

Two reds, neither a regression from these eight commits:

  • Fuzzy Load Test - Long Running Performance — failing 32 of the last 40 master runs, continuously since at least 2026-09-08, across many unrelated SHAs. rc.33 was itself cut with this red, so it is precedent rather than a new signal. Worth its own investigation, not a reason to hold a cut.
  • cargo in /. for aws-lc-sys — a Dependabot update job, not a code check.

Worth deciding before you merge

Five of @rtb-12's account-namespace / device-certificate PRs are open, and two are breaking: #3890 and #3906 (plus #3889, #3905, #3907). If those land soon after this, you get a breaking rc immediately behind an additive one. This is a fair cut now if you want the TEE and sealing work released ahead of that series; if the governance series is close, waiting and cutting once may be cheaper for the fleet.

Downstream, for context

The fleet is still on rc.32 — apps#70 bumps it to rc.33 and is open with three Browser E2E legs failing. So rc.34 would be two releases ahead of what apps actually pins; the fleet-bump automation will open its own PRs on release.

What is NOT in this PR

No changelog, no tag, no Cargo.lock change — matching #3903 and #3821, which touched Cargo.toml alone.

The [workspace.metadata.workspaces] comment still points at docs/RELEASE.md, which does not exist in the tree. Flagged on the rc.33 PR too and still unfixed; worth restoring or correcting separately from a release cut.

🤖 Generated with Claude Code

Matches the shape of every previous cut (#3903 for rc.33, #3821 for rc.32):
one line, the shared version in [workspace.metadata.workspaces]. No changelog,
no tag, no Cargo.lock change.

⚠️ MERGING THIS IS THE RELEASE. `release.yml` triggers on a push to master
touching `Cargo.toml`, so there is no separate cut step after this — see the PR
description for what merging publishes and what state master is in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chefsale pushed a commit to calimero-network/mero-tee that referenced this pull request Sep 14, 2026
Two CI guards caught what the bump commit missed, both correctly.

`release-version-sync-guard` requires `mero-kms/Cargo.toml`,
`Cargo.lock`'s `mero-kms-phala` entry and `versions.json`'s `imageVersion`
to move together. Bumping the image to 2.3.55 without the KMS left
`kms=2.3.54, merod=2.3.55` — and the guard exists because these two ship
as one artifact pair, so a version skew between them is a real thing to
refuse rather than a bookkeeping nit.

`docs-update-guard` requires any change under `mero-tee/**` to carry a
matching `docs/**`, `README.md` or `CHANGELOG.md` change. The previous
mero-tee PR (#268) satisfied it incidentally by updating
`fleet-sidecar.mdx`; a version bump touches no page, so it needs the
changelog entry it should have had anyway.

Both were run locally before this push rather than resubmitted to CI:
`scripts/policy/check_release_version_sync.sh` now reports
`OK: kms=2.3.55, merod=2.3.55`, and the changelog entry puts CHANGELOG.md
in the PR's diff, which is what the docs guard reads.

The three sidecar harness tests still pass.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3
@chefsale
chefsale merged commit 6c6fb4a into master Sep 14, 2026
21 checks passed
@chefsale
chefsale deleted the chore/release-0.11.0-rc.34 branch September 14, 2026 13:53
chefsale added a commit to calimero-network/mero-tee that referenced this pull request Sep 14, 2026
* feat(sidecar): write the account recovery envelopes nothing ever wrote

`PUT /api/fleet/recovery-envelope` has had no client since it shipped
(calimero-network/mdma#223). Holding an account root recovers the ACCOUNT
but not the LIST of namespaces it belongs to — those are separate
keypairs, and the local governance state left with the lost device. With
no envelope ever written, those namespaces keep existing on other
people's nodes and are permanently unaddressable, and nothing about the
gap is observable as a failure: the read is correctly defined to return
"nothing yet" rather than an error, so the recovery path simply always
shows nothing to recover.

The blocker was that sealing had no surface. That prerequisite was
decided as option (2) on the issue and shipped in
calimero-network/core#3918, so the sidecar can now seal via `meroctl
account seal-to`. The recipient is the account ROOT, never a device key:
a device is exactly what is gone in the case worth sealing for, so an
envelope addressed to one is valid, opaque, and unopenable precisely when
it is needed.

One envelope per (account, namespace) this node serves, never one per
account. A relay only sees the namespaces it serves, so a per-account
envelope is a partial list overwriting other relays' partial lists —
mdma#228, fixed by keying mdma's table per namespace.

Four things here fail silently, so each is a test rather than a comment:

- **Only linked accounts are written for.** `member_count` reports counts
  and never rosters, aggregating inside the TEE so no account id crosses
  the wire; an envelope per member would undo that by naming every member
  of every served namespace, including people with no cloud account.
  `GET /api/fleet/linked-accounts` says which accounts hold a cloud
  login and the intersection happens here, against a roster that never
  leaves. That is also the predicate authorizing the READ, so an envelope
  for an unlinked account could never be fetched by anyone.
- **An incomplete walk writes nothing.** A shorter list overwriting a
  longer one at a higher version is the same data loss as a stale
  clobber, carrying a valid version number.
- **The version is durable**, in `fleet-recovery.json` rather than memory
  or wall-clock. mdma answers 409 to a version going backwards, so a
  counter resetting on restart strands the envelope permanently.
- **Change is detected on the PLAINTEXT.** Sealing mints a fresh
  ephemeral key per call, so identical data seals to different bytes.
  Comparing ciphertexts would report a change every pass, burn a version
  each time, and rewrite mdma forever for data that never moved.

mdma's `ciphertext` is one opaque string while a sealed envelope is four
values a recovering holder needs, so the envelope is JSON-encoded into
it. mdma stores it without looking, as it does everything else here.

Verified: the new harness test drives all eight behaviours against
stubbed `meroctl` and `curl` — including a seal stub that returns
different bytes every call, which is what makes the plaintext-hash
assertion meaningful — and the existing authorship and inventory tests
still pass. `bash -n` and `shellcheck` clean on the rendered template.

Refs calimero-network/mdma#223.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* chore: bump merod to 0.11.0-rc.34, release 2.3.55

The sidecar's recovery-envelope writer (#268, calimero-network/mdma#223)
shells out to `meroctl account seal-to`, which arrived in
calimero-network/core#3918 and ships for the first time in 0.11.0-rc.34.
The pin was still rc.33, so the image as it stood ran a writer whose one
external call did not exist.

`imageVersion` moves with it, as in #207 (rc.8 to rc.13): merod is
measured into the image, so a new binary is a new MRTD and needs its own
image version rather than silently changing what 2.3.54 means.

Also asserts the dependency instead of relying on the pin being right,
which is the half #268 should have carried and did not. The delegated-
execution block already does this for `--public-intents`, for the same
reason and with the same failure shape: pin a core release that predates
the feature and the image still builds, still boots, and the sidecar still
runs — `seal_for_account` just fails every pass, logs a WARN, and writes
nothing.

That direction is the safe one, and that is precisely what makes it worth
asserting. Nothing breaks, no envelope is ever written, and the gap
surfaces when someone loses their last device and finds there is nothing
to recover from — the one moment no retroactive fix exists. A build-time
failure with a message naming the release to bump to is a much cheaper
place to learn it.

`meroctl account --help` is the part a CLI answers without a node, a
network or a config file, so it probes the baked binary the same way
`--public-intents` probes merod. Gated on `merod_mode == "read-only"`,
which is what gates the sidecar itself.

Verified: the three sidecar harness tests still pass (recovery, inventory,
authorship) and the task file parses as YAML.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* fix(release): sync the KMS version and record the bump in the changelog

Two CI guards caught what the bump commit missed, both correctly.

`release-version-sync-guard` requires `mero-kms/Cargo.toml`,
`Cargo.lock`'s `mero-kms-phala` entry and `versions.json`'s `imageVersion`
to move together. Bumping the image to 2.3.55 without the KMS left
`kms=2.3.54, merod=2.3.55` — and the guard exists because these two ship
as one artifact pair, so a version skew between them is a real thing to
refuse rather than a bookkeeping nit.

`docs-update-guard` requires any change under `mero-tee/**` to carry a
matching `docs/**`, `README.md` or `CHANGELOG.md` change. The previous
mero-tee PR (#268) satisfied it incidentally by updating
`fleet-sidecar.mdx`; a version bump touches no page, so it needs the
changelog entry it should have had anyway.

Both were run locally before this push rather than resubmitted to CI:
`scripts/policy/check_release_version_sync.sh` now reports
`OK: kms=2.3.55, merod=2.3.55`, and the changelog entry puts CHANGELOG.md
in the PR's diff, which is what the docs guard reads.

The three sidecar harness tests still pass.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

---------

Co-authored-by: Claude <noreply@anthropic.com>
chefsale added a commit to calimero-network/mero-tee that referenced this pull request Sep 15, 2026
…#270)

* feat(sidecar): write the account recovery envelopes nothing ever wrote

`PUT /api/fleet/recovery-envelope` has had no client since it shipped
(calimero-network/mdma#223). Holding an account root recovers the ACCOUNT
but not the LIST of namespaces it belongs to — those are separate
keypairs, and the local governance state left with the lost device. With
no envelope ever written, those namespaces keep existing on other
people's nodes and are permanently unaddressable, and nothing about the
gap is observable as a failure: the read is correctly defined to return
"nothing yet" rather than an error, so the recovery path simply always
shows nothing to recover.

The blocker was that sealing had no surface. That prerequisite was
decided as option (2) on the issue and shipped in
calimero-network/core#3918, so the sidecar can now seal via `meroctl
account seal-to`. The recipient is the account ROOT, never a device key:
a device is exactly what is gone in the case worth sealing for, so an
envelope addressed to one is valid, opaque, and unopenable precisely when
it is needed.

One envelope per (account, namespace) this node serves, never one per
account. A relay only sees the namespaces it serves, so a per-account
envelope is a partial list overwriting other relays' partial lists —
mdma#228, fixed by keying mdma's table per namespace.

Four things here fail silently, so each is a test rather than a comment:

- **Only linked accounts are written for.** `member_count` reports counts
  and never rosters, aggregating inside the TEE so no account id crosses
  the wire; an envelope per member would undo that by naming every member
  of every served namespace, including people with no cloud account.
  `GET /api/fleet/linked-accounts` says which accounts hold a cloud
  login and the intersection happens here, against a roster that never
  leaves. That is also the predicate authorizing the READ, so an envelope
  for an unlinked account could never be fetched by anyone.
- **An incomplete walk writes nothing.** A shorter list overwriting a
  longer one at a higher version is the same data loss as a stale
  clobber, carrying a valid version number.
- **The version is durable**, in `fleet-recovery.json` rather than memory
  or wall-clock. mdma answers 409 to a version going backwards, so a
  counter resetting on restart strands the envelope permanently.
- **Change is detected on the PLAINTEXT.** Sealing mints a fresh
  ephemeral key per call, so identical data seals to different bytes.
  Comparing ciphertexts would report a change every pass, burn a version
  each time, and rewrite mdma forever for data that never moved.

mdma's `ciphertext` is one opaque string while a sealed envelope is four
values a recovering holder needs, so the envelope is JSON-encoded into
it. mdma stores it without looking, as it does everything else here.

Verified: the new harness test drives all eight behaviours against
stubbed `meroctl` and `curl` — including a seal stub that returns
different bytes every call, which is what makes the plaintext-hash
assertion meaningful — and the existing authorship and inventory tests
still pass. `bash -n` and `shellcheck` clean on the rendered template.

Refs calimero-network/mdma#223.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* chore: bump merod to 0.11.0-rc.34, release 2.3.55

The sidecar's recovery-envelope writer (#268, calimero-network/mdma#223)
shells out to `meroctl account seal-to`, which arrived in
calimero-network/core#3918 and ships for the first time in 0.11.0-rc.34.
The pin was still rc.33, so the image as it stood ran a writer whose one
external call did not exist.

`imageVersion` moves with it, as in #207 (rc.8 to rc.13): merod is
measured into the image, so a new binary is a new MRTD and needs its own
image version rather than silently changing what 2.3.54 means.

Also asserts the dependency instead of relying on the pin being right,
which is the half #268 should have carried and did not. The delegated-
execution block already does this for `--public-intents`, for the same
reason and with the same failure shape: pin a core release that predates
the feature and the image still builds, still boots, and the sidecar still
runs — `seal_for_account` just fails every pass, logs a WARN, and writes
nothing.

That direction is the safe one, and that is precisely what makes it worth
asserting. Nothing breaks, no envelope is ever written, and the gap
surfaces when someone loses their last device and finds there is nothing
to recover from — the one moment no retroactive fix exists. A build-time
failure with a message naming the release to bump to is a much cheaper
place to learn it.

`meroctl account --help` is the part a CLI answers without a node, a
network or a config file, so it probes the baked binary the same way
`--public-intents` probes merod. Gated on `merod_mode == "read-only"`,
which is what gates the sidecar itself.

Verified: the three sidecar harness tests still pass (recovery, inventory,
authorship) and the task file parses as YAML.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* fix(release): sync the KMS version and record the bump in the changelog

Two CI guards caught what the bump commit missed, both correctly.

`release-version-sync-guard` requires `mero-kms/Cargo.toml`,
`Cargo.lock`'s `mero-kms-phala` entry and `versions.json`'s `imageVersion`
to move together. Bumping the image to 2.3.55 without the KMS left
`kms=2.3.54, merod=2.3.55` — and the guard exists because these two ship
as one artifact pair, so a version skew between them is a real thing to
refuse rather than a bookkeeping nit.

`docs-update-guard` requires any change under `mero-tee/**` to carry a
matching `docs/**`, `README.md` or `CHANGELOG.md` change. The previous
mero-tee PR (#268) satisfied it incidentally by updating
`fleet-sidecar.mdx`; a version bump touches no page, so it needs the
changelog entry it should have had anyway.

Both were run locally before this push rather than resubmitted to CI:
`scripts/policy/check_release_version_sync.sh` now reports
`OK: kms=2.3.55, merod=2.3.55`, and the changelog entry puts CHANGELOG.md
in the PR's diff, which is what the docs guard reads.

The three sidecar harness tests still pass.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* feat(sidecar): register with mdma, binding what it reports to a quote

Everything this sidecar told mdma about itself was taken on trust. The
fleet token identifies "some node" and never "which node", so mdma could
not tell one node's claims from another's. Two things rested on that:
`should_join` compared a REPORTED mrtd against a customer's
`allowed_measurements` — a hex string the caller types, so that allowlist
was satisfiable by assertion rather than by measurement — and relay
identity was overwritable by claiming a peer id, which buys denial of
service and disclosure of intent contents, since `relay_url` is where
clients are routed.

`GET /nodes/challenge` then `POST /nodes/register` closes both. The mdma
half merged as calimero-network/mdma#233; this is the producer it needs.

Nothing here signs anything, and no core change was needed. The executor
account is minted inside the TEE and never leaves it, so an allowlisted
image saying what its account is is exactly what attestation is for. The
binding rides the quote's report data: this computes
`SHA256(challenge | peer_id | executor_account | relay_url)`, asks merod's
`POST /admin-api/tee/attest` for a quote over that nonce, and mdma
recomputes the same hash from the request body. So a quote commits to one
identity and one challenge — a genuine quote from one node cannot be
presented alongside another's claim, and neither replays.

`/admin-api/tee/attest` is mounted unauthenticated, which is why this
reaches it over loopback with no token. No `application_id` is sent:
`build_report_data(nonce, None)` leaves report data's second half as
zeros and the whole binding is the nonce, so sending one would change what
mdma must expect for nothing.

Four things that fail quietly, so each is a test rather than a comment:

- **The hash is a contract computed twice.** Field order and separator are
  part of it; get either wrong and every registration is refused as bound
  to a different identity — indistinguishable from a bad attestation.
- **Only an accepted registration is recorded**, so a refusal or an outage
  retries rather than being remembered as done.
- **503 is not 403.** mdma answers 503 when the quote could not be
  EVALUATED — collateral unreachable, verifier missing — which is not a
  verdict about this node. Treating it as a refusal would leave a healthy
  fleet unregistered through someone else's outage.
- **Re-register on change, not on a timer.** A registration does not
  expire, and the identity is fixed unless the node re-keys or moves
  behind a different ingress.

Until it succeeds the node replicates but does not relay — the posture
every other failure on this path already takes.

Verified: the new harness test drives all seven behaviours against a
stubbed `curl` covering both hops, including asserting the attested nonce
equals what mdma will recompute; the three existing sidecar tests still
pass; `bash -n` and `shellcheck` clean.

Refs calimero-network/mdma#225.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

---------

Co-authored-by: Claude <noreply@anthropic.com>
chefsale added a commit to calimero-network/mero-tee that referenced this pull request Sep 15, 2026
* feat(sidecar): write the account recovery envelopes nothing ever wrote

`PUT /api/fleet/recovery-envelope` has had no client since it shipped
(calimero-network/mdma#223). Holding an account root recovers the ACCOUNT
but not the LIST of namespaces it belongs to — those are separate
keypairs, and the local governance state left with the lost device. With
no envelope ever written, those namespaces keep existing on other
people's nodes and are permanently unaddressable, and nothing about the
gap is observable as a failure: the read is correctly defined to return
"nothing yet" rather than an error, so the recovery path simply always
shows nothing to recover.

The blocker was that sealing had no surface. That prerequisite was
decided as option (2) on the issue and shipped in
calimero-network/core#3918, so the sidecar can now seal via `meroctl
account seal-to`. The recipient is the account ROOT, never a device key:
a device is exactly what is gone in the case worth sealing for, so an
envelope addressed to one is valid, opaque, and unopenable precisely when
it is needed.

One envelope per (account, namespace) this node serves, never one per
account. A relay only sees the namespaces it serves, so a per-account
envelope is a partial list overwriting other relays' partial lists —
mdma#228, fixed by keying mdma's table per namespace.

Four things here fail silently, so each is a test rather than a comment:

- **Only linked accounts are written for.** `member_count` reports counts
  and never rosters, aggregating inside the TEE so no account id crosses
  the wire; an envelope per member would undo that by naming every member
  of every served namespace, including people with no cloud account.
  `GET /api/fleet/linked-accounts` says which accounts hold a cloud
  login and the intersection happens here, against a roster that never
  leaves. That is also the predicate authorizing the READ, so an envelope
  for an unlinked account could never be fetched by anyone.
- **An incomplete walk writes nothing.** A shorter list overwriting a
  longer one at a higher version is the same data loss as a stale
  clobber, carrying a valid version number.
- **The version is durable**, in `fleet-recovery.json` rather than memory
  or wall-clock. mdma answers 409 to a version going backwards, so a
  counter resetting on restart strands the envelope permanently.
- **Change is detected on the PLAINTEXT.** Sealing mints a fresh
  ephemeral key per call, so identical data seals to different bytes.
  Comparing ciphertexts would report a change every pass, burn a version
  each time, and rewrite mdma forever for data that never moved.

mdma's `ciphertext` is one opaque string while a sealed envelope is four
values a recovering holder needs, so the envelope is JSON-encoded into
it. mdma stores it without looking, as it does everything else here.

Verified: the new harness test drives all eight behaviours against
stubbed `meroctl` and `curl` — including a seal stub that returns
different bytes every call, which is what makes the plaintext-hash
assertion meaningful — and the existing authorship and inventory tests
still pass. `bash -n` and `shellcheck` clean on the rendered template.

Refs calimero-network/mdma#223.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* chore: bump merod to 0.11.0-rc.34, release 2.3.55

The sidecar's recovery-envelope writer (#268, calimero-network/mdma#223)
shells out to `meroctl account seal-to`, which arrived in
calimero-network/core#3918 and ships for the first time in 0.11.0-rc.34.
The pin was still rc.33, so the image as it stood ran a writer whose one
external call did not exist.

`imageVersion` moves with it, as in #207 (rc.8 to rc.13): merod is
measured into the image, so a new binary is a new MRTD and needs its own
image version rather than silently changing what 2.3.54 means.

Also asserts the dependency instead of relying on the pin being right,
which is the half #268 should have carried and did not. The delegated-
execution block already does this for `--public-intents`, for the same
reason and with the same failure shape: pin a core release that predates
the feature and the image still builds, still boots, and the sidecar still
runs — `seal_for_account` just fails every pass, logs a WARN, and writes
nothing.

That direction is the safe one, and that is precisely what makes it worth
asserting. Nothing breaks, no envelope is ever written, and the gap
surfaces when someone loses their last device and finds there is nothing
to recover from — the one moment no retroactive fix exists. A build-time
failure with a message naming the release to bump to is a much cheaper
place to learn it.

`meroctl account --help` is the part a CLI answers without a node, a
network or a config file, so it probes the baked binary the same way
`--public-intents` probes merod. Gated on `merod_mode == "read-only"`,
which is what gates the sidecar itself.

Verified: the three sidecar harness tests still pass (recovery, inventory,
authorship) and the task file parses as YAML.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* fix(release): sync the KMS version and record the bump in the changelog

Two CI guards caught what the bump commit missed, both correctly.

`release-version-sync-guard` requires `mero-kms/Cargo.toml`,
`Cargo.lock`'s `mero-kms-phala` entry and `versions.json`'s `imageVersion`
to move together. Bumping the image to 2.3.55 without the KMS left
`kms=2.3.54, merod=2.3.55` — and the guard exists because these two ship
as one artifact pair, so a version skew between them is a real thing to
refuse rather than a bookkeeping nit.

`docs-update-guard` requires any change under `mero-tee/**` to carry a
matching `docs/**`, `README.md` or `CHANGELOG.md` change. The previous
mero-tee PR (#268) satisfied it incidentally by updating
`fleet-sidecar.mdx`; a version bump touches no page, so it needs the
changelog entry it should have had anyway.

Both were run locally before this push rather than resubmitted to CI:
`scripts/policy/check_release_version_sync.sh` now reports
`OK: kms=2.3.55, merod=2.3.55`, and the changelog entry puts CHANGELOG.md
in the PR's diff, which is what the docs guard reads.

The three sidecar harness tests still pass.

Refs calimero-network/mdma#223, calimero-network/core#3921.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* feat(sidecar): register with mdma, binding what it reports to a quote

Everything this sidecar told mdma about itself was taken on trust. The
fleet token identifies "some node" and never "which node", so mdma could
not tell one node's claims from another's. Two things rested on that:
`should_join` compared a REPORTED mrtd against a customer's
`allowed_measurements` — a hex string the caller types, so that allowlist
was satisfiable by assertion rather than by measurement — and relay
identity was overwritable by claiming a peer id, which buys denial of
service and disclosure of intent contents, since `relay_url` is where
clients are routed.

`GET /nodes/challenge` then `POST /nodes/register` closes both. The mdma
half merged as calimero-network/mdma#233; this is the producer it needs.

Nothing here signs anything, and no core change was needed. The executor
account is minted inside the TEE and never leaves it, so an allowlisted
image saying what its account is is exactly what attestation is for. The
binding rides the quote's report data: this computes
`SHA256(challenge | peer_id | executor_account | relay_url)`, asks merod's
`POST /admin-api/tee/attest` for a quote over that nonce, and mdma
recomputes the same hash from the request body. So a quote commits to one
identity and one challenge — a genuine quote from one node cannot be
presented alongside another's claim, and neither replays.

`/admin-api/tee/attest` is mounted unauthenticated, which is why this
reaches it over loopback with no token. No `application_id` is sent:
`build_report_data(nonce, None)` leaves report data's second half as
zeros and the whole binding is the nonce, so sending one would change what
mdma must expect for nothing.

Four things that fail quietly, so each is a test rather than a comment:

- **The hash is a contract computed twice.** Field order and separator are
  part of it; get either wrong and every registration is refused as bound
  to a different identity — indistinguishable from a bad attestation.
- **Only an accepted registration is recorded**, so a refusal or an outage
  retries rather than being remembered as done.
- **503 is not 403.** mdma answers 503 when the quote could not be
  EVALUATED — collateral unreachable, verifier missing — which is not a
  verdict about this node. Treating it as a refusal would leave a healthy
  fleet unregistered through someone else's outage.
- **Re-register on change, not on a timer.** A registration does not
  expire, and the identity is fixed unless the node re-keys or moves
  behind a different ingress.

Until it succeeds the node replicates but does not relay — the posture
every other failure on this path already takes.

Verified: the new harness test drives all seven behaviours against a
stubbed `curl` covering both hops, including asserting the attested nonce
equals what mdma will recompute; the three existing sidecar tests still
pass; `bash -n` and `shellcheck` clean.

Refs calimero-network/mdma#225.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

* docs: stop restating merodVersion where it can go stale

The README asserted the baked merod version as a current fact
("currently 0.11.0-rc.33") beside the pointer to versions.json that
already holds it. It was two releases behind by the time anyone noticed,
and would go stale again on the next bump.

Point at versions.json alone. The claim the sentence actually makes --
that the baked merod satisfies core >= 0.11.0-rc.6 for the key-deletion
half of the purge -- is unaffected and still stated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lnezo7uTBiJYPsoYPvRiW3

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants