4.4 Production Rule Surface
A rule that compiles cleanly for one author's BUILD.bazel is not the same artifact as a rule another team will load, parameterize, run, and cache for the next two years. Section 4.2 Custom Rules, Providers & Actions gave you a working rule: typed attrs, an implementation function, declared actions, providers, and a DefaultInfo. This section asks what breaks when that rule becomes someone else's dependency.
The failures are rarely dramatic. A new attr name disagrees with srcs / deps / data conventions and downstream BUILD files become noisy. A test rule inherits HOME from the developer's shell and passes only on some machines. An action sets "no-sandbox" because the tool reads an undeclared file, and now every consumer inherits that cache decision. A worker keeps warm state across requests. A runfiles migration forgets the legacy split and files vanish through data but not deps. None of these are bugs in the implementation function. They are bugs in the rule's public surface.
Four Failure Classes
The first class is API drift. 4.4.1 Rule Public API Design names the surface that BUILD authors, downstream rules, tools, and docs depend on: attr names and schemas, provider symbols and fields, default outputs, named output groups, public .bzl entry points, documentation, and evolution policy. It builds on 4.2.1 Rule Function and 4.2.7 Custom Provider Declaration, but changes the question from "does this run?" to "can hundreds of targets keep calling this?"
The second class is runtime ambiguity. 4.4.2 RunEnvironmentInfo covers the explicit launch-environment contract for bazel run and bazel test: which variables the rule defines, which it allows the shell to inject, and why tests should be stricter than ordinary runs. 4.4.5 Legacy Runfiles Split sits next to it because runfiles are the other half of the runtime contract. New rules return DefaultInfo(runfiles = ...), but legacy data_runfiles / default_runfiles still matter when debugging older rulesets or attribute-path surprises.
The third class is action leakage. 4.4.3 Action Execution Contract reframes 4.2.2 Actions as a production envelope: tools as declared inputs, structured Args instead of strings, deliberate env, stable mnemonic and progress_message, and execution_requirements only for real per-action needs. 4.4.6 Advanced Action Parameters is the optional refinement after that envelope is correct: unused_inputs_list lets a tool report which declared inputs were cache-irrelevant after execution, and resource_set lets a rule estimate local CPU and memory when defaults mislead the scheduler.
The fourth class is stateful speed. 4.4.4 Persistent Workers for Rule Authors is not a faster strategy switch. It is a stricter contract between Bazel, the rule, and a long-lived tool process. The rule must expose a stable executable in the execution configuration. The tool must speak WorkRequest / WorkResponse, keep one-shot mode working, and prove that warm state cannot leak across requests. Add it deliberately, when startup cost or process reuse is real. Do not bolt it on as a speculative perf knob.
What Counts As Breaking
This section widens the meaning of "breaking change." Renaming a public attr is a migration. Removing a provider field is a migration. Changing a mnemonic can break --strategy=Mnemonic=... rules and aquery scripts in downstream .bazelrc files. Switching use_default_shell_env from False to True quietly turns ambient host state into a hidden action input across the cache, which is the hermeticity boundary from 2.3 Hermeticity & Sandboxing. Adding an execution_requirements tag changes how every consumer's sandbox or remote cache treats the action.
The shared posture is conservative: prefer small, declared, documented surfaces over large implicit ones. Treat tools as inputs, not background state. Prefer additive changes such as new optional attrs, new provider fields, or new output groups over renames. Keep escape hatches such as inherited_environment, use_default_shell_env, sandbox tags, and worker mode visible and intentional.
Before You Share The Rule
If you have a rule from 4.2 Custom Rules, Providers & Actions and another team is about to depend on it, start with 4.4.1 Rule Public API Design. Then choose by the next risk: 4.4.2 RunEnvironmentInfo and 4.4.5 Legacy Runfiles Split for launched targets, 4.4.3 Action Execution Contract and 4.4.6 Advanced Action Parameters for nontrivial actions, and 4.4.4 Persistent Workers for Rule Authors only when repeated, slow-starting actions justify a persistent process. 2.5 Execution Strategies introduces worker strategy vocabulary, while 6.4 Execution Modes and Persistent Workers covers worker pool sizing. Here the rule-author contract keeps state reuse from becoming nondeterminism.
Tests and documentation for these surfaces continue in 4.5 Rule Testing & Documentation. If a contract from this section is worth stating, it is worth covering with an analysis test, an integration test, or an example workspace before the next consumer arrives.
Classify: A rule exposes attrs, providers, outputs and runfiles, and registered actions. Which parts belong to its production contract, and how can you tell whether a proposed change is breaking?
Reveal
Treat an entry as contract when downstream BUILD files write it, downstream rules read it, launched programs rely on it, or Bazel and surrounding tooling rely on its action behavior. If changing it in a minor release would force a consumer to edit declarations, provider reads, launch assumptions, strategy settings, or action queries, it needs a migration or compatibility path. Details with no observable consumer can remain implementation.
The mini-ruleset makes the public/private boundary inspectable in its defs.bzl facade. Run the whole project to exercise that surface as a consumer would.
A production rule has four public surfaces: a stable API, a launch-environment and runfiles contract for users, an action execution contract for Bazel's scheduler, and an optional worker opt-in. Section 4.2 Custom Rules, Providers & Actions taught how to make a rule run. This section makes it safe for other people to depend on it.
Sections in this chapter · 6
Designing stable attrs, providers, outputs, and documentation boundaries for reusable rules.
Declaring environment variables for test and run rules
Designing the inputs, tools, environment, mnemonic, and scheduling contract of a production action.
When a custom rule should support worker mode and what the tool protocol must provide.
Legacy runfiles split and migration to the unified runfiles parameter
unused_inputs_list and resource_set for production-scale action behavior