Repository navigation
feat(hosting): per-deployment announcement key — record field + per-sender inbox key (Plugins#1913) - #5775
Conversation
…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>
There was a problem hiding this comment.
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
Open (2)
What changed in this PR
Adds per-deployment announcement-key support for webhook authentication, sender attribution, tests, and architecture documentation.
Changes:
- Adds
AnnouncementKeySecretto 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; } |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
…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>

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: theHosting/Deploymentrecord 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'sSecretConfigKeysection can verify it as a per-sender key. On the control instance that child isHosting:PlatformWebhookSecret:<deployment>, the same shapeHosting:ModuleReportSecret:<deployment>already uses. The delivery is stored, andDeliveryResult.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).Hosting__ControlInbox__Secret, andSelfUpdateHandoveralready signs with that.Doc/Architecture/SelfUpdateAnnouncementKey(design, the four consumer rules, rollout order, the owner command) and updatedSelfUpdateControlLanesections.Verification
dotnet build -c Release -warnaserror:MeshWeaver.Graph.Test(which buildsMeshWeaver.Graph),Memex.Portal.SharedandMeshWeaver.Deployment.Contract, each with 0 warnings and 0 errors.WebhookInboxTest: 14/14 passed, 3 of them new:SenderKeyOfselector.The existing
SignedTarget_WithADriftedSecret_IsRefused_AndStoresNothingstill 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