Skip to content

rbac: RBAC hot-plug socket (contract::rbac_plug, additive) - #1404

Merged
AdaWorldAPI merged 2 commits into
mainfrom
ccr-0455e606-wmtsor
Oct 8, 2026
Merged

AdaWorldAPI merged 2 commits into
mainfrom
ccr-0455e606-wmtsor

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

This applies the hotplug.rs pattern to authorization. A consumer declares one const naming the classes it gates and the roles it uses. OGAR, as the authority, binds that const to its grant data. The consumer then pairs the binding with its own source of actors and gets a ClassRbac that the existing kernels accept. This removes the hand-copied policy tables, such as MedCare-rs's medcare-rbac, and the role masks callers supply themselves, such as a2ui-rs's.

Changes

New module lance-graph-contract::rbac_plug. All of it is additive and nothing existing changes.

  • RbacPlug { consumer, classids, roles }: the consumer's one const. Roles are RoleId names.
  • RbacAuthority::bind(&RbacPlug) -> Result<RbacBinding, RbacDrift>: implemented by the authority. The OGAR implementation in ogar-rbac is the paired next PR.
  • RbacBinding: private fields, no Default, and every lookup (plugged, grants_for, permits, field_mask_for) returns a Result. Grants and field masks on classes outside the plug are dropped when the binding is built, so it can't carry access the plug didn't ask for. declared_grants is the audit surface.
  • RbacDrift: named refusals: UnknownClassid, UnknownRole, NotPlugged, RoleNotPlugged and MirrorDrift. verify_concepts_against_mirror is the RBAC twin of hotplug::verify_against_mirror.
  • ActorSource + PluggedRbac: the ClassRbac view. Roles and memberships come from the actor source, grants and masks from the binding. A question outside the plug is a denial, and it gets an empty field mask. Scopes come from memberships, so decide with authorize_memberships.

Tests and disable runs

  • Results: 6 contract tests pass, plus one kernel test (plug_tests) that runs a bound binding through authorize_memberships with a physician in two practices. clippy -D warnings and fmt are clean.
  • Disable runs: each of these failed the test aimed at it:
    • the binding keeping out-of-plug grants;
    • plugged accepting any class;
    • the view allowing on drift;
    • an out-of-plug field mask returned full;
    • the view dropping memberships.

Board

entries/2026-10-08-rbac-hotplug-socket.md (index regenerated) and the LATEST_STATE delta.

Next

  • OGAR: impl RbacAuthority for OgarRbac<S>, as a paired PR. It builds against lance-graph main, so its CI stays red until this PR merges.
  • Consumers: the MedCare-rs and a2ui-rs plugs come after both merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg


Generated by Claude Code

claude added 2 commits October 8, 2026 05:49
The hotplug.rs pattern applied to authorization. contract::rbac_plug:
RbacPlug (one const per consumer: classids + role names), RbacAuthority
(implemented by OGAR), RbacBinding (private fields, no Default, Result
lookups, grants and masks bounded to the plug), RbacDrift (named
refusals), ActorSource + PluggedRbac (the ClassRbac view; out-of-plug
questions deny), and verify_concepts_against_mirror.

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

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 7316ac06-b8a5-41b4-b541-7c7504b598d9
📥 Commits

Reviewing files that changed from the base of the PR and between 83e6fe4 and d4855a3.

📒 Files selected for processing (6)
  • .claude/board/LATEST_STATE.md
  • .claude/board/entries/2026-10-08-rbac-hotplug-socket.md
  • .claude/board/entries/README.md
  • crates/lance-graph-contract/src/lib.rs
  • crates/lance-graph-contract/src/rbac_plug.rs
  • crates/lance-graph-rbac/src/authorize.rs
 __________________________________
< I void warranties and segfaults. >
 ----------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Comment @coderabbitai help to get the list of available commands.

AdaWorldAPI pushed a commit to AdaWorldAPI/OGAR that referenced this pull request Oct 8, 2026
impl RbacAuthority for OgarRbac<S>: binds a consumer's RbacPlug to the
source's grants. Plugged classids must be minted in ogar-vocab and agree
with the contract mirror; plugged roles must be defined by the source.
GrantSource gains defaulted defines_role and field_mask_of.

Pairs with AdaWorldAPI/lance-graph#1404 (contract::rbac_plug).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 8, 2026 06:12
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-08T06:15:07.607322Z d4855a3 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@AdaWorldAPI
AdaWorldAPI merged commit bcc9f50 into main Oct 8, 2026
11 of 12 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d4855a3873

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.map(|(role, gs)| {
let kept = gs
.into_iter()
.filter(|g| classids.contains(&g.target_classid))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Filter bindings to the plug's declared roles

When an authority supplies grant rows beyond plug.roles—for example, by loading its complete grant table—this constructor filters only by class ID and retains every role. If the actor source reports one of those undeclared roles, grants_for finds it and PluggedRbac::grant_permits authorizes access even though the consumer omitted that role from its plug. Pass the declared role set into construction and reject or discard grants and masks for roles outside it.

Useful? React with 👍 / 👎.

fn field_mask(&self, role: RoleId, class: ClassId) -> WideFieldMask {
match self.binding.field_mask_for(role, class) {
Ok(Some(mask)) => mask.clone(),
Ok(None) => WideFieldMask::from(FieldMask::FULL),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve unrestricted fields beyond position 63

For a class with more than 64 fields, omitting a field mask is documented as unrestricted, but this fallback sets only the low 64 bits; the existing WideFieldMask::full_for documentation explicitly requires the field count to represent all fields in a wide class. Consequently, an unrestricted grant on classes such as the 109-field motivating case silently loses every field at position 64 or above in authorize_memberships and authorize_scoped. The binding must carry enough schema width to construct a full mask, or require an explicit mask for wide classes.

Useful? React with 👍 / 👎.

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