Public Access
Give the two logs and the buckets a resource type, so a conflict can be written
AadResourceTypes.For maps a syncable type onto the AAD resource type its cache records bind to, and it had no arm for ConnectionLogEntry, ActivityLogEntry or ObjectStore. All three are on both enums, in the reconciler registry and in the cipher pinning; only this switch was missed, and it throws rather than falling back — so a merge conflict on a connection log, an activity log or a bucket raised ArgumentOutOfRangeException on the path that records what the merge discarded. The conflict log is the whole reason the merge is allowed to pick a winner, so the one item kind whose conflicts could not be recorded was a bucket: an editable item two machines can genuinely disagree about. Worth writing down why it lasted two phases. Of the three callers, ItemStore and OutboxStore reach the mapping only when an item carries plaintext fields, and none of these three kinds does — so they never touched the gap. ConflictStore calls it unconditionally, but a test only reaches that by causing a real merge conflict, and every existing one raised its conflict against a Host. Three arms missing, and no path in the suite crossed any of them. So the tests are the point of this commit as much as the arms are. The guard is AadResourceTypeTests.EverySyncableType_HasAnArmInTheStorageMapping: it walks the whole wire enum, and for each type asserts both that there is an arm and that the arm returns the same-named resource type, which is the mistake the file's cipher half already guards against on the server side. Written over the full enum rather than over ItemKinds.SyncedTypes, because that is the stronger claim and the one the switch really makes — the two reserved association types have arms too. Beside it, CacheStoreTests.AConflict_CanBeRecordedForEveryKindOfItem records a conflict per kind and reads the detail back, since an arm returning the wrong resource type seals under one AAD and opens under another, which surfaces as an empty detail rather than as a throw. Both were confirmed to fail with the arms removed: the theory fails on exactly ConnectionLogEntry, ActivityLogEntry and ObjectStore and passes on the other three, and the guard names those three and no others. The note in docs/adding-hosts-on-the-phone.md that recorded this as out of scope is marked fixed, with what let it survive, since that is the part worth knowing next time an item kind is added. 1529 tests pass, seven of them new.
This commit is contained in:
@@ -98,10 +98,27 @@ public sealed class LocalCacheProtector : IDisposable
|
||||
/// Maps a syncable entity type onto the resource type its AAD binds.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// A switch rather than a cast, even though the two enums happen to be adjacent. They are not the
|
||||
/// same list: <see cref="CryptoSpec.AadResourceType"/> also covers users, devices and vaults, so the
|
||||
/// numbers do not line up, and a cast would bind an item's ciphertext to the wrong resource type
|
||||
/// without failing anywhere a test would notice.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>It has to name every syncable type, and the throw is not a safety net.</b>
|
||||
/// <c>ConflictStore.RecordAsync</c> calls this unconditionally, so a type with no arm here is a type
|
||||
/// whose <em>conflicts</em> cannot be recorded — and the conflict log is the only reason the merge is
|
||||
/// allowed to pick a winner at all. Three types went two phases without one: the two logs and the
|
||||
/// buckets were added to both enums and to the reconciler registry, and this switch was not touched,
|
||||
/// so a merge over any of them turned a recorded conflict into an
|
||||
/// <see cref="ArgumentOutOfRangeException"/>. The two other callers hid it —
|
||||
/// <c>ItemStore</c> and <c>OutboxStore</c> only reach this when an item carries plaintext fields, and
|
||||
/// none of those three does.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <c>AadResourceTypeTests.EverySyncableType_HasAnArmInTheStorageMapping</c> is what says so now, and it
|
||||
/// is a name comparison rather than a list to keep by hand.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal static class AadResourceTypes
|
||||
{
|
||||
@@ -117,6 +134,14 @@ internal static class AadResourceTypes
|
||||
SyncEntityType.Snippet => CryptoSpec.AadResourceType.Snippet,
|
||||
SyncEntityType.PortForward => CryptoSpec.AadResourceType.PortForward,
|
||||
SyncEntityType.KnownHostKey => CryptoSpec.AadResourceType.KnownHostKey,
|
||||
|
||||
// Appended in the order the enums grew, which is why these three are not beside their
|
||||
// neighbours by number. See docs/crypto.md §4.3 on why 14–16 are not one-behind their wire
|
||||
// counterparts the way the arms above are.
|
||||
SyncEntityType.ConnectionLogEntry => CryptoSpec.AadResourceType.ConnectionLogEntry,
|
||||
SyncEntityType.ActivityLogEntry => CryptoSpec.AadResourceType.ActivityLogEntry,
|
||||
SyncEntityType.ObjectStore => CryptoSpec.AadResourceType.ObjectStore,
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(entityType), entityType, "No AAD resource type is defined for this entity type."),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user