Skip to content

feat(hosting): per-deployment announcement key — record field + per-sender inbox key (Plugins#1913) - #5775

Merged
meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/per-deployment-announce-key
Sep 26, 2026
Merged

meshweaver-cloud[bot] merged 2 commits into
mainfrom
feat/per-deployment-announce-key

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

What

The core half of the per-deployment announcement key (Systemorph/MeshWeaver.Plugins#1913), so that client-b, which is customer-administered, can hand its self-update to the control instance without being given the fleet-wide inbox secret. Refs #5757.

  • DeploymentContent.AnnouncementKeySecret: the Hosting/Deployment record names its own Key Vault object. It holds a name only, never a value.
  • WebhookInbox.Deliver / WebhookInbox.SenderKeyOf: if the target's shared secret does not verify a delivery, a child of the target's SecretConfigKey section can verify it as a per-sender key. On the control instance that child is Hosting:PlatformWebhookSecret:<deployment>, the same shape Hosting:ModuleReportSecret:<deployment> already uses. The delivery is stored, and DeliveryResult.SenderKey (an init-only property, so the positional signature is unchanged) records which child verified. The inbox only decides whether to store the bytes. What a sender key is allowed to cause is decided by the consumer in MeshWeaver.Plugins (companion PR, which follows once this is sealed).
  • The sender needs no code change: the deployment's pod mounts its own key as Hosting__ControlInbox__Secret, and SelfUpdateHandover already signs with that.
  • Docs: new Doc/Architecture/SelfUpdateAnnouncementKey (design, the four consumer rules, rollout order, the owner command) and updated SelfUpdateControlLane sections.

Verification

  • dotnet build -c Release -warnaserror: MeshWeaver.Graph.Test (which builds MeshWeaver.Graph), Memex.Portal.Shared and MeshWeaver.Deployment.Contract, each with 0 warnings and 0 errors.
  • WebhookInboxTest: 14/14 passed, 3 of them new:
    • an own-key delivery is accepted and names its sender;
    • a sender key admits nothing that verifies with no key (drifted, empty-string, re-bodied);
    • the pure SenderKeyOf selector.

The existing SignedTarget_WithADriftedSecret_IsRefused_AndStoresNothing still holds.

Cross-repo

Pairs-with: none — nothing public is removed; only an init property and a static method are added.
Implementers: none — no interface member is added.
Mirror-sync: none — no i18n key is added or changed.

🤖 Generated with Claude Code

…ender inbox key (Plugins#1913)

A deployment that must not hold the fleet-wide inbox secret (pearl, customer-administered) needs a
key of its own to announce its self-update. Core half:

- DeploymentContent.AnnouncementKeySecret: the record names its own vault object (NAME only).
- WebhookInbox.Deliver: when the target's shared secret does not verify, a child of the target's
  SecretConfigKey section (Hosting:PlatformWebhookSecret:<deployment>, the ModuleReportSecret shape)
  may verify as a PER-SENDER key; the delivery is stored and DeliveryResult.SenderKey names it. The
  inbox only stores — what the key may cause is the consumer's decision (MeshWeaver.Plugins).
- No sender change: the pod mounts its own key as Hosting__ControlInbox__Secret.
- Doc/Architecture/SelfUpdateAnnouncementKey: design, consumer rules, rollout order, owner command.

Refs #5757. Refs Systemorph/MeshWeaver.Plugins#1913.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 26, 2026 19:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved contract parity/API gaps, local signing alignment, and ambiguous sender-key attribution block approval.

Review effort: Lite
Findings: 2 High severity

Open (2)
What changed in this PR

Adds per-deployment announcement-key support for webhook authentication, sender attribution, tests, and architecture documentation.

Changes:

  • Adds AnnouncementKeySecret to deployment records.
  • Adds per-sender HMAC verification and attribution.
  • Updates self-update documentation, logging, and test coverage.
File Summary
test/​MeshWeaver.Graph.Test/​WebhookInboxTest.cs Adds sender-key verification and refusal coverage.
src/​MeshWeaver.Graph/​Configuration/​WebhookEventNodeType.cs Implements sender-key verification; ambiguous matches and ordering behavior require changes.
src/​MeshWeaver.Documentation/​Data/​Architecture/​SelfUpdateControlLane.md Updates control-lane rollout documentation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​SelfUpdateAnnouncementKey.md Documents the design and rollout; local signing behavior needs alignment.
src/​MeshWeaver.Documentation/​Data/​Architecture.md Adds the architecture documentation index entry.
src/​MeshWeaver.Deployment.Contract/​DeploymentContent.cs Adds the announcement-key field; parity coverage, fluent configuration, and German translation need updates.
memex/​Memex.Portal.Shared/​SelfUpdate/​SelfUpdateHandover.cs Updates self-update key documentation.
memex/​Memex.Portal.Shared/​Api/​WebhookInboxEndpoints.cs Logs verified sender-key names.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

/// fleet-secret path this record used before (Doc/Architecture/SelfUpdateAnnouncementKey).</para>
/// </summary>
[Description("Key Vault secret NAME of this deployment's own self-update announcement key")]
public string? AnnouncementKeySecret { get; init; }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 51d7be5: added DeploymentRecordExtensions.WithAnnouncementKeySecret(vaultSecret) and a row for it in the ConfiguringAnInstanceFromAspire parity table. The field is now reached by a transform, so the operator-written exemption list does not grow. MeshWeaver.Deployment.Contract.Test passes locally, 50/50, including RendererParityTest.

Comment on lines +228 to +235
foreach (var child in configuration.GetSection(secretConfigKey).GetChildren())
{
if (string.IsNullOrWhiteSpace(child.Value))
continue;
if (VerifyHmacSha256(signatureHeader, body, child.Value))
return child.Key;
}
return null;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, fixed in 51d7be5. SenderKeyOf now returns null when more than one child verifies, so two senders provisioned with the same value name nobody and the delivery is refused. It is no longer attributed to whichever child is listed first. This is pinned in SenderKeyOf_NamesTheChildThatVerifies, and the doc states the rule. The consumer side (Plugins AnnouncementKeys.Verify) selects the key by the deployment the event names, so the owner check does not depend on enumeration order.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  1 files  ±0    1 suites  ±0   3m 24s ⏱️ +12s
348 tests ±0  348 ✅ ±0  0 💤 ±0  0 ❌ ±0 
352 runs  ±0  352 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

460 tests  ±0   460 ✅ ±0   56s ⏱️ +7s
  3 suites ±0     0 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 690 tests  ±0   1 690 ✅ ±0   4m 7s ⏱️ -6s
    2 suites ±0       0 💤 ±0 
    2 files   ±0       0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

♻️ This comment has been updated with latest results.

…ets its fluent transform + parity row

- SenderKeyOf returns null when more than one child verifies: the answer is an identity, so two
  senders provisioned with one value are refused rather than attributed to the first listed.
- DeploymentRecordExtensions.WithAnnouncementKeySecret + its ConfiguringAnInstanceFromAspire parity
  row, so RendererParityTest reaches the new field (Deployment.Contract.Test 50/50).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    3 files  ±0      3 suites  ±0   6m 22s ⏱️ -1s
2 156 tests +3  2 156 ✅ +3  0 💤 ±0  0 ❌ ±0 
2 157 runs  +3  2 157 ✅ +3  0 💤 ±0  0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

759 tests  ±0   567 ✅ ±0   7m 16s ⏱️ -1s
  3 suites ±0   192 💤 ±0 
  3 files   ±0     0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files  ±0      5 suites  ±0   14m 32s ⏱️ -14s
4 245 tests +4  4 243 ✅ +4  2 💤 ±0  0 ❌ ±0 
4 249 runs  +4  4 247 ✅ +4  2 💤 ±0  0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   17 files  ±0     17 suites  ±0   36m 39s ⏱️ -1s
9 658 tests +7  9 464 ✅ +7  194 💤 ±0  0 ❌ ±0 
9 667 runs  +7  9 473 ✅ +7  194 💤 ±0  0 ❌ ±0 

Results for commit 51d7be5. ± Comparison against base commit f69cac2.

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