Skip to content

contract + rbac: walk_rails_authorized and visible_fields (H7) - #1558

Merged
AdaWorldAPI merged 5 commits into
mainfrom
ccr-0455e606-wmtsor
Oct 14, 2026
Merged

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

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

What

The access decision already returns a field mask per class (Decision.field_mask, which folds the role masks and the class default from #1556). Until now, nothing in lance-graph used that mask to remove hidden fields from an output. This PR adds the step that does.

selection::walk_rails_authorized is the same walk as walk_rails, plus one mask in the AND at every hop:

effective = view.mask ∩ presence ∩ allowed(class)
  • Hidden fields stay hidden at every depth. Each hop uses its own class's mask. A reader allowed to see a work package but not a user's e-mail gets the assignee's name and no e-mail.
  • A hidden rail is not followed. A rail is a field position, so masking it out also prunes everything reachable only through it. A reader cannot read a linked object through a link they may not see.
  • No permit-all fallback. allowed returns a WideFieldMask, not an Option. A class the reader may not read returns EMPTY and emits nothing.

The step is policy data only: the walk applies the caller's decision and decides nothing itself. walk_rails is unchanged; it now delegates with no decision.

Refused decisions show nothing

ScopedDecision and MembershipDecision keep the open default in field_mask when access is refused; that field is documented as irrelevant on refusal. Passed straight into the walk, it would show every field of a class the reader may not read.

The new method visible_fields() returns the mask only when the decision allows access, and EMPTY otherwise. The walk's docs tell callers to use it.

Tests: a_refused_decision_shows_no_fields fails if the method returns field_mask unconditionally. an_allowed_decision_shows_its_field_mask checks the allowed case.

Tests

There are four new tests, selection::tests::authorized_walk_*:

  • a hidden field of a nested class;
  • a hidden rail;
  • a full decision, which must equal the plain walk;
  • an unreadable root.

Two disable runs show the tests depend on the new step:

  • With the mask step removed, 3 tests fail.
  • With the mask applied only to emitted fields, so the rail loop still sees the unmasked set, the hidden-rail test fails.

cargo test -p lance-graph-contract: all suites pass. clippy -D warnings is clean.

Access-control model document

.claude/knowledge/access-control-model.md covers:

  • the pipeline: identity → actor → roles → decision → output;
  • the four axes of a decision and how each combines;
  • the rules that are easy to get wrong;
  • the 2026-10-14 decision that access control stays policy data keyed by classid and never hand-rolls reasoning.

Not in this PR

  • OGAR ogar-doc-ir's resolve_doc calls walk_rails. Passing a decision through to it is a separate OGAR PR.
  • Report grids (lance-graph-report-ogar) have no per-field positions to mask, so they are not covered here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg


Generated by Claude Code

…op (H7)

The access decision already produced a field mask per class, but nothing in
lance-graph removed the hidden fields from an output. walk_rails_authorized
adds one mask to the walk's AND at every hop -- view.mask, presence, and the
mask allowed for that hop's own class -- so a hidden field is never emitted
at any depth and a hidden rail is not followed. The mask argument returns a
WideFieldMask, not an Option, so there is no permit-all fallback. walk_rails
is unchanged and delegates with no decision.

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
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 14, 2026 22:39
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 3bc8d03d-515d-450d-ae37-9e05ca472527
  • Autofix · 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.

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 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-14T22:40:38.982959Z f3e0c28 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.

A refused decision keeps the open default in field_mask, documented as
irrelevant on refusal. Fed to walk_rails_authorized it would show every field
of a class the actor may not read. visible_fields() returns field_mask only
when the decision allows, EMPTY otherwise; the walk's docs point at it.

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

@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: f3e0c28792

ℹ️ 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".

Comment thread crates/lance-graph-contract/src/selection.rs Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
@AdaWorldAPI AdaWorldAPI changed the title contract: walk_rails_authorized applies the reader's field mask per hop contract + rbac: walk_rails_authorized and visible_fields (H7) Oct 14, 2026
One page: the identity -> actor -> roles -> decision -> output pipeline, the
four axes and how each combines, the rules that are easy to get wrong (admin
is a role, mask-less roles get the class default, refused decisions carry an
open mask, hidden rails hide what is behind them), and the 2026-10-14
decision that access control stays policy data keyed by classid and never
hand-rolls reasoning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
@AdaWorldAPI
AdaWorldAPI merged commit 80891b1 into main Oct 14, 2026
17 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