|RFC-0239: Platform Versioning in Practice
Conceptual model and guarantees for compatability on Fuchsia.
|Date submitted (year-month-day)
|Date reviewed (year-month-day)
- laying out a conceptual model for how API levels and ABI revisions are used in practice on Fuchsia,
- providing API and ABI compatibility guarantees to our partners, stated in terms of that conceptual model, and
- outlining how we plan to meet those compatibility guarantees.
- Each Fuchsia milestone has an accompanying API level. API level 15 can be thought of as "the 15th edition of the Fuchsia platform surface, which was first supported by F15."
- Each Fuchsia release supports multiple API levels. For example, F15 milestone releases supported API levels 11, 12, 13, 14, and 15.
- End-developers select a single target API level when building a component. A component targeting API level 13 will see the Fuchsia platform surface as it was in API level 13, and will run on any Fuchsia system that supports API level 13.
- The Fuchsia kernel and platform components support multiple API levels by implementing them all simultaneously.
As discussed in RFC-0227, the primary components of a Fuchsia platform release are:
- The Integrator Development Kit (IDK), a small set of libraries, packages, and tools required to build and run components that target Fuchsia, and
- The Operating System (OS) binaries, including the kernel, bootloaders, packages, tools, and other ingredients necessary to configure and assemble Fuchsia product bundles.
The IDK is the build-system-agnostic precursor to the Fuchsia SDKs, describing the API elements (e.g., protocols, methods, fields, arguments, libraries) that make up the Fuchsia platform surface and allowing end-developers to write components that use those API elements. Some of the interfaces in the IDK are implemented by OS binaries like the kernel and platform components, and others are implemented by external components (for instance, applications or drivers), with the expectation that the OS binaries will call them.
The Fuchsia platform surface is under constant development, and the OS binaries are constantly updated to match. These changes quickly1 flow downstream to end-developers and product owners. Before the introduction of API levels, the IDK only contained a single description of the platform surface. An IDK built on Monday would describe a different version of the Fuchsia platform surface from the one implemented by an OS built later that week on Friday. This used to cause issues: if a FIDL method and its implementation were deleted on Wednesday, an external component built with Monday's IDK may try to call that method, only to be told by Friday's OS that no such method exists.
Naively, we could try to forbid mismatches between the IDK and OS versions. We
could say that the OS will only run components built using the IDK from the
exact same Fuchsia release (that is, OS version
2.20210213.2.5 would only run
components built with IDK version
2.20210213.2.5). However, this is
infeasible - we can't require end-developers to constantly update and rebuild
their components as we release new versions of Fuchsia. Thus, in general, the
IDK that builds external components and the OS that runs them may come from
different Fuchsia releases - possibly even from different milestones.
This prompts the question: under what circumstances will a component built with an IDK from one release be able to successfully run on the OS from another?
RFC-0002 introduced API levels and ABI revisions as tools to help answer that question, but wasn't very specific about how they ought to be used in practice, and provided no concrete compatibility guarantees. This RFC fills those gaps by showing how API levels and ABI revisions are used in practice, establishing a compatibility guarantee policy, and briefly discussing strategies for complying with that policy.
The model and policies in this RFC have been built out incrementally over the past few years, but have not thus far been encoded in any RFC. This particular proposal was arrived at through many docs and conversations, largely taking place between members of the Platform Versioning Working Group.
When describing compatibility between a component that exposes an API and another component that uses that API, we assume that one side of the interaction is an external component built out-of-tree (that is, outside of the Fuchsia platform build, via the IDK), and the other side of the interaction is an OS binary, like the kernel or a Fuchsia platform component, built in-tree (that is, built within the Fuchsia platform's GN build graph). Defining versioning practices and compatibility for interactions between external components is out of scope and left for future work.
This section will lay out the conceptual model behind Fuchsia Platform Versioning. Many of the concepts described here were previously introduced in RFC-0002, but are presented here with more concrete detail and restrictions.
The text of this "Design" section should be taken as informative, rather than normative. The Policy changes section below will repeat any parts of this text that represent changes from existing policy.
The Fuchsia platform surface is constantly changing, and new Fuchsia releases are constantly being created - multiple times a day. Rather than reasoning about each Fuchsia releases's compatibility with the Fuchsia platform surface from each other release, which would be overwhelming, we think about a release's compatibility with a limited number of API levels.
Think of API levels as "versions", "editions", or "snapshots" of the APIs that make up the Fuchsia platform surface. API level 10 is "the 10th edition of the Fuchsia API surface." API levels are synchronized with milestones, so API level 10 can also be thought of as, "the latest version of the Fuchsia platform surface when F10 was published."
You can use API levels to make statements like, "the
fuchsia.lightsensor.LightSensorData FIDL table had three fields in API level
correlated_color_temperature), but two more
were added in API level 11 (
is_calibrated). Since API level 11,
it has had five fields, but we may remove some or add more in API level 16."
Like editions of a textbook, API levels are immutable. The contents of a textbook may change over time, but the contents of the 2nd edition of a textbook will never change once the 2nd edition has been published. This means that any two Fuchsia releases that know about a given API level will agree on the set of API elements that make up that API level.
The Fuchsia platform surface is not limited to FIDL - it also comprises syscalls, C++ libraries and the methods therein, persistent file formats, and more. Conceptually, these are all versioned by API level, but in practice, versioning support for FIDL libraries is much farther along than for other aspects of the Fuchsia platform surface.
Targeting an API level
End-developers select a single target API level when building a component. If a developer targets API level 11, their component will have access to exactly the API elements that were available in API level 11. If a method was added in API level 12, the component will not see it in their generated bindings. Conversely, if a method from API level 11 was removed in API level 12, the component will continue to be able to use it.
As part of building a component, the tools in the IDK embed the target API level's associated ABI revision in the component's package. This is often called the component's "ABI revision stamp".
The ABI revision stamp allows us to guarantee that the runtime behavior observed by the component will match the behavior specified by its target API level. Currently, the ABI revision stamp is used very coarsely: if the OS supports that ABI revision's corresponding API level, the component is allowed to run. Otherwise (for instance, if an external component targets API level 11 but the OS binaries no longer implement methods that were part of API level 11), Component Manager will prevent it from starting up.
Releases and Phases
Each Fuchsia release supports multiple API levels, and assigns each API level
a phase. You can find the API levels in a Fuchsia release by downloading
its IDK and looking in the
For instance, a hypothetical Fuchsia version
20.20240203.2.1 might include:
- API levels 17, 18, and 19 in the supported phase. This means IDK version
20.20240203.2.1can build components targeting any of these API levels, and Fuchsia version
20.20240203.2.1can run components that target any of these API levels. Most end-developers should target supported API levels most of the time.
- API levels 15 and 16 in the sunset phase. Fuchsia will still run components that target sunset API levels, but the IDK will no longer support targeting them when building components.
version_history.json would also list API levels 14 and
earlier in the retired phase. Fuchsia does not build or run components
targeting retired API levels, but they are retained for posterity.
API levels are published in the supported phase, shortly before their
corresponding milestone branch cut. The hypothetical
release above comes from before API level 20 was published, so API level 20 is
conspicuously absent. Shortly before the F20 branch cut, API level 20 will be
published, and subsequent releases (say,
20.20240301.4.1) will list API level
20 as "supported". In particular, all milestone releases include their
corresponding API level in the supported phase.
When we choose to drop support for an API level, we first move it to the sunset phase. This creates a back-stop of sorts - existing binaries targeting that API level continue to run, but no new binaries targeting that API level will be created (at least - not with new releases of the IDK). Once all existing binaries targeting a given API level have been phased out, we can retire it.
API levels are not sunset or retired on any particular schedule. Rather, we sunset an API level once our partners are no longer building against it, and we retire an API level once our partners no longer want to run binaries that target it. Someday, as the number of partners grows and we are less closely attuned to their individual needs, we will need a more formal policy, but this will do for now.
In addition to the API levels discussed above, there are two special mutable
Unlike other API levels, the contents of
HEAD can be changed in
arbitrary ways from release to release. API elements can be added, modified,
replaced, deprecated, and/or removed. In principle,
could look completely different from
20.20240203.2.1, though in
practice, changes will be incremental.
As described in RFC-0083
HEAD represents the bleeding edge
of development, intended for use by in-tree clients (for example, other
platform components or integration tests), where there is no expectation of
stability. API elements introduced in
HEAD may not even be functional; for
example, a method may have been added to a protocol at
HEAD, but the protocol
server might not actually implement that method yet.
NEXT represents a "draft" version of the next numbered API level. Shortly
before each milestone branch cut, we publish the contents of
NEXT as the new
API level. Mechanically, we do this by creating a changelist replacing
NEXT in FIDL annotations and C preprocessor macros with the
specific number of the next milestone branch. API elements in
NEXT should be
functional, and are unlikey to change before the next API level is published,
though changes are still possible.
In the language of RFC-0083,
HEAD is newer than
NEXT, meaning that all API
NEXT are also included in
End-developers may target
NEXT, but doing so voids the
API and ABI
compatibility guarantees. See the sections describing those guarantees for more
about the implications of targeting
HEAD from out-of-tree.
This section lists the specific policy changes proposed by this RFC.
This RFC makes the following changes to the platform versioning model:
- Numbered API levels are now immutable. This property is already true in
practice for all API levels except the special "in-development" API level.
This RFC effectively deprecates the "in-development" API level, in favor of
- Introduce API level phases. "Supported" and "retired" already exist in practice, though the latter is currently called "unsupported". This RFC proposes renaming it, and adding "sunset".
NEXT. This is another special API level, like
HEADis "newer than"
NEXT. This replaces the current practice of designating the newest API level the "in-development" API level.
- Allow backwards-incompatible changes to
NEXT. The current policy, enforced by tooling but not established in any RFC, is that only backwards-compatible changes may be made to the in-development API level. This RFC removes that restriction, and will not apply it to
NEXT. See below for more discussion.
- Couple API levels to milestones. API level
Nwill be created shortly before the F
Nmilestone branch cut, based on the contents of
NEXT. This has been true in practice, but has not been ratified in any RFC.
- API levels move through phases as quickly as our partners allow. We will sunset a supported API level as soon as none of our partners need to build binaries targeting it, and no sooner. We will retire a sunset API level as soon as none of our partners want to run any binaries targeting it on new Fuchsia releases, and no sooner.
API compatibility guarantee
Fuchsia now makes the following API (that is, build-time) compatibility guarantee for numbered API levels (subject to caveats below):
If an end-developer can successfully build their component targeting a numbered API level
Nusing some release of the IDK, they will be able to successfully build that same source code using any release of the IDK with
Nin the supported phase.
In other words, an end-developer can fearlessly upgrade or roll back their IDK without breaking their build, until their chosen target API level is sunset. If updating to a different IDK without changing their target API level causes their build to break (for example, if an API element they were using no longer exists), that will be treated as a Fuchsia platform bug.
On the other hand, there is no API compatibility guarantee for the mutable
HEAD. When targeting
end-developer may find that updating their IDK causes their code to no longer
Practically speaking, our current partners work in close coordination with the
Fuchsia team, and we will avoid breaking the builds of end-developers that
NEXT on a "best effort" basis. We will make little-to-no effort to
avoid breaking the builds of end-developers targeting
ABI compatibility guarantee
Fuchsia now makes the following ABI (that is, run-time) compatibility guarantee for numbered API levels (subject to caveats below):
A component built targeting a numbered API level
Nwill successfully run on any release of Fuchsia with
Nin the supported or sunset phase, and will not see any objectionable behavioral changes between releases.
In other words, an end-developer does not need to change or recompile their code in order to run on newer versions of Fuchsia, up until the point when their chosen target API level is retired. If the platform behaves differently in a way that interferes with their component's functionality on a different version of Fuchsia, that will be treated as a Fuchsia platform bug.
On the other hand, there is no ABI compatibility guarantee for components
that target the mutable pseudo-API levels
HEAD. A component built
HEAD will be permitted to run if and only if the version
of Fuchsia on which it is running exactly matches the version of the IDK that
built it. For example, a component targeting
NEXT built with IDK version
16.20231103.1.1 will run on Fuchsia version
16.20231103.1.1, but will not
(by default) run on a device with Fuchsia version
In most circumstances, ensuring the version of the IDK that builds a component
exactly matches the version of the OS on which it runs is not feasible.
Noteworthy exceptions are integration testing and local experimentation.
Out-of-tree repositories generally do have control over which version of the
IDK they use for compilation and which version of the OS they use for testing.
If they want to be able to test functionality in
HEAD, they should
keep the two in sync.
Unsupported configurations are forbidden
component_manager refuses to launch components whose ABI revision
stamp indicates that they are not covered by the ABI compatibility guarantee.
- A given release of Fuchsia will only run a component that targets API level
Nis either supported or sunset as of that release.
- A given release of Fuchsia will only run a component that targets
HEADif it was built using the IDK from the exact same release.
Likewise, product assembly will refuse to assemble products with components that are not compatible with the platform.
Product owners will have the ability to selectively disable these checks. For
instance, they will be able to allowlist an individual component to target
HEAD, even if the IDK that built it came from a different Fuchsia
release. Product owners disable these checks at their own risk.
Implementing these checks will require some changes to ABI revision stamps. A full design of these changes is beyond the scope of this RFC, but in brief:
- As is the case today, each API level will be assigned a unique ABI revision when it is published. Components targeting a numbered API level will be stamped with that API level's corresponding ABI revision.
- Additionally, each release will be assigned a unique ABI revision.
NEXTwill be stamped with this per-release ABI revision.
The OS binaries in a given release will include a list of the ABI revisions for all API levels in the supported or sunset phases, as well as the ABI revision for that release. By default, only components stamped with one of these ABI revisions will be allowed to run.
Note that the "one ABI revision per API level, plus one more for
arrangement is not necessarily the last word on this design. ABI revisions are
how we encode our ABI compatibility guarantees in a machine-readable way, so if
our guarantees become more nuanced, so too must our allocation of ABI
For example, say we designate part of our API surface as having "Long Term Support (LTS)", and continue supporting the LTS APIs within an API level for longer than other APIs. A hypothetical F30 release may support the LTS APIs from API level 25, but not the non-LTS APIs. That release would be able to run components that targeted API level 25 using only LTS APIs, but could not run components targeting API level 25 that used non-LTS APIs. Thus, these two kinds of components would need to be stamped with different ABI revisions, which we could accomplish by giving each (API Level, LTS status) pair its own ABI revision.
We have ideas along these lines, and if we choose to pursue them, they will be described in a subsequent RFC.
Platform components support all supported API levels simultaneously
Since we currently have no mechanism by which platform components can alter their behavior based on the ABI revision stamp of the component on the other side of the connection, supporting multiple API levels means supporting multiple API levels simultaneously. The behavior of the platform component must meet the specifications of each API level in the supported or sunset phase.
More precisely, a platform component must not assume anything about the API level targeted by an external component, except that it is targeting one of the supported or sunset API levels.
As a simple example, if a method
Foo was removed from a FIDL protocol in API
level 15 (that is,
Foo was annotated with
but API level 14 is still supported, platform components that implement the
protocol server in question must continue to implement
Foo until all API
levels that include it have been retired.
Conversely, if that FIDL protocol server was implemented by an out-of-tree
driver, a platform component communicating with that driver must work
regardless of whether the driver implements
Foo or not.
This strategy constrains not only the implementation of platform components, but also kinds of modifications we can make to the platform surface. Theoretically, APIs may change arbitrarily from API level to API level, but practically speaking, some changes (for instance, reusing a FIDL protocol method ordinal) leave us unable to correctly communicate with external components targeting different API levels without further information.
We may develop different strategies for supporting multiple API levels, but that's an area for future work.
Both the API and ABI compatibility guarantees are subject to a few caveats.
The meaning of "guarantee"
"Guaranteeing" compatibility does not mean that Fuchsia will never break our compatibility promises - to err is human. It means that when we break compatibility according to the terms above, we consider that a bug. We will take responsibility for remedying the situation internally, without requiring any action from the end-developer, beyond reporting the bug.
Voiding the guarantee
The API and ABI guarantees do not apply if end-developers are "doing something weird." This includes, but is not limited to:
- modifying files in their copy of the IDK,
- accessing members in internal namespaces,
- manually synthesizing data that is intended to be built by libraries in the IDK,
- linking shared libraries built by different IDKs into a single binary,
As a notable counter-example, depending on buggy behavior in Fuchsia does not count as "doing something weird." In some unfortunate circumstances, fixing a bug may count as an "objectionable behavioral change." See Objectionable changes for more discussion.
A full accounting of prohibited behaviors is out of scope for this RFC. To paraphrase United States Supreme Court Justice Potter Stewart in Jacobellis v. Ohio, "I shall not today attempt further to define ['doing something weird'], and perhaps I could never succeed in intelligibly doing so. But I know it when I see it."
The ABI compatibility guarantee promises that components will not see "objectionable behavioral changes" between Fuchsia releases that support the component's target API level. The definition of "objectionable" is clearly subjective, and changes that are desirable to some may be objectionable to others (relevant xkcd). Ultimately, reports of objectionable behavior changes will need to be addressed on a case-by-case basis.
Security and privacy
If certain parts of the Fuchsia platform surface are found, in retrospect, to have significant security or privacy issues, we may selectively drop support for the offending functionality, as necessary to meet our security and privacy goals.
Maintaining backwards compatibility with older API levels has a performance cost. If nothing else, backwards-compatible platform binaries will have to be larger, as more logic will be retained. To some extent, this is inevitable - however, the fact that API levels are retired atomically potentially exacerbates the problem.
The whole of this RFC concerns compatibility, both backward and forward.
Testing to ensure API and ABI compatibility is another large topic.
The Compatibility Tests for Fuchsia (CTF) effort aims to provide test coverage to confirm we meet our ABI compatibility guarantee. Coverage is currently low, but that is being actively worked on.
Currently, we are unable to write tests to verify our API compatibility
guarantee; all of our in-tree tests are built against the
LEGACY API level,
so we cannot replicate the experience of an out-of-tree customer targeting a
numbered API level. This will need to be addressed, but doing so should not
block acceptance of this RFC.
Upon ratification of this RFC, its text will be reused as the basis for multiple documents targeted at Fuchsia platform developers:
- The compatibility policies themselves.
- A guide to API levels and compatibility.
- The beginnings of a document outlining usage guidelines that void our compatibility guarantees (that is, "doing something weird"), perhaps modeled after Abseil's compatibility guidelines document.
Documentation will also need to be updated to reflect the renaming of existing concepts.
Drawbacks, alternatives, and unknowns
Drawback: API levels are atomic
As laid out by RFC-0002, API levels cover the entire Fuchsia platform surface. Therefore, changes to an API level's phase (e.g., retiring an API level) affect the whole API level; we can't retire just a subset of the functionality. Likewise, an end-developer targeting a given API level targets that API level for the whole Fuchsia platform surface; they can't, for instance, target API level 13 for most APIs but API level 16 for one particular API.
Future extensions to the Platform Versioning model may allow for meaningful subdivisions, but dependencies between the various libraries in the IDK make this challenging.
Alternative: No sunset phase
An earlier draft of this RFC omitted the sunset phase, leaving it as "potential future work". The sunset phase is a very reasonable extension to the model, and it was brought up frequently enough in discussions that proactively including it seems appropriate.
Alternative: Other possible pseudo-API levels
As described above,
HEAD has multiple potential use cases: communication
between in-tree components, APIs exposed to integration tests, experimental
APIs, etc. Should these actually be represented by distinct pseudo-API-levels?
This RFC proposes starting with just
HEAD, but leaving gaps
between them when assigning numerical values. That way, more pseudo-API-levels
can be added if we find use cases for them.
Alternative: Backwards-compatible in-development API level
Fuchsia's de-facto versioning policies current say that only backwards
compatible changes may be made to the in-development API level. This RFC rolls
back that policy, and as a result, makes no guarantee about compatibility
between an IDK and OS binaries from different releases when the end-developer
If we maintained this policy of backwards compatibility within the
in-development API level (or
NEXT), we could do better: we could guarantee
that an IDK and OS binaries would be compatible as long as the OS binaries come
from a newer release than the IDK. We relied on this property until
relatively recently, when we moved downstream repositories to target supported
This has the benefit that end-developers can safely start depending on new Fuchsia functionality as soon as it is added to the in-development API level - they don't need to wait for that API level to get published.
However, it introduces significant complexity to the system:
- Fuchsia platform developers must maintain backwards compatibility not only with previous API levels (the API signatures of which are encoded in FIDL and header files themselves), but also with previous versions of the in-development API level (which are not). The only way to see these previous versions is to examine git history, which in practice does not happen.
- Since we need to be able to remove functionality from the platform, there needs to be some API level that supports backwards-incompatible changes. This means Fuchsia platform developers must make backwards-incompatible changes to the numbered API level after the in-development API level. Thus, there are effectively two in-development API levels, and Fuchsia platform developers need to reason about both.
- It makes ABI revisions significantly more complex. To support backwards-compatible changes within the in-development API level, we would need to retain the per-release ABI revisions (of which several are created every day) indefinitely. The OS binaries would need to support the ABI revisions for each release during which one of its supported API levels was the in-development API level, as well as all the ABI revisions for releases from the current milestone so-far. At our current release cadence, this would add up to around 168 ABI stamps per API level, and as of this writing, a release of Fuchsia would need to support more than 800 ABI stamps. While the memory costs of such a solution are unlikely to be significant, the complexity of maintaining compatibility with hundreds, thousands, or tens of thousands of distinct ABIs does not seem worth it at this time, especially since none of our customers currently use the in-development API level.
This RFC doesn't preclude the possibility of offering additional compatibility guarantees in the future.
Alternative: Decouple API levels from milestones
This RFC explicitly couples API levels to milestone numbers. This is not strictly necessary - notably, Android decouples the two. The Android project uses this capability to release API changes alongside minor versions of the OS. For example, API Level 24 corresponds to Android 7.0, and API level 25 corresponds to Android 7.1.
In my research, I have not found another example of a platform that chose to follow this path. Other platforms I found describe availability of API elements in terms of release versions, for example, "Available since Chrome 88" or "Deprecated in iOS 6.1."
macOS and iOS API levels are coupled to their versioning schemes, but both allow API changes at minor revisions, unlike Fuchsia. However, this appears to be largely motivated by a desire to couple their releases' major version with the calendar year, while still allowing API updates to ship multiple times a year.
Fuchsia's milestones are more closely modeled after Chrome's, incrementing the major version frequently, and leaving the minor version unused. Should we wish to stabilize API levels more frequently, we can do so by incrementing Fuchsia's milestone number more frequently.
Alternative: Numbered "in-development" API level
An earlier draft of this RFC stayed closer to the Platform Versioning model
that is currently implemented. In that model,
NEXT does not exist, and
instead, the largest numbered API level is mutable and called the
"in-development" API level.
Mechanically, there are only a few small differences between the models:
- Currently, Fuchsia platform developers chose a particular API level at which
they want their feature to launch. After this RFC is implemented, they will
make production-bound API changes to
NEXT, which will automatically be published in the next numbered API level.
- Currently, end-developers targeting the in-development API level
automatically transition to targeting the supported version of that API
level once it is published. After this RFC is implemented, they will
Neither of these differences seems overwhelmingly positive or negative.
The main benefit of
NEXT over the in-development API level is ease of
communication. Earlier drafts of this RFC resorted to verbal gymnastics to
express the compatibility guarantees: "A component built targeting supported
N will successfully run on any release of Fuchsia that supports API
N in the supported phase." In the current proposal, all parties will
agree about the contents of a given API level, with no qualifiers necessary.
Alternative: Don't remove things
We could make Platform Versioning simpler by disallowing backwards-incompatible changes altogether. Each API level would support all functionality from previous ones, and the "platform supports all API levels" property would come "for free" by virtue of supporting the latest API level.
Many platforms including Windows and Android have largely taken this approach, but as a result they are unable to clean up cruft from older revisions of their API surface.
Though Fuchsia platform engineers will continue to need to support old API elements as long as they appear in a supported or sunset API level, end-developers will only need to worry about the contents of their chosen target API level, and won't need to see the cruft.
Furthermore, though "never removing things" may make versioning simpler, it by no means solves the problem entirely. End-developers still need to reason about which API level to target, based on the oldest Fuchsia platform version in their intended install base.
Most of our downstream partners currently depend on canary releases of Fuchsia, which are produced multiple times per day. In the future, more of our partners will consume the IDK and OS binaries from milestone release branches, but for now, we value the immediate feedback this arrangement allows. ↩