[claude] Version Harmony's own change types (HarmonyVersion design)
メンテナーはふだん 4 日以内に返信
まだ誰も着手していません。
評価
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
[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.
- 主要言語
- C#
- スター
- 14
- フォーク
- 4
- 平均マージ
- 4日 19時間
- マージ済み PR(30日)
- 14
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
sillsdev/harmony のほかの issue
-
enhancement
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
メンテナーはふだん 4 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
メンテナーはふだん 4 日以内に返信
-
ServerCommit no longer enforces required properties on JSON deserialization (regression rc.241 → rc.266)対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンbug Data bug
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 4 日以内に返信
-
enhancement
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
メンテナーはふだん 4 日以内に返信
-
enhancement
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
メンテナーはふだん 4 日以内に返信
sillsdev/harmony の issue をすべて見る
似ている issue
-
dev/release: verify-release-candidate.sh runs 0 Java tests with Debian's Maven対応中かも @kou が今日担当しました。 オープンType: bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
apache/arrow-adbc#4947 ·
メンテナーはふだん 1 日以内に返信
-
:watch: Not Triaged CAC
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
dotnet/AspNetCore.Docs#38001 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
Nimblesite/SharpLsp#468 ·
メンテナーはふだん 1 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
elastic/ecs-dotnet#575 ·
-
alerts bug effort:S P2
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
nightscout/nocturne#2511 ·
メンテナーはふだん 1 日以内に返信