diff --git a/docs/reviews/2026-08-18-r3-test-truth-ledger.md b/docs/reviews/2026-08-18-r3-test-truth-ledger.md index 5a9eefd0..76e7c722 100644 --- a/docs/reviews/2026-08-18-r3-test-truth-ledger.md +++ b/docs/reviews/2026-08-18-r3-test-truth-ledger.md @@ -1498,3 +1498,43 @@ Verification follows the proportionate R3 policy: - a targeted scan finds source reads only in the two explicitly retained whole-tree policy methods. The final inventory and twelve-assembly Release gate remain deferred to closeout. + +## Batch AF create-authority drift-model contract replacement + +Batch AF converts the final approved test-model/source correspondence check. +`CreateAuthorityDriftModelSourcePinTests` is renamed to +`CreateAuthorityDriftModelContractTests`; its descriptive test method +`HandCalledDriftProbe_StillModelsTheExecutorDrainAdvance` is retained. No +product source changes and no test is removed. + +The replacement no longer regexes two Runtime files. It inspects compiled +Runtime metadata and proves: + +- `ApplyWeenieDescriptionAction` applies the accepted description, refreshes + the canonical snapshot, advances create authority exactly once, and then + advances the executor baseline in semantic execution order; +- `RegisterEntityCore` still has the named Boolean initial-residence input, + refreshes the ordinary existing-generation snapshot, and places the one + registration-time authority advance behind a conditional branch that skips + over the call; and +- the Runtime assembly contains exactly those two call sites targeting + `RuntimeEntityDirectory.AdvanceCreateAuthority`: the modeled executor drain + and the gated ordinary registration route. + +This is the stale-model alarm the source regex intended, but it now survives +comments, whitespace, local renames, and equivalent expression refactors. +The expectation-item 6/8 probe comments in the hydration and supersession +suites now say `metadata-pinned` and name the new contract class, preserving +the rationale without leaving misleading source-pin references. + +Verification follows the proportionate R3 policy: + +- the contract plus its hydration and supersession behavior suites pass 90/90; +- the complete `AcDream.App.Tests` hermetic lane passes 5,381/5,381 with zero + skips or failures; +- the complete locked Release build covers all 44 projects with zero warnings + and zero errors; and +- targeted scans find no source reader in the renamed contract and no stale + source-pin comment in the affected tests. With this batch, all 85 approved + replacements are implemented; final inventory reconciliation and the one + complete Release gate follow next. diff --git a/tests/AcDream.App.Tests/Rendering/LiveEntityCreateSupersessionRecoveryTests.cs b/tests/AcDream.App.Tests/Rendering/LiveEntityCreateSupersessionRecoveryTests.cs index 36b3e5e6..81d423be 100644 --- a/tests/AcDream.App.Tests/Rendering/LiveEntityCreateSupersessionRecoveryTests.cs +++ b/tests/AcDream.App.Tests/Rendering/LiveEntityCreateSupersessionRecoveryTests.cs @@ -83,7 +83,7 @@ public sealed class LiveEntityCreateSupersessionRecoveryTests // covered. // C3c-R1 F3: honest MODEL of the executor drain's advance // (ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests); a + // metadata-pinned by CreateAuthorityDriftModelContractTests); a // nested production OnCreate can no longer reach it — // post-residence registration is description-only // (RuntimeEntityObjectLifetime :660-665, diff --git a/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelContractTests.cs b/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelContractTests.cs new file mode 100644 index 00000000..381d12ac --- /dev/null +++ b/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelContractTests.cs @@ -0,0 +1,113 @@ +using System.Reflection; +using System.Reflection.Emit; +using AcDream.App.Tests.Architecture; +using AcDream.Runtime.Entities; + +namespace AcDream.App.Tests.World; + +/// +/// The create-authority drift probes in the expectation-item 6/8 tests +/// (`LiveEntityHydrationControllerTests` and +/// `LiveEntityCreateSupersessionRecoveryTests`) hand-call +/// as an honest model +/// of the executor drain's advance: the sole production drain site that +/// advances create authority for an existing incarnation. A nested production +/// OnCreate cannot produce that drift because the initial-residence +/// registration route branches around the ordinary registration-time advance. +/// This compiled contract flags the model as stale if either route changes, so +/// those probes can be re-derived from the new production boundary. +/// +public sealed class CreateAuthorityDriftModelContractTests +{ + private const BindingFlags Declared = BindingFlags.Instance + | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic + | BindingFlags.DeclaredOnly; + + [Fact] + public void HandCalledDriftProbe_StillModelsTheExecutorDrainAdvance() + { + MethodInfo drain = RequiredMethod( + typeof(RuntimeInitialCreateContinuationExecutor), + "ApplyWeenieDescriptionAction"); + AssertCallOrder( + drain, + (typeof(RuntimeEntityDirectory), + nameof(RuntimeEntityDirectory.ApplyAcceptedWeenieDescriptionSnapshot)), + (typeof(RuntimeEntityDirectory), nameof(RuntimeEntityDirectory.RefreshSnapshot)), + (typeof(RuntimeEntityDirectory), + nameof(RuntimeEntityDirectory.AdvanceCreateAuthority)), + (typeof(RuntimeInitialCreateResidenceState), "AdvanceExecutorBaseline")); + Assert.Single( + CompiledCallGraph.Read(drain), + call => IsAuthorityAdvance(call.Target)); + + MethodInfo registration = RequiredMethod( + typeof(RuntimeEntityObjectLifetime), + "RegisterEntityCore"); + ParameterInfo residenceFlag = Assert.Single( + registration.GetParameters(), + parameter => parameter.Name == "beginInitialResidence" + && parameter.ParameterType == typeof(bool)); + Assert.NotNull(residenceFlag); + + IReadOnlyList registrationCalls = + CompiledCallGraph.Read(registration); + CompiledCall refresh = Assert.Single( + registrationCalls, + call => call.Target.DeclaringType == typeof(RuntimeEntityDirectory) + && call.Target.Name == nameof(RuntimeEntityDirectory.RefreshSnapshot)); + CompiledCall ordinaryAdvance = Assert.Single( + registrationCalls, + call => IsAuthorityAdvance(call.Target)); + Assert.True(refresh.Offset < ordinaryAdvance.Offset); + Assert.Contains( + CompiledCallGraph.ReadBranches(registration), + branch => branch.OpCode.FlowControl == FlowControl.Cond_Branch + && branch.Offset > refresh.Offset + && branch.Offset < ordinaryAdvance.Offset + && branch.TargetOffset > ordinaryAdvance.Offset); + + MethodBase[] productionCallSites = + MethodsCallingRuntimeAuthorityAdvance().ToArray(); + Assert.Equal(2, productionCallSites.Length); + Assert.Contains(drain, productionCallSites); + Assert.Contains(registration, productionCallSites); + } + + private static IEnumerable MethodsCallingRuntimeAuthorityAdvance() => + typeof(RuntimeEntityObjectLifetime).Assembly.GetTypes() + .SelectMany(type => type.GetMethods(Declared) + .Cast() + .Concat(type.GetConstructors(Declared))) + .Where(method => method.GetMethodBody() is not null) + .Where(method => CompiledCallGraph.Read(method).Any(call => + IsAuthorityAdvance(call.Target))); + + private static bool IsAuthorityAdvance(MethodBase method) => + method.DeclaringType == typeof(RuntimeEntityDirectory) + && method.Name == nameof(RuntimeEntityDirectory.AdvanceCreateAuthority); + + private static MethodInfo RequiredMethod(Type owner, string name) => + owner.GetMethod( + name, + BindingFlags.Instance | BindingFlags.Static + | BindingFlags.Public | BindingFlags.NonPublic) + ?? throw new MissingMethodException(owner.FullName, name); + + private static void AssertCallOrder( + MethodBase method, + params (Type Type, string Method)[] expected) + { + IReadOnlyList calls = CompiledCallGraph.Read(method); + int cursor = -1; + foreach ((Type type, string name) in expected) + { + int found = Enumerable.Range(cursor + 1, calls.Count - cursor - 1) + .FirstOrDefault(index => calls[index].Target.DeclaringType == type + && calls[index].Target.Name == name, -1); + Assert.True(found > cursor, + $"Missing compiled edge after {cursor}: {type.FullName}.{name}."); + cursor = found; + } + } +} diff --git a/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelSourcePinTests.cs b/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelSourcePinTests.cs deleted file mode 100644 index 68659a9a..00000000 --- a/tests/AcDream.App.Tests/World/CreateAuthorityDriftModelSourcePinTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Text.RegularExpressions; - -namespace AcDream.App.Tests.World; - -/// -/// The create-authority drift probes in the expectation-item 6/8 tests -/// (LiveEntityHydrationControllerTests + LiveEntityCreateSupersessionRecoveryTests) -/// hand-call record.Canonical.AdvanceCreateAuthority() as an HONEST -/// MODEL of the executor drain's advance — the SOLE remaining production -/// site that advances create authority for an existing incarnation. A -/// nested production OnCreate can no longer produce that drift: -/// post-residence ExistingGeneration registration is description-only -/// (RuntimeEntityObjectLifetime gates the advance on -/// !beginInitialResidence) and ConsumeExecuted removes the -/// completed residence entry at Released, closing the FIFO-adoption path -/// (empirically confirmed: the restored nested-OnCreate probe produced no -/// drift and no CreateSupersessionRecovery). This pin flags the model as -/// STALE if the production site ever moves or loses the advance — the -/// item 6/8 probes must be re-derived from wherever it goes. -/// -public sealed class CreateAuthorityDriftModelSourcePinTests -{ - [Fact] - public void HandCalledDriftProbe_StillModelsTheExecutorDrainAdvance() - { - string executor = ReadRuntimeSource( - "Entities", - "RuntimeInitialCreateContinuationExecutor.cs"); - - // Exactly one production advance, and it lives inside the - // WeenieDescription drain stage the probes model. - Assert.Single( - Regex.Matches(executor, @"_entities\.AdvanceCreateAuthority\(") - .Cast()); - Assert.Matches( - new Regex( - @"private bool ApplyWeenieDescriptionAction[\s\S]{0,6000}?" - + @"_entities\.AdvanceCreateAuthority\(canonical\);"), - executor); - - // The registration-time advance stays gated OFF the residence - // route — the reason a nested production OnCreate cannot reach the - // modeled drift. - string lifetime = ReadRuntimeSource( - "Entities", - "RuntimeEntityObjectLifetime.cs"); - Assert.Matches( - new Regex( - @"if \(!beginInitialResidence\)\s*" - + @"Entities\.AdvanceCreateAuthority\(retained\);"), - lifetime); - } - - private static string ReadRuntimeSource(params string[] relativePath) - { - DirectoryInfo? directory = new(AppContext.BaseDirectory); - while (directory is not null) - { - if (File.Exists(Path.Combine(directory.FullName, "AcDream.slnx"))) - { - return File.ReadAllText(Path.Combine( - directory.FullName, - "src", - "AcDream.Runtime", - Path.Combine(relativePath))); - } - - directory = directory.Parent; - } - - throw new DirectoryNotFoundException("Could not find AcDream.slnx."); - } -} diff --git a/tests/AcDream.App.Tests/World/LiveEntityHydrationControllerTests.cs b/tests/AcDream.App.Tests/World/LiveEntityHydrationControllerTests.cs index 8fcdceaf..56569778 100644 --- a/tests/AcDream.App.Tests/World/LiveEntityHydrationControllerTests.cs +++ b/tests/AcDream.App.Tests/World/LiveEntityHydrationControllerTests.cs @@ -1371,7 +1371,7 @@ public sealed class LiveEntityHydrationControllerTests // honest MODEL of the executor drain's advance // (RuntimeInitialCreateContinuationExecutor // .ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests). A nested + // metadata-pinned by CreateAuthorityDriftModelContractTests). A nested // production OnCreate can no longer reach it here: // post-residence ExistingGeneration registration is // description-only (RuntimeEntityObjectLifetime gates the @@ -1509,7 +1509,7 @@ public sealed class LiveEntityHydrationControllerTests // honest MODEL of the executor drain's advance // (RuntimeInitialCreateContinuationExecutor // .ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests). A nested + // metadata-pinned by CreateAuthorityDriftModelContractTests). A nested // production OnCreate can no longer reach it here: // post-residence ExistingGeneration registration is // description-only (RuntimeEntityObjectLifetime gates the @@ -1864,7 +1864,7 @@ public sealed class LiveEntityHydrationControllerTests // revalidation guard stays covered. // C3c-R1 F3: honest MODEL of the executor drain's advance // (ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests); a + // metadata-pinned by CreateAuthorityDriftModelContractTests); a // nested production OnCreate can no longer reach it — // post-residence registration is description-only // (RuntimeEntityObjectLifetime :660-665, @@ -1983,7 +1983,7 @@ public sealed class LiveEntityHydrationControllerTests // WeenieDescription stage; model that advance directly. // C3c-R1 F3: honest MODEL of the executor drain's advance // (ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests); a + // metadata-pinned by CreateAuthorityDriftModelContractTests); a // nested production OnCreate can no longer reach it — // post-residence registration is description-only // (RuntimeEntityObjectLifetime :660-665, @@ -2019,7 +2019,7 @@ public sealed class LiveEntityHydrationControllerTests // (AdvanceCreateAuthority), not at registration; model it directly. // C3c-R1 F3: honest MODEL of the executor drain's advance // (ApplyWeenieDescriptionAction — the sole production site, - // source-pinned by CreateAuthorityDriftModelSourcePinTests); a nested + // metadata-pinned by CreateAuthorityDriftModelContractTests); a nested // production OnCreate can no longer reach it — post-residence // registration is description-only (RuntimeEntityObjectLifetime // :660-665, !beginInitialResidence gate) and ConsumeExecuted