Skip to content

ActionHandler⟷RBAC⟷orchestration spine: §4 trait + authorize_scoped + commit_via + OgarRbac (F1–F4)#601

Merged
AdaWorldAPI merged 2 commits into
mainfrom
claude/medcare-bridge-lance-graph-wmx76z
Jun 23, 2026
Merged

ActionHandler⟷RBAC⟷orchestration spine: §4 trait + authorize_scoped + commit_via + OgarRbac (F1–F4)#601
AdaWorldAPI merged 2 commits into
mainfrom
claude/medcare-bridge-lance-graph-wmx76z

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Completes the ActionHandler ⟷ RBAC ⟷ orchestration chain on the lance-graph side. Built from .claude/plans/integration-actionhandler-rbac-orchestration-v1.md5+3-hardened (5 research savants + 3 brutally-honest reviewers reviewed the plan; every P0 they caught is fixed) then implemented one Sonnet agent per file (commented drafts → central uncomment + reconcile + compile).

What's here (F1–F4)

  • F1 contract::rbac — the §4 ClassRbac surface: roles_reaching / row_scope / field_mask as default methods (so ClassGrants + the green PROBE-OGAR-RBAC-AUTHORIZE compile/pass unchanged) + ScopeSpec (axis-3 Copy token) with a restrictive intersect. Reuses the existing class_view::FieldMask (added FieldMask::union for the projection-fold). roles_reaching is a default-empty CONJECTURE hook (axis-2 role-hierarchy not implemented this PR).
  • F2 lance-graph-rbac::authorize_scoped + ScopedDecision — the §5 two-stage: stage-1 reuses authorize() unchanged; stage-2 folds the granting subset (actor_roles ∧ grant_permits, not roles_reaching) into a restrictive-AND row-scope + union field-mask. AccessDecision is frozen (3 variants incl Escalate).
  • F3 ActionInvocation::commit_via<R: ClassRbac> — the ClassRbac convergence of commit's inline ActorContext gate. commit untouched. Pinned: no is_admin bypass (admin must be a granted role), ActorId (&str) not ActorContext, None required_role ⇒ proceed (parity).
  • F4 lance-graph-ogar::OgarRbac<S: GrantSource> — keystone Q5 as a local newtype (impl ClassRbac for OgarClassView would be an orphan-rule E0117). Owns no grant data; reads from an injected GrantSource — the §6 project_role.granted evaporation seam (body unchanged when the fixture flips to the tenant resolver).

Companion

F5 run_cycle (the end-to-end spine) + F6 dispatch_via are in rs-graph-llm (depends on commit_via): AdaWorldAPI/rs-graph-llm PR.

Tests / gates

contract 735 · rbac 23 (probe green) · ogar rbac_impl 3 — all green; clippy -D warnings + fmt clean on the touched crates.

Deferred (OGAR repo, noted in the plan)

MARS class mint in ogar_vocab::class_ids + the gen_statem → ActionDef behavioral lift (the three ogar-from-elixir todo!()s). The spine is generic over the trait, so MARS plugs in unchanged.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added role-based access control integration with action authorization, enabling scoped access decisions with row-level and field-level visibility controls based on granted roles.
    • Enhanced authorization mechanisms to compute field projection masks and enforce row visibility constraints while removing implicit admin bypasses.
  • Documentation

    • Added integration plan documentation and status tracking for RBAC authorization orchestration.

The lance-graph half of the integration plan, 5+3-hardened then built
one-agent-per-file (commented drafts, central uncomment+reconcile+compile).

F1 contract::rbac — §4 ClassRbac default methods (roles_reaching/row_scope/
field_mask) + ScopeSpec (axis-3 Copy token) + ScopeSpec::intersect. All
DEFAULT so ClassGrants + PROBE-OGAR-RBAC-AUTHORIZE compile/pass unchanged.
Reuses contract::class_view::FieldMask (added FieldMask::union for the
projection-fold). roles_reaching is a default-empty CONJECTURE hook (axis-2
hierarchy not implemented this PR).

F2 lance-graph-rbac::authorize_scoped + ScopedDecision — the §5 two-stage:
stage-1 reuses authorize() unchanged; stage-2 folds the granting subset
(actor_roles AND grant_permits, NOT roles_reaching) into a restrictive-AND
row-scope + union field-mask. AccessDecision frozen (3 variants incl Escalate).

F3 contract::action::ActionInvocation::commit_via<R: ClassRbac> — the
ClassRbac convergence of commit's inline ActorContext gate. commit untouched.
PINNED: no is_admin bypass (admin must be a granted role); ActorId(&str) not
ActorContext; None required_role => proceed (parity).

F4 lance-graph-ogar::OgarRbac<S: GrantSource> — keystone Q5 as a LOCAL newtype
(impl ClassRbac for OgarClassView would be an orphan-rule E0117). Owns no grant
data; reads from an injected GrantSource — the §6 project_role.granted
evaporation seam (body unchanged when the fixture flips to the tenant resolver).

Tests: contract 735, rbac 23 (probe green), ogar rbac_impl 3. clippy+fmt clean.
Deferred (OGAR repo): MARS class mint + gen_statem->ActionDef lift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@AdaWorldAPI, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 56 minutes and 6 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6188ac81-c702-4066-9a83-3dbb5980f7ca

📥 Commits

Reviewing files that changed from the base of the PR and between 21053c4 and 33ee54a.

📒 Files selected for processing (4)
  • .claude/plans/integration-actionhandler-rbac-orchestration-v1.md
  • crates/lance-graph-contract/src/action.rs
  • crates/lance-graph-contract/src/rbac.rs
  • crates/lance-graph-rbac/src/authorize.rs
📝 Walkthrough

Walkthrough

Adds the ActionHandler ↔ RBAC ↔ Orchestration integration spine: extends ClassRbac with three new axis hooks (roles_reaching, row_scope, field_mask) plus ScopeSpec/FieldMask::union, introduces ScopedDecision/authorize_scoped in the RBAC crate, adds a no-bypass commit_via on ActionInvocation, and delivers OgarRbac<S: GrantSource> as a local newtype RBAC bridge for OGAR. An integration plan document records the multi-agent workflow and canonical signatures.

Changes

ActionHandler ↔ RBAC ↔ Orchestration Integration Spine

Layer / File(s) Summary
Contract RBAC primitives: ScopeSpec, FieldMask::union, ClassRbac axes
crates/lance-graph-contract/src/rbac.rs, crates/lance-graph-contract/src/class_view.rs
Introduces ScopeSpec (tenant + predicate_key with const intersect), FieldMask::union (bitwise-OR for projection accumulation), and three default-method axis hooks on ClassRbac (roles_reaching, row_scope, field_mask), with tests covering defaults, overrides, and ScopeSpec::intersect semantics.
Scoped authorization kernel: ScopedDecision and authorize_scoped
crates/lance-graph-rbac/src/authorize.rs
Adds ScopedDecision (wrapping AccessDecision, Option<ScopeSpec>, FieldMask) and authorize_scoped, which calls stage-1 authorize then folds row scopes (AND-intersection) and field masks (OR-union) across permitting roles, short-circuiting to FULL/None on any non-Allow.
RBAC-aware ActionInvocation::commit_via
crates/lance-graph-contract/src/action.rs
Adds commit_via on ActionInvocation, following the same adjudication order as commit but resolving authorization exclusively via ClassRbac::actor_roles + grant_permits with no is_admin bypass. Tests cover authorized commit with timestamp stamping, ungranted failure, required_role: None skip, and explicit grant required for admin actors.
OgarRbac newtype and GrantSource evaporation seam
crates/lance-graph-ogar/src/rbac_impl.rs, crates/lance-graph-ogar/src/lib.rs
Introduces OgarRbac<S: GrantSource> as a local newtype (avoids orphan-rule violations) with GrantSource as the injected §6 evaporation seam. ClassRbac is implemented by delegating to source.roles_of and grants_permit(source.grants_of(role)). Includes role resolution, permit logic, and an evaporation-proof generic test.
Integration plan and board state records
.claude/plans/integration-actionhandler-rbac-orchestration-v1.md, .claude/board/INTEGRATION_PLANS.md, .claude/board/LATEST_STATE.md
Adds the full integration plan document (workflow, non-negotiables, hardened v2 specs, canonical signatures, activation steps) and updates the board index and latest-state entries.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant authorize_scoped
  participant authorize
  participant ClassRbac

  rect rgba(70, 130, 180, 0.5)
    Note over Caller, ClassRbac: Stage 1 — Gate decision
    Caller->>authorize_scoped: actor, class, Operation::Act
    authorize_scoped->>authorize: gate check
    authorize-->>authorize_scoped: AccessDecision
  end

  rect rgba(180, 70, 70, 0.5)
    Note over authorize_scoped, ClassRbac: Stage 2 — Scope/mask fold (Allow only)
    alt non-Allow
      authorize_scoped-->>Caller: ScopedDecision{Deny, scope:None, mask:FULL}
    else Allow
      loop each actor role where grant_permits
        authorize_scoped->>ClassRbac: row_scope(role, class) → AND-intersect
        authorize_scoped->>ClassRbac: field_mask(role, class) → OR-union
      end
      authorize_scoped-->>Caller: ScopedDecision{Allow, scope, field_mask}
    end
  end
Loading
sequenceDiagram
  participant Executor
  participant ActionInvocation
  participant OgarRbac
  participant GrantSource

  Executor->>ActionInvocation: commit_via(def, rbac, actor_id, impact, guard, now_ms)
  ActionInvocation->>ActionInvocation: sticky terminal guard
  ActionInvocation->>ActionInvocation: def predicate/class match
  ActionInvocation->>OgarRbac: actor_roles(actor_id)
  OgarRbac->>GrantSource: roles_of(actor_id)
  GrantSource-->>OgarRbac: &[RoleId]
  OgarRbac-->>ActionInvocation: &[RoleId]
  ActionInvocation->>OgarRbac: grant_permits(role, class, Act{predicate})
  OgarRbac->>GrantSource: grants_of(role) → grants_permit
  OgarRbac-->>ActionInvocation: bool
  ActionInvocation->>ActionInvocation: optional guard field check
  ActionInvocation->>ActionInvocation: map GateDecision → ActionState
  ActionInvocation-->>Executor: Flow / Pending / Cancelled / Failed
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AdaWorldAPI/lance-graph#598: Introduces the ClassRbac/authorize groundwork in lance-graph-rbac::authorize that this PR directly extends with authorize_scoped and ScopedDecision.
  • AdaWorldAPI/lance-graph#600: Adds grants_permit, ClassGrant, and typed OpMask to the contract RBAC layer — the grant_permits gate used by both commit_via and OgarRbac delegates to that logic.
  • AdaWorldAPI/lance-graph#582: Adds FieldMask::FULL/intersect and PermissionSpec.projection at the same crates/lance-graph-contract/src/class_view.rs + RBAC FieldMask layer that this PR extends with FieldMask::union.

Poem

🐇 Hoppity-hop through the RBAC gate,
No admin bypass — you must authenticate!
ScopeSpec narrows, FieldMask grows wide,
OgarRbac newtype steps past orphan-rule pride.
commit_via stamps the flow, the kanban spins on —
The integration spine is wired, the rabbit hops along! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely captures the four main features (F1–F4) introduced in this PR: ClassRbac trait extensions, authorize_scoped authorization flow, commit_via method, and OgarRbac implementation, all forming the ActionHandler⟷RBAC⟷orchestration integration spine.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🧹 Nitpick comments (3)
.claude/board/INTEGRATION_PLANS.md (1)

1-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Status field should be "Active" for an in-PR plan, not "shipped".

Line 13 sets Status to "shipped", but the PR has not merged yet. Comparing to other in-flight plans in this index (e.g., lines 758: "Status:** Active (draft, pre-execution)", line 852: "Status:** Active"), the convention is to use "Active" for code that is ready but not yet merged. Once the PR merges and code is live, the status changes to "Shipped". The notation "(PLAN; shipped)" may have been intended as "plan document ready", but to avoid ambiguity with the standard Status vocabulary, recommend changing to "Active".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/board/INTEGRATION_PLANS.md around lines 1 - 14, The status field in
the integration plan header "integration-actionhandler-rbac-orchestration-v1" is
marked as "(PLAN; shipped)" but should reflect that this plan is still in-flight
since the PR has not yet merged. Change the status notation from "(PLAN;
shipped)" to "(PLAN; Active)" to align with the established convention in the
file where "Active" indicates code ready but not yet merged (as seen in other
in-flight plans), and "Shipped" is reserved for code that is live in production.
.claude/plans/integration-actionhandler-rbac-orchestration-v1.md (1)

245-250: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Activation steps should mention LATEST_STATE.md update.

Line 249 says "prepend INTEGRATION_PLANS.md + a PR_ARC entry in the SAME commit", but File 3 (.claude/board/LATEST_STATE.md) also receives a new entry at lines 13–16. For clarity, the activation steps should note that LATEST_STATE.md is updated in the same commit. This aligns with the append-only rule and the governance model described in INTEGRATION_PLANS.md § "How to use this file."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/plans/integration-actionhandler-rbac-orchestration-v1.md around
lines 245 - 250, The Activation section's board-hygiene step currently only
mentions prepending INTEGRATION_PLANS.md and a PR_ARC entry in the same commit,
but omits mention of LATEST_STATE.md which also receives a new entry. Add a
clarification to the board-hygiene step (line 249) to explicitly note that
LATEST_STATE.md is updated in the same commit, ensuring all three file updates
(INTEGRATION_PLANS.md, PR_ARC entry, and LATEST_STATE.md append) are documented
together to align with the governance model and append-only rule.
crates/lance-graph-contract/src/class_view.rs (1)

137-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add local coverage for the new mask primitive.

union is now a contract-level primitive; please add a small #[cfg(test)] case in this module for overlap/identity behavior instead of relying only on RBAC-side usage.

Example focused coverage
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn field_mask_union_or_combines_positions() {
+        let left = FieldMask::from_positions(&[0, 2]);
+        let right = FieldMask::from_positions(&[2, 3]);
+
+        let union = left.union(right);
+
+        assert!(union.has(0));
+        assert!(union.has(2));
+        assert!(union.has(3));
+        assert_eq!(union.count(), 3);
+        assert_eq!(union.union(FieldMask::EMPTY), union);
+    }
+}

As per coding guidelines, crates/**/*.rs: “Add Rust unit tests alongside implementations via #[cfg(test)] modules.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/lance-graph-contract/src/class_view.rs` around lines 137 - 144, The
union method on the mask primitive needs local unit test coverage to verify its
behavior. Add a #[cfg(test)] module at the end of the class_view.rs file that
tests the union method with multiple test cases covering overlap behavior (two
masks with overlapping bits should produce a mask containing all bits from both)
and identity behavior (union with an empty mask should return the original mask,
and union of identical masks should return that same mask).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/plans/integration-actionhandler-rbac-orchestration-v1.md:
- Around line 123-136: The hardening section (F1 and F2 in the plan document)
contradicts the canonical signatures defined later in the same document. The F1
section currently states to "Drop the ScopeSpec new type" but the canonical
signature at lines 196-197 explicitly defines ScopeSpec with fields tenant:
Option<u64> and predicate_key: u32, and the actual implementation uses it
throughout. Similarly, F2 currently states scope: Option<TenantId> but the
canonical signature at line 205 specifies scope: Option<ScopeSpec>. Correct the
hardening text to match the canonical signatures: update F1 to describe ADDING
ScopeSpec (not dropping it) with the exact structure { tenant: Option<u64>,
predicate_key: u32 }, and update F2 to specify scope: Option<ScopeSpec> instead
of Option<TenantId> in the ScopedDecision struct definition.

In `@crates/lance-graph-contract/src/action.rs`:
- Around line 347-356: The role authorization check in the block around line
347-356 only uses `def.required_role` as a boolean gate to decide whether to
perform a check, but does not validate the actor against the actual specific
role value contained in `def.required_role`. Instead of checking if any actor
role permits the Act operation, extract the actual required role from
`def.required_role` and verify that the actor's roles include that specific
required role, similar to how the `commit` function performs its specific-role
authorization check.

In `@crates/lance-graph-contract/src/rbac.rs`:
- Around line 50-67: The intersect method in the Scope struct currently has
incorrect logic for handling conflicting tenants. When both self and other have
different tenant values (Some(a) and Some(b) where a != b), the method returns
Some(a) instead of representing the conflict/empty intersection. Fix this by
checking if the two tenant values are equal when both are Some: if they match,
return that tenant; if they differ, return None to represent the empty/conflict
scope (since a restrictive AND with conflicting tenants should result in an
empty scope). Additionally, update the test case around lines 430-445 that
currently expects the "self wins" behavior to instead verify that conflicting
tenants are properly represented as an empty/conflict state.

In `@crates/lance-graph-rbac/src/authorize.rs`:
- Around line 166-168: The issue is that unwrap_or_default() is converting None
(global scope sentinel) into Some(ScopeSpec::default()), breaking the API
contract where scope == None should indicate unrestricted global access. In the
scope aggregation logic (around the fold operation mentioned), replace the
unwrap_or_default() call with conditional logic that only intersects concrete
scopes when both are Some, preserving None as the final result when all roles
have global scope. This ensures that allowed decisions with only global roles
correctly report scope == None as documented in the ScopedDecision API.

---

Nitpick comments:
In @.claude/board/INTEGRATION_PLANS.md:
- Around line 1-14: The status field in the integration plan header
"integration-actionhandler-rbac-orchestration-v1" is marked as "(PLAN; shipped)"
but should reflect that this plan is still in-flight since the PR has not yet
merged. Change the status notation from "(PLAN; shipped)" to "(PLAN; Active)" to
align with the established convention in the file where "Active" indicates code
ready but not yet merged (as seen in other in-flight plans), and "Shipped" is
reserved for code that is live in production.

In @.claude/plans/integration-actionhandler-rbac-orchestration-v1.md:
- Around line 245-250: The Activation section's board-hygiene step currently
only mentions prepending INTEGRATION_PLANS.md and a PR_ARC entry in the same
commit, but omits mention of LATEST_STATE.md which also receives a new entry.
Add a clarification to the board-hygiene step (line 249) to explicitly note that
LATEST_STATE.md is updated in the same commit, ensuring all three file updates
(INTEGRATION_PLANS.md, PR_ARC entry, and LATEST_STATE.md append) are documented
together to align with the governance model and append-only rule.

In `@crates/lance-graph-contract/src/class_view.rs`:
- Around line 137-144: The union method on the mask primitive needs local unit
test coverage to verify its behavior. Add a #[cfg(test)] module at the end of
the class_view.rs file that tests the union method with multiple test cases
covering overlap behavior (two masks with overlapping bits should produce a mask
containing all bits from both) and identity behavior (union with an empty mask
should return the original mask, and union of identical masks should return that
same mask).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7c0a6ab7-0238-409b-a7e6-3def3b9ce813

📥 Commits

Reviewing files that changed from the base of the PR and between 5f672f2 and 21053c4.

📒 Files selected for processing (9)
  • .claude/board/INTEGRATION_PLANS.md
  • .claude/board/LATEST_STATE.md
  • .claude/plans/integration-actionhandler-rbac-orchestration-v1.md
  • crates/lance-graph-contract/src/action.rs
  • crates/lance-graph-contract/src/class_view.rs
  • crates/lance-graph-contract/src/rbac.rs
  • crates/lance-graph-ogar/src/lib.rs
  • crates/lance-graph-ogar/src/rbac_impl.rs
  • crates/lance-graph-rbac/src/authorize.rs

Comment thread .claude/plans/integration-actionhandler-rbac-orchestration-v1.md Outdated
Comment thread crates/lance-graph-contract/src/action.rs Outdated
Comment thread crates/lance-graph-contract/src/rbac.rs
Comment thread crates/lance-graph-rbac/src/authorize.rs
…ope fold, required_role, plan doc)

Thread 2 (action.rs): commit_via now honors the SPECIFIC required_role value —
the actor must hold that exact role AND it must grant Act on the object class,
not merely 'any granted role'.

Thread 3 (rbac.rs ScopeSpec): add `deny: bool` so a two-tenant intersection is
the EMPTY scope, not 'self wins'. Self-wins silently widened row visibility to a
tenant the other granting role never authorized. `deny` is the absorbing element
of intersect; ScopeSpec::DENY is the canonical empty scope. CodeRabbit's suggested
None-for-conflict was unsafe — None=global would widen, not restrict.

Thread 4 (authorize.rs fold): intersect only CONCRETE Some row-scopes; a None
(global) role no longer materializes as ScopeSpec::default() and forces the Some
branch. The None sentinel ('no restriction') is preserved through the fold.

Thread 1 (plan doc): the v2 hardening prose said 'drop ScopeSpec, use
Option<TenantId>' contradicting the canonical signatures block (which kept
ScopeSpec). Corrected F1/F2 prose + added the deny field to the canonical block.

contract 735, rbac 23 green; fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYvNjD8M8LMNYbRy3gq2FP
@AdaWorldAPI AdaWorldAPI merged commit f0ee631 into main Jun 23, 2026
5 checks passed
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