Netflix — Scaling ArchUnit with Nebula ArchRules¶
Summary¶
A 2026-05-08 Netflix TechBlog post by John Burns and Emily Yuan
of the JVM Ecosystem team (within Java Platform) introducing
Nebula ArchRules — a pair of Gradle plugins that take
ArchUnit from a per-repo JUnit-suite tool
into an organisation-wide distributed-rule-enforcement system
deployed across Netflix's polyrepo fleet of "tens of thousands
of Java repositories". The forcing function was a Netflix incident
"relating to a library releasing a backwards-incompatible
change" — code removed had been deprecated for years, but
"Library authors often struggle to know when it is safe to remove
deprecated code, or refactor code that is not meant to be used by
downstream applications." The team's response was a paved-road
substrate combining (a) a lifecycle-annotation taxonomy —
@Deprecated (JDK), @Public (custom), @Experimental (custom),
and "All other APIs are assumed to be 'internal'" — that lets
library authors mark API stability per-element (patterns/api-stability-annotations);
(b) the Nebula ArchRules Library
Plugin that adds a separate archRules Gradle source set
where developers implement ArchRulesService (single abstract
method returning Map<String, ArchRule>) — code in this source
set bundles into a separate jar with the arch-rules classifier
and publishes as a "separate variant with the usage attribute set
to arch-rules" via Gradle Module
Metadata (consumers must opt into Gradle Module Metadata for
resolution); (c) the Nebula ArchRules Runner Plugin that
discovers rules at consumer build time via Java's ServiceLoader,
runs them in classpath-isolated Gradle work actions per source
set, and emits JSON + console reports. Two distinct rule-library
flavors are codified: standalone rule libraries (no main code,
only archRules) for "defining rules for code you don't own,
such as Core Java APIs or OSS libraries" — these get added
explicitly via dependencies { archRules("your:rules:1.0.0") };
and bundled rule libraries (main code + archRules co-located
in the same library), recommended "whenever possible", where the
Runner "will be able to automatically detect these rules and run
them in only the source sets that use this library as a
dependency" — i.e. rules ride along with the code they govern,
auto-scope to consumers, and require zero per-consumer configuration
(patterns/bundled-rules-auto-scoped-to-library-consumers). The
canonical case-study application: library authors at Netflix mark
API surface with the lifecycle annotations, ship a bundled rules
library that detects callsites of @Deprecated / @Experimental
/ non-@Public (i.e. internal) APIs from outside the library's
own package, and "Our internal Nebula standard Gradle wrapper and
plugin suite automatically enable the ArchRules runner on every
project, and provides a custom reporter which sends the report
data to our Internal Developer Portal on every main-branch CI
build. This way, library authors can easily see a report of all
downstream consumers using their experimental, deprecated, or
non-public APIs, giving them confidence to make 'breaking'
changes, knowing that it will not actually break downstream
consumers." — the inversion of the API-deprecation problem from
"announce + hope nobody depends on it" to "ship a rule + read
the dashboard of who actually depends on it". The core technical
case for ArchUnit over alternatives like
PMD / Spotbugs is named on three
axes: (1) bytecode (ASM) vs AST — "works cross-language (JVM),
because it uses ASM/bytecode, not AST parsing," meaning a single
rule covers Java, Kotlin, Scala uniformly and "doesn't matter how
that code was produced. What is analyzed is the actual code that
will be run" (i.e. syntactic-sugar transformations can't hide
behavior from rules); (2) builder API vs XPath/CDATA —
ArchUnit's fluent type-safe Java API like
ArchRuleDefinition.priority(Priority.MEDIUM).noClasses().should().callConstructorWhere(...)
is unit-testable in-process, vs PMD's "raw string meant to be
used within PMD's xpath parser. There is no IDE guidance on
crafting it. To test it, a whole separate PMD process needs to be
wired up to interpret the rule and evaluate it against a source
file"; (3) class-graph retention — "Because ArchUnit
processes the entire classpath with ASM, it retains a graph of the
class data, allowing rules to easily traverse class relationships
and call sites", enabling cross-class rules vs per-file AST
rules. The post closes with operational scale: "We are now
running 358 (and counting) rules across over 5,000 repositories
detecting over nearly 1 million issues. About 1,000 of these
issues are for 'High' priority rules" — the canonical wiki
instance of patterns/centralized-fleet-wide-rule-catalog —
plus four named OSS standalone rule libraries Netflix has open-
sourced (Nullability via JSpecify @NullMarked, Gradle Plugin
Best Practices, Joda/Guava-Discouragement, Security/CVE-detection),
and forward-looking work on auto-remediation integration:
"We will explore deterministic solutions such as
OpenRewrite and non-deterministic
solutions such as LLMs. Pairing the easy rule authorship and
deterministic results of ArchUnit with an auto-remediation tool
that can correctly interpret the results to solve the issue at
hand will be a very powerful combination" — plus IDE-inspection
surfacing of failures.
Key takeaways¶
- The forcing function is library lifecycle in a polyrepo. "After a Netflix incident relating to a library releasing a backwards-incompatible change, our team was asked to provide some tooling and practices to improve the Java library lifecycle management. This was not a simple case of a library making a reckless breaking change. The code removed had been deprecated for years." Across "tens of thousands of Java repositories" organized in a polyrepo strategy, the library author has no central place to see who consumes their API and no mechanism to detect that a "deprecated for years" API is still in active use. The wiki canonical framing: a monorepo's "refactor everyone at once" affordance is the substitute for this tooling; in a polyrepo, the substitute has to be machine-checkable rules running in every consumer's CI (Source).
- Lifecycle annotations are the API-surface contract. "@Deprecated from the Java standard library, @Public A custom annotation to use on APIs meant to be used downstream, @Experimental A custom annotation for new APIs which may not yet be stable, All other APIs are assumed to be 'internal'." The taxonomy is deny-by-default: anything not marked
@Publicor@Experimentalis implicitly internal, regardless of Java visibility (apublic classnot annotated@Publicis internal). This sidesteps the JVM's lack of aninternal-to-libraryaccess modifier —publicis a JVM ABI requirement for cross-module visibility, but@Public-the-annotation becomes the actual API contract. Canonical wiki instance of patterns/api-stability-annotations in the JVM-bytecode + ArchUnit-enforcement ecosystem (paired with the Kotlin variant in Airbnb Viaduct) (Source). - Bytecode (ASM) over AST is the structural choice. "Some tools, such as PMD, process rules against an AST (abstract syntax tree). … Rules that need to support multiple JVM languages, such as Kotlin or Scala, often need to be rewritten for each language. It also allows code which should be found to be hidden under syntactic sugar not anticipated by the rule author. ArchUnit uses ASM to analyze actual compiled bytecode, which means it doesn't matter how that code was produced. What is analyzed is the actual code that will be run." Three load-bearing properties: language-agnostic (one rule covers Java + Kotlin + Scala), syntactic-sugar-immune (Kotlin extension functions, Scala implicits, Java
varall desugar to bytecode the rule sees), and what-runs-is-what's-checked (annotation processors / KSP / kapt / lombok-generated code is analyzed, not the source you wrote). Canonical wiki framing of concepts/bytecode-vs-ast-static-analysis (Source). - Builder API + class graph beats XPath + per-file AST. Two structural advantages of ArchUnit's API design over PMD/Spotbugs: "Tools like PMD and Spotbugs are not optimized for custom rule authorships. Most usage of these tools run built-in provided rules, or add in pre-made third party plugins." The PMD example given in the post — a regex-buried-in-XPath-CDATA targeting
DateTimeinstantiation without aDateTimeZone— "is a raw string meant to be used within PMD's xpath parser. There is no IDE guidance on crafting it. To test it, a whole separate PMD process needs to be wired up to interpret the rule and evaluate it against a source file." The ArchUnit equivalent is type-safe Java (compiled, IDE-completion-supported, in-process unit-testable). Plus: "Because ArchUnit processes the entire classpath with ASM, it retains a graph of the class data, allowing rules to easily traverse class relationships and call sites. This allows rules to have much more context about the code it is evaluating." Cross-class rules ("this method may not be called from outside its own package") require classpath-graph awareness that per-file AST tools structurally cannot provide (Source). - Bundled rules auto-scope to library consumers — the central design lever. "A bundled rule library is a library with both main and archRules sources. … Whenever possible, we recommend writing rules in this bundled way. That is because the ArchRules Runner Plugin will be able to automatically detect these rules and run them in only the source sets that use this library as a dependency." The mechanic: when a project pulls in
com.netflix:my-library:1.0.0astestImplementation, the Runner plugin notices the bundledarch-rulesclassifier jar in that source set's classpath, registers the rules via ServiceLoader, and runs them only against that source set. No per-consumer configuration: the rule travels with the library it governs and only fires where the library is actually consumed. "In the following example, we have a Project which uses a test helper library as a testImplementation dependency, and also adds a standalone rules library to the archRules configuration. The test runtime classpath will only contain the implementation jar for the helper library, but the arch rules runtime will contain the archrules jar for the bundled rules and standalone rules. This all happens automatically." Canonical wiki instance of patterns/bundled-rules-auto-scoped-to-library-consumers — the central novelty over vanilla ArchUnit-as-JUnit-suite (Source). - Standalone rule libraries are the cross-cutting fallback. "A Standalone Rule library contains no main code: only archRules. These are useful for defining rules for code you don't own, such as Core Java APIs or OSS libraries. They are also useful for generic rules that can apply to any code, such as 'don't use code marked as @Deprecated'." Standalone libraries can't auto-scope (no library-consumption signal to attach to), so they must be added explicitly:
dependencies { archRules("your:rules:1.0.0") }. Netflix open-sources four such collections undernebula-plugins/nebula-archrules: Nullability (JSpecify@NullMarked, with Kotlin auto-exclusion since "Kotlin has built-in nullability"), Gradle Plugin Best Practices, Joda/Guava-Discouragement (since "these have been superseded by java.time and standard library enhancements"), and Security Rules (CVE detection — "detecting usage of known vulnerable APIs. Ideally, we keep dependencies up to date to mitigate CVEs. But sometimes that is not immediately feasible, and in those cases, a compile time check to ensure the specific vulnerable API is not used is often good enough") (Source). - Gradle Module Metadata is load-bearing for the publication mechanism. "This code and its dependencies will not be bundled with your main code. It is bundled into a separate Jar with the arch-rules classifier. When publishing, your library will publish this jar as a separate variant with the usage attribute set to arch-rules. This means that in order for downstream projects to use these rules, they must use Gradle Module Metadata for dependency resolution." The standard Maven POM format can't express "this jar is for archRules-classpath-only, not main-classpath" — the classifier-as-variant-with-usage-attribute resolution requires Gradle Module Metadata (
.modulefiles alongside.pom). This is a real adoption ceiling: organizations on Maven-only or older Gradle versions can't consume Nebula ArchRules without first migrating their dependency-resolution layer (Source). - ServiceLoader + classpath isolation is the discovery + execution mechanic. "In any case, the library plugin will automatically generate a service loader registration entry for your ArchRulesService so that the runner can discover your rules. … In each of these configurations, the archRules classpath is combined with the runtimeClasspath with the arch-rules variant selected. This configuration is the classpath used when the ServiceLoader discovers implementations of ArchRulesService. … Once the rules classpath is determined, the runner plugin will create a Gradle work action to evaluate rules against that specific source set. This action runs with classpath isolation using the archRuleRuntime configuration." Two-stage isolation: per-source-set rule-classpath construction (so
mainsource-set rules can differ fromtestsource-set rules) + classpath-isolated work-action execution (so rules can pull in their own dependency closure without polluting the project's compile classpath). The output is "a binary serialization of rule violations to a file for reporting"* — bin-serialized intermediate format, decoupled from the reporter (Source). - Customization API lets consumers override producer-shipped rules. "In a project running rules, you also have the ability to customize rule configurations using the archRules extension. For example, you can override a rule's priority level" via
archRules { ruleClass("com.netflix.nebula.archrules.deprecation") { priority("HIGH") } }. "Other customizations include disabling running rules on certain source sets and configuring the failure threshold (i.e., high priority failures will cause the build to fail)." The shape: producer ships rules with a default severity; consumer can elevate, suppress, or disable per-source-set, with build-failure thresholding tunable per-priority. The library author owns the rule; the consumer owns the policy (Source). - Operational scale: 358 rules × 5,000 repos × ~1M issues. "We are now running 358 (and counting) rules across over 5,000 repositories detecting over nearly 1 million issues. About 1,000 of these issues are for 'High' priority rules. Being able to run these rules on this scale allows us to quickly gain insight into our large fleet of microservices, and identify the areas carrying the most critical technical debt. This makes it easier to focus and prioritize our efforts." Three derived numbers: ~200 issues per rule on average (1M / 358 × 5000 ≠ that; the ratio is per-fleet, not per-rule-per-repo; per-repo it's ~200 issues per repo); 0.1% high-priority rate (1,000 / 1,000,000); ~14 rules per repo on average if all rules ran on all repos (but bundled rules auto-scope, so the actual per-repo footprint is much smaller). The dashboarding-via-Internal-Developer-Portal-on-every-CI-build model is the operator-facing payoff: library authors don't have to query repos; they read a per-rule downstream-consumer report and decide when to remove the deprecated method (Source).
- The library-author-removes-deprecated-API workflow is the user-facing payoff. Concrete worked example from the Case Study Solution: "They write ArchRules to detect usage of the annotations, scoped to their library's package, such as: ArchRuleDefinition.priority(Priority.MEDIUM).noClasses().that(resideOutsideOfPackage(packageName + '..')).should().dependOnClassesThat(resideInAPackage(packageName + '..').and(are(deprecated()))).orShould().accessTargetWhere(targetOwner(resideInAPackage(packageName + '..')).and(target(is(deprecated())).or(targetOwner(is(deprecated()))))).allowEmptyShould(true).because('Deprecated APIs are subject to removal');" — i.e. "no class outside the library's package may depend on or access a class/member inside the library's package that is annotated
@Deprecated." This rule travels with the library; runs on every consumer's CI; reports usage to the Internal Developer Portal. Library author sees the report, knows exactly which downstream repos still call deprecated APIs, "giving them confidence to make 'breaking' changes, knowing that it will not actually break downstream consumers. If their changes are currently blocked by downstream usage, they can easily see exactly which projects are reporting those usages" (Source). - Auto-remediation is the named next step. "Going forward, we will be exploring how to tie auto-remediation solutions into the ArchRules findings. ArchUnit currently provides very specific and detailed information about failures in reports, which makes a very strong input signal to an auto remediation tool. We will explore deterministic solutions such as OpenRewrite and non-deterministic solutions such as LLMs. Pairing the easy rule authorship and deterministic results of ArchUnit with an auto-remediation tool that can correctly interpret the results to solve the issue at hand will be a very powerful combination." The framing is detection-and-remediation as a coupled pipeline: ArchRules emit precise structured failure data (rule + class + line); OpenRewrite consumes those signals to apply deterministic recipe-based fixes; LLMs handle non-deterministic / context-dependent fixes. Plus IDE-inspection: "We also will investigate how to get ArchRule failure information surfaced in the IDE as inspections" — moving the feedback loop from CI-blocking to author-time (Source).
Architectural numbers + operational notes (from source)¶
- Rules in production: 358 ("and counting").
- Repositories enforcing rules: over 5,000.
- Total issues detected: nearly 1 million.
- High-priority issues: about 1,000 (≈0.1% of total).
- Polyrepo scale (Netflix-wide): "tens of thousands of Java repositories."
- OSS standalone rule libraries (named, on
github.com/nebula-plugins/nebula-archrules): Nullability (JSpecify@NullMarked, Kotlin-aware), Gradle Plugin Best Practices, Joda/Guava-Discouragement, Security Rules (CVE detection at compile time). - Lifecycle annotations:
@Deprecated(JDK),@Public(Netflix custom),@Experimental(Netflix custom); everything else is "internal" by default. - Plugin pair: ArchRules Library Plugin (rule-authoring) + ArchRules Runner Plugin (consumer-side execution).
- Source set added by Library Plugin:
archRules(separate frommain/test). - Discovery substrate: Java's
ServiceLoaderreadingMETA-INF/services/com.netflix.nebula.archrules.ArchRulesServiceregistrations auto-generated by the Library Plugin. - Publication substrate: Gradle Module Metadata with classifier
arch-rulesand usage attributearch-rules. - Execution substrate: Gradle work action with classpath isolation via
*archRuleRuntimeconfiguration per source set. - Output formats: JSON file (collected from all source sets), console-formatted text report, binary-serialized intermediate (for custom reporters).
- Customization shape:
archRules { ruleClass("...") { priority("HIGH") } }— per-rule severity override; per-source-set disable; failure-threshold tuning by priority. - Internal infrastructure: "Our internal Nebula standard Gradle wrapper and plugin suite automatically enable the ArchRules runner on every project, and provides a custom reporter which sends the report data to our Internal Developer Portal on every main-branch CI build."
- Annotation precedent / OSS context: ArchUnit is "a popular OSS library (3.5k stars, 84 contributors) used to enforce 'architectural' code rules as part of a JUnit suite. It is used internally by Gradle, Spring, and is provided as part of the Spring Modulith platform."
- Future direction: auto-remediation via OpenRewrite (deterministic) + LLMs (non-deterministic); IDE-inspection surfacing.
- Authors: John Burns (github.com/wakingrufus) and Emily Yuan (linkedin.com/in/emilyyuan03) of the JVM Ecosystem team within Java Platform.
Systems extracted¶
New wiki pages:
- systems/archunit — OSS bytecode-static-analysis library (3.5k stars, 84 contributors as of post; consumed by Gradle, Spring, Spring Modulith). Builder-API rules over an ASM-derived class graph. Canonical primitive on top of which Nebula ArchRules is built.
- systems/nebula-archrules — Netflix's pair of Gradle plugins (Library + Runner) that lifts ArchUnit from per-repo JUnit suite to fleet-wide rule catalog. 358 rules × 5,000 repos × ~1M issues at post date. Bundled-rules-auto-scope mechanic + standalone rule libraries + ServiceLoader discovery + classpath-isolated execution + JSON/console reports.
- systems/asm-bytecode-toolkit — JVM bytecode framework (
asm.ow2.io) that ArchUnit uses for class-graph construction. Underpins the bytecode-vs-AST tradeoff. - systems/gradle-module-metadata — Gradle's
.module-file metadata format that supports classifier-with-usage-attribute variants. Load-bearing for Nebula ArchRules' publication mechanism — consumers must use Module Metadata for dependency resolution. - systems/openrewrite — declarative refactoring tool (
docs.openrewrite.org) for deterministic source-code transformations, named by Netflix as the deterministic auto-remediation candidate paired with ArchRules failure signals. - systems/pmd-static-analyzer — OSS Java static analyzer using XPath over AST, named in the post as the comparison foil illustrating why Netflix chose ArchUnit (XPath-CDATA rules vs ArchUnit's type-safe builder API; per-file AST vs class-graph retention).
Extended (cross-link added):
- systems/spring-modulith — mentioned as ArchUnit consumer; cross-link only.
Concepts extracted¶
New wiki pages:
- concepts/architectural-fitness-function — the canonical framing of architecture as machine-executable test, originating with Ford/Parsons/Kua (Building Evolutionary Architectures) and instantiated by ArchUnit. The wiki canonical naming + connection to the lifecycle-annotation-detection use case.
- concepts/bytecode-vs-ast-static-analysis — the structural tradeoff between source-AST-based and compiled-bytecode-based static analysis on the JVM. Bytecode = language-agnostic + sugar-immune + class-graph-aware; AST = source-fidelity + per-file. ArchUnit/ASM vs PMD/Spotbugs is the canonical comparison.
- concepts/polyrepo-shared-build-logic — the cross-cutting concern Netflix's Java Platform is built around: how do tens of thousands of repos share build conventions, dependency policies, and rule enforcement without monorepo-style co-evolution? Nebula plugins are the substrate.
- concepts/breaking-change-detection-via-static-analysis — the use case framing: instead of "deprecate-and-pray," ship a rule that detects calls to deprecated/internal APIs, and read the dashboard to know when it's safe to remove.
Patterns extracted¶
New wiki pages:
- patterns/bundled-rules-auto-scoped-to-library-consumers — co-locate rules with the library they govern; consumer's rule-runner auto-detects them via classpath inspection + ServiceLoader and runs them only on source sets where the library is consumed. Zero-configuration scoping is the central novelty.
- patterns/centralized-fleet-wide-rule-catalog — the operational shape: thousands of repos, hundreds of rules, one canonical plugin enabled by default in the org's Gradle wrapper, dashboard-aggregated reports per main-branch CI build. Generalizes to any polyrepo org needing cross-cutting rule enforcement.
- patterns/build-time-tech-debt-detection — using static-analysis rules in CI not just to fail builds but to measure and prioritize tech debt at fleet scale. ArchRules' 358-rule × 5,000-repo dashboard is the canonical instance.
- patterns/static-analysis-as-cross-repo-impact-discovery — using rules + dashboard not as build-failures but as API-usage-discovery instruments: library author writes a rule against their own deprecated API, reads the report of which downstream repos still call it, decides when to remove. Inverts the deprecate-and-pray dynamic.
Extended (existing):
- patterns/api-stability-annotations — extend with the Netflix
@Public/@Experimental/ implicit-internal taxonomy as the JVM-bytecode + ArchUnit-enforcement instance, paired with the Kotlin-validator instance from Airbnb Viaduct.
Caveats¶
- Architecture-and-discipline post, not measurement post. Operational numbers are 358 rules + 5,000 repos + ~1M issues + ~1,000 high-priority + tens-of-thousands-of-Java-repos. No: per-rule execution latency, plugin-evaluation overhead per build, dashboard-ingest throughput, rule-authorship time-to-deploy, false-positive rate, fix-rate after detection, year-over-year trend.
- Polyrepo number is approximate. "Tens of thousands of Java repositories" is order-of-magnitude. The 5,000-repo number is "running these rules", which is a subset (probably the active Java services + libraries; probably excludes archived / experimental repos).
- Bundled-rule auto-scoping mechanic depends on Gradle internals. The Runner plugin's per-source-set classpath construction + ServiceLoader discovery is Gradle-specific; cannot be ported to Maven without re-building the dependency-resolution + plugin-execution model. Implicit lock-in to Gradle Module Metadata adoption.
- The PMD comparison is partially polemical. The post sets up PMD's XPath-rule-as-CDATA as a strawman; PMD does have a Java rule API (not just XPath). The fundamental tradeoff (AST vs bytecode, per-file vs class-graph) is real, but the rule-authorship-ergonomics gap is narrower than the post implies. No quantitative comparison is offered.
- Auto-remediation via OpenRewrite + LLMs is forward-looking. The "Going forward" framing is explicit — "we will be exploring," "we will explore" — not a deployed system. Treat as a roadmap, not a current capability.
- OSS rule libraries are presented as exemplars, not claimed as battle-tested. "While the most powerful way to use ArchRules is for you to write your own rules, we have built some OSS rule libraries that anyone is free to use, or reference as examples." The OSS libraries are reference implementations, not Netflix's primary rule catalog (which is internal).
- No discussion of rule failure as adoption ceiling. A bundled rule can fail a downstream consumer's build. The post mentions failure-threshold tuning but doesn't discuss the social dynamics: when a library author ships a new high-priority rule that breaks 30 downstream repos, who pays the cleanup cost? The post implicitly assumes the dashboard-as-warning model; build-failing rules require organizational coordination not addressed.
- No comparison to monorepo alternatives. The post takes the polyrepo strategy as given and doesn't discuss "why didn't you adopt a monorepo where deprecated-API removal is a single PR?" — the answer is presumably history + autonomy + tooling sunk cost, but it's not discussed.
- JVM-only. ArchUnit is bytecode-only; the framing doesn't translate to Netflix's polyglot fleet (Python services, Node services, Go services). The post is scoped to "any JVM+Gradle engineering organization," which is honest but limits the conclusions' generality.
Source¶
- Original: https://netflixtechblog.com/scaling-archunit-with-nebula-archrules-b4642c464c5a?source=rss----2615bd06b42e---4
- Raw markdown:
raw/netflix/2026-05-08-scaling-archunit-with-nebula-archrules-bb0087a8.md
Related¶
- companies/netflix — Netflix TechBlog (Tier 1).
- sources/2026-05-13-airbnb-viaduct-1-0-and-the-future-of-airbnbs-data-mesh — sibling instance of patterns/api-stability-annotations in the Kotlin/Airbnb ecosystem with systems/kotlin-binary-compatibility-validator as the analogous enforcement substrate. Same problem (machine-checkable API contract for OSS / polyrepo consumers); different toolchain (Kotlin/KSP vs JVM/ArchUnit).
- sources/2026-05-04-netflix-democratizing-machine-learning-building-the-model-lifecycle-graph — the previous Netflix Java Platform / AI Platform post on MDS. Sibling on the Netflix-paved-road axis: MDS for ML metadata, Nebula ArchRules for JVM library lifecycle. Both surface to operators (ML practitioners; library authors) via internal portals.
- patterns/api-stability-annotations — the canonical pattern this post extends.
- patterns/bundled-rules-auto-scoped-to-library-consumers — the central novel pattern.
- patterns/centralized-fleet-wide-rule-catalog — the operational pattern.
- concepts/architectural-fitness-function — the foundational framing.
- concepts/bytecode-vs-ast-static-analysis — the technical tradeoff.
- systems/archunit — the OSS primitive.
- systems/nebula-archrules — the Netflix system.