Skip to content

D-RMAP-DIR-1: compiled membership closure vs a 256-slot focus over dir-sim (lab only) - #1635

Merged
AdaWorldAPI merged 7 commits into
mainfrom
ccr-0455e606-wmtsor
Oct 20, 2026
Merged

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

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

A lab probe that measures whether dir-sim's membership, inheritance and role questions gain more from compiled closure rows shared across users (B+) or from a reusable 256-slot group focus (F). No production file changes. dir-sim stays authoritative and is read only through its public API. Nothing is promoted.

Plan and full results: .claude/plans/d-rmap-dir-1-membership-closure-focus-v1.md (round 1 in §1–8, round 2 in §9).

Arms

arm what
A dir-sim View walks (authoritative)
D crepe Datalog over the fixture's own integer ids. An independent oracle: it never sees a dir-sim ordinal.
B user and group membership CSR, nesting CSR
B+ B, plus closure rows per group for every chain (all, security, mail), up and down, and the roles carried by each group's SID chain
B+w B+ reading only the closure words that hold a question group (question-aware, no focus; benchmark only)
F the B+ closure of one chain projected onto up to 256 bound groups. F+users adds one focus row per user, built up front; F lazy builds a user's row on first use.

Role and location are ordinary groups. A role × location combination is either a materialized composite group nested in both, or a conjunction asked of the closure (a fixture without composites tests the second form alone). Neither adds membership semantics.

Correctness

  • tests/equivalence.rs, 11 tests. B, B+, D and F (eager and lazy rows) all equal A on every user, group and chain of the small fixture. Specific cases:
    • two-hop paths through unfocused groups are represented on every chain;
    • after an overlay with a cycle, the incrementally refreshed F equals a fresh bind, A and D;
    • composite membership and policy conjunction each agree with A and D;
    • F refuses what it cannot answer completely: more than 256 groups, a duplicate, a stale B+ or focus, an unfocused group;
    • a witness from another store is refused.
  • The benchmark checks before it times. At 8,000 and 65,000 users, B+ equals A on a sample and D on every membership, and every arm equals B+ on every user and question.
  • Disable runs:
    • round 1: 7 guards red, plus 1 shown to be an optimisation only;
    • round 2: 8 guards red. One check was vacuous at first: the focus-witness check was not load-bearing until a current-B+/stale-focus case was added.

Results (8,000 users, release build, medians)

A B B+
one user's effective groups 408–466 µs 92–124 ns 32–49 ns
one user's roles (SID chain) 412–431 µs 181–192 ns 13–16 ns
all-user reconciliation across 2 versions 7.1 s — 0.9 ms
build — 2.6 ms, 52 allocs +1.6 ms, 7 allocs, 1.65 MiB retained

Round 2, F against B+, measured on L1d 48 KiB / L2 2 MiB:

  • Preparation. F's projection now walks each focus group's downward closure row: 1.6–2.7 µs at 64 groups, against 72–82 µs for round 1's per-slot test. That moves F's break-even from ~1,000 reuses to 2–100. Rebuilding a focus for every question never pays.
  • Working set. At a fixed 150 questions, F stays at 74–79 ns per evaluation from 1.5k to 11k groups, while B+ grows from 218 to 436 ns with row width. That is 2.9× → 5.5×. B+ passing L2 cost it only 5–17 % (warm vs hot). Cold: F ~2×, F+users ~4×.
  • The question set decides.
    • Composite membership (one bit per question): F 1.7–2.9× at every focus size.
    • Conjunctions: 2.4–5.4× up to about 650 questions. At 256 groups the 4,300 conjunctions take 135 KiB as masks (more than L1d), and F's mask form becomes 1.5–1.7× slower than B+. The 8 KiB register fitting L1 decides nothing here.
  • Accounting. Every phase is timed and counted alone (slots, projection, eager or lazy per-user rows, refresh after membership edits, refresh after a narrow and a wide nesting pair). The hot path of every arm makes 0 allocations.

Recommendation

  • B: yes. It removes the per-call scan.
  • B+: yes. Sharing the compiled closure across users is where most of the acceleration comes from.
  • F: a real but conditional gain on top of B+. It needs a reused focus, a question set that fits L1, and many groups. It is not proposed for production, and nothing is promoted.

D-RMAP-DIR-2 (B built in Snapshot::build from the lanes, B+ as a version-witnessed cache behind View) is not authorized.

Open, and not settled by this PR: B, B+ and F all still read rows at addresses taken from a user's direct-group list on the hot path, so they do not satisfy the no-gather rule. That stays with ISS-ADMISSION-PREP-BINDING.

Files

  • crates/dir-rmap-probe/: own workspace; fixture (Spec::iam_scaled, without_composites), prep (B and B+), focus (F, F+users, F lazy), oracle (D), tests, and examples/bench.rs (counting allocator, W1–W9, working-set sweep).
  • .github/workflows/dir-rmap-probe.yml: fmt, clippy and tests, plus the 8,000-user benchmark as an equivalence smoke test (sweep factors 1 and 2; timings are not gated).
  • Board: STATUS_BOARD rows D-RMAP-DIR-1 and D-RMAP-DIR-2, two entries, INTEGRATION_PLANS, entries index.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg

…ab only)

A lab probe over lance-graph-dir-sim's public API; no production file changes.

- fixture: deterministic IAM directory (roles, locations, role x location
  composite groups nested in both, resources with cycles, licenses, teams,
  divisions that cut the SID chain, grants, a u8 attribute lane).
- B: bidirectional membership CSR + nesting CSR, by dir-sim ordinal.
- B+: closure rows per group for every chain (all, security, mail), a
  fixpoint over the nesting CSR; roles carried by the SID chain per group;
  incremental nesting insertion.
- F: 256-slot focus over selected groups, B+ closure rows projected onto
  the slots; F+users adds one focus row per user.
- D: crepe Datalog oracle over fixture ids.

Tests: every arm equals dir-sim's walks and the oracle on every user and
group, for all three chains, roles and direct membership; the focus
answers role x location like both; incremental nesting equals a recompile
after an overlay; stale witnesses and unfocused groups are refused; the
fixture is checked for cycles, large groups and filtering chains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
- examples/bench.rs: W1-W9 for arms A, D, B, B+, F and F+users at 8,000
  and 65,000 users; focus sizes 16/64/128/256; 1 to 10,000 evaluations
  per prepared focus; memory; cold vs warm. Every timed answer is checked
  against A (and D) first.
- Clippy fixes (Rng::next_u64, collapsed if-let); the closure's parent
  admit check is marked an optimisation: its disable run stayed green
  because an unadmitted row is never seeded or written.
- Plan d-rmap-dir-1-membership-closure-focus-v1.md with the measured
  results; STATUS_BOARD rows D-RMAP-DIR-1 (MEASURED) and D-RMAP-DIR-2
  (PROPOSED, not authorized); entry, INTEGRATION_PLANS, entries index.
- CI dir-rmap-probe.yml: fmt, clippy, tests, and the 8,000-user benchmark
  as an equivalence smoke run (timings not gated).

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

coderabbitai Bot commented Oct 20, 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: e90ab20a-a2a7-4c06-b6a6-01236260c2fc
  • 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.

…, composite vs conjunction fixture

F now binds one chain (all/security/mail) and projects the complete B+
closure through down rows; a focus-local reference projection is kept
for the equality test. Preparation is split (slots, projection, eager
or lazy per-user rows), refresh is incremental after membership edits
and nesting pairs, and a focus refuses a stale B+, >256 groups and
duplicates. New tests: per-chain F vs A and D, two-hop paths through
unfocused groups, refreshed focus vs fresh bind after an overlay with a
cycle, composite membership vs policy conjunction (composites=0 fixture).

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 20, 2026 22:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 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-20T22:17:37.315735Z a1125ee 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.

@coderabbitai

coderabbitai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Add Carrot credits or activate Agent usage billing to use Autofix

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

ℹ️ 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 on lines +61 to +65
pub struct Witness {
pub version: VersionId,
pub users: usize,
pub groups: usize,
}

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 Add a store identity to the freshness witness

When a prepared cache is checked against a separately constructed VersionStore with the same user/group counts, this witness can falsely pass: every store starts its local VersionId sequence at zero, so two unrelated root snapshots both produce (VersionId(0), users, groups). Their GUID-to-ordinal mappings and memberships can differ, causing the old closure or focus to return incorrect answers despite check returning Ok; include a snapshot/store identity or content seal in the witness.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 79e802c. The witness now also holds a Weak to the version's Snapshot and compares it by pointer. The Weak keeps the allocation alive, so the address can't be reused while the witness exists. New test a_witness_from_another_store_is_refused: two stores over the same fixture share VersionId(0) and the same counts, and a cache from one refuses the other. With the pointer comparison removed, that test fails.


Generated by Claude Code

Comment thread crates/dir-rmap-probe/src/focus.rs Outdated
impl FocusUsers {
pub fn bind(c: &Compiled, groups: &[u16]) -> FocusUsers {
let focus = Focus::bind(c, groups);
let rows = (0..c.b.users as u16).map(|u| focus.row_of(c, u)).collect();

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 the full user count when precomputing focus rows

For a valid maximum-size dir-sim snapshot with 65,536 users, casting c.b.users to u16 wraps to zero, so this range produces no rows. Subsequent FocusUsers evaluation then indexes an empty vector and panics; iterate over the usize count and cast each in-range ordinal instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 79e802c. FocusUsers::from_focus now iterates 0..users as usize and casts each ordinal to u16. The same pattern in the benchmark and a test is fixed too.


Generated by Claude Code

…junction workloads, working-set sweep

Bench: counting global allocator; per-phase timing and allocations for
focus construction, projection (down rows vs reference), eager and lazy
per-user rows, refresh after membership edits and after a narrow and a
wide nesting pair; computed and measured crossovers (T_F(N) vs T_B+(N)),
a rebuild-per-question arm and a question-aware B+ (needed words only);
W5a conjunction / W5b composite membership / W5c conjunction on a
fixture without composites; group-count sweep against L1/L2 with bytes
touched and working sets, hot/warm/cold. CI runs the sweep at 1,2.

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

Codex review on #1635: (1) a witness of (version, users, groups) passes
across two stores, since every VersionStore numbers versions from 0;
it now also holds a Weak to the version's Snapshot, compared by pointer
(the Weak keeps the address from being reused). New test: a cache from
one store refuses another store's witness over the same data. (2) user
ranges iterate as usize, so 65,536 users no longer wrap to an empty
range. Bench: the W8 rebind time no longer includes choosing the focus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
Plan §9: completeness tests and disable runs, the working-set sweep
against L1/L2, composite vs conjunction, per-phase preparation
accounting and the amortization table, revised F verdict. Board: entry
for round 2, STATUS_BOARD row, regenerated entries index. Bench: W8
rebind time no longer includes building the witness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G22yT6htkcdyXsihxxXdrg
@AdaWorldAPI
AdaWorldAPI merged commit 753dfca into main Oct 20, 2026
12 of 13 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