|
| 1 | +--- |
| 2 | +uid: AppCompat |
| 3 | +--- |
| 4 | + |
| 5 | +<!-- Copyright 2024 Yubico AB |
| 6 | +
|
| 7 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | +you may not use this file except in compliance with the License. |
| 9 | +You may obtain a copy of the License at |
| 10 | +
|
| 11 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +
|
| 13 | +Unless required by applicable law or agreed to in writing, software |
| 14 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | +See the License for the specific language governing permissions and |
| 17 | +limitations under the License. --> |
| 18 | + |
| 19 | +# Maintaining compatibility |
| 20 | + |
| 21 | +This article describes the various decisions that the SDK makes in order to maintain application and source |
| 22 | +compatibility across our versions. |
| 23 | + |
| 24 | +## App-compat strategy for this SDK |
| 25 | + |
| 26 | +The .NET SDK strives to maintain both source and behavioral compatibility across its releases. |
| 27 | + |
| 28 | +**Source compatibility** is the promise that your source code should continue to compile as-is when you update your |
| 29 | +application to the latest version of the SDK. This promise extends to "minor" (feature) and "patch" (bug-fix) releases |
| 30 | +of the SDK. Additionally, we only make this guarantee for the `Yubico.YubiKey` and the `Yubico.Core` assemblies. Since |
| 31 | +`Yubico.NativeShims` and `Yubico.DotnetPolyfills` are meant to be purely for internal use, we *do not* make any |
| 32 | +guarantees here. |
| 33 | + |
| 34 | +**Behavioral compatibility** is the promise that your application will behave exactly the same after you have upgraded |
| 35 | +the YubiKey SDK version. Maintaining this guarantee is far more difficult and is sometimes simply not possible. However, |
| 36 | +we will continue to do our best to maintain behavioral stability across releases. If a behavioral change is |
| 37 | +necessitated, such as fixing a bug, we may choose to simply fix the issue. This is far more likely to occur if the bug |
| 38 | +prevented the feature from ever working in the first place and no workaround was present. If the change is more nuanced |
| 39 | +than that, or it is changing behavior for some other reason, we have a separate mechanism that we've started using so |
| 40 | +that these behavior changes may be managed through an opt-in or opt-out decision. |
| 41 | + |
| 42 | +There are two exceptions to these promises: |
| 43 | + |
| 44 | +1. Sometimes a breaking change is unavoidable. Perhaps a new YubiKey feature was released that is simply impossible to |
| 45 | + express with the existing shape of the API. Or a bug was discovered, and it simply must be addressed. In these cases, |
| 46 | + we will do everything in our power to first mark the affected types or members with the `ObsoleteAttribute` so that |
| 47 | + you are alerted the fact that there's an issue with the old usage. The attribute will contain text that will result |
| 48 | + in a usage warning when you recompile. This text will be included in the warning message and will point you to the |
| 49 | + new API that should be used instead. The old API will remain for several minor releases before we consider it safe to |
| 50 | + remove entirely. A major release would remove all obsolete APIs in one go. |
| 51 | + |
| 52 | +2. You will note that the promise is only made for minor and patch releases. For example, upgrading feature releases |
| 53 | + (i.e. `1.9.1` to `1.10.0`) or upgrading patch releases (i.e. `1.9.0` to `1.9.1`) have this guarantee. What has been |
| 54 | + omitted here is "major" releases (i.e. `1.10.0` to `2.0.0`). Major version releases are our chance to make broader |
| 55 | + changes that address design-level issues. It should be expected that there will be source level breaking changes when |
| 56 | + a major version is released. |
| 57 | + |
| 58 | +Our SDK does *not* make any promises around **Application Binary Interface (ABI)** stability. This expectation is |
| 59 | +generally far less common in the .NET ecosystem to begin with, however there are two very important implications here: |
| 60 | + |
| 61 | +1. You *must* recompile your code against a new version of our SDK. Simply replacing our assemblies with a newer version |
| 62 | + is **not** supported and could result in undefined behavior and bugs in your application's behavior. |
| 63 | + |
| 64 | +2. If an enumeration does not have an explicitly defined value, you should assume that the underlying value may change. |
| 65 | + While these changes should not result in any changes to behavior (assuming you've recompiled) it does mean that these |
| 66 | + values should not be serialized and stored across versions. If you need to persist these values for whatever reason, |
| 67 | + it is strongly recommended you create your own stable values to map to, or use another mechanism that does not depend |
| 68 | + on the specific compiler-generated enumeration value. |
| 69 | + |
| 70 | +## Managing behavior changes through app-compat switches |
| 71 | + |
| 72 | +Sometimes it's unavoidable that the SDK must make a behavior breaking change. For example: a bug has been addressed that |
| 73 | +causes subtle behavior changes that have existed for many releases. Or perhaps an optimization has been made that may |
| 74 | +result in different timings that could have an effect on UI applications. |
| 75 | + |
| 76 | +In these cases, we've introduced a new mechanism for adjusting these behaviors through the use of app-compat switches. |
| 77 | +These switches use the |
| 78 | +[`AppContext.SetSwitch`](https://learn.microsoft.com/en-us/dotnet/api/system.appcontext.setswitch) mechanism exposed by |
| 79 | +the .NET Base Class Library. |
| 80 | + |
| 81 | +Whether a behavior change is opt-in or opt-out will be decided on a case-by-case basis. Generally, if we view the change |
| 82 | +to be a net positive and have a low risk of observable changes to an application, we will make the change opt-out. That |
| 83 | +means, you will get the new behavior by default. Only if the change causes your application problems should you consider |
| 84 | +setting the switch to disable that behavior. |
| 85 | + |
| 86 | +For more observable or impactful changes, or changes that would benefit a smaller subset of consumers, we will make the |
| 87 | +change opt-in. That is, the existing behaviors will be maintained, and your application must explicitly call `SetSwitch` |
| 88 | +with a value of `true`. |
| 89 | + |
| 90 | +This decision is clearly very subjective. Any time a behavior change is made, there is a high likelihood that at least |
| 91 | +one consumer will be adversely affected no matter which behavior we choose. That's why we've introduced these switches |
| 92 | +in the first place. There will always be a case where someone will need to override out decision. This is your mechanism |
| 93 | +to do so. |
| 94 | + |
| 95 | +All of our compatibility switch names are defined in two central classes: |
| 96 | + |
| 97 | +- [YubiKeyCompatSwitches](xref:Yubico.YubiKey.YubiKeyCompatSwitches) - This class holds all the compatibility switches |
| 98 | + that affect the behaviors of the `Yubico.YubiKey` assembly. |
| 99 | +- [CoreCompatSwitches](xref:Yubico.Core.CoreCompatSwitches) - This class holds all the compatibility switches that |
| 100 | + affect the `Yubico.Core` assembly. `Yubico.Core` serves as our platform abstraction layer, so switches here may only |
| 101 | + impact a certain operating system or a certain downstream dependency. While not YubiKey specific, it may affect things |
| 102 | + like enumeration and eventing of YubiKeys. |
| 103 | + |
| 104 | +Each flag will have a clear explanation of what behavior it affects, what the default is, and what the impact of |
| 105 | +overriding the default should be. Use these constants as the value for the `switchName` parameter of |
| 106 | +`AppContext.SetSwitch`. |
0 commit comments