Skip to content

feat(context): introduce GroupRequest and ContextGroupId types for group management#2043

Open
rtb-12 wants to merge 127 commits intomasterfrom
feat/context-management-proposal
Open

feat(context): introduce GroupRequest and ContextGroupId types for group management#2043
rtb-12 wants to merge 127 commits intomasterfrom
feat/context-management-proposal

Conversation

@rtb-12
Copy link
Contributor

@rtb-12 rtb-12 commented Feb 19, 2026

[core] Implement hierarchical context group management

Description

Implements the full Context Groups feature across core/crates/, enabling administrators to organize contexts into groups with shared application targets, coordinated upgrades, and membership management. Built across 6 phases on top of the context-config contract types (ContextGroupId, GroupRequest, AppKey).

Phase 1 — Storage Foundation

  • 5 new key structs in store/src/key/group.rs: GroupMeta (0x20), GroupMember (0x21), GroupContextIndex (0x22), ContextGroupRef (0x23), GroupUpgradeKey (0x24) — all with AsKeyParts/FromKeyParts impls and roundtrip tests
  • Value types: GroupMetaValue, GroupUpgradeValue, GroupUpgradeStatus with Borsh serialization
  • Primitive types in primitives/src/context.rs: UpgradePolicy (Automatic / LazyOnAccess / Coordinated), GroupMemberRole (Admin / Member), GroupInvitationPayload

Phase 2 — Group CRUD + Membership

  • group_store.rs (~390 LOC): Complete storage helper layer — CRUD for group metadata, members, context indices, and upgrade records with efficient key-only iteration for counting
  • 6 actor handlers: create_group, delete_group, add_group_members, remove_group_members, get_group_info, list_group_members
  • 6 admin API routes with corresponding server handlers

Phase 3 — Context-Group Integration

  • create_context.rs: Pre-validates group membership + app override; post-creation registers context in group index
  • delete_context.rs: Unregisters context from group on deletion
  • list_group_contexts: New paginated endpoint for listing a group's contexts

Phase 4 — Upgrade Propagation

  • Canary-first upgrade strategy: Upgrades the first context as a canary; on success, spawns an async propagator for the remaining contexts
  • propagate_upgrade(): Async fn that iterates group contexts, calls update_application() per context, persists progress after each step
  • Status tracking: GroupUpgradeStatus::InProgress { total, completed, failed }Completed { completed_at }
  • 3 API endpoints: POST /upgrade, GET /upgrade/status, POST /upgrade/retry

Phase 5 — Advanced Policies + Crash Recovery

  • Lazy-on-access upgrade: execute.rs transparently upgrades stale contexts before method execution when UpgradePolicy::LazyOnAccess is set
  • Crash recovery: ContextManager::started() scans for InProgress upgrades and re-spawns propagators
  • Auto-retry: Failed context upgrades are retried up to 3× with exponential backoff (5s, 10s, 20s)

Phase 6 — Group Invitations + Join Flow

  • GroupInvitationPayload: Borsh-serialized + base58-encoded invitation token (mirrors ContextInvitationPayload pattern)
  • Targeted (invitee_identity: Some) and open (invitee_identity: None) invitation modes
  • Join validation: Verifies inviter is still admin, checks invitee identity match, validates expiration
  • 2 API endpoints: POST /groups/:id/invite, POST /groups/join

Post-implementation fixes (PR review)

  • Phase 1: Fixed off-by-one in propagate_upgrade completed counter on retry/recovery paths
  • Phase 2: Fixed swallowed store errors, stale GroupContextIndex on re-registration, orphaned upgrade records on group deletion, Duration::new panic on malformed Borsh, switched to ValidatedJson for upgrade input
  • Phase 3: Rewrote count_group_admins/count_group_contexts without Vec allocation, added offset/limit to enumerate_group_contexts, batched member cleanup in delete_group
  • Phase 4: Decoupled GroupUpgradeValue/GroupUpgradeStatus store types from calimero-context-primitives API boundary — introduced GroupUpgradeInfo and primitives-local GroupUpgradeStatus with From conversion impls
  • Phase 5: Added count_group_members() for efficient member counting, inlined value read in enumerate_in_progress_upgrades to reuse store handle

New API routes

POST   /admin-api/groups                              → CreateGroup
GET    /admin-api/groups/:group_id                    → GetGroupInfo
DELETE /admin-api/groups/:group_id                    → DeleteGroup
POST   /admin-api/groups/:group_id/members            → AddGroupMembers
POST   /admin-api/groups/:group_id/members/remove     → RemoveGroupMembers
GET    /admin-api/groups/:group_id/members            → ListGroupMembers
GET    /admin-api/groups/:group_id/contexts           → ListGroupContexts
POST   /admin-api/groups/:group_id/upgrade            → UpgradeGroup
GET    /admin-api/groups/:group_id/upgrade/status     → GetGroupUpgradeStatus
POST   /admin-api/groups/:group_id/upgrade/retry      → RetryGroupUpgrade
POST   /admin-api/groups/:group_id/invite             → CreateGroupInvitation
POST   /admin-api/groups/join                         → JoinGroup

Test plan

  • cargo check --workspace — compiles cleanly
  • cargo fmt --check — no formatting issues
  • cargo clippy -- -A warnings — no errors
  • cargo test -p calimero-context — 16 tests pass
  • cargo test -p calimero-context-primitives — 3 tests pass
  • cargo test -p calimero-store — key roundtrip + value serialization tests pass
  • End-to-end group lifecycle tests via meroctl against a running merod node
  • Contract integration tests in contracts/ repo

Documentation update

N/A — these are internal protocol types. Public API documentation should be added once the full group management feature is shipped end-to-end.


Note

Medium Risk
Adds a large set of new group-management request/response types and client APIs, plus changes to node identity/config serialization; mistakes could break group operations or config compatibility and affect auth-protected admin endpoints.

Overview
Introduces context group management primitives in calimero-context-config by adding ContextGroupId/AppKey, GroupRequest/GroupRequestKind, group invitation payload types, and new group-related query request/response structs.

Extends the context-config client to build and send group mutate/query operations (and removes UB transmute decodes in favor of safe Repr unwrapping), and adds an on-chain ExternalGroupClient with nonce-retry handling for group contract mutations.

Expands the HTTP client/admin API surface with a new group module exposing admin-api/groups/* methods, adds PATCH/PUT/DELETE-with-body support and better error extraction in ConnectionInfo, and updates delete_context to accept an optional requester body.

Updates node configuration identity to a structured [identity] section that can include an optional group signing identity (GroupIdentityConfig), adds a small auth helper to fetch a stored public key by verified key_id, and updates docs/deps/tests (adds wiremock tests for the new client methods, and updates README.mdx link).

Written by Cursor Bugbot for commit b73d448. This will update automatically on new commits. Configure here.

Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 167.3s

💡 1 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-3e67acf7

Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 227.1s

💡 3 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-078e5b50

Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 203.4s

💡 2 suggestions, 📝 1 nitpicks. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-86d4a70f

Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 197.8s

🟡 2 warnings, 💡 2 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-04819256

…etaValue, GroupUpgradeValue, and GroupUpgradeStatus structures for enhanced group upgrade management
Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 89% | Review time: 319.2s

🟡 2 warnings, 💡 4 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-f10665bd

rtb-12 and others added 3 commits March 17, 2026 19:10
Each test starts a real TCP mock server, asserts the client sends the
correct HTTP verb + path, and round-trips the response body through serde.
Auth is bypassed by setting node_name=None in ConnectionInfo.
Also resolves PR review thread PRRT_kwDOLIG5Is50218T (comment 2946573387).

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…dtrips

Replace fragile borsh serialize/deserialize roundtrips for PublicKey↔SignerId
and ContextGroupId conversions with direct From<[u8; 32]> and to_bytes()
accessors now that SignerId exposes them.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…assertions

Replace scattered unsafe pointer casts from `[SignerId]` to `[Repr<SignerId>]`
with a safe `Repr::slice_from_inner` helper that uses inline `const` assertions
to enforce the `#[repr(transparent)]` layout invariant at compile time per
monomorphization, turning a silent UB risk into a compile error.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 93% | Review time: 332.1s

🟡 1 warnings, 💡 4 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-b85d5844

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Organizes the 27 group management methods into a dedicated sub-module
(`src/client/group.rs`) per the module structure convention, keeping
`client.rs` focused on core context/application API methods.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 541.4s

💡 4 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-2fcad253

rtb-12 and others added 2 commits March 17, 2026 21:26
store_group_signing_key was called unconditionally with the requester's
public key but the node's private key, producing an invalid
(requester_pk → node_sk) mapping whenever requester != node_pk.
In create_group_invitation this caused signature verification to fail on
the joiner side (inviter_identity=requester_pk signed with node_sk).

Add a requester == node_pk equality guard before the store call in both
create_group_invitation and upgrade_group so the auto-store only fires
when the node IS the requester.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
- Add security note to get_key_public_key clarifying key_id must come
  from a verified JWT token, not untrusted caller input
- Replace unreachable!() with descriptive unimplemented!() messages in
  NoopAuth/NoopStorage test stubs to aid debugging if called unexpectedly
- Replace slice_from_inner unsafe cast in manage_context_allowlist with
  safe iterator-based Repr::new() mapping to avoid reliance on undocumented
  repr layout guarantees

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Copy link

@meroreviewer meroreviewer bot left a comment

Choose a reason for hiding this comment

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

🤖 AI Code Reviewer

Reviewed by 3 agents | Quality score: 100% | Review time: 370.3s

🟡 1 warnings, 💡 2 suggestions. See inline comments.


🤖 Generated by AI Code Reviewer | Review ID: review-4661abb1

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

- Introduced a new document detailing the Context Group Management feature, which allows users to organize multiple contexts, manage shared memberships, and propagate application upgrades.
- Updated the README to include a link to the new documentation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant