Skip to content

Enforce :rules-engine has no public API outside @InternalRulesEngineAPI#3480

Open
ajpallares wants to merge 16 commits into
pallares/rules-engine-skeletonfrom
pallares/rules-engine-enforce-internal-api
Open

Enforce :rules-engine has no public API outside @InternalRulesEngineAPI#3480
ajpallares wants to merge 16 commits into
pallares/rules-engine-skeletonfrom
pallares/rules-engine-enforce-internal-api

Conversation

@ajpallares
Copy link
Copy Markdown
Member

@ajpallares ajpallares commented May 14, 2026

Resolves SDK-4340

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-ios and hybrids

Motivation

A baseline-diff API check (regenerate api.txt, git diff --exit-code) is silenceable: a contributor can add a non-internal public declaration and just regenerate the baseline.

Description

Adds an intrinsic CI assertion that the metalava signature dump (rules-engine/build/api-dump.txt) contains nothing beyond the @InternalRulesEngineAPI annotation itself. Runs in the existing metalava job. Bypassing it requires editing the script's allow-list, not regenerating a file.

Verified locally: passes on current source; fails (with the offender printed) when a non-annotated public fun is added.

Stacked on #3478.


Note

Low Risk
The provided diff contains no actual code changes (only a +++ /dev/null marker), so there is no functional impact or risk to runtime behavior.

Overview
No functional changes are present in the supplied diff; it only shows a +++ /dev/null marker without any added/removed content, so there is nothing substantive for reviewers to validate.

Reviewed by Cursor Bugbot for commit 94d6c29. Bugbot is set up for automated code reviews on this repo. Configure here.

ajpallares and others added 2 commits May 14, 2026 12:29
…public API

Adds scripts/check-rules-engine-internal-only.sh which regenerates rules-engine/api.txt and asserts it contains only the @InternalRulesEngineAPI annotation declaration. Unlike a baseline diff, this check is intrinsic and cannot be silenced by regenerating api.txt.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.89%. Comparing base (a6f92d3) to head (3ed9eaf).

Additional details and impacted files
@@                       Coverage Diff                       @@
##           pallares/rules-engine-skeleton    #3480   +/-   ##
===============================================================
  Coverage                           79.89%   79.89%           
===============================================================
  Files                                 369      369           
  Lines                               14871    14871           
  Branches                             2048     2048           
===============================================================
  Hits                                11881    11881           
  Misses                               2157     2157           
  Partials                              833      833           

☔ View full report in Codecov by Sentry.
📢 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.

ajpallares and others added 5 commits May 14, 2026 19:06
# Conflicts:
#	scripts/api-dump.sh
…into pallares/rules-engine-enforce-internal-api
The `metalava { hiddenAnnotations.add(…) }` block lives in this branch
(rather than the skeleton) because the
`scripts/check-rules-engine-internal-only.sh` guardrail is the only
thing that depends on it: it asserts that, once
`@InternalRulesEngineAPI`-annotated declarations are hidden, the
generated `api.txt` contains nothing but the annotation interface
itself. Anything else is a leaked non-internal public API.

Without this block, the skeleton would have nothing exercising metalava
on `:rules-engine` (publishing wiring also lives in a separate draft
PR), so we keep it co-located with the check that needs it.

Co-authored-by: Cursor <cursoragent@cursor.com>
…into pallares/rules-engine-enforce-internal-api
ajpallares added a commit that referenced this pull request May 15, 2026
`aed6e2f88` committed an `api.txt` for parity with other public-library
convention-plugin modules, but the immediate follow-up #3480
(no-public-apis enforcement) replaces that baseline with an explicit
annotate-or-fail check that doesn't keep a file in source. Committing
the baseline here just to delete it there is churn, so:

- Drop `rules-engine/api.txt`.
- Set `enforceCheck = false` so the standard `check` task no longer runs
  `metalavaCheckCompatibility*` against a missing baseline.
- Redirect `metalavaGenerateSignature*` output to `build/api-dump.txt`
  so local spot-checks don't drop a file at the module root.
- Keep `hiddenAnnotations.add(...InternalRulesEngineAPI)` so the
  generated dump filters gated symbols.
- Update the trailing comment to call out both follow-up PRs
  (publishing/distribution + #3480 enforcement).

Co-authored-by: Cursor <cursoragent@cursor.com>
ajpallares and others added 2 commits May 15, 2026 20:13
…into pallares/rules-engine-enforce-internal-api

Co-authored-by: Cursor <cursoragent@cursor.com>
After merging the latest skeleton, the metalava block carried a comment
that referred to PR #3480 as the home of the no-public-APIs check, which
no longer made sense on this branch (this is PR #3480). Replace it with
a short note explaining what the dump is and why `enforceCheck` is off.

The skeleton's `filename.set(...)` now writes to `build/api-dump.txt`
instead of `rules-engine/api.txt`, so retarget the check script at the
new path. The "must match `filename.set(...)`" comment exists so a
future tweak in either file flags the other.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ajpallares ajpallares marked this pull request as ready for review May 15, 2026 18:22
@ajpallares ajpallares requested a review from a team as a code owner May 15, 2026 18:22
@ajpallares ajpallares requested a review from a team May 15, 2026 18:23
Copy link
Copy Markdown
Contributor

@tonidero tonidero left a comment

Choose a reason for hiding this comment

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

I think the logic makes sense in general, but from my comment in the previous PR, I do wonder if it makes sense to just expose the API as "public" but just make it clearer devs shouldn't use it with the artifact name...

ajpallares added a commit that referenced this pull request May 21, 2026
Mirrors the iOS counterpart shift on PR #6787 (commit `931f36373`):
the per-declaration opt-in marker is dropped in favor of a structural
guarantee. On Android the structural guarantee was already in place —
`:rules-engine` is consumed by `:purchases` / `:ui:revenuecatui` as an
`implementation` dependency, which keeps every symbol off the SDK's
transitive compile classpath, so third-party consumers never see the
declarations regardless of their visibility modifier. The annotation
was therefore signaling intent rather than enforcing it.

Removing it cleans up:

- `InternalRulesEngineAPI.kt` (the `@RequiresOptIn` annotation) is
  deleted outright.
- `RulesEngine` becomes a plain `public object`. Doc comment trimmed
  to point readers at the structural-via-`implementation` guarantee
  rather than at the now-gone annotation.
- `RulesEngineTest` drops its `@OptIn(InternalRulesEngineAPI::class)`.
- `rules-engine/build.gradle.kts` no longer needs the `metalava`
  block. With no annotation to hide, the convention plugin's defaults
  produce a committed `rules-engine/api.txt` at the module root with
  `enforceCheck` on — same shape as `:feature:amazon` and the rest of
  the published modules. This also removes the `build/api-dump.txt`
  spot-check redirection.
- `rules-engine/api.txt` baseline is committed for the first time.
  Skeleton surface is exactly `RulesEngine` (the namespace object).

The follow-up commit on PR #3480 will drop
`scripts/check-rules-engine-internal-only.sh` (its premise — "api
dump must contain only the annotation declaration" — no longer
holds) and the corresponding CircleCI step. PR #3480 itself ends up
empty as a result; iOS `4c32721b8` made the same call and rescoped
its enforcement PR.

No platform-specific Detekt equivalent of iOS's
`no_leaking_rules_engine_import` SwiftLint rule is added at this
time. Per-module `implementation` dependencies plus Kotlin's
`internal` visibility on every non-namespace declaration cover the
intended surface for now.

Verified: `:rules-engine:testDefaultsDebugUnitTest`, `detektAll`,
`metalavaGenerateSignatureDefaultsRelease`, and
`metalavaCheckDefaultsRelease` all green.

Co-authored-by: Cursor <cursoragent@cursor.com>
…is gone

Follows up the skeleton-side change (commit dropping
`@InternalRulesEngineAPI`). The check this PR introduced —
`scripts/check-rules-engine-internal-only.sh` — asserts that the
metalava api dump only contains the `InternalRulesEngineAPI`
annotation declaration and treats anything else as a leak. With the
annotation deleted that assertion is vacuously false (the dump now
contains every public symbol of the module), so the script either
fails by definition or has to be inverted into a different kind of
check.

iOS made the same call on `4c32721b8`: the equivalent
`check_rules_engine_no_public_api` Fastlane lane was deleted rather
than rescoped, because once the per-declaration markers are gone the
guarantee shifts to the *consumer* side (import form on iOS,
`implementation` dependency on Android). On Android the consumer-side
guarantee was already structural — `:purchases` /
`:ui:revenuecatui` pull `:rules-engine` in as `implementation`, so
nothing in the module ever lands on the SDK's transitive compile
classpath — so we don't need the iOS lint-rule equivalent either.

Removed:

- `scripts/check-rules-engine-internal-only.sh` (the script).
- `.circleci/config.yml` step that invoked the script under the
  `api-tester` job.

The `metalava { hiddenAnnotations = … }` block in
`rules-engine/build.gradle.kts` was already dropped by the skeleton
merge — no annotation to hide, default plugin config now produces a
committed `rules-engine/api.txt` baseline at the module root with
`enforceCheck` on, same shape as `:feature:amazon` and the rest of
the published modules.

Verified: `:rules-engine:testDefaultsDebugUnitTest`, `detektAll`, and
`:rules-engine:metalavaCheckDefaultsRelease` all green.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants