Skip to content

Admit a caller by the subjects it holds, not by its identity alone - #40

Merged
LucaCappelletti94 merged 4 commits into
mainfrom
feat/subject-set-spelling
Sep 20, 2026
Merged

LucaCappelletti94 merged 4 commits into
mainfrom
feat/subject-set-spelling

Conversation

@LucaCappelletti94

@LucaCappelletti94 LucaCappelletti94 commented Sep 20, 2026

Copy link
Copy Markdown
Owner

A caller has always been a set here, an identity plus whatever share keys it holds, and the session binding has always handed Postgres both. A subscription could only ever say the first, so a membership granted to a key reached nobody, and a caller holding keys alone could not take a filter that names a membership at all. This makes the subscription say what the binding already says. Each compiled term now declares which of the caller's values its own SQL reads, and it is seeded from that one and not the other, so an identity-spelled filter keeps comparing one session value and a set-spelled filter is matched against every subject the caller holds. Seeding one from the other is what would admit rows the registered query does not return, which is the failure the split exists to prevent.

The hidden membership subscription the server opens on a client's behalf follows the same rule. It is keyed by table and opened once, so a table watched by an identity term and a set term at once is mirrored under both spellings joined by OR rather than under whichever term was described first. Its set spelling is the guarded search pg2sqlite emits rather than the obvious substring test, because the plain one reverse translates as a position query and disagrees with the membership on an unset setting, an empty one, and a column value carrying the delimiter. A test pins that predicate against the translator rather than against a literal, so a change to the emitted idiom fails here instead of quietly widening what the mirror admits.

Getting here took four fixes in the dependencies, each of which was the real obstacle rather than a detour: the caller stated as a set of subjects and the drain's future declared Send in subql, a session setting declared as a delimited set in pg2sqlite, and a membership policy matched against that set in rls2fga. All four are merged and this moves every workspace onto them. The end-to-end proof is a caller who owns nothing and is a member of nothing, holding only the key a team is granted to, receiving that team's rows.

One piece is deliberately not here. A replica evaluating a set-spelled query locally needs its own subjects function, and the packing that renders it lives in the server crate while the client depends only on the core one. Spelling it a second time in a second crate is exactly the drift this change works to avoid, so the sharing move comes first and the client registration with it.

Summary by Sourcery

Authorize and synchronize callers according to the identity and subject keys they actually hold, rather than identity alone.

New Features:

  • Support authorization and subscription delivery based on the caller’s held subject set, including callers authenticated solely by share keys.
  • Allow replicas to register and evaluate the packed caller subject set locally through deployment-defined capability-key encoding.
  • Mirror membership subscriptions for identity and subject-set terms together, including combined table predicates.

Bug Fixes:

  • Ensure subscription seeding uses the caller value actually referenced by each compiled term, preventing rows from being over-admitted or missed.
  • Make subject-set membership matching fail closed for unset or empty settings and correctly handle delimiter-containing values.
  • Enable key-only callers to receive rows granted through their share-key memberships.

Enhancements:

  • Move capability-key packing into the core authorization model and separate key rendering from server-only key minting.

Build:

  • Update workspace dependencies and lockfiles to consume the required upstream changes.

Deployment:

  • Add configuration for mapping the caller subject-set function and session setting in the server.

Documentation:

  • Document replica subject-set configuration and the separation between capability-key packing and server-side key minting.

Tests:

  • Add unit, translation, client integration, and end-to-end coverage for packed subject-set registration, membership predicates, combined caller kinds, fail-closed behavior, and key-only access.

@sourcery-ai sourcery-ai 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.

Sorry @LucaCappelletti94, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 21 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 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: Repository: LucaCappelletti94/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 6bf13024-251f-47ee-94c3-d476b97517f3


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 Sep 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-09-20T10:41:42.069003Z 5cc2820 PR opened
ℹ️ 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.

@sourcery-ai

sourcery-ai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extends caller-aware subscriptions from identity-only authorization to independently compiled identity and subject-set authorization, including precise per-term seeding, combined hidden membership mirrors, delimiter-safe reverse translation, and end-to-end share-key coverage.

Sequence diagram for subject-set subscription authorization

sequenceDiagram
    participant Caller
    participant Server
    participant Materializer
    participant Postgres
    participant Replica

    Caller->>Server: Subscribe(query)
    Server->>Materializer: translate_subscription_sql(query)
    Materializer->>Postgres: Compile identity and subject-set mappings
    Server->>Server: seed_for_terms(terms)
    alt Identity term
        Server->>Server: typed_subscriber(identity)
    else Subject-set term
        Server->>Server: caller_subjects(identity, capabilities, kind)
    end
    Server->>Replica: Register query with precise seed
    Server->>Server: mirror_predicate(identity_function, subjects_function)
    Server->>Replica: Open hidden membership subscription
    Replica-->>Caller: Rows granted by identity or held subject key
Loading

Flow diagram for combined membership mirroring

flowchart LR
    Terms["Membership terms"] --> Table["One mirror per membership table"]
    Table --> Identity["Identity reach"]
    Table --> Subjects["Subject-set reach"]
    Identity --> Or["Joined with OR"]
    Subjects --> Or
    Or --> Changes["Membership changes delivered"]
Loading

File-Level Changes

Change Details Files
Model caller authorization as separate identity and subject-set inputs throughout subscription compilation and session binding.
  • Introduce CallerMappings with independent identity and subject-set session-variable mappings.
  • Track each term’s TermCaller and seed identity terms from the subscriber while subject-set terms use all held subjects.
  • Allow subject-only callers, while refusing anonymous or mistyped registrations rather than narrowing authorization.
  • Expose the subject-set reach helper for shared server/client use.
crates/connetto-server/src/bin/connetto-server.rs
crates/connetto-server/src/lib.rs
crates/connetto-server/src/materializer.rs
crates/connetto-server/src/session.rs
Build hidden membership subscriptions that preserve every caller spelling used for a watched table.
  • Aggregate per-table membership terms by identity versus subject-set usage.
  • Join identity and guarded subject-set predicates with OR when one table is used by both kinds of term.
  • Report missing mappings explicitly and retain table-keyed subscription lifecycle and routing behavior.
crates/connetto-server/src/session.rs
Add translator-focused and end-to-end coverage for subject-set authorization.
  • Verify the guarded delimiter-aware predicate reverse-translates to PostgreSQL membership semantics.
  • Test mixed identity/set mirrors and missing subject mappings.
  • Add a fanout fixture proving a caller holding only a granted share key receives rows despite owning none.
crates/connetto-server/src/session.rs
crates/connetto-server/tests/it/subscription_translate.rs
crates/connetto-test-harness/src/fanout.rs
crates/connetto-test-harness/src/lib.rs
crates/connetto-test-harness/tests/it/membership_term.rs
Update workspace dependency lockfiles for the supporting subql, pg2sqlite, and rls2fga changes.
  • Regenerate affected Cargo.lock files to consume the four dependency fixes.
Cargo.lock
crates/connetto-web/Cargo.lock
examples/dioxus-desktop-demo/Cargo.lock
examples/dioxus-web-demo/Cargo.lock
examples/wasm-smoke/Cargo.lock
examples/webauthn-unlock/Cargo.lock
examples/yew-web-demo/Cargo.lock

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@LucaCappelletti94
LucaCappelletti94 changed the base branch from chore/pin-narrative-and-subject-coupling to main September 20, 2026 10:39

@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: 5cc2820f7d

ℹ️ 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 +3741 to +3744
caller_subjects(
state.principal.identity(),
state.principal.capabilities(),
kind,

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 Seed subject-set terms from capability keys only

When an identified caller registers a term reading the subject-set setting, this passes both its identity and capabilities to caller_subjects, even though CallerBinding populates app.subjects exclusively with CapabilityKey::pack(caller.capabilities()). Consequently, a direct subject-set comparison or later membership event naming the user's identity can match in the compiled term although the actual PostgreSQL predicate is false, producing live results outside the registered filter. Pass only capability subjects when seeding TermCaller::Subjects.

Useful? React with 👍 / 👎.

Comment on lines 4160 to 4161
if state.subs.contains_key(&label) {
return Ok(());

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 Widen an existing membership mirror for later caller kinds

When one session has separate subscriptions that reference the same membership table using different caller kinds, the first subscription installs a table-only hidden label and this early return prevents the second subscription from adding its reach. For example, an identity-based subscription opened first leaves a later subject-set subscription with an identity-only mirror, so key-owned membership rows are neither snapshotted nor delivered on subsequent changes; reversing registration order breaks the identity-based subscription instead. The existing mirror must be replaced or widened when the new MemberTable flags are not already covered.

Useful? React with 👍 / 👎.

Comment on lines +235 to +238
"CASE WHEN {function}() IS NOT NULL THEN {function}() <> '' \
AND instr({column}, '{separator}') = 0 \
AND instr('{separator}' || {function}() || '{separator}', \
'{separator}' || {column} || '{separator}') > 0 END"

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 Escape the configured separator before rendering SQL

For deployments whose custom CapabilityKey::SEPARATOR is a single quote, interpolating the character directly between SQL quotes produces an unterminated or malformed literal in every generated instr expression. Since the trait explicitly permits deployments to choose this separator, subject-set membership subscriptions then fail while being opened; render the separator as an escaped SQLite string literal rather than inserting the raw character.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Sep 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.54545% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.29%. Comparing base (4e48a27) to head (0959f2d).

Files with missing lines Patch % Lines
crates/connetto-server/src/session.rs 84.07% 14 Missing and 11 partials ⚠️
crates/connetto-server/src/bin/connetto-server.rs 0.00% 10 Missing ⚠️
crates/connetto-client/src/lib.rs 91.30% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #40      +/-   ##
==========================================
- Coverage   83.29%   83.29%   -0.01%     
==========================================
  Files         114      114              
  Lines       25770    25937     +167     
  Branches    25770    25937     +167     
==========================================
+ Hits        21466    21605     +139     
- Misses       3076     3095      +19     
- Partials     1228     1237       +9     
Flag Coverage Δ
client 63.76% <20.37%> (-0.33%) ⬇️
rest 56.60% <75.89%> (+0.55%) ⬆️
server 48.74% <34.45%> (-0.09%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@LucaCappelletti94
LucaCappelletti94 merged commit 88eaa32 into main Sep 20, 2026
100 of 101 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.

1 participant