tenantlayer.io

Contributing

TenantLayer is Apache 2.0 and open to contributions. There are 15 unclaimed issues, and seven marked as a good way in.

The one rule that is not negotiable

Every isolation claim is mutation-tested. After a test goes green, break the implementation and confirm it goes red. A pull request that adds an isolation assertion should say, in its description, what you broke and that the test caught it.

This is a security library, and the failure mode is a test that passes for the wrong reason. Real examples from this codebase: a BUILD SUCCESS with zero tests executed; an @Async test that would have passed if the work never left the calling thread; an isolation assertion that passed with the library switched off entirely; and an entity scanner whose every lookup threw and was swallowed, so “no unprotected tables” and “the scan is broken” looked identical.

What will be rejected

A change can be well written, well tested and useful, and still be declined. The bar is not “does it work” but “can it fail open”.

  • It lets data be read with no tenant bound

    No tenant must mean no rows, never all rows. Fail-closed is the whole promise.

  • It weakens a fail-closed default

    Or adds a setting whose insecure value is the default. Insecure has to be something a person typed on purpose.

  • It adds a non-optional dependency

    Spring Security, Kafka, WebFlux, Feign and Testcontainers are all optional and stay that way. A test enforces this by removing them from the classpath.

  • A test connects to Postgres as a superuser

    Superusers bypass row-level security, so such a test passes whether or not isolation works — including after someone deletes the policy.

  • It asserts only the negative

    "Tenant A sees none of B’s rows" is satisfied by an empty table. Pair it with "A sees its own rows".

None of these are hypothetical. Every one is a bug that has been found in this codebase and is now guarded by a test written to catch it.

How it works

  1. 1.Open an issue first — for anything beyond a typo. Scope here is opinionated, and it is kinder to hear “that belongs in the paid tier” before you write it than after.
  2. 2.Fork, branch, work — Branch naming is not policed.
  3. 3.Sign your commits off — git commit -s. It is the Developer Certificate of Origin, and CI checks it.
  4. 4.Open a pull request — The template asks what you broke to prove the test works. That is not a formality.
  5. 5.CI must be green — JDK 17 and 21, Hibernate 6 and 7, plus a build of the example against the packaged artifact.
  6. 6.A maintainer squash-merges — Your branch history stays yours; main stays readable.

There is no CLA, deliberately

Open-core projects usually require a contributor licence agreement so the company can move contributed code into its proprietary tier. This project does not, because its governing rule makes that unnecessary: free is correctness, paid is operations, and the free core is never crippled to sell the paid one.

Free-core code does not migrate into Pro. Pro ships from a separate repository as a separate artifact. So a CLA would buy nothing a Developer Certificate of Origin does not, while measurably deterring contributors.

Your contribution stays Apache 2.0, and it will not turn up behind a paywall.

Who decides

Suchait Gaurav is the maintainer and has the final say on scope and design. Worth stating rather than leaving you to infer it: you should know whose opinion you are arguing with, and that a well-reasoned disagreement is welcome and may still end in a no. Decisions about the free/paid boundary are made against the published placement rule and nothing else — if one looks inconsistent with it, saying so in an issue is a fair challenge.