Crosswake. RuntimeLine. RebuildPolicy
(crosswake v0.1.2)
View Source
Policy contract deriving OTA-safe vs. rebuild-required classification from the
existing native_runtime_version compatibility axis.
Derivation principle
This module does NOT maintain a full static registry of rebuild truth — it derives classification from the data that already owns that truth:
Capability-axis change classes (
capability_family_add,bridge_schema_change,permission_add,entitlement_add,push_capability_change,url_scheme_change) derive their verdict fromCapability.rebuild, which is the authoritative field on the capability record. Never classify by change-class label alone; always key offCapability.rebuild. This is the footgun the Expo EAS / CodePush "it's-just-JS" OTA break demonstrates: label-only classification silently misses native-incompatible changes.System-level change classes (
sdk_floor_bump,privacy_manifest_entry) have no capability owner; they map via the locked@system_rebuild_classesclosed set. New system classes go through a phase, not a config key.
companion_required is never OTA-safe
A change that requires a companion rebuild still requires a binary rebuild —
it is never safe to push OTA. classify/2 with :companion_required always
returns {:rebuild_required, :companion_shell}.
diff/2 is tooling input, NOT a release-gate oracle
diff/2 classifies changes between two Root.t() manifests and returns a
list of {change_class(), verdict()} pairs. It does NOT know whether a binary
with the new native_runtime_version has already shipped to the App Store or
Google Play. Using diff/2 output as a release gate without consulting the
published version history is incorrect — it is a doctor/tooling input only.
Summary
Functions
Classifies a change_class() as OTA-safe or rebuild-required.
Detects change classes between two Root.t() manifests and classifies each.
Public predicate over Capability.rebuild().
Types
@type change_class() ::
:bridge_schema_change
| :capability_family_add
| :permission_add
| :entitlement_add
| :sdk_floor_bump
| :privacy_manifest_entry
| :push_capability_change
| :url_scheme_change
@type verdict() :: :ota_safe | {:rebuild_required, :native_shell | :companion_shell}
Functions
@spec classify(change_class(), Crosswake.Manifest.Types.Capability.t() | nil) :: verdict()
Classifies a change_class() as OTA-safe or rebuild-required.
For capability-axis change classes, the capability argument must be a
Capability.t() struct — nil is not accepted because the source of truth
(Capability.rebuild) is required. Passing nil raises ArgumentError.
For system-level classes (sdk_floor_bump, privacy_manifest_entry), the
capability argument is ignored and may be nil.
Examples
iex> cap = %Capability{id: "haptics", version: "1.0.0", rebuild: :native_required}
iex> RebuildPolicy.classify(:capability_family_add, cap)
{:rebuild_required, :native_shell}
iex> cap = %Capability{id: "haptics", version: "1.0.0", rebuild: :none}
iex> RebuildPolicy.classify(:capability_family_add, cap)
:ota_safe
iex> RebuildPolicy.classify(:sdk_floor_bump, nil)
{:rebuild_required, :native_shell}
@spec diff(Crosswake.Manifest.Types.Root.t(), Crosswake.Manifest.Types.Root.t()) :: [ {change_class(), verdict()} ]
Detects change classes between two Root.t() manifests and classifies each.
Returns a list of {change_class(), verdict()} pairs by comparing root_a
and root_b and feeding each detected change through classify/2.
This function is tooling/doctor input, NOT a release-gate oracle.
It classifies changes present between two manifest snapshots; it does not
know whether a binary with the new native_runtime_version has already
shipped. Do not use the output of diff/2 alone as a production release gate.
@spec rebuild_required?(Crosswake.Manifest.Types.Capability.rebuild()) :: boolean()
Public predicate over Capability.rebuild().
Returns true when a rebuild is required for the given rebuild value,
false when OTA-safe.
Examples
iex> RebuildPolicy.rebuild_required?(:none)
false
iex> RebuildPolicy.rebuild_required?(:native_required)
true
iex> RebuildPolicy.rebuild_required?(:companion_required)
true