[claude] Version Harmony's own change types (HarmonyVersion design)
Maintainer thường phản hồi trong vòng 4 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 20/100
Hướng nghiên cứu
The proposal names HarmonyVersion.cs, ModelVersionBuilder, ModelVersionChanges, and ModelVersionsValidator, with synthetic history tests described in the issue. Start by reading the existing model version and remote resource change implementations, then compare them with the proposed design. Done means Harmony-owned change history is integrated, skipped versions and invalidation are handled, and validation guides apps to the current HarmonyVersion.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
[Claude, autonomous]
Harmony registers its own change types (the five remote resource changes from AddCrdtRemoteResources). Model versions (#135) make apps list them in version 0 with RemoteResourceChanges<TMetadata>(), but nothing makes an app list them again when a Harmony release adds a change type or changes how one applies. Until that happens there is nothing to version, so this is parked. The design below was built and tested on the #135 branch and then dropped as machinery for a one-entry history; it is here so it doesn't have to be reinvented.
Design
Harmony keeps its own append-only version history for its types, the same shape as an app's ModelVersionBuilder. Apps name the version they are on; the change version numbers come from the app's own model versions, so a consumer never types a number for a type Harmony owns.
// consumer
.Add(0, "Before model versions", v => v
.Change<NewWordChange>()
.RemoteResourceChanges<MediaMetadata>(HarmonyVersion.V1))
.Add(3, "Harmony 2.4", v => v.RemoteResourceChanges<MediaMetadata>(HarmonyVersion.V2))
HarmonyVersionis a sealed class with one static instance per version, each pointing at its predecessor. A version lists the open generic change types it added or changed, and optionally an earlierHarmonyVersionto invalidate from, exactly like a consumer'sChange<T>(invalidateFrom:).RemoteResourceChanges<TMetadata>(version)lists what was added or changed since the Harmony version the app last listed (everything, for a new app), so skipped versions fold in. Where a skipped version names aninvalidateFrom, the earliest one wins.- A Harmony
invalidateFrommaps to the app's model version that listed the first Harmony version at or after it. That is exact: a change is stored with its authoring app's latest model version (AuthoredModelVersion), and options validation makes an app list the Harmony version it runs, so changes authored at that model version or later ran that Harmony version or a later one, and earlier ones didn't. An app that never listed one (a new app) invalidates nothing. - Options validation fails when remote resources are enabled and the latest listed Harmony version isn't
HarmonyVersion.Current, naming the call to add:Add a model version that calls RemoteResourceChanges<MediaMetadata>(HarmonyVersion.V2). Listing an older version after a newer one throws in the builder. - Discovery is the startup error, not autocomplete: IntelliSense sorts members alphabetically, so a
V10would sort beforeV2either way.
Code
As last built on the #135 branch, updated to main after #135 merged.
HarmonyVersion.cs:
public sealed class HarmonyVersion
{
public static readonly HarmonyVersion V1 = new(nameof(V1), null, v => v
.Change(typeof(RemoteResourceUploadedChange<>))
.Change(typeof(CreateRemoteResourceChange<>))
.Change(typeof(CreateRemoteResourcePendingUploadChange<>))
.Change(typeof(SetRemoteResourceMetadataChange<>))
.Change(typeof(DeleteRemoteResourceChange<>)));
internal static HarmonyVersion Current => V1;
private HarmonyVersion(string name, HarmonyVersion? previous, Action<HarmonyVersionChanges> configure)
{
Name = name;
Previous = previous;
Number = (previous?.Number ?? 0) + 1;
var changes = new HarmonyVersionChanges(this);
configure(changes);
Changes = changes.List;
}
internal HarmonyVersion Next(string name, Action<HarmonyVersionChanges> configure)
{
return new HarmonyVersion(name, this, configure);
}
internal int Number { get; }
internal string Name { get; }
private HarmonyVersion? Previous { get; }
private IReadOnlyList<(Type OpenType, HarmonyVersion? InvalidateFrom)> Changes { get; }
/// What an app that last listed previous (null for none) must list to be on this version, closed over TMetadata:
/// every type added or changed by a version after it, invalidated from the earliest version any of them names.
internal IEnumerable<(Type Type, string Discriminator, HarmonyVersion? InvalidateFrom)> ChangesSince<TMetadata>(HarmonyVersion? previous)
{
return VersionsSince(previous)
.SelectMany(v => v.Changes)
.GroupBy(c => c.OpenType)
.Select(g =>
{
var type = g.Key.MakeGenericType(typeof(TMetadata));
var discriminator = (string)type.GetProperty(nameof(IPolyType.TypeName))!.GetValue(null)!;
return (type, discriminator, g.Select(c => c.InvalidateFrom).Where(v => v is not null).MinBy(v => v!.Number));
});
}
private IEnumerable<HarmonyVersion> VersionsSince(HarmonyVersion? previous)
{
for (var version = this; version is not null && version != previous; version = version.Previous)
{
yield return version;
}
}
public override string ToString() => $"{nameof(HarmonyVersion)}.{Name}";
internal sealed class HarmonyVersionChanges(HarmonyVersion version)
{
internal List<(Type OpenType, HarmonyVersion? InvalidateFrom)> List { get; } = [];
public HarmonyVersionChanges Change(Type openType, HarmonyVersion? invalidateFrom = null)
{
if (invalidateFrom is not null && invalidateFrom.Number >= version.Number)
throw new ArgumentOutOfRangeException(nameof(invalidateFrom), invalidateFrom, $"must be before {version}");
List.Add((openType, invalidateFrom));
return this;
}
}
}
ModelVersionBuilder additions:
private readonly List<(HarmonyVersion Version, int ModelVersion)> _harmonyVersions = [];
internal HarmonyVersion? HarmonyVersion => _harmonyVersions.Count == 0 ? null : _harmonyVersions[^1].Version;
/// The model version from which this app authored changes with harmonyVersion or a later one:
/// the one that listed the first such version. Null when none did.
internal int? ModelVersionListing(HarmonyVersion harmonyVersion)
{
return _harmonyVersions.Where(h => h.Version.Number >= harmonyVersion.Number)
.Select(h => (int?)h.ModelVersion)
.FirstOrDefault();
}
// in Add: construct with new ModelVersionChanges(this, number), and after configure:
if (changes.HarmonyVersion is not null) _harmonyVersions.Add((changes.HarmonyVersion, number));
// in Clear:
_harmonyVersions.Clear();
This replaces _remoteResourcesListed and RemoteResourcesListed.
ModelVersionChanges takes the builder in its constructor (_builder), and its RemoteResourcesListed becomes internal HarmonyVersion? HarmonyVersion { get; private set; }. The helper replaces the parameterless one:
public ModelVersionChanges RemoteResourceChanges<TMetadata>(HarmonyVersion version) where TMetadata : class
{
ArgumentNullException.ThrowIfNull(version);
var listed = _builder.HarmonyVersion;
if (listed is not null && listed.Number >= version.Number)
throw new ArgumentException($"{version} must come after {listed}, which an earlier model version lists");
foreach (var change in version.ChangesSince<TMetadata>(listed))
{
//a new app, or one that never listed that version, has no changes to invalidate
var invalidateFrom = change.InvalidateFrom is null ? null : _builder.ModelVersionListing(change.InvalidateFrom);
Change(change.Discriminator, invalidateFrom);
Types.Add(change.Type);
}
HarmonyVersion = version;
return this;
}
ModelVersionsValidator, replacing the RemoteResourcesListed check:
if (options.RemoteResourcesEnabled && options.ModelVersionBuilder.HarmonyVersion != HarmonyVersion.Current)
return ValidateOptionsResult.Fail(
$"Add a model version that calls {nameof(ModelVersionChanges.RemoteResourceChanges)}<{options.RemoteResourceMetadataType!.Name}>({HarmonyVersion.Current}), " +
"Harmony's remote resource change types changed");
Tests, against a synthetic history (V2 changes deletes and invalidates from V1, V3 changes uploads and deletes):
var v2 = HarmonyVersion.V1.Next("V2", v => v.Change(typeof(DeleteRemoteResourceChange<>), invalidateFrom: HarmonyVersion.V1));
var v3 = v2.Next("V3", v => v.Change(typeof(RemoteResourceUploadedChange<>)).Change(typeof(DeleteRemoteResourceChange<>)));
// ChangesSince: v3 since v1 lists uploads (no invalidation) and deletes invalidated from v1, so skipping V2 still redoes old deletes
// RemoteResourceChanges: an app with V0 listing v1, V1 notes, V2 listing v3 gets
// version 2: delete:RemoteResource invalidateFrom 0, uploaded:RemoteResource invalidateFrom 2
// a new app listing v3 in version 0 lists all five with invalidateFrom 0
// listing v2 after v3 throws "HarmonyVersion.V2 must come after HarmonyVersion.V3"
// the sample with the five types listed by hand fails validation naming RemoteResourceChanges<MediaMetadata>(HarmonyVersion.V1)
Compatibility of adding it later
HarmonyVersion.V1 lists the same five types the plain helper lists today, at the same numbers, so switching version 0 from RemoteResourceChanges<T>() to RemoteResourceChanges<T>(HarmonyVersion.V1) stores identical applied versions and the "released version was modified" check passes. The one constraint: Harmony must not add a resource change type or change how one applies before this lands. Apps would have to list those types by hand, which _harmonyVersions doesn't record, so a later Harmony invalidateFrom would map to the model version that first called RemoteResourceChanges with the new version, missing changes authored between the hand listing and that call. Adding the new type to the plain RemoteResourceChanges<T>() instead would change every app's released version 0, which reconcile rejects.
- Ngôn ngữ chính
- C#
- Star
- 14
- Fork
- 4
- Merge trung bình
- 5 ngày 4 giờ
- Pull request đã merge (30 ngày)
- 13
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của sillsdev/harmony
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 4 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 4 ngày
-
bug Data bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 4 ngày
-
enhancement
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 58/100
Maintainer thường phản hồi trong vòng 4 ngày
-
enhancement
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 68/100
Maintainer thường phản hồi trong vòng 4 ngày
Tất cả issue của sillsdev/harmony
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 65/100
KnifeLemon/Filee#363 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
-
area-Native untriaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
dotnet/machinelearning#7837 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
[CI] .trgen-ignore required for several grammars.Có thể đã có người làm @kaby76 đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
antlr/grammars-v4#5073 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[Enhancement]: Add a search box to the Time Zone dropdownCó thể đã có người làm @idumlupinar đã nhận hôm nay. Đang mởStatus: New
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
dnnsoftware/Dnn.Platform#7579 ·
Maintainer thường phản hồi trong vòng 2 ngày