Skip to content

fix(sdk)!: enforce sane visibility#371

Open
mkleene wants to merge 13 commits into
mainfrom
enforce-sane-visibility
Open

fix(sdk)!: enforce sane visibility#371
mkleene wants to merge 13 commits into
mainfrom
enforce-sane-visibility

Conversation

@mkleene
Copy link
Copy Markdown
Contributor

@mkleene mkleene commented May 20, 2026

We expose types only if they are reachable from things we want people to use

If a type is not transitively reachable from one of our root classes then we
do not expose it. We make an exception for exceptions.

All types that are exposed by things we want people to use are public

This allows clients to do explicit typing for names.

Summary by CodeRabbit

  • Chores

    • Updated test dependencies (JUnit Platform and ArchUnit)
    • Refined internal API visibility to clarify the public API surface
  • Bug Fixes

    • InvalidZipException now extends SDKException
  • Tests

    • Added automated validation for the public API surface
    • Improved fuzz-test error handling for malformed inputs

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c8adea72-d9ef-44ce-a18e-5d46de400dbf

📥 Commits

Reviewing files that changed from the base of the PR and between f1fc93c and 97fcd5f.

📒 Files selected for processing (4)
  • sdk/pom.xml
  • sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDF.java

📝 Walkthrough

Walkthrough

Adds an ArchUnit-based PublicApiSurfaceTest, centralizes JUnit Platform test versions and adds ArchUnit dependency, changes InvalidZipException to extend SDKException, makes many internal classes package-private, exposes Autoconfigure.AttributeValueFQN, and promotes TDF to the public API root.

Changes

API Surface Refactoring

Layer / File(s) Summary
Test Infrastructure and Enforcement
sdk/pom.xml, sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java
Pins JUnit Platform artifacts in dependencyManagement, adds ArchUnit dependency, and introduces PublicApiSurfaceTest which computes reachable types from API roots and asserts public/protected exposure invariants.
Exception Hierarchy Realignment
sdk/src/main/java/io/opentdf/platform/sdk/InvalidZipException.java, sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java
InvalidZipException now extends SDKException; fuzzing test updated to catch SDKException.
Cryptographic and Encryption Implementation (Package-Private)
sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java, AsymDecryption.java, AsymEncryption.java, CryptoUtils.java, ECCurve.java, ECKeyPair.java
Reduced visibility of cryptographic primitives and nested DTO/enum types from public to package-private.
ZIP I/O Implementation (Package-Private)
sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java, ZipWriter.java
ZipReader, its nested Entry type, and ZipWriter are now package-private.
Data Structures and Configuration Types (Package-Private)
sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java, AssertionConfig.java, PolicyObject.java, Resources.java
Standardized modifier ordering; made PolicyBinding and AssertionValueAdapter package-private; AssertionConfig.BindingMethod package-private; Resources package-private; corrected PolicyObject.AttributeObject modifier order.
Configuration and Strategic API Surface Definition
sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java
Made AttributeValueFQN public static to be part of the public configuration API surface.
Core SDK Entry Points
sdk/src/main/java/io/opentdf/platform/sdk/TDF.java, TDFReader.java, TDFWriter.java, Planner.java
TDF promoted to public as the primary SDK entry point; TDFReader, TDFWriter, and Planner made package-private.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • marythought
  • jentfoo

Poem

🐰 A hop, a trim, the surface neat and small,
Tests trace the roots and guard the public wall,
Internal springs tucked in a burrow deep,
TDF stands proud — the gateway we will keep! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(sdk)!: enforce sane visibility' directly and specifically describes the main change—making the SDK's class visibility enforcement more consistent and intentional by exposing only types reachable from designated API roots.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enforce-sane-visibility

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 and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the SDK's public API surface by adjusting the visibility of numerous classes and members to ensure only intended components are exposed. Key changes include transitioning several classes to package-private visibility, updating InvalidZipException to extend SDKException, and adding a new ArchUnit test, PublicApiSurfaceTest, to enforce reachability-based visibility rules. Review feedback focuses on optimizing the new test by removing an unused import, using more efficient data structures, and eliminating redundant logic in the API reachability algorithm.

import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.core.importer.ImportOption;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The import org.junit.jupiter.api.BeforeEach is unused and should be removed to keep the code clean.

Config.class.getName(),
RequestHelper.class.getName()
);
static List<JavaClass> reachableClasses;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Consider using a Set<JavaClass> for reachableClasses instead of a List. This would improve the performance of the contains checks in the test methods from O(N) to O(1).

Suggested change
static List<JavaClass> reachableClasses;
static Set<JavaClass> reachableClasses;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this isn't java.lang.reflect

Comment thread sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java
Comment on lines +124 to +127
for (JavaType r: m.getParameterTypes()) {
addAllRawTypes(r, work, queued);
}
m.getParameterTypes().forEach(t -> addAllRawTypes(t, work, queued));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The loop and the forEach call are performing the exact same operation on m.getParameterTypes(). One of them should be removed to avoid redundancy.

                m.getParameterTypes().forEach(t -> addAllRawTypes(t, work, queued));

}
for (JavaConstructor ctor : c.getConstructors()) {
if (!isExposed(ctor.getModifiers())) continue;
addAllRawTypes(ctor.getRawReturnType(), work, queued);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling addAllRawTypes on the constructor's return type is redundant because JavaConstructor.getRawReturnType() returns the declaring class c, which is already being processed in the current iteration.

Comment thread sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java
@github-actions
Copy link
Copy Markdown
Contributor

X-Test Failure Report

@mkleene mkleene marked this pull request as ready for review May 20, 2026 22:33
@mkleene mkleene requested review from a team as code owners May 20, 2026 22:33
@github-actions
Copy link
Copy Markdown
Contributor

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/src/main/java/io/opentdf/platform/sdk/InvalidZipException.java (1)

4-6: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update stale Javadoc to match the new exception hierarchy.

The class comment still states RuntimeException, but Line 8 now extends SDKException.

Proposed patch
 /**
  * InvalidZipException is thrown to indicate that a ZIP file being read
- * is invalid or corrupted in some way. This exception extends RuntimeException,
- * allowing it to be thrown during the normal operation of the Java Virtual Machine.
+ * is invalid or corrupted in some way. This exception extends SDKException.
  */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/main/java/io/opentdf/platform/sdk/InvalidZipException.java` around
lines 4 - 6, The class Javadoc for InvalidZipException is outdated (mentions
RuntimeException) but the class now extends SDKException; update the Javadoc for
InvalidZipException to state it extends SDKException and accurately describe its
role in the new exception hierarchy, referencing InvalidZipException and
SDKException so readers know this is a specialized SDKException thrown when a
ZIP file is invalid or corrupted.
🧹 Nitpick comments (1)
sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java (1)

63-102: 💤 Low value

Minor: inconsistent access modifier on test methods.

Line 64 declares void onlyReachableTypesAreExposed() without an access modifier, while line 85 declares public void allReachableTypesAreExposed(). For consistency, both should omit the public modifier (JUnit 5 doesn't require it).

Suggested fix
 `@Test`
-public void allReachableTypesAreExposed() {
+void allReachableTypesAreExposed() {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java` around
lines 63 - 102, In PublicApiSurfaceTest, make the access modifiers consistent by
removing the unnecessary public modifier from the allReachableTypesAreExposed()
test method so it matches onlyReachableTypesAreExposed() (both package-private),
i.e., change the signature of allReachableTypesAreExposed() to have no access
modifier; leave the method body and annotations unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java`:
- Line 57: The public nested type AttributeValueFQN is inaccessible because its
enclosing class Autoconfigure is package-private; update the visibility so
external callers of Config.TDFConfig.attributes, Config.withDataAttributes,
Config.withDataAttributeValues, and SDK.attributeValueExists can reference it:
either make Autoconfigure public or extract AttributeValueFQN into its own
public top-level class (public class AttributeValueFQN) and update any
references accordingly to preserve API compatibility.

---

Outside diff comments:
In `@sdk/src/main/java/io/opentdf/platform/sdk/InvalidZipException.java`:
- Around line 4-6: The class Javadoc for InvalidZipException is outdated
(mentions RuntimeException) but the class now extends SDKException; update the
Javadoc for InvalidZipException to state it extends SDKException and accurately
describe its role in the new exception hierarchy, referencing
InvalidZipException and SDKException so readers know this is a specialized
SDKException thrown when a ZIP file is invalid or corrupted.

---

Nitpick comments:
In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java`:
- Around line 63-102: In PublicApiSurfaceTest, make the access modifiers
consistent by removing the unnecessary public modifier from the
allReachableTypesAreExposed() test method so it matches
onlyReachableTypesAreExposed() (both package-private), i.e., change the
signature of allReachableTypesAreExposed() to have no access modifier; leave the
method body and annotations unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78021bcb-1ccf-441c-af28-c172effae882

📥 Commits

Reviewing files that changed from the base of the PR and between 9991b07 and 41c4951.

📒 Files selected for processing (21)
  • sdk/pom.xml
  • sdk/src/main/java/io/opentdf/platform/sdk/AesGcm.java
  • sdk/src/main/java/io/opentdf/platform/sdk/AssertionConfig.java
  • sdk/src/main/java/io/opentdf/platform/sdk/AsymDecryption.java
  • sdk/src/main/java/io/opentdf/platform/sdk/AsymEncryption.java
  • sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java
  • sdk/src/main/java/io/opentdf/platform/sdk/CryptoUtils.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ECCurve.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ECKeyPair.java
  • sdk/src/main/java/io/opentdf/platform/sdk/EntityIdentifiers.java
  • sdk/src/main/java/io/opentdf/platform/sdk/InvalidZipException.java
  • sdk/src/main/java/io/opentdf/platform/sdk/Manifest.java
  • sdk/src/main/java/io/opentdf/platform/sdk/Planner.java
  • sdk/src/main/java/io/opentdf/platform/sdk/PolicyObject.java
  • sdk/src/main/java/io/opentdf/platform/sdk/Resources.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDFReader.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDFWriter.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java
  • sdk/src/main/java/io/opentdf/platform/sdk/ZipWriter.java
  • sdk/src/test/java/io/opentdf/platform/sdk/Fuzzing.java
  • sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java

Comment thread sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java Outdated
@github-actions
Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions
Copy link
Copy Markdown
Contributor

X-Test Failure Report

@github-actions
Copy link
Copy Markdown
Contributor

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java (1)

43-50: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add TDF to the designated roots if this PR intends it to be a supported entry point.

TDF becomes public in this change set, but this test still seeds reachability only from SDK, SDKBuilder, AssertionConfig, Config, RequestHelper, PolicyEnums, and EntityIdentifiers. That means the ArchUnit rule validates TDF only if some other root happens to reference it, instead of protecting it as an intentional public root.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java` around
lines 43 - 50, The API roots set in PublicApiSurfaceTest omits TDF even though
TDF was made public; update the API_ROOTS constant used by the ArchUnit test to
include TDF.class.getName() so TDF is treated as an explicit public entry point
(modify the Set.of(...) in the API_ROOTS declaration that currently lists SDK,
SDKBuilder, AssertionConfig, Config, RequestHelper, PolicyEnums,
EntityIdentifiers to also include TDF.class.getName()).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/src/main/java/io/opentdf/platform/sdk/TDF.java`:
- Line 34: TDF is declared public but its constructors and entry-point factory
methods are package-private, making the type unusable to external callers; fix
by making the TDF constructors and all factory/entry methods (notably createTDF
and loadTDF and any overloads) public so external consumers can instantiate/use
the class, and ensure any other package-private methods in the TDF class that
are intended as part of the public SDK surface are likewise promoted to public
(adjust visibility for constructors and the methods referenced in the TDF class
accordingly).

In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java`:
- Around line 119-121: The superclass lambda currently ignores the discovered
superclass value `ec` and re-enqueues `c`, so change the lambda to operate on
`ec`: call addAllRawTypes(ec, work, queued) (instead of addAllRawTypes(c,...))
and also use the superclass's interfaces by replacing the external
work.addAll(c.getRawInterfaces()) with work.addAll(ec.getRawInterfaces()) inside
the ifPresent block (or otherwise ensure you call getRawInterfaces() on `ec` not
`c`) so base classes become reachable through subclass exposure.

---

Outside diff comments:
In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java`:
- Around line 43-50: The API roots set in PublicApiSurfaceTest omits TDF even
though TDF was made public; update the API_ROOTS constant used by the ArchUnit
test to include TDF.class.getName() so TDF is treated as an explicit public
entry point (modify the Set.of(...) in the API_ROOTS declaration that currently
lists SDK, SDKBuilder, AssertionConfig, Config, RequestHelper, PolicyEnums,
EntityIdentifiers to also include TDF.class.getName()).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bae3c51a-a659-463b-a63d-4d5305550333

📥 Commits

Reviewing files that changed from the base of the PR and between 41c4951 and f1fc93c.

📒 Files selected for processing (3)
  • sdk/src/main/java/io/opentdf/platform/sdk/Autoconfigure.java
  • sdk/src/main/java/io/opentdf/platform/sdk/TDF.java
  • sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java

* operations and configurations.
*/
class TDF {
public class TDF {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

TDF is public but still unusable outside the package.

External callers can now see TDF, but both constructors and all of the actual entry points (createTDF / loadTDF) remain package-private. As shipped, consumers can reference the type but cannot create or operate on one, so this public promotion is incomplete.

As per coding guidelines, keep public SDK APIs stable and additive where possible.

Also applies to: 71-80, 369-542

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/main/java/io/opentdf/platform/sdk/TDF.java` at line 34, TDF is
declared public but its constructors and entry-point factory methods are
package-private, making the type unusable to external callers; fix by making the
TDF constructors and all factory/entry methods (notably createTDF and loadTDF
and any overloads) public so external consumers can instantiate/use the class,
and ensure any other package-private methods in the TDF class that are intended
as part of the public SDK surface are likewise promoted to public (adjust
visibility for constructors and the methods referenced in the TDF class
accordingly).

Comment on lines +119 to +121
// Superclass and interfaces are part of the API surface of c.
c.getRawSuperclass().ifPresent(ec -> addAllRawTypes(c, work, queued));
work.addAll(c.getRawInterfaces());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the discovered superclass type here, not c.

Line 120 ignores ec and re-enqueues the current class, so exposed base classes never become reachable through subclass exposure. That can make this enforcement test misclassify public supertypes on both assertions.

Suggested fix
-            c.getRawSuperclass().ifPresent(ec -> addAllRawTypes(c, work, queued));
+            c.getRawSuperclass().ifPresent(ec -> addAllRawTypes(ec, work, queued));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/src/test/java/io/opentdf/platform/sdk/PublicApiSurfaceTest.java` around
lines 119 - 121, The superclass lambda currently ignores the discovered
superclass value `ec` and re-enqueues `c`, so change the lambda to operate on
`ec`: call addAllRawTypes(ec, work, queued) (instead of addAllRawTypes(c,...))
and also use the superclass's interfaces by replacing the external
work.addAll(c.getRawInterfaces()) with work.addAll(ec.getRawInterfaces()) inside
the ifPresent block (or otherwise ensure you call getRawInterfaces() on `ec` not
`c`) so base classes become reachable through subclass exposure.

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions
Copy link
Copy Markdown
Contributor

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