From 05a56f7ada6587a67e4f32fd819fa559a6ddbdfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=96=A4=20ShadowNine=20=F0=9F=92=9B?= Date: Sun, 25 Oct 2026 13:21:42 +0100 Subject: [PATCH 1/2] fix lifecycle cleanup and retire stale qualification documents --- .gitignore | 1 + CONTRIBUTING.md | 7 + THIRD-PARTY-NOTICES.md | 8 +- TODO.md | 698 +--------- docs/beta3-release-notes.md | 21 - docs/beta4-release-notes.md | 24 - docs/external-connections.md | 85 -- docs/mcp-alternatives-research.md | 144 -- docs/qualification/all-tools/README.md | 1206 +---------------- .../compiler-extended-policy.md | 0 .../compiler-probe-policy.md | 10 +- .../{v2.0.0 => all-tools}/runners.md | 2 +- docs/qualification/v2.0.0/acceptance.md | 138 -- .../v2.0.0/admission-preparation.md | 46 - .../v2.0.0/compiler-checkpoint.md | 112 -- .../v2.0.0/contract-checkpoint.md | 118 -- .../qualification/v2.0.0/lifecycle-key-fix.md | 77 -- docs/qualification/v2.0.0/lifecycle-resume.md | 68 - docs/qualification/v2.0.0/local-checkpoint.md | 85 -- docs/qualification/v2.0.0/mcp-closeout.md | 40 - .../v2.0.0/navigation-followup.md | 57 - .../v2.0.0/navigation-native-regression.md | 43 - .../v2.0.0/navigation-package.md | 31 - .../v2.0.0/navigation-preparation.md | 46 - .../v2.0.0/nested-dispatch-regression.md | 86 -- .../v2.0.0/next-live-investigation.md | 39 - .../v2.0.0/offline-contract-audit.md | 56 - .../v2.0.0/performance-preparation.md | 59 - docs/qualification/v2.0.0/release-notes.md | 70 - docs/qualification/v2.0.0/release-signoff.md | 142 -- .../v2.0.0/remaining-checkpoint.md | 469 ------- .../v2.0.0/requirement-matrix.md | 153 --- .../v2.0.0/settings-observation.md | 44 - .../v2.0.0/stack-trace-diagnostic.md | 71 - docs/qualification/v2.0.0/support-matrix.md | 105 -- externals/CheatEngine.Client | 2 +- externals/CheatEngine.SDK | 2 +- libs/CheatEngine.Mcp.Core/Jobs/JobRegistry.cs | 33 +- .../Jobs/LuaJobKernelScripts.cs | 34 +- libs/CheatEngine.Mcp.Core/Jobs/ManagedJob.cs | 70 +- .../Targets/McpStateLedger.cs | 10 +- .../Targets/TargetResources.cs | 49 +- .../CheatEngineMcpPluginBuilderExtensions.cs | 1 + .../Lua/PluginLuaToolRuntime.cs | 122 +- .../CheatEngine.Mcp.Plugin/McpServerModule.cs | 46 +- .../McpStatusIndicator.cs | 13 +- srcs/CheatEngine.Mcp.Plugin/README.md | 13 +- .../Debugger/DebuggerLuaScripts.cs | 146 +- srcs/CheatEngine.Mcp.Tools/Mono/MonoTools.cs | 15 +- .../DebuggerExecutionLoop.cs | 6 +- .../DebuggerExecutionLoopEncoding.cs | 19 +- tests/CheatEngine.Mcp.LiveTarget/Program.cs | 4 +- .../Core/JobRegistryTests.cs | 90 +- .../Core/McpStateLedgerTests.cs | 19 + .../Core/TargetResourcesTests.cs | 21 + .../DebuggerExecutionLoopBytesTests.cs | 20 +- .../LiveActivePluginLifecycleQualification.cs | 486 +++++++ ...ActivePluginLifecycleQualificationTests.cs | 100 ++ .../LiveDebuggerEventOracleTests.cs | 18 +- .../LiveDebuggerExecutionQualification.cs | 10 +- ...LiveDebuggerExecutionQualificationTests.cs | 6 +- .../LiveDebuggerTransitionMemoryTests.cs | 11 +- .../LiveQualification/LivePluginBridge.cs | 112 +- .../LiveQualificationOptIn.cs | 7 +- .../LiveQualificationOptInTests.cs | 32 + .../LiveQualificationScenario.cs | 1 + .../LiveQualification/LiveSandboxOptions.cs | 14 +- .../LiveSandboxOptionsTests.cs | 26 + .../LiveSandboxSession.Lifecycle.cs | 211 ++- .../LiveQualification/LiveSandboxSession.cs | 4 +- .../McpLiveQualificationTests.cs | 5 + .../LiveQualification/TargetMemoryProbe.cs | 172 +++ .../TargetMemoryProbeTests.cs | 48 + .../NativeLua/NativeLuaAsmDebuggerV2Tests.cs | 7 +- ...iveLuaDebuggerAdapterQualificationTests.cs | 117 +- .../NativeLuaDebuggerBreakpointArityTests.cs | 12 +- .../NativeLuaDebuggerCaptureTests.cs | 137 +- .../NativeLua/NativeLuaDebuggerTraceTests.cs | 23 +- .../NativeLua/NativeLuaExecuteTests.cs | 32 + .../NativeLuaLivePluginBridgeTests.cs | 65 +- .../NativeLua/NativeLuaStateLedgerTests.cs | 52 +- .../NativeLuaStatusIndicatorTests.cs | 29 +- .../Plugin/McpModuleLifecycleTests.cs | 151 ++- tests/CheatEngine.Mcp.Tests/README.md | 4 +- .../Support/StateTestHarness.cs | 12 +- .../Tools/Debugger/DebuggerToolsTests.cs | 6 +- .../Tools/Mono/MonoToolsTests.cs | 15 + 87 files changed, 2596 insertions(+), 4425 deletions(-) delete mode 100644 docs/beta3-release-notes.md delete mode 100644 docs/beta4-release-notes.md delete mode 100644 docs/external-connections.md delete mode 100644 docs/mcp-alternatives-research.md rename docs/qualification/{v2.0.0 => all-tools}/compiler-extended-policy.md (100%) rename docs/qualification/{v2.0.0 => all-tools}/compiler-probe-policy.md (97%) rename docs/qualification/{v2.0.0 => all-tools}/runners.md (98%) delete mode 100644 docs/qualification/v2.0.0/acceptance.md delete mode 100644 docs/qualification/v2.0.0/admission-preparation.md delete mode 100644 docs/qualification/v2.0.0/compiler-checkpoint.md delete mode 100644 docs/qualification/v2.0.0/contract-checkpoint.md delete mode 100644 docs/qualification/v2.0.0/lifecycle-key-fix.md delete mode 100644 docs/qualification/v2.0.0/lifecycle-resume.md delete mode 100644 docs/qualification/v2.0.0/local-checkpoint.md delete mode 100644 docs/qualification/v2.0.0/mcp-closeout.md delete mode 100644 docs/qualification/v2.0.0/navigation-followup.md delete mode 100644 docs/qualification/v2.0.0/navigation-native-regression.md delete mode 100644 docs/qualification/v2.0.0/navigation-package.md delete mode 100644 docs/qualification/v2.0.0/navigation-preparation.md delete mode 100644 docs/qualification/v2.0.0/nested-dispatch-regression.md delete mode 100644 docs/qualification/v2.0.0/next-live-investigation.md delete mode 100644 docs/qualification/v2.0.0/offline-contract-audit.md delete mode 100644 docs/qualification/v2.0.0/performance-preparation.md delete mode 100644 docs/qualification/v2.0.0/release-notes.md delete mode 100644 docs/qualification/v2.0.0/release-signoff.md delete mode 100644 docs/qualification/v2.0.0/remaining-checkpoint.md delete mode 100644 docs/qualification/v2.0.0/requirement-matrix.md delete mode 100644 docs/qualification/v2.0.0/settings-observation.md delete mode 100644 docs/qualification/v2.0.0/stack-trace-diagnostic.md delete mode 100644 docs/qualification/v2.0.0/support-matrix.md create mode 100644 tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualification.cs create mode 100644 tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualificationTests.cs create mode 100644 tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbe.cs create mode 100644 tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbeTests.cs diff --git a/.gitignore b/.gitignore index ce7f663..1986f37 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ +*.dll # ASP.NET Scaffolding ScaffoldingReadMe.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe0ee7b..cd88118 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,6 +71,13 @@ dotnet format whitespace CheatEngine.Mcp.slnx --no-restore --verify-no-changes The two dependencies live in `externals/CheatEngine.Client` and `externals/CheatEngine.SDK`. Git records their exact commits, while `.gitmodules` identifies `main` as the update branch. +Before updating, inspect each dependency with `git -C externals/CheatEngine.Client status` +and `git -C externals/CheatEngine.SDK status`. Changes inside a submodule are not included +when the parent repository is committed. Preserve them on a local branch, validate and +commit them in that repository first, then record the dependency revisions in MCP. +Do not reset a dirty dependency to make the parent status clean. Local integration commits +must be reviewed and available upstream before a release records them as dependencies. + To prepare a dependency update: ```powershell diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 3956c1c..3dc325d 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -11,8 +11,8 @@ Client and SDK are built from Git submodules, not restored as NuGet packages. Th | Component | Source commit | License | Copyright | Shipped in | |---|---|---|---|---| -| CheatEngine.Client | 9b836e6ced8e97b4506dc8523998c7c2081cc2ad | MIT | Copyright (c) 2026 AriusII, ShadowNineX and CheatEngine.Client contributors | Embedded plugin dependencies and gateway | -| CheatEngine.SDK | 09dad6c15782d9eee38f013d834d432d79e6623e | MIT | Copyright (c) 2026 AriusII, ShadowNineX and CheatEngine.SDK contributors | Six SDK runtime assemblies in the plugin and gateway; native `cheatengine-sdk-lua-bridge.dll` in the plugin | +| CheatEngine.Client | 101b13e2caa8bfdfd5cec6ae2f96b9e32dfe5691 | MIT | Copyright (c) 2026 AriusII, ShadowNineX and CheatEngine.Client contributors | Embedded plugin dependencies and gateway | +| CheatEngine.SDK | 1e45f32edfba80e4a7ef8f62f404e617067cbfed | MIT | Copyright (c) 2026 AriusII, ShadowNineX and CheatEngine.SDK contributors | Six SDK runtime assemblies in the plugin and gateway; native `cheatengine-sdk-lua-bridge.dll` in the plugin | ## NuGet packages @@ -64,8 +64,8 @@ SDK changes the runtime version, update this table. | Component | Upstream license file | |---|---| -| CheatEngine.Client | (the recorded submodule commit) | -| CheatEngine.SDK | (the recorded submodule commit) | +| CheatEngine.Client | (the recorded submodule commit) | +| CheatEngine.SDK | (the recorded submodule commit) | | Microsoft.Extensions.AI.Abstractions 10.10.1 | (the source commit of the package) | | ModelContextProtocol 2.2.0 | and (the source commit of the packages) | | .NET runtime packs 10.0.13 | `LICENSE.TXT` and `THIRD-PARTY-NOTICES.TXT` of the runtime packs | diff --git a/TODO.md b/TODO.md index 7a7058e..145844b 100644 --- a/TODO.md +++ b/TODO.md @@ -1,631 +1,67 @@ -# Continuation checklist - -Work resumed at the user's request on 2026-10-20. This checklist records **incomplete native qualification**; -delete it only when its remaining requirements have actually passed. - -## Latest native checkpoint (current-27, 2026-10-21) - -- [x] Keep the 17 report sessions pinned to exact bundle 06: `artifacts/dist/all-tool-final-20261020-06`, - product `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 - `E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 - `1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`, and installed CE host SHA-256 - `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. No live-session evidence from a - different product or gateway binary was pooled. -- [x] Generate `artifacts/all-tools-goal/all-tools-current-27/tool-results.json` from those 17 sessions and - fresh normalized-checkout Release TRXs. It records **200 verified** and **7 not verified** rows across all - 207 advertised tools, with `nativeQualificationComplete=false`. Report SHA-256: - `E5A8293487BCCF155C28DBB72311FC86832A9F00FD54901F22B0123AD50C2BCE`. The open rows are the six - DBK/DBVM-dependent kernel operations and `symbol_enable_sources`. -- [x] Validate the current source checkout: Release portable **5,838/5,838**, Debug portable **5,838/5,838**, - and NativeLua **766/766**, all with zero failures or skips. Release/Debug solution builds completed with zero - warnings/errors. Both `dotnet format style` and `dotnet format whitespace` verification passed. - TRX SHA-256 values: Release `FB0CFC401CA94A7AD242CAAD2B98E1E6B93D5B4C1E43C2515D5693001442DE0A`, - Debug `2BAA55FFE972CAAA1B2382C33511A9F08C22C3EAE9142D4E0D80D25185321653`, NativeLua - `23BAE4953CC35F2812BB126B83A7E41F97DC0E27C5486C1CAEBDC51105415A8A`. -- [x] Preserve the two historical failures as diagnostic questions, not native tool failures: the exact-bundle - `structure_list` calls pass in workflows and state-workflows; all mapped debugger success/cleanup cases pass - on bundle 06. That bundle includes the reviewed breakpoint-arity correction and deferred trace cleanup after - the callback returns. The old provider `-101889` state and debugger monitor-overlap `SEHException` did not - recur, but neither historical root cause is established. The separate provider trace remains diagnostic-only. -- [x] Keep plugin reload and the completed symbol/lifecycle cases bounded to their passing evidence. Three - in-process plugin disable/enable cycles pass when the owned registry row is seeded before host startup. - Explicit symbol reload and the local DLL/PDB fixture pass. The earlier unseeded zero-row observation does not - establish a product regression. -- [x] Record no-VM kernel evidence: exact-bundle `kernel_get_status` reports DBK/DBVM inactive and an - independent driver inventory finds no matching driver. This qualifies status/environment only; it does not - qualify any of the six DBK/DBVM operations. No driver, service, boot, firmware, VBS or virtualization setting - was changed. -- [x] Keep `symbol_enable_sources` unverified. The only local `ntdll.pdb` does not match the current image, - and no safe independent effect oracle was established without risking public symbol downloads. No source - enable call was made. -- [x] Normalize CRLF in the two test-only files flagged by whitespace verification. The audit at - `artifacts/test-results/all-tool-qualification-20261021/line-ending-normalization-audit.json` (SHA-256 - `4E7C9F78571C2E6616F169E929C1C907380AC5797A2DECA4F49394FF5B7B140A`) proves their normalized source hashes - are unchanged. `LiveCompilerCandidateTests.cs` changed from `72A3C085D66021A306CFC48F71C89BC1F252C680253608BF30E3BE0D4C4888BB` - to `0D28E3480C9DDDA5BCF54739A8A3021F862FFD7B09B0B51685C5AD00C4446CE3`; `LivePluginRegistrySeedTests.cs` - changed from `85F3064C9EFD34690694620C63EC2337DE1FC154CAE79DC7872B531D3A6E14F6` to - `30E433B3C6B1B51C5F8FF70E7CF7F54DFD8B70BE53D31311FA3791DF34CBA09E`. In both cases the normalized-text - hash equals the before hash. The old v5 live-session input manifest remains preserved with its original bytes; - those native sessions preceded this line-ending-only test-source edit, so this checkpoint does not claim the - current files match the old frozen hashes. The test assembly hash consequently changed from frozen - `40AC972ACC4B964AEF3801568472697BB6464DEC5C40BBD81EF93609AF28E338` to current - `B37721965CDC2B0EC38A448A235A5DF43B2494C539F326CB3193EEAD53FCCECF`; the apphost hash remains - `116E40A84557F723F078278BE14FC6B4D98155C775E392453C05E992FB1F0031`. The product plugin/gateway hashes - above are unchanged. -- [ ] Establish the historical provider-state and debugger monitor-overlap causes; current exact-bundle - success is evidence of non-recurrence, not proof of why the old failures occurred. -- [ ] Qualify `symbol_enable_sources` with an independent effect oracle; do not invoke a path that can download - symbols from a public server or infer success from the echoed request. -- [ ] Keep the six DBK/DBVM-dependent tools open until a reviewed suitable environment passes their success and - cleanup oracles. No VM is available for this checkpoint. -- [ ] Explain the original run-root no-reparse refusal (`20261020T170209Z-6007`, `C000050B`); keep its guard. -- [x] Complete the fresh independent re-review of the corrected current-27 validation record at - `artifacts/test-results/all-tool-qualification-20261021/current27-final-validation-independent.json`. - Final verdict: **GO**, no actionable findings. Record SHA-256: - `B41DF8AF659A1765D91D45C6E028955FE8A53CF36755770006640A5925F1B93C`. Its first review caught a - CRLF-sensitive parser error for successful builds; the corrected record reads the exact log lines and shows - both configurations passing. The report itself remains incomplete with seven open native tools. - -## Previous checkpoint (current-26, 2026-10-21) - -- [x] Keep all seventeen report sessions pinned to exact bundle 06: `artifacts/dist/all-tool-final-20261020-06`, - version `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 - `E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 - `1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`, and installed CE host SHA-256 - `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. -- [x] Pass the reviewed assembler scenario `20261021T001320Z-0923`: 9 mapped tool receipts, 2 cleanup - receipts and MTP 1/1. Summary SHA-256 `85FB4F03A647F1480251989E142310AA2B1C34D2429BFC27775D981299316972`, - receipts SHA-256 `239B91358E1B17E2123C6125BBC54F686532D2816FCE89D85BA09D54B9B956F6`, TRX SHA-256 - `09A3DDB31E81C786F4D1CDBC3764D01FCBB3894FC63DD9993AC0D81E090B33FB`. -- [x] Pass compiler export and fixed managed injection in `20261021T000621Z-a348`: 2 mapped tool receipts, - 1 cleanup receipt and MTP 1/1. The held export survived host B shutdown and the owned Framework fixture - returned `7319`. Summary SHA-256 `98D5AB7446DF4EB8400BD469DB7CEAE81B0358493A0E8395A9BB9C15F57AFBD4`, - receipts SHA-256 `D43098C8457BC7C19DF0F4DFA21B361081B7C020D93C51484B6C989AB44EF36F`, TRX SHA-256 - `F7D1CA2B7E5732BD562A789805B42FCC294F805B6CBED914E910E62D8F6FE900`. -- [x] Pass process creation in `20261021T001819Z-ef61`: 1 mapped tool receipt, 1 cleanup receipt and - MTP 1/1. Summary SHA-256 `90433A160F5BD1AFAF488CE8B34CF30D9FE20EC73022FA17898DB936D45BD1E4`, - receipts SHA-256 `ACDE933D78072678C88F1E99E12B17ED4A7344279D76AE4FAD376BA4523BD771`, TRX SHA-256 - `3A4F02645A2C936D426FFE62F7C61E776CE9DA61D0D3BDED76DC9D5FD97EDEFE`. -- [x] Pass Mono in `20261021T001905Z-0c47`: 15 mapped tool receipts, 1 cleanup receipt and MTP 1/1. - Summary SHA-256 `1FDC0CCD0F9F4DE1A3BFB36D43EE1F4D6E7A61F520E69F15B8F3B36591D5F196`, receipts SHA-256 - `BBA6703428CFD816BF5B1AEC8AE7874C841336A0CDCAAA0F0D81BB651147AEC0`, TRX SHA-256 - `B1491A2E32D5CBB76897FF0D304F736952791D18222B6DAEE4B850255C50A6FC`. The preceding v4 Mono attempt - `20261021T000658Z-1f16` stopped before child startup because its generated manifest exceeded the legacy - path limit; the reviewed v5 launcher now preflights the shorter 237-character path. -- [x] Retain the later v5 compiler-injection failure as diagnostic-only: `20261021T001411Z-c26f` has a mapped - `compiler_call_held_export` receipt recording `IsError=true`, `kind=invalid_argument`, and - `hostEffect=not_started`. The separate fixed hold-ready file was not published, so neither compilation nor - injection was attempted. Settings were restored, installed files were unchanged, compiler files were removed, - cleanup failures were zero and all owned processes stopped. Its full API error text was not saved, so the - rejection cause is unknown. The separate same-bundle compiler/injection pass above remains valid. -- [x] Qualify debugger tools with current-bundle success evidence: monitor-free basic run `20261020T225741Z-5c14` - covers attach, break, status, context, stack, continue and detach; event capture `20261020T220121Z-d800` - and execution `20261020T220529Z-dd07` also pass with cleanup. `structure_list` passes in state-workflows and - workflows (`20261020T220755Z-2b6f`, `20261020T223158Z-a2f8`). A separate diagnostic-only replay - (`artifacts/provider-fields-diagnostic/provider-fields-live-20261020T125039Z-8ac7.json`) also passed its - historical provider prefix on an isolated candidate: A/B recorded 118/22 provider-top reads, calibrated - top 0 and 1 against the loaded Lua module, and had no negative tops, read errors, or trace overflow. The old - provider `-101889` and debugger monitor-overlap `SEHException` have not recurred, but their historical causes - remain unknown; the diagnostic was not qualification evidence and no speculative product change was made. -- [x] Qualify plugin reload on a startup-seeded `Plugins64` row. Three in-process disable/enable cycles passed - 1/1 in `20261020T232333Z-da96`; this does not establish why the earlier unseeded attempt saw zero rows after - the Settings form had already been created. That earlier harness seeded the registry only after the form - existed, so it did not establish a product reload regression. Summary SHA-256 `ADB3A4FF821D36FECE6E7B4E8C3D31AB20BCB13A9128571F2D9834C861151BE5`, - receipts SHA-256 `CE4FB6E1574F8E22FF585F86BF7022F29C5506025B147745A7AFB08A6C5D4469`, TRX SHA-256 - `10D80C82568C5EF16D9FD20080C95CD3604A4BA37A8DB203610279E303B67ABA`. -- [x] Check whether the local Windows symbol cache can safely qualify `symbol_enable_sources`. The only local - `ntdll.pdb` cache entry is keyed `2CF5F86ACB68735923D72913BBD9B0E31`, while the current system `ntdll.dll` - (version `10.0.26100.9549`, SHA-256 `651AC9041AF8E6B5EBD331D53163244C0A27D36D1433F0F15CAC1F28C5F2E82C`) - embeds CodeView GUID `C031D45B-0DA1-8A6E-74D0-6ED6AB2DD9E1`, age 1. The local PDB therefore does not match - this image. No source-enable call was made: a fail-closed local-only path and independent source readback - were not established, and the kernel source path still requires DBK/DBVM. -- [x] Obtain no-VM kernel suitability evidence: exact-bundle `kernel_get_status` passed in - `20261020T221714Z-226c`; DBK/DBVM were inactive and the independent driver inventory contained no matching - driver. This is environment/status evidence only; it does not qualify the six kernel operations. -- [x] Regenerate `artifacts/all-tools-goal/all-tools-current-26/tool-results.json` with current-25 evidence, - four passing native sessions, and the two later diagnostic failures. It contains **200 verified** and - **7 not verified** rows across the 207 advertised tools, with `nativeQualificationComplete=false`; 27 rows - were added since current-25. Report SHA-256: - `04168F6F69F5A36143F4F73E9774FB1CE314888F3F9EEB841B77AE2BC2B4166D`. Release portable passed 5,832/5,832 - and NativeLua 757/757. -- [ ] Establish the cause and corrective fix for the historical provider-state failure; the old - `-101889` stack-top result remains unexplained despite current-bundle `structure_list` passes. -- [ ] Establish the cause of the historical debugger monitor-overlap `SEHException`; current-bundle basic, - event and execution scenarios pass, but the old exception's cause remains unknown. -- [ ] Qualify `symbol_enable_sources` with an independent effect oracle. The local `ntdll.pdb` does not match - the current system image; do not infer an effect from echoed request values or invoke a source path that can - download symbols from a public server. -- [ ] Keep the six DBK/DBVM-dependent tools open: `kernel_initialize_dbvm`, `kernel_poll_watch`, - `kernel_read_physical`, `kernel_start_watch`, `kernel_translate_address`, and `kernel_write_physical`. - This host has no VM and DBK/DBVM are inactive; do not change driver, service, boot, firmware, VBS or - virtualization settings without a separately reviewed, recoverable plan and appropriate authorization. -- [ ] Explain the original run-root no-reparse refusal (`20261020T170209Z-6007`, `C000050B`); the guard was - not weakened. -- [x] Complete independent review and final repository/report validation for current-26. Reviewer verdict: GO, - no actionable findings. The independent validation record is - `artifacts/test-results/all-tool-qualification-20261020/current26-final-validation-independent.json`, - SHA-256 `4D2994B22426AD96FD9AE57F820BFECD617C8D0DB73F70CC165A7F19AD155040`; it validates report SHA-256 - `04168F6F69F5A36143F4F73E9774FB1CE314888F3F9EEB841B77AE2BC2B4166D`, has zero session issues, and - rechecks 3,287 frozen inputs with only the declared postrun TODO/README edits. `git diff --check` passes. - -## Previous checkpoint (current-25, 2026-10-21) - -The current-25 report recorded 173 verified and 34 unverified tools across 207 advertised tools. Its report -SHA-256 was `19F232D92161EE2976E99887A9AE75CB7E404A7F128080B7A5572F1A5079DA27`; current-26 adds the -four reviewed native sessions and preserves the failed Mono/compiler attempts as diagnostic observations. - -- [x] Qualify plugin disable/re-enable on the exact bundle. A private `Plugins64` row was seeded before - host A initialized; three in-process cycles passed the MTP scenario 1/1 in `20261020T232333Z-da96`. - Summary SHA-256 `ADB3A4FF821D36FECE6E7B4E8C3D31AB20BCB13A9128571F2D9834C861151BE5`, receipts SHA-256 - `CE4FB6E1574F8E22FF585F86BF7022F29C5506025B147745A7AFB08A6C5D4469`, and TRX SHA-256 - `10D80C82568C5EF16D9FD20080C95CD3604A4BA37A8DB203610279E303B67ABA`. State was restored, installed - files were unchanged, and cleanup failures were zero. The earlier unseeded session remains a failed - observation; the later run passed with startup-seeded state. -- [x] Add six direct smoke receipts from `20261020T233322Z-c49d`. Its MTP case passed 1/1 and the independent - audit at `artifacts/test-results/all-tool-qualification-20261020/final-bundle-06-smoke-native/run-2a296e25f98e4b06a7a891194f810312/postrun-validation-independent.json` - confirmed exact candidate/host identity, all 3,280 frozen input hashes, restored state, unchanged installed - files, zero cleanup failures and no remaining owned processes. The wrapper exited 1 after the pass because - StrictMode read an absent optional `diagnosticOnly` field; the independent audit preserves that boundary. - All four optional gates were true, so this session supplies no disabled-gate refusal evidence. Its six - direct report additions are `instance_list`, `pointer_get_access_info`, `scan_list_scanners`, `scan_delete`, - `speedhack_set_speed`, and `speedhack_get_state`; pointer access uses supplied-fact arithmetic, not a live - register capture. -- [x] Qualify the four fixed C/ABI execution tools in `20261020T234126Z-7aba`; the exact-bundle MTP test - passed 1/1. Summary SHA-256 `046241E91BBDF48321D5B772819981D9441879BDE431EAAC84CF7E5A781E75E0`, receipts SHA-256 - `9AABB5EE2C6C567F94036DABD247B20D61D52CF13881B86EC896A37A357E95D2`, and TRX SHA-256 - `3573259E6C65ECC9D2363F13605D5D7C870D804F35E52363E0543E62A203E014`. `exec_compile_c`, - `exec_call_remote`, `exec_call_method` and `exec_call_local` each have their direct success receipt. - Both owned allocations have separate `freed=true` cleanup receipts; the passing scenario also requires - both allocations to return `not_found`. User state was restored, installed files were unchanged, all 3,280 - frozen inputs matched, cleanup failures and remaining owned processes were zero. Only target-code execution - was enabled; Auto Assembler, unsafe Lua and kernel access stayed disabled. - -## Previous native checkpoint (current-22, 2026-10-21) - -- [x] Complete the reviewed native success and cleanup cases available on exact final bundle 06. - Candidate: `artifacts/dist/all-tool-final-20261020-06`, product - `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 - `E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 - `1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`. - Debugger event capture passed in `20261020T220121Z-d800`; debugger execution/run-to passed in - `20261020T220529Z-dd07`; state workflows passed in `20261020T220755Z-2b6f`; read-only kernel status - passed in `20261020T221714Z-226c`; and the reviewed local DLL/PDB symbol fixture passed in - `20261020T222142Z-7fef`. The workflows scenario then passed its one MTP test in - `20261020T223158Z-a2f8`. All six passing sessions record the exact bundle identity, restored user state, - unchanged installed CE files and zero cleanup failures. State-workflows' launcher postrun count assertion - expected one `host_A_stopped` receipt, but the valid restart plus final cleanup produced two; its separate - read-only postrun validator confirmed the MTP pass, all 3,278 frozen inputs, host restart identities, - receipts, cleanup and zero remaining owned processes. The workflows test also passed 1/1 and its summary - reports restored settings, unchanged installation and zero cleanup failures. Its launcher stopped after the - native pass because StrictMode rejected an omitted optional `release.failed` field. The independent - validator at `artifacts/test-results/all-tool-qualification-20261020/final-bundle-06-workflows-native/run-9f2c7d83e0b14d41a784b62f09c13a65/postrun-validation-independent.json` - originally confirmed all 3,278 frozen input hashes, exact candidate/host identity, release and cleanup - receipts, preference readback/restoration, module resolution and process/settings cleanup. The refreshed - proof records that snapshot check and confirms the only later input changes are the README, TODO, report - generator and regression-test updates; it rechecks the summary, receipts, TRX, runner-log result and current - candidate/host. - The runner's OS exit code was not independently captured. No retry was made. -- [x] Regenerate `artifacts/all-tools-goal/all-tools-current-22/tool-results.json` using exact-bundle-06 - evidence and the retained portable/NativeLua reports. It contains **156 verified** and **51 not verified** - rows across all 207 advertised tools, with `nativeQualificationComplete=false`; 52 rows became newly verified - since current-21, with no regressions. Fifteen of those newly verified rows are read-only and have no - tool-specific cleanup receipt: the report records `cleanupRequired=false`, `cleanupReceiptPresent=false`, - and `cleanupSatisfied=true` instead of claiming a receipt exists. SHA-256: - `66E3D5F31515DC1ACB896D74BF97CCD7CB210DC9FF390427BB7DCE0EBD7C5408`. Six passing sessions above add - debugger, state, `kernel_get_status`, symbol fixture, runtime/resource, reversible module-preference and - owned-module resolution evidence. The seventh included session is the failed plugin lifecycle attempt below - and does not qualify a tool. -- [x] Final-source Release portable validation passed 5,832/5,832 tests, and NativeLua passed 757/757, - with no failures or skips. The report reuses the Release TRX for gateway and portable inputs; current-source - builds and Debug validation were completed before the final native batch. -- [x] Added and ran `Report_NoCleanupReceiptMapping_MarksCleanupNotApplicable` against the real report script. - The no-restore Debug build passed with zero warnings/errors; native MTP discovery found the fully qualified - test and the focused run passed 1/1. TRX SHA-256: - `7B76CE533BD63B8FC89DA2F114AE72D660415A7EA58365C214AA1F9F5BC69635`. The normal sandbox denied the - runner's named-pipe IPC, so test execution used the approved elevated invocation. This verifies report - metadata only and adds no native MCP qualification. -- [ ] Resolve the provider-state root cause. The older `structure_list` session `20261020T092949Z-4423` - received stack top `-101889`; the final-bundle state and workflows runs passed their `structure_list` cases, - but did not reproduce or explain the earlier exception. The tool now has passing exact-bundle evidence; - containment and non-reproduction are not a root-cause fix. -- [x] Qualify same-process plugin reload. The current-22 attempt `20261020T221415Z-d111` observed zero - owned rows after load; it does not establish why that observation occurred. Exact-bundle current-25 session - `20261020T232333Z-da96` seeds the owned row before host startup and passes three in-process disable/enable - cycles with cleanup and restoration verified. -- [ ] Qualify `symbol_enable_sources` with an independent effect oracle. Do not infer an effect from echoed - request values; avoid the Windows source path that may download symbols and the kernel path that requires - DBK/DBVM. -- [ ] Obtain suitable native evidence for six environment-dependent kernel operations: - `kernel_initialize_dbvm`, `kernel_poll_watch`, `kernel_read_physical`, `kernel_start_watch`, - `kernel_translate_address`, and `kernel_write_physical`. Only read-only `kernel_get_status` passed on - this host. The host inventory, status receipt and per-operation safety/evidence requirements are recorded - in `docs/qualification/all-tools/later-kernel-test.md`. No DBK/DBVM initialization, driver, VBS, service - or boot changes were made. -- [ ] Explain the original run-root no-reparse refusal (`20261020T170209Z-6007`, `C000050B`); the - path-specific refusal remains unresolved and the guard was not weakened. -- [x] Complete the independent final review of current-22, including the refreshed workflows postrun - validation and the failed plugin-reload boundary. The reviewer returned GO with no actionable findings; - diagnostic-only summaries are normalized to failed before report qualification. - -## Historical checkpoint (pre final bundle 06) - -- [x] Finish the reviewed live success and cleanup cases on the exact bundle 6 candidate. - Compiler and managed injection passed in `20261020T175440Z-0fa2`; the isolated basic debugger passed in - `20261020T180500Z-3fca`; read-only kernel status passed in `20261020T180540Z-55c5`; and explicit - all-scope symbol reload passed in `20261020T181211Z-6bc0`. Every passing session restored user state, - left the installed CE tree unchanged and reported no cleanup failures. The symbol receipt shows an empty - pre-reload result and one exact module-filtered export afterward at the independently parsed PE address, - with `symbolsLoaded=true`. -- [x] Generate `artifacts/all-tools-goal/all-tools-current-19/tool-results.json` from 19 same-candidate - sessions and the preserved passing gateway, portable and NativeLua reports. The report records **189 - verified tools** (188 backend plus gateway `instance_list`), **6 diagnostic-only**, **12 not verified**, - and `nativeQualificationComplete=false`. The compiler, managed-injection, kernel-status and symbol-reload - rows plus six basic debugger rows now have same-session success and cleanup receipts. SHA-256: - `F3B7FCBA80C2D60159070A0454DF132E751952C807B352BD472C966C35EEB268`. Candidate version: - `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`; plugin SHA-256 - `5F86B15826142FD655498AA1E8E5AF8A380A488D27E55AF939679A4B7A72F9FA`; gateway SHA-256 - `059727F11E310988C501C4DB494529660FF40D938B20638805AF05924E9ADBB3`. -- [x] Validate current source: Release and Debug builds completed with zero warnings/errors; each portable run - passed **5,793** tests with zero failures or skips. NativeLua passed **757** tests with zero failures or skips. - Fresh TRXs are under `artifacts/test-results/all-tool-qualification-20261020/final/current-release-portable/`, - `current-debug-portable/` and `current-native-lua/`. -- [ ] Resolve the provider-state root cause. The old `structure_list` session `20261020T092949Z-4423` - received stack top `-101889`; current-source diagnostics and later workflows did not reproduce it. The - containment guards and non-reproduction are not a root-cause fix. -- [ ] Resolve the advanced debugger run-to cleanup failure before qualifying event capture. Session - `20261020T162659Z-23f9` reached its run-to stop but detach threw `SEHException` during monitor overlap. - The isolated basic debugger pass does not cover run-to or debugger event tools. Six run-to/breakpoint/register/ - step rows remain diagnostic-only and five debugger event rows remain not verified. -- [ ] Resolve same-process plugin reload. Diagnostic `20261020T133119Z-43a6` loaded the plugin but the - Settings bridge found zero owned plugin rows. No supported independent reload discriminator or safe fix has - been established; the case remains unverified. -- [ ] Qualify `symbol_enable_sources` with an independent readback/effect oracle. Do not infer an effect from - its echoed request. -- [ ] Obtain safe native evidence for the six remaining kernel operations. Only read-only - `kernel_get_status` passed. The other operations require DBK/DBVM; this host has no VM, and no driver, - DBVM, VBS or service state was changed. -- [x] Run the independent `state-workflows` scenario on owned x64 targets with the same bundle 6. - Session `20261020T171316Z-d57b` passed all 78 mapped state/record/pointer/file/code cases, target/gateway/host - restarts and persisted-note recovery. Independent review checked every mapped receipt and cleanup receipt, - all four disabled gates, owned allowlists, unchanged inputs and installed files, restored settings and no - remaining owned processes. The Release harness build has zero warnings/errors and 103 focused tests pass. -- [x] Generate `artifacts/all-tools-goal/all-tools-current-16/tool-results.json`: **133 verified** (132 backend - plus gateway discovery), **12 diagnostic-only**, **62 unverified**, `nativeQualificationComplete=false`. - This combines nine passing sessions, the debugger diagnostic and the failed original-root state session - on the same exact product binaries. Failed and diagnostic observations do not qualify success. -- [ ] Explain the original run-root's no-reparse refusal. Failed state session `20261020T170209Z-6007` - stopped before `pointer_save_map` began; its cleanup and restoration passed. A read-only standalone - call into the same Core DLL reproduces `C000050B` on children below the old `runs` directory despite - ordinary directory attributes. The identical guarded walk and native workload pass under the new - `Local/Temp/CheatEngine.Mcp.LiveQualification` root. No guard, native flag or product code was weakened; - changing the fixture root is not a fix for the original path-specific refusal. -- [x] Run the bounded inspection scenario on both owned x64 and x86 targets using bundle 6. - Sessions `20261020T164951Z-0ffb` and `20261020T165142Z-1eee` passed, including gateway restart, - allocation release and restoration of original module preferences. Independent review confirmed both - single-test TRXs, all intended receipts, four disabled optional gates, empty file/table allowlists, - unchanged frozen inputs, restored user settings and installed files, and no remaining owned processes. - The Release harness build has zero warnings/errors; all 101 focused admission/options/cleanup tests pass. -- [x] Preserve the earlier `artifacts/all-tools-goal/all-tools-current-15/tool-results.json` from the same bundle's - eight passing sessions and one failed diagnostic: **55 verified** (54 backend plus gateway discovery), - **12 diagnostic-only**, **140 unverified**, and `nativeQualificationComplete=false`. - The two inspection sessions add 16 verified rows: the 14 read/utility/symbol-preference cases plus - process attachment and disabled-gate runtime information. Diagnostic observations remain unqualified. -- [ ] Identify the cause of the original run-root's no-reparse refusal documented above; the path-specific - `C000050B` reproducer remains unexplained. -- [ ] Complete the required independent final review and address any actionable findings. Current-source - builds and test suites passed above, but the remaining native cases are still open. - -## Checkpoint validation - -- [x] Release solution build: zero warnings and errors (`artifacts/pause-checkpoint-build-2.log`). -- [x] Standalone Lua suite: 743 passed, zero failures/skips (`artifacts/pause-checkpoint-lua-2.log`). -- [x] Changed C# whitespace verification passed (`artifacts/pause-checkpoint-format-1.log`). -- [x] Portable Release suite: 5,728 passed, zero failures/skips (`artifacts/pause-checkpoint-portable-2.log`). -- [x] Run the saved checkpoint through Debug/CI and inspect the result after publication. - GitHub build run `39314293272` passed at `1f987742b7fcd7760057a19508ddacbf2aad4bd8`. - Fresh local Debug build: zero warnings/errors; portable suite: 5,728 passed, zero failures/skips. - -## Resumed verification on 2026-10-20 - -- [x] Correct stale Mono fixture identity validation, rebuild the owned fixture from current source, and pass all - 32 focused manifest/build-identity regressions. Preserve the old fixture under an ignored checkpoint folder. - Fresh standalone `continue-mono-preflight.json` verified matching source/runtime/EXE identities, PID/nonce, - nonzero pinned object pointer, primitive values, warmup result, stop marker and exit code zero. - This qualifies no MCP Mono tool. -- [x] Publish the isolated `artifacts/dist/continue-20261020-1` bundle, including its Native AOT gateway smoke check. -- [x] Verify the resumed source with 5,736 portable tests in both Debug and Release and 743 standalone Lua tests, - all with zero failures/skips. Both full solution builds passed with zero warnings/errors. - The Release report includes all 11 seeded fuzz tests; both style and whitespace verification passed. -- [x] Correct the Tools README's test filters and verify discovery of 2,005 Tools and 320 Contract cases. -- [x] Fix contradictory remaining-work text for verified report rows; reproduce the old failure and pass all - 14 cumulative-report regressions while retaining failed/diagnostic evidence boundaries. -- [x] Run the isolated process-create scenario: `20261020T092911Z-916a` passed, restored user settings, preserved - installed CE files and reported no cleanup failures. -- [ ] Resolve the ordinary workflow's reproduced native Lua-state failure before continuing native verification. - `20261020T092949Z-4423` completed allocation release and scanner-stop cleanup, then `structure_list` received - a provider state with stack top `-101889`. The complete session failed; settings restoration, installed-file - preservation and host cleanup passed. Its earlier operation receipts do not qualify native success. -- [x] Prepare and review a current-source provider diagnostic. The old instrumented SDK predates current - provider-stack guards and same-state callback cleanup and must not be replayed unchanged. - The reviewed current-source prefix ran once in `20261020T100317Z-52fd`; it did not reproduce the state failure - and restored settings with no cleanup failures. Host A produced no trace, so the diagnostic postcheck failed. - Host B retained 150 complete records, including 88 provider records. No native tool is qualified by this run. -- [x] Fix the incomplete diagnostic capture, verify synchronous creation of the owned trace directory and - obtain complete captures in another independently reviewed bounded run. - The candidate-only helper passed 18 focused cases, including a junction escape refusal. Independent review - verified all 282 frozen inputs. Run `20261020T102110Z-3f05` retained 946 complete host-A records and 150 - host-B records, with no overflow/failure record. Settings restoration, installed-file preservation and - owned-process cleanup passed. The failure did not recur; its root cause remains unresolved. -- [x] Repair private Mono provisioning and its effect oracle: four collector-copy regressions and three legacy - path-limit boundaries pass. The refreshed source passes 5,743 portable cases in each of Debug and Release, - plus 743 standalone Lua cases, all with zero failures/skips. Both builds and both formatting checks pass. - `continue-20261020-3` passed publication, packaged-plugin catalog validation and Native AOT gateway smoke. - Review confirmed exactly four intended golden-line changes: warning descriptions and document sizes. -- [x] Complete the current-bundle Mono scenario `20261020T113335Z-7c44`: all 15 Mono tool receipts passed, - including both one-shot method invocations, exact object fields and the complete instance-search page. - Collector detach, job absence, target exit code zero, settings restoration, installed-file preservation - and absence of all owned processes were independently checked. -- [x] Repeat smoke, isolated process-create, assembler/API-hook generation, fixed C/ABI and DLL/PDB scenarios - on `continue-20261020-3`. Runs `20261020T113552Z-757d`, `20261020T113649Z-0577`, - `20261020T113725Z-a97b`, `20261020T113806Z-2760` and `20261020T113838Z-0c99` passed with cleanup. - Smoke used the plugin's enabled default gates; it made no kernel calls. The other scenarios assert their - explicit capability configuration. Automatic symbol discovery still leaves explicit reload inconclusive. -- [x] Correct diagnostic dispatch before a new debugger attempt: diagnostic mode must skip the ordinary - break/context cycle. Two regressions failed against the old order; all four focused cases pass after - the correction. Independent bounded review found no remaining dispatch issue. -- [x] Run that isolated diagnostic once: `20261020T114533Z-36ae` observed an installed `CC` entry byte - and advancing owned hit/progress counters, but zero callbacks. It made no context request; callback - restoration, breakpoint removal, detach and sandbox cleanup passed. This qualifies no debugger tool. -- [x] Correct breakpoint API overload arity: omit absent optional method/callback slots rather than passing - explicit `nil`. Three of eight argument-shape regressions failed before the fix; all eight pass after it, - and all 751 standalone Lua cases pass. Independent review found no remaining arity issue. - New local distribution `continue-20261020-4` passed its isolated plugin and Native AOT gateway probes. -- [x] Verify that correction natively: diagnostic `20261020T115504Z-1dfb` observed one exact owned callback, - matching context, restored callback/breakpoint/debugger state and advancing post-detach counters. It remains - diagnostic-only. Ordinary debugger session `20261020T115551Z-681a` separately passed the existing basic and - first execution group with cleanup. Six independent scenarios repeated successfully on the same bundle. - Report `all-tools-current-6` verifies 50 bounded native cases on `continue-20261020-4`, with 157 unverified. -- [x] Require explicit host acknowledgement for thread-ignore changes; four false-success cases reproduced - the old behavior. All six acknowledgement cases and 757 standalone Lua cases pass after the correction. -- [x] Prepare and independently review the five remaining debugger event cases, with exact owned thread/ - instruction oracles, 23 passing oracle regressions, empty job admission and bounded restoration proof. -- [ ] Complete their native run. Bundle `continue-20261020-5` passed isolated-plugin and Native AOT probes, - but session `20261020T121721Z-6535` crashed host A during existing run-to cleanup, before the new cases. - Windows Application event 1000 independently records PID 72728 / `0x11C18`, access violation `0xc0000005`, - unknown faulting module at `0x7ffb09b29820`. Settings and installed files were preserved and all owned - processes exited. Missing host-A disable evidence remains a cleanup failure; no native tool is qualified. - Original evidence is retained in `artifacts/test-results/continue-20261020/debugger-crash-6535`. -- [x] Run a fresh independent Astra checkpoint review. It found no implementation issue; its stale suspension - wording finding is corrected. This does not replace final verification after the remaining native work. -- [x] Verify the current debugger/event source with 5,774 portable tests in each of Release and Debug and - 757 standalone Lua tests, all with zero failures/skips. Both solution builds passed without warnings/errors. - One earlier loaded Debug attempt hit the notebook directory-lease contention timeout; its focused case - and the complete sequential rerun passed without changing the notebook implementation or its test. - Report `all-tools-current-7` retains 207 routing passes, 13 failed-session observations and 194 unverified - rows for bundle 5. None of the crashed session's receipts establishes native success. -- [x] Repeat all six independent scenarios on bundle 5 after the final checks and a fresh input freeze: - smoke `20261020T122756Z-115c`, process-create `20261020T122829Z-828c`, assembler `20261020T122901Z-eac8`, - fixed C/ABI `20261020T122940Z-16c2`, symbols `20261020T123008Z-bc8d` and Mono `20261020T123037Z-35e9`. - All passed with settings/install preservation and no cleanup failures or remaining owned processes. - Post-run hash verification matched all frozen source/dependency/binary inputs. `all-tools-current-8` - now records 38 verified native cases, 12 failed-session observations and 157 unverified rows for bundle 5. - Ordinary debugger and x64/x86 workflow retries remain suspended pending new diagnostic evidence. -- [x] Extend the private provider diagnostic to version-2 atomic field capture, with bounded self-memory reads, - exact loaded-Lua module identity, per-state/lifetime calibration and contained diagnostic failures. - Independent review findings were fixed; 20 focused SDK cases and 92 harness cases pass. The packaged plugin - probe and embedded-SDK equality checks passed; independent preflight verified all 3,281 frozen inputs. - Single-use run `20261020T125039Z-8ac7` retained 946 complete A records and 150 B records, with matching - loaded-module hashes and same-state healthy top-0/top-1 calibration. No incomplete, overflow or first-failure - record occurred. Settings/install preservation, all owned process exits and post-run input hashes passed. - Report: `artifacts/provider-fields-diagnostic/provider-fields-live-20261020T125039Z-8ac7.json`. - The failure did not recur. This validates capture only; it establishes no root cause or native tool success. -- [x] Map the existing passing native gateway discovery receipt with a gateway-only scope. All 17 report - regressions pass, including rejection of interchanged gateway/backend scopes. Report - `artifacts/all-tools-goal/all-tools-current-9/tool-results.json` reuses the same exact bundle-5 sessions: - 39 verified cases (38 backend and one gateway-local), 12 failed-session observations and 156 unverified. - This bookkeeping correction adds no backend native qualification. -- [x] Run one independently reviewed private plugin-registry diagnostic, `20261020T133119Z-43a6`. - Exactly the owned registry pair was seeded and verified before the single requested cycle. The Settings - checklist still reported zero rows before and after reload (`loading=nil`); no transition was reached. - The failed hypothesis is diagnostic-only. User settings, installed files, all 3,266 frozen inputs and - owned-process cleanup passed; it establishes no cause or native success. Do not repeat it unchanged. - Report: `artifacts/plugin-registry-diagnostic/plugin-registry-live-20261020T133119Z-43a6.json`. -- [x] Refresh source validation after the gateway-report changes: 5,777 portable cases pass in each of Debug - and Release, and all 757 standalone Lua cases pass, with zero failures/skips. Both solution builds report - zero warnings/errors; style and whitespace verification report no changes. Format workspace loading - reports duplicate analyzer-release documents and a source-generator metadata reference warning. - Report `artifacts/all-tools-goal/all-tools-current-10/tool-results.json` uses these fresh Release/Lua TRXs - and the same seven bundle-5 sessions: 39 verified (38 backend + gateway discovery), 12 failed-session - observations and 156 unverified. The private plugin diagnostic remains excluded. -- [x] Build and independently review a private external crash-capture helper, then verify it only against - its own x64 fixture. Run `d36ed920e2cd4260a65e28cb667f8875` passes 20 admission-refusal and six runtime - cases: clean stop, duration detach, handled breakpoint/AV, one second-chance AV dump and survival after - confirmed exit-kill disablement followed by abrupt helper exit. All owned processes exited. - Four exclusions remain explicit: x86, oversized executable, recovery fault injection and redundant parser - permutations. The first fixture attempt failed before helper startup because of reader sharing mode; - the corrected bounded readers pass five local probes and independent review. This validates fixture - capture only; it qualifies no CE tool or recovery-injection branch. - Rebuilt v3 run `8d13ede4ac5c469cbef5b42b3ed35167` repeats all 26 passing cases and four explicit exclusions, - with absolute compiler/linker identities and cleared `CL`, `_CL_` and `LINK` environment flags. -- [ ] Independently review the separate private CE integration and capture one bounded debugger failure - group with that helper. The reviewed 3,763-file snapshot's one-use attempt - `c035416b6a624bcc9986b425a77c9cbc` exited before sandbox construction: its manifest validator inserted - already-parented JSON nodes into another array. A read-only preflight reproduced the exact exception. - No sandbox/helper evidence was produced; installed files and frozen inputs were unchanged and no owned - processes remained. The launcher conservatively records restoration unconfirmed because no sandbox - summary exists. Preserve this failed attempt; fix and test admission plus durable runner output in a new - reviewed snapshot before another attempt. Outer-debugger non-reproduction cannot establish product stability. - Reviewed v2 attempt `0eb66dff7959434c801cc37fd9b4dead`, session `20261020T151945Z-3c25`, reached both private - hosts and helper readiness, but `debugger_detach` returned an unknown-outcome `SEHException` during the basic - group, before the first execution group. The helper retained 224 events: one first-chance invalid-handle - exception, one thread-name exception and ten CLR exceptions, all passed through; no second-chance AV or - dump occurred. Read-only cleanup confirmed detached state. Helper stop/detach and exit, user settings, - installed files, frozen inputs and all owned process exits passed independent evidence review. - This failed diagnostic qualifies no tool and does not cover the original run-to crash. Preserve the - consumed attempt; investigate monitor timing and the native detach path before any new reviewed probe. - Reviewed V3 attempt `57a45025da89451f81f6be24af22f7ba`, session `20261020T162659Z-23f9`, ran basic checks - without the monitor, then attached it before the first execution group. Basic detach succeeded and the - execution assertions reached run-to; cleanup `debugger_detach` then failed with `SEHException` - (`errorId=c9261948eb11f175`). The helper retained 221 events, including ten first-chance exceptions: - one thread-name, one invalid-handle and eight CLR exceptions, all passed through. There was no AV or dump. - Independent review confirmed helper stop/detach/exit, restored settings, unchanged installed files and - all 3,777 frozen inputs, and no owned processes. This narrows the monitor-overlap correlation; it does - not prove observer causality, identify the native callee, resolve the original crash or qualify a tool. - Preserve the consumed diagnostic and do not repeat the unchanged monitor scenario. - Report `artifacts/all-tools-goal/all-tools-current-14/tool-results.json` retains the six passing bundle-6 - sessions plus this diagnostic: 39 verified, 12 diagnostic-only and 156 unverified rows. The set of - verified tools is unchanged; no diagnostic receipt is promoted to native success. -- [x] Correct `symbol_reload`'s polling instruction: `runtime_get_overview` has no `symbolsLoaded` field; - `symbol_find` supplies it. Update both tool-description goldens and pass all five contract snapshots - after a zero-warning/error Release build. This later description edit is not in bundle 5; its native - receipts and report remain evidence for that old exact bundle. Final package/check repetitions remain due. -- [x] Refresh verification after that description edit: portable Release and Debug each pass 5,777 cases; - standalone Lua passes 757, all without failures/skips. The Debug rebuild has zero warnings/errors; - style, whitespace and Git whitespace checks pass. Report - `artifacts/all-tools-goal/all-tools-current-11/tool-results.json` uses the fresh Release/Lua TRXs and the - same seven old bundle-5 sessions: 39 verified, 12 failed-session observations and 156 unverified rows. - No private crash/symbol diagnostic or newer product bundle is qualified by this refresh. -- [x] Retain unexpected operation-body exception types and managed frames in local dispatch logs, using - the existing dispatcher diagnostic format without exception messages, data or source-file paths. - Two regressions fail on the old implementation and pass with the change. The 92 focused dispatch, - error-correlation and logging cases pass in both Release and Debug, with zero failures/skips; - both builds have zero warnings/errors and independent source review found no blockers. - This offline diagnostic improvement does not resolve a native failure or qualify a tool. The full - 5,777-case runs and report 11 above predate this edit; final source/package verification remains due. -- [x] Refresh full offline validation after the logging edit: Release and Debug solution builds have zero - warnings/errors; portable suites each pass 5,779 cases and standalone Lua passes 757, with zero - failures/skips. All 11 seeded fuzz cases pass in each portable run. Full style, whitespace and Git - whitespace checks pass. Report `artifacts/all-tools-goal/all-tools-current-12/tool-results.json` uses - the fresh Release/Lua TRXs and the same seven bundle-5 sessions. All 14 historical summary/receipt - hashes are unchanged. Classifications remain 39 verified (38 backend plus gateway discovery), 12 - failed-session observations and 156 unverified rows; no replacement bundle or native fix is qualified. -- [x] Resume live testing after renewed user authorization and repeat six reviewed scenarios on fresh - `continue-20261020-6`: smoke `20261020T160645Z-e46d`, process-create `20261020T160750Z-1b18`, assembler - `20261020T160814Z-f8ee`, C/ABI execution `20261020T160848Z-7b1b`, DLL/PDB `20261020T160912Z-1be0`, and - Mono `20261020T160938Z-bcb2`. All six live TRXs pass their single intended case. Independent review - confirms exact success/cleanup receipts, restored settings, unchanged installed CE files, all 9,153 - frozen input hashes and absence of every recorded host, target, child and Mono PID. - Report `artifacts/all-tools-goal/all-tools-current-13/tool-results.json` contains only bundle-6 evidence: - 39 verified rows (38 backend plus gateway discovery), 168 unverified and zero failed-session observations. - The previous bundle's debugger failures remain unresolved historical evidence, not successful new-bundle - cases. No debugger attachment/mutation or kernel call was made; smoke queried read-only debugger status. - This checkpoint does not satisfy final all-tool qualification or the required final independent review. - -The temporary live-testing hold after repeated cybersecurity prompts was lifted by the user's explicit -request to do the live tests. Ordinary debugger/workflow retries still require new diagnostic evidence -and review; kernel tests remain planned for a later attended session on this PC. Preserve the remaining -unchecked qualification items and existing safety checks. - -The initial checkpoint tests exposed a Mono numeric-type mismatch and an outdated debugger receipt -assertion. Those were corrected before the final checkpoint validation. No new CE session was started -after the pause request. - -## First steps next time - -1. Read this file, `docs/mcp-alternatives-research.md`, and `docs/qualification/all-tools/README.md`. - Check actual Git state and CI before making changes. Preserve checked and unchecked evidence boundaries. -2. Rebuild after any source edit. Do not rebuild a running test apphost or change tracked sources/binaries - during a live native session. Recreate the prepared input hash manifest after every source/binary change. -3. Verify all owned CE/Mono/fixture processes from the previous run have exited and user settings restoration - succeeded. Use private CE copies and owned targets; preserve the installed CE directory and user state. -4. Continue the independent scenarios below. Check off an item only after its assertions **and cleanup** pass. - -## Prepared work awaiting native verification - -- [ ] Repeat x64 `workflows` on the corrected exact bundle with the bounded manifest reader and isolated process-create case. - The earlier run completed file-target recovery, managed object work and signature generation, but its - later process-create failure invalidated the whole session. Finish target/gateway/host restarts and note persistence. -- [ ] Run the same updated `workflows` scenario on x86. Keep pointer-width and physical module-path checks. -- [x] Run the isolated `process-create` scenario. The unchanged Framework executable worked from a short path - and exited before `Main` at a 253-character executable path. The fixture now copies to `target.exe`, rejects - paths >=240 before launch, and records the raw create response before waiting for its manifest. -- [x] Run the private Mono scenario and qualify all 15 tools, including the fixed static and instance calls, - exact object fields, complete instance-search page, collector detach, job absence and owned target exit. - Only target-code execution is enabled; the other three gates remain disabled. Each method is invoked once; - never retry an uncertain invocation. - Attempts `20261020T102640Z-8602` and `20261020T102945Z-4038` failed before method invocation: first the - legacy runtime's 290-character temporary manifest path, then the missing private collector DLL. - Both restored settings, preserved installed files and cleaned up owned processes. The shorter root passed - a standalone fixture preflight. The harness now checks the path limit and restores only the reviewed - collector DLL for Mono; its OS module path/hash oracle leaves optional patch/table effects unqualified. - Run `20261020T105656Z-fce5` then attached and verified the loaded collector but failed assembly selection - before method invocation. The fixture compared the image name to the EXE filename rather than its CLI - assembly metadata name. The correction passes all 43 Mono fixture cases in Debug and Release; the raw - assembly list is retained before assertions. Passing session `20261020T113335Z-7c44` now qualifies these - 15 bounded cases on `continue-20261020-3`; optional patch/table effects remain unqualified. -- [x] Run the revised debugger transition diagnostic. It captures unconditional callback count/raw primitive - values plus independent owned entry-byte/hit/progress snapshots. Request context only after the exact owned - thread/RIP event. Diagnostic sessions must never count as verified tool success. -- [x] Repeat the isolated transition diagnostic on `continue-20261020-4` after the breakpoint arity fix, - then finish the ordinary debugger success cases only if the exact owned event and cleanup pass. - Both the diagnostic and existing ordinary group passed on that bundle. The later bundle-5 crash above - is unresolved, so further ordinary debugger attempts remain suspended pending new diagnostic evidence. -- [ ] Use that evidence to finish debugger capture/trace/thread-ignore success cases and clean up every job, - breakpoint, callback and debugger attachment. Do not infer breakpoint installation from list presence alone. -- [ ] Resolve same-process plugin reload. The reviewed Settings bridge still observes zero plugin rows after - `loadPlugin` succeeds. No supported Lua-only discriminator or fix has been established; do not bypass - ownership guards or guess unmanaged callback addresses. -- [ ] Resolve the intermittent SDK frame-restoration/compiler failure with new evidence; non-reproduction is - diagnostic only. Re-run the compiler, managed-injection, C/ABI and patch cases on the final exact bundle. -- [ ] Finish remaining symbol source/reload cases without mistaking automatic discovery for an explicit effect. -- [ ] Qualify DBK/DBVM operations only in a suitable reviewed environment. Refusals and portable doubles do not - establish successful kernel operation. - The user requested no VM. Read-only inventory on 2026-10-20 reported `HypervisorPresent=true` and - `VirtualizationBasedSecurityStatus=2`; no drivers, virtualization settings or services were changed. - On 2026-10-20 the user selected a later kernel test on this PC. Prepare the prerequisites and keep these - cases unchecked until that separately arranged test; do not disable VBS or change drivers/settings now. - The proposed admission, owned-page oracles and recovery requirements are in - `docs/qualification/all-tools/later-kernel-test.md`; they are a plan, not execution evidence. - -## Mono prerequisite and local assets - -- [x] Verified the official Mono 6.12.0.206 x64 MSI with a valid Microsoft signature; extracted privately, - without a global installation. -- [x] Inspected shipped `mscorlib.dll` IL and `object.h`: legacy 32-bit GC handle bits map to the native - `mono_gchandle_get_target(uint32_t)` C ABI and return the object header pointer. -- [x] Built and ran the standalone owned fixture: matching PID/hash/nonce, nonzero pinned object pointer, - expected primitive values, stop marker and exit code zero. This qualifies no MCP Mono tool. - -Local ignored runtime: `artifacts/fixtures/mono-6.12.0.206-x64/extracted/runtime`. -MSI provenance and extraction inventories are in the parent folder. MSI SHA-256: -`4125F57D97CFA88257915EDC969E913DE198CD8E22396A29849037479A0AC368`. -Build the fixed helper with `eng/Build-LiveMonoTarget.ps1`; it refuses existing output files, checks reparse -ancestors, pins runtime inputs, and writes the identity sidecar under `artifacts/live-mono-target`. -The legacy compiler's output is not deterministic: pin actual EXE bytes in the prepared run manifest; -do not replace that with a transient hardcoded EXE hash in tracked test source. - -Local runner helpers remain ignored in `artifacts/issue-edits`: -`Save-RoadmapInputs.ps1` (use `-IncludeMono` for the complete private runtime input set) and -`Invoke-RoadmapSourceWorkflows.ps1` (includes `process-create`, `mono` and the debugger diagnostic switch). -The reviewed `Invoke-CurrentMono.ps1` uses the direct apphost, a shorter independent run root and an outer -deadline that preserves the cleanup-owning harness; it independently checks the separate Mono PID as well. -Raw live artifacts remain in the workstation's `CheatEngine.Mcp.LiveQualification/runs` folder. - -## Evidence and completion - -- [x] Generate cumulative all-tool evidence with `eng/Write-AllToolQualificationReport.ps1 -LiveSummaries`. - Pass a PowerShell array. Sessions must have identical product version/plugin/gateway hashes; operation - success receipt and each required cleanup receipt must appear exactly once in the same passing session. - Where the map says cleanup is `none`, record cleanup as not required, not as a present receipt. Keep - failed/diagnostic observations separate. Every one of the 207 catalog entries needs its own honest evidence classification. - Current report: `artifacts/test-results/continue-20261020/all-tools-2/tool-results.json` (207 routing passes, - one verified native case, 12 failed-session observations, 194 `not_verified` rows). - New exact-bundle report: `artifacts/test-results/continue-20261020/all-tools-current-5/tool-results.json` - for `continue-20261020-3`: 207 routing passes, 38 verified native cases and 169 `not_verified` rows. - No results from different plugin/gateway bytes were pooled. - These 38 cases must be repeated on the arity-corrected `continue-20261020-4` before they qualify that bundle. - Those repetitions passed: `all-tools-current-6` contains 50 verified / 157 unverified rows for bundle 4, - including 12 independently verified debugger cases. Bundle 5's independent repetitions passed in - `all-tools-current-8`: 38 verified cases, 12 failed-session observations and 157 unverified rows. - Its debugger crash remains unresolved; bundle 4's debugger results do not transfer to bundle 5. -- [ ] Re-run every required success case on the final exact replacement bundle, with restart and cleanup proof. -- [ ] Update the roadmap and all-tools checklist from those passing receipts. Leave environment blockers unchecked. -- [ ] Run a **fresh independent Astra subagent** for final verification; fix findings and repeat until no - actionable finding remains. Earlier bounded reviews do not satisfy this final requirement. -- [ ] Complete required Release/Debug/formatting/fuzzing checks, publish the verified final main state and verify - its remote commit. Do not create another beta release until the user requests it. - -Pinned dependency mains at this checkpoint: Client `5c0d3057c040920665579ca3b0f464f3cd865317` (merged PR 69), -SDK `7c5b681a4d8055129d7d51b28931e2f6c6d87361` (merged PR 99). Preserve Git submodules rather than reverting to NuGet. -Package 14's frozen product version is `2.0.0-beta.4+cfb2421426f32edcc8986caa1be1896872f7a4e7`; -its successful symbol session was `20261019T184727Z-76aa`. See the all-tools README for exact hashes and failures. +# Remaining work + +Cleanup checkpoint: 2026-10-25. This is the only active checklist. + +The user has asked to stop the repeated qualification loop and tidy the repository. +The records below remain open where evidence is missing; they are not instructions to +restart live probes. Resume a blocked item only when its listed prerequisite changes +and the user requests that work. Do not repeat an unchanged diagnostic attempt. + +## Evidence already recorded + +- Exact bundle 06 / current-27: **200 of 207 advertised tools verified**, seven unverified. + This evidence belongs to that bundle, not to later source builds. +- Active plugin disable/re-enable, capture cleanup, allocation release and old-port closure + passed in the separately pinned `20261022T215930Z-2235` session. +- The `C000050B` run-root refusal has a bounded, high-confidence BindFlt redirect diagnosis. + Keep the no-reparse guard; the trace limitations remain part of the finding. +- The source-built plugin was loaded and a bounded memory-read smoke check passed on + 2026-10-25. This does not qualify its Client tuple or the complete native tool catalog. + +Exact hashes, failures, receipts and limitations remain in local ignored artifacts and +Git history. The removed run journals are backed up under +`artifacts/cleanup-20261025/document-history/`; they are not additional tasks. + +## Blocked native work + +- [ ] **Historical provider-state failure:** establish the cause of the old `-101889` + state. Resume only with new evidence containing the provider pointer, thread IDs, + Lua state identity/generation and raw fields, alongside healthy-state calibration. + Successful replays establish non-reproduction only. +- [ ] **Historical debugger/SDK failures:** establish the monitor-overlap `SEHException` + cause and account for the earlier frame-restoration/compiler failure. Resume only with + a matching native dump/stack, event sequence and exact module identities, or another + concrete new artifact. Do not rerun the unchanged observer scenario. +- [ ] **`symbol_enable_sources`:** obtain a reviewed network-isolated execution path or + a separately qualified local-only CE API, a matching image/PDB and independent effect + readback. The audited current CE path selects a public symbol server; existing local + DLL/PDB registration fixtures do not qualify source enabling. +- [ ] **Six kernel operations:** qualify `kernel_initialize_dbvm`, `kernel_poll_watch`, + `kernel_read_physical`, `kernel_start_watch`, `kernel_translate_address` and + `kernel_write_physical` in a separately reviewed suitable environment, including cleanup. + This host has not met that prerequisite. Do not change drivers, VBS, Spectre mitigations, + boot or virtualization settings as part of cleanup. See the [kernel plan](docs/qualification/all-tools/later-kernel-test.md). + +## Gates for a future qualified release + +- [ ] Review a Client host profile for the intended CE/SDK tuple and obtain fresh receipts. + The recorded CE 7.7.1.10828 source-build tuple does not match the old 7.7.0.10621 profile. +- [ ] Freeze one final plugin/gateway/source candidate and run all required native success, + restart and cleanup cases against those exact binaries. Never pool different candidates. +- [ ] Regenerate the per-tool report and reconcile documentation from passing receipts. + Keep `nativeQualificationComplete=false` until every applicable native case passes. +- [ ] Complete the previously requested fresh independent final qualification review + (Astra), with required Release/Debug, formatting, packaging and fuzzing checks. + A bounded cleanup review does not satisfy that final release gate. +- [ ] Publish only when explicitly requested, then verify the remote commit and artifacts. + Local cleanup commits do not authorize a push or another beta release. + +## Repository handoff + +Client and SDK changes belong in their own Git repositories, followed by the MCP commit +that records their revisions. Local integration commits may precede upstream review; +they must be published/reviewed upstream before a distributable release relies on them. +Preserve ignored qualification artifacts, rollback binaries and installed CE/user state. + +See [all-tool qualification](docs/qualification/all-tools/README.md) for the evidence map +and [CONTRIBUTING.md](CONTRIBUTING.md) for normal build/test commands. diff --git a/docs/beta3-release-notes.md b/docs/beta3-release-notes.md deleted file mode 100644 index d407664..0000000 --- a/docs/beta3-release-notes.md +++ /dev/null @@ -1,21 +0,0 @@ -# CheatEngine.Mcp 2.0.0-beta.3 - -This Windows x64 prerelease adds the MCP roadmap work since beta.2. Download `CheatEngine.Mcp-2.0.0-beta.3-win-x64.zip` and verify it with `SHA256SUMS.txt`. The ZIP contains the single Cheat Engine plugin DLL, the matching gateway executable, installation instructions, and licenses. - -## Changes since beta.2 - -- Added bounded pointer access analysis and pointer-chain validation, plus an explicit C# compile-and-export tool. Compilation does not inject the result. -- Added prepared module, region, thread, symbol, and breakpoint navigation resources. Call the corresponding explicit enumeration tool first; stale or target-mismatched snapshots are refused. Full CE enumeration can still block regardless of result limits. -- Bounded structure lookup and element paging, fixed thread-list ownership, and retained failed pointer-search cleanup reservations. -- Improved effect metadata, unavailable-field handling, error correlation, packaged catalog checks, and Debug/Release portable CI coverage. -- Removed the prerelease exports resource URI; call `module_list_exports` explicitly. Clients should handle omitted unavailable fields and prepare navigation snapshots before reading them. - -## Support and known limits - -The plugin runs in Cheat Engine 7.7 x64 and needs the x64 .NET 10, ASP.NET Core 10, and Windows Desktop 10 runtimes. The x64 gateway is self-contained. The four optional execution capabilities remain enabled by default; configure their exposure for your own setup. See the [support matrix](https://github.com/CheatEngineNet/CheatEngine.Mcp/blob/main/docs/qualification/v2.0.0/support-matrix.md) for details. - -Portable tests, package checks, and the gateway smoke test do not establish live Cheat Engine qualification. An intermittent native Lua dispatch corruption and an empty Settings plugin-list failure remain unresolved. The new standalone navigation/Settings Lua cases and the broader live lifecycle, performance, install/upgrade, and rollback checks have not run on this release candidate. This prerelease is not the stable v2.0.0 sign-off; [qualification status](https://github.com/CheatEngineNet/CheatEngine.Mcp/blob/main/docs/qualification/v2.0.0/release-signoff.md) records the remaining work. - -## Upgrade and rollback - -Keep a backup of the existing matching plugin and gateway, your configuration, and CE settings. Disable the plugin, close Cheat Engine and the gateway, then install both beta.3 files from this ZIP in a new folder. Replace only this plugin's old CE entry if its path changed, point your MCP client at the new gateway, and discover a fresh `instanceId` with `instance_list`. Do not mix beta.2 and beta.3 files. To roll back, close both processes and restore the previous matching pair and registration. The [recovery guide](https://github.com/CheatEngineNet/CheatEngine.Mcp/blob/main/srcs/CheatEngine.Mcp.Resources/Knowledge/Documents/connection-troubleshooting.md#several-instances-updates-and-removal) covers locked files and partial upgrades. diff --git a/docs/beta4-release-notes.md b/docs/beta4-release-notes.md deleted file mode 100644 index 807b6e7..0000000 --- a/docs/beta4-release-notes.md +++ /dev/null @@ -1,24 +0,0 @@ -# CheatEngine.Mcp 2.0.0-beta.4 - -This Windows x64 prerelease updates the Client/SDK dependencies, fixes two tool adapters and expands automated coverage across the complete MCP catalog. Download `CheatEngine.Mcp-2.0.0-beta.4-win-x64.zip` and verify it with `SHA256SUMS.txt`. The ZIP contains the single plugin DLL, matching gateway executable, installation instructions and licenses. - -## Changes since beta.3 - -- Updated `CheatEngine.Client` to 1.1.0 and `CheatEngine.SDK` to 2.0.1, including regenerated normal and Native AOT dependency locks. -- Fixed `code_get_comments` so its address array reaches Lua as one argument, and fixed `process_list_threads` to use CE's documented zero-based StringList indexer. -- Added catalog-derived routing/refusal checks for every backend tool, positive adapter evidence for all 193 backend tools, and gateway-local discovery checks. New catalog entries must have reviewed adapter-test mappings. -- Expanded standalone Lua adapter tests and execution-argument, conversion/calculation and pointer-file boundary, failure and cleanup tests. -- Added a bounded live workflow scenario that tests target, gateway and host restarts independently of the unresolved plugin reload bridge. -- Preserved the latest test-platform package updates from `main`. - -## Validation and known limits - -The recorded automated baseline passes 5,314 portable tests in both Debug and Release and 651 standalone Lua tests, with no failures or skips. Combined coverage reaches 94.50% of instrumented handwritten product C# lines, including 94.37% in Tools. This C# percentage does not measure embedded Lua branch coverage or native CE behavior. - -The release pipeline checks the isolated bundled DLL, reflected catalog, Native AOT gateway smoke responses, ZIP contents and checksums. Native evidence for 37 backend tools comes from the separately identified private dependency-refresh candidate recorded in the [all-tool qualification report](https://github.com/CheatEngineNet/CheatEngine.Mcp/blob/main/docs/qualification/all-tools/README.md); it does not qualify newly rebuilt release bytes. Another 156 backend tools still need native success fixtures, and same-process plugin reload remains unqualified. SDK provider-stack guards reject invalid states; the original intermittent dispatcher corruption was investigated, but its root cause remains unresolved. This beta is not stable v2.0.0 sign-off. - -## Installation, upgrade and rollback - -Use Cheat Engine 7.7 x64 with the x64 .NET 10, ASP.NET Core 10 and Windows Desktop 10 runtimes. The gateway is self-contained. All four optional execution capabilities remain enabled by default; configure their exposure for your setup. The recorded bounded native workflows explicitly disabled them. - -Back up the previous matching plugin/gateway pair, configuration and CE settings. Disable the plugin and close CE and the gateway before extracting beta.4 into a new folder. Install `CheatEngine.Mcp.dll`, point the MCP client at the matching `CheatEngine.Mcp.Gateway.exe`, then use `instance_list` to discover a fresh instance ID. Keep both binaries from the same ZIP. To roll back, close both processes and restore the previous matching pair and registration. See the [recovery guide](https://github.com/CheatEngineNet/CheatEngine.Mcp/blob/main/srcs/CheatEngine.Mcp.Resources/Knowledge/Documents/connection-troubleshooting.md#several-instances-updates-and-removal) for locked files and partial upgrades. diff --git a/docs/external-connections.md b/docs/external-connections.md deleted file mode 100644 index 31a50fb..0000000 --- a/docs/external-connections.md +++ /dev/null @@ -1,85 +0,0 @@ -# External connection design - -Status: integration contract. The shipped backend remains local to Windows. This document does not introduce a -network listener, a connection-profile tool, or an automatic companion-tool connection. - -## Transport and profiles - -A profile is an MCP client's named server configuration. The profile launches one gateway on the same Windows -machine as Cheat Engine. The gateway discovers only that machine's local instances; backend ports and discovery -credentials stay on that machine. - -| Client location | Gateway location | Transport boundary | -| --- | --- | --- | -| Windows desktop | The same Windows machine | Existing stdio gateway command | -| WSL on that machine | Windows, using an explicitly configured Windows executable | WSL interoperability carries stdio; Windows paths and Windows process identities remain authoritative | -| Another machine | The Windows CE host | An authenticated SSH session starts the gateway and carries stdio; no forwarding of backend HTTP ports | - -The remote transport must provide a clean binary-safe stdin/stdout stream and separate stderr. Shell banners, -terminal control sequences, interactive prompts, and profile output cannot be mixed into MCP stdout. A remote -launcher must be explicitly configured by the user; a tool response must never supply the command to launch. - -The initial integration uses the client's existing server configurations rather than adding credentials or remote -command execution to the plugin. Existing gateway options remain `--instance-directory` and -`--call-timeout-seconds`. This design adds no command-line flags. - -## Identity and authentication - -The client identifies an observation by `(profileId, hostIdentity, instanceId, targetProcessId, selectionEpoch)`. -`profileId` is a stable local name; the authenticated transport supplies `hostIdentity`. A display name or PID is -never sufficient to choose a connection. Every routed operation still requires its exact `instanceId`, and a fresh -plugin activation requires rediscovery. - -SSH authentication and host-key verification belong to the transport. Profiles store a destination and credential -reference, never a copied private key, password, backend token, or discovery record. Unknown or changed host keys -require the client's normal explicit trust decision. The existing per-activation backend authentication remains -between the remote gateway and the remote plugin on loopback. - -WSL is not a separate CE host: the Windows gateway runs with the explicitly chosen Windows account. Windows -process IDs, module paths, write roots, and permissions apply. Linux `/proc` PIDs and `/mnt/...` paths must not be -substituted into tool arguments automatically. - -## Reconnection and effects - -After transport loss, a call that was sent has an unknown effect until independently checked. Reconnection must -not replay writes, attachment, process creation, allocation, code execution, or resource release. The client starts -a fresh MCP session, lists instances, confirms the selected host and target, and inspects retained jobs/resources. -An old instance ID is never silently replaced by the only available instance. - -Read-only calls may be repeated explicitly after reconnection, but their results are new observations. Snapshot -IDs, cursors, imported datasets, jobs and leases belong to their original activation. A restart invalidates them. -Durable notes retain historical build identities; resolving an address again requires matching the current module. - -## Companion analysis tools - -Debugger and decompiler integrations run as separately configured MCP servers. Their outputs are observations, -not commands to execute in CE. A companion observation carries its profile/host identity, input-file digest, module -build identity, pointer width, RVA or file offset, and source timestamp. RVAs and file offsets are distinct types. - -Before using an RVA with a live target, match the module name, width and build metadata and validate its image -range. A PE build-metadata fingerprint supports build matching but is not proof of byte-for-byte integrity. When -integrity matters, retain a separate original-file SHA-256 and explicitly verify the relevant bytes. An absolute -address copied from another host or process is never reused directly. - -The first interchange paths are offline dump imports, structure-definition files and exported investigation notes. -No companion may silently change CE's selected target, enable capability gates, or approve a mutation. Automatic -live decompiler/debugger synchronization requires a separately implemented protocol and qualification fixtures. - -## Integration acceptance matrix - -These are required checks before a transport integration can be advertised as qualified. They are not receipts of -remote or WSL execution in this release. - -| Scenario | Required result | -| --- | --- | -| Windows, WSL and SSH startup | MCP initialize/discovery works through clean stdio; stderr stays separate | -| Two profiles with the same display name/PID | Explicit profile, authenticated host and instance select the correct target | -| Unknown/changed host key or failed authentication | No MCP operation is sent; credentials are absent from logs/results | -| Lost connection before, during and after a write | No automatic replay; unknown effects are reported and independently inspected | -| Plugin/gateway/target restart | Rediscovery and target checks occur; stale identities, cursors and jobs are refused | -| Companion module mismatch or out-of-image RVA | No live address is produced and no target mutation occurs | -| WSL path/PID confusion | Invalid foreign paths/IDs are refused rather than translated implicitly | -| Completed fixture | Owned mutations are restored, resources released and both transport ends closed | - -Remote/WSL transport execution and live companion synchronization remain unqualified until these environment -checks have receipts. The local gateway's routing and stale-identity tests cover the underlying local contract only. diff --git a/docs/mcp-alternatives-research.md b/docs/mcp-alternatives-research.md deleted file mode 100644 index cd6f2d8..0000000 --- a/docs/mcp-alternatives-research.md +++ /dev/null @@ -1,144 +0,0 @@ -# MCP alternatives and useful gaps - -Research date: 2026-10-19. Baseline: this repository at `0866b6e` (beta.4), with 193 backend tools and the gateway's -`instance_list`. The comparison is based on public documentation and our source, not a live test of other servers. -Tool counts are not comparable coverage measurements: several alternatives expose a separate tool per value type. - -## Findings - -The useful gaps are workflows around the existing memory and inspection primitives: discover text without knowing -its contents first, inspect an object's virtual-function pointers, and explain where batched pointer chains diverge. -The existing catalog already covers memory editing, scans, RTTI, structure reads, code references, captured-operand -analysis and typed errors. Duplicating those tools would add more names without adding useful capability. - -Larger opportunities include durable investigation notes, offline metadata searches, structure interchange and -qualified plugin reloads. These need their own persistence, lifecycle or input-format contracts. No dependency was -added for the three bounded improvements below. - -## What already exists - -| Workflow | Existing tools | -|---|---| -| Read different types at many expressions | `memory_read_batch` | -| Take snapshots and compare typed changes | `memory_create_snapshot`, `memory_compare_snapshot` | -| Follow chains with complete evidence | `pointer_read_chain`; batch validation through `pointer_read_chains` | -| Explain a captured memory operand | `pointer_get_access_info` | -| Identify an object's RTTI class | `memory_get_address_info(includeRtti=true)` | -| Read, compare and export known layouts as C | `structure_read`, `structure_compare`, `structure_generate_c_header` | -| Find strings whose exact text is known | `aob_find_value(valueType="string" or "wstring")` | -| Find code references, referenced strings and control flow | `code_find_references`, `code_find_strings`, `code_get_function_graph` | -| Search modules and symbols without dumping every result | `module_list`, `symbol_find` | -| Keep comments and bookmarks in a cheat table | `code_set_comment`, address-list record tools, `table_save` | - -## Implementation checklist - -These are workflow improvements inspired by the comparison, not claims that every alternative has these exact APIs. - -- [x] `memory_find_strings`: discover readable Unicode runs in a caller-selected range, with explicit UTF-8/UTF-16LE - decoding, literal filtering, addresses, bounded previews and pagination. This answers “what text is near this - object?” without requiring the AI to know the text in advance or interpreting a large hex dump itself. -- [x] `memory_read_vtable`: inspect a fixed number of pointer slots from an object/subobject or a direct table - address, with the target's pointer width, symbol names and per-slot errors. This supplies facts for subsequent - `code_disassemble`/`code_get_function_graph` calls; it does not infer a class layout or table extent. -- [x] `pointer_read_chains(includeHops=true)`: return each candidate's completed dereferences, including the prefix - before a failed hop, using the existing read budget. The default response remains compact. -- [x] Register both new tools in backend/gateway composition, source-generated JSON, contract snapshots, the tool - map and positive adapter-evidence inventory; run focused and full portable tests. - -## Remaining roadmap - -| Priority | Work | Why it is separate | -|---|---|---| -| High | Extend exact-bundle native success fixtures, including the new inspection tools. | Portable Client-double and routing tests cannot prove CE host behavior. The previous qualification report remains explicit about native gaps. | -| High | Durable investigation notebook: named findings, module identity/RVA, evidence and confidence. | CE comments/records persist, but are not a structured research notebook. File ownership, schema migration and target identity need a clear contract. | -| Medium | Offline IL2CPP metadata/dump import and paginated class/method searches. | Complements the current live collectors, but needs representative versioned fixtures and a supported input format. | -| Medium | Standalone structure-definition interchange. | `structure_generate_c_header` and table persistence exist; importing/exporting selected layouts needs an explicit format and conflict behavior. | -| Medium | Paginated process enumeration. | `process_list` filters/caps results, whereas module and symbol tools also page. Less urgent than inspecting target data. | -| Medium | Qualified same-process plugin reload. | This is native lifecycle work, with an existing qualification gap; a loader/core split should not be introduced as incidental feature work. | -| Later | Remote/WSL connection profiles and companion decompiler/debugger workflows. | These change deployment assumptions and should be designed and tested as separate integrations. | - -## Completion criteria for the remaining roadmap - -Feature completeness means an AI can discover, inspect, explain, preserve and revisit its findings with explicit -limits and reliable results. A catalog entry alone does not complete a workflow. Each feature below needs public -input/output contracts, deterministic tests, MCP/gateway checks, help content and representative native evidence -where a live host is involved. - -- [ ] **Exact-bundle native coverage:** provide an owned fixture and independent expected result for every tool; - record target identity, mutation restoration, resource cleanup and restart behavior. Keep unsupported environment - cases explicit. Extend the existing acceptance matrix to include these new tools. -- [x] **Persistent investigation notes:** create, search, update and export named findings; preserve module identity, - relative address, supporting observations and confidence. Prove restart persistence and reject stale target - bindings. Define file ownership, version migration and conflict behavior before implementation. -- [x] **Offline IL2CPP metadata:** import an explicitly supported dump format, page type/method/field searches, - retain the source's identity, and resolve an RVA only against a matching loaded module. Test representative formats, - malformed/oversized files and mismatched modules; do not assume a metadata address is currently executable. -- [x] **Structure interchange:** export/import selected layouts with field types, offsets, nested layouts and - target pointer width. Define duplicate-name handling, recursive references and validation before changing any - existing structure; demonstrate a lossless round trip with independent fixture expectations. -- [x] **Process pagination:** keep the current default response compatible, add stable ordering and a continuation - contract, and explicitly handle processes exiting or appearing between pages. -- [ ] **Same-process reload:** qualify disable/re-enable in the actual host, including plugin-list discovery, - outstanding callbacks, leases, port cleanup and fresh instance identity. Repeated successful load calls alone - do not meet this criterion. -- [x] **Optional external connections:** design connection identity, authentication and failure recovery for remote - clients and companion analysis tools; document which process/module each observation refers to. - -## Latest verified baseline - -The four feature criteria above passed their portable and standalone Lua checks and the private x64 workflow -`20261019T131741Z-b4d5` on the source-built bundle. That workflow checked the notebook after target, gateway and -host restarts; exercised offline metadata import/search/resolution; round-tripped nested structure definitions; -and replayed process pages. It restored user state, preserved the installed CE files and reported no cleanup failures. -These feature checks do not complete the separate all-tool or same-process reload criteria. - -| Completed feature | Representative passing regression | -|---|---| -| Durable notes and stale binding refusal | `InvestigationToolsTests.CreateThenRestartSearch_RecoversTheImmutableRevision`; `Update_RetainedStaleBinding_IsRejectedBeforePublishingSuccessor` | -| Offline metadata identity and bounded imports | `Il2CppDumpStoreTests.Tools_ResolveMethod_RejectsChangedTargetBuildAndUnboundImports`; `Tools_ImportRejectsMalformedUtf8AndOversizeFiles` | -| Structure interchange and width validation | `StructureInterchangeToolsTests.Import_ProductionAdapter_ValidFilePassesEncodedNestedReferencesToOneLuaMutation`; `Import_PointerWidthMismatch_IsRefusedBeforeLuaMutation` | -| Compatible, stable process pages | `ProcessPagingTests.List_LegacyRequest_PreservesTheOriginalRequestAndJsonShape`; `List_PagedSnapshot_SortsOnceAndKeepsTheOriginalProcessesAcrossPages` | - -The current catalog contains 206 backend tools plus gateway discovery. -Resumed Release verification on 2026-10-20 passed 5,736 portable cases and 743 standalone Lua cases. -The isolated process-create scenario passed with cleanup, but the x64 workflow reproduced an invalid native Lua -state before `structure_list`; the complete workflow failed. Ordinary x64/x86 workflows remain suspended; -the independent Mono, smoke, assembler, execution and symbol scenarios continued separately. -Detailed receipts and remaining gaps are recorded in the [full tool qualification report](qualification/all-tools/README.md). -The earlier x86 pointer-map correction for out-of-width WOW64 module metadata still awaits fresh native verification. - -Subsequent isolated verification on `continue-20261020-4` passed all 15 Mono cases, the existing debugger -basic/execution group and six independent scenario repetitions. Its exact-bundle report verifies 50 native cases -and leaves 157 unverified, with all 207 routing rows accounted for. The arity-corrected diagnostic is kept -separate from those success cases. New bundle `continue-20261020-5` includes the thread-ignore acknowledgement -fix and prepared event fixtures; its first debugger attempt crashed during the earlier run-to cleanup, before -the new cases. That session supplies no native qualification. Six independent scenarios then passed on bundle 5; -its own report records 38 verified native cases, 12 failed-session observations and 157 unverified rows. -The latest source passes 5,774 portable cases in each of Debug and Release and 757 standalone Lua cases. -The SDK provider-state and debugger crash causes, -same-process reload and suitable kernel-operation environments remain unresolved. - -## Earlier inspection baseline - -Before the remaining roadmap implementation began, the catalog contained 195 backend tools plus gateway -discovery. The .NET 10.0.402 Debug and Release builds completed with zero warnings/errors. Each portable suite -passed 5,387 tests; standalone NativeLua passed 651 tests, all with zero failures/skips. Nine deterministic fuzz -tests exercised 1,600 generated cases. The bundled-plugin load probe and Native AOT gateway smoke check passed. -These receipts describe that baseline; further roadmap changes require fresh verification. - -| Requirement | Representative passing evidence | -|---|---| -| Exact string bytes/addresses and Unicode handling | `MemoryStringToolsTests.FindStrings_Utf8MultibyteRuns_ReadsTheExactRangeOnceAndReturnsExactAddresses`; `FindStrings_MinimumLength_CountsScalarsInsteadOfBytesOrUtf16CodeUnits` | -| Native pointer-width slots, symbols and independent errors | `MemoryVtableToolsTests.ReadVtable_ObjectAddress_ReadsTheFirstPointerThenNamesEachNonNullSlot`; `ReadVtable_UnreadableSlot_ReportsItAndContinuesToLaterSlots` | -| Useful chain evidence without additional reads | `PointerBatchHopTests.ReadChains_IncludeHops_ReportsOrderedSignedDereferencesWithoutExtraReads`; `ReadChains_UnreadableHop_RetainsOnlyCompletedPrefixAndIndependentCandidates` | -| Exact values through MCP and the gateway | `ExactValuePipelineTests.MemoryFindStrings_UnicodeAndWideAddress_RoundTripAcrossClientAndGateway`; `MemoryReadVtable_WidePointersNullAndUnreadableSlots_RoundTripAcrossClientAndGateway` | - -The tests above use Client doubles. Separate private x64 and x86 workflow sessions passed on the exact packaged -baseline, restored user state, left the installed CE files unchanged, and reported no cleanup failures. The reports -verify routing for 196 advertised tools, positive adapter evidence for 195 backend tools and native success evidence -for 62 tools. The other 133 backend tools still require native success fixtures. Both sessions used disabled optional -capability gates and zero plugin reload cycles; they do not establish gated operations or same-process reload. - -The completed feature contracts now have the verification described above. The -[external connection design](external-connections.md) defines transport, identity, authentication and recovery; -its environment acceptance matrix is explicitly separate from local test results. diff --git a/docs/qualification/all-tools/README.md b/docs/qualification/all-tools/README.md index 165216a..83d3128 100644 --- a/docs/qualification/all-tools/README.md +++ b/docs/qualification/all-tools/README.md @@ -1,1164 +1,52 @@ # Full tool qualification -The current catalog contains 206 backend tools and the gateway's `instance_list` tool. -Every tool needs a reviewed success case with known inputs, an independent result or side-effect oracle, and -cleanup verification when it changes external state. Catalog discovery, routing, disabled-feature refusals and -compilation of a fixed Lua body do not establish successful native execution. - -## Latest checkpoint (current-27, 2026-10-21) - -The report is [`all-tools-current-27/tool-results.json`](../../../artifacts/all-tools-goal/all-tools-current-27/tool-results.json), -SHA-256 `E5A8293487BCCF155C28DBB72311FC86832A9F00FD54901F22B0123AD50C2BCE`. It records **200 verified** -and **7 not verified** rows across all 207 advertised tools; `nativeQualificationComplete=false`. All 17 -live sessions use the same exact candidate `artifacts/dist/all-tool-final-20261020-06`: product -`2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 -`E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 -`1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`, and installed CE host SHA-256 -`CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. No product or gateway evidence -from another candidate was pooled. - -The current exact-bundle `structure_list` workflows pass, and the mapped debugger success and cleanup cases -pass with the reviewed breakpoint-arity and deferred trace-cleanup changes. The old provider `-101889` and -debugger monitor-overlap `SEHException` did not recur; their historical causes remain unknown. Three plugin -disable/enable cycles pass when the registry row is seeded before host startup. Explicit symbol reload and the -local DLL/PDB fixture pass. `symbol_enable_sources` remains open because there is no safe independent effect -oracle; no public symbol download or source-enable call was made. - -Exact-bundle `kernel_get_status` passes and confirms DBK/DBVM inactive; an independent driver inventory found -no matching driver. This environment has no VM, so the six DBK/DBVM-dependent tools remain open. No driver, -service, boot, firmware, VBS, virtualization or user setting was changed. The original run-root `C000050B` -refusal remains unexplained and its no-reparse guard remains in place. - -Normalized-checkout validation passed Release portable **5,838/5,838**, Debug portable **5,838/5,838**, and -NativeLua **766/766**, all without failures or skips. Release and Debug solution builds completed with zero -warnings or errors; style and whitespace verification passed. The report's Release TRX SHA-256 is -`FB0CFC401CA94A7AD242CAAD2B98E1E6B93D5B4C1E43C2515D5693001442DE0A`; NativeLua is -`23BAE4953CC35F2812BB126B83A7E41F97DC0E27C5486C1CAEBDC51105415A8A`. Debug TRX SHA-256 is -`2BAA55FFE972CAAA1B2382C33511A9F08C22C3EAE9142D4E0D80D25185321653`. - -Whitespace verification identified CRLF normalization in two test-only source files after the native sessions. -The audit at `artifacts/test-results/all-tool-qualification-20261021/line-ending-normalization-audit.json` -confirms their normalized text hashes are unchanged; the earlier frozen v5 manifest is preserved as historical -evidence. Product plugin/gateway hashes are unchanged. See the current-27 entry in `TODO.md` for exact file -hashes. The independent current-27 validation record is -[`current27-final-validation-independent.json`](../../../artifacts/test-results/all-tool-qualification-20261021/current27-final-validation-independent.json). -It rechecks all 17 summary/receipt hashes and identities, all 200 qualifying report rows, the candidate binaries, -and final test/build artifacts. The 17 summaries also record restored user state, unchanged installed CE files, and -zero cleanup failures. Its independent final review returned **GO** with no actionable findings; record SHA-256 -is `B41DF8AF659A1765D91D45C6E028955FE8A53CF36755770006640A5925F1B93C`. All-tool qualification remains -incomplete. - -## Previous checkpoint (current-26, 2026-10-21) - -The report is [`all-tools-current-26/tool-results.json`](../../../artifacts/all-tools-goal/all-tools-current-26/tool-results.json), -SHA-256 `04168F6F69F5A36143F4F73E9774FB1CE314888F3F9EEB841B77AE2BC2B4166D`. It records **200 verified** -and **7 not verified** rows across 207 advertised tools; `nativeQualificationComplete=false`. All 17 -reported live sessions use the exact candidate `artifacts/dist/all-tool-final-20261020-06`: product -`2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 -`E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 -`1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`, and installed CE host SHA-256 -`CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. Four passing sessions added 27 -tool rows since current-25: assembler (9), compiler plus managed injection (2), process creation (1), and -Mono (15). Both later failed attempts remain in the report as observations and do not count as success. - -Assembler, compiler/injection, process-create and Mono each passed their MTP scenario, supplied mapped -success and cleanup receipts, restored settings, preserved installed CE files and stopped all owned -processes. Their run IDs and evidence hashes are listed in `TODO.md`. The v5 frozen source/binary manifest -contains 3,287 unique inputs; the process-create postrun independently rehashed all of them with no mismatch, -and the Mono launcher repeated its pre/post input check. The earlier Mono v4 attempt stopped before starting -the Mono child because its generated manifest exceeded the legacy path limit; the reviewed shorter run root -now passes preflight and the native Mono scenario. - -The later v5 compiler-injection attempt has a mapped `compiler_call_held_export` receipt, but it records -`IsError=true`, `kind=invalid_argument`, and `hostEffect=not_started`. The separate fixed hold-ready file was -not published, so compilation and injection were not attempted. It timed out waiting for that file, then -restored settings and cleaned the owned files and processes. The full API error body was not retained, so the -rejection cause remains unknown. The same exact candidate passed held export and fixed managed injection in -`20261021T000621Z-a348`, which supplies the compiler success evidence. - -The old provider `-101889` stack-top failure and debugger monitor-overlap `SEHException` have not recurred -in exact-bundle cases: `structure_list` passes in state-workflows and workflows, while debugger basic, -event-capture and execution scenarios pass with cleanup. A separate diagnostic-only replay -(`artifacts/provider-fields-diagnostic/provider-fields-live-20261020T125039Z-8ac7.json`) also passed its -historical provider prefix on an isolated candidate: hosts A/B recorded 118/22 provider-top reads, each -calibrated top 0 and 1 against the loaded Lua module, with no negative tops, read errors, or trace overflow. -This replay is not final-bundle qualification and does not explain the historical failure. Both historical -root causes remain unknown; no speculative native-code change was made. Plugin reload passes three in-process -disable/enable cycles when the owned `Plugins64` entry is seeded before host startup. The earlier harness -inserted the registry values only after the Settings form existed, so its zero-row observation does not -establish a product reload regression. - -The no-VM kernel evidence is recorded in [the kernel qualification record](later-kernel-test.md): exact-bundle -`kernel_get_status` passed, DBK/DBVM were inactive and the independent driver inventory contained no matching -driver. Hypervisor/VBS presence does not establish DBK/DBVM readiness. The six DBK/DBVM-dependent operations -(`kernel_initialize_dbvm`, `kernel_poll_watch`, `kernel_read_physical`, `kernel_start_watch`, -`kernel_translate_address`, and `kernel_write_physical`) remain unqualified. `symbol_enable_sources` also -remains open because no independent effect oracle is available without relying on echoed output or external -symbol downloads. A read-only cache check found `C:\Symbols\ntdll.pdb\2CF5F86ACB68735923D72913BBD9B0E31\ntdll.pdb`, -but its key does not match the current system `ntdll.dll` CodeView identity (`C031D45B-0DA1-8A6E-74D0-6ED6AB2DD9E1`, -age 1; DLL SHA-256 `651AC9041AF8E6B5EBD331D53163244C0A27D36D1433F0F15CAC1F28C5F2E82C`). No source-enable call was made. -No driver, service, boot, firmware, VBS or virtualization setting was changed. The -historical provider/debugger causes and original run-root `C000050B` refusal remain unresolved; its no-reparse -guard was not weakened. All-tool qualification remains incomplete. - -Release portable validation passed **5,832/5,832** tests and NativeLua passed **757/757**, with no failures or -skips. The report reuses the Release TRX for the gateway and portable suite inputs. - -Independent final review returned **GO** with no actionable findings. The validation record at -`artifacts/test-results/all-tool-qualification-20261020/current26-final-validation-independent.json` has -SHA-256 `4D2994B22426AD96FD9AE57F820BFECD617C8D0DB73F70CC165A7F19AD155040`; it confirms the report hash, -zero session issues, and a recheck of 3,287 frozen inputs with only the declared postrun edits to `TODO.md` -and this README. The report remains incomplete because the seven native tool rows above are still open. - -## Previous checkpoint (current-25, 2026-10-21) - -The current report is [`all-tools-current-25/tool-results.json`](../../../artifacts/all-tools-goal/all-tools-current-25/tool-results.json), -SHA-256 `19F232D92161EE2976E99887A9AE75CB7E404A7F128080B7A5572F1A5079DA27`. It contains **173 verified** -and **34 not verified** rows across the 207 advertised tools; `nativeQualificationComplete=false`. Its eleven -live sessions all use the exact candidate at `artifacts/dist/all-tool-final-20261020-06`: product -`2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 -`E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, gateway SHA-256 -`1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`, and installed CE host SHA-256 -`CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. The four new execution rows passed -on this bundle with no regressions from current-24. - -Plugin lifecycle was separately qualified in `20261020T232333Z-da96`: the owned `Plugins64` entry was seeded -before host startup, then three in-process disable/enable cycles passed 1/1 MTP case. The summary, receipts -and TRX hashes are recorded in `TODO.md`; settings were restored, installed files were unchanged, and cleanup -failures were zero. The earlier unseeded attempt observed zero plugin rows after load; this seeded pass -qualifies the reload path without claiming that it proves the earlier observation's root cause. - -The smoke session `20261020T233322Z-c49d` passed its 1/1 MTP case and directly qualifies six mapped tools. -Its wrapper exited 1 after the native pass because StrictMode read an absent optional `diagnosticOnly` field. -The independent postrun audit at -`artifacts/test-results/all-tool-qualification-20261020/final-bundle-06-smoke-native/run-2a296e25f98e4b06a7a891194f810312/postrun-validation-independent.json` -confirmed exact candidate/host identity, all 3,280 frozen input hashes, restored state, unchanged installed -files, zero cleanup failures and no remaining owned processes. All four optional gates were enabled in that -session; it does not establish disabled-gate refusal behavior. The six mapped cases are `instance_list`, -`pointer_get_access_info` (supplied-fact arithmetic, not a register capture), `scan_list_scanners`, -`scan_delete`, `speedhack_set_speed`, and `speedhack_get_state`. - -Execution session `20261020T234126Z-7aba` passed its exact-bundle MTP case 1/1. It qualifies -`exec_compile_c`, `exec_call_remote`, `exec_call_method`, and `exec_call_local` with direct receipts. Both the -owned data and code allocations have separate `freed=true` receipts and were independently confirmed absent; -the scenario cannot pass unless both cleanup checks succeed. All 3,280 frozen inputs matched, user state was -restored, installed files were unchanged, and cleanup failures and owned processes remaining were zero. -Target-code execution alone was enabled for this scenario; Auto Assembler, unsafe Lua, and kernel access -were disabled. - -Release portable validation passed **5,832/5,832** tests and NativeLua passed **757/757**, with no failures or -skips. The report reuses the Release TRX for its gateway and portable suite inputs. - -Still open: the historical provider-state `structure_list` failure (`20261020T092949Z-4423`, stack top -`-101889`) did not recur in exact-bundle workflows, but its root cause is not established; the historical -debugger monitor-overlap `SEHException` likewise did not recur in the passing event, execution and basic -scenarios, but its cause is unknown. `symbol_enable_sources` still lacks an independent effect oracle. Six -DBK/DBVM-dependent operations (`kernel_initialize_dbvm`, `kernel_poll_watch`, `kernel_read_physical`, -`kernel_start_watch`, `kernel_translate_address`, and `kernel_write_physical`) remain unqualified because -this PC has no VM and DBK/DBVM were inactive; no machine-level setting or driver was changed. The original -run-root `C000050B` refusal also remains unexplained, and its guard was not weakened. The exact-bundle -`structure_list` and debugger tools have passing cases despite those historical causes remaining unresolved. -All-tool qualification remains incomplete. See [the kernel qualification record](later-kernel-test.md) for -the environment boundary and per-operation evidence plan. - -## Previous checkpoint (current-23, 2026-10-21) - -The current report is `artifacts/all-tools-goal/all-tools-current-23/tool-results.json`, SHA-256 -`3AB5D941145F2BC706229EAB6307C8806E545F699AD9DD575E50EBD9770C5254`. It contains **163 verified** and -**44 not verified** rows across the 207 advertised tools; `nativeQualificationComplete=false`. All native -sessions recorded by this report use the exact candidate at `artifacts/dist/all-tool-final-20261020-06`: -product `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8`, plugin SHA-256 -`E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B`, and gateway SHA-256 -`1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A`. - -The report requires one exact success receipt and checks a mapped cleanup receipt only when the live-evidence -map requires one. When the map says `cleanupReceipt: none`, rows state `cleanupRequired=false`, -`cleanupReceiptPresent=false`, and `cleanupSatisfied=true`; no tool-specific cleanup receipt is claimed. -Fifteen of the 59 newly verified rows since current-21 are read-only cases in this category. Their session must still pass the -overall restoration and zero-cleanup-failure checks. - -Six same-candidate sessions passed: debugger event capture (`20261020T220121Z-d800`), debugger -execution/run-to (`20261020T220529Z-dd07`), state workflows (`20261020T220755Z-2b6f`), read-only -`kernel_get_status` (`20261020T221714Z-226c`), the reviewed local DLL/PDB fixture -(`20261020T222142Z-7fef`), and the workflows case (`20261020T223158Z-a2f8`). The last passed its one MTP -test. Its launcher then exited in its postrun check because StrictMode rejected an omitted optional -`release.failed` field in an otherwise successful response. An independent validator -(`artifacts/test-results/all-tool-qualification-20261020/final-bundle-06-workflows-native/run-9f2c7d83e0b14d41a784b62f09c13a65/postrun-validation-independent.json`) -first confirmed the summary pass, all 3,278 frozen input hashes, exact candidate and installed-host hashes, -resource release and cleanup, symbol-preference readback/restoration, module-expression resolution, stopped -owned processes and restored temporary settings. The refreshed proof records that snapshot check and verifies -only the four later README/TODO/report-generator/regression-test inputs changed; it rechecks the raw summary, -receipts, TRX, candidate, host and runner-log pass. The runner OS exit code was not independently captured. -No retry was made. -This run adds runtime overview/resource-release, -module-preference, module-symbol and broader owned-workflow receipts; 52 report rows became newly verified -since current-21, with no regressions. It recorded `pluginCycles=0` and provides no plugin-reload evidence. - -The monitor-free debugger-basic case (`20261020T225741Z-5c14`) then passed its single MTP case. Its summary -SHA-256 is `490CA85D0C3176E83575EC7FF1559C033EF6BCBF2A10A3B51A9D068577282040` and its receipt SHA-256 is -`1F396E4B0EA5F52AE78A3FE34259EDC5805B55FD080B71292E41A38FA2F218DD`. It supplies the missing attach, -break-thread, status, context, stack-trace, continue and detach success receipts in that one passing run, -plus `live_debugger_windows_cleanup`, both host-stop receipts and the usual restoration checks. Those seven -rows are now verified in current-23. The earlier monitor-overlap `SEHException` was not reproduced; its -historical cause is still unknown. - -The earlier state-workflows launcher also reported a postrun count mismatch because it expected one -`host_A_stopped` receipt while the valid restart and final cleanup produced two. That MTP test passed; its -independent read-only validator confirmed the receipts, host restart identities, cleanup, all 3,278 frozen -input hashes, restored state and zero owned processes remaining. - -The exact-bundle plugin lifecycle attempt (`20261020T221415Z-d111`) failed its positive assertion: after -plugin load, the Settings bridge saw zero owned plugin rows. Cleanup, state restoration and installed-file -preservation succeeded; this failed summary is retained in the report and does not qualify a tool. The later -current-25 startup-seeded lifecycle case passed three disable/enable cycles; the original observation's cause -remains unknown. - -Release portable validation passed **5,832/5,832** tests and NativeLua passed **757/757**, with no failures or -skips. The report uses the same Release TRX for its gateway and portable suite inputs. Current-source builds -and Debug validation were completed before the final native batch. - -Still open: the provider-state `structure_list` failure (`20261020T092949Z-4423`, stack top `-101889`) was -not reproduced by the final-bundle state/workflows cases and has no established root cause, although -`structure_list` now has passing exact-bundle evidence; the original run-root `C000050B` refusal remains -unexplained; `symbol_enable_sources` has no independent effect oracle; and six DBK/DBVM-dependent operations -(`kernel_initialize_dbvm`, `kernel_poll_watch`, `kernel_read_physical`, `kernel_start_watch`, -`kernel_translate_address`, `kernel_write_physical`) lack suitable native evidence without a VM/DBK/DBVM -environment. Only read-only `kernel_get_status` is qualified. The historical debugger monitor-overlap -exception also remains unexplained despite current-bundle debugger success. No DBK/DBVM initialization or -machine-level driver, service, boot, firmware, VBS or virtualization settings were changed. Temporary -private CE settings used by the harness were restored. The independent final review of current-22 returned GO -with no actionable findings; current-23 also received an independent GO review with no actionable findings. -All-tool qualification remains -incomplete. The exact read-only status evidence and per-operation -admission and cleanup requirements are in [the kernel qualification record](later-kernel-test.md). - -Earlier reports and the `state-workflows`/`inspection` session details below preserve historical evidence and -counts; they are not pooled into the current report unless the report explicitly lists that evidence. - -## Historical checkpoint (pre final bundle 06) - -## Checklist - -- [x] Enumerate the complete advertised catalog. -- [x] Check routing, missing/unknown instance refusal and stale-identity refusal for every backend tool. -- [x] Check gateway-local discovery with zero and two instances. -- [x] Run the portable Release suite; final results are recorded below. -- [x] Run the portable Debug suite with the same exclusions. -- [x] Run standalone Lua 5.3 with CE API doubles (latest: 757 passed, zero failed or skipped). -- [x] Add and run result assertions for the Lua, table-save and code-view adapters (12 passed). -- [x] Add seven record adapter cases and four code/process adapter cases; run both focused classes. -- [x] Fix the `code_get_comments` array argument expansion found by those tests. -- [x] Fix `process_list_threads` to use CE's documented zero-based StringList indexer; verify empty, truncated and error cleanup paths. -- [x] Prepare eight additional bounded live cases and review their assertions. -- [x] Attempt the private x64 lifecycle session against the published beta.3 bundle. -- [x] Verify both private hosts stopped, user state was restored and the installed CE files stayed unchanged. -- [ ] Qualify same-process plugin reload: the Settings bridge observes zero plugin rows after `loadPlugin` succeeded. -- [x] Add a separate disabled-capability workflow scenario so independent restart checks can complete. -- [x] Run the eight added live checks and six bounded workflows, then target, gateway and host restarts. -- [x] Map a positive production-adapter result/effect assertion for every backend tool. -- [x] Add SDK provider-stack containment and normal-path preservation regressions. -- [x] Exercise the SDK dispatcher with rooted worker/main coroutines, including queued-stop cleanup. -- [x] Prepare, review and run a bounded provider trace replay; retain non-reproduction as diagnostic evidence only. -- [x] Add direct adapter checks for managed collectors, debugger callbacks, execution, kernel calls, patches and table loading using doubles. -- [x] Update to published Client 1.1.0 and SDK 2.0.1; regenerate portable and AOT lockfiles. -- [x] Replace the Client and SDK NuGet dependencies with pinned main-branch Git submodules and verify the source build graph. -- [x] Require reviewed positive adapter mappings for every name in the production tool catalog. -- [x] Add execution-argument, conversion/calculation and pointer-file boundary/failure tests. -- [x] Measure handwritten product C# coverage using fresh portable and NativeLua reports. -- [x] Rebuild and repeat the disabled-capability workflows on the updated dependency bundle. -- [x] Qualify utility calculation/conversion, runtime overview, assembly bytes and reversible symbol preferences on the source-built x64 package. -- [ ] Complete the x86 workflow after the pointer-width fix and physical system-module path correction. -- [ ] Qualify owned resource release, scanner stop and structure guessing with independent readback and cleanup. -- [ ] Qualify file-as-process open/save with exact bytes, file preservation after refused attachment and verified host/target restart recovery. -- [x] Qualify fixed Lua and reversible Auto Assembler operations in their dedicated private scenario on package 12. -- [x] Qualify Windows debugger attachment, stopped state and cleanup against an owned target on package 11. -- [x] Qualify the fixed compiler scenario against source-built package 10, including gate refusal, exported bytes and cleanup. -- [x] Qualify managed injection with the reviewed helper enabled in the private host on package 11. -- [ ] Resolve the intermittent native SDK frame-restoration crash reproduced by the package 11 compiler session. -- [ ] Qualify generated AOB signatures, generated API hooks and owned process creation. -- [ ] Supply the remaining native managed object/instance, Mono, debugger event, compiler/ABI, PDB and patch fixtures. -- [x] Prepare and review separate fixed C/ABI, local DLL/PDB and advancing-thread debugger cases with independent cleanup receipts. -- [x] Qualify the four fixed C/ABI execution tools on package 13, including independent return values, buffer effects and cleanup. -- [x] Qualify local native DLL injection on package 13 using OS module identity, image hashes and independently parsed exports. -- [x] Qualify explicit local PDB loading on a fresh second host and the independently known four-field layout on package 14. -- [x] Prepare the private x64 Mono fixture and verify standalone startup, pinned-object publication and graceful exit. -- [x] Qualify all 15 Mono tools against that owned fixture, including method effects, collector detach and target cleanup. -- [ ] Qualify successful gated operations and DBK/DBVM operations in their required environments. -- [ ] Qualify all tools on the exact replacement product bundle after fixes, including restart and cleanup. - -The machine-readable report contains one row for each of the 207 tools. It keeps routing, tests using -doubles, direct fixed-Lua bodies and live results separate. The reviewed -[`behavior-evidence.json`](behavior-evidence.json) maps positive adapter evidence for all 206 backend names. -Each mapped test lists its exact required TRX case names; every case must occur once and pass. -Passing only one branch of a parameterized test cannot qualify another branch's tool. -These cases use Client or CE API doubles, including real standalone Lua where applicable. They are partial -cases, not complete behavior partitions or native CE qualification. The gateway's local discovery checks -bring the advertised catalog total to 207. A live mapping requires its own concrete oracle and cleanup receipts. - -## Resumed verification on 2026-10-20 - -The latest debugger/event source passes 5,774 portable cases in each of Debug and Release, plus 757 -standalone Lua cases, with zero failures/skips. Both builds pass without warnings/errors. An earlier loaded -Debug run hit notebook directory-lease contention; its focused rerun and the full sequential rerun passed -without changing that behavior. `all-tools-current-7` records bundle 5's failed debugger session separately: -207 routing passes, 13 failed-session observations and 194 unverified native rows, with no verified native row. -After the final input freeze, six independent bundle-5 repetitions passed: smoke `20261020T122756Z-115c`, -process-create `20261020T122829Z-828c`, assembler `20261020T122901Z-eac8`, fixed C/ABI `20261020T122940Z-16c2`, -symbols `20261020T123008Z-bc8d` and Mono `20261020T123037Z-35e9`. Each passed cleanup, settings restoration, -installed-file preservation and owned-process exit checks. All frozen inputs matched after the batch. -`all-tools-current-8` consequently records 38 verified native cases, 12 failed-session observations and -157 unverified rows on bundle 5. The debugger crash remains unresolved; bundle 4's 12 passing debugger -cases are historical evidence only and are not pooled into this bundle's report. - -Report `artifacts/all-tools-goal/all-tools-current-9/tool-results.json` adds the existing passing smoke -session's exact two-instance discovery receipt under the reserved `gateway_discovery` scope. It contains -39 verified cases: 38 backend cases and gateway-local `instance_list`, plus 12 failed-session observations -and 156 unverified rows. The report rejects gateway/backend scope interchange; all 17 report regressions -pass. This does not add a backend native success or transfer evidence between bundles. - -The subsequent source refresh passes 5,777 portable cases in each of Debug and Release and all 757 -standalone Lua cases, with zero failures/skips. Both builds report zero warnings/errors. Style and whitespace -verification find no changes; workspace loading reports duplicate analyzer-release documents and a -source-generator metadata reference warning. `artifacts/all-tools-goal/all-tools-current-10/tool-results.json` -uses the fresh Release/Lua reports and the same seven bundle-5 sessions; its classifications remain 39 -verified (38 backend plus gateway discovery), 12 failed-session observations and 156 unverified. - -After correcting the symbol-reload polling description, fresh portable Debug and Release runs again pass -5,777 cases each, and standalone Lua passes 757, all without failures/skips. The Debug rebuild has zero -warnings/errors; style, whitespace and Git whitespace checks pass. Report -`artifacts/all-tools-goal/all-tools-current-11/tool-results.json` uses those fresh Release/Lua reports and -the same seven old bundle-5 sessions. Its 39 verified, 12 failed-session observations and 156 unverified -rows remain evidence for that exact old bundle; private diagnostics and a replacement bundle are unqualified. - -A later offline logging change retains unexpected operation-body exception types and managed frames in -local dispatch diagnostics, following the existing dispatcher format. Exception messages, data and -source-file paths remain excluded; public error behavior is unchanged. Two regressions fail before the -change, and all 92 focused dispatch, error-correlation and logging tests pass in both Release and Debug -without failures/skips. Both builds have zero warnings/errors; changed-file style and Git whitespace -checks pass, and independent source review found no blockers. These checks do not establish a native -fix or tool qualification. The full-suite runs and report 11 above predate this logging edit. - -The subsequent full offline refresh validates the logging change: portable Release and Debug each pass -5,779 cases, standalone Lua passes 757, and all 11 seeded fuzz cases pass in each portable configuration. -There are no test failures/skips; both solution builds have zero warnings/errors, and full style, -whitespace and Git whitespace checks pass. Report -`artifacts/all-tools-goal/all-tools-current-12/tool-results.json` uses the fresh Release/Lua TRXs and -the same seven bundle-5 sessions, whose 14 summary/receipt hashes remain unchanged. All 207 routing -checks pass; the report retains 39 verified rows (38 backend plus gateway discovery), 12 failed-session -observations and 156 unverified rows. These remain old-bundle receipts, with no native qualification -for the changed source or a replacement bundle. - -The user subsequently authorized live testing again, lifting the temporary hold after cybersecurity -prompts. A fresh `continue-20261020-6` bundle passed isolated plugin-load and Native AOT gateway smoke -checks. Its plugin SHA-256 is `5F86B15826142FD655498AA1E8E5AF8A380A488D27E55AF939679A4B7A72F9FA`; -its gateway SHA-256 is `059727F11E310988C501C4DB494529660FF40D938B20638805AF05924E9ADBB3`. -Six reviewed sequential live scenarios passed: smoke `20261020T160645Z-e46d`, process-create -`20261020T160750Z-1b18`, assembler `20261020T160814Z-f8ee`, C/ABI execution `20261020T160848Z-7b1b`, -DLL/PDB `20261020T160912Z-1be0`, and Mono `20261020T160938Z-bcb2`. Each TRX contains one intended live -case, passed without failures/skips. Independent raw-evidence review confirms success/cleanup receipts, -restored settings, unchanged installed files, unchanged hashes for all 9,153 frozen inputs and no -remaining recorded host, target, child or Mono process. The reviewed launchers enforce one-use attempts, -post-cleanup input verification and sequential stop-on-failure behavior. - -Report `artifacts/all-tools-goal/all-tools-current-13/tool-results.json` uses only these six bundle-6 -sessions: 39 verified rows (38 backend plus gateway discovery), 168 unverified and zero failed-session -observations. The absence of a failed observation on this new bundle does not resolve or transfer the -old bundle's debugger failures. No debugger attachment/mutation or kernel call ran; smoke queried -read-only debugger status. Wrapper logs establish admission/restoration; the TRXs and sandbox receipts -carry native scenario evidence. Ordinary debugger/workflow retries still need new diagnostic evidence; -kernel tests remain planned for later. Final all-tool qualification and final independent review remain -incomplete. - -Kernel cases remain unchecked. The user selected a later test on this same physical PC; see the -[proposed test plan](later-kernel-test.md). No driver, VBS or virtualization configuration is changed now. - -The separate provider-fields diagnostic subsequently passed one reviewed run, `20261020T125039Z-8ac7`. -Its V2 capture uses bounded self-memory reads, the exact loaded Lua DLL identity and per-state/lifetime -healthy calibration; diagnostic failures cannot replace the original provider guard. All 20 focused SDK and -92 harness cases passed, the packaged SDK bytes matched, and independent preflight verified 3,281 frozen inputs. -Both hosts retained complete traces (946 and 150 records), matching loaded-module hashes and same-state -top-0/top-1 calibration. No incomplete, overflow or first-failure record occurred. Settings restoration, -installed-file preservation, owned-process exits and post-run frozen hashes passed. This private prefix -uses a diagnostic snapshot and is excluded from shipping-bundle native qualification. The provider failure -did not recur, so its cause and the separate debugger access violation remain unresolved. - -Private plugin-registry diagnostic `20261020T133119Z-43a6` tested a new guarded registry-seeding hypothesis -once. The selected owned path and prior enabled state were read back before the request, but the Settings -checklist remained at zero rows before and after reload (`loading=nil`). No plugin transition was reached. -The summary and report preserve `diagnosticOnly=true`, `stableQualification=false` and `passed=false`. -User state was restored, installed files and all 3,266 frozen inputs were unchanged, and owned processes -exited without cleanup failures. The result rejects that bounded hypothesis; it does not identify a cause -or qualify plugin lifecycle. Original response, logs and summary hashes are retained in -`artifacts/plugin-registry-diagnostic/plugin-registry-live-20261020T133119Z-43a6.json`. - -The external crash monitor has separately passed its reviewed fixture-only run -`d36ed920e2cd4260a65e28cb667f8875`: 20 admission-refusal cases and six runtime cases, including handled -exceptions, one second-chance AV dump, verified detach and target survival after confirmed exit-kill -disablement. All owned processes exited. The x86, oversized-file, recovery-fault-injection and redundant -parser partitions remain explicitly untested. An initial reader-sharing failure occurred before helper -startup; the corrected bounded readers passed local probes and re-review. These results qualify only the -owned monitor fixture, not Cheat Engine or any MCP tool. Rebuilt v3 fixture run -`8d13ede4ac5c469cbef5b42b3ed35167` repeats the 26 passing cases and four explicit exclusions while binding -absolute compiler/linker identities and clearing inherited `CL`, `_CL_` and `LINK` flags. - -The separately reviewed CE integration's one-use attempt `c035416b6a624bcc9986b425a77c9cbc` failed before -sandbox construction. A read-only call reproduced the manifest validator's JSON parent-ownership exception. -The apphost exited with code 2; no sandbox summary, helper ready/log/dump or native qualification was produced. -Installed files and all 3,763 frozen inputs were unchanged, and no owned processes remained. The launcher -records restoration unconfirmed because the sandbox summary is absent. Its missing durable child output is -also being corrected in a new snapshot. The failed attempt remains immutable and must not be replayed. - -The corrected, independently reviewed v2 attempt `0eb66dff7959434c801cc37fd9b4dead` reached both private hosts -in session `20261020T151945Z-3c25`, with durable logs and exact helper readiness. It failed at basic -`debugger_detach` with an unknown-outcome `SEHException`, before the first execution group. The helper's 224 -events include one first-chance invalid-handle exception (`0xC0000008`), one thread-name exception and ten CLR -exceptions, all passed through. There was no second-chance AV or dump. Read-only cleanup observed detached -state; helper stop/detach, settings restoration, unchanged installed/frozen files and owned process exits -all passed independent review. This is failed diagnostic evidence, not crash coverage or tool qualification. -The exact native callee and any observer effect remain unproven; do not replay the consumed attempt. - -V3 attempt `57a45025da89451f81f6be24af22f7ba`, session `20261020T162659Z-23f9`, changes that ordering on -bundle 6: the basic group completes without the monitor before monitor readiness and first-group -execution. Basic detach succeeded, and the execution body reached its independently checked run-to stop. -Cleanup then failed in `debugger_detach` with `SEHException`, error ID `c9261948eb11f175`; the plugin log -retains managed frames through `PluginLuaToolRuntime.TryRun`. The 221 helper events contain one thread-name, -one invalid-handle and eight CLR exceptions, all first-chance and passed through. There is no AV or dump. -Helper stop/detach/exit, user-state restoration, unchanged installed files and all 3,777 frozen inputs, -and absence of owned processes passed independent review. This establishes a monitor-overlap correlation -for this detach failure, not its cause, a fix, or native qualification. The original AV remains unresolved; -do not repeat this unchanged diagnostic. - -Report `artifacts/all-tools-goal/all-tools-current-14/tool-results.json` includes the six passing bundle-6 -sessions and this failed diagnostic separately: 39 verified, 12 diagnostic-only and 156 unverified rows. -The 39 verified names are identical to report 13; diagnostic observations add no native qualification. - -The saved checkpoint's GitHub build [39314293272](https://github.com/CheatEngineNet/CheatEngine.Mcp/actions/runs/39314293272) -passed at `1f987742b7fcd7760057a19508ddacbf2aad4bd8`. -Fresh local verification passed both Debug and Release solution builds with zero warnings/errors, -5,736 portable cases in each configuration and 743 standalone Release Lua cases, all with zero failures/skips. -The eight new build-identity cases accept rebuilt legacy compiler output only with matching actual EXE/sidecar -hashes and reviewed source, and reject altered identities or stale source. - -The isolated `continue-20261020-1` distribution passed publication and its Native AOT gateway smoke check. -Its plugin SHA-256 is `838B398B0BB842B568115093C1D00CDBDA7BFBD52DDC510B015324639FEB9B25`; -its gateway SHA-256 is `11874E779DEC0397E95FE72185EB5BC4DCEFE03DCA9F1FA7962BA2C5E5340533`. -The rebuilt Mono fixture also passed a fresh standalone startup/stop preflight against matching source/runtime/EXE -identities, PID/nonce, nonzero pinned object pointer, primitive values, warmup result and exit code zero. -It remains prepared for MCP verification; no Mono tool was exercised in these sessions. - -Process-create session `20261020T092911Z-916a` passed its owned child identity, exit and target-restoration assertions. -Both hosts stopped, user settings were restored, installed files were preserved and no cleanup failure was reported. -The subsequent x64 workflow `20261020T092949Z-4423` completed allocation release and scanner-stop cleanup, then -`structure_list` was refused because CE supplied a Lua state with a negative stack top (`-101889`). -The workflow failed; its earlier receipts provide observations only. -Both hosts stopped, user settings were restored, installed files were preserved and its cleanup-failure list was empty. -That reproduced state failure remains unresolved. Independent fresh scenarios can still provide bounded evidence; -passing them does not establish SDK stability. - -The separate current-source diagnostic prefix `20261020T100317Z-52fd` completed without reproducing the failure, -restored user settings, preserved installed files and reported no cleanup failures or remaining owned processes. -It was marked diagnostic-only before the prelude. -Its capture postcheck failed because host A produced no trace; host B retained 150 complete records, including -88 provider records, with no overflow or first-failure record. -The incomplete capture neither explains the ordinary failure nor supplies native qualification. - -After synchronous owned-directory creation and before/after reparse checks were added to the private harness, -18 focused cases passed and independent review verified all 282 frozen inputs. The single replacement -diagnostic `20261020T102110Z-3f05` retained complete traces from both hosts: 946 records on A and 150 on B, -including 472 and 88 provider records respectively, with no incomplete, overflow or failure record. -User settings were restored, installed files were preserved and all owned processes exited. -The state failure did not recur. This closes the capture defect only. - -Mono attempts `20261020T102640Z-8602` and `20261020T102945Z-4038` failed before method invocation. -The first exceeded the legacy runtime's temporary-manifest path limit; the second reached `mono_attach`, -which was refused because the private harness had left the collector DLL inside its disabled autorun folder. -Both restored settings, preserved installed files and cleaned up all owned processes, including the Mono target. -The harness now checks the path limit and stages only the reviewed collector DLL for Mono sessions. -The corrected oracle verifies the loaded collector through OS module path/hash evidence; optional patch and -table-option warnings remain unqualified. These changes await a complete passing native scenario. - -The next Mono run `20261020T105656Z-fce5` attached and independently verified the loaded collector's exact -private module path/hash, then failed assembly selection before method invocation. The fixture had compared -Mono's image name with an EXE filename; it now reads the CLI assembly name from the hash-checked fixture's -metadata and requires one exact match. All 43 focused Mono fixture cases pass in Debug and Release, including -renamed-file, foreign-name and duplicate-name regressions. The raw assembly list is retained before assertions. -Collector detach, user settings restoration, installed-file preservation and all owned process exits passed. -Because the complete session failed, its earlier receipts remain observations. - -After those corrections, Debug and Release each passed 5,743 portable cases; the fresh standalone Lua run passed -743 cases, all with zero failures/skips. Both solution builds and both formatting checks passed. -The refreshed `continue-20261020-3` distribution passed packaged-plugin validation and Native AOT gateway smoke. -Its plugin SHA-256 is `DBA6ABAFB35002523958FAFCDEE4A6FE53F921485D56E92C2F9BBD88E28EEA71`; -its gateway SHA-256 is `C7E9D569D9CD487AE6782BBE73A337972927673BF2B513C8A9CA9E76E54125E2`. -Exactly four catalog golden lines changed: the two Mono warning descriptions and two document-size values. - -The cumulative `continue-20261020/all-tools-2` report for `continue-20261020-1` retains all 207 routing rows, positive adapter evidence for all -206 backend tools, one verified native case (`process_create`), 12 `observed_in_failed_session` rows and 194 -`not_verified` rows. Both latter classifications lack verified native success on these exact bytes. -Historical results from other bundles are not pooled into this report. -The report's remaining-work text now acknowledges a verified native case without implying wider behavior or -environment coverage; its regression failed against the old script and all 14 cumulative-report cases passed after -the correction, including failed-session and diagnostic-only boundaries. - -The next Mono session `20261020T113335Z-7c44` passed all 15 bounded Mono cases on `continue-20261020-3`. -The fixed static invocation returned 42, the instance invocation produced counter 7007, and exact field/address -checks and the complete instance-search page matched the owned fixture. Collector detach, job absence, -target exit code zero, settings restoration, installed-file preservation and owned-process exit all passed. -The optional patch/table effects remain unqualified. - -Fresh same-bundle sessions also passed smoke (`20261020T113552Z-757d`), isolated process-create -(`20261020T113649Z-0577`), assembler and API-hook generation (`20261020T113725Z-a97b`), fixed C/ABI -execution (`20261020T113806Z-2760`) and the DLL/PDB fixture (`20261020T113838Z-0c99`), each with cleanup. -Smoke used the plugin's enabled default gates and made no kernel calls. The other scenarios assert their -explicit configuration. API-hook generation checked exact generated source and unchanged allocation bytes; -it did not apply or execute the generated hook. The DLL/PDB case verified injection identity and a fresh-host -four-field layout; automatic symbol discovery still leaves explicit `symbol_reload` inconclusive. - -The `continue-20261020/all-tools-current-5` report combines only these six sessions from the identical bundle: -207 routing passes, 38 verified native cases and 169 `not_verified` rows. All 206 backend tools retain positive -adapter evidence. These bounded native cases do not complete every behavior or environment partition. - -Independent preflight found that debugger diagnostic dispatch first ran an ordinary break/context cycle. -Diagnostic mode now invokes only its callback-gated transition probe; normal mode retains basic then execution -checks. Two focused regressions reproduced the old order, and all four pass after the correction. This harness -fix supplies no debugger native qualification; a fresh diagnostic run is still required. - -The corrected dispatch diagnostic `20261020T114533Z-36ae` observed the installed `CC` byte and advancing -owned loop counters but zero callback invocations throughout its bounded observation. It never requested -MCP debugger context. Callback restoration, breakpoint removal, debugger detach and sandbox cleanup passed; -settings and installed files were preserved. This failed diagnostic qualifies no native tool. - -The breakpoint adapter also passed explicit `nil` optional slots to CE's arity-sensitive overload parser. -It now omits absent method/callback arguments for global and thread-specific calls. Eight standalone Lua -cases check exact argument counts and positions across default/explicit methods and persistent/one-shot -breakpoints, including callback invocation and owned removal; three reproduced the old defect. All eight -and the complete 751-case standalone Lua suite pass after the correction. Independent review found no -remaining arity issue. Whether this explains the native callback failure still requires a new diagnostic. - -The resulting local `continue-20261020-4` distribution passed isolated-plugin catalog validation and the -Native AOT gateway smoke check. Its plugin SHA-256 is -`4293CFFD053C625B271BD9C27FE0098627C40995FDCE14AB48613DACE5BF33C2`; its gateway SHA-256 is -`723CC9D148E94AEFA6F36F4D06DCAC32FC638ACDCC508101EA753E6F0EA6438D`. -The 38 native cases above remain evidence for `continue-20261020-3` until repeated on these new bytes. - -Those repetitions passed on bundle 4: Mono `20261020T115638Z-f2b5`, smoke `20261020T115702Z-60cc`, -process-create `20261020T115728Z-287d`, assembler `20261020T115750Z-9cd7`, C/ABI `20261020T115824Z-7842` -and DLL/PDB `20261020T115846Z-c4af`. The corrected debugger diagnostic `20261020T115504Z-1dfb` separately -observed one exact owned callback, matching context and advancing post-detach counters, with clean restoration. -It remains diagnostic-only. Ordinary debugger session `20261020T115551Z-681a` then passed the basic and -first execution group, including register restoration, stepping, run-to and cleanup. -The exact-bundle `all-tools-current-6` report therefore verifies 50 bounded native cases and leaves 157 -unverified; all 207 routing rows pass. Diagnostic observations supply none of those verified cases. - -The no-break adapter also now requires CE's explicit Boolean acknowledgement, instead of reporting the -requested thread-ignore state after a refused or missing host result. Four false-success cases reproduced -the old defect; all six direction/result cases and all 757 standalone Lua cases pass after the correction. -The five remaining debugger tools have reviewed capture/trace/ignore fixtures and 23 passing exact-oracle -regressions; they still need native success evidence. - -Local bundle `continue-20261020-5` passed isolated-plugin catalog and Native AOT gateway checks. Its plugin -SHA-256 is `E87F9297CE2D8BB1630FD0EF4970EF524F9A72F984F239827A3CB276291E6020`; its gateway SHA-256 is -`F56E4B75BC6487A25EB8C0ECE46DC59BC8D5417BAC9658803CABE50C6969C177`. -Its debugger session `20261020T121721Z-6535` failed during existing run-to cleanup before the new event group. -Windows Application event 1000 independently confirms owned host A PID 72728 (`0x11C18`) crashed with access -violation `0xc0000005`, unknown module/address `0x7ffb09b29820`. Settings were restored, installed files were -unchanged and all owned processes exited. The missing host-A disable receipt remains a cleanup failure. -Its earlier operation receipts are failed-session observations, not native qualification for bundle 5. -Crash evidence is retained in `artifacts/test-results/continue-20261020/debugger-crash-6535`; no unchanged -debugger retry was made. The 50 bundle-4 successes are not transferred to these new product bytes. - -A fresh independent Astra checkpoint review found no implementation issue. Its stale suspension wording -finding was corrected. The final all-tool verification and publication checklist remains incomplete. - -## Source-built native results from 2026-10-19 - -The paused source checkpoint passes the Release build with zero warnings/errors, **5,728** portable cases, -**743** standalone Lua cases and changed-source whitespace verification (`pause-checkpoint-*` final reports). -At that pause, the new Mono, process-create and debugger diagnostic work remained prepared for live verification. -No CE session was started between the pause and the explicit resume on 2026-10-20. -See the repository's [continuation checklist](../../../TODO.md). - -The file-target guard and recovery fixture, fresh-host PDB case and private debugger transition diagnostic -passed the integrated Release build with zero warnings/errors. All **5,665** portable and **742** standalone Lua -cases passed with zero failures or skips (`debugger-transition-portable-1` and `debugger-transition-native-lua-1`). -The diagnostic bridge records only the exact owned breakpoint event before one context request; -its callback preserves the previous callback's returns and errors. Its results cannot qualify native tool -success: a report-generation check with a passing session marked `diagnosticOnly` retained all 207 routing -rows and verified zero native tools. File-target recovery and debugger transition checks remain incomplete. - -Package 14 (`cfb2421426f32edcc8986caa1be1896872f7a4e7`) passed the native symbol fixture session -`20261019T184727Z-76aa`. Its `native-fixtures-symbol-report-14` report retains all 207 routing rows and verifies -`exec_inject_library`, `symbol_add_module` and `structure_get_pdb_layout`. The explicit PDB case uses a fresh -second host without DLL injection, requires the type to be absent beforehand, verifies all four exact fields, -and proves owned allocation cleanup. `symbol_reload` remains inconclusive. Settings were restored, installed -files were unchanged and cleanup reported no failures. - -| Package 14 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `E0BCF6FFF28C5FCFFD00E93ED700D25CEC35EFA2242EAB4BD37213C13E3F0FC2` | -| `CheatEngine.Mcp.Gateway.exe` | `80ECA93C2E12CC30D2EE7A29042398D13DD62C3488DD69F411E440AE305F0E91` | - -Workflow `20261019T184630Z-7b0d` reached the exact 4,096-byte file preservation proof after an -`unsupported`/`not_started` attach refusal. Recovery then encountered a sharing violation while reading -the replacement target manifest. The target had already published a complete manifest atomically; the -other handle owner is unknown. The failed session provides no native qualification. All five recorded owned -processes exited, settings were restored, installed files were unchanged and sandbox cleanup had no failures. - -Debugger diagnostic `20261019T184844Z-bfa7` stayed responsive but did not record the exact owned breakpoint -event within its deadline. It never requested context for that transition. Callback identity restoration and -detach completed; continue was refused because no stopped context was available. All four owned processes -exited and the sandbox restored settings without cleanup failures. This diagnostic failed and provides no -native qualification or explanation of the earlier status-call stall. - -Workflow `20261019T190337Z-d002` completed file-target restart recovery and the independently checked -generated signature, then failed when its Framework process-create fixture did not publish a manifest. -The complete session remains failed and supplies no verified native tool results. All six recorded -sandbox processes exited, settings were restored and installed files were unchanged. Direct controls -subsequently reproduced startup failure at a 253-character executable path while the unchanged executable -succeeded from a shorter path. The isolated process-create fixture now uses a shorter filename and rejects -paths at or above its conservative 240-character limit before launch. - -The official Mono 6.12.0.206 x64 installer was verified with a valid Microsoft signature and extracted into -a private folder without a system installation. Its shipped metadata and header establish the legacy -32-bit GC handle representation and the native object-pointer signature. Standalone preflight -`mono-preflight-1` published the independently checked PID, assembly hash, nonzero pinned-object pointer, -initial primitive values and fixed method warmup result, then exited with code zero after its stop marker. -This confirms the fixture prerequisite; it does not qualify any MCP Mono tool. - -Package 13 (`dfe5179f83944206eb9fdee9e5895c67ec543be8`) passed smoke session -`20261019T180318Z-59b1`, execution session `20261019T180412Z-c4a2` and native symbol fixture session -`20261019T180445Z-90d7`. Each restored user state, preserved installed files and reported no cleanup failures. -The reports `native-fixtures-{smoke,execution,symbol}-report-13` verify six smoke tools, four execution tools -and native library injection respectively. Each retains all 207 routing rows. Automatic symbol discovery -made the explicit reload/add-module checks inconclusive; their receipts are absent and they remain unqualified. - -| Package 13 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `5ED25C614204F9C9E65F2A5292734593377CEB5BA0EE9856580FB7244F3EA6CC` | -| `CheatEngine.Mcp.Gateway.exe` | `D3E854D45CB2A07C7615E1A1895F8ED156E72935CE606C24BFDF5740A6183B0A` | - -The initial package 13 sessions exposed two harness defects: an unsupported address-list `clear` call, -and an omitted ordinary target stop signal. The supported record API now replaces the invalid clear call; -its regression fails against the old code and all **733** standalone Lua cases pass after the correction. -The ordinary target signal is restored, and the passing smoke session verifies normal shutdown of both -hosts and targets. Ten focused lifecycle cases also pass. The obsolete source-string assertion was removed -in favor of the behavioral Lua case. Initial failed sessions supply no native qualification. - -The fixed process-selection trace in `20261019T175101Z-500d` confirms that the file-transition read failure -already occurs inside installed CE: `openProcess` returns Boolean `true` and a new nonzero handle, but CE's -own `readInteger` returns `nil` at the previously readable owned address. This separates that failure from -SDK result conversion. No callback-reset workaround or successful file-to-process transition is claimed. - -Debugger session `20261019T180635Z-84d3` observed the exact owned thread and the fixed native return address -in a nonempty stack, then stalled after the first instruction breakpoint in the separate execution group. -Cleanup calls timed out and host A required termination; all owned processes stopped, settings were restored -and installed files were unchanged. Missing disabled-menu evidence remains a cleanup failure. The complete -session is failed and provides no verified debugger success cases. - -SDK fixes [#97](https://github.com/CheatEngineNet/CheatEngine.SDK/pull/97) and -[#98](https://github.com/CheatEngineNet/CheatEngine.SDK/pull/98) are merged. Package 13 used -SDK main `900f240975ad077d5b920c70442b96a99bcc2aa4` and Client main -`9b836e6ced8e97b4506dc8523998c7c2081cc2ad`. The process-selection fix requires an actual Boolean `true` -from CE before checking the selected PID and pointer width. Its five rejection regressions fail against -the previous code; all 77 affected tests and 751 Engine NativeLua tests passed after the fix. -This establishes the selection contract, not the cause of the file-target transition failure. - -The subsequent SDK [#99](https://github.com/CheatEngineNet/CheatEngine.SDK/pull/99) and Client -[#69](https://github.com/CheatEngineNet/CheatEngine.Client/pull/69) guards are merged after passing CI. -The source dependencies now pin SDK main `7c5b681a4d8055129d7d51b28931e2f6c6d87361` and Client main -`5c0d3057c040920665579ca3b0f464f3cd865317`. A known file target is refused before process selection; -Client reports `unsupported` with `not_started` and invalidates stale target leases. SDK's 83 focused cases -and all 1,223 Release Engine cases passed; Client's 65 focused cases and full 1,657-case Core suite passed. -The old implementations fail the new refusal regressions. This contains the unsafe transition; it does -not restore CE's native memory callbacks. Exact-bundle native verification remains pending. - -The next fixture batch adds separate x64 scenarios for four fixed C/ABI calls and a locally built native -DLL/PDB, plus a dedicated advancing debugger thread with published instruction addresses. Review requires -an exact one-byte step boundary and a nonempty stack containing the fixed native call's known return address. -The combined Release build, **5,647** portable tests and **732** standalone Lua tests pass with no failures or skips. -Reports are `native-fixtures-portable-3` and `native-fixtures-native-lua-2` under -`artifacts/test-results/ai-analysis-20261019`. These are prepared -cases; native success requires a complete passing session and its independent cleanup receipts. -There are now **178** prepared backend mappings. The DLL/PDB builder passed a real local MSVC preflight; -its source lives under the existing live-target project so the repository project-layout rule remains intact. - -The next correction batch passed its Release build with zero warnings or errors, **5,608** portable cases, -**722** standalone Lua cases and all five contract snapshots, with zero failures or skips. Parsed TRX result -counts agree with each runner summary. Reports are `parallel-followup-portable-1`, -`parallel-followup-native-lua-1` and `parallel-followup-goldens-1` under -`artifacts/test-results/ai-analysis-20261019`. - -Debugger status now retains its validated core state when CE cannot supply its advisory breakpoint flag. -`reportedBroken` is optional and is omitted when unavailable; explicit `true` and `false` are preserved. -Clients caching the old required-field schema should refresh the tool catalog. `broken` still requires the -independent context check, including register-global restoration, and execution guards retain their strict checks. -Independent Astra review closed the debugger findings. Native qualification of the replacement bytes is pending. - -Package 11 (`2460bfa198cb05d5134846f759b8fdf06a982a55`) passed debugger session -`20261019T154118Z-82ff` and managed-injection session `20261019T154346Z-c862`. Both restored user settings, -preserved installed files and reported no cleanup failures. The debugger report verifies seven native tool cases; -its status, context, stack, break, continue and detach assertions use the owned target. Injection returned the -fixed payload's expected value and verified owned compiler-file cleanup. These results describe this package only. - -| Package 11 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `432E41DEDC8C5C08BDEA0843009B9342FCB2A6C3F547AD0DE594EA7CA5456F67` | -| `CheatEngine.Mcp.Gateway.exe` | `4F22475239BA9F39C1B17541DE9D5088711D7F53AA1B8AB4129ABFF153AA052E` | - -The package 11 workflow `20261019T154209Z-2a27` reached signature generation but its fixture dereferenced an -omitted match count before checking whether generation succeeded. It now retains the raw response and fails -explicitly when the chosen address has no unique signature. The assembler session `20261019T154458Z-2fee` -found CE's additional terminal newline; the two fixed input scripts now include that newline without relaxing -content comparisons. Both failed sessions restored settings and preserved installed files. - -The disabled-compiler session `20261019T154553Z-ab0b` passed. The enabled session `20261019T154610Z-703f` -compiled and exported the expected DLL, then crashed in the SDK's outer `LuaFrame.Dispose`/`lua_settop` path. -It returned `instance_unavailable` with unknown host effect. Both owned hosts and targets stopped, user settings -were restored and installed files were unchanged, but the crashed host's disabled-menu evidence is missing, so -the session has a cleanup-evidence failure and supplies no verified native success cases. Native runs were -suspended until the SDK cleanup candidate below was prepared and reviewed; a passing earlier package does not close this regression. - -The follow-up SDK candidate `c934aec483c6c5cfe7a24112d9c3218ba4a1629b` -([PR #97](https://github.com/CheatEngineNet/CheatEngine.SDK/pull/97)) releases synchronized callbacks on the -already admitted Lua state. It removes a second provider acquisition during cleanup. The three new regression -cases fail against the previous implementation and pass with the fix; all six focused cases, 136 hosting tests, -363 Lua tests and 746 engine NativeLua tests passed with zero failures or skips. Independent Astra review closed -the bounded patch. These results establish the cleanup invariant, not the cause of the native access violation. - -MCP's corresponding Release build passed with zero warnings or errors, all **5,608** portable cases and **722** -standalone Lua cases passed, and the managed-object fixture is integrated with four prepared evidence mappings. -It publishes a pinned object's direct reference and known fields from the owned target, checks module identity, -object values, search results and exact structure layout, then verifies job and structure cleanup. There are -**164** prepared backend mappings; preparation does not establish native success. - -Package 12 (`eaa89d411069ab6919b10aa3297105c1c4046262`) passed the compiler pair -`20261019T161750Z-f007` / `20261019T161807Z-9d25`, smoke session `20261019T162310Z-674e`, -managed injection `20261019T162347Z-73ed` and assembler session `20261019T162840Z-f817`. -All restored user state, preserved installed files and reported no sandbox cleanup failures. The assembler -session used corrected fixture assertions with unchanged product binaries. The corresponding reports are -`managed-fixture-{compiler,smoke,injection}-report-12` and `managed-fixture-assembler-report-12b` under -`artifacts/test-results/ai-analysis-20261019`. These bounded passing cases do not establish that the intermittent -native access violation is resolved. - -| Package 12 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `0A1B36991DC4AAD41E3B3A793E7E9D7AE3A291EA4671768650FAFD3E62E0FE4F` | -| `CheatEngine.Mcp.Gateway.exe` | `0885F6064B8AE1606A0A15718E9B3F65211645177C9F3AFEEC418B0C5AA72497` | - -The initial managed-object run `20261019T161918Z-43f2` exposed an incorrect structure-count expectation: -CE adds a `Vtable` pointer at offset zero before the three known instance fields. The corrected oracle requires -exactly that header, the target pointer width and all three exact field offsets/types/sizes; 13 focused tests -cover acceptance and malformed or incomplete layouts. The workflow `20261019T162751Z-e779` passed those -managed-object assertions and verified job/structure cleanup, then failed the file-target restoration readback. -Opening and saving the private 4,096-byte file preserved its exact contents, but reattaching reported the original -PID while reading its known memory value failed. No file-tool or partial-workflow qualification is claimed. - -The initial hook run `20261019T162218Z-fc7e` incorrectly required a standalone syntax check to accept generated -source. Generation is now qualified by the complete expected template, independently decoded instructions, -exact owned addresses/rollback bytes, unchanged target memory and verified allocation cleanup. The separate -DISABLE check returned `Access violation` at named `dealloc`, consistent with the cached CE implementation's access -to absent ENABLE-time allocation information; the host remained running. This observation does not qualify hook application. The complete corrected assembler -session above passed its independent apply/release and rollback cases. - -Debugger session `20261019T162423Z-8f1b` attached and detached cleanly but did not observe the requested thread -stop within ten seconds. The fixture selected an arbitrary matching thread; CE documents that a sleeping thread -may need to wake before the request takes effect. A dedicated advancing thread fixture is being prepared. -This failed session supplies no native debugger qualification for package 12. All failed sessions above restored -settings and preserved installed files; their sandbox cleanup lists were empty. - -Package 10 (`4401986963a7a765b579f3003e06a454672ea707`) passed compiler sessions -`20261019T143232Z-9eaa` and `20261019T143249Z-e5e9`, then smoke session `20261019T143707Z-7128`. -All three restored user settings, preserved installed files and reported no sandbox cleanup failures. -The compiler case verified the fixed exported PE and owned-file cleanup; the smoke case verified isolation -and its existing bounded operations. Their evidence remains separate from package 9 below. - -| Package 10 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `827D12EF42F72D0375A8A2C7AB2CFEBFE8177A06E73D1B408DA4A0DB2E9EDFDD` | -| `CheatEngine.Mcp.Gateway.exe` | `1BBE58D33385CD96159C3DFD449EECC2CCD7402860588017D7749EF527475B19` | - -The new workflows (`20261019T143059Z-8e25` and `20261019T144940Z-f28d`) failed a resource fixture assertion. -The allocation descriptor legitimately omits its optional process ID, and default-false flags may also be omitted. -The fixture must establish target identity independently and validate required allocation fields without assuming -optional fields are present. The assembler run (`20261019T143415Z-29fc`) reached record-script readback, -then failed because CE normalized LF to CRLF. Its correction normalizes only those line endings. -Managed injection (`20261019T143328Z-3b5f`) refused because the private host had disabled CE's required -`DotNetInject.lua`; setup now needs that exact reviewed script for this scenario. The debugger run -(`20261019T143539Z-9adb`) attached successfully but returned invalid status on immediate readback and cleanup -readback. The diagnostic rerun (`20261019T144757Z-c2b2`) showed that the independent context query succeeded, -but CE's advisory `debug_isBroken` query returned a non-Boolean value. A narrow correction is being verified: -preserve strict core status validation and omit an unavailable advisory value. All these sessions stopped their -private hosts and targets and restored settings. Failed sessions do not qualify their partial tool receipts. - -The next parallel fixture batch passed its combined Release build with zero warnings or errors, all **5,594** -portable cases and all **714** standalone Lua cases, with no failures or skips. Reports are -`parallel-checklist-portable-1` and `parallel-checklist-native-lua-2` under -`artifacts/test-results/ai-analysis-20261019`. The first Lua run caught an incorrect test expectation: -Lua validation can prevent the native open while the dispatcher correctly reports that Lua execution started. -The corrected case also proves the native open was never called. - -This batch fixes file-target handling: CE's unsigned sentinel is preserved, address expressions resolve before -opening the file, and a failed open cannot reuse an earlier file target's sentinel as proof of success. -Separate resource/scanner/structure, file, assembler and debugger fixtures have reviewed cleanup and evidence -mappings. Their corrected live runs are pending; the mappings alone do not qualify these tools. - -The latest x64 workflow, `20261019T135210Z-8cfc`, passed on package -`artifacts/dist/roadmap-source-20261019-9`, including the six utility/runtime/assembly/symbol cases and target, -gateway and host restarts. Its report verifies **130** backend native success cases, all **207** routing cases -and all **206** positive adapter mappings. The report is -`artifacts/test-results/ai-analysis-20261019/source-workflow-x64-report-9/tool-results.json`. -The summary also records the owned target executable and managed assembly hashes. User state was restored, -the installed files were unchanged and cleanup reported no failures. - -| Package 9 file | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `89082402AADA29AFE3D8BBD400102B3A46F9341724233552D93E8E474B0F6919` | -| `CheatEngine.Mcp.Gateway.exe` | `2B5E37A2E66310EAD87651D927DE70FC4A06439F29D6CC8310BB4D9D40FB2E6E` | - -The x86 run `20261019T135014Z-b9af` passed pointer-map export and the six added cases, then failed the module-file -oracle: Windows' cross-width process enumeration reported `System32` for the 32-bit module that CE resolved to -`SysWOW64`. Cleanup and restoration passed. The fixture now resolves only direct System32 module paths to the -physical x86 system directory, retaining strict base-address, path, PE-machine and export comparisons. -Its corrected x86 run is pending. A failed run's partial receipts are not counted as qualification. - -The following package 8 results remain evidence for their own binary hashes: - -The x64 workflow `20261019T131741Z-b4d5` passed with the source-built Client and SDK, followed by smoke session -`20261019T132216Z-47db`. Both restored user state, preserved the installed CE files and reported no cleanup failures. -The workflow verified target, gateway and host restarts, including the persisted notebook after restart. Its seven -.NET collector receipts match independently parsed metadata from the owned managed assembly. The smoke session -verified debugger status without an attached debugger, speed state and scanner lifecycle; it does not qualify -register preservation while a debugger is stopped. - -Fresh reports verify routing for all **207** advertised names and positive adapter evidence for all **206** backend -tools. The workflow supplies **124** native mappings and smoke supplies **5** different mappings on the same package, -for **129** backend tools with reviewed native success cases. The remaining **77** backend tools have no verified -native success case on this package. These are individual cases, not all behavior partitions. All four optional gates stayed disabled -and no same-process plugin reload was requested. `nativeQualificationComplete` remains false. - -Reports are in `artifacts/test-results/ai-analysis-20261019/source-workflow-x64-report-8` and -`source-smoke-x64-report-8`, using the 5,560-case portable and 705-case standalone Lua reports below. Package -`artifacts/dist/roadmap-source-20261019-8` passed its isolated plugin-load and Native AOT gateway checks: - -| File | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `4C840290E380F1F138F30FDAB88E8B48F647EAD482542C633D0E4C8AC6C20BA7` | -| `CheatEngine.Mcp.Gateway.exe` | `325D761C275FE3F79F59A9D79675CA11A6291BAFEE330D8568A3B56B74F92A28` | - -The corresponding x86 workflow `20261019T132136Z-5c93` failed at pointer-map export: CE reported 64-bit WOW64 -support modules alongside the 32-bit target modules, and capture retained those out-of-width ranges. The export -validator correctly refused that map. This failed session restored user state, preserved the installation and -reported no cleanup failures; its earlier receipts do not qualify the complete x86 workflow. The capture projection -now retains only complete module ranges representable by the selected pointer width, while preserving validation -of genuinely invalid persisted maps. The new regressions cover actual x86/x64 capture, the inclusive address -boundary and invalid export metadata before any output bytes are written. - -The same follow-up adds explicit symbol-preference clearing with `modules=[], replace=true`, so a previously empty -list can be restored. Null lists and empty prepend requests still refuse before dispatch. Six further live cases -are prepared for utilities, overview, assembly and symbol preferences; restoration is admitted before mutation and -checks the exact original list. Assembly cleanup verifies completed release and subsequent `not_found`. - -The follow-up Release build and scoped formatting checks passed. All **5,566** portable tests and **706** standalone -Lua tests passed with zero failures or skips, recorded in `pointer-width-portable-1` and `pointer-width-native-lua-1` -under the same artifact directory. The public contract snapshot diff contains only the intended explicit-clear -description and updated help-document sizes. Independent Astra review found no remaining actionable findings. -The six added cases passed in the package 9 x64 session above; complete x86 qualification remains pending. - -## Source dependency verification from 2026-10-19 - -The initial source verification pinned Client `feaf14a0fad0bbbb869bbe4badb547304aa81d8f` and SDK -`06c529f84e47295e3ca7a4e81ca808b9c4686b3a`. Client's source-reference support was merged through its PR #67, -with its portable, package-consumption and CI checks passing. MCP's normal and AOT lockfiles resolve these -dependencies as projects. Tests verify the dependency identity, Release compilation and the direct SDK reference -boundary; CI initializes both submodules recursively. - -The portable Release suite passed **5,559** cases and standalone Lua passed **665**, with no failures or skips. -The reports are in `artifacts/test-results/ai-analysis-20261019/roadmap-record-delete-portable` and -`roadmap-record-delete-native-lua`. Commit `ef37e32` also passed the isolated single-DLL load probe and Native AOT -gateway smoke check in `artifacts/dist/roadmap-source-20261019-4`. - -The expanded private x64 workflow found that this CE host has no `AddressList.clear()` method. The fix preflights -all records, then destroys them through their current IDs; regression tests cover nested records, callback-driven -deletion, callback-created survivors, empty lists, refusal and bounded error details. The subsequent live run -`20261019T111119Z-887f` verified its cleanup, but stopped at a separate SDK defect: assigning a null parent reports -success without moving a record out of its group. User state was restored, the source installation was unchanged -and cleanup reported no failures. This failed session does not qualify the complete workflow or its prepared -native evidence mappings. - -The parent-assignment correction uses Client `9b836e6ced8e97b4506dc8523998c7c2081cc2ad` (merged main) -and SDK `09dad6c15782d9eee38f013d834d432d79e6623e` (PR #96 merged after all CI gates passed). -Both submodules now use their merged `main` commits. The local results below were run on the SDK's equivalent -pre-squash candidate `c4480e6c7fa24b58a6b495a378f11cdca042673c`. -SDK now resolves the child again and verifies its parent ID after the setter returns. Nested root moves are refused -before mutation because CE has no supported detach operation; an already-root record is a verified no-op. -The protected getter tests cover errors and malformed parent IDs before and after assignment, without fabricated -runtime resets. All **746** Engine NativeLua cases passed; independent Astra review found no remaining defects. - -MCP's corresponding Release suite passed **5,559** cases and standalone Lua passed **665**, with zero failures or skips. -Reports are under `artifacts/test-results/ai-analysis-20261019/roadmap-parent-portable-3` and -`roadmap-parent-native-lua-2`. Eight additional native evidence mappings bring the prepared total to **122**. -Five are specific to the smoke scenario; the resource and job mappings use the expanded workflows. Each mutable -case requires independent readback and cleanup evidence. These new mappings still require passing native sessions. - -Private x64 run `20261019T121114Z-6c36` verified the record-parent correction and completed the file/table operations, -then stopped because `process_set_paused` treated CE's opaque unattached-debugger breakpoint value as a stopped -target. The harness restored user state, left the source installation unchanged and reported no sandbox cleanup -failures; the fixture's pause cleanup also encountered that false refusal. This failed run does not qualify the -complete workflow. - -The corrected pause/resume, execution, Mono and .NET structure guards check debugger attachment first and require -a boolean breakpoint result only when attached. They preserve prepared register globals and retain a tracked pause -when resume cannot be verified. Independent Astra review found no remaining findings in this fix. The clean Release -rebuild passed with zero warnings/errors; **5,559** portable cases and **683** standalone Lua cases passed with no -failures/skips. Their parsed reports are in `debugger-state-portable-1` and `debugger-state-native-lua-1` under the -same artifact directory. - -Private x64 run `20261019T123934Z-d8c3` used the merged source dependencies and passed pause/resume, code analysis, -module exports and code dissection. It stopped at the function-range fixture: that assertion incorrectly required -an exact function entry and exclusive end, although CE documents an estimate. The API now describes its raw stop -boundary and the fixture checks a bounded estimate against the owned instruction layout. The run restored user -state, left the source installation unchanged and reported no cleanup failures. This failed session does not -qualify the complete workflow. - -The corrected function estimate passed in private x64 run `20261019T125424Z-afb6`, including dissection cleanup. -The next failure was in the .NET metadata fixture before any collector call: its expected `Program` type omitted -the owned target's namespace. The fixture now validates the fully qualified type and method metadata before host -startup, with a portable reflection cross-check. User-state restoration and the installation fingerprint passed; -cleanup reported no failures and no owned processes remained. This session remains failed, so its receipts are -observations rather than full-workflow qualification. - -Review also found that the speedhack guards and debugger status query used `debug_getContext(false)` without -preserving prepared register globals. A separate bounded context reader now requires a local x86/x64 debugger -interface and restores the known register write set after every result, including malformed returns and exceptions. -Regression cases preserve absent, false and object-valued globals while allowing unrelated callback changes to -survive. Status retains the distinction between context availability and `debug_isBroken`. All **705** standalone -Lua cases and **5,560** portable Release cases passed with no failures/skips; independent Astra review found no -remaining issues in this patch. The clean Release rebuild had zero warnings/errors. Parsed reports are in -`context-preservation-native-lua-2` and `context-preservation-portable-2` under the same artifact directory. -These tests use CE API doubles. An attached-debugger native session is still required to qualify preservation -in the live host. - -## Roadmap verification from 2026-10-19 - -The expanded roadmap build passed with zero warnings or errors. The portable Release suite passed 5,551 tests, -and standalone Lua 5.3 passed 658 tests, with zero failures or skips. The report verifies routing for all 207 -advertised names and positive adapter evidence for all 206 backend tools. Eleven seeded fuzz tests exercise -1,792 generated cases, including valid and malformed offline IL2CPP dumps. - -Fresh combined coverage reaches **21,597 / 22,890 handwritten product C# lines (94.35%)**. The union excludes -generated files and tests. Line hits do not prove every decision outcome, embedded Lua branch or native host behavior. -The private distribution passed its isolated plugin-load probe and Native AOT gateway smoke check. - -These results are retained under `artifacts/test-results/ai-analysis-20261019/roadmap-portable-release-2`, -`roadmap-native-lua-1`, `roadmap-tool-report-portable-1`, and `roadmap-product-coverage-1.json`. -The expanded native fixtures are being reviewed and run separately; a prepared live mapping is not a passing result. -The inspection baseline below remains historical evidence for its own bundle. - -## Inspection additions from 2026-10-19 - -The catalog adds `memory_find_strings` and `memory_read_vtable`. The existing `pointer_read_chains` gains optional -completed-hop traces while preserving its compact default and read budget. The new portable adapter cases assert -Unicode decoding, byte addresses, paging, bounded previews, pointer widths, ordered slots, per-slot errors and -target-change rejection. MCP and gateway cases preserve wide addresses and distinguish null pointers from failed -reads. Updated contract snapshots and positive adapter mappings include both new tools. - -The verified inspection baseline passed 5,387 portable tests in both Debug and Release and 651 standalone NativeLua -tests, with no failures or skips. Nine deterministic fuzz tests cover 1,600 generated cases across values, pointer -files, Unicode memory inspection, virtual tables and pointer chains. The .NET SDK and CI use 10.0.402; the matching -bundle passed the isolated plugin-load probe and Native AOT gateway smoke check. - -Private x64 run `20261019T073555Z-2c66` and x86 run `20261019T074249Z-5578` passed with two owned targets and two -private CE copies. Both restored user state, preserved the source installation and reported no cleanup failures. -The x86 fixture was corrected to select the main module by an independently observed base address because CE can -report duplicate identical module names under WOW64; the earlier failed run remains diagnostic evidence only. - -Each architecture's report verifies 196 routing cases, 195 positive adapter mappings and 62 native success mappings. -The remaining 133 backend tools are unverified for native success. The sessions include target/gateway/host restarts, -but zero plugin reload cycles and no enabled optional capability gates. - -The package hashes were: - -| File | SHA-256 | -| --- | --- | -| `CheatEngine.Mcp.dll` | `1969546DF46C548D4AFB4408886493F538C5FCF9458D9E3DF36ECBBF41C71047` | -| `CheatEngine.Mcp.Gateway.exe` | `ADA1A445E21D79E354EBBA842B13822C662E83946194B23BD1574B30E533687D` | - -Further roadmap implementation is underway. These receipts qualify the inspection baseline only; new changes need -fresh builds and reports. Earlier coverage percentages below retain their original measurement date. - -## Automated results from 2026-10-16 - -The dependency refresh uses the published NuGet packages `CheatEngine.Client` **1.1.0** and -`CheatEngine.SDK` **2.0.1**. Both normal and AOT lockfiles were regenerated. Debug and Release builds -completed with zero warnings or errors. The isolated bundled-DLL reflection/load probe and the published -Native AOT gateway smoke check also passed. - -Fresh final TRX reports contain: - -| Suite | Passed | Failed | Skipped | -| --- | ---: | ---: | ---: | -| Portable Release | 5,314 | 0 | 0 | -| Portable Debug | 5,314 | 0 | 0 | -| Standalone NativeLua | 651 | 0 | 0 | -| Opt-in live Workflows session | 1 | 0 | 0 | - -The generated report verifies routing for all **194** advertised tools, positive adapter evidence for -all **193** backend tools, and the exact required parameterized cases. The added -`AllToolBehaviorEvidenceTests` derives its names from the production catalog and checks that every -adapter mapping references an existing public test, source file, oracle and case list. This structural -guard does not execute the mapped test or prove its outcome; the report checks the fresh TRX results. -The report records input hashes but does not independently attest that a supplied TRX came from current source. - -Additional tests pin execution argument types and numeric limits, indexed validation errors, timeout -endpoints and symbol-lookup failure semantics. Conversion/calculator tests cover byte order, fixed scalar -values, operator precedence, invalid expressions and resource limits. Pointer-file tests cover schema -and snapshot validation, incomplete coverage, chunked nonseekable reads, stream ownership and failures -before writes. These checks use deterministic inputs and result, error or side-effect assertions. - -Combined portable and standalone Lua coverage reaches **20,193 / 21,369 handwritten product C# lines -(94.50%)**. Hits are unioned by source file and line; generated files and test code are excluded. -This percentage does not measure embedded Lua branch coverage or prove native host behavior. - -| Product project | Covered / instrumented lines | Line coverage | -| --- | ---: | ---: | -| Core | 4,357 / 4,581 | 95.11% | -| Gateway entry point | 9 / 10 | 90.00% | -| Hosting | 1,228 / 1,348 | 91.10% | -| Plugin | 794 / 851 | 93.30% | -| Prompts | 704 / 709 | 99.29% | -| Resources | 222 / 222 | 100.00% | -| Tools | 12,879 / 13,648 | 94.37% | - -The separate static source-to-test pairing analyzer could not parse this repository's C# 14 extension -blocks with its bundled Roslyn 4.14. Its partial classifications are not used as coverage evidence. - -The current private x64 run `20261016T174241Z-e914` passed the eight additional checks, six bounded -workflows and one restart each of target A, gateway and host A. All four optional gates were explicitly -disabled, and zero plugin reload cycles were requested. The session passed in about 37 seconds, restored -user state, left the source installation unchanged and reported no cleanup failures. The runner also -confirmed that no recorded owned processes remained. Its 813 prepared inputs matched their manifest. - -The report verifies **37 backend tools** against this run's exact primary and cleanup receipts. -The other **156 backend tools** still lack native success evidence; gateway-local `instance_list` has -its separate discovery tests. Same-process plugin reload and the native fixtures listed above remain -unqualified. `nativeQualificationComplete` remains false. - -Current private bundle SHA-256 values: - -- DLL: `8C01D51F78642FF036E4616032A541D5DE2BD534006D9FF51C8C97B81FC33AE8` -- Gateway: `664EB1CD2756ED076C443446A6D09A151CEB30CA43BB69BC2392555C3BEFD2E3` - -Fresh TRX and Cobertura files are under `artifacts/test-results/all-tools-20261016/` in -`portable-reviewed`, `portable-debug-reviewed`, `native-reviewed` and `live`. The per-tool report is -`tool-report-final/tool-results.json`, and the deduplicated line totals are in -`product-coverage-final.json`. These local artifacts and raw native logs are not committed. -Installed and released binaries have not been replaced by this work. - -## Automated results from 2026-10-11 - -The final portable suites passed in both Debug and Release: 5,046 tests each, zero failures or skips. -The final standalone NativeLua suite passed 651 tests, zero failures or skips. The report generated from -the Release and NativeLua TRX files verifies positive adapter evidence for all 193 backend tools. -It also verifies routing for all backend tools and the gateway's own discovery cases. - -[`live-evidence.json`](live-evidence.json) maps 37 positive native tool cases from the reviewed workflow -session below. Each case requires its primary receipt and any mapped cleanup receipt, along with a passed -session, restored user state, an unchanged source installation and no cleanup failures. These 37 cases -do not establish native qualification of the entire catalog. - -## Failure observed on 2026-10-11 - -The ordinary x64 lifecycle session used the published `2.0.0-beta.3+059f249cc3293dff65b19ca257ecfe196d20aac1` -plugin and gateway, with all four optional gates disabled. Reading the second instance's runtime resource -failed with `The Lua stack does not contain the inputs required by the protected operation.` -The stack trace reaches `LuaProtectedApi.PushBytes`, `LuaState.TryGetGlobal` and -`MainThreadDispatcher.Dispatch` before the MCP operation body starts. This identifies the failure -boundary; it does not prove its underlying cause. No automatic native retry was made. - -The private run is `20261011T134351Z-8024`. Its final summary reports `passed: false`, -`userStateRestored: true`, `sourceInstallationUnchanged: true` and no cleanup failures. -The eight added live cases were not reached in that run. Raw logs and discovery material stay -in the private run directory; they are not committed. - -The `code_get_comments` fix is currently a source change. The installed/released beta.3 bundle has not -been replaced by this work. The regression checks two ordered addresses through the actual managed -adapter and real Lua 5.3 with CE API doubles, avoiding the array-shape bypass in the previous compile-only case. - -## Later native results from 2026-10-11 - -The ordinary SDK candidate first reached the new checks in `20261011T144953Z-1ce8`, exposing an incorrect -test assumption that a target selected before plugin activation must have a positive selection epoch. -Client legitimately starts at zero; the test now checks a nonnegative epoch and verifies that setting the -same pointer size preserves it. - -Run `20261011T145414Z-f9a6` then found a production bug: `process_list_threads` read `list.Strings[i]`, -but the installed CE reference documents `list[i]` (or `list.String[i]`). The previous double supplied -the unsupported property. The fix uses `list[i]`, and regression doubles expose only numeric entries. -All 637 standalone Lua tests passed after that fix; further adapter additions bring the final total to 651. - -Candidate 2 ran all eight added checks and six workflows successfully in `20261011T150242Z-86a0`, -then failed at the separate Plugin Manager bridge: its checklist remained empty. The earlier experiment -that retained the empty `Plugins64` key failed the same way. Installed CE 7.7 internals are not available; -we have no supported Lua workaround for host-driven disable/re-enable. The exact plugin-row guard stays intact. - -The dedicated `workflows` scenario passed in `20261011T151957Z-8cfc` in 33.5 seconds. It explicitly -stages all four optional gates disabled, checks the eight tools and six workflows, restarts target A, -restarts the gateway, then restarts host A while checking host B and rejecting stale identities. It records -zero plugin cycles. Its final summary reports `passed: true`, `userStateRestored: true`, -`sourceInstallationUnchanged: true` and an empty cleanup-failure list. No recorded owned processes remained. - -That private candidate uses SDK `2.0.1-mcpqualification.1` with the provider guards below. Its bundled DLL -and AOT gateway passed isolated catalog/load probes and gateway smoke checks. DLL SHA-256: -`F0DA5938E8CABE34C7BED9CCEE16FFE92C5731D9E37A9F2546835576DA594D68`; gateway SHA-256: -`E8547E93BF97BE9D0EFA07E225A27A4A6B41000454834CBF7B03A7FD10664C44`. -The private runner verified 930 input hashes before launch. This candidate is neither installed nor released. - -Its ordinary native bridge uses the checked-in SDK binary (`889DC4C2...`), whereas the diagnostic package -used the released CI-built binary (`B008C8D8...`). Their recorded native source/build fingerprints match; -the binaries are not byte-identical. PE and package audits passed. Successful private runs do not establish -that the original released-bundle dispatcher failure is resolved. - -## SDK investigation from 2026-10-11 - -The SDK source now refuses a provider state with a negative stack top before stamp verification or dispatch. -It also verifies that stamp checking preserves the stack top. Both failures release the operation admission -through the existing exception path; they do not reset the stack or retry the provider. These checks contain -an invalid state. They do not fix or explain the original corruption. - -Standalone SDK evidence: 278 native-Lua tests passed, 14 main-thread/coroutine tests passed, and the focused -provider/runtime run passed 14 tests. The rooted-coroutine fixture models the published CE 7.5 topology; -it does not establish the internals of the installed CE 7.7 host. - -The private diagnostic package `CheatEngine.SDK.2.0.1-mcpprovidertrace.2` was used in -`20261011T144032Z-1764`. The bounded resource/memory/scanner/AOB replay passed, restored user state, -left the installation unchanged and stopped all recorded owned processes. Host A recorded 3,593 trace -events and host B recorded 266. All 397 provider-returned states had nonnegative tops and stamp checking -preserved them. Neither trace froze, overflowed nor contained an incomplete record. The failure did not -reproduce; this is not root-cause resolution or full native qualification. - -An earlier candidate replay, `20261011T143815Z-b0c8`, also passed and restored state, but no trace was -created because the private harness had not forwarded the trace opt-in to its sanitized child environment. -That harness omission was corrected and reviewed before the recorded replay. Neither replay reached the -eight additional lifecycle checks. Raw traces, host logs and discovery material remain private. - -Client's dispatcher delegates to synchronous SDK invocation. No Client production change is supported by -the observed failure. Installed and released binaries remain unchanged. +The catalog contains 206 backend tools plus gateway `instance_list`. A verified native +case needs exact candidate identity, successful operation receipts, an independent result +or effect oracle, and cleanup evidence where applicable. Routing, refusals, portable tests +and Lua adapter tests do not establish native tool success. + +## Current status + +The latest catalog-wide native report remains **current-27 (2026-10-21): 200 verified, +7 unverified**, with `nativeQualificationComplete=false`. It applies only to bundle 06: + +| Identity | Value | +|---|---| +| Product | `2.0.0-beta.4+1f987742b7fcd7760057a19508ddacbf2aad4bd8` | +| Plugin SHA-256 | `E05A449551716AC485E2C5897397D109584B94CBEC3444FD8C7461432CF0462B` | +| Gateway SHA-256 | `1FEEFE4975AE558C78932B63C93BAE7A97EB6DBA571C76402B87B63B0406467A` | +| Report SHA-256 | `E5A8293487BCCF155C28DBB72311FC86832A9F00FD54901F22B0123AD50C2BCE` | + +The local [report](../../../artifacts/all-tools-goal/all-tools-current-27/tool-results.json) +contains the per-tool classifications. The seven open tools are `symbol_enable_sources` +and the six DBK/DBVM-dependent operations listed in [the current checklist](../../../TODO.md). +Later source changes and the separately qualified active-lifecycle scenario do not inherit +bundle 06's catalog-wide result. + +## Where to find the evidence + +| Record | Purpose | +|---|---| +| [Current checklist](../../../TODO.md) | One list of remaining requirements and the prerequisites for resuming them | +| [Runner contracts](runners.md) | Maintained scenario boundaries and evidence requirements | +| [Kernel qualification plan](later-kernel-test.md) | Suitability, independent oracles and recovery prerequisites | +| [Test project guide](../../../tests/CheatEngine.Mcp.Tests/README.md) | Portable, NativeLua and explicit live test execution | + +Old checkpoint documents and release drafts were removed from the active tree. Their local +backups are under `artifacts/cleanup-20261025/document-history/`, and prior committed versions +remain in Git history. Raw evidence stays under ignored `artifacts/`; none of it is a second +work queue. Do not repeat unchanged diagnostic runs or mark an environment blocker passed. ## Reproduce the evidence report -Build and run the repository's portable and NativeLua categories as documented in -`tests/CheatEngine.Mcp.Tests/README.md`, with TRX reports enabled. Then run: - -```powershell -./eng/Write-AllToolQualificationReport.ps1 ` - -GatewayTrx artifacts/test-results/all-tools-20261016/portable-reviewed/CheatEngine.Mcp.Tests_net10.0_x64.trx ` - -PortableTrx artifacts/test-results/all-tools-20261016/portable-reviewed/CheatEngine.Mcp.Tests_net10.0_x64.trx ` - -NativeLuaTrx artifacts/test-results/all-tools-20261016/native-reviewed/CheatEngine.Mcp.Tests_net10.0_x64.trx ` - -OutputDirectory artifacts/test-results/all-tools-20261016/tool-report-final -``` - -The output is `tool-results.json` inside that output directory. Supply `-LiveSummary` with the private -session's `summary.json` to include its outcome. A receipt from a failed session is marked -`observed_in_failed_session`, never `verified`. Missing or duplicate routing cases leave their row -incomplete. `nativeQualificationComplete` stays false until the full per-tool native acceptance matrix -is implemented and reviewed; this report does not infer qualification from suite counts. +Use [the test guide](../../../tests/CheatEngine.Mcp.Tests/README.md) to create fresh portable +and NativeLua TRX reports. Then supply their actual paths to +[`eng/Write-AllToolQualificationReport.ps1`](../../../eng/Write-AllToolQualificationReport.ps1), +using `-GatewayTrx`, `-PortableTrx`, `-NativeLuaTrx` and a new `-OutputDirectory`. +For native qualification, pass the exact candidate's session summaries with `-LiveSummaries` +as a PowerShell array. Do not combine different plugin/gateway hashes or change a frozen +candidate during a live session. + +The output is `tool-results.json`. A failed session is `observed_in_failed_session`, never +`verified`; missing or duplicate required receipts leave the case unqualified. Review each +row and the session restoration/cleanup result before changing the checklist. diff --git a/docs/qualification/v2.0.0/compiler-extended-policy.md b/docs/qualification/all-tools/compiler-extended-policy.md similarity index 100% rename from docs/qualification/v2.0.0/compiler-extended-policy.md rename to docs/qualification/all-tools/compiler-extended-policy.md diff --git a/docs/qualification/v2.0.0/compiler-probe-policy.md b/docs/qualification/all-tools/compiler-probe-policy.md similarity index 97% rename from docs/qualification/v2.0.0/compiler-probe-policy.md rename to docs/qualification/all-tools/compiler-probe-policy.md index 0547000..9f1f145 100644 --- a/docs/qualification/v2.0.0/compiler-probe-policy.md +++ b/docs/qualification/all-tools/compiler-probe-policy.md @@ -1,10 +1,7 @@ # Native compileCS qualification policy -Status: The maintainer approved compiler-only phases 1 through 4 on 2026-10-08 for this roadmap session. -Optional phase 5 (managed injection) is not approved. - -[CONTRIBUTING.md](../../../CONTRIBUTING.md#live-qualification) currently prohibits live code-execution probes. -This approval is a bounded exception and does not change the general rule. +This is the maintained contract for the compiler-only harness, not authorization to start a run. +Follow [CONTRIBUTING.md](../../../CONTRIBUTING.md#live-qualification) and the current user request. Every compiler run still requires both explicit runner acknowledgements, a reviewed clean candidate, and the prerequisites below. Ordinary VL authorization alone is insufficient for a future compiler session; managed injection requires separate approval. @@ -183,7 +180,8 @@ The expected signed decimal result is `7319`. ## Measurements and evidence -The report must use the evidence fields defined in [acceptance.md](acceptance.md) and remain redacted before commit. +The report must retain exact candidate, host and fixture identities, operation and cleanup receipts, +independent oracle results and user-state restoration evidence, and remain redacted before commit. It must record the policy revision, authorization decision, candidate commit, clean-tree state, package hashes, payload hashes, and configuration. It must record the Windows build, CE fingerprint and version, .NET runtimes, target architecture, fixture hash, and compiler availability evidence. For every call it must record the instance, operation, start and end time, duration, expected result, actual result, error kind, host effect, retryability, and correlation identifier. diff --git a/docs/qualification/v2.0.0/runners.md b/docs/qualification/all-tools/runners.md similarity index 98% rename from docs/qualification/v2.0.0/runners.md rename to docs/qualification/all-tools/runners.md index 9674cfc..37240c6 100644 --- a/docs/qualification/v2.0.0/runners.md +++ b/docs/qualification/all-tools/runners.md @@ -45,4 +45,4 @@ in the same passing session. Failed and diagnostic-only sessions supply observat Use an unused distribution directory to preserve any gateway currently used by an MCP client. Raw reports stay in the private run directory; committed evidence must redact personal paths and exclude discovery tokens. -Use the [requirement matrix](requirement-matrix.md) to map actual results to the issue checkboxes. +Use the [current checklist](../../../TODO.md) and the per-tool evidence maps to record actual results. diff --git a/docs/qualification/v2.0.0/acceptance.md b/docs/qualification/v2.0.0/acceptance.md deleted file mode 100644 index 8e7b31c..0000000 --- a/docs/qualification/v2.0.0/acceptance.md +++ /dev/null @@ -1,138 +0,0 @@ -# Stable v2.0.0 acceptance criteria - -Status: Stable-scope acceptance plan; criteria pass only at the evidence level recorded for an exact candidate. - -Implementation, offline verification, native qualification, soak, and release publication are separate evidence levels. -A tool implementation or portable test result does not satisfy a native or release requirement. -The current status of every remaining roadmap item is recorded in [requirement-matrix.md](requirement-matrix.md). -The reviewed plan selects all three P1 features for stable v2.0.0 and does not silently defer any of them. -The observed planning environment is Windows 11 Pro x64 build `10.0.26300`, Cheat Engine x64 `7.7.1.10828`, and .NET runtime `10.0.12`. -That observation is not evidence for another Windows build, Cheat Engine build, runtime patch, or final release package. - -## Evidence levels - -- `implementation` proves that reviewed source and documentation implement the intended bounded contract. -- `V` proves locked restore, Debug build, and portable tests without LiveQualification or NativeLua. -- `V+` adds style and whitespace verification, Release build, `eng/Publish.ps1`, and review of intended golden changes. -- `VN` proves changed fixed Lua, Lua runtime, job kernel, or status-indicator behavior against a real Lua 5.3 x64 DLL with CE APIs stubbed. -- `AOT` proves Native AOT publication and the executable MCP smoke check for the exact gateway candidate. -- `VL` proves maintainer-authorized behavior in private real Cheat Engine copies against owned disposable targets. -- `soak` proves sustained candidate behavior and bounded resource recovery over the planned duration. -- `release` proves the final public package, tag, assets, hashes, installation, and first-use path. - -The portable suite must pass against both Debug and Release outputs for release sign-off. -VN evidence must record the selected Lua DLL path in redacted form, its version when available, and its SHA-256. -VL remains outside ordinary CI and must follow the existing explicit acknowledgement, private-copy, user-state backup, reviewed-autorun, and disposable-target restrictions. -Kernel, hypervisor, debugger injection, compiler, and managed invocation probes require their separately reviewed qualification policy before VL execution. -The four capability switches are enabled by default, file and table allowed roots are empty by default, and the switches are exposure controls rather than a sandbox. - -## P1 pointer access analysis - -The planned tool name is `pointer_get_access_info`, subject to final contract review. -The tool accepts bounded supplied instruction facts, target architecture, register values, and an optional observed access address. -The authoritative instruction representation and contradiction rules must be explicit in the public schema. -The result reports the memory operand, base and index registers, scale, signed displacement, effective address, candidate structure base, next pointer-search value, and uncertainty. -The evaluator must preserve the existing `debugger_start_capture` effective-address behavior by reusing or factoring its operand rules. -The tool must distinguish pre-execution context from post-execution access or write context. -The tool must handle x86 and x64 width, register aliases, address-size override, RIP-relative next-instruction semantics, signed offsets, and documented overflow or wrap behavior. -The tool must describe indexed array access as dynamic and must not present it as a stable pointer-chain offset. -The tool must return typed unsupported, missing-fact, contradictory-fact, unresolved-symbol, and observed-address-mismatch outcomes without inventing an address. -The supplied-facts path must perform no live target read, symbol lookup, mutation, or hidden target selection. -Portable and VN coverage must include aliases, scaling, positive and negative displacement, RIP-relative length, address-size override, supplied symbols, missing symbols, missing registers, multiple operands, implicit operands, segment bases, VSIB, undecodable input, and mismatch reporting. -Native qualification must prove that the fact shape captured from reviewed x86 and x64 fixture instructions produces the expected analysis without changing capture behavior. - -## P1 batch pointer-chain validation - -The planned tool name is `pointer_read_chains`, subject to final contract review. -One call accepts at most 128 candidates, at most 64 offsets per candidate, and at most 4,096 combined pointer and final-value reads. -Each dispatch slice admits at most 32 candidates and 128 reads, and combined final-value reads are limited to 65,536 bytes. -Each candidate has a caller-supplied stable identifier, a base expression, and offsets in the existing dereference-then-add signed hexadecimal convention. -The result keeps chain resolution, optional target-address comparison, and optional final-value read as separate statuses. -An unreadable hop must remain distinct from a resolved miss, a resolved match, and a failed final-value read. -The tool compares the resolved address before the optional final-value read and preserves the comparison result when only that read fails. -Matches-only mode must retain aggregate processed, matched, missed, unreadable, error, and not-run counts. -Cancellation or interruption after completed candidates must return bounded partial progress without describing unprocessed candidates as misses. -Every candidate in one call must use one target selection epoch, and a changed epoch must stop the remaining work. -Module expressions must rebase at validation time, while absolute roots must be documented as restart-sensitive. -The tool must not create, mutate, or delete saved pointer maps, scans, paths, or caller input. -Portable coverage must verify bounds, stable ordering, all statuses, overflow, signed offsets, module rebasing, response summaries, cancellation, and target-epoch change. -Native qualification must exercise known stable and unstable chains on disposable x86 and x64 fixtures across target restarts, including an unreadable hop and a resolved address with an unreadable final typed value. - -## P1 typed C# compilation - -The planned tool name is `exec_compile_csharp`, subject to feasibility and final contract review. -The feasibility check must confirm `compileCS` availability, signature, diagnostics, prerequisites, output filename, and lifetime on the reviewed Cheat Engine 7.7 build. -The tool accepts at most 131,072 source characters and at most 32 explicit reference assemblies. -Every reference must pass the existing host-file policy and remain held against replacement while Cheat Engine reads it. -The tool requires the target-code-execution capability before invoking Cheat Engine. -The operation uses the blocking-native dispatch class and reports timeout or lost-response effects honestly. -The stable path must copy the generated assembly to a caller-selected approved-root path before returning success. -The result must include the checked output path, byte length, SHA-256, bounded diagnostics, and the compiler availability outcome. -Compiler diagnostic text is limited to 16,384 UTF-8 bytes with an explicit truncation flag. -The upstream compiler supplies line-message text rather than reliable structured diagnostic codes or severities. -Compilation remains separate from injection or invocation, and a later `exec_inject_dotnet` call is always explicit. -The implementation must not promise assembly unload, CE temporary-file retention, or cleanup that Cheat Engine cannot confirm. -Portable and VN coverage must include bounds, disabled capability, invalid references, changing files, protected paths, diagnostics, missing compiler, output collision, copy failure, and missing or expired CE output. -Native qualification requires a separately reviewed code-execution policy and must cover valid source, compiler diagnostics, unavailable prerequisites, instance-B shutdown during an instance-A artifact workflow, approved-root export, separate injection, and cleanup. -Failure of the feasibility check blocks this feature from contract freeze and requires an explicit linked deferral with evidence. - -## Contract and package acceptance - -The reviewed tool inventory, registrations, source-generated JSON metadata, backend and gateway schemas, resources, prompts, completions, knowledge documents, and packaged reflection output must agree. -Reflection-disabled JSON and Native AOT analysis must cover every new input and result type. -Exact integers, 64-bit addresses, signed offsets, null values, empty values, errors, host effects, retryability, details, hints, and correlation identifiers must survive backend and gateway serialization. -Every routed backend call must require its explicit `instanceId`, and disappearance must never select another instance. -The packaged-DLL probe must receive the reviewed golden directory explicitly and compare the actual lone packaged plugin DLL with the candidate goldens. -The plugin DLL, gateway executable, ZIP, checksums, version, source identity, and tag must agree before publication. -The clean tested compiler candidate is `808a626e65eddab113737a27a7c2345555b1c241`, still identified as `2.0.0-beta.2`, and is not the final stable candidate. -The historical pointer smoke used different package bytes recorded in `local-checkpoint.md`, so its native results must not be attributed to the clean compiler candidate. - -## Performance acceptance - -Performance thresholds remain planned until three reproducible baseline runs record the environment, workload sizes, and measurement method. -For a short operation, the candidate p95 must not exceed the greater of twice the baseline p95 or the baseline p95 plus 100 milliseconds. -For a bounded batch or job operation, the candidate p95 must not exceed one and one-half times the baseline p95. -Instance discovery, busy refusal, and admission failure must remain bounded and must not wait for unrelated native work to finish. -Long work must expose bounded jobs, progress, truncation, dropped-item accounting, stop behavior, and TTL behavior. -Documented blocking-native calls may outlive an MCP timeout, but they must report that limitation and must not report false success. -Completions and live resources must share admission bounds and must not start an unbounded scan. - -## Planned two-hour soak - -The soak is planned and has not passed. -The exact candidate package must run for two continuous hours with two private Cheat Engine instances. -The workload must include at least 1,000 short calls, 20 bounded jobs, 30 plugin enable or disable cycles, and 10 target restart or switch cycles. -The soak fails on a crash, hang, cross-instance effect, stale target effect, cleanup failure, unexplained drop, false success, or retained owned resource. -After final cleanup, process handles must return to no more than the stabilized baseline plus 25. -After final cleanup, private bytes must return to no more than the stabilized baseline plus 64 MiB. -The baseline and final measurements must use the same idle interval and collection method. -Any threshold change requires recorded baseline evidence and review before RC freeze. - -## Evidence record schema - -Each evidence entry in [release-signoff.md](release-signoff.md) must include a unique evidence identifier and the roadmap requirement identifiers it covers. -Each entry must name its level, status, candidate version, full commit, clean-tree state, configuration, and applicable artifact hashes. -Each native entry must record the Windows build, Cheat Engine version and installation fingerprint, exact .NET runtimes, target architecture, fixture identity, and MCP client when applicable. -Each entry must record the start time, duration, bounded cases, expected result, actual result, cleanup outcome, limitations, reviewer, and safe evidence reference. -An entry status is `pass`, `fail`, `blocked`, `not_run`, or `not_applicable`. -Raw tokens, discovery records, personal paths, e-mail addresses, and unredacted live transcripts must never enter committed evidence. -A roadmap checkbox may close only when reviewed evidence covers its complete acceptance criterion on the applicable candidate or links an explicit reviewed deferral. - -## Installation and migration acceptance - -Clean installation must use only the exact candidate ZIP and its included instructions. -Upgrade qualification must replace beta.2 and any older folder deployment without mixing plugin and gateway builds. -Install, upgrade, disable, uninstall, and rollback must preserve user configuration, tables, unrelated plugins, Cheat Engine settings, and recoverable backups. -Paths with spaces, a standard user account, allowed roots, read-only deployment behavior, malformed configuration, occupied endpoints, and unavailable log locations must have recorded outcomes. -The final release notes must state the support matrix, beta migration, capability limitations, recovery paths, and changes since beta.2. -The default beta-to-stable contract permits additive optional fields and new tools when existing callers retain their behavior. -Removing or renaming tools, changing existing field meanings, changing pointer offset order, or changing host-effect reporting requires a reviewed future breaking contract. -The current working tree proposes explicit exceptions before stable freeze: unavailable fields become omitted, collector descriptions are corrected, effect metadata changes, and the blocking module-exports resource URI is removed in favor of its explicit tool. -These changes are enumerated in release-notes.md and offline-contract-audit.md; they are not claimed as additive-compatible changes. Row 2.07 remains partial until the final contract review accepts or revises these exceptions and the migration guidance. -The beta tags remain unchanged, and stable publication uses a new annotated `v2.0.0` tag at the exact final binary source commit. - -## P2 and research disposition - -Section 12 conveniences and section 13 research are outside the stable v2.0.0 blocking path unless a later reviewed decision promotes one into scope. -Each item still requires an explicit linked follow-up or an unsupported disposition before roadmap closure. -No unchecked P2 or research item may be described as implemented, qualified, or silently deferred. diff --git a/docs/qualification/v2.0.0/admission-preparation.md b/docs/qualification/v2.0.0/admission-preparation.md deleted file mode 100644 index 8c3ec2d..0000000 --- a/docs/qualification/v2.0.0/admission-preparation.md +++ /dev/null @@ -1,46 +0,0 @@ -# Isolated admission measurement preparation - -Status: implemented in an isolated snapshot, managed-tested and independently reviewed by Astra high with no remaining actionable findings in this scope. No new native session or performance qualification is claimed. - -Snapshot: `artifacts/performance-preparation/admission-dcbf0ddf13a04534a0bc83af3fdeb37b`, copied from the previously reviewed [performance preparation](performance-preparation.md). The primary source, ordinary package and pending Settings-observation inputs remain unchanged. The source inventory verifies 861 files with exactly seven new or modified files relative to that prior snapshot. - -## Prepared measurement - -Each of the three hash-pinned beta.2 baseline sessions and three candidate sessions will open two independent gateway processes. Four rounds each issue eight 1,024-item int32 batches against owned host A, split equally between gateways, alongside discovery, a host-B sentinel read, one serialized CE UI snapshot, a one-item regions resource read, and module completion. All requests in a round settle before the next round or cleanup. Before/after checks compare retained jobs/resources on both hosts and verify the four optional gates are disabled. - -Every sample records its client, category, monotonic start/end timestamps and outcome. Only exact protocol/tool `busy` with `hostEffect=not_started` counts as a refusal. Timeouts, cancellations, unexpected failures and empty completions remain distinct. Failure receipts preserve bounded diagnostics, inner/stack information when available, operation and error correlation. A failed peer does not let the measurement return while other requests remain active. - -Comparison requires three complete baselines and one complete candidate: four rounds, thirteen uniquely identified requests per round, the exact client/category distribution and interval-derived concurrency. It requires cross-gateway batch overlap, and every observer or busy batch must overlap successful batch work in the same round. Missing successful/refused response classes, unloaded observations, or empty completions cannot become success evidence. Successful batch p95 uses the 1.5-times baseline budget; other measured response classes use the short-call allowance. The worst of three baseline p95 values is retained for each class. - -These intervals describe client calls, not native dispatch occupancy. The UI observation includes the CE driver's 100 ms timer and file exchange. First/repeated module completion is labeled separately; repeat calls may hit the five-second cache. Empty completion does not establish a busy refusal, and its refresh may outlive the response. Four observations of an observer category are a fixed diagnostic sample set, not broad statistical qualification. - -## Managed verification - -Release build passed with zero warnings/errors. The exact four-class selection passed **52 tests, zero failures, zero skips**: ten measurement tests, twenty-two comparison tests, nine performance-matrix tests and eleven lifecycle-helper tests. Logs and TRX reside inside the snapshot; exact source/log/test-module hashes are recorded in `artifacts/performance-preparation/admission-evidence.json`. - -| Requirement | Evidence | -| --- | --- | -| Wait for peers after failure and active cancellation | `MeasureAsync_WaitsForEveryPeerAfterOneFails`, `MeasureAsync_ActiveCallerCancellationSettlesAndClassifiesTheInvocation` | -| Refuse invalid request lists before starting work | `MeasureAsync_ValidatesEveryRequestBeforeInvokingAny`, `MeasureAsync_RejectsMoreThanSixteenRequestsBeforeInvokingAny`, `MeasureAsync_PreStartCancellationInvokesNothing` | -| Distinguish actual protocol refusals, malformed failures and empty completion | `ClassifyTool_RequiresExactBusyHostEffectAndRejectsMalformedErrors`, `Classifiers_DistinguishProtocolToolFailureAndEmptyCompletion` | -| Preserve failure diagnostics and correlation with bounded output | `Classifiers_RetainBoundedDiagnosticsAndProtocolCorrelation` | -| Reject missing refusals or a deficient baseline | `Compare_FastSuccessfulRequestsCannotReplaceMissingRefusals`, `Compare_OneBaselineMissingAnOutcomeCannotBeReplacedByTheOtherTwo` | -| Reject observer-only overlap and unloaded observations | `Compare_UiOverlapCannotReplaceCrossGatewayBatchOverlap`, `Compare_RejectsUnloadedObserverAndBusyTiming` | -| Refuse malformed rounds/clients/timing claims and enforce budget boundaries | `Validate_RejectsIncompleteOrMisreportedEvidence`, `Compare_AppliesResponseSpecificBudgetAtBoundary` | - -Successful commands from this snapshot: - -```powershell -dotnet build tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj -c Release --no-restore -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet artifacts/bin/CheatEngine.Mcp.Tests/release/CheatEngine.Mcp.Tests.dll --filter-class CheatEngine.Mcp.Tests.LiveQualification.LiveAdmissionMeasurementTests --filter-class CheatEngine.Mcp.Tests.LiveQualification.LiveAdmissionComparisonTests --filter-class CheatEngine.Mcp.Tests.LiveQualification.LivePerformanceMatrixTests --filter-class CheatEngine.Mcp.Tests.LiveQualification.LiveLifecycleQualificationTests --fail-skips on --report-trx --results-directory artifacts/admission-final-results -``` - -The initial build found an invalid test JSON literal; a later build found formatting/analyzer violations. Both were corrected before the first passing 49-test run. Astra then found that observer timing could occur outside active batches and that exception classification discarded diagnostic details. The repairs added overlap controls and bounded diagnostics, followed by the clean 52-test run above. Astra's second read-only pass found no remaining actionable correctness findings in this admission preparation. - -## Remaining native enumeration gap - -The actual package source establishes that 4,096-module and 16,384-region collection sizes are destination bounds, not native traversal bounds. Client allocates that destination; SDK invokes CE's whole-table producer first, inspects the complete Lua sequence, and returns `DestinationTooSmall` before copying if necessary. CE's module iterator runs to exhaustion; its region iterator starts at zero and has no item cursor. Module-detail navigation also enumerates through `ModuleLocator.Find`. - -Package provenance: Client `v1.0.0` tag `110a302b0908cbf8d71e4f5cdf1f0f0e6e7fe31cfc`; SDK `2.0.0` source `325c47b573f8bd39a247f1d0101f110fa36c1696` and lock-matching package content hash. References: [Client inspection](https://github.com/CheatEngineNet/CheatEngine.Client/blob/v1.0.0/libs/CheatEngine.Client.Core/Domains/InspectionClient.cs), [SDK inspection](https://github.com/CheatEngineNet/CheatEngine.SDK/blob/325c47b573f8bd39a247f1d0101f110fa36c1696/libs/CheatEngine.SDK.Engine/Inspection/EngineInspection.cs). Installed CE binary equivalence to the inspected public CE source remains unproven. - -The separate [prepared-navigation repair](navigation-preparation.md) now preserves module/detail/region resources and module completion through five-second immutable snapshots prepared by explicit source tools, with process/epoch validation and truthful cold/stale errors. Its isolated implementation passes 4,207 portable tests. In that snapshot, admission qualification explicitly prepares modules/maps before timing each round and distinguishes the baseline completion cache from the candidate fresh policy. Final 9.01/9.03/9.05 qualification, integration, candidate packaging and native runs remain open. diff --git a/docs/qualification/v2.0.0/compiler-checkpoint.md b/docs/qualification/v2.0.0/compiler-checkpoint.md deleted file mode 100644 index a54f9b4..0000000 --- a/docs/qualification/v2.0.0/compiler-checkpoint.md +++ /dev/null @@ -1,112 +0,0 @@ -# Compiler qualification checkpoint - -Date: 2026-10-08. -Status: Approved compiler-only phases 1 through 4 passed locally; stable release qualification remains incomplete. - -## Exact tested candidate - -The tested checkout was clean on `codex/roadmap-v2` at `808a626e65eddab113737a27a7c2345555b1c241`. -Both published binaries identify as `2.0.0-beta.2+808a626e65eddab113737a27a7c2345555b1c241`. -This checkpoint records those bytes; a later documentation commit is not a newly qualified binary candidate. -No push, tag, release upload, installed-plugin replacement, or managed injection was performed. - -| Artifact | SHA-256 | -| --- | --- | -| Release plugin DLL | `C07B5DBE73B8AE509C091850CE0F74501028CB868D4AEE0000F07E821B5419FC` | -| Release Native AOT gateway | `7DB3AF0853D85198FE07280BCBB5A4047115D08A344FC31FB8FF6ABE5F23139D` | -| Debug plugin DLL | `93F1F3B22A177654BC092E3525985049C1AD39F250CF7F7AE905C07C42E5A610` | -| Debug Native AOT gateway | `85B423D61D20D4EED5C8E2929132D78DD2B5BC24200EF79B576653834477B740` | -| Local Release ZIP | `EB803348B3F953D89D735637CCE95F254DCA5EF66B2480CDCF72239453EBFB42` | -| Reviewed compiler policy | `C35BC2F0A21639C52D8F69088349CEAB5B49DB64C88983DED33272304EBAD3B2` | -| Fixed raw-compiler bridge template | `E42693FFD25AD257262CEA04C228D5540F4887FA8F5EC759A1E087EFA404A84B` | - -The fixed source and reference hashes are checked before admission and recorded in each run summary. -They match the payloads in the [approved compiler policy](compiler-probe-policy.md). - -## Build and package evidence - -- Locked restore passed; Debug and Release nonincremental builds passed with zero warnings and errors. -- Portable tests passed 4,037/4,037 in each configuration, with zero failures or skips. -- NativeLua passed 611/611 using the installed Lua 5.3 x64 DLL and stubbed CE APIs. -- Both format checks passed. -- Both publications compared the actual lone packaged plugin DLL against the explicit goldens in two isolated load contexts and passed the Native AOT executable MCP smoke checks. -- The Release ZIP passed file and embedded-dependency inventory, matching binary identity, entry-hash, and checksum checks. - -The catalog remains 193 backend tools, 43 resources, 15 templates, and 43 prompts; gateway discovery adds `instance_list`. -Portable commands excluded `Category=LiveQualification` and `Category=NativeLua` and used `--fail-skips on`. -Build and package logs are retained locally as `artifacts/issue-edits/compiler-qualified-*.log`. -The tested distributions are `artifacts/dist/roadmap-debug` and `artifacts/dist/roadmap-release`. -The ZIP is `artifacts/releases/compiler-qualified/CheatEngine.Mcp-2.0.0-beta.2-win-x64.zip`; it does not replace the public beta.2 release. - -## Native environment and prerequisites - -| Component | Observed identity | -| --- | --- | -| OS | Windows 11 Pro x64, `10.0.26300` | -| Build SDK and test runtime | .NET SDK `10.0.401`; runtime `10.0.12` | -| Plugin host frameworks | .NET, ASP.NET Core, and Windows Desktop `10.0.12`, x64 | -| x86 fixture runtime | .NET `10.0.12`, x86 | -| Cheat Engine host | x64 `7.7.1.10828`; SHA-256 `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F` | -| CE compiler bridge | `CSCompiler.dll` `1.0.0.0`; SHA-256 `13DCE393B59B9EF4A5D4FCDC27267D018B350BDC44A62AACC5DBC7F1DF7F7A1C` | -| Installed .NET Framework | v4 Full release `533509`, version `4.8.09221` | -| Framework C# compilers | x86 and x64 `csc.exe` `4.8.9221.0` | -| Lua 5.3 x64 | SHA-256 `C95DCDFA0F60F97B43D970D77FD1BB907AF4DE04B500A3C89A99600B20B35BD2` | - -CE exposed `compileCS` and successfully compiled the fixed source on this installed prerequisite set. -This proves availability in the recorded environment, not a minimum-runtime claim or behavior when a prerequisite is absent. -The existing reviewed autorun hashes passed unchanged. -No prerequisite was installed, no source installation was changed, and no saved registry setting was changed to admit the run. -The x86/x64 labels below describe the selected disposable targets; the compiler and plugin host remained x64, and no generated assembly was loaded into either target. - -## Native results - -| Evidence ID | Target | Disabled-gate run | Enabled compiler run | Result | -| --- | --- | --- | --- | --- | -| COMPILER-01 | x64 | `20261007T234620Z-a6d5` | `20261007T234637Z-777e` | Pass; combined test 40.443 seconds. | -| COMPILER-02 | x86 | `20261007T234723Z-ba94` | `20261007T234739Z-96c7` | Pass; combined test 41.210 seconds. | - -Each disabled-gate run refused before compiler dispatch and produced no output or temporary-tree change. -Exclusive CE scan leases were compared by exact path and four-byte length; their unreadable contents were not claimed as hashed evidence. -Each enabled run compiled the valid fixed source once and immediately exported a 3,072-byte PE file whose length and SHA-256 matched the result. -Instance B stayed responsive and its compiler directories remained unchanged during A's compiler calls. -Invalid source, invalid reference, and invalid core assembly each returned the expected `host_refused` / `hostEffect=unknown` diagnostic with no published output or protected partial file. -Their diagnostic lengths were 16, 241, and 241 UTF-8 bytes respectively in both target runs, without truncation. -Missing reference and missing core assembly were refused as `not_found` / `hostEffect=not_started` without a compiler receipt or temporary-tree change. - -| Target | Export SHA-256 | Separate raw artifact SHA-256 | -| --- | --- | --- | -| x64 | `64CAEF72C3096D846301D3E9AE700EEA427DE5267A4B83CB958682FB3769ACF8` | `B5F3AC1BF2D8CC006B02EB2EE7ED85861AC999FFB503F2E06988FF58A681E7B0` | -| x86 | `59ECF4F9EFF2A9AEC4BFE811E37548FD3E99E5B50E268146145EB42E5892859B` | `50630CFAA9D4B2A07D78DEE0D53A043EAA4A5AD54FEB6DEAB18952E61051DA93` | - -Both runs classified raw lifetime as `retained_after_a`: the separate raw artifact retained its original hash after B closed and after A closed, at the policy's two-second observation intervals. -The exported file independently retained its own original hash after both shutdowns. -A and B used distinct temporary roots; these observations do not qualify shared-temp cleanup, reload, eventual expiry, or missing-artifact behavior. -The harness subsequently removed the owned compiler files only after all owned processes stopped and restoration succeeded. -All four run summaries report `passed=true`, empty `cleanupFailures`, `userStateRestored=true`, `sourceInstallationUnchanged=true`, and `ownedCompilerFilesRemoved=true`. -Preexisting MCP gateways were preserved, and no owned CE host or fixture remained after the runs. -No generated assembly was injected, invoked, or executed. - -## Refused attempts and path diagnosis - -Run `20261007T232735Z-e269` stopped before a compiler call because CE's exclusive scan lease could not be hashed. -The harness now records only the exact known lease's path, length, and exclusive status, while every other unreadable file still stops qualification. -The correction has seven portable regression cases and independent review. -Run `20261007T233614Z-351e` passed the disabled gate; enabled run `20261007T233631Z-d1f6` then refused its output path before compiler admission. -Read-only native probes established that the desktop app's packaged-app filesystem redirected the logical LocalApplicationData path into its package cache, despite ordinary directory attributes and no filesystem reparse tag. -The existing secure path policy correctly refused the redirect. -The successful runs used the existing `CHEATENGINE_MCP_LIVE_QUALIFICATION_RUN_ROOT` override with the verified physical directory; the unchanged anchored walk succeeded through every component. -No production path restriction was weakened, no additional compiler call was replayed, and all stopped sessions restored state and removed owned compiler files successfully. -The earlier attempts had zero compiler receipts. - -## Review and remaining work - -Independent Astra review covered the candidate and compiler harness, including exact opt-in, fixed payloads, candidate identity, request publication, response bounds, path containment, exclusive leases, cleanup, and evidence claims. -Review findings were fixed before the successful native runs, with portable and NativeLua regressions. -The final independent Astra pass found no actionable findings in the completed scope after checking all four summaries and validation logs and independently recomputing the artifact hashes. -This review does not claim the absence of all possible defects or completion of the open qualification and release gates. - -The feasibility and installed-prerequisite roadmap item is complete for the recorded CE build. -Unavailable compiler or absent-prerequisite behavior, shared-temp lifetime and expiry, reload, delayed export after closing B, and separate managed injection remain unqualified. -Optional phase 5 has not been approved. -The broader lifecycle/domain matrix, measured performance baselines, two-hour soak, installation/upgrade/rollback, supported MCP clients, CI/analysis, final stable source and package, and public release remain open. -The earlier pointer/native smoke evidence is retained separately in [local-checkpoint.md](local-checkpoint.md) and must not be attributed to these compiler-only runs. diff --git a/docs/qualification/v2.0.0/contract-checkpoint.md b/docs/qualification/v2.0.0/contract-checkpoint.md deleted file mode 100644 index 0ad2458..0000000 --- a/docs/qualification/v2.0.0/contract-checkpoint.md +++ /dev/null @@ -1,118 +0,0 @@ -# Stable v2.0.0 contract checkpoint - -Date: 2026-10-09. -Status: Current-source audit with bounded clean-candidate evidence; final stable contract freeze remains incomplete. - -## Candidate identity - -The clean tested candidate is `808a626e65eddab113737a27a7c2345555b1c241` and still identifies as `2.0.0-beta.2+808a626e65eddab113737a27a7c2345555b1c241`. -Its Release plugin SHA-256 is `C07B5DBE73B8AE509C091850CE0F74501028CB868D4AEE0000F07E821B5419FC`. -Its Release Native AOT gateway SHA-256 is `7DB3AF0853D85198FE07280BCBB5A4047115D08A344FC31FB8FF6ABE5F23139D`. -Its local Release ZIP SHA-256 is `EB803348B3F953D89D735637CCE95F254DCA5EF66B2480CDCF72239453EBFB42`. -The clean candidate passed 4,037 portable tests in both Debug and Release, 611 NativeLua tests, both packaged-DLL golden comparisons, and the Native AOT executable checks recorded in `compiler-checkpoint.md`. -The historical pointer smoke used Release plugin SHA-256 `E6E2AE56B43CF54F1F5978B566EE762939B76E549D557FDDF8B073BB8262750B` and must not be attributed to the clean compiler candidate. -Neither candidate is the final stable package. - -## Section 3 crosswalk - -| ID | Current result | Exact existing evidence | Required candidate level and remaining work | -| --- | --- | --- | --- | -| 3.01 | partial | `ContractSnapshotTests.GatewayCatalog_CurrentBuild_MatchesGoldenSnapshot`, `ContractSnapshotTests.Backend_ToolsAndInitialize_MatchGoldenSnapshots`, `CompositionParityTests.SchemaOnlyCatalog_MatchesTheLiveBackendListing`, `CompositionParityTests.GatewayTools_AreTheBackendToolsWithOnlyTheRoutingArgumentAdded`, and the clean packaged-DLL comparison passed. | Repeat V+ and packaged reflection after final names, workflows, prompts, resources, and goldens freeze on the exact RC and final stable commit. | -| 3.02 | pass for the clean tested candidate | `CompositionParityTests.PluginAndGatewayExecutable_RegisterTheSameJsonResolvers`, both Debug and Release Native AOT checks, and the packaged-DLL golden comparison cover the three P1 input and result types. | Repeat V+, AOT, and packaged reflection on the exact RC and final stable commit. | -| 3.03 | source audit and standalone fixed-Lua regressions complete; exact-candidate and CE evidence pending | The full-domain review in offline-contract-audit.md covers unavailable observations, zero/empty results, raw collector conventions and gateway forwarding. All 626 Release NativeLua cases passed after narrow user authorization. | Retain portable/schema/standalone/native results on the exact candidate; do not infer installed-collector parity or real CE behavior from public source and stubbed host APIs. | -| 3.04 | portable client audit complete; final candidate pending | The 12 `ExactValuePipelineTests` cases cover maximum 64-bit addresses and pointers, both signed 64-bit extremes, unsigned values above JavaScript's safe range, and negative hexadecimal offsets through a real .NET MCP client and gateway. | Retain these regressions in the clean RC and final stable checks; this is not qualification of named interactive clients or real target memory. | -| 3.05 | pass for the clean tested candidate | `GatewayToolCatalogTests.RoutedTools_InstanceId_IsTheFirstPropertyAndRequirement`, `GatewayServerTests.CallTool_TwoPublishedInstances_RoutesOnlyToRequestedBackendAndStripsInstanceId`, `GatewayServerTests.CallTool_MissingInstanceId_ReturnsInvalidArgumentEnvelope`, `GatewayServerTests.CallTool_UnknownInstanceId_ReturnsInstanceUnavailableNotStarted`, and `GatewayServerTests.CallTool_StaleIdentity_ReturnsInstanceUnavailableNotStartedAndNeverForwards` cover explicit routing and disappearance. | Repeat portable V+ and the maintained two-instance VL on the exact final package. | -| 3.06 | source audit complete; candidate validation pending | The inventory review covers all 193 backend tools. Six further metadata corrections supplement the earlier eight, and the slow exports resource projection is removed while the explicit tool remains. See offline-contract-audit.md. | Validate final snapshots, descriptions and packaged reflection on the exact candidate; native dispatch timing remains a separate qualification gate. | -| 3.07 | source audit, selected portable cases and standalone hint regression complete; real CE and final candidate pending | Existing gate tests plus the new table, secondary AA, mixed record batch and process-open tests below cover refusal before effects. `record_set_script` now declares its fixed AA requirement and refuses before dispatch; the `record_clear` recovery-hint assertion passed in the approved standalone NativeLua suite. | Verify actual CE table/script hooks when permitted and retain the complete portable/standalone/native evidence on the final candidate. | -| 3.08 | pass at documentation level | `McpConfigurationTests.Load_Defaults_AreLoopbackAndAllExecutionIsEnabled`, `McpConfigurationTests.Load_PluginUserAndEnvironment_UseDocumentedPrecedence`, README configuration text, `support-matrix.md`, and `acceptance.md` agree on enabled switches, empty roots, precedence, and exposure semantics. | Reconcile these statements against the exact RC configuration and final release notes. | -| 3.09 | source and portable audit complete; final candidate pending | Existing `ContractJsonTests` and gateway protocol-error tests remain applicable. `ErrorCorrelationTests.CallTool_EveryHostEffect_PreservesCorrelatedEnvelopeThroughGateway` adds direct and routed coverage for all six host effects, exact envelope equality, hint, details, retryability and the logged correlation ID. Non-object context and invalid reserved IDs have explicit regressions. | Include the reviewed correlation correction in a clean candidate; repeat portable and packaged checks on the exact RC and final source before checking the roadmap's final-candidate criterion. | -| 3.10 | pass for portable routing behavior | `GatewayServerTests.CallTool_CancelledForward_DoesNotRetryAnotherInstance`, `GatewayServerTests.CallTool_BackendHang_ReturnsTimeoutUnknownAndNeverRetries`, and `GatewayServerTests.CallTool_BackendDropsConnection_ReportsUnknownEffectAndNeverRetries` prove the gateway does not replay uncertain calls. | Retain these tests in V+ and verify representative native mutation state after a timeout or lost response. | -| 3.11 | pass for the clean tested candidate | `ContractSnapshotTests.GatewayExecutable_StdioWithoutInstances_MatchesGoldenSnapshots`, `GatewayServerTests.ListTools_WithoutPublishedInstances_ExposesStableRoutedCatalog`, `GatewayResourceTests.ListResources_WithoutInstances_ServesTheDocumentsAndTheInstanceList`, `GatewayResourceTests.ReadResourceAndGetPrompt_Local_AreServedWithoutAnyBackend`, and `GatewayCompletionTests.Complete_AllowedValuesOfARoutedTemplate_AreServedByTheGatewayWithoutAnyBackend` cover no-instance behavior. | Repeat AOT executable and V+ checks on the exact RC and final stable gateway. | - -## Source boundaries reviewed - -The contract tests are in `tests/CheatEngine.Mcp.Tests/Contract/ContractSnapshotTests.cs`, `CompositionParityTests.cs`, `OutputSchemaContractTests.cs`, `ToolCatalogContractTests.cs`, and `ToolContractTests.cs`. -The serialization and catalog rules are tested in `tests/CheatEngine.Mcp.Tests/Core/ContractJsonTests.cs` and `McpContractRulesTests.cs`. -The gateway routing, resources, completions, and errors are tested in `tests/CheatEngine.Mcp.Tests/Hosting/GatewayServerTests.cs`, `GatewayToolCatalogTests.cs`, `GatewayResourceTests.cs`, `GatewayCompletionTests.cs`, and `ErrorCorrelationTests.cs`. -The feature defaults, precedence, validation, and activation boundary are tested in `tests/CheatEngine.Mcp.Tests/Plugin/FeatureOptionsTests.cs`, `McpConfigurationTests.cs`, and `McpModuleLifecycleTests.cs`. -`McpContractRules` validates public naming, annotations, dispatch classes, schemas, and output presence at catalog construction. -`GatewayToolCatalog` adds the required routing argument while preserving every backend field. -`GatewayRouter`, `BackendConnectionPool`, and `InstanceIdentityVerifier` bind a request to one verified activation and report unknown effects without replay. -`McpFeatureGate` and the domain builders carry the four capability requirements into the public catalog and runtime admission path. -`McpEnvironmentConfigurationSource`, `McpOptionsValidators`, and the plugin configuration builder implement the documented precedence and validation boundary. - -## Contract freeze gate - -The stable contract cannot freeze until partial rows 3.01, 3.03 and 3.06 have complete reviewed evidence, native portions of 3.07 are verified, and the working-tree 3.04, 3.07 and 3.09 changes are validated on the clean candidate. -Rows that pass for `808a626e65eddab113737a27a7c2345555b1c241` still require the exact RC and final-candidate repetitions named above. -This checkpoint does not qualify any complete native workflow domain, installation path, client, soak, or public release. - -## Error-envelope follow-up - -An offline source audit found that an internal error with scalar or array details received a logged correlation ID but omitted that ID from the response. -`McpErrorCorrelation` now keeps non-object JSON context under `details.value` beside `errorId`, without numeric conversion. -It also replaces a non-string reserved `errorId` instead of emitting duplicate keys. -Object context and existing string IDs are preserved, other error kinds are unchanged, and gateway forwarding preserves the complete backend envelope. - -| Requirement | Portable evidence | -| --- | --- | -| Preserve array, string, exact large integer, boolean and null context with the logged ID. | `ErrorCorrelationTests.CallTool_NonObjectInternalDetails_KeepsValueAndReturnsTheLoggedErrorId` | -| Replace an invalid reserved ID while retaining the other object properties. | `ErrorCorrelationTests.Correlate_NonStringExistingId_ReplacesItWithoutDuplicateKeys` | -| Preserve every host-effect class, operation, retryability, hint, details and correlation through direct MCP and gateway calls. | `ErrorCorrelationTests.CallTool_EveryHostEffect_PreservesCorrelatedEnvelopeThroughGateway` | - -The 22 focused correlation cases passed in Release, and independent Astra source review found no remaining actionable findings after documentation corrections. -The previous and current full-suite results are recorded separately in [remaining-checkpoint.md](remaining-checkpoint.md). -These are portable source/filter/routing checks with controlled backends, not evidence that a real CE operation produced every host effect. - -The latest offline package refresh also verifies the working-tree contract in the actual Debug and Release single-DLL distributions and Native AOT gateway executables. Both packaged plugins match all backend catalog snapshots in two isolated contexts each; both AOT executables pass the existing no-instance initialization/catalog/knowledge test. Exact binary hashes, same-input ZIP reproduction, extraction checks and the uncommitted-source boundary are recorded in [remaining-checkpoint.md](remaining-checkpoint.md#offline-package-refresh---2026-10-09). These current local package results do not replace the required clean RC/final stable repetitions or native qualification. - -## Exact values, gates and effect metadata follow-up - -The 2026-10-09 continuation adds actual MCP serialization and loopback gateway forwarding around controlled Client doubles. -The gateway's controlled backend returns the direct tool response, so the check proves serialization and forwarding without claiming another target read. -`MemoryRead_ExactAndEmptyValues_RemainStringsThroughClientAndGateway` covers signed extrema, `9007199254740993`, unsigned maximum, pointer maximum, address maximum, zero and empty text. -`PointerReadChain_SignedOffsetsAndWideAddresses_RoundTripWithoutNumericConversion` covers `0`, `-1` and `-8000000000000000` offsets without numeric coercion. -All 12 focused cases passed in Release; both full portable suites at that earlier checkpoint passed 4,126 tests. The latest full offline audit passed 4,135 tests in each configuration, as recorded in [remaining-checkpoint.md](remaining-checkpoint.md). - -| Gate boundary | New portable evidence | -| --- | --- | -| Secondary Lua, target-code and kernel requirements before AA apply. | `CodeAsmTableV2Tests.Apply_SecondaryCapabilityDisabled_RefusesBeforeClientMutation` | -| Loaded XML AA, Mono and kernel content; each opaque-table gate; current-table Mono before load. | `Load_GatedTableContent_DisabledFeatureRefusesBeforeClientLoad`, `Load_OpaqueTable_DisabledExposureGate_RefusesBeforeClientLoad`, `Load_CurrentTableUsesMonoWithCodeExecutionDisabled_ProbesButRefusesBeforeClientLoad` | -| A mixed create/delete batch containing a gated AA record cannot mutate earlier records. | `RecordToolsTests.Create_BatchContainingAutoAssemblerWithoutGate_RefusesBeforeCreatingAnyRecord`, `Delete_BatchContainingActiveAutoAssemblerWithoutGate_RefusesBeforeDeletingAnyRecord` | -| A fixed script-editing requirement rejects before dispatch. | `RecordToolsTests.SetScript_AutoAssemblerWithoutGate_RefusesBeforeAnyDispatch` | -| Mono auto-attach on process creation or file-open cannot reach the open script while disabled. | `ProcessMonoAutoAttachTests.CreateOrOpenFile_MonoAutoAttachWithCodeExecutionOff_RefusesBeforeTheOpenScript` | - -The XML fixtures explicitly verify inspection, so a malformed/protected-table fallback cannot masquerade as content-classification coverage. -Focused Release results were 42 assembly/table tests, 177 record tests and 10 process-auto-attach tests, all passing. -The source audit traced the declarative filter, fixed-Lua scanner, table inspector, AA classifier, record mutations, Mono auto-attach, debugger-interface choices, kernel symbol sources, Exec/C# and Speedhack admission paths. -This remains the documented exposure-control boundary: address expressions and stored AA scripts do not acquire sandbox guarantees or new secondary gate promises. -The `record_clear` fixed-Lua hint assertion subsequently passed in the explicitly authorized standalone Release NativeLua suite; real CE hook behavior remains unqualified. - -Effect metadata now marks `record_create` and `table_save` destructive because an initial value or explicit overwrite can replace existing data. -`record_set_script` publishes `auto_assembler` in its fixed requirements. -`asm_release_patch`, `record_set_active`, `record_delete`, `record_clear` and `runtime_release_resources` publish `may_prompt`: original AA sections can block or show a dialog, including when called through bulk resource cleanup. -Independent Astra review cleared these scoped source/test changes after the identified corrections. It did not audit every remaining tool or qualify a native runtime. - -The .NET and Mono fallback review used public upstream commit `ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37`, separately from installed CE `7.7.1.10828`. -[Mono field enumeration](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/bin/autorun/monoscript.lua#L2159-L2205) reads every returned offset; the collector writes it for Mono and its IL2CPP binding uses `il2cpp_field_get_offset`. -The [.NET Lua field wrapper](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/luadotnetpipe.pas#L278-L337) emits every decoded `Offset`, and its [parameter wrapper](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/luadotnetpipe.pas#L210-L245) emits `CType` for each returned entry. -The [parameter decoder](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/dotnetpipe.pas#L493-L561) preallocates the result array and skips assignment for the explicit failure sentinel without compacting it; the Lua wrapper still emits every slot. -Consequently, a present `CType` can be default metadata from a failed parameter lookup. Its presence does not prove that meaningful metadata was reported. -Offset/property emission alone does not justify changing the public shape to nullable; changing the managed type would not recover failure information already lost upstream. Raw collector values remain preserved under the clarified contract below. -It does not establish parity with the installed collector or complete the remaining domain audit, and no collector was executed. - -## .NET parameter metadata correction - -The pinned collector calls `IMetaDataImport::GetParamProps` and copies `pdwCPlusTypeFlag` into `CType`. -Microsoft's [runtime implementation](https://github.com/dotnet/runtime/blob/9fc5e2dd35febb732f16020969b43da7933d326b/src/coreclr/md/compiler/import.cpp#L2764-L2793) obtains this code from the parameter's Constant metadata: it returns `ELEMENT_TYPE_VOID` when there is no constant, otherwise the constant's element type. -This is not the declared parameter type. The prior public description and example incorrectly suggested otherwise. - -`DotNetParameter.ElementType` retains the raw integer for compatibility, but now identifies it as this metadata-constant code; `ElementTypeName` names only that code. -The changed portable test preserves a declared `Game.Player` signature alongside `1` / `Void` for a parameter without a constant, and retains a zero-initialized unavailable entry without inventing a type name. -`0` can mean unavailable metadata; it must not be interpreted as a declared type. The collector does not retain enough information to reconstruct the original failure. - -The [ECMA-335 Param table](https://ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf) permits missing rows and sequence `0` for return metadata. -The [runtime enumeration](https://github.com/dotnet/runtime/blob/9fc5e2dd35febb732f16020969b43da7933d326b/src/coreclr/md/compiler/import.cpp#L574-L625) does not filter those rows; the pinned CE implementation enumerates once into a 30-entry buffer, sorts by sequence and discards sequence before returning Lua entries. -The public array therefore promises collector order, potentially including return metadata, placeholders or an incomplete list, and its `index` is the returned position rather than a declared ordinal. -The optional `signature` remains the returned representation of declared types. No signature parser or inferred types were added. -The tool, output schema, knowledge guide and workflow use this same distinction; native collector parity remains unverified. diff --git a/docs/qualification/v2.0.0/lifecycle-key-fix.md b/docs/qualification/v2.0.0/lifecycle-key-fix.md deleted file mode 100644 index 444b425..0000000 --- a/docs/qualification/v2.0.0/lifecycle-key-fix.md +++ /dev/null @@ -1,77 +0,0 @@ -# Settings plugin checklist key experiment - -Status: the user authorized both stages conditionally. All eight standalone bridge cases passed; the one lifecycle attempt still failed at the first disable request with measured checklist count zero. Cleanup and independent Astra evidence review passed. Subsequent source review disproved the key-needed premise, and the unsupported registry change was rolled back. No further native run is authorized by this completed two-stage approval. - -## Corrected source interpretation - -The failed ordinary-SDK run `20261009T120128Z-8f44` reached the first disable request after all six workflow groups completed. Its refusal only established that the plugin checklist count was not one; the old message did not report whether it was zero or greater than one. - -The original preparation incorrectly interpreted `FillCheckListBox` as being inside the `OpenKey(...\Plugins64, false)` block. Inspection of the exact pinned file shows that the `end` at line 1248 closes that block; the checklist call at 1250 is outside it. A missing plugin registry key does not itself skip that call. This corrects and supersedes the earlier causal claim. Both the source researcher and Astra confirmed the correction. See [the exact block boundary and outer exception handler](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/MainUnit2.pas#L1214-L1261). - -The reload has early exits for a missing form or a different runtime class, plus a broad empty exception handler that can swallow failures before checklist population. The [Lua plugin loader](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaHandler.pas#L9114-L9145) uses the same plugin handler as the [checklist implementation](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/plugin.pas#L1694-L1714). A zero-row observation cannot distinguish those paths, a genuinely empty list, or installed-build differences. The actual cause remains unproven. Both the test process and CE are x64; a default registry-view mismatch has no supporting evidence here. - -## Tested experiment, subsequently rolled back - -- Open or create the empty `Plugins64` key only after the existing guarded user-state scope has been established. -- Retain all validation of subkeys, exact value names, paired types, enabled values and known private plugin paths before removing any existing values. -- Delete only the validated owned values, leaving the empty key available for CE's reload. The normal end-of-session settings restoration remains responsible for restoring the original registry state. -- Keep the exact one-row and exact plugin-name checks. A refusal now reports the observed count in the existing bounded ASCII receipt. -- Add the zero-row regression and assert count details for both zero and multiple rows. Refused requests still send no key messages and never apply Settings. - -The experiment changed `LiveSandboxSession.Lifecycle.cs`, `LivePluginBridge.cs`, and `NativeLuaLivePluginBridgeTests.cs`. After the failed run and corrected source review, only the unsupported key creation/retention change was reverted to the prior validated registry handling. The useful observed-count error and zero-row regression remain. Product source, published plugin/gateway bytes, capability gates and the lifecycle scenario are unchanged. The earlier failed run's source, input manifest and runners are retained under `artifacts/issue-edits/lifecycle-8f44-source/`. - -## Prepared verification sequence - -1. Run only the eight `NativeLuaToolRuntimeTests.LivePluginBridge_*` cases using the staged Lua DLL in one isolated process. The runner has a 60-second deadline, fails skips and requires exactly eight passed cases. This checks the fixed bridge and error contract using the existing simulated host; it does not touch registry settings or launch CE. -2. Only if all eight pass, run one ordinary-SDK x64 `lifecycle` session with the exact workload in [lifecycle-resume.md](lifecycle-resume.md#exact-proposed-workload). This is the first real verification of the preserved-key correction and all downstream plugin cycles/restarts. The attempt ends for review on any failure; there is no automatic retry. - -Both steps required a new explicit execution decision because the earlier one-session lifecycle approval had been consumed. The user subsequently approved this exact conditional sequence; the completed results follow below. - -The Release test-project build passed with zero warnings/errors (`artifacts/issue-edits/goal-lifecycle-key-build.log`). Source whitespace and runner syntax passed. The two no-execution preflights each verified 859 exact files, including all 527 unchanged product sources and the retained ordinary Release distribution. - -- Test module SHA-256: `6F43AD2E67629A39F1CACFC49B3F76ABA46D9BDBE05E33CA3476DF57A54C2630`. -- Standalone input manifest SHA-256: `9D74F0C5F926935C1265DBBE66784A8607FB9F2B3AEDDB1BF7EFF46E298B4A6E`. -- Lifecycle input manifest SHA-256: `72A30BC04C6544176346142AF9819A8209E518A6893C3CCB0B13470AF658711C`. - -Historical commands executed once, in order after approval (their original manifests are retained and intentionally no longer match the reverted current source): - -```powershell -& artifacts/issue-edits/Invoke-LifecycleKeyNativeTests.ps1 -Execute -# Proceed only after its eight-case result is verified as passing. -& artifacts/issue-edits/Invoke-LifecycleKeyFix.ps1 -Execute -``` - -Independent Astra high preparation review found no remaining actionable issues in ownership validation, retained-key semantics, exact row/name guards, the eight-case scope, both 859-hash manifests or runner cleanup. That source review did not establish that the correction would resolve the live failure. - -That review missed the incorrect source-block interpretation above. Its clean result must not be used as support for the superseded key-needed explanation. - -## Approved conditional execution - -The standalone stage ran first in `artifacts/lifecycle-key-native-results/c23fb28679324287a65c9936e1e59ffe`: **8 passed, 0 failed, 0 skipped**, 1.027 seconds. The TRX contains the exact five refusal theory cases (including zero and multiple rows), disable, enable and bounded multibyte-error cases. The owned test process exited 0 with no timeout or runner/cleanup failure and was absent afterward. The simulated-host result does not validate registry behavior. - -After verifying those eight outcomes, the single real-CE stage ran as `20261009T121623Z-71c7`: **0 passed, 1 failed, 0 skipped**, 17.852 seconds. The prelude and all six bounded workflow groups completed. The registry receipt reports `count=0, keyRetained=true`, but nonce 1's first disable request returned `expected exactly one owned plugin row (count=0)`. No plugin cycle or restart completed. Preserving the empty key was insufficient in this installed build; this evidence does not establish which registry path/view CE actually read or why its checklist remained empty. - -All four optional gates remained false. CFG bytes were inspected without execution. Mutating-workflow cleanup failures were zero, user settings were restored, the source installation was unchanged, and all four recorded host/target PIDs plus both test-process PIDs were absent. Both 859-file input manifests still matched after execution, including the unchanged ordinary product package. Independent Astra high review confirmed the test order, exact outcomes, identities and cleanup with no actionable evidence findings. - -Evidence: `artifacts/issue-edits/goal-lifecycle-key-execution-evidence.json`; live output is under `artifacts/lifecycle-key-results/380c1fe8ccf44d388c5a71294102af78`. The tested source, runners and manifests are retained under `artifacts/issue-edits/lifecycle-71c7-source/` before further edits. - -| Artifact | SHA-256 | -| --- | --- | -| Standalone result | `336B110762908D68AB06BD3B93E63713E92D6451FF43838A4E4C2B40F638CC78` | -| Standalone TRX | `45B8406473F9FB3C278F228B43058482052D21D511A765FF8E1D896B5B215618` | -| Lifecycle result | `214CAFD53A1955C5954BB0E31D6455E250B6C48F69F7C9BD1882C47ABAE63CB8` | -| Lifecycle TRX | `A4CA3FDAF66FDAAD3EED73FC9AF3F12DC6E5DE95B4194073A1440EE1CBAACDF9` | -| Native summary | `FA635351E2C6AE070681CA4FCE007BF59D947D41F0DA86F3925CB0F84032FBB8` | -| Native receipts | `AB01C154F5C590126F4718DF7B973B241BD9C6C7BA5175B184BCBAC7AD29F6CD` | - -This two-stage approval is consumed. Plugin lifecycle, the historical SDK stack failure and stable qualification remain unresolved. The registry rollback is a source correction, not a newly executed native pass. The next work is source-based diagnosis of observable reload boundaries; no automatic native retry follows this failure. - -The post-rollback Release test-project build passed with zero warnings/errors (`artifacts/issue-edits/goal-lifecycle-key-rollback-build.log`), and `git diff --check` passed. All 527 product-source hashes and the ordinary distribution's plugin/gateway hashes still match the original package manifest. The original execution manifests remain unchanged as historical evidence; they no longer describe the current harness/test binary. Final review caught an empty consolidated `LiveCounters` field; it was corrected to the explicit numeric counters from the unchanged live TRX. - -Final Astra review confirmed that correction with no remaining actionable findings. The checkpoint in #28 and #33 was replaced with the passed subcases, failed lifecycle result and retracted source rationale; exact normalized body readback succeeded. Both issues remain open. #31 was separately reread and remains open with its extended compiler/injection and final contract/CI gates unchanged. - -## Next diagnostic limits from source - -`getSettingsForm()` [wraps the existing global form](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaHandler.pas#L4657-L4662); it does not create or initialize it. The old form-before-reload ordering was not proof of form initialization. Lua's [object binding exposes `ClassName`](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaObject.pas#L126-L132), which could check the reload's exact-class guard. Existing checklist counts before/after reload are also observable. - -However, `LoadingSettingsFromRegistry` is a public field, not a published RTTI property in [the Settings declaration](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/formsettingsunit.pas#L360-L387). It must not be assumed readable through the ordinary Lua property binding. No plugin-array enumeration binding was established by the source review. A correct class plus zero rows both before and after reload would still leave a swallowed exception and an empty plugin list indistinguishable. These observations are an offline design boundary, not a prepared or authorized new session; a further run needs a probe that can discriminate the remaining paths. diff --git a/docs/qualification/v2.0.0/lifecycle-resume.md b/docs/qualification/v2.0.0/lifecycle-resume.md deleted file mode 100644 index b3121c5..0000000 --- a/docs/qualification/v2.0.0/lifecycle-resume.md +++ /dev/null @@ -1,68 +0,0 @@ -# Ordinary-SDK lifecycle session preparation - -Status: the user approved one session after offline preparation and Astra review. It executed and failed at the first plugin-disable step after all six workflow groups completed. Cleanup passed; no retry is authorized or scheduled. - -## Why this is the next boundary - -Four ordinary-SDK prefix sessions and one instrumented prefix session passed, and two deterministic standalone nested-dispatch cases also passed. They did not execute plugin disable/re-enable or the later restart matrix. The historical private `.2` session `20261008T015918Z-ee72` completed all six workflow groups before the fixed Settings/Plugins bridge refused its first disable request. The bridge's form-initialization and bounded error-receipt fixes have offline coverage but still need verification in CE. - -The maintained `lifecycle` scenario covers that boundary using the current ordinary SDK package. A passing result could qualify this specific current-package workflow/lifecycle sequence. It would not establish the cause or fix of the earlier uninstrumented native stack failure, reproduce historical package bytes/timing, or qualify the stable release. - -## Exact proposed workload - -One x64 session starts two private CE copies and two owned x64 targets, using the existing guarded harness: - -1. Attach both targets; verify runtime identities, scalar sentinels and all four disabled capability gates; run the original read-only resource prelude. -2. Run all six bounded workflow groups: memory, named scanner, AOB, control-flow graph inspection, modules and structures. Temporary allocations, snapshots, named scans and structures are owned and cleaned up by the existing harness. The CFG fixture writes bytes into owned non-executable memory for analysis; it does not execute target code. -3. Disable and re-enable plugin A three times. Require fresh activation identity/endpoint/token and stale-instance rejection, while B remains available and unchanged. -4. Restart A's owned target and reconnect; restart the owned gateway and verify both instances; restart private host A together with its owned target, reject the former instance and verify both current instances. -5. Close the owned processes, restore the snapshotted user settings, compare the source installation fingerprint, and retain the original error together with any cleanup error. - -All optional gates remain false: `unsafeLua`, `autoAssembler`, `kernelAccess`, `targetCodeExecution`. No debugger attachment, compiler qualification, managed injection, kernel/DBVM workflow or soak is selected. No user process is closed to satisfy preflight; the harness refuses an existing CE or DebugView process. - -Each private CE driver retains its normal five-minute lifetime, and each owned target its normal ten-minute lifetime. These are per-process bounds, not a promise that the whole restart scenario completes in five minutes. The runner lets the maintained harness perform ordered shutdown and settings restoration; it does not abruptly terminate the harness parent. Any failure ends this single attempt for review, without an automatic retry. - -## Exact candidate and runner - -- Distribution: `artifacts/dist/goal-contract2-release`, already used by the four passing ordinary-SDK prefix sessions. -- Plugin SHA-256: `1620DD5978C4996BA80DEB5B81403EB3B695B3D029C1D1D533A3F39254D5A97F`. -- AOT gateway SHA-256: `ADB97797600614CEF21C7F5E2352F95E9102A3D4DFCE48DA4F5B19335E65CA88`. -- CE 7.7.1.10828 x64 executable SHA-256: `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. -- Source: `0f722ef7d6b37eee0985073dfe594c6559b31c14` plus the reviewed uncommitted MCP changes. This is not a clean stable candidate or a published release. -- SDK: ordinary dependency; the private stack trace package is not selected, and the child process receives no stack-trace opt-in. - -`artifacts/issue-edits/Prepare-LifecycleResume.ps1` verifies all 527 product-source hashes and the full Release distribution against the retained package manifest. It captures the current live-harness source, built test and target output trees, .NET host, CE executable and runner in `goal-lifecycle-resume-inputs.json`. - -The root Release test-project build passed with zero warnings/errors (`goal-lifecycle-resume-build.log`). Script syntax and no-execution preflight passed for 820 exact files. No source or package change was required to select the existing scenario. - -`Invoke-LifecycleResume.ps1` defaults to hash verification only. After explicit approval, its `-Execute` mode invokes exactly `CheatEngine.Mcp.Tests.LiveQualification.McpLiveQualificationTests` with the `LiveQualification` trait and fail-skips. It requires one executed/passed case, the complete three-cycle/three-restart receipt, disabled gates, unchanged package hashes, restored user state, unchanged installation, no cleanup failures and absence of recorded original/restarted host/target PIDs. - -Proposed command from the workspace, only after approval: - -```powershell -& artifacts/issue-edits/Invoke-LifecycleResume.ps1 -Execute -``` - -Independent Astra high review verified all 820 input hashes, the 527 product-source matches, exact scenario routing, original/restarted PID coverage, completion checks and preservation of the harness's restoration path. No remaining actionable findings were reported in that scope. - -- Prepared manifest SHA-256: `7A20146763110D9A459F4C4CF0FAF065A4C7EDD471C32042F26063BBDCD9D06F`. -- Built test module SHA-256: `7F8404D131ABC76F3CE612FE5B181DD83706C44ED2193E5E2C7D7906D20E43F7`. - -The user separately authorized this broader lifecycle run after review. That one-session authorization is consumed; further native execution requires a new decision. - -## Approved execution result - -Run `20261009T120128Z-8f44` completed the original prelude and all six workflow groups with the unchanged ordinary-SDK package. Memory, named scans, AOB, CFG, modules and structures each have their completion receipt; each mutating group's cleanup reported zero failures. CFG fixture bytes were not executed. All four optional gates were false. - -The first `CyclePluginAsync("A")` failed when the Settings/Plugins bridge returned `expected exactly one owned plugin row`. The new bounded error-response path worked: the harness reported the precise refusal immediately, instead of waiting for a missing response. The earlier form-before-registry-reload change therefore did not resolve checklist population in real CE. - -The test result is **Passed: 0, Failed: 1, Skipped: 0**, 18.441 seconds; the test process exited 2. No plugin reload or target/gateway/host restart pass is claimed. The six preceding workflow groups passing does not make the lifecycle test pass or demonstrate a fix for the historical native stack failure. - -User state was restored, the source installation stayed unchanged, cleanup failures were empty, and all four recorded owned host/target PIDs were absent. All 820 prepared input hashes still matched after execution. The failed attempt is retained for source-based diagnosis; it has not been retried. - -- Raw summary SHA-256: `FCAEA7CA2B06C37B854EF57B854FA511622B28D95EC9969BE761978609807594`. -- Runner receipt SHA-256: `80E6A139EC4E52D202ACEAE10E8A7ACA2AD5D5ECA2ABBDC62E6E918B0453A941`. -- TRX SHA-256: `0897C68743C4FC2A11922DF7DC872972915A8F7F05E1A04CE3F8184AF33FC87A`. -- Local result directory: `artifacts/lifecycle-resume-results/c244a91c720a41e88e3a71e59979a583`. -- Bounded evidence extract: `artifacts/issue-edits/goal-lifecycle-8f44-evidence.json`. -- Pre-edit bridge/lifecycle source snapshot: `artifacts/issue-edits/lifecycle-8f44-source/`. diff --git a/docs/qualification/v2.0.0/local-checkpoint.md b/docs/qualification/v2.0.0/local-checkpoint.md deleted file mode 100644 index 1cda7db..0000000 --- a/docs/qualification/v2.0.0/local-checkpoint.md +++ /dev/null @@ -1,85 +0,0 @@ -# Local roadmap implementation checkpoint - -Date: 2026-10-08. -Status: Historical working-tree evidence, not a stable release or a clean-commit qualification. - -These checks ran on `codex/roadmap-v2`, based on `c51a0ec3af373c852cda062a91888ad94fa0cce9`, before the implementation was committed locally as `b0e42752958b6b2b861d56f4e87b0d48d48d0514`. -The tested binaries identify as `2.0.0-beta.2` and embed the earlier base commit; that identity does not identify the modified source tree. -The package hashes below identify the actual tested bytes. -A future clean candidate must repeat the final-source and final-package gates in [release-signoff.md](release-signoff.md). - -## Implementation and verification - -| Evidence ID | Roadmap requirements | Level | Result | Scope and limitations | -| --- | --- | --- | --- | --- | -| LOCAL-01 | Sections 4, 5 and 6 implementation | implementation | Pass | Three new tools, typed schemas, bounded behavior, capability and file-policy integration, guidance, and regression cases. Native compiler qualification remains open. | -| LOCAL-02 | Section 11 restore and portable checks | V | Pass | Locked restore; Debug and Release builds with zero warnings or errors; 4,005 portable tests in each configuration, zero failures or skips. | -| LOCAL-03 | Sections 4 and 6 fixed Lua | VN | Pass | 607 NativeLua tests, including existing debugger-capture regressions and 15 cases added after independent review; actual Lua 5.3 x64 with stubbed CE APIs. | -| LOCAL-04 | Sections 3 and 11 public contract | V+ | Pass | Reviewed additive tool snapshots, resource-size updates, both format checks, and five isolated bundle tests including negative tool/resource/prompt cases. | -| LOCAL-05 | Section 11 distributions and packaged parity | V+, AOT | Pass | Debug and Release publication; actual lone packaged DLL compared to explicit goldens in two load contexts; Native AOT executable MCP smoke checks. | -| LOCAL-06 | Section 11 local package | implementation | Pass | Five-file Release ZIP inventory, embedded dependency inventory, matching DLL/gateway version and base-commit identity, entry hashes and checksum verification. No upload or tag operation. | -| LOCAL-07 | Section 5 x64 pointers; bounded parts of sections 7 and 8 | VL | Pass | Run `20261007T225551Z-5e8e`, 23.829 seconds; two private CE instances and owned x64 targets; supplied pointer facts, batch outcomes, isolation, target restart and existing maintained smoke scenario. | -| LOCAL-08 | Section 5 x86 pointers; bounded parts of sections 7 and 8 | VL | Pass | Run `20261007T225641Z-a5c0`, 22.967 seconds; the same Release package and scenario with an independently built x86 fixture whose manifest reports 32-bit pointers. | - -The full portable commands exclude `Category=LiveQualification` and `Category=NativeLua` and use `--fail-skips on`. -The existing catalog contains 193 backend tools, 43 resources, 15 resource templates, and 43 prompts; gateway discovery adds `instance_list`. -The stale embedded-documentation mismatch observed during incremental development was resolved by a nonincremental rebuild and then independently by fresh publication. - -## Independent Astra review - -An independent `gpt-6-astra` subagent reviewed the implementation, arithmetic, file and effect boundaries, batch cancellation and epochs, tests, packaging, native fixture cleanup, and evidence claims. -The first pass found three supplied-facts defects: accepted hexadecimal values were not normalized before Lua; EIP-relative addresses did not infer 32-bit wrapping; and stack-relative POP and bit-string memory instructions could access an address different from their bracket expression. -The fixes normalize accepted facts, narrow EIP arithmetic, and refuse unsupported POP, bit-string, and prefixed-text semantics without extending the existing capture opcode policy. -Fifteen public-tool NativeLua cases cover the fixes, and the full 607-test NativeLua suite passed. -The same independent reviewer re-reviewed the fixes and found no remaining actionable defects in scope. -That is a source and test review result, not a claim that all possible defects or outstanding native/release gates have been exhausted. - -## Environment - -| Component | Observed identity | -| --- | --- | -| OS | Windows 11 Pro x64, `10.0.26300` | -| Build SDK | .NET `10.0.401` | -| Native host | Cheat Engine x64 `7.7.1.10828` | -| Plugin frameworks | Microsoft.NETCore.App, Microsoft.AspNetCore.App, Microsoft.WindowsDesktop.App `10.0.12`, x64 | -| x86 fixture framework | Microsoft.NETCore.App `10.0.12`, x86 | -| Native host SHA-256 | `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F` | -| Lua 5.3 x64 SHA-256 | `C95DCDFA0F60F97B43D970D77FD1BB907AF4DE04B500A3C89A99600B20B35BD2` | - -The Lua DLL came from the reviewed CE installation. -The existing reviewed autorun hashes were unchanged and accepted by the native harness. -No runtime, compiler, driver, dependency, or host installation was changed for these runs. - -## Tested artifact SHA-256 - -| Artifact | SHA-256 | -| --- | --- | -| Release plugin DLL | `E6E2AE56B43CF54F1F5978B566EE762939B76E549D557FDDF8B073BB8262750B` | -| Release Native AOT gateway | `450D0E89EDABC95551689AFE277EB8FBDFF9683A8F0B4395D6293F3C74AE1FA8` | -| Debug plugin DLL | `0D95A43501EC95453E96DC7BF508CEE6A32CCCCEA3125732E5A55D8180CF9C45` | -| Debug Native AOT gateway | `B0D198A18B9955AA6AFBD917396339997CAA54EE089981A5BAEBE0C563479F61` | -| Local Release ZIP | `72B3E25DF77816DD9B5403EEB229D32350EC72DE1B92206C777E95D03321D37C` | - -Distributions are under the ignored `artifacts/dist/roadmap-debug` and `artifacts/dist/roadmap-release` directories. -The local ZIP is under `artifacts/releases/roadmap-candidate`; it is a working-build artifact and is not the public beta.2 payload. -Build, test, format, publication, and native console logs are retained locally under `artifacts/issue-edits/roadmap-*.log`. -Raw native reports remain outside the repository in the harness's per-user run storage. -Only redacted results and run identifiers are recorded here. - -## Native cleanup and limits - -Both native runs reported `passed=true`, empty `cleanupFailures`, `userStateRestored=true`, and `sourceInstallationUnchanged=true`. -The harness stopped only its owned private CE processes and disposable targets; existing MCP client gateways were preserved. -The live scenario covers signed pointer offsets, unreadable hops, zero-address resolution with an unreadable final value, two-instance isolation, and a fresh chain after target restart. -Supplied x86 and x64 instruction facts passed through the actual gateway and plugin; this is not a live debugger-capture qualification. -The maintained scanner, memory read/write, disassembly, record-freeze, speedhack restoration, and one-host withdrawal checks also passed in both runs. -No native compiler, injection, debugger capture, kernel, or hypervisor probe ran. - -## Remaining release work - -At this checkpoint, the compiler's actual availability, diagnostics, prerequisites, raw temporary-file lifetime, and separate injection remained unqualified. -Compiler-only phases 1 through 4 of the [compiler policy](compiler-probe-policy.md) were subsequently approved for this session; separate injection remains unapproved. -The broader workflow matrix, repeated activation recovery, measured performance baselines, two-hour soak, installation/upgrade/rollback, named MCP clients, CI and analysis, clean final source identity, version/tag change, and public release verification remain open. -The completed working-tree checks do not close any of those requirements. -Subsequent clean-commit compiler evidence is recorded separately in [compiler-checkpoint.md](compiler-checkpoint.md). -See [release notes](release-notes.md) for the draft migration path and [release-signoff.md](release-signoff.md) for final gates. diff --git a/docs/qualification/v2.0.0/mcp-closeout.md b/docs/qualification/v2.0.0/mcp-closeout.md deleted file mode 100644 index e51fc58..0000000 --- a/docs/qualification/v2.0.0/mcp-closeout.md +++ /dev/null @@ -1,40 +0,0 @@ -# MCP implementation closeout - -On 2026-10-09, the maintainer narrowed the remaining work to MCP changes and requested closure of issues #28, #31 and #33 when no further work could proceed, followed by a local commit to `main` if tests passed. This supersedes the earlier requirement to keep those issues open until a public stable release. It does not approve more native investigation or claim stable qualification. - -## Integrated changes - -The 61 changed or new source, test and knowledge files from the reviewed navigation follow-up are now integrated into the primary checkout. Before integration, existing destination files matched the original preparation baseline; all copied files then matched the reviewed snapshot. The 110 pre-existing modified/untracked files were backed up locally, and newer qualification documents were preserved. - -This includes prepared module, region, thread, symbol and breakpoint navigation; explicit blocking-native metadata for full enumeration; bounded structure lookup/page access; corrected thread StringList ownership; ordered snapshot publication; and retained pointer-search cleanup bounds. The reviewed performance/admission harness preparations and regression tests are retained, without claiming that their native workloads ran. - -Evidence for the integration is in the ignored local receipt `artifacts/issue-edits/final-mcp-integration.json`. Earlier isolated Debug/Release portable and local package results remain recorded in [navigation-followup.md](navigation-followup.md) and [navigation-package.md](navigation-package.md). They do not replace verification of this integrated checkout. - -## Verification - -Fresh checks ran against the integrated primary checkout, using the maintained CI commands: - -| Check | Result | -| --- | --- | -| Locked solution restore | Passed. | -| Debug and Release solution builds | Both passed with zero warnings/errors. | -| Debug portable suite | 4,234 passed, zero failed/skipped. | -| Release portable suite | 4,234 passed, zero failed/skipped. | -| Style and whitespace verification | Both passed with no source changes. | -| Debug and Release local publish | Both passed; each exact bundled DLL matched its catalog goldens in two isolated contexts, and each Native AOT gateway passed the empty-registry smoke check. | -| Local Release archive | Five-file inventory and checksum verification passed. | -| Independent Astra review | Scoped clear with no actionable findings; all 820 reviewed file hashes, both TRXs, logs, distributions and ZIP contents independently verified. | - -The portable runs excluded `NativeLua` and `LiveQualification` and cleared inherited `CHEATENGINE_*` variables. The bundle probe loads the ordinary native bridge to inspect its export; it does not execute Lua scripts or launch CE. Local publish/package commands do not upload anything. - -`artifacts/issue-edits/final-mcp-validation.json` records both TRX counts/times/hashes, all 820 reviewed product/test/knowledge file hashes, build/format/test logs, distribution files and ZIP identity. The ZIP is 17,802,177 bytes with SHA-256 `6603934531FACA7D37D49CAF0897B9BC57612B3C73EEBBD68268B0E8010D4799`. These pre-commit development binaries identify base commit `0f722ef7d6b37eee0985073dfe594c6559b31c14`; they do not prove a clean final stable package. - -## Deferred work and issue disposition - -The historical CE Lua dispatch failure and empty Settings plugin list remain unresolved. The five new standalone navigation Lua cases and ten Settings cases have not run; the proposed conditional real-CE lifecycle session has not run. Integration supersedes their frozen primary-checkout inputs: any future execution must prepare and review inputs for the then-current checkout instead of reusing those manifests. - -Compiler prerequisites/expiry/reload/injection, the broader native workflow matrix, performance measurements, soak, installation/upgrade/rollback and supported-client checks remain unqualified. The release sign-off checklist records these gaps; unchecked criteria stay unchecked. Closing the issues at the maintainer's request is administrative closure of this effort, not evidence that those criteria passed. - -The version remains `2.0.0-beta.2`. This work does not create a stable tag, upload assets, publish a release or push to GitHub. A future stable release requires an explicit qualification/release effort with a clean source identity and applicable native evidence. - -After the passing checks and final Astra review, [#28](https://github.com/CheatEngineNet/CheatEngine.Mcp/issues/28), [#31](https://github.com/CheatEngineNet/CheatEngine.Mcp/issues/31) and [#33](https://github.com/CheatEngineNet/CheatEngine.Mcp/issues/33) were closed as `not_planned`. Readback verified each closure reason and that the original issue body/checklist was unchanged. The closing comments explain the maintainer's scope decision, completed MCP checks and deferred native/stable requirements. diff --git a/docs/qualification/v2.0.0/navigation-followup.md b/docs/qualification/v2.0.0/navigation-followup.md deleted file mode 100644 index e6a1ab5..0000000 --- a/docs/qualification/v2.0.0/navigation-followup.md +++ /dev/null @@ -1,57 +0,0 @@ -# Remaining prepared navigation routes - -Status: the isolated implementation and evidence below passed Astra high review. These repairs are now integrated into the primary checkout for the [MCP closeout](mcp-closeout.md), whose verification is recorded separately. The earlier [local package](navigation-package.md#latest-reviewed-follow-up-package) includes these repairs; native CE qualification remains incomplete. - -The snapshot is `artifacts/performance-preparation/navigation-followup-7adc921d5236434493cb15e1775855bf`, copied from the 863-file reviewed first navigation repair. The frozen ordinary package and both 859-file Settings preparations remain unchanged. - -## Implementation and limits - -Thread, registered-symbol and breakpoint resources now require a successful explicit preparation within five seconds. These reads use a copied bounded snapshot; they perform no native list enumeration. Thread and breakpoint snapshots require the same selected process/epoch; registered symbols remain global and available without a target. Symbol ownership is evaluated from current MCP leases. Breakpoint ownership describes the preparation-time observation; direct set/delete invalidates it, while other changes require an explicit refresh or expiry. - -The thread collector now passes a caller-owned StringList to `getThreadlist`, parses CE's hexadecimal entries and destroys the list even after collection failure. Pinned CE expects that argument; it does not return a thread table. All three native producers enumerate their full collections before MCP's copy cap. The six explicit module/detail/region/thread/symbol/breakpoint preparations therefore declare `blocking_native`, with descriptions stating that CE can block for seconds. - -Resources name their prepared helper through `McpSourceTool.PreparedProjection`, published as `cheatengine/preparedProjection`. Startup requires a unique public instance method on the same container, the source tool's exact result type, explicit `short` metadata, no tool annotation and no feature gate. Existing source-tool read-only, closed-world, ungated and catalog checks remain. Invalid declarations fail even for a short source. This metadata records the reviewed implementation promise; it cannot prove what a method executes. - -`structure_get` now searches at most 65,536 indexed global definitions, then reads only the requested element page, at most 1,024 entries. A miss beyond the lookup cap is `limit_exceeded`; exact totals and pagination are preserved. Filtered value/compare operations keep their existing scripts. [Pinned CE's binding](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaStructure.pas#L23-L53) is index-only and returns entries in `DissectedStructs`. Its [structure declaration](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/StructuresFrm2.pas#L133-L228) has no `Internal` property: the existing optional read defaults to false. The read description no longer promises a meaningful internal classification. Installed CE/source equivalence is unproven. - -## Verification ledger - -| Requirement | Evidence | -| --- | --- | -| Managed cache/projection behavior | 213 focused cases passed; final portable run passed 4,234/4,234, zero failures/skips. | -| Honest dispatch metadata and narrow projection validation | 38 catalog-capture cases passed; full portable run verifies without capture. Five golden files changed only intended descriptions, six dispatch classes, document sizes and six projection keys. Schemas, URIs and other metadata are unchanged. | -| Actual thread and structure Lua scripts | Regression cases compile with the test project; new native cases have not run. | -| Source and evidence review | Astra high found dispatch metadata, symbol publication ordering, the process test facade and an undefined source-class validation gap; all were repaired. Final source, golden and evidence reviews are scoped clear. | -| Earlier navigation package | Separately reviewed and scoped clear in [navigation-package.md](navigation-package.md); excludes these follow-up changes. | -| Latest follow-up package | All 867 reviewed files copied with matching hashes; maintained DLL probes, AOT smoke, deterministic ZIP and staging passed. Astra high package evidence review is scoped clear; see [the latest package](navigation-package.md#latest-reviewed-follow-up-package). | - -The initial focused run passed 200/203 cases. Two test fixtures were corrected (the detached target requires three scalar checks, and the supplied structure page lacked its asserted next offset); the tool-map document was shortened to its existing budget. Symbol reads now publish within their admitted dispatch and use an ordering token so a delayed older copy cannot replace a newer publication. The native process fixture now supplies the stable process facade needed to reach Lua; this correction is not a native execution result. - -The first broad run passed 4,233/4,234: a script-inventory assertion still expected 18 scripts after adding the nineteenth. That count was updated without weakening its per-script checks. The final Release build passed with zero warnings/errors; the complete portable rerun passed all 4,234 in 1 minute 46.069 seconds. Both broad results are retained. No native or live cases appear in the final TRX. - -Commands executed from the isolated snapshot: - -```powershell -dotnet build tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj -c Release --no-restore -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -p:SourceRevisionId=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet artifacts/bin/CheatEngine.Mcp.Tests/release/CheatEngine.Mcp.Tests.dll --filter-not-trait Category=LiveQualification --filter-not-trait Category=NativeLua --fail-skips on --report-trx --results-directory artifacts/followup-portable-final-results -``` - -`artifacts/performance-preparation/navigation-followup-evidence.json` records all 867 source hashes (32 modified and four new files), build/module identity, final TRX, test names and log hashes. All 863 prior snapshot hashes still match. The base-commit product version identifies the source baseline, while the inventory identifies its uncommitted changes; it is not a clean stable candidate. Both no-execution Settings preflights again verified all 859 inputs, and their manifests are unchanged. - -Native thread/structure regression execution, integration, live performance/admission/lifecycle qualification and final release evidence remain outstanding. The local development package still needs final stable source/version identity. No issue closure, commit, push or publication is claimed. - -## Matching Debug portable verification - -All 867 reviewed files were copied with matching hashes to `artifacts/candidate-preparation/debug-followup-a80bc6b812c14cbb8bb98a94804d80f5`. Locked restore and the Debug test-project build passed; the build reported zero warnings/errors. The Debug portable suite passed **4,234/4,234**, zero failures/skips, in 1 minute 42.351 seconds. Its exact test-name inventory matches the reviewed Release TRX. NativeLua and LiveQualification were excluded, and no golden-update or native opt-in environment variable was inherited. - -The isolated copy and original reviewed source hashes still match. Both 859-file Settings inventories and the 1,088-file navigation native preparation also remain unchanged. `artifacts/candidate-preparation/debug-followup-evidence.json` records source/module/log/TRX hashes, run times and the comparison with Release; `Record-DebugFollowupEvidence.ps1` verifies these recorded conditions. Astra found that directly serializing the TRX timestamp XML element dropped its attributes; the recorder now writes their exact string values. Final independent Astra high evidence review is scoped clear, with no test rerun needed for that recording correction. - -Commands executed from the Debug snapshot: - -```powershell -dotnet restore tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj --locked-mode -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -p:SourceRevisionId=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet build tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj -c Debug --no-restore -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -p:SourceRevisionId=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet artifacts/bin/CheatEngine.Mcp.Tests/debug/CheatEngine.Mcp.Tests.dll --filter-not-trait Category=LiveQualification --filter-not-trait Category=NativeLua --fail-skips on --report-trx --results-directory artifacts/debug-portable-results -``` - -This adds Debug evidence for the same uncommitted development source. It does not satisfy final clean-candidate checks or execute the pending native regressions. diff --git a/docs/qualification/v2.0.0/navigation-native-regression.md b/docs/qualification/v2.0.0/navigation-native-regression.md deleted file mode 100644 index 6e75d1e..0000000 --- a/docs/qualification/v2.0.0/navigation-native-regression.md +++ /dev/null @@ -1,43 +0,0 @@ -# Prepared standalone navigation Lua regression - -Status: historical unexecuted preparation, superseded by the [MCP-only closeout](mcp-closeout.md). The earlier runner/preflight/Astra preparation checks below passed, but no execution approval was received. Future runs require fresh reviewed inputs; do not reuse a pre-integration manifest against the integrated checkout. - -The [reviewed navigation follow-up](navigation-followup.md) passed 4,234 portable tests and was [packaged locally](navigation-package.md#latest-reviewed-follow-up-package). Its changed thread and structure Lua bodies remain unexecuted. This preparation uses that exact reviewed Release test module in the isolated follow-up snapshot; it does not replace the frozen primary Settings test module or either pending Settings preparation. - -## Exact proposed scope - -Run these five facts from `CheatEngine.Mcp.Tests.NativeLua.NativeLuaToolRuntimeTests` once, in one owned process: - -| Case | What it verifies | -| --- | --- | -| `ProcessV2_FixedEffects_ReturnObservedTypedHostState` | Hexadecimal thread IDs copied from the caller-owned StringList, successful destruction, and existing simulated create/open/save/pointer-size result contracts. | -| `ProcessV2_ThreadList_DestroysItsCallerOwnedStringListWhenCollectionFails` | The StringList is destroyed when the stubbed collector throws. | -| `StructureGet_StubbedElements_MapTypesAndReportMissingNames` | Detailed/concise element projection, paging, child metadata and case-sensitive missing-name refusal. | -| `StructureGet_BoundedDefinitionLookupAndPagedElements_AvoidWholeStructureTraversal` | Lookup stops at the matching definition and reads only indices 900/901 despite reported totals of 70,000 definitions and 100,000 elements. | -| `StructureGet_MissingDefinition_ReportsNotFoundWithinBoundAndLimitExceededPastIt` | A known-complete search reports not found; a search beyond the 65,536-definition cap reports limit exceeded. | - -The ordinary embedded SDK bridge and the pinned standalone Lua 5.3 DLL execute the scripts against Lua table/function stubs. CE is not launched, a real process is not selected, and no debugger or compiler/injection workflow is used. The process fixture's create/open/save operations are simulated; its managed file-output check uses a unique file under the runner's private temporary directory and deletes its output afterward. The runner sets `TEMP` and `TMP` to that run's retained result directory, clears stack-trace instrumentation and golden-update variables, and leaves the user's ordinary environment unchanged. - -The runner fails skips, checks the exact five TRX names and their passed outcomes, and requires five executed/passed with zero failed/not-executed. A 60-second deadline terminates only the test process it created; it waits for that process to exit before finishing console capture. Results and temporary artifacts are retained for review. There is no automatic retry or follow-on CE run, even if all five pass. These simulated-host results cannot prove installed CE ABI equivalence or complete native lifecycle/performance acceptance. - -## Frozen inputs and command - -The manifest verifies 1,088 files: all 867 reviewed snapshot sources, the complete test output file inventory, the Lua DLL, dotnet host, evidence and preparation scripts. - -| Input | SHA-256 | -| --- | --- | -| Input manifest | `0BABAF0BB237A137C1EADA7EC05DA8A5395A8C2D443A774EBC917D5386031267` | -| Reviewed test module | `D7A21B4ADD7204DF3312473A13D22451041FFBE2339EE4FA12BF23D830D60697` | -| Lua library | `C95DCDFA0F60F97B43D970D77FD1BB907AF4DE04B500A3C89A99600B20B35BD2` | - -The manifest and runner are under `artifacts/native-navigation-preparation/`. Preparation and preflight read/hash files without loading the test module or Lua DLL. The existing clean Release build and portable evidence are reused because no source/test code changed for this preparation. No new test discovery or native test run was performed. - -Only after explicit approval: - -```powershell -& artifacts/native-navigation-preparation/Invoke-NavigationLuaRegression.ps1 -Execute -``` - -The earlier native-work stop still applies. Previous one-session approvals are consumed; this proposal does not inherit them. The separate ten-case Settings bridge run followed conditionally by one ordinary-SDK lifecycle session remains pending and unchanged. - -Final Astra review found no actionable preparation issues. It independently verified all 1,088 hashes (867 source and 216 test-output files plus the five preparation/runtime inputs), the exact five-case allowlist, outcome/count checks, owned-process deadline, temporary-file isolation and absence of a follow-on CE run. Both existing 859-file Settings inventories remain unchanged. No discovery or native execution was used in that review. diff --git a/docs/qualification/v2.0.0/navigation-package.md b/docs/qualification/v2.0.0/navigation-package.md deleted file mode 100644 index 8e33f5e..0000000 --- a/docs/qualification/v2.0.0/navigation-package.md +++ /dev/null @@ -1,31 +0,0 @@ -# Local navigation packages - -Status: local packaging and independent Astra high evidence review passed for the [first navigation repair](navigation-preparation.md). Subsequent thread/symbol/breakpoint/structure repairs are not included in this package. This is an isolated development payload, not the final stable candidate or a published release. - -The packaging source is `artifacts/candidate-preparation/source-bbea5ef9e63c4b5cb8dca301224b39cb`. All 863 copied source hashes match the reviewed navigation snapshot. Build environment properties disable SCM discovery and explicitly set `RepositoryCommit` and `SourceRevisionId` to `0f722ef7d6b37eee0985073dfe594c6559b31c14`; all differences from that base remain separately recorded in the source inventory. Source identity alone does not represent these uncommitted changes. - -The maintained `eng/Publish.ps1` completed a locked Release publish. Its probe cold-loaded the exact lone DLL into two isolated contexts and compared both reflected catalogs with the reviewed goldens: 193 backend tools, 43 resources, 14 templates and 43 prompts. The bridge check loaded the ordinary embedded native bridge and inspected its export; it did not invoke Lua operations or attach to a target. The maintained Native AOT gateway smoke completed initialization and tool/resource/template/prompt listing against a fresh empty instance registry. - -`eng/Release.ps1` produced two byte-identical ZIPs in separate output directories. The maintained staging script verified the ZIP checksum, exact five-file flat inventory, per-file extraction hashes and matching plugin/gateway version identity. No installed deployment was replaced, no CE process or target was launched, and no release was uploaded. - -| Artifact | SHA-256 | -| --- | --- | -| Plugin DLL | `964AB04832035031FA07BF4EBD1D6A8CE8BA743754C0077CABF36F92D958DFDD` | -| Native AOT gateway | `DDB7CEC18D354F911B0F2CBABFC5C84B3F2C9A0A71AEC01A6E2F17B975758971` | -| ZIP, 17,759,980 bytes | `0A5056CC8B482F959DA4F7C150D3B0112F0B46F7CC509DC08A44D4EE516A1342` | - -Exact paths, logs, source hashes and extracted inventory are in `artifacts/candidate-preparation/package-evidence.json`. The development version remains `2.0.0-beta.2+0f722ef7d6b37eee0985073dfe594c6559b31c14`; this changed local payload must not replace published beta.2 binaries. Final version/commit identity, later repairs, clean-candidate checks, live qualification and publication remain outstanding. - -The [follow-up](navigation-followup.md) also corrects the explicit preparation tools' dispatch metadata. The first package predates that correction; the later package below includes it. - -## Latest reviewed follow-up package - -The 867-file reviewed follow-up was copied with exact hash verification to `artifacts/candidate-preparation/followup-aa947ac543104f65954bcee7297bbb63`. The same maintained locked Release publish succeeded, including both isolated exact-DLL catalog checks (193 tools, 43 resources, 14 templates, 43 prompts) and the Native AOT gateway smoke against an empty registry. Two local release ZIP builds were byte-identical; maintained staging verified all five files, checksums and plugin/gateway version identity. - -| Latest artifact | SHA-256 | -| --- | --- | -| Plugin DLL | `A52F05A8641AE09F0C3F608BCDFF414B4C7B1C62418CAEA220FF2054C01D8DFD` | -| Native AOT gateway | `622FFD85EF21728A8FA04F0BB6A85135EEF41A765BDD3DD30C76138C33498579` | -| ZIP, 17,796,609 bytes | `08208984369011EF8829D8508D57E08657762C86D55B5AE13A1031A03993F3AB` | - -Source/file hashes, exact paths and retained log hashes are in `artifacts/candidate-preparation/followup-package-evidence.json`. Independent Astra high package evidence review is scoped clear, including all 867 source hashes, both identical ZIPs, extracted inventories, retained logs and unchanged Settings preparations. This payload includes the repaired prepared-resource metadata and remains a local development version `2.0.0-beta.2+0f722ef7d6b37eee0985073dfe594c6559b31c14`. It cannot replace published beta.2 assets or supply final stable identity evidence. The bridge probe only loaded the ordinary bridge to check exports; no Lua operation, CE host or target was run. New native Lua regressions, integration into primary product source, native qualification and publication remain outstanding. diff --git a/docs/qualification/v2.0.0/navigation-preparation.md b/docs/qualification/v2.0.0/navigation-preparation.md deleted file mode 100644 index 2c4f149..0000000 --- a/docs/qualification/v2.0.0/navigation-preparation.md +++ /dev/null @@ -1,46 +0,0 @@ -# Isolated prepared navigation repair - -Status: implemented, portable-tested and independently source-reviewed by Astra high with no actionable findings in this scope. Final Release build passed with zero warnings/errors; all 4,207 portable tests passed with zero failures/skips. No native execution or integration into the primary source is claimed. - -Snapshot: `artifacts/performance-preparation/navigation-9c315236323c4d41bf2d60075d20e411`, copied from the verified 861-file [admission preparation](admission-preparation.md). Its final inventory has 863 files: 26 modified and two new relative to that snapshot. The ordinary package and pending Settings-observation inputs remain unchanged; both no-execution preflights still verify 859 inputs. - -The later [navigation follow-up](navigation-followup.md) repairs the remaining thread/symbol/breakpoint routes and corrects the explicit preparation tools' dispatch classes. This earlier snapshot and its separate local package retain the older labels; their review and test results do not supersede those subsequent findings. - -## Behavior - -Explicit attached-process `module_list`, `module_get`, and `memory_list_regions` calls prepare immutable inspection data for five seconds. Navigation reads perform one admitted current-target check, then use that prepared data. Module, region and section enumeration, symbol lookup and PE/PDB reads are absent from these prepared navigation paths. The explicit tools retain their native enumeration behavior; destination limits still do not establish native traversal bounds. - -Module and memory-map publications share a process ID, selection epoch and revision. A newer epoch invalidates old data; delayed older publication is refused. Module-only publication invalidates a prior map. Every read checks freshness against a monotonic clock; the exact five-second boundary is expired. Cold, stale or wrong-target resources return `invalid_state` with an explicit preparation hint. Completion offers no values on those failures. - -Module detail retains only the latest successful `module_get` result, accepting its canonical name or exact normalized selector. Each returned sections array is detached from the retained copy. Any later module or memory-map publication invalidates that detail result. Calls to `module_list` with an explicit `processId` do not populate or replace the attached-target snapshot. - -The new module-completion policy joins one in-flight listing and keeps the existing global dispatch admission and 750 ms wait. Successful results have no outer completion cache that could extend snapshot lifetime or hide a changed target; failed listings retain a one-second retry interval. Other completion policies remain unchanged. Request intervals and wait deadlines do not prove native dispatch occupancy. - -Resource URIs, tool names and JSON schemas are preserved. Requiring explicit preparation before navigation is an intentional behavior change, described in tool/resource metadata and embedded documentation. Admission qualification now prepares modules and regions before each timed round, recording those calls separately; that native workload remains unexecuted. - -## Verification - -| Requirement | Evidence | -| --- | --- | -| Exact expiry and process identity | `Modules_ExpireAtTheLifetimeBoundaryAndRejectWrongIdentity`, `IsCurrent_RejectsExpiredVersion` | -| Reject delayed old publication | `Modules_OlderEpochCannotReplaceNewerPublication`, `MemoryMap_StalePublicationAfterNewerModulePublicationIsRejected`, `ReadOfNewerTargetRejectsDelayedOlderPublication` | -| Atomic map/module version and invalidation | `MemoryMap_PublicationSharesModuleVersionAndModuleRefreshInvalidatesMap` | -| Preserve prepared projections without further inspection | `PreparedReads_EqualTheirExplicitPreparationAndDoNoFurtherInspection`, `ListPreparedRegions_ProjectsTheExactDefaultExplicitResultWithoutInspectionCalls` | -| Retain complete raw data after filtered source calls | `ListPreparedRegions_UsesTheCompleteMapAfterAnExplicitFilteredRead` | -| Prevent other-process and caller-array contamination | `List_OtherProcess_DoesNotPoisonTheAttachedPreparedTarget`, `PreparedDetails_AreDetachedFromExplicitAndPreparedCallers` | -| Fresh completion, coalescing and failure throttle | `Complete_FreshDispatchSuccessesAreNeverServedFromTheOuterCache`, `Complete_FreshDispatchConcurrentRequestsShareOnlyTheirInFlightListing`, `Complete_FreshDispatchFailureIsThrottledUntilItsRefreshInterval` | -| Cold, expired and switched-target wire behavior | `CompleteOverTheWire_ColdModulesNeverEnumerate`, `CompleteOverTheWire_ExpiredOrChangedTargetNeverOffersStaleModules` | -| Stable protocol surface | Reviewed backend/gateway tool and resource goldens; non-capture `ContractSnapshotTests` and `ToolCatalogContractTests` | - -Final verification from the isolated snapshot: - -```powershell -dotnet build tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj -c Release --no-restore -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -p:SourceRevisionId=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet artifacts/bin/CheatEngine.Mcp.Tests/release/CheatEngine.Mcp.Tests.dll --filter-not-trait Category=LiveQualification --filter-not-trait Category=NativeLua --fail-skips on --report-trx --results-directory artifacts/navigation-portable-final-results -``` - -The final portable run passed **4,207/4,207**, zero skips, in 1 minute 44.590 seconds. Its scope includes the focused navigation and admission classes, contract snapshots, packaging fixtures, knowledge lint and remaining managed regressions. NativeLua and live qualification were explicitly excluded. Build, test module, log, TRX and every source hash are recorded in `artifacts/performance-preparation/navigation-evidence.json`; the recorder also verifies the prior snapshot and unchanged Settings manifests. The informational version identifies the base commit plus this separately hashed uncommitted source inventory; it does not identify a clean release candidate. - -Before the final run, 214/214 focused cases and 38/38 non-capture contract cases passed. The first full portable run passed 4,196/4,207: one documentation lint failure omitted the tool name for `maxOffset`, and ten packaging fixtures failed because disabling SCM queries removed source-revision metadata. The documentation now names the tool, stays within its existing byte budget, and the final build explicitly supplies `SourceRevisionId`. No packager behavior was changed. Reviewed golden changes contain only preparation descriptions and embedded document sizes; names, URIs and schemas remain unchanged. - -Astra's read-only source review and final evidence review found no remaining actionable findings in this repair. The final review independently verified all 863 snapshot hashes, 861 prior hashes, both 859-input Settings preparations, test counters, build identity, documentation budget and the four constrained golden changes. Integration, candidate packaging, native latency/admission tests and the wider lifecycle/release matrix remain open; this repair alone cannot close #28, #31 or #33. diff --git a/docs/qualification/v2.0.0/nested-dispatch-regression.md b/docs/qualification/v2.0.0/nested-dispatch-regression.md deleted file mode 100644 index 3a1c3e7..0000000 --- a/docs/qualification/v2.0.0/nested-dispatch-regression.md +++ /dev/null @@ -1,86 +0,0 @@ -# Standalone nested-dispatch regression - -Status: Release build, hash preflight and independent Astra preparation review complete. The user approved one execution, and both standalone native-Lua cases passed. - -## Purpose and limits - -The approved instrumented CE session `20261009T111321Z-688b` observed balanced stacks but no synchronized callback inside its three short AOB waits. Repeating that workload would not guarantee the suspected nesting. This preparation creates deterministic nesting in the SDK's existing standalone Lua test host. - -It exercises the production `MainThreadDispatcher`, protected Lua calls, callback lifetime and `LuaFrame` restoration. It does not use `DispatchOverrideForTests`. The two cases cover a successful nested callback and a nested managed exception while the outer callback remains inside a protected host-wait call. - -This can test the SDK's stack restoration under the simulated contract. It cannot qualify CE's actual scheduler, reproduce the historical native crash, establish its cause, or demonstrate a production fix. - -## Isolated inputs - -- SDK source archive: commit `325c47b573f8bd39a247f1d0101f110fa36c1696`, ZIP SHA-256 `CAC8BF7218249088597E18011A3333AB30A360872A9A917AFA3B7D7DCA1DF30C`. -- Private source directory: `artifacts/dependencies/sdk-reentry-tests`. It is an archive without its own Git metadata; the parent MCP checkout's HEAD is not its source revision. -- Production SDK source remains the uninstrumented baseline. Changes are limited to two new test/helper files and staging the original published native bridge. -- Published bridge SHA-256: `B008C8D8C136187F241542E6223DC0831999D8300DC2C4C01E1CF49F6FBA7698`. -- Staged Lua library: `artifacts/native-lua-reentry/lua53-64.dll`, SHA-256 `C95DCDFA0F60F97B43D970D77FD1BB907AF4DE04B500A3C89A99600B20B35BD2`. -- Build identity: `2.0.1-mcpreentrytest.1`, explicit SDK source commit and disabled parent Git source queries. - -The earlier instrumented source trees, packages and raw run evidence are preserved separately. - -## Required assertions - -1. Both workers use distinct coroutines of the same Lua global state. Their queued callbacks execute on their captured worker states on the main OS thread, while the SDK's provider returns the main root state. -2. Callback order is exactly `outer-enter`, `wait-enter`, `inner-enter`, `inner-exit`, `wait-exit`, `outer-exit`. -3. Nested SDK work encounters the root host-wait sentinel at a nonzero stack height, changes that stack inside a frame and restores it on both success and exception. -4. Both worker stacks retain their original sentinel values and heights after dispatch returns. Main-thread dispatch flags are reset after the outer callback and on both joined workers. -5. The exact nested exception reaches the inner caller, while the outer caller continues successfully. - -## Process and lifetime boundary - -Only one OS thread may access the shared Lua global state at a time. Both workers must reach the native synchronize stand-in and block before the main thread pumps either callback. Completion of a callback does not release its worker's native epilogue. After the entire outer callback unwinds, the main thread releases and joins one worker before releasing the other. - -Lua states, coroutine references and wait handles remain alive until all workers exit. A failure that leaves a worker active must prevent another theory case or teardown from using those states. The external runner gives the isolated test process a 60-second execution deadline and terminates only that owned process if necessary. - -## Prepared runner - -`artifacts/issue-edits/Invoke-ReentryLuaTests.ps1` defaults to hash verification without execution. Its explicit `-Execute` mode invokes only `CheatEngine.SDK.Hosting.Tests.Threading.MainThreadReentrancyTests` with the `NativeLua` trait, fails skips, and requires exactly two executed and passed cases in the TRX report. It uses the staged Lua DLL, removes the trace opt-in variable, records console/results and applies bounded cleanup after runner errors. - -`Save-ReentryTestInputs.ps1` checks the extracted sources against the pinned archive, rejects unexpected source changes, and records hashes for the test output tree, source tree, runner, Lua library and .NET host. The final manifest is created only after the source and Release build are complete. - -No Cheat Engine process, target process, debugger, compiler/injection workflow or capability gate is used. The previous one-session instrumented CE approval was consumed. The user then separately approved one execution of these exact standalone cases; that execution is complete. No additional native execution is scheduled. - -## Completed offline evidence - 2026-10-09 - -- Locked restore passed. Release build of `tests/CheatEngine.SDK.Hosting.Tests/CheatEngine.SDK.Hosting.Tests.csproj` passed with zero warnings/errors after correcting the new files' compile and style diagnostics. Initial failures are retained in `goal-reentry-test-build.log` and `goal-reentry-test-build-2.log`; the successful result is `goal-reentry-test-build-3.log`. -- Source comparison checked 1,277 entries. Exactly two new test/helper files and the original published native bridge differ from the archive; no existing SDK production source changed. -- No-execution runner preflight verified all 1,485 input hashes. The source and output copies of the native bridge both match the published bridge hash above. -- Test module SHA-256: `81F3DCABC50555F27FC9799CB94A73AAE231416447F8424BCAE5334E3E56E0A0`. -- Input manifest SHA-256: `8F921108C1B48C8891903C94AD20E9B5F8BE6996451AE63DA7135BB9C2630A29`. -- Installed runtime inventory records .NET x64 10.0.12; the SDK is pinned to 10.0.401. `dotnet --list-runtimes` passed. `dotnet --info` printed the identities but exited nonzero because sandbox access to Service Control Manager was denied; that command is not recorded as a successful check. -- Independent Astra high review found no remaining actionable findings in the prepared source, callback serialization, lifetime/quarantine paths, runner and exact input hashes. It verified the output bridge identity and successful build. It did not execute the cases. - -The exact build command, from the private SDK directory: - -```powershell -dotnet build tests/CheatEngine.SDK.Hosting.Tests/CheatEngine.SDK.Hosting.Tests.csproj -c Release --no-restore -p:MinVerVersionOverride=2.0.1-mcpreentrytest.1 -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=325c47b573f8bd39a247f1d0101f110fa36c1696 -p:SourceRevisionId=325c47b573f8bd39a247f1d0101f110fa36c1696 -v:minimal -``` - -The approved execution command, run once from the MCP workspace: - -```powershell -& artifacts/issue-edits/Invoke-ReentryLuaTests.ps1 -Execute -``` - -| Requirement | Evidence | -| --- | --- | -| Deterministic nested success and managed exception cases compile | `MainThreadReentrancyTests.Nested_synchronized_dispatch_preserves_worker_and_root_stacks(false/true)`; successful Release build and reviewed source | -| Exact inputs and isolated runner are prepared | 1,485-hash preflight; source/output bridge match; Astra scoped clear | -| Both native cases pass | Exact false/true cases passed in the approved run below; no real-CE qualification | - -## Approved standalone execution - -Run directory: `artifacts/reentry-lua-results/885121c89ef846fead7b76804b21e5f3`. The single approved runner invocation completed with **Passed: 2, Failed: 0, Skipped: 0**, total duration 237 ms. The TRX names identify both `innerThrows: False` and `innerThrows: True` cases; both passed. - -The owned test process (PID 46208) exited with code 0, without timeout, runner failure or cleanup failure. A follow-up process check found that PID absent. All 1,485 prepared input hashes still matched after execution. - -- Receipt SHA-256: `A99389CBB5DE27B418688FF3252A215CCCAF1D8473073B3F9B76E172ABCA7A11`. -- TRX SHA-256: `3B0C076521EDC02D36636319990259A4FC33ADB5C11A49143E78E940A532488D`. -- Evidence extract: `artifacts/issue-edits/goal-reentry-test-execution-check.json`. - -This result demonstrates balanced restoration and correct exception ownership for the two deterministic simulated-host paths. The earlier CE trace contains no synchronized reentry, so these passing standalone cases do not connect the historical CE failure to this mechanism, rule out other schedules, or demonstrate a production fix. The original crash and native qualification gates remain open. - -Independent Astra high final review confirmed both exact TRX cases, console/receipt agreement, raw evidence hashes and all 1,485 input hashes, with no remaining actionable findings in that scope. Preparation, approved execution and evidence review were checked off in #28 and #33; exact body readback confirmed both issues remain open. #31 remains open unchanged. diff --git a/docs/qualification/v2.0.0/next-live-investigation.md b/docs/qualification/v2.0.0/next-live-investigation.md deleted file mode 100644 index 6c93a56..0000000 --- a/docs/qualification/v2.0.0/next-live-investigation.md +++ /dev/null @@ -1,39 +0,0 @@ -# Next live investigation for the unresolved dispatch crash - -Status: The user explicitly authorized this bounded real-CE investigation on 2026-10-09 after the proposal was reviewed. Two initial runs, one run with the exact post-AOB allocation probe, and one with the restored original read-only prelude passed. Results are recorded in [remaining-checkpoint.md](remaining-checkpoint.md#authorized-bounded-ce-investigation---2026-10-09). The historical crash remains unresolved. This permission is separate from the earlier standalone NativeLua approval and does not authorize other native or release gates. - -## Executed bounded investigation - -The maintained `dispatch-diagnostic` scenario first ran `MemoryNamedScanThenAob` against the already verified local Release distribution at `artifacts/dist/goal-contract2-release`. This exercises the memory, named-scan and AOB ordering that preceded the recorded Lua stack failure. The narrower `NamedScanThenAob` and `AobOnly` cases were available to isolate a failing prefix, but these passing runs did not justify executing them. Each executed case was a separate disposable session; it is diagnostic evidence, not a full lifecycle or stable-release pass. - -Package identity and hashes are in [remaining-checkpoint.md](remaining-checkpoint.md#offline-package-refresh---2026-10-09) and `artifacts/issue-edits/goal-contract2-package-manifest.json`. The package uses the ordinary SDK dependency and uncommitted reviewed MCP source; the unfinished private diagnostic SDK/MCP clones are outside this proposed run. - -The runner settings select Release, x64 targets, `dispatch-diagnostic`, the explicit case, and the absolute current distribution path through `CHEATENGINE_MCP_LIVE_QUALIFICATION_DISTRIBUTION_DIRECTORY`. Following the explicit user authorization, the existing live acknowledgement is set only for the command process and restored afterward. No compiler or managed-injection acknowledgement is supplied. - -The two initial passes exercised the common Lua dispatcher/frame again through scalar sentinel reads, but did not exercise the allocation operation that failed after AOB in the older lifecycle run. The reviewed diagnostic now adds `memory_allocate` with name `live-dispatch-post-aob`, size 64 and `executable=false` immediately after AOB cleanup, followed by `memory_free` in `finally` after confirmed acquisition. Acquisition, release and cleanup results are recorded separately. This matches the first operation in the original CFG fixture without adding CFG writes or graph calls; product source and package bytes remain unchanged. - -After that probe passed, source comparison identified the next controlled difference: both failed lifecycle runs called `VerifyBothAsync` and `LiveResourceQualification.VerifyAsync` after attachment and before the memory/scanner/AOB sequence. The separate `ResourcePreludeMemoryNamedScanThenAob` diagnostic case restores those exact read-only prelude calls and keeps the same allocation/free endpoint. The prelude checks runtime identity/gates and scalar sentinels, reads static catalogs/documentation and each host's runtime resource, renders a prompt and checks instance completions. Rendering the prompt does not execute its debugger workflow. This variant does not run the later full lifecycle operations. It was explicitly selected for its run; omitting the case selector now runs all four fixed diagnostic cases in separate sessions. - -## Workload and ownership - -- Launch two private CE copies and two test-owned x64 targets through the existing live harness, with an isolated run directory outside the repository and installed CE directory. -- Keep all four optional capability gates disabled. Exercise only the bounded memory/scanner/AOB workflows, the post-AOB 64-byte non-executable allocation/free probe and final sentinel reads; their temporary allocations and named scanner state are owned and cleaned up by the harness. -- Preserve the existing refusal when CE or DebugView is already running; do not close user processes to satisfy preflight. Snapshot and restore the user settings that the private-host harness temporarily changes, and leave the source installation unchanged. -- Keep the existing five-minute private-host lifetime bound. Retain the original operation failure alongside any cleanup failure, then verify that the owned hosts/targets stopped and user state was restored. -- Stop for review after a crash, failed restoration or unexpected effect. No debugger, extended compiler, injection, kernel/DBVM, soak, tag, upload or release is included. - -## Evidence required before any completion claim - -Record the exact package and host hashes, selected case, original error or crash, bounded operation ordering, cleanup receipts and owned-process termination. A pass in this reduced scenario does not resolve the previously observed uninstrumented production crash or qualify plugin lifecycle. Root-cause evidence, a demonstrated production fix, original-order lifecycle reruns, and the other issue gates remain separate. - -## Next preparation boundary - -The four approved ordinary-SDK sessions are complete. A separately approved private stack-balance/reentrancy session also passed as `20261009T111321Z-688b`, retaining the exact published native bridge and measuring actual versus saved stack heights. The [diagnostic ledger](stack-trace-diagnostic.md) records the workload, hashes, offline checks, execution evidence and limits. Its 1,874 trace records show balanced instrumented boundaries, but no synchronized callback reentry during the three short AOB waits. No crash was reproduced and no production fix demonstrated. This one-session approval is consumed; no further live run is scheduled. An offline trace validator now passes 11 synthetic controls and both recorded host snapshots; a discriminating reproduction remains to be prepared. The callback/provider pointer difference alone remains expected under CE's per-thread state contract. - -Two separately approved [standalone nested-dispatch cases](nested-dispatch-regression.md#approved-standalone-execution) now pass. They force genuine dispatcher callbacks on captured worker coroutines during a protected root-state call, including stack perturbation and a managed inner exception. They establish that these simulated schedules restore the stacks correctly, so nesting alone has not reproduced the historical defect. A next live proposal must target a remaining difference in the real failing lifecycle or scheduling evidence; repeating the same short passing prefix does not supply that evidence. - -The separately approved [ordinary-SDK full lifecycle attempt](lifecycle-resume.md#approved-execution-result), `20261009T120128Z-8f44`, then completed all six workflow groups before the first Settings/Plugins disable request refused with `expected exactly one owned plugin row`. Cleanup and settings restoration passed. This reproduced the remaining bridge failure with the ordinary package, while still not reproducing the earlier native stack failure. Its one-session approval is consumed; further work is offline source-based bridge diagnosis before any new execution proposal. - -The subsequent [retained-key experiment](lifecycle-key-fix.md#approved-conditional-execution) passed eight approved standalone bridge cases, then its one conditionally approved lifecycle run `20261009T121623Z-71c7` again completed all six workflow groups and refused the first disable with measured `count=0`. Both 859-file manifests still matched; settings/source preservation, cleanup and owned-process termination passed. Astra confirmed the evidence. Exact source review then disproved the key-needed premise: checklist population is outside the key condition. That unsupported change was rolled back while keeping the observed-count error and zero-row test. The two-stage approval is consumed; the next source investigation must distinguish reload early return, swallowed exception and empty plugin list before any further execution is proposed. - -The [Settings observation proposal](settings-observation.md) is now built and independently reviewed, with exact manifests for ten standalone bridge cases and a conditional one-session lifecycle attempt. It adds only read-only runtime class and pre-reload count observations to the existing bounded refusal. These narrow the observable possibilities but do not fully distinguish a swallowed exception from an empty plugin list. Both stages await renewed authorization; no further native run has occurred. diff --git a/docs/qualification/v2.0.0/offline-contract-audit.md b/docs/qualification/v2.0.0/offline-contract-audit.md deleted file mode 100644 index ab57059..0000000 --- a/docs/qualification/v2.0.0/offline-contract-audit.md +++ /dev/null @@ -1,56 +0,0 @@ -# Offline contract audit - -Date: 2026-10-09. -Scope: working-tree source, public contract metadata, result mappings and portable checks. -This is not installed-collector, native workflow, timing, or final-package qualification. - -## Coverage - -The metadata inventory is `tests/CheatEngine.Mcp.Tests/Contract/Golden/tool-summary.txt`: 193 backend tools and the gateway's `instance_list`. -The remaining-domain reviewer traced all 162 tools outside the previously reviewed Asm, Record, Runtime and Table domains. -The result-shape reviewer covered the other domains and gateway; the managed collector review is recorded in [contract-checkpoint.md](contract-checkpoint.md). - -| Domain | Backend tools | Metadata and result review | -| --- | ---: | --- | -| Aob | 3 | An indeterminate scan qualifies its empty result with `exact`, `unique` and `targetVerified`; no additional mismatch. | -| Asm | 8 | Earlier patch-release `may_prompt` correction; optional diagnostics and documented empty hook-template bytes retained. | -| Code | 14 | Each search creates a fresh job, so its starter is non-idempotent. Pinned dissector source confirms nil tables mean no recorded references, strings or functions. | -| Debugger | 18 | Feature-dependent branches traced; invalid state, optional registers and loss/truncation qualifiers distinguish unavailable observations. | -| DotNet | 10 | Constant metadata codes and collector array positions no longer promise declared parameter types or ordinals; see the pinned-source analysis. | -| Exec | 7 | Fixed execution requirement and conditional kernel compiler requirement match the implementation; unavailable paths/diagnostics remain optional. | -| Kernel | 7 | Watch polling drains a native log and can evict retained events. Initialization probes must distinguish unavailable from an observed false. | -| Lua | 2 | API lookup reads the host installation's `celua.txt`, so it is open-world. Return and search truncation/counts qualify their arrays. | -| Memory | 19 | Dump overwrite is destructive. The system-module probe must distinguish unavailable from false; read/batch errors already preserve unavailable values. | -| Module | 5 | Import/export enumeration parses the full directory in one dispatch before paging, so it is `host_scan`. The SDK requires a string path; no separate unavailable-path sentinel was established. | -| Mono | 15 | Fixed and conditional execution requirements traced; fields, optional method facts and object reads reviewed against the pinned public collector source. | -| Pointer | 16 | Imported maps/scans have no job, so their job ID is omitted; live capture/search results retain the actual ID. | -| Process | 9 | Target state is qualified by `isOpen`; optional process facts and file size remain unavailable when unobserved. | -| Record | 14 | Earlier destructive-write, AA requirement and prompting corrections; empty dropdown text remains a valid value. | -| Runtime | 6 | Earlier bulk-release prompting correction; empty jobs/resources are valid and process facts are qualified by attachment state. | -| Scan | 8 | A detached main scanner has no process ID. FoundList returns formatted strings, including read-failure markers, without a separate readability flag. | -| Speedhack | 2 | An unavailable symbol probe must not become observed absence or decide whether a mutation is needed. | -| Structure | 15 | Unreadable values are omitted and comparisons classify them. The CE bridge returns field names as strings; an empty name does not establish missing metadata. | -| Symbol | 10 | A non-table module-preference result must be a refusal, distinct from a valid empty list. | -| Table | 3 | Earlier save-overwrite correction; opaque format explicitly qualifies zero inspection counts. | -| Util | 2 | Pure calculations/conversions preserve valid zero and empty results. | - -Gateway forwarding preserves the backend result; `instance_list` qualifies incomplete discovery explicitly. -The audit treats a legitimate documented sentinel differently from a false observation. Defensive fallbacks alone are not proof that a native API can actually omit its promised data. - -Pinned public source confirms that [`CheckAddress`](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/DissectCodeThread.pas#L494-L576) returns false when no references are recorded, and the [Lua wrapper](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaDissectCode.pas#L144-L271) uses nil for these empty lists. Those normalizations remain unchanged. -[`FoundList.Value`](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaFoundlist.pas#L82-L100) always returns text, and [its reader](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/foundlisthelper.pas#L626-L665) includes formatted failure markers. The result description now states that a formatted or empty value is not proof of readability and directs callers to `memory_read` to verify it. - -## Corrections and verification boundaries - -Six more tools receive effect-metadata corrections: `code_start_search`, `memory_dump_to_file`, `lua_find_api`, `kernel_poll_watch`, `module_list_exports` and `module_list_imports`. -The kernel exception is explicit in contract validation: this tool must be destructive, non-read-only and non-idempotent, while passive poll tools retain their existing rule. -Recovery guidance and the legacy tool map distinguish the native-log drain from the passive retained-page primitive. -The module classification is conservative source review, not a measured duration or a claim that the work was made faster. -Its exports resource projection is removed before contract freeze because the unchanged resource validator admits only short source tools. Explicit `module_list_exports` calls remain available; the prerelease URI removal and replacement are recorded in the release notes. - -The result corrections cover imported pointer job IDs, memory/kernel status probes, detached scanner IDs, module-preference failures and speedhack probe failures. -The prepared fixed-Lua regressions subsequently passed in the explicitly authorized standalone Release NativeLua suite: 626 passed, zero failures/skips. These run against a standalone Lua 5.3 runtime with CE-shaped host stubs; they do not qualify actual CE operations or collector behavior. Live CE, target attachment, native diagnostic, debugger and extended compiler/injection work remain stopped. -The .NET parameter correction preserves raw payload fields and documents unrecoverable collector ambiguity; it does not synthesize types from signature text. - -Both Debug and Release portable suites passed 4,135 tests; builds, style and whitespace checks passed. Independent Astra final review found no remaining actionable findings in this scoped patch and its evidence; details are recorded in [remaining-checkpoint.md](remaining-checkpoint.md). -Roadmap rows 3.03 and 3.06 still require the applicable native and exact-candidate evidence before final contract freeze. diff --git a/docs/qualification/v2.0.0/performance-preparation.md b/docs/qualification/v2.0.0/performance-preparation.md deleted file mode 100644 index da4ec60..0000000 --- a/docs/qualification/v2.0.0/performance-preparation.md +++ /dev/null @@ -1,59 +0,0 @@ -# Isolated performance preparation - -Status: isolated implementation, clean Release build, focused managed regressions and final Astra high review complete. No new native performance, lifecycle, or soak run is authorized or claimed. - -The snapshot is `artifacts/performance-preparation/source-f695c724e788433c97cb34470f17278a`. It was copied from the current working tree at HEAD `0f722ef7d6b37eee0985073dfe594c6559b31c14`; it is not a clean release commit. Changes below have not been integrated into the primary source tree or the ordinary product package. The prepared [Settings observation](settings-observation.md) and its two 859-file manifests remain unchanged while its execution decision is pending. - -## Workload matrix - -The earlier performance runner measured only short scalar reads and 16-item batches. The isolated expansion adds fixed work sizes, warmups and sample counts for larger batches, regions, named scans, AOB, baseline-compatible `pointer_read_chain`, module/symbol resolution, structures, dissection and discovery. It retains three separately started published-beta.2 baseline sessions and three candidate sessions, with exact published baseline plugin/gateway hashes checked before use. - -Samples measure complete client workflows, including transport, decoding, assertions and any explicitly included polling/reset/cleanup. They do not isolate CE native execution time. Scan/dissection polling deadlines begin after their start calls return; those calls can themselves block or outlive a client timeout. The current 100 ms short-call allowance and 1.5-times bounded-work threshold remain the planned acceptance policy in [acceptance.md](acceptance.md#performance-acceptance), not measured results. - -All 190 shared baseline/current backend input schemas match after ignoring only documentation fields (`title` and `description`). That comparison does not establish output or native-behavior compatibility. The new runner deliberately uses the baseline's single-chain pointer tool, not the newer batch-chain tool. - -The first independent Astra review found scanner reuse without reset, incomplete result assertions, mismatched profile validation gaps, ownership recorded too late after mutations, an AOB range extending beyond its stated allocation, and pass recording before gateway disposal. All were repaired. A second pass found that scanner cleanup could replace the original failure, and that a future runtime receipt would incorrectly label measured work as unexecuted. The repaired cleanup helper preserves both errors; its four outcome combinations passed managed regression tests. Final Astra high review found no remaining actionable findings in the prepared matrix and pointer-retention patch. - -Pinned public CE source synchronously waits for dissection work inside its Lua binding: [LuaDissectCode.pas, lines 31–83](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaDissectCode.pas#L31-L83) calls `waitTillDone` after `dowork`; [DissectCodeThread.pas, lines 420–432](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/DissectCodeThread.pas#L420-L432) implements the completion wait. Installed-binary equivalence remains unproven. The harness must retain the owned target buffer through uncertain start, response, poll or clear outcomes and leave its final containment to owned target shutdown. A stop receipt alone does not prove that native work ended. - -## Pointer-reference cleanup retention - -The resource audit found one concrete retention gap. A live pointer-reference range search creates a temporary value-scan session. If releasing it fails, the tool correctly tracks the session, but previously allowed another range search to append another failed session indefinitely. Lua ledger and managed-job limits do not cap these directly tracked Client leases. - -The isolated fix reserves one range-search slot before dispatch. Successful cleanup or failure before creating a session releases that reservation; failed cleanup retains it until the resource's release/acknowledgement callback runs. Identity comparison prevents an older callback from clearing a newer search's reservation. Exact AOB and stored-map paths do not use this temporary session. A failure to register the retained resource conservatively leaves the slot closed; a new activation is the recovery boundary if no resource was registered. - -The six focused managed cases passed and cover repeated cleanup-failure refusal, scan failure followed by cleanup failure, acknowledgement, retryable cleanup followed by successful release, reentry, and pre-body dispatch failure. Independent Astra source review found no actionable correctness defect in this scoped fix. - -## Managed verification - -- Release build: zero warnings and errors (`performance-final-build.log` in the snapshot). -- Final performance/lifecycle helper selection: 20 passed, zero failed/skipped (`performance-final-tests.log` and `artifacts/performance-final-results/*.trx`). -- All pointer regressions: 130 passed, zero failed/skipped (`pointer-managed-regressions.log` and `artifacts/pointer-managed-regressions/*.trx`). This separate run preceded the final performance-only cleanup repair; pointer source and tests did not change afterward. -- Source inventory: 856 verified files, exactly six changed/new files relative to the initial snapshot. Exact source, build-log and TRX hashes are retained in `artifacts/performance-preparation/preparation-evidence.json`. -- Both primary Settings no-execution preflights still verified 859 inputs. No native process was started. - -| Requirement | Managed evidence | -| --- | --- | -| Reject missing or mismatched paired workload evidence | `Pair_RefusesBaselineMissingOneMatrixWorkload`, `Pair_RefusesCandidateWithWrongFixedSampleCount`, `Pair_RefusesBaselineWithWrongFixedSampleCount` | -| Preserve a scan failure when cleanup also fails | `RunWithCleanupAsync_PreservesBothFailuresAndAlwaysReleases` (four combinations) | -| Prevent repeated failed pointer scans from accumulating resources | `FailedCleanup_RefusesRepeatedRangeSearchesWithoutAddingResources` (two cases) | -| Permit subsequent searches after confirmed release or acknowledgement | `RetryableCleanup_HoldsReservationUntilReleaseSucceeds`, `AcknowledgedFailedCleanup_AllowsANewRangeSearch` | -| Refuse reentry and recover from pre-body dispatch failure | `ReentrantRangeSearch_IsRefusedBeforeAnotherDispatch`, `DispatchFailure_ReleasesReservationForTheNextRangeSearch` | - -Successful commands, run from the isolated snapshot: - -```powershell -dotnet build tests/CheatEngine.Mcp.Tests/CheatEngine.Mcp.Tests.csproj -c Release --no-restore -p:EnableSourceControlManagerQueries=false -p:RepositoryCommit=0f722ef7d6b37eee0985073dfe594c6559b31c14 -v:minimal -dotnet artifacts/bin/CheatEngine.Mcp.Tests/release/CheatEngine.Mcp.Tests.dll --filter-class CheatEngine.Mcp.Tests.LiveQualification.LivePerformanceMatrixTests --filter-class CheatEngine.Mcp.Tests.LiveQualification.LiveLifecycleQualificationTests --fail-skips on --report-trx --results-directory artifacts/performance-final-results -dotnet artifacts/bin/CheatEngine.Mcp.Tests/release/CheatEngine.Mcp.Tests.dll --filter-class 'CheatEngine.Mcp.Tests.Tools.Pointer.*' --fail-skips on --report-trx --results-directory artifacts/pointer-managed-regressions -``` - -The first focused test attempt was 32/33: the retryable-release test used the fixture's default Lua ledger, whose fixed-Lua executor is deliberately unavailable in portable tests. It was corrected by injecting a managed-only `TargetResources` into that one test, after which all 33 focused tests passed. The final cleanup regression addition then passed its 20-test selection. No native substitute, broad native suite, full portable-suite rerun, or candidate-package rebuild is claimed. - -## Remaining requirements - -- The native matrix is unexecuted; roadmap 9.01 and its performance acceptance remain unchecked. -- Multi-client concurrency/UI and resource/completion measurements are now implemented, managed-tested and Astra-reviewed in a separate [admission preparation](admission-preparation.md) snapshot. Native 9.03/9.05 qualification remains outstanding. -- CE-backed module completion uses dispatch admission, but cached and empty responses cannot prove a fresh native call or a busy refusal. A 750 ms completion wait can return while its native refresh continues. -- Package-matched Client/SDK source confirms that collection caps apply after full native enumeration. A separate [prepared-navigation repair](navigation-preparation.md) now redirects module/detail/region resources and module completion to explicit five-second snapshots and passes 4,207 portable tests. It is not integrated into the primary source/package; explicit source-tool native enumeration remains unbounded by copy/page limits. -- The two-hour soak, lifecycle failure, historical SDK stack failure, broader native/compiler/installation/client matrix, final clean-candidate checks and publication are still outstanding. Issues #28, #31 and #33 cannot be closed from this preparation. diff --git a/docs/qualification/v2.0.0/release-notes.md b/docs/qualification/v2.0.0/release-notes.md deleted file mode 100644 index ff2d062..0000000 --- a/docs/qualification/v2.0.0/release-notes.md +++ /dev/null @@ -1,70 +0,0 @@ -# Stable v2.0.0 release notes draft - -Status: Unpublished draft; the current working build still identifies itself as `2.0.0-beta.2`. -Publication requires the final candidate gates in [release-signoff.md](release-signoff.md). - -## Changes since beta.2 - -- `pointer_get_access_info` analyzes supplied x86 or x64 instruction and register facts and explains effective addresses, uncertainty, and pointer-search guidance without live symbol lookup. -- `pointer_read_chains` validates bounded candidate chains with separate resolution, comparison, and optional final-value outcomes, cancellation progress, and target-change guards. -- `exec_compile_csharp` wraps CE's compiler and immediately exports its temporary assembly through the existing file policy; compilation remains separate from injection. -- The backend catalog contains 193 tools; gateway discovery adds `instance_list`. -- Publication compares the actual bundled plugin against reviewed tool, resource, template, and prompt snapshots. -- Build CI now runs the portable suite in Debug and Release. -- The private native fixture supports separate x86 and x64 runs and target restart checks. -- Internal tool errors include the logged correlation ID; non-object details are preserved under `details.value` alongside `errorId`. -- `record_create` and `table_save` advertise their potentially destructive writes. `record_set_script` declares and checks its Auto Assembler requirement before dispatch; `record_clear` includes the missing recovery hint when that switch is off. -- Record activation/deletion/clear and individual or bulk patch release advertise `may_prompt`, because script execution can block CE or show dialogs. -- `dotnet_get_method_parameters` now describes `elementType` as the collector's metadata-constant code and `index` as its returned array position; declared types come from the optional signature, and the collector can return incomplete or placeholder entries. -- Additional effect annotations cover fresh code-search jobs, destructive file dumps, host-file API lookup, consuming DBVM polls and potentially blocking module-directory reads. -- The prerelease resource URI `cheatengine://instance/modules/{module}/exports{?offset,limit}` is removed: full export-directory reads can block CE and must not be prefetched as a short resource. Use the unchanged `module_list_exports` tool explicitly; module-list and module-detail resources remain available. -- Unavailable memory/kernel status and imported pointer job IDs are omitted instead of fabricated as false or empty text. Detached scanner results omit the process ID; failed module-preference and speedhack probes report refusal. An unavailable post-change speedhack observation omits `hooksInstalled` while retaining the restore resource. -- Module, region, thread, registered-symbol and breakpoint resources now use five-second prepared snapshots. Call the corresponding explicit list/get tool before reading its resource; cold, expired or target-mismatched snapshots report `invalid_state`. Module-name completion also requires a fresh attached-target module list. The explicit enumeration tools declare `blocking_native`; their result limits do not bound CE's underlying enumeration. -- `structure_get` bounds global definition lookup and reads only the requested element page. Thread collection now owns and disposes the StringList required by CE. Failed cleanup of a pointer-reference range search retains one reservation and refuses another search until recovery. - -The optional-field omissions, removed exports URI and explicit resource-preparation requirement above are beta-to-stable compatibility exceptions that still require final stable contract review (requirement 2.07). Clients must distinguish omitted status/job fields from observed false or empty values, use `module_list_exports` for exports, and prepare snapshots before navigation. The stable contract has not frozen, and the beta tags are unchanged. The [MCP-only closeout](mcp-closeout.md) permits administrative issue closure without stable sign-off. - -The approved compiler-only phases passed on the exact local candidate and environment recorded in [compiler-checkpoint.md](compiler-checkpoint.md), with separate x86 and x64 disposable targets. -Both immediate exports survived both CE shutdowns; raw artifacts were retained at the observation points with distinct per-instance temporary roots. -Shared-temp expiry, unavailable prerequisites, reload, and separate injection remain unqualified, as does the final stable package. - -An uninstrumented local candidate also exposed native Lua dispatch corruption, including a private-host crash. -Later instrumented runs did not reproduce that failure, but they do not establish a production fix. -Stable publication remains blocked on resolving it and completing the missing qualification in [remaining-checkpoint.md](remaining-checkpoint.md). - -## Support and limitations - -The package remains Windows x64 with an x64 Cheat Engine 7.7 host and a self-contained x64 gateway. -The plugin requires x64 .NET 10, ASP.NET Core 10, and Windows Desktop 10 runtimes. -Target architecture and optional compiler, collector, debugger, kernel, and DBVM availability are separate from plugin-host support. -Use the [support matrix](support-matrix.md) and [evidence ledger](release-signoff.md) for exact tested environments and outstanding qualification. - -Instruction text is authoritative for supplied pointer analysis; optional instruction bytes validate length and supported address-size facts and are not an independent decoder. -Post-execution register facts and indexed operands can be uncertain or dynamic. -Absolute pointer roots must be refreshed after a target restart; symbolic roots resolve against the currently selected target. -An unreadable final value does not turn a resolved pointer address into a failed chain. - -## Upgrade from beta.2 or a folder deployment - -1. Preserve the existing matching plugin and gateway pair as a rollback copy, along with personal configuration and CE's runtime configuration. -2. Disable the plugin, close Cheat Engine, and stop the MCP client's gateway before replacing loaded files. -3. Install the plugin DLL and gateway from the same verified package into a new directory. -4. Replace the previous entry for this plugin when its path changes, including a folder deployment, before selecting the new single `CheatEngine.Mcp.dll`; keep unrelated plugin entries. -5. Point the MCP client at the new gateway executable and enable the plugin. -6. Discover a fresh `instanceId` using `instance_list`, then verify `runtime_get_info` before attaching to a target. - -Keep personal settings in the existing user data directory; no configuration migration or new dependency is introduced by these changes. -The four capability switches remain enabled by default and are exposure controls, not a sandbox. -File and table allowed roots remain empty by default. -Do not overwrite or delete unrelated CE plugins, tables, records, or settings during an upgrade. - -To roll back, close both processes, replace this plugin's CE entry with its previous entry, restore the matching gateway registration and any deliberately changed configuration, and rediscover the new activation. -Do not force replacement of locked files or reuse an old activation identifier. -The [installation recovery guide](../../../srcs/CheatEngine.Mcp.Resources/Knowledge/Documents/connection-troubleshooting.md#several-instances-updates-and-removal) covers a locked gateway, partial upgrade, rollback and removal. -These are migration instructions; clean-install, upgrade, rollback, and supported-client qualification must still be recorded on the final package. - -## Release identity - -The final notes must name the clean source commit, exact version, qualified environments, public ZIP, and checksum file. -Existing beta tags remain unchanged; stable publication uses a new annotated `v2.0.0` tag. -No local working-build evidence is a claim that stable assets have been published. diff --git a/docs/qualification/v2.0.0/release-signoff.md b/docs/qualification/v2.0.0/release-signoff.md deleted file mode 100644 index 6a6eda1..0000000 --- a/docs/qualification/v2.0.0/release-signoff.md +++ /dev/null @@ -1,142 +0,0 @@ -# Stable v2.0.0 release sign-off - -Status: Not approved for release. The real-CE Lua dispatch crash remains unresolved; passing offline and standalone Lua checks do not qualify the production host. - -The maintainer subsequently requested an [MCP-only implementation closeout](mcp-closeout.md), administrative issue closure and a tested local commit to `main`. The original stable-release criteria below remain unmet; issue closure no longer represents stable sign-off. - -This document is the durable progress and evidence index for the stable v2.0.0 program. -Unchecked items remain incomplete even when related source code exists. -Issue #28 checkboxes must be updated only after the corresponding evidence is reviewed. - -## Critical path - -- [ ] Review and approve the support matrix, acceptance criteria, release-stopping defects, and evidence rules. -- [x] Create focused implementation and qualification issues with owners, dependencies, required evidence levels, and roadmap requirement identifiers: #29 through #33 in the v2.0.0 milestone. -- [x] Run the local working candidate through the existing dynamic Cheat Engine 7.7.1.10828 fingerprint and reviewed-autorun checks; the final clean candidate must repeat them. -- [x] Add disposable x86 and x64 fixture coverage required by the target architecture promise. -- [x] Make the packaged-DLL probe compare the exact packaged plugin against the explicit candidate golden directory. -- [x] Complete `pointer_get_access_info` implementation, V+, applicable VN, documentation, and supplied-facts gateway/plugin qualification; live debugger capture remains a separate open workflow. -- [x] Complete `pointer_read_chains` implementation, V+, documentation, and x86 and x64 native fixture qualification. -- [x] Complete the `compileCS` feasibility check under a reviewed policy; see the exact environment and remaining native cases in [compiler-checkpoint.md](compiler-checkpoint.md). -- [ ] Complete `exec_compile_csharp` implementation and qualification or record an explicit reviewed deferral with a linked follow-up. -- [ ] Reconcile and freeze the final tools, schemas, resources, prompts, completions, routing, capability gates, and migration contract. -- [ ] Diagnose the production Lua dispatch failure, demonstrate a fix without timing-altering instrumentation, and repeat the failed native workflow/lifecycle cases on the corrected candidate. -- [ ] Complete lifecycle, recovery, workflow-domain, installation, upgrade, rollback, and supported-client qualification. -- [ ] Record reproducible performance baselines and approve the measured-relative thresholds. -- [ ] Complete the planned two-hour soak against the exact RC package. -- [ ] Build and qualify the exact RC commit and package. -- [ ] Set the final version to `2.0.0` in committed source and rebuild from the clean final commit. -- [ ] Repeat the required final-source and final-package checks after the version change. -- [ ] Create the annotated `v2.0.0` tag at the exact binary source commit. -- [ ] Upload the ZIP and `SHA256SUMS.txt` as a draft and verify remote identity, hashes, sizes, and notes. -- [ ] Publish with `draft=false` and `prerelease=false` only after review. -- [ ] Download and verify the public package and smoke-test the shipped installation and first-use path. - -## Evidence ledger - -Working-tree evidence is recorded in [local-checkpoint.md](local-checkpoint.md), including package hashes, environment, test counts, two native run identifiers, cleanup, and limitations. -It does not establish a clean final stable candidate. -Compiler-only evidence for a clean local candidate is recorded in [compiler-checkpoint.md](compiler-checkpoint.md). -Subsequent failed native runs, the unresolved production crash, and passing working-tree checks are recorded in [remaining-checkpoint.md](remaining-checkpoint.md). The separately approved standalone and real-CE sessions below have completed; their one-session approvals are consumed. The [Settings observation sequence](settings-observation.md) and [five standalone navigation cases](navigation-native-regression.md) are historical unexecuted preparations, superseded by the MCP-only closeout. Future execution requires fresh reviewed inputs and authorization under the earlier native-work stop. This ledger does not authorize further native, debugger, extended compiler/injection, soak or release execution. -The exact candidate gates below refer to the final stable candidate and remain open. - -| Evidence ID | Requirements | Level | Candidate | Environment | Outcome | Cleanup | Safe reference | Status | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| LOCAL-01 through LOCAL-08 | Sections 3-6 and bounded portions of 7, 8 and 11 | implementation, V+, VN, AOT, VL | Historical uncommitted `codex/roadmap-v2`, beta.2 version; exact artifact hashes in checkpoint | Windows 11 Pro 26300; CE 7.7.1.10828; .NET 10.0.12; x86/x64 targets | All recorded checks passed | Both native runs restored user state and preserved the source installation with no cleanup failures | [Local checkpoint](local-checkpoint.md) | pass within recorded scope | -| COMPILER-01 and COMPILER-02 | Section 6 feasibility and compiler-only phases 1-4; bounded section 11 checks | V+, VN, AOT, VL | Clean `808a626e65eddab113737a27a7c2345555b1c241`, beta.2, Release; exact artifact hashes in checkpoint | Windows 11 Pro 26300; CE 7.7.1.10828 x64; .NET 10.0.12 and Framework 4.8.09221; x86/x64 targets | Build/package checks and all approved compiler-only phases passed | Four sessions restored user state and preserved installation; compiler files removed; no cleanup failures | [Compiler checkpoint](compiler-checkpoint.md) | pass within recorded scope; no injection or stable release | -| LIFECYCLE-X64-01 | Sections 7-9 | VL | Clean `d3984574e0d0757beebd3ea36deb6f9bef7358d1`, beta.2, Release; hashes in checkpoint | Windows x64; exact Windows build/runtime patch not emitted; CE 7.7.1.10828 x64; owned x64 targets | Memory, named scan and AOB subcases passed, then allocation refused with a Lua stack-input failure and unknown host effect | Owned hosts/targets stopped; user state restored and source preserved; no reported cleanup failures | [Native lifecycle result](remaining-checkpoint.md#native-lifecycle-and-workflow-result) | failed; not lifecycle-wide qualification | -| DIAGNOSTIC-X64-01 | Production crash investigation | diagnostic VL | `207d6fdb62c4874185ba843f00add85da1438d1f`, beta.2, Release; hashes in checkpoint | Windows x64; exact Windows build/runtime patch not emitted; CE 7.7.1.10828 x64; owned x64 targets | Host A crashed with `0xC0000005` in the Lua stack/dispatch path during AOB work; no root cause or fix established | User state/source preservation recorded and owned processes stopped; host A lifecycle-indicator cleanup evidence failed | [Diagnostic candidate](remaining-checkpoint.md#diagnostic-candidate-207d6fd) | failed; release blocker remains | -| OFFLINE-CONTRACT2 | Bounded sections 3 and 11 | portable, standalone VN, packaged reflection, AOT and local archive checks | Uncommitted working tree on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, beta.2; Debug/Release binary and ZIP hashes plus source manifest in checkpoint | Windows x64; standalone Lua 5.3 DLL identity recorded; no CE host launched | 4,135 portable cases per configuration; 626 standalone Release NativeLua cases; actual packaged catalogs/AOT snapshots and same-input ZIP reproduction passed; independent Astra scoped review clear | Exact test-output executable restoration and staged-file hashes verified; no native host cleanup claimed | [Offline package refresh](remaining-checkpoint.md#offline-package-refresh---2026-10-09), [standalone NativeLua](remaining-checkpoint.md#authorized-standalone-nativelua-regressions---2026-10-09) | pass only within stated working-tree scope; clean candidate/native/CI/install/soak/release pending | -| DIAGNOSTIC-X64-02 | Bounded production crash investigation | diagnostic VL | Same uncommitted Release distribution as OFFLINE-CONTRACT2; ordinary SDK; exact package and harness hashes in checkpoint evidence | Windows 11 Pro x64 10.0.26300; CE 7.7.1.10828 with executable hash recorded; loaded CLR patch and target-binary hash not measured | Two memory/named-scan/AOB sessions passed; a third added the exact post-AOB 64-byte non-executable allocation/free probe; a fourth restored the original read-only prelude too; all passed with four optional gates false | Each run restored user state, preserved source installation and reported zero cleanup failures; all recorded host/target PIDs absent afterward | [Authorized bounded investigation](remaining-checkpoint.md#authorized-bounded-ce-investigation---2026-10-09) | bounded passes only; original crash unresolved and full lifecycle not qualified | -| DIAGNOSTIC-X64-03 | Stack observations during bounded crash investigation | instrumented diagnostic VL | Private SDK `2.0.1-mcpstacktrace.1`, MCP `2.0.0-mcpstacktrace.1`, modified source manifests and exact hashes in diagnostic ledger; original published native bridge retained | CE 7.7.1.10828 x64, owned x64 targets; prepared inputs hashed | One approved session passed; 1,874 records, 76 paired dispatches and 79 balanced frame restorations; no recorded refusal, underflow, overflow or incomplete trace; three short AOB waits contained no traced callback reentry | User state restored, source installation unchanged, zero cleanup failures and recorded owned PIDs absent | [Instrumented session](stack-trace-diagnostic.md#approved-instrumented-session) | balanced instrumented baseline only; historical crash and reentry hypothesis unresolved | -| DIAGNOSTIC-LUA-04 | Deterministic nested dispatcher stack restoration | standalone diagnostic VN | SDK archive `325c47b573f8bd39a247f1d0101f110fa36c1696` plus two new tests; private Release identity `2.0.1-mcpreentrytest.1`; production source unchanged; exact hashes in ledger | Windows x64, .NET 10.0.12, staged Lua 5.3 DLL and published native bridge identified by SHA-256 | Both selected nested success/managed-exception cases passed; 2 passed, 0 failed, 0 skipped | Owned test process exited 0 and absent afterward; no timeout or cleanup failure; all 1,485 prepared input hashes unchanged | [Standalone nested-dispatch execution](nested-dispatch-regression.md#approved-standalone-execution) | simulated SDK behavior verified only; no real-CE crash reproduction or production fix | -| DIAGNOSTIC-X64-05 | Ordinary-SDK full lifecycle attempt | incomplete VL | Same uncommitted ordinary Release distribution as OFFLINE-CONTRACT2; 820 prepared hashes matched before/after | CE 7.7.1.10828 x64, two private hosts and owned x64 targets | All six workflow groups completed; first plugin-disable bridge refused because its checklist did not contain exactly one owned row; 0 passed, 1 failed, 0 skipped | User state restored, source unchanged, no cleanup failures, all four recorded owned host/target PIDs absent | [Lifecycle failure](lifecycle-resume.md#approved-execution-result) | bridge failure reproduced; plugin cycles/restarts unqualified; historical stack failure unresolved | -| DIAGNOSTIC-X64-06 | Conditional bridge regression and retained-key lifecycle experiment | standalone VN and incomplete VL | Same ordinary Release distribution as OFFLINE-CONTRACT2; both 859-file manifests matched before/after execution | Windows x64 test module, staged Lua 5.3 DLL, CE 7.7.1.10828 x64 and owned x64 targets | Eight standalone cases passed, then one lifecycle attempt completed all six workflows but refused the first disable with measured count zero; the key-needed source premise was subsequently disproved and that change rolled back | User state restored, source unchanged, no cleanup failures, all recorded owned processes absent | [Conditional execution](lifecycle-key-fix.md#approved-conditional-execution) | eight bridge cases verified; lifecycle still failed; no post-rollback native pass or historical crash resolution | - -The following offline evidence belongs to isolated snapshots, not the primary product or a clean RC. The linked ledgers identify each snapshot, retained logs, test results, source inventory and independent Astra high review. Later snapshots include the earlier repairs, but the earlier focused results remain attributed to the source on which they ran. - -| Evidence ID | Requirements | Level | Candidate | Environment | Outcome | Cleanup | Safe reference | Status | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| OFFLINE-PERFORMANCE-01 | Preparation for 9.01, 9.02 and bounded 9.04 | implementation and focused portable checks | Isolated 856-file snapshot on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, uncommitted beta.2, Release; inventory and module hashes in ledger | Windows x64, managed test doubles; no CE host | Twelve-workload measurement/comparison preparation and pointer range-search retention repair reviewed; 20 final helper cases and 130 earlier unchanged pointer regressions passed | No target or CE state created | [Performance preparation](performance-preparation.md) | preparation verified; native baselines and performance acceptance not run | -| OFFLINE-ADMISSION-01 | Preparation for 9.03 and 9.05 | implementation and focused portable checks | Isolated 861-file snapshot on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, uncommitted beta.2, Release; inventory and module hashes in ledger | Windows x64, managed test doubles; no CE host | Two-gateway observation/comparison preparation reviewed; all 52 selected cases passed | No target or CE state created | [Admission preparation](admission-preparation.md) | preparation verified; native concurrency/UI acceptance not run | -| OFFLINE-NAVIGATION-02 | Bounded sections 3 and 8; preparation for 9.05 and 9.07 | implementation, Release portable and catalog checks | Isolated 867-file follow-up on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, uncommitted beta.2, Release; source/module/TRX hashes in ledger | Windows x64, managed test doubles; NativeLua and LiveQualification excluded | Prepared module/region/thread/symbol/breakpoint navigation, dispatch metadata, thread collector and bounded structure script reviewed; 213 focused, 38 catalog-capture and final 4,234 portable cases passed | No target or CE state created | [Navigation follow-up](navigation-followup.md) | portable scope passed; changed Lua scripts compiled only; primary integration and native acceptance pending | -| OFFLINE-NAVIGATION-PACKAGE-02 | Bounded sections 3 and 11.05 | packaged reflection, AOT smoke and local archive checks | Hash-matched copy of all 867 reviewed files on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, uncommitted beta.2, Release; exact DLL/gateway/ZIP hashes in ledger | Windows x64; two isolated DLL load contexts and gateway with empty registry | Maintained locked publish, exact-DLL golden comparison, AOT smoke, two identical ZIPs and five-file staging passed | No CE host, target or installed deployment changed; local output retained | [Latest navigation package](navigation-package.md#latest-reviewed-follow-up-package) | local package scope passed; no Lua execution, final stable identity, installation or publication evidence | - -The same reviewed navigation source also passed a separate Debug run: - -| Evidence ID | Requirements | Level | Candidate | Environment | Outcome | Cleanup | Safe reference | Status | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | -| OFFLINE-NAVIGATION-DEBUG-02 | Debug counterpart to OFFLINE-NAVIGATION-02; bounded section 11 | locked restore, Debug build and portable checks | Hash-matched isolated copy of all 867 files on `0f722ef7d6b37eee0985073dfe594c6559b31c14`, uncommitted beta.2, Debug; module/log/TRX hashes in ledger | Windows x64; NativeLua and LiveQualification excluded | Build had zero warnings/errors; 4,234 passed, zero failures/skips; exact test names match the reviewed Release run | No CE host or target attachment; all three pending native preparation inventories unchanged | [Debug verification](navigation-followup.md#matching-debug-portable-verification) | portable scope passed; Astra high evidence review scoped clear; final stable and native gates remain open | - -The candidate column must include version, full commit, clean-tree state, configuration, and relevant SHA-256 values. -The environment column must include exact native versions for VL, soak, installation, and release results. -The cleanup column must state whether owned state was released, user state was restored, and the source installation stayed unchanged. -The safe reference may be a CI URL, release URL, or redacted evidence file and must not expose secrets or personal paths. - -## Exact candidate gates - -- [ ] V passed with locked restore, Debug build, and portable tests excluding LiveQualification and NativeLua. -- [ ] V+ passed with both format checks, Release build, `eng/Publish.ps1`, and reviewed intended golden changes. -- [ ] The portable suite passed against both Debug and Release outputs with zero failures and zero skips. -- [ ] VN passed when applicable and recorded the selected real Lua 5.3 x64 DLL identity. -- [ ] AOT passed for the exact candidate gateway executable. -- [ ] The packaged lone plugin DLL matched the reviewed candidate tool, resource, template, and prompt goldens in isolated load contexts. -- [ ] VL passed on the exact candidate package with explicit maintainer authorization and all existing live restrictions. -- [ ] CI build, format, test, and analysis results were reviewed for release-stopping findings. -- [ ] Locked dependencies, high and critical advisory handling, embedded inventory, licenses, and notices were verified. -- [ ] The flat distribution and ZIP inventories, deterministic packaging promises, checksums, versions, and source identity were verified. - -## Contract freeze - -- [ ] Final public names and catalog order are reviewed. -- [ ] Input, result, error, and annotation shapes are reviewed. -- [ ] Reflection-disabled JSON and Native AOT metadata are complete. -- [ ] Gateway `instanceId` routing and stale-instance refusal are verified. -- [ ] Capability requirements reject before covered effects. -- [ ] File and table root defaults and configuration precedence are documented accurately. -- [ ] Tool map, workflows, prompts, resources, READMEs, and golden files agree with the candidate. -- [ ] Beta.2 migration and future compatibility policy are reviewed. - -## Native and recovery sign-off - -- [ ] Repeated enable, disable, and re-enable creates fresh activation identity, token, endpoint, discovery record, and Client state. -- [ ] CE shutdown, target exit and restart, client disconnect, gateway restart, and one-backend withdrawal preserve the other instance. -- [ ] Target transition guards and stale selection epochs prevent work against a replacement process. -- [ ] Owned allocations, patches, symbols, scanners, breakpoints, pauses, speed changes, jobs, and Mono attachment follow their documented cleanup paths. -- [ ] User-owned records, structures, breakpoints, symbols, tables, settings, and unrelated plugins remain intact. -- [ ] Failed cleanup retains honest orphan and recovery information. -- [ ] Cancellation, timeout, lost response, cursor replay, pagination, TTL, bounded buffers, and stop or dispose races have reviewed outcomes. -- [ ] Every advertised domain has a reviewed qualification classification, limitations, and recovery path. - -## Performance and soak sign-off - -- [ ] Three reproducible baseline runs record workload sizes, environment, and measurement method. -- [ ] Candidate p95 results meet the measured-relative thresholds in `acceptance.md`. -- [ ] Busy responses and dispatch concurrency do not starve Cheat Engine's main thread. -- [ ] Large jobs, completions, and live resources remain within configured bounds. -- [ ] The planned two-hour two-instance soak completes its required workload. -- [ ] Final handle and private-byte measurements meet the approved recovery thresholds. -- [ ] No crash, hang, cross-instance effect, stale-target effect, false success, cleanup failure, or leaked owned state remains. - -## Installation, upgrade, and release sign-off - -- [ ] A clean install from the exact candidate ZIP passes on the supported environment. -- [ ] Beta.2 and older folder deployment upgrade paths pass without mixing versions. -- [ ] Disable, uninstall, rollback, and locked-file recovery are reproducible. -- [ ] Paths with spaces, a standard user account, allowed roots, configuration failures, and endpoint conflicts have reviewed outcomes. -- [ ] Every named supported MCP client passes the documented first-use workflow. -- [ ] Stable release notes include support, migration, recovery, limitations, and changes since beta.2. -- [ ] The final stable commit repeats V+, Debug and Release portable tests, and applicable VN. -- [ ] The final Release package repeats AOT, packaged-DLL golden comparison, ZIP, checksum, and source-identity checks. -- [ ] The final Release package repeats clean install, upgrade, rollback, and maintainer-authorized two-instance VL. -- [ ] Retained RC evidence has a reviewed intervening diff and explicit applicability decision. -- [ ] The public stable assets download successfully and match the reviewed hashes and source identity. -- [ ] The shipped plugin discovery, gateway, and documented first-use workflow pass after publication. - -## Final decision - -- [ ] Every P0 requirement has complete reviewed evidence. -- [ ] Every P1 requirement is implemented or has an explicit reviewed deferral and linked follow-up. -- [ ] No release-stopping defect remains unresolved. -- [ ] The final candidate contract, package, support matrix, migration path, soak, and release evidence are approved. -- [ ] Issue #28 may be closed only after the public stable package is verified. diff --git a/docs/qualification/v2.0.0/remaining-checkpoint.md b/docs/qualification/v2.0.0/remaining-checkpoint.md deleted file mode 100644 index a806653..0000000 --- a/docs/qualification/v2.0.0/remaining-checkpoint.md +++ /dev/null @@ -1,469 +0,0 @@ -# Remaining qualification checkpoint - -Date: 2026-10-08. -Status: Reviewed local evidence; native corruption remains a release blocker. No roadmap closure or publication is claimed. - -## Candidate identity - -The tested checkout was clean at `d3984574e0d0757beebd3ea36deb6f9bef7358d1`. -The tested binaries identify as `2.0.0-beta.2+d3984574e0d0757beebd3ea36deb6f9bef7358d1`. -The staged Release plugin SHA-256 is `C87E44C8D68C54A660E1CFDB9208D411253D6AEE5BA9C5DFDBA8B41BF0682A12`. -The staged Release gateway SHA-256 is `F52824F10B0C33775B5B8BB9C8C9B95094A1CDA73AF51DD0B8C87B60C98F1DC8`. -The local Release ZIP SHA-256 is `F08D289243A285C294F386DD85331BEFB82F206602E289F9882191E2584AD37B`. -The ZIP contains five flat deployment files and is 17,717,063 bytes. -The candidate remains beta-versioned and is not an RC or stable release payload. -The d398457 logs identify the test process as `net10.0|x64`, but these receipts do not emit the exact Windows build or .NET runtime patch. -Promotion into tracked release evidence must join those environment fields from the reviewed candidate preflight rather than infer them here. - -## Offline and package evidence - -| Evidence | Result | Safe source | -| --- | --- | --- | -| Locked restore | Passed for the nine-project solution restore. | `artifacts/issue-edits/d398457-restore.log` | -| Debug build | Passed with zero warnings and zero errors in 8.34 seconds. | `artifacts/issue-edits/d398457-Debug-build.log` | -| Release build | Passed with zero warnings and zero errors in 7.58 seconds. | `artifacts/issue-edits/d398457-Release-build.log` | -| Debug portable tests | Passed 4,062 of 4,062 with zero failures and zero skips in 1 minute 41.709 seconds. | `artifacts/issue-edits/d398457-Debug-tests.log` | -| Release portable tests | Passed 4,062 of 4,062 with zero failures and zero skips in 1 minute 48.044 seconds. | `artifacts/issue-edits/d398457-Release-tests.log` | -| NativeLua | Passed 618 of 618 with zero failures and zero skips in 2.845 seconds. | `artifacts/issue-edits/d398457-native-lua.log` | -| Format | Style and whitespace commands completed with empty logs. | `artifacts/issue-edits/d398457-format-style.log` and `d398457-format-whitespace.log` | -| Release packaged DLL | The actual Release DLL passed both isolated reflection comparisons with 193 tools, 43 resources, 15 templates, and 43 prompts, followed by `PLUGIN_BUNDLE_PROBE_OK`. | `artifacts/issue-edits/d398457-publish-release.log` | -| Release Native AOT gateway | Native code generation and the executable MCP checks completed for the recorded Release gateway bytes. | `artifacts/issue-edits/d398457-publish-release.log` | -| Release distribution | The five staged files, embedded identities, ZIP inventory, and checksum verification passed. | `artifacts/issue-edits/d398457-publish-release.log`, `d398457-package.log`, and `d398457-stage.log` | - -The recorded Release distribution contains `CheatEngine.Mcp.dll`, `CheatEngine.Mcp.Gateway.exe`, `LICENSE`, `README.md`, and `THIRD-PARTY-NOTICES.md`. -Debug publication and its actual packaged-DLL, Native AOT, and distribution checks remain pending. -These results do not qualify native workflows, installation, interactive clients, CI, soak, or a stable release. - -## Native lifecycle and workflow result - -Evidence ID `LIFECYCLE-X64-01` is run `20261008T005457Z-838e` against the recorded `d3984574e0d0757beebd3ea36deb6f9bef7358d1` Release plugin and gateway hashes. -The native host was Cheat Engine x64 `7.7.1.10828` with executable SHA-256 `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. -The run used two private hosts, two owned x64 targets, the ModelContextProtocol .NET test client, and protocol `2025-06-18`. -The native summary leaves `TargetSha256` empty, so this checkpoint does not claim a target-binary hash. -The test process reported failure after 19.257 seconds, with the failing test body reporting 18.373 seconds. - -The packaged client checks reached 55 resources, 26 templates, and 43 prompts, but this is not interactive-client qualification. -All four execution gates were disabled for the workflow run. -The bounded memory cases passed integer round trips, partial batch reporting, a 16-byte snapshot, SHA-256 hashing, file-policy refusal, protection restoration, snapshot deletion, and both owned frees. -The independent scanner passed `unknown`, `changed`, `increased`, `decreased`, `reset`, and `between` transitions, then deleted the scanner and freed its owned region. -The bounded AOB cases found the exact pattern in 25 milliseconds, the wildcard pattern in 24 milliseconds, and the `int32` value pattern in 25 milliseconds, each as one exact unique target-verified result. - -The run then failed before the control-flow-graph fixture allocation completed. -`memory_allocate` returned `host_refused` with message `The Lua stack does not contain the inputs required by the protected operation.`. -The recorded operation was `Dispatcher.Invoke`, `hostEffect` was `unknown`, and `retryable` was `false`. -The CFG cleanup receipt records `freed=false` and zero cleanup failures because no owned CFG allocation was obtained. -The run stopped both owned hosts and targets, observed enabled and disabled lifecycle indicators for both hosts, restored user state, preserved the source installation, and reported no cleanup failures. -The summary correctly records `passed=false`. -This failed run is evidence only for the completed memory, scanner, AOB, and cleanup cases, and it is not lifecycle-wide or catalog-wide qualification. - -## x64 performance evidence - -The performance command passed one aggregate test in 1 minute 50.909 seconds. -Each run used two private Cheat Engine instances and measured full stdio request time, response parsing, and value assertion after five unrecorded warm-up pairs. -Each run recorded 100 `memory_read int32` calls and 30 `memory_read_batch` calls with 16 `int32` requests per batch. -The p95 calculation used nearest rank, and candidate limits used the maximum p95 across the three published beta.2 baseline runs. - -| Run | Package | Short p50 / p95 / max in ms | Batch p50 / p95 / max in ms | Outcome | -| --- | --- | --- | --- | --- | -| `20261008T005646Z-8411` | Published beta.2 baseline 1 | 9.2240 / 10.5650 / 62.5282 | 9.0115 / 10.4505 / 10.7427 | Passed; cleanup restored and source unchanged. | -| `20261008T005705Z-1b65` | Published beta.2 baseline 2 | 9.7902 / 10.9172 / 61.0560 | 9.3845 / 10.7567 / 11.0492 | Passed; cleanup restored and source unchanged. | -| `20261008T005723Z-469f` | Published beta.2 baseline 3 | 9.7945 / 11.8246 / 61.5980 | 7.2177 / 9.7169 / 10.0798 | Passed; cleanup restored and source unchanged. | -| `20261008T005741Z-c9b1` | `d398457` candidate 1 | 9.5969 / 11.8029 / 45.0871 | 9.5876 / 10.4264 / 10.7751 | Both p95 decisions passed; cleanup restored and source unchanged. | -| `20261008T005800Z-d335` | `d398457` candidate 2 | 9.1233 / 11.7610 / 44.2625 | 8.3695 / 9.4961 / 9.5099 | Both p95 decisions passed; cleanup restored and source unchanged. | -| `20261008T005819Z-99f0` | `d398457` candidate 3 | 6.9373 / 8.3891 / 34.2782 | 6.2625 / 7.4094 / 7.8098 | Both p95 decisions passed; cleanup restored and source unchanged. | - -The selected baseline short p95 was 11.8246 milliseconds, producing a candidate limit of 111.8246 milliseconds. -The selected baseline batch p95 was 10.7567 milliseconds, producing a candidate limit of 16.1351 milliseconds. -Every candidate run passed both recorded p95 limits. -Every baseline and candidate summary records `passed=true`, `userStateRestored=true`, `sourceInstallationUnchanged=true`, and zero cleanup failures. -The published beta.2 baseline plugin SHA-256 is `5699ECA2ED06461846A977802DF3E4FA7BF52159F50AA1446C4819908A7692AE`. -The published beta.2 baseline gateway SHA-256 is `2E0E7F8512D8A8AAC0197E982C7660BFA4336EBDB8981543817B709381C83885`. -These measurements qualify only the two recorded x64 memory workloads. -They do not qualify regions, scans, pointers, symbols, structures, dissection, instance discovery, busy responses, jobs, completions, live resources, blocking-native calls, UI responsiveness, resource recovery thresholds, or the two-hour soak. - -## Diagnostic candidate 207d6fd - -The diagnostic candidate is commit `207d6fdb62c4874185ba843f00add85da1438d1f` with product version `2.0.0-beta.2+207d6fdb62c4874185ba843f00add85da1438d1f`. -The staged Release plugin SHA-256 is `8588CBAC6665CAB094AC40DA0A6B9257835F7B93B5931E6DB1E2EDB9E45F1E26`. -The staged Release gateway SHA-256 is `BD9D4DFAA3A535A0AA388AA570A19E3A2B6D3B17BE09E74888716A28DBCA10F0`. -The local Release ZIP SHA-256 is `AC1E71B09F101BD9A347A7045F52A9795D1F076C1E4D617A305E9E6E847B513E`, and the ZIP is 17,726,054 bytes. -The Release build passed with zero warnings and zero errors in 7.20 seconds. -The Release portable suite passed 4,066 of 4,066 tests with zero failures and zero skips in 1 minute 50.370 seconds. -The actual Release DLL passed two isolated reflection comparisons with 193 tools, 43 resources, 15 templates, and 43 prompts, followed by `PLUGIN_BUNDLE_PROBE_OK`. -The Release Native AOT gateway build and the five-file flat ZIP inventory, checksum, and package verification passed. -These offline and package results are recorded in `artifacts/issue-edits/207d6fd-build.log`, `207d6fd-tests.log`, `207d6fd-publish-release.log`, `207d6fd-package.log`, and `207d6fd-stage.log`. - -Evidence ID `DIAGNOSTIC-X64-01` is native run `20261008T010811Z-ded4` against these exact Release plugin and gateway hashes. -The native host was Cheat Engine x64 `7.7.1.10828` with executable SHA-256 `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. -The run again used two private hosts, two owned x64 targets, the ModelContextProtocol .NET test client, and protocol `2025-06-18`. -The summary leaves `TargetSha256` empty, so this checkpoint does not claim a target-binary hash. -The memory cases and their cleanup passed before the crash, including protection restoration, snapshot deletion, both owned frees, and zero memory cleanup failures. -The scanner completed the `unknown`, `changed`, `increased`, `decreased`, `reset`, and `between` transitions, then deleted the scanner and freed its owned region with zero scanner cleanup failures. - -Host A then crashed during `AobTools.FindValue` with fatal error `0xC0000005`. -The recorded fatal path begins `LuaApi.lua_settop`, `LuaState.SetTop`, `LuaFrame.Dispose`, `MainThreadDispatcher.Dispatch`, and the client dispatch path before reaching `AobTools.FindValue`. -The intended diagnostic 3005 was not reached, so this candidate did not establish a root cause or a fix. -The test body failed after 20.882 seconds, and the assembly failed after 21.596 seconds. -No successful AOB result is claimed for this run. - -The summary records `userStateRestored=true`, `sourceInstallationUnchanged=true`, and `passed=false`. -AOB cleanup attempted to free its owned region and recorded one failure; successful release was not confirmed before owned-target shutdown. -The sole `cleanupFailures` entry says host A lacks the expected enabled and disabled MCP menu status indicators because the host crashed. -Host B recorded both MCP and SDK enabled and disabled lifecycle indicators, while host A recorded neither complete indicator pair. -Both host-stop receipts are present. -The parent post-run CIM inventory found no owned Cheat Engine or LiveTarget processes left running. -That process inventory is separate from the failed lifecycle-evidence cleanup check and does not turn the run into a cleanup pass. -This production-package failure remains unresolved. - -## Private dispatcher instrumentation - -The bounded diagnostic package uses MCP product base `4c0698981f50ee4867bc1efe08a62867fd5c89c3` and the latest harness commit `09d76d7defec28756e621e167731f47efaff2f96`. -The product base passed 4,074 of 4,074 Release portable tests with zero failures and zero skips. -The plugin privately pins `CheatEngine.SDK` version `2.0.1-mcpdiagnostic.1` from SDK base `325c47b573f8bd39a247f1d0101f110fa36c1696` with reviewed local instrumentation. -The private SDK package SHA-256 is `1BFC46AA5377464351B2F50967C0BEC23D7DB90A25B7AB7EDF2EE1EC1CFC9B2C`. -The SDK tracked patch SHA-256 is `682DA09724C37C9AC401951A76EFD63B27F675C7C81FFF667F4F063547F79FD7`, and the new trace-source SHA-256 is `4177B62FCD29AA5C465B45DC0DF19EFE65DF98810F75F70B77164AA2983F5773`. -The exact embedded SDK assembly hashes are recorded in `artifacts/issue-edits/dispatch-diagnostic-provenance.json`. -Strict SDK build and pack completed with zero errors, and the focused SDK suites passed 11 `MainThreadTests` plus 53 `AobBoundedScanTests` with zero failures or skips. -The private MCP package passed both isolated packaged-DLL comparisons, `PLUGIN_BUNDLE_PROBE_OK`, Native AOT publication, and five-file ZIP verification. -Its plugin SHA-256 is `3906E707D8C11947664E384FCA09E43CB1EA2451368D48E87D78C08D5485314A`, gateway SHA-256 is `A04CF2303D77BF7B2F560F1ABFC876C00D57C990330565599BE2981543D184C8`, and ZIP SHA-256 is `0F5A84AD54421AC06CF869F2ADBEE6E7B1C0322E8F63ADAA22613CCEC6BDAF8E`. - -The first diagnostic attempt, run `20261008T014302Z-328d`, failed before either host or target launched because the staging predicate did not admit the explicitly selected diagnostic configuration. -That failed attempt records `passed=false`, `userStateRestored=true`, `sourceInstallationUnchanged=true`, and an empty `cleanupFailures` array. -The shared `RequiresCapabilityConfiguration` predicate was corrected in harness commit `09d76d7defec28756e621e167731f47efaff2f96`; 83 focused portable tests passed, and the bounded correction received independent Astra review. - -Three subsequent fresh x64 diagnostic cases passed against the same private instrumented package. - -| Run | Case | Completed evidence | Cleanup | -| --- | --- | --- | --- | -| `20261008T014451Z-4fb2` | `AobOnly` | Two AOB tool calls completed three exact, unique, bounded, target-verified native scans. | State restored, source unchanged, both host lifecycle pairs observed, cleanup failures empty. | -| `20261008T014508Z-1e67` | `NamedScanThenAob` | The six named-scan transitions completed before the same two AOB calls and three native scans. | State restored, source unchanged, both host lifecycle pairs observed, cleanup failures empty. | -| `20261008T014525Z-4e08` | `MemoryNamedScanThenAob` | The bounded memory cases, six named-scan transitions, two AOB calls, and three native scans completed. | State restored, source unchanged, both host lifecycle pairs observed, cleanup failures empty. | - -The authoritative summaries and per-host diagnostic logs are under `C:\Users\Shadow\AppData\Local\Packages\OpenAI.Codex_2p2nqsd0c76g0\LocalCache\Local\CheatEngine.Mcp.LiveQualification\runs\`. -The parent post-run CIM inventory found no owned Cheat Engine or LiveTarget processes left after these attempts. -The trace field named `top` records the frame's saved top, not a contemporaneous Lua stack top measurement. -The private `.1` package also used the SDK tag's checked-in native Lua bridge, SHA-256 `889DC4C231D182F9B7BAA9E29880555AAD949F42023DDE232FE327542C3C5387`, whereas the published SDK 2.0.0 package contains bridge `B008C8D8C136187F241542E6223DC0831999D8300DC2C4C01E1CF49F6FBA7698`. -Both managed instrumentation and native bridge bytes therefore changed in these three runs. The added logging can change timing, and different bridge hashes alone do not establish a functional difference or ABI defect. -These passing diagnostic cases neither isolate the effect of instrumentation nor identify the root cause or prove a production fix. -Each of the three passing summaries explicitly records `stableQualification=false`. -A controlled follow-up restored the exact published bridge while retaining the managed trace patch, as recorded below. - -## Published-bridge diagnostic follow-up - -Private SDK `2.0.1-mcpdiagnostic.2` retains the managed trace patch and embeds the exact published `B008C8D8C136187F241542E6223DC0831999D8300DC2C4C01E1CF49F6FBA7698` native bridge. -The final bundled plugin's seven SDK assemblies and native bridge were compared byte-for-byte with the private package. The private package SHA-256 is `E28B6AA10006698BCBBE4672814C5DD519E79414D4A38AA57AD62270F57F034C`. -The plugin SHA-256 is `D75E915607687E97CB332E0E3C17EA2017A7876641B72E032A8C7C834A982E62`, gateway SHA-256 is `47CD2721DBD8E4FDB10C09F12966C77484BE42FDCDC1B77921AF6FFB8E9370D0`, and ZIP SHA-256 is `A661362E1B17990C5BE4CB54653A9C727FC754E33873D19660D2F72C2BBCFC8A`. -Both isolated packaged-DLL checks, the Native AOT gateway checks, and ZIP staging passed. After replacing stale private test-output bridge copies, the 11 dispatcher and 53 bounded-AOB standalone tests passed against verified `B008` files. -Product source remains `4c0698981f50ee4867bc1efe08a62867fd5c89c3`; harness source is `0f722ef7d6b37eee0985073dfe594c6559b31c14`, whose exact-case admission change passed 93 portable harness tests and independent Astra review. - -Run `20261008T015810Z-9f16` executed only `MemoryNamedScanThenAob` and passed. Both live owned CE processes were separately observed loading bridge bytes with the exact published hash. -The summary records three native scans, `stableQualification=false`, restored user state, unchanged source installation, and no cleanup failures. No owned CE or target processes remained afterward. - -Run `20261008T015918Z-ee72` restored the original lifecycle ordering using the same private `.2` package. All six workflow groups completed: memory, named scanner, AOB, control-flow graph, modules, and structures. -It then failed on the first plugin-disable cycle. The driver reported `expected exactly one owned plugin row`, and the harness timed out waiting for its response. -This run records `passed=false`, restored user state, unchanged source installation, and no cleanup failures. Both owned hosts and targets stopped; both hosts supplied enabled/disabled lifecycle indicators. -The subsequent offline harness correction initializes the settings form before reloading its contents, retains exact owned-row validation, and returns bounded error receipts immediately. Its initial build and six standalone bridge tests passed before live work was stopped; the correction has not been verified in CE. -Offline Astra review then identified byte truncation that could split a UTF-8 character in an error receipt. The formatter now converts error details to bounded printable ASCII before truncation. The added multibyte regression is compiled but has not been executed under the user's stop constraint. - -The initial attempt to select the full lifecycle was refused before host launch because the private PowerShell runner retained an empty diagnostic-case variable. The runner was corrected to remove an unset variable; this admission refusal is separate from the native run above. -These results do not resolve the earlier uninstrumented production corruption. Per-event instrumentation still changes timing, and no production fix is claimed. -At the user's request, live CE, native diagnostic, and debugger work is stopped. Further work is restricted to offline review, edits, and ordinary build checks; no additional native validation is implied by the prepared harness changes. - -## Pending work and candidate boundary - -The diagnostic candidate `207d6fd` has one failed native x64 run and is not a root-cause fix. -The reviewed bounded diagnostic mechanism has now run, but production lifecycle x64 remains blocked on mechanism-level diagnosis and an uninstrumented production candidate rerun through the original ordering. -Lifecycle x86, the broader workflow matrix, two-hour soak, installation, upgrade, rollback, named interactive clients, CI and analysis, extended compiler lifetime cases, and managed injection remain pending. -Debug publication remains pending. -The final version change, exact stable package rebuild, final native and installation repetition, tag, draft upload, publication, public download, and shipped first-use smoke remain pending. -Only the narrowly completed subcases above may be checked off. The broader workflow, lifecycle, and release requirements remain open. - -## Follow-up preparation - -The lifecycle harness preserves the original workflow failure alongside cleanup faults and admits success only after gateway cleanup. -Cleanup receipt flags say attempted rather than claiming successful release from an allocation-created flag. -Focused portable tests enforce the fixed soak counts and diagnostic scenario admission. -The isolated dispatcher diagnostic passed its three `.1` cases and the single controlled `.2` case. The `.2` full-order run reached all six workflow groups but failed its first lifecycle transition. Root-cause proof, production fix, production-package qualification, and verification of the latest lifecycle harness correction remain open. - -## Offline goal checkpoint - -The all-issues goal is active for #28, #31 and #33. -This checkpoint describes local working-tree changes on `0f722ef7d6b37eee0985073dfe594c6559b31c14`; they are not a clean release candidate or a published package. -Live CE, NativeLua, native diagnostic, debugger, extended compiler and injection execution remain stopped. - -An error-contract audit found that internal tool errors with array or scalar context logged an ID without returning it. -The correction preserves that context under `details.value` beside `errorId`, replaces non-string reserved IDs without duplicate properties, and preserves all other envelope fields. -The 22 focused correlation cases passed; the six host-effect values now have direct and routed MCP coverage, including exact envelope and logged-ID equality. -The requirement-to-test mapping is in [contract-checkpoint.md](contract-checkpoint.md). - -The configuration reference now lists the C# compiler gate and explains private native-dispatch log events accurately. -The support-report checklist covers versions, operation/error ID, gates and redacted logs without requesting tokens or discovery contents. -Upgrade guidance covers a matching plugin/gateway pair, old plugin-entry replacement, locked files, partial upgrades, rollback and uninstall while retaining user data. -The reviewed release-note draft includes the unresolved production crash and does not claim stable publication. -Eight #28 documentation, harness-control and unsupported-disposition criteria are complete; broader native and release gates remain open. - -Validation caught an oversized knowledge page and the intended resource-size snapshot changes. -The page was shortened within its existing budget; only the three changed document sizes changed in the two resource snapshots. -It also caught a stale Costura bundle after an ordinary incremental dependency update: normal outputs were current but the single plugin DLL still embedded the old Resources assembly. -`McpTrackBundledDependencies` now supplies copy-local implementation files as compiler freshness inputs, allowing Fody to weave fresh plugin IL when a dependency implementation or embedded document changes. -The controlled offline check compared all five embedded product assemblies byte-for-byte by SHA-256 against current outputs after a resource-only edit, a no-op build and restoration. -All phases passed, the edited source was restored exactly, and the no-op build preserved the plugin output timestamp. -The report is `artifacts/issue-edits/incremental-bundle-verification.json`. -After the build-rule correction, all five actual single-DLL bundle tests passed again in both Release and Debug, including isolated catalog equality and deliberate mismatch rejection. -Those focused results are in `artifacts/issue-edits/goal-final-{debug,release}-bundle-tests.log`. - -Nonincremental Debug and Release builds succeeded without warnings or errors, and both full portable suites passed 4,097 tests with zero failures or skips after the correlation and documentation corrections. -Logs are `artifacts/issue-edits/goal-verified-{debug,release}-{build,tests}.log`. -Style and whitespace verification and `git diff --check` passed. -Independent Astra source review cleared the correlation, documentation and incremental-build changes after all reported findings were addressed. -The new native multibyte receipt regression remains compiled but unexecuted, and the earlier production Lua corruption remains unresolved. -No push, tag, release or additional native run was performed. - -## Offline contract and startup follow-up - 2026-10-09 - -The previous goal turn made concrete progress with source fixes, portable evidence and reviewed issue updates. -This continuation remains a local working tree on `0f722ef7d6b37eee0985073dfe594c6559b31c14`; no clean candidate, CI publication or native rerun is implied. - -The 12 new exact-value cases passed through actual .NET MCP serialization and loopback gateway forwarding with controlled target/backend doubles. -They cover signed and unsigned 64-bit extrema, values above JavaScript's exact integer range, maximum hexadecimal addresses and pointers, negative offsets, and distinct zero/empty/unavailable values. -The gate-before-effect additions passed with 42 assembly/table cases, 177 record cases and 10 process-auto-attach cases. -The XML table fixtures verify that they were actually inspected; opaque fixtures separately check all three documented exposure requirements. -Mixed record batches prove a later gated record cannot leave earlier records created or deleted, and process tests prove no open script was reached before refusal. - -The 12 lifecycle tests passed, including two new real-module checks. -An occupied loopback port refuses startup, leaves no discovery record, and can be rebound after the occupying listener stops. -A file in place of the log directory does not stop the actual authenticated backend; logging stays best-effort, the file is preserved, and disable withdraws discovery and closes the listener. -These cases are portable backend/module evidence, not exact-package installation in CE. - -Independent Astra review identified and cleared eight effect-metadata corrections: two destructive flags (`record_create`, `table_save`), a fixed AA requirement (`record_set_script`), and five `may_prompt` classes (patch release, bulk resource release, record activation, deletion and clear). -The script-editing runtime gate also runs before dispatch. -`record_clear` now includes the standard capability recovery hint; its new NativeLua assertion is compiled but remains unexecuted. -Tool descriptions, the tool map, safety reference, AA guidance and cleanup workflow explain the same script-dialog behavior. -The catalog, resource and summary golden changes are intentional and reviewed. - -The source/evidence crosswalk is in [contract-checkpoint.md](contract-checkpoint.md). -Full-domain nullable-result and metadata audits remain incomplete; these focused additions do not close roadmap section 3, the broader installation gate, #31 or #33. -The real-CE failure, final candidate, native/compiler lifetime matrix, soak, installation/client qualification and release dependencies recorded above remain open. - -Final validation for this continuation: Debug and Release builds both succeeded with zero warnings and errors. -Each full portable suite passed 4,126 tests with zero failures and skips, excluding `LiveQualification` and `NativeLua`. -The logs are `artifacts/issue-edits/goal-contract-{debug,release}-{build,tests}.log`. -An initial Release run found only an AA guide size overrun (24 bytes); the new paragraph was shortened within the unchanged budget, its resource snapshots were recaptured, and the complete Release suite then passed. -Only eight intended tools changed in the two tool catalogs and summary; the six resource-size changes match edited knowledge documents, including the three previous checkpoint changes. -Style, whitespace verification and `git diff --check` passed. -Final Astra re-review found no remaining actionable findings in this scoped patch and its evidence after correcting the upstream parameter-sentinel interpretation; the remaining collector ambiguity is recorded in the contract checkpoint. -The pinned read-only collector source copies and blob identities are retained under `artifacts/issue-edits/collector-ec45d5f/source-manifest.json`. -Five completed offline subcases were appended to each of #28 and #33 and verified by exact body readback; all broader checkboxes and all three issues remain open. -No push, tag, release or native execution occurred. - -## Full offline contract audit - 2026-10-09 - -The next continuation completed the remaining source review across all 193 backend tools and the gateway's discovery/result forwarding. Coverage and native-source limitations are recorded in [offline-contract-audit.md](offline-contract-audit.md). -It corrected six more tool-effect annotations, six unavailable-result mappings, the .NET collector parameter descriptions and the scan-value readability description. -The slow module-exports resource projection was removed while its explicit tool and the resource dispatch guard remain intact; current snapshots contain 43 backend resources, 14 templates and 43 prompts. - -The speedhack follow-up preserves restore tracking when a post-change symbol observation is unavailable, probes before releasing a prior resource, and reports prior restore effects honestly if a later operation refuses. -Failed cleanup details contain the resource ID and release outcome instead of invented observed values. -An internal marker preserves the kernel result's object shape when every probe is unavailable; the public typed result omits that marker and all unavailable fields. - -Debug and Release builds succeeded with zero warnings/errors; each full portable suite passed 4,135 tests with no failures or skips. -Build/test logs are `artifacts/issue-edits/goal-contract2-{debug,release}-{build,tests}.log`. Style, whitespace verification and `git diff --check` passed. -An initial catalog run correctly refused the slow exports-resource projection, and the first full Release run found a stale generic poll-annotation fixture. Both were corrected before the passing full runs; the failed logs are retained separately. -The focused speedhack suite passed 20 tests. NativeLua regression edits were compiled but not executed, and `LiveQualification` and `NativeLua` remain excluded from portable runs. -Independent Astra final review found no remaining actionable findings in the scoped source changes, snapshots, validation logs or issue draft. It verified all 27 changed tool entries per catalog, all 11 rendered document sizes and the single removed resource template; this does not qualify native behavior or a final candidate. - -The reviewed changes intentionally affect beta compatibility: unavailable status/job fields can be omitted and the exports URI is removed. They are documented explicitly in the release notes, acceptance and support policy; requirement 2.07 is reopened pending their final contract disposition rather than claiming additive compatibility. -Six completed offline subcases were appended to each of #28 and #33 and verified by exact body readback. The compatibility-policy checkbox in #28 was reopened to match requirement 2.07, and its earlier completion paragraph was qualified as historical. #31 was reviewed and left unchanged; all three issues remain open. -The original real-CE failure, native/compiler qualification, soak, installation/client checks, final candidate and release gates remain open. No commit, push, tag, release or native execution occurred in this continuation. - -## Offline package refresh - 2026-10-09 - -The reviewed contract changes were rebuilt through `eng/Publish.ps1` in both Debug and Release, with locked restore, into new local distribution directories. Each actual single plugin DLL passed cold loading and full catalog equality in two isolated contexts: 193 tools, 43 resources, 14 templates and 43 prompts. The bundle check resolves the embedded bridge export without invoking Lua or enabling a CE plugin. - -Both Native AOT gateways passed the maintained no-instance startup smoke. The exact published executable bytes also passed `GatewayExecutable_StdioWithoutInstances_MatchesGoldenSnapshots` in each configuration: one test passed with zero failures/skips each. This compares initialization, tools, resources/templates and prompts, and reads embedded knowledge and a rendered prompt against an empty private instance registry. The published executable temporarily replaced only its corresponding test-output gateway; hashes verified both the executed bytes and exact restoration afterward. - -`eng/Release.ps1` verified each five-file ZIP, embedded dependency inventory, notices, checksums and matching plugin/gateway product versions. `eng/Stage-QualificationPackage.ps1` verified and extracted each archive to a new path containing spaces; every staged file hash matches its distribution file. Independent packaging into a second empty directory produced the same ZIP hash in each configuration. This proves same-input archive reproducibility, not independent compiler/build reproducibility or installation in CE. - -| Configuration | Plugin SHA-256 | AOT gateway SHA-256 | ZIP SHA-256 | ZIP bytes | -| --- | --- | --- | --- | ---: | -| Release | `1620DD5978C4996BA80DEB5B81403EB3B695B3D029C1D1D533A3F39254D5A97F` | `ADB97797600614CEF21C7F5E2352F95E9102A3D4DFCE48DA4F5B19335E65CA88` | `E2F2EDC8CDA3A5EB4C152B642F35A9C06762CBF7EF9100F2C4D5671BD1F9DB0E` | 17,752,137 | -| Debug | `E6E00295DB20F07E2C413019368B549565E0AE7F112C6E112FA90CC431F8FBA7` | `5509B7209D29C35CDA3A5710F20BC452FBB595E3CC532093F3C278FF854D22F0` | `AF62B5E0AA7309984B9107E72906AE0DE3766CE0A9A0C8C59EA638B268B94918` | 26,031,849 | - -The embedded product identity is `2.0.0-beta.2+0f722ef7d6b37eee0985073dfe594c6559b31c14`, but these bytes include the uncommitted working-tree changes. They are not the published beta.2, a clean candidate, or a stable release. The local package manifest records 527 product-source file hashes and the retained product diff hash to make this boundary explicit. - -Evidence is under `artifacts/issue-edits/`: `goal-contract2-publish-{debug,release}.log`, `goal-contract2-aot-{debug,release}-golden.log`, `goal-contract2-aot-golden-receipts.json`, `goal-contract2-package-{debug,release}.log`, the corresponding `-repro.log` files, `goal-contract2-stage-{debug,release}.log`, and `goal-contract2-package-manifest.json`. - -Requirement 2.09 is corrected from pass to partial: defining the soak duration does not supply its missing two-hour execution result. No broader qualification checkbox is completed by this offline package refresh. At this package checkpoint, live CE, NativeLua, native diagnostic, debugger and extended compiler/injection work remained stopped; the later standalone-only authorization is recorded below. No commit, push, tag, upload or release occurred. - -## Authorized standalone NativeLua regressions - 2026-10-09 - -The user subsequently authorized the prepared standalone NativeLua regression suite in Release mode, explicitly without launching CE or attaching to a target. The earlier compiled-only and native-stop statements describe their historical checkpoints; this narrow permission now allows standalone NativeLua regression checks. It does not resume live CE, native diagnostic, debugger, extended compiler or injection qualification. - -An initial run lacked `CHEATENGINE_MCP_LUA53_PATH` and failed the explicit runtime prerequisite. The next run used `C:\Program Files\Cheat Engine\lua53-64.dll`, SHA-256 `C95DCDFA0F60F97B43D970D77FD1BB907AF4DE04B500A3C89A99600B20B35BD2`, set only for the command process and restored afterward. It passed 625 of 626 cases and exposed an incorrect test expectation: restoring an already-hooked speedhack fixture must make no new hook attempt. - -`SpeedhackV2_SymbolProbeFailsAfterRestoringSpeed_ReturnsAnUnknownObservation` now verifies one speed-setting call, zero hook attempts, and both requested and target speed equal to 1 while the unavailable post-change observation remains omitted. Only the assertion and its comment changed; product source and the packaged bytes above are unchanged. The Release test rebuild passed with zero warnings/errors, and the complete authorized suite then passed **626/626**, zero failures/skips, in 3.152 seconds. - -| Prepared behavior now executed | Test evidence within the passing suite | -| --- | --- | -| Unknown memory/kernel status and detached scanner ID | `AddressExtras_StubbedCheatEngine_ReportsSystemModulesAndRttiClasses`, `KernelStatus_ReportsBooleanStatesAndOmitsUnavailableOrInvalidAnswers`, `ScanV2_MainScannerWithoutAPositiveProcessId_IsNotAttached` | -| Missing module-preference list differs from a valid empty list | `ModuleSymbolGetModulePreference_Stubbed_CopiesAtMost1024NamesAndRefusesAMissingHostList` | -| Unavailable speedhack observations preserve mutation/restore reporting | `SpeedhackV2_SymbolProbeUnavailable_RefusesStateAndChangesBeforeAnyMutation`, `SpeedhackV2_SymbolProbeFailsAfterChangingSpeed_TracksTheRestoreAndReturnsAnUnknownObservation`, `SpeedhackV2_SymbolProbeFailsAfterRestoringSpeed_ReturnsAnUnknownObservation` | -| Record-clear refusal supplies the capability recovery hint | `RecordClear_ActiveAutoAssemblerWithoutGate_LeavesTheAddressListUntouched` | -| Bounded lifecycle error text remains printable ASCII | `LivePluginBridge_LongMultibyteError_RemainsBoundedAscii` | - -Command: `dotnet test --solution CheatEngine.Mcp.slnx -c Release --no-restore --no-build --filter-trait Category=NativeLua --filter-not-trait Category=LiveQualification --fail-skips on`, with the explicit process-local Lua DLL path above. -Logs/receipts: `artifacts/issue-edits/goal-contract2-native-fix-build.log`, `goal-contract2-native-lua-release.log`, `goal-contract2-native-lua-runtime.json`; the initial `-missing-path.log` and `-stale-assertion.log` failures are retained separately. - -These checks execute fixed Lua against CE-shaped stubs in a standalone runtime. They do not demonstrate a fix for the production dispatch crash or qualify real host lifecycle, collectors, debugger/injection/compiler workflows, installation, soak, or a clean final package. All three issues remain open. - -Changed-test style and whitespace verification and `git diff --check` passed. Independent Astra final review verified the package/source/hash evidence, the corrected assertion, the 626-pass result and the updated documentation/issue draft, with no remaining actionable findings in this scope. It explicitly retained the real-CE and final-candidate limits. -Five completed subcases were appended to both #28 and #33 and verified by exact body readback and OPEN state. #31 was reread and remains unchanged with its compiler-lifetime/injection and final contract/CI requirements open. The current GitHub issue list still contains exactly #28, #31 and #33, and the all-issues goal remains active. - -## Authorized bounded CE investigation - 2026-10-09 - -After reviewing [the concrete proposal](next-live-investigation.md), the user explicitly authorized bounded memory/scanner/AOB investigation in private CE copies and owned x64 targets, with all four optional capability gates disabled. This is separate from the earlier standalone Lua permission. It does not authorize the full lifecycle/workflow matrix, debugger, extended compiler/injection, kernel/DBVM, soak, or publication. - -The existing `MemoryNamedScanThenAob` diagnostic case ran in two fresh sessions against the unchanged ordinary-SDK Release distribution from the offline package refresh. Both private plugin copies and the gateway in each run match the recorded package hashes; the unfinished private diagnostic SDK/MCP clones were not used. - -| Run ID | Case | Reported total test duration | Result | -| --- | --- | ---: | --- | -| `20261009T101356Z-aa03` | `MemoryNamedScanThenAob` | 18.922 seconds | Passed; all gates false; user state restored, source installation unchanged, zero cleanup failures. | -| `20261009T101855Z-5bcc` | Same case, fresh session | 18.323 seconds | Passed with the same gate and cleanup results. | - -Each case exercised the existing bounded memory and named-scanner workflows followed by two AOB tool calls containing three native scans, then checked the original scalar sentinel in both targets. Every recorded owned host/target PID was absent in the post-run process check. Both host logs contain enabled and disabled lifecycle indicators for this single activation; repeated plugin cycles were not exercised. - -The immediately preceding preflight recorded Windows 11 Pro x64 `10.0.26300`, CE `7.7.1.10828`, and CE executable SHA-256 `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F`. An installed-runtime inventory was retained; it is not a measurement of the CLR patch loaded by CE. The native summary still contains no target-binary hash, so these results do not supply that missing identity fact. - -Evidence: `artifacts/issue-edits/goal-live-resume-preflight.json`, `goal-live-resume-runtimes.log`, `goal-live-resume-memory-scan-aob.log`, `goal-live-resume-memory-scan-aob-repeat.log`, and `goal-live-resume-evidence.json`. The evidence extract retains source summary/receipt hashes, exact package identities, workload/gate results, cleanup fields and the recorded-PID absence check. Raw private run directories remain outside the repository. - -These passes do not establish a root cause or production fix. The original `d398457` failure occurred at the next CFG fixture allocation after AOB; the later `207d6fd` failure occurred inside AOB value scanning. The reduced diagnostic case does not reproduce the entire original lifecycle ordering, and the production release blocker remains open. - -### Exact post-AOB allocation probe - -Independent Astra source review confirmed that the final scalar reads exercise the common Lua dispatcher/frame, but not the target allocation and ownership operation that failed in `d398457`. The diagnostic harness now allocates 64 non-executable bytes immediately after AOB cleanup and frees the named allocation in `finally` after confirmed acquisition. It records both results and preserves original and cleanup exceptions. No CFG writes, graph analysis, product changes or additional capability gates were introduced. Astra reviewed the probe before execution and found no actionable issues in that scope. - -Run `20261009T102831Z-f606` passed the augmented `MemoryNamedScanThenAob` case: **1 passed, 0 failed, 0 skipped**, total 18.317 seconds. The allocation returned size 64 and `executable=false`; release returned `kind=released`, `hostEffect=completed`, `isComplete=true`, and no manual recovery requirement. All four gates remained false. Memory/scanner/AOB/probe cleanup reported zero failures; both private hosts and targets stopped, the user state was restored and the source installation remained unchanged. All four recorded PIDs were absent afterward. - -The same plugin and gateway hashes from the package refresh were used. Evidence: `artifacts/issue-edits/goal-live-post-aob-allocation.log` and `goal-live-post-aob-evidence.json`; the latter records the harness source, summary and receipt hashes. Release build and scoped whitespace verification passed after formatting the new receipt objects; the initial formatting diagnostics remain in separate logs. This additional pass covers the missing post-AOB allocation operation but still does not identify or fix the historical crash or qualify the full original lifecycle sequence. - -### Original read-only prelude restored - -Source comparison showed that both historical failed lifecycle runs also called `VerifyBothAsync` and `LiveResourceQualification.VerifyAsync` after attaching the targets and before the workflow prefix. The separate `ResourcePreludeMemoryNamedScanThenAob` case restores those calls before the bounded memory/scanner/AOB and post-AOB allocation/free sequence. The prelude reads runtime/gate identity and scalar sentinels in each owned target, lists static catalogs, reads documentation and runtime resources, renders a prompt and checks instance completions. It does not execute the rendered debugger workflow. Runtime resource observation includes the fixed-Lua ledger snapshot; it does not create state or release resources. Astra reviewed this path and the four-file harness change before execution with no actionable findings. - -Run `20261009T103236Z-b6c6` passed: **1 passed, 0 failed, 0 skipped**, total 18.519 seconds. The prelude completed with 55 resources, 25 templates and 43 prompts. The original exports template was removed by the earlier reviewed contract change; the historical prelude only listed it and never invoked native export parsing. The 64-byte non-executable post-AOB allocation and completed release succeeded. All four gates were false; workflow and probe cleanup reported zero failures, user state was restored, source installation was unchanged, and every recorded owned host/target PID was absent afterward. - -Release build and scoped style/whitespace verification passed. The focused `LiveQualificationOptInTests` suite passed **50/50**, zero failures/skips, including the new exact-selector theory case. No full-suite rerun is claimed after this harness-only addition. Evidence: `artifacts/issue-edits/goal-live-prelude-{build,optin-tests,style,whitespace,run}.log`, `goal-live-prelude-evidence.json`, `goal-live-prelude-harness.patch` and `goal-live-prelude-source-hashes.json`. - -This restores the historical native call order through the previously failing allocation using the current package, but does not reproduce historical bytes or timing. The result narrows the reproduction gap; it does not establish a root cause, demonstrate a production fix or qualify subsequent workflow/lifecycle transitions. - -Independent Astra final review matched all four retained raw summaries and receipt hashes to their extracts, the latest harness hashes and patch, build/test/format logs, package identities and documentation. It found no actionable issues in this continuation's evidence. Four completed diagnostic subcases were appended to both #28 and #33 and verified by exact body readback; their original incomplete gates remain unchecked and both issues remain OPEN. #31 was reviewed and remains OPEN with its compiler/injection and final contract/CI requirements unchanged. A fresh product recheck confirmed all 527 source hashes and the Release plugin/gateway hashes still match the offline package manifest. - -### Mechanism-level evidence still missing - -The pinned SDK source review did not establish a root cause. The earlier refusal is the `LuaProtectedApi.ValidateInvocation` guard where required inputs exceed the observed Lua stack height. The later AV occurs while restoring the outer `MainThreadDispatcher` frame; the crash site does not establish which earlier operation corrupted state. - -An older passing instrumented `.2` run, `20261008T015810Z-9f16`, records different callback/worker and AOB-provider Lua state pointers in dispatches 63 and 64. Distinct coroutine pointers can share a Lua VM, so this observation alone is not a defect. Existing trace `top` fields contain saved frame heights, not contemporaneous measurements, and cannot prove balanced execution. - -A next diagnostic, if needed after confirming the CE coroutine contract, must distinguish actual and saved stack heights before frame restoration, around `synchronize` and callback disposal, and at the precise insufficient-input guard. It should record state/thread/dispatch identity with bounded storage and survive a host crash. A new isolated SDK diagnostic package with the exact published native bridge would require preparation, offline review and separate execution authorization; the current passing ordinary-SDK runs do not authorize or validate it. - -Follow-up authoritative-source review explains the pointer difference: CE's `lua_synchronize` retains the caller's `L` as `syncvm`, and its synchronized callback uses that captured state. The plugin state getter instead returns a Lua state associated with the current OS thread. Different pointers are therefore expected in this path and are not evidence of an invalid state. CE's main-thread `memscan_waitTillDone` loop calls `CheckSynchronize` while waiting, which supplies a concrete reentrancy boundary for the proposed stack-balance trace. These facts narrow the diagnostic design; they do not prove a reentrancy defect in the installed build. - -Pinned public-source references (`ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37`, not a byte-for-byte claim about installed CE): [capture caller state](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaHandler.pas#L3456-L3494), [invoke the synchronized callback](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaCaller.pas#L358-L386), [per-thread state getter](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaHandler.pas#L188-L208), [plugin getter export](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/pluginexports.pas#L2622-L2635), and [scan wait synchronization](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaMemscan.pas#L145-L159). - -## Private stack trace prepared offline - 2026-10-09 - -The [stack trace diagnostic ledger](stack-trace-diagnostic.md) records the new private SDK/MCP source identities, exact package hashes, one-session proposal and completed offline checks. The SDK Release build passed; all 9 managed recorder tests passed. An owned helper process was terminated with its mapping open: all 16 committed records survived, and disabled/collision/missing-directory cases passed. Package inspection confirmed all seven embedded SDK assemblies and the unchanged published native bridge. Actual-DLL reflection and AOT smoke passed. The private MCP portable suite passed 4,134 checks with two notices-version failures; the corrected notices class then passed all 3 cases. No full-suite rerun after that documentation-only fix is claimed. - -The prepared runner passed syntax and no-execution preflight for 235 exact input hashes. Independent Astra high review verified the repaired source, package and runner with no remaining actionable findings in that scope. The user then explicitly approved one instrumented session; its result follows. #28, #31 and #33 remain open. - -### One approved instrumented session - -Run `20261009T111321Z-688b` passed the exact prepared case: **1 passed, 0 failed, 0 skipped**, 18.433 seconds. Host A retained 1,753 records covering 71 synchronized dispatches and three AOB frames; B retained 121 records covering five dispatches. All 79 observed frame restorations matched their saved heights. There were no recorded pre-restore underflows, protected-input refusals, failed probes, overflows, incomplete records or frozen traces. The post-AOB 64-byte non-executable allocation/free passed; gates remained false, user state was restored, installation unchanged, cleanup failures empty, and all four recorded owned PIDs were absent. - -Astra independently verified the trace interpretation: the depth-2 observations were ordinary inline child calls, not synchronized reentry. Three AOB waits took 4.8749, 2.4261 and 4.7970 ms with top 0 at both boundaries and no recorded SDK callback reentry. This is a balanced baseline, not a reproduction or production fix; it does not rule out other host callbacks. The [diagnostic ledger](stack-trace-diagnostic.md#approved-instrumented-session) records exact identities and evidence. This one-session authorization is consumed; no further live session is scheduled. The offline trace validator then passed 11 synthetic positive/negative controls and both recorded host snapshots, including actual nested-thunk detection, stack mismatch/interruption cases and expected pointer differences. The original failure/reentry reproduction remains open. - -Final Astra review confirmed the validator's sequence tie-breaking and completed evidence ledger with no remaining actionable findings in that scope. The executed diagnostic and validator subcases were checked off in #28 and #33, with exact normalized body readback and both issues still open; #31 remains open unchanged. Rechecking all 527 primary product-source hashes and the ordinary Release plugin/gateway confirmed that this private diagnostic did not change them. - -### Deterministic standalone reentry cases prepared - -The [nested-dispatch regression ledger](nested-dispatch-regression.md) records two new standalone native-Lua cases in a separate, uninstrumented SDK archive at `325c47b573f8bd39a247f1d0101f110fa36c1696`. They force a nested synchronized callback during an active protected host-wait call, exercise nonzero root-stack restoration on success and managed exception, and assert both worker sentinels and exact exception ownership. Actual Lua access is serialized; worker epilogues are released and joined one at a time after the outer callback unwinds. - -The Release test-module build passed with zero warnings/errors. Source comparison verified 1,277 entries with only two new test files and the original published native bridge replacement. The no-execution runner verified 1,485 input hashes; its 60-second deadline terminates only the owned test process. Independent Astra high review found no remaining actionable findings in the source, lifetime handling, runner or hashes. - -The user then approved one standalone execution. Run `885121c89ef846fead7b76804b21e5f3` passed both exact cases: **2 passed, 0 failed, 0 skipped**, 237 ms. The owned process exited 0 and was absent afterward; no timeout, runner failure or cleanup failure occurred. All prepared hashes still matched. This proves the tested simulated-host restoration paths, not a real-CE reproduction or production fix. No further native execution is scheduled. #28, #31 and #33 remain open. - -Independent Astra final evidence review was clear. The three completed preparation/execution/review subcases were checked off in #28 and #33, and exact body readback preserved the original incomplete acceptance gates. Source review identified the maintained full `lifecycle` scenario as the remaining path through the historical Settings/Plugins disable failure and restart matrix; its [ordinary-SDK preparation](lifecycle-resume.md) is being reviewed separately, without implying execution authorization. - -### Ordinary-SDK lifecycle reached the remaining bridge failure - -After Release build and independent Astra preparation review passed, the user explicitly approved one full x64 `lifecycle` session with the same ordinary-SDK distribution. Run `20261009T120128Z-8f44` completed the prelude and all six workflow groups, then failed at the first plugin-disable request with `expected exactly one owned plugin row`. The bounded bridge error receipt now reports the real refusal immediately, but its earlier form-initialization correction did not resolve checklist population. The test reports **0 passed, 1 failed, 0 skipped**, 18.441 seconds. - -All four gates stayed disabled. Mutating workflow cleanup failures were zero, user state was restored, the source installation stayed unchanged, and all four recorded owned host/target PIDs were absent. All 820 reviewed inputs still matched. Exact hashes and limits are in the [executed lifecycle ledger](lifecycle-resume.md#approved-execution-result). Plugin cycles and restarts remain unqualified; no production stack-crash fix is claimed. This one-session approval is consumed. The next work is offline investigation of the real Settings/Plugins population event, preserving exact owned-row validation. - -Independent Astra final review confirmed that failed result and its limits. The completed six-group workflow and cleanup/evidence subcases were checked off in #28 and #33; exact issue readback preserved the incomplete lifecycle gates. A subsequent [retained-key experiment](lifecycle-key-fix.md) was prepared on the mistaken premise that checklist population was inside the `OpenKey(Plugins64, false)` branch. Release build, both 859-hash preflights and independent Astra source/runner review passed, but that review missed the incorrect block interpretation. - -The user approved the eight standalone bridge cases followed conditionally by one lifecycle session. The standalone stage passed **8/8**, zero failures/skips. Run `20261009T121623Z-71c7` then completed all six workflows but failed the first disable with measured `count=0`, despite the `keyRetained=true` receipt: **0 passed, 1 failed, 0 skipped**. Preserving the empty key is insufficient. All gates stayed false, cleanup and settings restoration passed, both test processes and all four host/target PIDs were absent, and both 859-file manifests still matched. Astra independently confirmed the evidence and its limits. Exact artifacts and hashes are in the [conditional execution ledger](lifecycle-key-fix.md#approved-conditional-execution). No plugin cycle/restart or historical stack-failure resolution is claimed; this approval is consumed and further diagnosis proceeds offline. - -Exact pinned-source inspection then established that `FillCheckListBox` is outside the registry-key conditional. The researcher and Astra confirmed the correction. The unsupported key creation/retention change was rolled back; the measured-count receipt and zero-row regression remain. CE's reload has early returns and a broad exception handler, so the zero-row result does not identify the failing path. Original run artifacts and both tested source snapshots are retained; no post-rollback native result is claimed. - -The rollback Release build and whitespace checks passed; all 527 product-source hashes and ordinary distribution plugin/gateway hashes remained unchanged. Final Astra review was clear after correcting the consolidated live-counter fields from the retained TRX. #28 and #33 now check off the eight bridge cases and verified evidence while preserving incomplete lifecycle gates; exact body readback passed. #31 was reviewed and remains open unchanged. The active goal remains unfinished; no issue closure, push or release is claimed. - -### Settings observation prepared offline - -Further source and harness review found no separate Settings UI refresh path or specific mismatched registry value explaining the zero-row failure. The [Settings observation preparation](settings-observation.md) now captures the existing form's runtime class and the checklist count before reload in the bounded post-reload row-count refusal. Exact owned-row/name guards and the original registry handling remain unchanged. Two new standalone theory cases check distinct pre/post counts, class text and refusal before input/application, bringing the selected bridge scope to ten cases. - -Release build passed with zero warnings/errors, both no-execution manifests verified 859 inputs including 527 unchanged product sources, and independent Astra high review found no actionable issues. The proposed sequence is ten standalone cases followed conditionally by one unchanged ordinary-SDK x64 lifecycle attempt. Neither stage is executed or authorized yet. This observation can narrow the early-class and list-change hypotheses but cannot identify a swallowed exception or resolve an unchanged zero-row list on its own; it is not a product fix. - -### Isolated performance preparation - -While the Settings decision remains pending, the [isolated performance checkpoint](performance-preparation.md) expands the 9.01 runner and fixes a separately discovered pointer-reference cleanup-retention gap. The primary source, ordinary package and prepared Settings inputs remain unchanged. Release build passed with zero warnings/errors; final performance/lifecycle managed checks passed 20/20, and the separate pointer regression run passed 130/130, all without skips. All actionable Astra findings in this staged scope were repaired, and final Astra high review was clear. A retained 856-file source inventory identifies exactly six changed/new files and hashes the evidence. - -The completed offline preparation subcases were checked off in #28 and #33 with exact body readback; integration and native acceptance remain unchecked. Both issues remain open, and a fresh open-issue list still contains #28, #31 and #33. No native performance run, full-suite rerun, issue closure, commit, push or release occurred. - -### Isolated admission preparation - -The [admission checkpoint](admission-preparation.md) adds two independent gateway processes, fixed concurrent batches and UI/resource/completion observations in a second isolated snapshot. Astra found and the implementation repaired missing observer-versus-batch overlap checks and loss of original exception diagnostics. The final Release build has zero warnings/errors; 52 selected managed tests passed with no skips. Astra's second pass found no remaining actionable correctness findings in this scope. The 861-file source inventory identifies seven changed/new files relative to the first reviewed snapshot. - -#28 and #33 now check off this managed preparation, with exact body readback and incomplete native gates preserved. Both 859-input Settings no-execution preflights still pass. Package-matched SDK/Client source confirms that module and region caps apply only after native enumeration, including module-detail navigation's lookup. The separate navigation repair is recorded below; integration and native 9.01/9.03/9.05 acceptance remain open. - -### Isolated prepared navigation repair - -The [prepared navigation checkpoint](navigation-preparation.md) redirects module/detail/region resources and module completion to five-second, process/epoch/version-checked snapshots from explicit source tools. Its navigation paths contain no module/section/region enumeration, and cold/stale reads fail with preparation hints. The explicit tools still perform native enumeration. Latest module details are retained with detached sections arrays; fresh module completion does not extend snapshot lifetime through an outer cache. Descriptions document the intentional preparation requirement while resource URIs and schemas remain unchanged. - -The final isolated Release build passed with zero warnings/errors and all **4,207 portable tests passed**, with zero failures/skips. Earlier focused 214/214 and non-capture contract 38/38 checks also passed. The first broad run found a pointer-documentation lint failure and ten packaging-fixture failures caused by missing source-revision metadata; the documentation and isolated build command were corrected. No packaging behavior changed. The 863-file inventory records 26 modified and two new files relative to the reviewed admission snapshot. Astra's source and final evidence reviews are scoped clear; the final review independently verified source/manifest hashes, counters, build identity and constrained golden changes. - -The primary product sources, ordinary distribution and both 859-input Settings preparations remain unchanged. A fresh issue review still shows #28, #31 and #33 open; #31 retains its compiler/shared-temp/export/injection and final stable-evidence gates. Integration, candidate packaging, native performance/admission/lifecycle acceptance and publication remain incomplete. No additional native session, commit, push or release occurred. - -The completed offline navigation subcase is now checked off in #28 and #33. Exact issue body readback passed for both edits (64,475 and 26,313 characters respectively), preserving the unchecked integration and native acceptance gates. #31 remains open unchanged. The Settings-observation approval remains pending; no earlier consumed approval was reused. - -### Navigation package and remaining route repairs - -The [first navigation package](navigation-package.md) passed the maintained local publish, two isolated exact-DLL catalog checks, Native AOT gateway smoke, deterministic repeated ZIP creation and exact five-file staging. Independent Astra high evidence review was clear. It is an uncommitted development payload with the base beta.2 version and cannot replace the published beta.2 assets. It excludes the follow-up below and retains earlier dispatch labels, so it requires rebuilding before final qualification. - -The [navigation follow-up](navigation-followup.md) adds five-second prepared thread/symbol/breakpoint resources, corrects the caller-owned thread StringList API, and bounds indexed structure lookup and page access. Six explicit whole-enumeration preparations now honestly declare `blocking_native`; their resource helpers declare `short` and are validated through explicit prepared-projection metadata. Astra found and the implementation repaired symbol publication ordering, a missing process facade in the native test fixture, and a resource-only source-class validation gap. The new thread/structure Lua regressions compile but remain unexecuted. - -Final Release build passed with zero warnings/errors. All 213 focused cases passed; catalog capture passed 38 cases, and the final non-capture portable suite passed **4,234/4,234**, zero failures/skips. The first broad run's single stale script-count assertion was corrected from 18 to 19 and the complete suite rerun. Final Astra high source, golden and evidence reviews were scoped clear, including all 867 source hashes, the 863 prior hashes and both frozen 859-input Settings inventories. Evidence: `artifacts/performance-preparation/navigation-followup-evidence.json`. - -Completed package and offline follow-up subcases are checked off in #28 and #33. Exact normalized issue readback passed (64,632 and 26,470 characters), preserving all incomplete native/integration/release gates. A fresh review confirmed #31 still requires compiler/prerequisite, shared-temp expiry/reload, delayed export, injection and final stable evidence. All three issues remain open; the goal is not complete. No primary product integration, new native execution, commit, push or publication occurred. - -The follow-up was subsequently [packaged locally](navigation-package.md#latest-reviewed-follow-up-package) from a fresh hash-matched copy of all 867 reviewed files. Both exact-DLL probes, Native AOT empty-registry smoke, two identical ZIP builds and maintained five-file staging passed. Final Astra high package evidence review was scoped clear; it independently verified both ZIPs, source and file hashes, versions, logs and both frozen Settings inventories. This supersedes the first development package for the repaired navigation source, but remains an uncommitted beta.2-version payload with no stable identity or native qualification claim. Evidence: `artifacts/candidate-preparation/followup-package-evidence.json`. - -The latest local-package subcase is also checked off in #28 and #33. Exact readback passed at 64,861 and 26,699 characters. Native Lua regressions, primary integration, final source/version identity, native acceptance and release remain open; #31 remains unchanged. The Settings-observation approval is still pending, and no consumed approval was reused. - -### Standalone navigation Lua cases prepared - -The [five-case navigation regression](navigation-native-regression.md) is now prepared against the already reviewed Release test module. It exercises thread StringList parsing/destruction and the new bounded structure definition/page script in an ordinary standalone Lua bridge with a simulated host. The runner restricts the exact five fact names, requires all five passed with no skips, uses a 60-second owned-process deadline and isolates temporary files. It has no CE launch, real target, debugger or follow-on session. - -The no-execution preflight and independent Astra high preparation review verified 1,088 files, including all 867 reviewed sources and 216 test-output files. The reviewed portable test module is unchanged, so no rebuild or test discovery was needed. Both 859-file Settings inventories also remain unchanged. The separate five-case native execution still requires explicit approval under the user's earlier stop. No additional acceptance gate is checked off on the strength of this unexecuted proposal. - -### Matching navigation Debug checks - -The [reviewed navigation follow-up](navigation-followup.md#matching-debug-portable-verification) now also has a passing Debug portable run from a separate hash-matched copy of its 867 files. Locked restore and build passed with zero build warnings/errors; all 4,234 cases passed with zero failures/skips in 1 minute 42.351 seconds. The Debug and Release TRX test names match exactly. Evidence: `artifacts/candidate-preparation/debug-followup-evidence.json`. - -Both Settings inventories (859 files each) and the navigation native inventory (1,088 files) still match. No primary product source, existing Release test output or pending native input changed. Astra found and verified the repair of a timestamp serialization error in the evidence recorder; final independent Astra high evidence review is scoped clear. Final clean-candidate and native gates remain open. - -The completed Debug subcase is checked off in #28 and #33. Exact body readback passed (64,663 and 26,501 characters), preserving all incomplete acceptance gates. Both issues remain open; #31 is unchanged. No native session, commit, push or publication occurred. - -### Maintainer-directed MCP closeout - -The maintainer subsequently requested focusing on MCP, closing the remaining issues where further work could not proceed, and committing tested code locally to `main`. The [closeout record](mcp-closeout.md) supersedes the earlier keep-open requirement and pending native preparations. It records integration of all 61 reviewed files, fresh 4,234-pass Debug and Release portable runs, clean builds/formatting, both package/AOT probes, local ZIP verification and final Astra review with no actionable findings. Issues #28, #31 and #33 are now verified closed as `not_planned`, with their acceptance checklists unchanged. The historical native defects and unperformed stable qualification remain explicitly deferred; no stable release or push is claimed. diff --git a/docs/qualification/v2.0.0/requirement-matrix.md b/docs/qualification/v2.0.0/requirement-matrix.md deleted file mode 100644 index 51a8ea3..0000000 --- a/docs/qualification/v2.0.0/requirement-matrix.md +++ /dev/null @@ -1,153 +0,0 @@ -# Stable v2.0.0 requirement matrix - -Date: 2026-10-09. -Status: Crosswalk of the 121 roadmap entries that were unchecked when this matrix was created; subsequent evidence and checkbox updates are recorded in the linked checkpoints. - -pass means the complete requirement has evidence at the level stated, with final-candidate repetitions separate when named. -partial means useful evidence exists but one or more clauses remain unproved. -not_run means no execution evidence exists for the requirement. -environment_dependency means completion requires an approved policy, controlled environment, actual client, runtime, hardware, or external release boundary. - -The compiler checkpoint used clean candidate 808a626e65eddab113737a27a7c2345555b1c241, still identified as beta.2. -Later offline and two-workload x64 performance results are recorded in [remaining-checkpoint.md](remaining-checkpoint.md). -The later workflow runs exposed native Lua dispatch corruption, including a private-host crash. Lifecycle and soak remain blocked until a demonstrated fix is verified; the completed subcases do not override this release blocker. -The historical pointer smoke used Release plugin SHA-256 E6E2AE56B43CF54F1F5978B566EE762939B76E549D557FDDF8B073BB8262750B and is not evidence for the clean compiler candidate. -The observed environment is Windows 11 Pro x64 build 10.0.26300, Cheat Engine x64 7.7.1.10828, and .NET runtime 10.0.12. -No row generalizes that environment to another Windows, CE, runtime, target, or client. - -| ID | Requirement | Status | Evidence now | Remaining dependency or action | Owner | -| --- | --- | --- | --- | --- | --- | -| 2.01 | Confirm final version/tag as `2.0.0` / `v2.0.0`; retain existing beta tags and publish a new stable release. | partial | support-matrix.md selects 2.0.0 and v2.0.0 while retaining beta tags, but publication has not occurred. | Publish only after the final stable package passes every release gate. | release coordinator | -| 2.02 | Set the supported Windows versions, CE version/build, plugin architecture, .NET host frameworks, and target architectures in one compatibility matrix. | pass | support-matrix.md records the intended Windows, CE host, plugin, runtime, gateway, target, transport, and client boundaries. | Review the definition against exact RC evidence and retain its selected boundary. | release coordinator | -| 2.03 | Start from the current Windows x64 / CE 7.7 support boundary; qualify x86 and x64 **targets** separately from the x64 plugin host. | partial | The stable definition is recorded in support-matrix.md and acceptance.md, with the remaining limitation named in this row. | Complete the selected x86 and x64 target workflow matrix; current pointer and compiler evidence is narrower. | release coordinator | -| 2.04 | Classify each advertised capability as qualified, optional/environment-dependent, experimental, or unsupported, with evidence and limitations. | partial | The stable definition is recorded in support-matrix.md and acceptance.md, with the remaining limitation named in this row. | Review the per-domain table and replace experimental or optional status only with complete evidence. | release coordinator | -| 2.05 | Define which P1 features belong in stable v2 before an RC is tagged; move deferred work into linked follow-ups. | pass | All three P1 features are intentionally selected in support-matrix.md and acceptance.md. | Review the definition against exact RC evidence and retain its selected boundary. | release coordinator | -| 2.06 | Freeze stable tool names, arguments, result/error shapes, resource URIs, prompts, and gateway routing semantics. | partial | The stable definition is recorded in support-matrix.md and acceptance.md, with the remaining limitation named in this row. | Finish the section 3 audit, freeze the RC contract, and repeat exact-candidate snapshots. | release coordinator | -| 2.07 | Write the beta-to-stable compatibility policy: additive evolution, intentional breaking changes, configuration migration, and future deprecations. | partial | The default policy remains defined; release-notes.md now enumerates proposed unavailable-field and export-resource corrections that intentionally change prerelease compatibility. | Accept or revise the explicit exceptions and client migration guidance in the final contract review before freeze. | release coordinator | -| 2.08 | Define release-stopping defects: crashes, corruption, cross-instance/target effects, unreported partial writes, leaked state, broken installation, and wrong outcome reporting. | pass | support-matrix.md defines the release-stopping defect classes. | Review the definition against exact RC evidence and retain its selected boundary. | release coordinator | -| 2.09 | Set the RC bug-bash/soak duration, supported environments, exercised workloads, and pass/fail criteria; record results rather than infer stability from tool counts. | partial | acceptance.md defines the two-hour two-instance workload and pass or fail criteria; no completed two-hour result exists. | Resolve the native release blocker, then run the approved soak on the qualified candidate and record its measured outcome. | release coordinator | -| 2.10 | Agree measurable performance and responsiveness thresholds from section 9's baselines, including workload sizes, measurement method, and allowed exceptions for blocking native calls. | partial | acceptance.md defines measured-relative budgets; remaining-checkpoint.md records three baseline and three candidate sessions for two x64 memory workloads. | Complete and review the other workload baselines, UI responsiveness, and final-candidate repetitions. | release coordinator | -| 3.01 | Reconcile catalog constants, registrations, generated snapshots, tool map, workflow prompts, and packaged-plugin reflection output for the final candidate. | partial | contract-checkpoint.md records the source review; remaining-checkpoint.md records current Debug/Release single-DLL catalog equality and exact AOT executable snapshots. | Reconcile final names, schemas, prompts, resources, workflows, tool map, and packaged DLL on the exact clean RC. | contract owner | -| 3.02 | Verify schema compatibility with reflection-disabled JSON and the published Native AOT gateway, including every new input/result type. | pass | contract-checkpoint.md names resolver parity, AOT, packaged reflection, and the clean candidate result. | Complete the named audit gap and repeat V+ on the exact RC and final stable commit. | contract owner | -| 3.03 | Verify nullable/unavailable results remain distinct from legitimate zero/empty values. | partial | offline-contract-audit.md records full-domain source coverage, six unavailable-result corrections and clarified collector/FoundList limits; all 626 standalone Release NativeLua cases passed after explicit authorization. | Verify the exact candidate and applicable real CE/collector behavior; preserve documented upstream ambiguity. | contract owner | -| 3.04 | Verify 64-bit addresses, signed offsets, exact integer values, and hexadecimal conventions survive client serialization without precision loss. | partial | All 12 ExactValuePipelineTests passed: real .NET MCP and gateway serialization preserve extrema, exact integers above the JavaScript safe range, signed offsets, addresses and pointers. | Include these working-tree regressions in the clean RC and final stable verification; named interactive clients are separate. | contract owner | -| 3.05 | Verify every routed call requires its explicit `instanceId`; instance disappearance must never silently select another CE process. | pass | contract-checkpoint.md names required-instance, stale-instance, and two-instance routing tests. | Complete the named audit gap and repeat V+ on the exact RC and final stable commit. | contract owner | -| 3.06 | Verify read-only/destructive/idempotent/open-world annotations, dispatch classes, and capability requirements match actual effects. | partial | The full 193-tool source inventory is reviewed; offline-contract-audit.md records 14 corrected tool entries across both continuations and the prefetchable exports-resource removal. | Reconcile the exact candidate catalog/package; measure applicable native dispatch budgets separately. | contract owner | -| 3.07 | Verify disabled capabilities reject before the covered effect, including loaded tables, scripts, indirect Lua/AA paths, and argument-dependent operations. | partial | Source audit and portable table, secondary AA, record batch and process auto-attach checks are recorded in contract-checkpoint.md; the record_clear hint assertion passed in the approved standalone NativeLua run. | Run actual CE hook cases when permitted and include the changes in final-candidate checks. | contract owner | -| 3.08 | Document actual defaults: the four capability switches are currently on; file/table allowed roots are empty. Switches are exposure controls, not a sandbox. | pass | Configuration tests and public docs agree on enabled switches, empty roots, precedence, and exposure semantics. | Complete the named audit gap and repeat V+ on the exact RC and final stable commit. | contract owner | -| 3.09 | Verify typed `isError`, `error.kind`, `hostEffect`, retryability, details, hints, and correlation IDs through direct backend and gateway calls. | partial | The working-tree source/portable audit in contract-checkpoint.md covers all six host effects through direct and routed errors and corrects missing correlation IDs on non-object context. | Include the reviewed correction in a clean candidate and repeat exact RC/final portable and package checks. | contract owner | -| 3.10 | Preserve the rule that uncertain writes/injections/patches are not automatically replayed after timeout or transport failure. | pass | contract-checkpoint.md names cancellation, timeout, and dropped-connection no-replay tests. | Complete the named audit gap and repeat V+ on the exact RC and final stable commit. | contract owner | -| 3.11 | Verify tools/list, resources, prompts, completions, and offline knowledge remain consistent when no CE instance is running. | pass | contract-checkpoint.md names no-instance tool, resource, prompt, completion, and executable snapshot tests. | Complete the named audit gap and repeat V+ on the exact RC and final stable commit. | contract owner | -| 6.01 | Report CE-close/reload lifetime accurately, including CE's documented possibility that another CE instance closing removes an unused compiler output; verify this behavior on the supported build. Do not promise assembly unload or cleanup that CE cannot confirm. | environment_dependency | compiler-checkpoint.md records approved phases 1 through 4 on clean 808a626e65eddab113737a27a7c2345555b1c241. | Run reviewed shared-temp close and reload observations on private CE copies and record retained or expired behavior. | compiler qualification owner | -| 6.02 | Qualify valid source, diagnostics, unavailable compiler, invalid/missing references, disabled capability, and generated-artifact expiry. | environment_dependency | compiler-checkpoint.md records approved phases 1 through 4 on clean 808a626e65eddab113737a27a7c2345555b1c241. | Add unavailable-prerequisite and expiry cases under an approved policy. | compiler qualification owner | -| 6.03 | Under the separately reviewed qualification policy, compile in instance A, close instance B, then export and separately inject from A; verify either retained availability or the documented missing/expired-artifact outcome, including cleanup. | environment_dependency | compiler-checkpoint.md records approved phases 1 through 4 on clean 808a626e65eddab113737a27a7c2345555b1c241. | Approve injection phase 5, define a deterministic owned method, and run close-B, delayed-export, injection, and cleanup. | compiler qualification owner | -| 7.01 | Exercise repeated plugin enable/disable/re-enable cycles; verify fresh activation identity, token, endpoint/discovery record, Client state, and no stale service reuse. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.02 | Exercise CE shutdown, target exit/restart, client disconnect, gateway restart, and backend withdrawal while another instance remains usable. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.03 | Qualify blocked target changes while retained state or active scans exist, and successful switching after explicit cleanup. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.04 | Prove a reused PID or stale selection epoch cannot apply work to a replacement target. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.05 | Verify allocations, patches, registered symbols, named scanners, MCP breakpoints, pauses, speed changes, jobs, and Mono attachment follow their documented ownership/release paths. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.06 | Preserve CE/user-owned table records, structures, breakpoints, symbols, and unrelated state during MCP cleanup. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.07 | Verify failed cleanup retains accurate orphan/recovery information instead of reporting successful release. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.08 | Exercise cancellation before admission, during a job, and after native work starts; keep outcomes distinct. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.09 | Exercise timeout/lost response after mutations; inspect state and report unknown effects without automatic replay. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.10 | Verify cursor replay, pagination, retained finished jobs, TTL expiry, bounded buffers, dropped-item accounting, and stop/dispose races. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.11 | Verify stale IDs/tokens/discovery records are rejected and refreshed only through explicit instance discovery. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 7.12 | Verify cleanup order and idempotency where supported, including failure midway through releasing several resources. | partial | Portable lifecycle and ownership tests plus bounded LOCAL-07 and LOCAL-08 smoke evidence cover only named subcases. | Extend the private lifecycle scenario, run the exact bounded case, and record ownership, cross-instance behavior, and cleanup. | lifecycle qualification owner | -| 8.01 | Memory: scalar/array/batch reads and writes, strings, bytes, signedness, byte order, readback, unreadable pages, and partial writes. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.02 | Memory utilities: snapshots/comparison/sampling/copy/hash, protection restore, allocation lifetime, dump/load limits, and file-policy failures. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.03 | Value scans: exact/unknown/between/changed/increased/decreased, empty/default inputs, x86/x64 ranges, first/next/reset/delete, saved/named scanner isolation, and cancellation support. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.04 | Mapped memory: inclusion and override restoration after success, refusal, error, cancellation, and target changes. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.05 | AOBs: module/range/protection/alignment filters, multiple patterns, wildcard matching, mapped memory, uniqueness, and signature verification against real fixture code. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.06 | Pointers: chain hops, positive/negative offsets, pointer maps, path limits, rescans, module rebasing, incomplete captures, and native scan-file import/export fidelity. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.07 | Modules/symbols: PE headers, imports/exports, patch comparison, exact resolution, PDB layouts, symbol reload/state, and ownership of registered symbols. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.08 | Code analysis: disassembly bytes/columns, function estimates, direct branches, CFG limits, strings/references, dissection jobs, cancellation, and explicit limits for stores/JIT/indirect control flow. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.09 | Structures: autoguess limitations, field alignment/signedness, nested definitions, typed read/write/compare, PDB/.NET fill, header generation, and CE-owned persistence. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.10 | Records/tables: hierarchy, groups, pointers, dropdowns, activation/freeze, script gates, load/save/merge, malformed/protected content, rollback/partial effects, and user-owned data preservation. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.11 | User-mode debugger: attach/detach, thread breakpoints, access/write/execute captures, effective-address grouping, context/register writes, stepping/tracing, thread filters, and restore/cleanup. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.12 | Debugger interface policy: Windows/VEH/kernel behavior, gate checks, fallback reporting, reattach restrictions, and explicit refusal of unsupported interfaces/connections. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.13 | .NET: fixture-supported domains/modules/types/methods/parameters, object reads and bounded instance searches, runtime unavailable cases, and collector lifetime. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.14 | Mono/IL2CPP: attachment/status, assemblies/classes/fields/methods, static fields, object layouts, bounded search, and documented generic/array/collector limitations; qualify invocation separately where permitted. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.15 | Assembly/execution: reversible patch apply/disable, API-hook/injection scaffolds, native/managed injection, local/remote/member calls, C compilation and (if selected for stable v2) C# compilation, calling conventions, and uncertain timeout effects, under a separately reviewed qualification policy. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.16 | Lua: fixed-script error/result conversion, bounded structured output, malformed/opaque values, capability classification, main-thread behavior, and lifecycle transitions. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.17 | Kernel/DBVM: retain explicit environment-dependent status; separately document qualified status/translation/physical-memory/watch cases and supported refusal paths without treating unavailable hardware as a pass. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.18 | Speedhack: supported/unsupported targets, non-default speed restoration, ownership, target switching, and cross-instance isolation. | environment_dependency | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Define or approve the policy and controlled environment, then run the complete row without treating unavailability as success. | domain qualification owner | -| 8.19 | Keep the existing live harness opt-in, outside ordinary CI, on private CE copies and disposable targets, with user-state backup/recovery evidence. | pass | The live harness is opt-in, private-copy, disposable-target, backup guarded, and outside ordinary CI. | Repeat the same admission and cleanup boundary for the exact RC and final package. | domain qualification owner | -| 8.20 | Preserve its reviewed-autorun checks; inspect upstream changes before updating approved hashes. | pass | The historical and compiler checkpoints record unchanged reviewed autorun hashes. | Repeat hash validation and inspect any upstream change before accepting a different autorun set. | domain qualification owner | -| 8.21 | Design richer disposable fixtures for pointer chains, native code/structures/RTTI, tables/PDBs, and x86 targets where supported. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.22 | Design separate controlled managed fixtures for .NET, Mono, and IL2CPP and record the exact collector/runtime versions. | not_run | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.23 | Define and review any new qualification policy for debugger/injection/compiler/managed-invocation/kernel work. The current harness explicitly excludes kernel, hypervisor, and code-execution probes; this issue does not remove that restriction. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 8.24 | Record unqualified optional capabilities explicitly, with follow-ups or experimental support status; do not claim universal native compatibility from the existing smoke scenario. | partial | local-checkpoint.md and compiler-checkpoint.md cover only their named pointer, smoke, and compiler cases. | Extend the owned fixture and run every clause in this domain row; current broad smoke evidence remains partial. | domain qualification owner | -| 9.01 | Establish reproducible latency/UI-responsiveness baselines for short reads, large batches, regions, scans, pointers, symbols, structures, dissection, and instance discovery. | partial | remaining-checkpoint.md retains the two measured x64 memory workloads. [Performance preparation](performance-preparation.md) and [admission preparation](admission-preparation.md) add reviewed, managed-tested measurement runners; the broader native matrix is unexecuted. | Integrate the reviewed preparation, then run three reproducible baselines and candidate measurements with fixed workloads and environment identity under separate native authorization. | performance qualification owner | -| 9.02 | Choose practical acceptance budgets per dispatch class and document operations that can block CE or outlive an MCP timeout. | partial | acceptance.md defines planned budgets. [Performance preparation](performance-preparation.md) validates paired workload comparisons; [navigation follow-up](navigation-followup.md) labels six whole-enumeration preparations blocking_native. Neither proves native latency or meets the final-candidate gate. | Approve measured-relative budgets after native baseline review and verify the integrated candidate's blocking-native exceptions. | performance qualification owner | -| 9.03 | Check dispatch concurrency/busy responses under multiple clients without starving CE's main thread. | not_run | [Admission preparation](admission-preparation.md) implements two-gateway overlap, busy classification, UI observations and comparison checks; 52 managed cases passed. No native concurrency/UI measurement has run. | Execute the reviewed measurement on baseline and integrated candidate packages under separate native authorization; record all required response classes and overlap. | performance qualification owner | -| 9.04 | Verify large jobs stay within configured job/buffer/TTL/result bounds and expose truncation or dropped data. | partial | [Performance preparation](performance-preparation.md) adds measured-workload preparation and a managed-tested one-slot bound for retained failed pointer range searches. This narrow repair does not qualify every job, TTL, buffer or dropped-data contract. | Complete the broader native job/buffer/TTL and soak cases on the exact RC; record counts, limits and resource recovery. | performance qualification owner | -| 9.05 | Verify expensive completions/live-resource reads share admission limits and cannot turn ordinary client navigation into an unbounded scan. | partial | [Navigation follow-up](navigation-followup.md) uses explicit five-second prepared projections for module/region/thread/symbol/breakpoint navigation and passes 4,234 Release portable cases. The [local package](navigation-package.md#latest-reviewed-follow-up-package) is reviewed; primary integration, changed Lua execution and native admission evidence remain pending. | Run the prepared Lua regressions with approval, integrate the reviewed source, and qualify the packaged candidate's navigation/admission behavior in CE. | performance qualification owner | -| 9.06 | Exercise sustained polling and repeated enable/disable cycles; check managed/native memory, handles, sockets, retained jobs, and event subscriptions. | not_run | acceptance.md records budgets and workload; remaining-checkpoint.md has only two measured x64 memory workloads, with this criterion and soak still unqualified. | Run the two-hour soak and compare handles, private bytes, sockets, jobs, and subscriptions to baseline. | performance qualification owner | -| 9.07 | Keep symbol waits, whole-heap searches, unsupported cancellation, and long native calls explicit rather than masking them with a successful timeout response. | partial | [Navigation follow-up](navigation-followup.md) corrects six explicit enumeration tools to blocking_native and keeps prepared resource helpers short. [Performance preparation](performance-preparation.md) documents dissection wait and uncertain cleanup limits. These scoped reviews do not qualify all native timeout paths. | Integrate the reviewed changes and verify remaining long-call/cancellation/timeout outcomes on the exact candidate without automatic replay. | performance qualification owner | -| 9.08 | Document tuning defaults and measured tradeoffs; raising concurrency must not be described as making CE's main thread parallel. | partial | acceptance.md records budgets and workload; remaining-checkpoint.md has only two measured x64 memory workloads, with this criterion and soak still unqualified. | Implement the measurement or soak scenario, run it on the exact RC, and record counts, p95, limits, and recovery. | performance qualification owner | -| 10.01 | Qualify a clean installation from the exact candidate ZIP on a supported Windows environment using only the documented deployment files. | not_run | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Install only the exact candidate ZIP into a clean private CE copy and record first-use and cleanup. | installation and client owner | -| 10.02 | Verify CE version/architecture and all required .NET 10 host frameworks; provide actionable diagnostics for missing/mismatched runtime configuration. | partial | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Run the exact-package installation or client case and record preservation, refusal, rollback, and cleanup. | installation and client owner | -| 10.03 | Qualify paths with spaces, a standard user account, valid file/table roots, and read-only deployment locations where supported. | environment_dependency | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Use a standard-user environment and an owned read-only fixture; do not infer these cases from administrator runs. | installation and client owner | -| 10.04 | Qualify replacing beta.2 with the RC/stable package, including migration from older folder deployments/plugin entries. | not_run | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Run beta.2 and older folder-deployment upgrades with version-pair and rollback evidence. | installation and client owner | -| 10.05 | Preserve user configuration, tables, unrelated plugins, and CE settings; backup/rollback steps must be reproducible. | partial | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Run the exact-package installation or client case and record preservation, refusal, rollback, and cleanup. | installation and client owner | -| 10.06 | Verify matching plugin/gateway versions and source identity; detect unsupported mismatches clearly. | partial | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Run the exact-package installation or client case and record preservation, refusal, rollback, and cleanup. | installation and client owner | -| 10.07 | Document exact configuration precedence, per-activation reads, environment overrides, and validation failures. | pass | README, gateway README, and McpConfigurationTests.Load_PluginUserAndEnvironment_UseDocumentedPrecedence document and test precedence. | Reconcile final-package docs and repeat invalid configuration cases. | installation and client owner | -| 10.08 | Verify malformed config, invalid ports/roots/names, unavailable log locations, occupied endpoints, and startup refusal do not leave half-enabled state. | partial | Configuration coverage plus 12 passing McpModuleLifecycleTests include real-module occupied-port cleanup and best-effort logging when the log directory cannot be used. | Repeat against the exact installed candidate; portable loopback hosting does not prove the CE plugin installation lifecycle. | installation and client owner | -| 10.09 | Test first use in the supported MCP clients: registration, discovery, instance choice, target orientation, tool listing, structured errors, resources, prompts, and completions. | environment_dependency | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Name supported clients first, then run the complete first-use workflow in each actual client. | installation and client owner | -| 10.10 | Verify fresh/stale instance IDs, same-name instances, admin/user mismatch, target vs CE PID, and wrong executable/version troubleshooting. | partial | Current docs and portable configuration checks cover design behavior, not an exact-package installation result. | Run the exact-package installation or client case and record preservation, refusal, rollback, and cleanup. | installation and client owner | -| 10.11 | Provide a support-report checklist with versions, operation/error ID, effective gates, and redacted logs; never request tokens or discovery-record contents. | pass | The connection-troubleshooting support checklist names environment/package/client versions, operation and error fields, gates, reproduction and redacted logs; it excludes tokens and discovery contents. README links to it. | Keep final-release version guidance current; this documentation pass does not qualify an installation or client. | installation and client owner | -| 10.12 | Document disable/uninstall, rollback, and recovery from a locked gateway or partially completed upgrade. | pass | The connection-troubleshooting recovery section documents separate staging, matching plugin/gateway paths, client registration, locked files, partial upgrades, rollback and removal while preserving user data. README and release notes link to it. | Execute the separate exact-package clean-install, upgrade and rollback gates before release. | installation and client owner | -| 11.01 | Run required V+ checks on the exact RC commit and review intended golden-file changes. | not_run | The clean compiler candidate has bounded local package evidence, but it is beta-versioned and is not the RC or final stable package. | Create the exact RC commit and run V+ with reviewed golden changes. | release engineering owner | -| 11.02 | Verify all newly selected workflow-matrix evidence references the same candidate commit or record why a prior result remains applicable. | not_run | The clean compiler candidate has bounded local package evidence, but it is beta-versioned and is not the RC or final stable package. | Cross-reference every retained result to one candidate or document the intervening diff and applicability. | release engineering owner | -| 11.03 | Confirm CI format/build/test/analysis results and resolve actionable release-stopping findings; record limitations for checks unavailable in a given environment. | not_run | The clean compiler candidate has bounded local package evidence, but it is beta-versioned and is not the RC or final stable package. | Run and review remote CI build, format, test, and analysis for the exact RC. | release engineering owner | -| 11.04 | Verify locked dependency restore, high/critical advisory handling, embedded dependency inventory, licenses, and third-party notices. | partial | remaining-checkpoint.md records fresh Debug/Release locked publication, embedded dependency and notice checks for the uncommitted working tree; these are not clean RC or stable bytes. | Repeat this gate on the exact RC and final stable bytes, then record hashes, logs, remote references, and limitations. | release engineering owner | -| 11.05 | Verify the flat ZIP inventory, deterministic packaging where promised, checksums, absence of development leftovers, and plugin/gateway version/source agreement. | partial | [Latest navigation package](navigation-package.md#latest-reviewed-follow-up-package) records the 867-file reviewed source copy, two byte-identical Release ZIPs, all five staged-file hashes and matching plugin/gateway beta.2 base identities. Its manifest records the uncommitted changes; it is not a clean RC or stable payload. | Repeat this gate on the exact RC and final stable bytes, then record hashes, logs, remote references, and limitations. | release engineering owner | -| 11.06 | Verify upload/publish repeatability, draft recovery, tag/source mismatch refusal, immutable published binaries, and uploaded digest/size checks. | partial | The clean compiler candidate has bounded local package evidence, but it is beta-versioned and is not the RC or final stable package. | Repeat this gate on the exact RC and final stable bytes, then record hashes, logs, remote references, and limitations. | release engineering owner | -| 11.07 | Prepare reviewed stable release notes with support matrix, migration instructions, known limitations, and changes since beta.2. | pass | release-notes.md is an unpublished reviewed draft covering all four clauses, including the unresolved production dispatch crash and outstanding qualification. | Finalize exact source, hashes, qualified environments and public asset links only after the separate final stable gates pass. | release engineering owner | -| 12.01 | Standalone structure XML export: define its format and fidelity; do not imply a lossless CE structure round trip from a simple XML fragment. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.02 | Bulk register-context updates: define snapshot/stop-state requirements, validation before effects, per-register/partial outcomes, and readback. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.03 | Optional typed FPU/XMM writes using CE-local context APIs; current extra-register reads already exist, so do not duplicate them. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.04 | Extended CE-local native calls (`executeCodeLocalEx`): define convention/argument/return support and blocking/unknown-effect behavior; preserve the existing simple call. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.05 | Native RTTI/vtable inventory beyond class-name lookup: bounded slots, module validation, unreadable entries, ABI/layout limitations, and no guessed prototypes. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.06 | CE dissection-reference editing: explicitly track the deferred add/delete operations from the legacy map and protect CE/user-owned analysis state. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.07 | Richer structured memory-region/analysis views using existing CE results; region enumeration/protection already exist. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.08 | Read-only inspection of CE SDK exported-function availability/type/offset/callability where supported; raw entry points must not become unrestricted callable tools by default. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.09 | Better compiler/calling-convention/object-search guidance and domain prompts where existing tools need too much manual orchestration. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.10 | Evaluate typed window/input/pixel/clipboard helpers only for CE-owned windows and concrete CE UI workflows, excluding general desktop/target-application automation, with explicit host effects and bounded cleanup of synthetic input state. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 12.11 | Evaluate remote access to our CE gateway only when required, with explicit authentication, instance identity, and documented transport scope; local Windows support remains the stable baseline. | not_run | No implementation or qualification evidence exists; this item is outside the stable blocking path. | Create a linked P2 follow-up with design, ownership, acceptance, and evidence requirements; do not block stable v2.0.0. | P2 follow-up owner | -| 13.01 | **LBR feasibility:** retain the deliberate removal of `dbg_lbr_enable`/`dbg_lbr_records` until modern Windows/CPU/CE support is demonstrated. CE's own documentation warns of OS/CPU restrictions. Do not make a thin wrapper a release requirement. | pass | The tools remain absent and support-matrix.md retains LBR as unsupported pending feasibility. | Keep LBR absent unless a linked effort supplies modern CE, OS, CPU, policy, and cleanup evidence. | research follow-up owner | -| 13.02 | If LBR is viable, define actual return semantics, kernel/debugger prerequisites, bounds, prior-state restoration, cleanup, and native qualification before adding typed tools. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.03 | **Shared allocation/sections:** prove a release lifecycle for handles, views, named mappings, failure rollback, target exit, and plugin disable before restoring typed shared memory. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.04 | **DBK map/unmap:** establish MDL ownership and confirmed cleanup before exposing mappings through MCP. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.05 | **Caller-selected CR3 access/translation:** define an explicit address-space contract, bounds, residency/translation failures, and qualification; selected-target translation is already available. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.06 | **Driver write-protection bypass:** distinguish `dbk_writesIgnoreWriteProtection` from page flags, establish prior-state/recovery behavior, and justify demand before adding a global toggle. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.07 | **Raw kernel allocation:** require a demonstrated release/recovery path; compiler-created kernel code is not a general managed allocation service. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.08 | **DBVM debugger or broader native host/ceserver support:** require a separate CE-specific design and qualification plan before changing the current unsupported boundary. | not_run | No feasibility or native qualification evidence exists beyond the explicit unsupported boundary. | Create a linked research follow-up or retain an explicit unsupported disposition; do not claim qualification. | research follow-up owner | -| 13.09 | Keep deliberately unsupported cases explicit when CE cannot provide reliable APIs; a stable release may document them without implementing them. | pass | support-matrix.md explicitly records unsupported low-level capabilities. | Retain the unsupported disposition and link future work if demand and reliable APIs emerge. | research follow-up owner | -| 14.01 | All P0 tasks are complete with evidence, and every P1 item has a completed implementation or explicit linked deferral. | partial | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.02 | Every advertised domain has a reviewed support/qualification status and documented recovery/limitations. | partial | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.03 | No unresolved release-stopping crash, corruption, ownership/isolation, installation, or outcome-reporting defect remains. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.04 | Final tool/schema/resources/prompts contract and beta migration documentation are reviewed. | partial | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.05 | RC bug-bash/soak results and clean-install/upgrade/rollback results are recorded. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.06 | Set `Version` to `2.0.0` in a committed change and build from that clean final source commit. Record its identity and the final DLL/gateway/ZIP hashes; an RC result alone does not qualify a rebuilt stable payload. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.07 | On the final stable commit, repeat V+, portable tests against both Debug and Release outputs, and VN when fixed Lua/runtime/job/indicator code changed since the applicable VN evidence. On the final Release package, repeat the Native AOT executable smoke checks, packaged-DLL golden comparison, and ZIP/checksum/source-identity checks. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.08 | Repeat clean-install/upgrade/rollback checks and the maintained two-instance real-CE qualification against the final Release package, with explicit maintainer opt-in and existing live restrictions. For other workflow/soak evidence retained from the RC, record the intervening diff, why the evidence still applies, and which affected cases were rerun; changes beyond version/packaging metadata require reassessing that evidence. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.09 | Create the annotated `v2.0.0` tag at the exact binary source commit; do not move beta or published stable tags. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.10 | Upload the maintained complete Windows x64 ZIP plus `SHA256SUMS.txt` as a draft, review notes/assets, and verify remote hashes and source identity. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.11 | Publish with `draft=false` and `prerelease=false`; mark the intended stable release as Latest after verification. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.12 | Download/verify the public stable package and smoke-test the shipped installation path, gateway, plugin discovery, and documented first-use workflow. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.13 | Update README/status/support policy to the released stable version and document the patch-release process. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | -| 14.14 | Close this roadmap only when the stable release is verified; keep optional/research work in linked follow-up issues. | not_run | No final stable release evidence exists. | Complete all dependent P0, P1, candidate, native, install, soak, and publication gates before checking this item. | release coordinator | - -## Suggested follow-up groups - -One P2 design follow-up can cover 12.01 through 12.09 as typed CE convenience contracts without implying native qualification. -One UI and transport evaluation follow-up can cover 12.10 and 12.11 while retaining the local Windows baseline. -One low-level feasibility follow-up can cover 13.01 through 13.08, with separate policies before any debugger, kernel, DBVM, driver, or raw allocation run. -These are suggested scopes only, and this document does not create or claim any linked issue. - -## Release interpretation - -Section 12 and section 13 remain outside the stable blocking path, but each row needs a linked follow-up or explicit unsupported disposition before roadmap closure. -Section 14 remains blocked until the exact stable package is built, qualified, published, downloaded, and smoke-tested. -A parent roadmap checkbox may be checked only after review confirms that the complete row, not a nearby subcase, has the required evidence. diff --git a/docs/qualification/v2.0.0/settings-observation.md b/docs/qualification/v2.0.0/settings-observation.md deleted file mode 100644 index 15df10f..0000000 --- a/docs/qualification/v2.0.0/settings-observation.md +++ /dev/null @@ -1,44 +0,0 @@ -# Settings reload observations - -Status: historical unexecuted preparation, superseded by the [MCP-only closeout](mcp-closeout.md). The earlier Release build, two 859-file preflights and Astra review passed. Neither the ten standalone cases nor the proposed live session was authorized or executed; integration supersedes the primary-checkout inputs, so future runs require fresh preparation and review. - -## Evidence and purpose - -The [retained-key experiment](lifecycle-key-fix.md) failed with a measured zero-row checklist. Its registry change was rolled back after the source premise was disproved. The ordinary product distribution remains unchanged; there is no demonstrated fix for the empty list or historical SDK stack failure. - -Pinned CE's reload returns early for a missing form or a runtime class other than `TformSettings`, and its broad exception handler can swallow an error before checklist population. `getSettingsForm()` wraps the existing form rather than creating it. The [object binding](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaObject.pas#L91-L96) exposes the runtime class through the read-only [ClassName property](https://github.com/cheat-engine/cheat-engine/blob/ec45d5f47f92a239ba0bf51ec5d04a7509c3fd37/Cheat%20Engine/LuaObject.pas#L123-L132). - -The fixed bridge now reads that class and the checklist count before reload. A row-count refusal adds both observations beside the existing post-reload count. Class text is capped at 128 characters, and the complete existing error receipt remains ASCII-sanitized and capped at 1,024 characters. These observations do not change settings, populate rows, reload the plugin, or weaken the exact one-row/name guard. The rest of the existing lifecycle scenario is unchanged. - -The new evidence can identify a class mismatch and distinguish a list already empty before reload from a changed list. It cannot by itself prove that the reload completed, identify a swallowed exception, or establish why an unchanged zero-row list is empty. Matching the expected class only rules out that one pinned-source guard; installed binary/source equivalence is still unproven. A passing lifecycle attempt would not demonstrate that these extra reads fixed anything. - -The additional source review confirmed that normal Settings opening and its page-change handler provide no independent checklist-refresh operation. The guard preserves non-plugin user settings and both private hosts share that guarded CE settings tree. No specific mismatched registry value has been identified as the cause. No internal flags, native addresses, user settings or checklist objects are modified by this observation patch. - -## Prepared sequence - -1. Run the ten exact `NativeLuaToolRuntimeTests.LivePluginBridge_*` cases in one owned process with the same staged Lua DLL and 60-second deadline. The two new `LivePluginBridge_ReloadChangesRows_ReportsObservedClassAndBeforeAfterCounts` theory cases assert independently observed pre/post counts and class text, refusal before key messages/Settings apply, and unchanged enabled state. Existing eight cases retain their earlier scope. Require ten passed, zero failed/skipped before continuing. -2. If all ten pass and the complete sequence is approved, run one maintained x64 `lifecycle` session with the unchanged ordinary SDK package. Keep the same two private CE copies, owned targets, original prelude/six workflow groups, three plugin cycles and target/gateway/host restarts described in [lifecycle-resume.md](lifecycle-resume.md#exact-proposed-workload). All four optional gates remain false; no compiler/injection acknowledgement is set. Stop for evidence review on failure; no automatic retry. - -This preserves the known failing ordering. Its incremental purpose is the bounded Settings observation, not a new product change. Existing settings backup/restoration, refusal of pre-existing user CE/DebugView processes, owned-process cleanup and post-run checks remain in force. No debugger, private SDK trace or additional native entry point is introduced. - -The Release test-project build passed with zero warnings/errors (`artifacts/issue-edits/goal-settings-observation-build.log`). Both no-execution preflights verified 859 files including all 527 unchanged product sources and the original distribution. Native tests have not been run under the earlier stop; this prepared sequence requires a new execution decision because previous approvals are consumed. - -| Prepared input | SHA-256 | -| --- | --- | -| Test module | `F767C901F32DA587DFB04723EE0339D9181BB88CCC3C66355BA842B35A7F1411` | -| Standalone manifest | `B2AAA114D396827219BEA835EB1A4C66536DA176F113388545438C7D2C3F628F` | -| Lifecycle manifest | `529ADA4C471080611FE176033EBC5C9EDBEE2A3A0D3C6CF00467C47605CE34BA` | -| Ordinary plugin | `1620DD5978C4996BA80DEB5B81403EB3B695B3D029C1D1D533A3F39254D5A97F` | -| Ordinary gateway | `ADB97797600614CEF21C7F5E2352F95E9102A3D4DFCE48DA4F5B19335E65CA88` | - -Prepared commands, only after approval: - -```powershell -& artifacts/issue-edits/Invoke-SettingsObservationNativeTests.ps1 -Execute -# Proceed only after verifying all ten exact cases passed. -& artifacts/issue-edits/Invoke-SettingsObservation.ps1 -Execute -``` - -These runs cannot satisfy the complete requirements of #28, #31 or #33. Final clean-candidate checks, the broader native matrix, compiler/injection qualification, installation/client/environment cases, soak and publication remain separate requirements. - -Independent Astra high review found no actionable issues in the read-only observations, exact row/name guards, bounded errors, two new cases, both manifests or conditional runners. It specifically confirmed that an expected class with unchanged zero rows remains ambiguous. Source whitespace and runner syntax checks passed. No new native pass is claimed. diff --git a/docs/qualification/v2.0.0/stack-trace-diagnostic.md b/docs/qualification/v2.0.0/stack-trace-diagnostic.md deleted file mode 100644 index 6830c27..0000000 --- a/docs/qualification/v2.0.0/stack-trace-diagnostic.md +++ /dev/null @@ -1,71 +0,0 @@ -# Private stack trace diagnostic - 2026-10-09 - -Status: the user separately approved one instrumented session, and run `20261009T111321Z-688b` passed. This is private diagnostic evidence, not a production fix or stable-release candidate. The four approved ordinary-SDK sessions and this one instrumented session are complete. No further live run is scheduled or authorized by this one-session approval. - -## Prepared workload - -Run exactly one `ResourcePreludeMemoryNamedScanThenAob` session through the maintained Release live harness, using two private CE copies and two owned x64 targets. Preserve the existing read-only resource/runtime prelude, bounded memory/named-scan/AOB operations, 64-byte non-executable post-AOB allocation/free and final sentinel reads. All four optional gates remain false. The private harness creates each host's absolute trace directory before launch and forwards the trace flag only for the diagnostic scenario. Normal environment scrubbing removes inherited trace flags. - -The harness retains its existing five-minute host lifetime, refusal while user CE/DebugView processes are running, ownership-based cleanup, user-settings restoration and source-installation checks. The runner requires the exact prepared hashes and one new session, then decodes both hosts' trace files and checks their process identities, package identity, gates, restoration and recorded-process absence. Missing, invalid, exhausted or frozen traces require review. There is no automatic retry or expansion to another workload. - -Execution entry point: `artifacts/issue-edits/Invoke-StackTraceLive.ps1 -Execute`. Without `-Execute`, the script only checks the prepared hashes; that dry preflight passed for **235 inputs**. `goal-stack-trace-run-manifest.json` holds the absolute inputs, selected case and hashes. No execution approval is inferred from the dry preflight. - -## Instrumentation and interpretation - -SDK source was archived from `325c47b573f8bd39a247f1d0101f110fa36c1696`, then modified in `artifacts/dependencies/sdk-stack-trace`. Source manifests retain the base archive identity and hashes for all 1,285 source entries, including changed/new files. The private MCP snapshot derives from the dirty `0f722ef7d6b37eee0985073dfe594c6559b31c14` working tree: 849 source entries, with explicit private package/version/lockfile/notices and harness changes. Neither source identity represents a clean published commit. - -The private SDK measures actual Lua stack heights separately from saved frame heights around dispatch, callback disposal, frame restoration, AOB acquisition and scanner waits. The protected-operation refusal records the exact existing guard observation and required inputs before the original exception. Dispatch/parent identities, OS and managed threads, runtime epoch/generation and nesting depth allow correlation across the known synchronization boundary. Different callback/provider Lua pointers are expected under CE's per-thread state contract and do not alone show a defect. - -Tracing adds native `lua_gettop` measurements and changes timing. It can therefore perturb the reproduction and can itself encounter an invalid state. A numeric probe-intent record precedes each added measurement; a missing result can also mean concurrent exhaustion or freezing and does not alone prove a native fault. Ordinary probes stop when full/frozen. A below-saved-height observation is a diagnostic anomaly to inspect, not independently a proven corruption cause. - -The writer uses a fixed file mapping: 128-byte header, 32,768 ordinary 96-byte slots and one dedicated first-anomaly slot. Slots never overwrite earlier evidence; their sequence is published last. Managed trace setup/write errors disable tracing without replacing SDK operation results or cleanup. Existing files are never overwritten. The first-anomaly slot is retained even after ordinary overflow. The reader omits unpublished slots and reports incomplete admitted records. Evidence survives the tested abrupt process termination; machine-crash/power-loss durability is not claimed. - -## Verified package identities - -| Input | SHA-256 | -| --- | --- | -| Private SDK `2.0.1-mcpstacktrace.1` NuGet package | `5B9501B338F52253CB012AACBD906878C4FF479A11346C6A02638D7C44688365` | -| Private MCP `2.0.0-mcpstacktrace.1` plugin | `86CBA116A248F11728D36A4112E310B0C81D8FFF2E1DEFD40F8C06048C3E5605` | -| Private Native AOT gateway | `B400424A25392DAF3B8E86C12B5CF23BD5364D198E691A2A8CD3BFE47EA5C350` | -| Original published SDK 2.0.0 native bridge | `B008C8D8C136187F241542E6223DC0831999D8300DC2C4C01E1CF49F6FBA7698` | -| Reviewed installed CE x64 executable | `CF6CCC664A90C23531EC20F5C41458CFDC1D04679D6D05ED94C06BCECF95544F` | - -The package directory is `artifacts/mcp-stack-trace/artifacts/dist/stack-trace1`. PE resource inspection extracted/decompressed and compared all seven embedded SDK assemblies plus the native bridge against the private NuGet package. The bridge hash was also checked against the published SDK 2.0.0 bytes. `goal-stack-trace-provenance.json` records these results. Product files and dependency references in the primary checkout were not changed for this diagnostic. - -## Completed offline checks - -- [x] SDK Release build: zero warnings/errors. -- [x] Focused managed recorder tests: **9 passed, 0 failed, 0 skipped**; NativeLua excluded. -- [x] Actual opt-in facade in an owned helper process: terminate with the mapping open and undisposed, then recover **16 committed records, 0 incomplete**, with matching PID, sequence, context, saved/actual values and synthetic state. Disabled tracing, existing-file collision and missing-directory containment also passed. -- [x] Private local publish: isolated actual-DLL checks in two fresh contexts, each **193 tools, 43 resources, 14 templates, 43 prompts**; Native AOT gateway build and no-instance smoke; five-file distribution. -- [x] Portable MCP regressions: initial **4,134 passed, 2 failed, 0 skipped** out of 4,136. Both failures were the private SDK version missing from notices. Corrected the private notices, retained licenses/attribution and reran the complete notices class: **3 passed, 0 failed, 0 skipped**. No later full-suite pass is claimed. -- [x] Script syntax and no-execution hash preflight passed. -- [x] Independent Astra high review: resolved managed-fault cleanup interference, mapping cleanup, first-anomaly accounting, the final-slot probe edge and mapped-memory disposal admission. Final source, package and runner review found no remaining actionable findings in this scope; it does not establish the native root cause. - -Logs and manifests are retained under `artifacts/issue-edits/goal-stack-trace-*`; source and binaries remain local artifacts. The historical crash, production fix, full lifecycle reruns and remaining issue gates are still unresolved. No issue closure, commit, push, tag or release follows from this evidence. - -## Approved instrumented session - -After the explicit one-session approval, the prepared command completed with **1 passed, 0 failed, 0 skipped**, total 18.433 seconds. Run `20261009T111321Z-688b` used the exact plugin/gateway hashes above. The resource prelude, memory/named-scan/AOB sequence, post-AOB allocation/free and final sentinel checks passed. The allocation was 64 bytes and non-executable; release reported complete success. All four optional gates were false. User state was restored, source installation was unchanged, cleanup failures were empty, and all four recorded owned process IDs were absent afterward. - -| Observation | Host A | Host B | -| --- | --- | --- | -| Committed trace records | 1,753 | 121 | -| Paired synchronized dispatches | 71 | 5 | -| Observed frame restorations | 74, including 3 AOB frames | 5 | -| Underflow, restoration mismatch, protected-input refusal or probe failure | 0 | 0 | -| Overflow, incomplete or frozen records | 0 | 0 | - -All 76 synchronized callback entries were depth 1 with no parent. The 200 depth-2 inline scopes were ordinary child calls inside their callbacks. Host A's three AOB waits lasted **4.8749, 2.4261 and 4.7970 ms**, each beginning and ending at stack height 0. No traced synchronized callback or inline call reentered during those waits. Different callback/provider Lua pointers remain expected; no pointer-equality requirement is inferred. - -Independent Astra review checked raw trace/summary hashes, event pairing, state/thread identity, nesting, stack observations and cleanup. No actionable discrepancy was found. This establishes balanced observations at the instrumented boundaries in one successful run. It does not reproduce the historical crash, identify its cause, exercise the synchronized-reentry hypothesis or rule out uninstrumented CE callbacks. - -Evidence: `goal-stack-trace-live-20261009T111321Z-688b.json`, decoded `goal-stack-trace-20261009T111321Z-688b-{A,B}.json`, and `goal-stack-trace-analysis-20261009T111321Z-688b.json`. The original preparation manifest intentionally retains its preparation-time authorization flag; subsequent execution was authorized explicitly in the conversation. - -### Offline trace validator controls - -`StackTraceValidation.ps1` classifies decoded measurements, incomplete/frozen/overflow evidence, paired AOB waits and synchronized callback entries strictly inside a wait on the same OS thread. It reports parent/depth correlation without treating ordinary inline nesting or unequal caller/provider state pointers as corruption. It is an observation classifier, not a complete Lua execution proof. - -`Test-StackTraceValidation.ps1` passed **11 synthetic controls and both recorded host snapshots**: balanced measurements, actual nested thunk with differing expected provider/callback pointers, timestamp ties at either wait boundary resolved by sequence order, pre-restore underflow, post-restore mismatch, unknown-height handling, unrelated-thread exclusion, interrupted records, ordinary inline nesting, and diagnostic evidence categories. The two recorded snapshots produced no findings/evidence/reentry and three paired waits total. Logs and script hashes are in `goal-stack-trace-validator-tests.log` and `goal-stack-trace-validator-hashes.json`. - -The remaining diagnostic gap is an evidence-backed reproduction of the original failure or synchronized reentry; another identical successful baseline would not resolve it. No additional live workload has been scheduled. diff --git a/docs/qualification/v2.0.0/support-matrix.md b/docs/qualification/v2.0.0/support-matrix.md deleted file mode 100644 index e6075a0..0000000 --- a/docs/qualification/v2.0.0/support-matrix.md +++ /dev/null @@ -1,105 +0,0 @@ -# Stable v2.0.0 support matrix - -Status: Reviewed stable-scope proposal; exact release-candidate qualification remains incomplete. - -This document defines the minimum intended stable boundary and does not claim that the release candidate has passed qualification. -The final supported and qualified rows require evidence from the exact release candidate package. -The current 121-item status and ownership crosswalk is in [requirement-matrix.md](requirement-matrix.md). - -## Product boundary - -- The observed qualification baseline is Windows 11 Pro x64 build `10.0.26300`, and no broader Windows version is currently claimed as qualified. -- The Cheat Engine plugin host is x64 only. -- The gateway is the self-contained `win-x64` executable from the matching package. -- The plugin requires the x64 .NET 10, ASP.NET Core 10, and Windows Desktop 10 runtimes. -- The plugin and gateway must come from one build and must report matching version and source identity. -- External debugger and decompiler integration is outside the stable v2.0.0 boundary. -- General desktop automation and target-application UI automation are outside the stable v2.0.0 boundary. - -## Minimum compatibility matrix - -| Component | Intended stable boundary | Candidate qualification requirement | Current status | -| --- | --- | --- | --- | -| Windows | Windows 11 Pro x64 build `10.0.26300` is the only observed candidate environment | Record the exact edition, version, build, and architecture used for every native, soak, installation, and release result; broader Windows x64 versions remain unqualified until separately evidenced. | Local candidate environment observed; final package pending. | -| Cheat Engine host | Cheat Engine 7.7 x64 | Qualify the exact installed host through the harness fingerprint and reviewed autorun checks. | Baseline only. | -| Installed candidate environment | Cheat Engine 7.7.1.10828 x64 | Run the exact candidate package and record the dynamic installation fingerprint, reviewed autorun result, and cleanup. | Local working package passed both native fixture runs; final clean candidate pending. | -| Plugin runtime | `Microsoft.NETCore.App`, `Microsoft.AspNetCore.App`, and `Microsoft.WindowsDesktop.App` 10.0.x x64 | Record every exact runtime patch used by the candidate qualification. | Pending qualification. | -| Gateway runtime | Self-contained `win-x64` Native AOT executable | Run the executable MCP smoke check from the exact candidate distribution. | Local Debug and Release executables passed; final clean candidate pending. | -| Target architectures | x86 and x64 Windows user-mode targets | Exercise independent disposable fixtures for each architecture and record pointer width, target identity, and cleanup. | Both local fixture architectures passed the maintained scenario and restart checks. | -| MCP transport | Local stdio gateway to authenticated per-activation loopback backends | Verify explicit `instanceId` routing, stale-instance refusal, and two-instance isolation. | Pending candidate qualification. | -| MCP clients | Clients named by the release documentation | Run packaged first-use registration, discovery, selection, resources, prompts, completions, errors, and cleanup in every named client. | No client is qualified by this draft. | - -The exact Windows build used during planning and bounded candidate checks is `10.0.26300`, but that fact is not final-package qualification. -The local working package passed the bounded native cases recorded in [local-checkpoint.md](local-checkpoint.md). -That evidence must not be generalized to every advertised domain or to an untested final stable package. -The live harness already accepts and fingerprints an installed Cheat Engine 7.7-or-newer x64 host dynamically, so no new static profile is required for this build. - -## Capability classification - -Every advertised domain has a current classification below, and the final release evidence must update the evidence without silently broadening the classification. - -- `qualified` means the exact candidate package passed its required portable and native cases on the supported environment. -- `optional_environment` means the contract and refusal paths are stable, but successful use depends on an installed runtime, collector, driver, hypervisor, compiler, or target property. -- `experimental` means the typed contract is available but its native coverage or operating boundary is intentionally narrower than the stable promise. -- `unsupported` means the release documents the boundary and does not advertise successful operation. - -`experimental` in this table means that the typed contract is present but the full roadmap workflow row has not passed on an exact stable candidate. - -| Advertised domain | Current classification | Evidence and limitation | -| --- | --- | --- | -| AOB | experimental | Portable coverage exists, but the complete module, range, protection, alignment, mapped-memory, uniqueness, and real-fixture signature matrix has not passed. | -| Auto Assembler | optional_environment | Generation and portable gate behavior exist, while apply and cleanup require the capability gate, a target, and a separately reviewed execution policy for native qualification. | -| Code analysis | experimental | The maintained live smoke covered disassembly columns only, so functions, CFG, strings, references, dissection jobs, cancellation, and limits remain unqualified. | -| Debugger | optional_environment | Portable and NativeLua coverage exists, but debugger interfaces, capture, register mutation, stepping, tracing, and cleanup need a reviewed debugger policy and native fixture. | -| .NET | optional_environment | The domain depends on a compatible managed target and collector, and no controlled managed fixture has completed the roadmap matrix. | -| Execution | optional_environment | Calls, C compilation, injection, and invocation require target-code-execution and separate policies; only bounded C# compiler phases 1 through 4 have passed. | -| Instance discovery | experimental | Portable routing and the historical two-instance smoke passed, but the exact final gateway still needs stale-record, same-name, withdrawal, and client qualification. | -| Kernel and DBVM | optional_environment | Typed refusal paths exist, but unavailable hardware or drivers do not qualify successful translation, physical-memory, or watch behavior. | -| Lua | experimental | Fixed Lua has broad NativeLua evidence, while unrestricted `lua_execute`, main-thread behavior, and lifecycle transitions have not completed native qualification. | -| Memory | experimental | The maintained live smoke proved bounded scalar read and write cases, but the full scalar, array, batch, string, byte-order, unreadable-page, and partial-write matrix remains open. | -| Modules | experimental | Portable coverage exists, but the complete PE, import, export, and patch comparison matrix has not passed on the stable candidate. | -| Mono and IL2CPP | optional_environment | Collector availability, target runtime, attachment, object layout, searches, and invocation require controlled fixtures and a separate invocation policy. | -| Pointers | experimental | `pointer_get_access_info` and `pointer_read_chains` have bounded x86 and x64 evidence, but pointer maps, rescans, incomplete captures, and native file fidelity remain open. | -| Processes | experimental | Selection and target restart have bounded live evidence, but create, open-file, pause, pointer-width override, and full recovery behavior remain open. | -| Records | experimental | The maintained live smoke proved create, update, freeze, unfreeze, and delete on an owned record, but the full hierarchy, dropdown, script, rollback, and preservation matrix remains open. | -| Runtime | experimental | Portable lifecycle and resource-ledger coverage exists, but repeated native activation, all ownership classes, failed cleanup, and full recovery remain open. | -| Scans | experimental | The maintained live smoke covered main and independent scanner isolation, but every value mode, range, cancellation, mapped-memory, and lifecycle case remains open. | -| Speedhack | optional_environment | One owned x86 and x64 fixture scenario proved speed restoration and cross-instance isolation, while supported-target boundaries and target switching remain open. | -| Structures | experimental | Portable coverage exists, but native autoguess, nested layout, PDB and .NET fill, read and write, header generation, and CE-owned persistence remain open. | -| Symbols | experimental | Portable coverage exists, but exact resolution, PDB layouts, reload, source state, and registered-symbol ownership need candidate qualification. | -| Tables | experimental | File policy and portable coverage exist, but load, save, merge, malformed content, scripts, rollback, partial effects, and user-owned preservation remain open. | -| Utilities | qualified | The pure managed calculation and value-conversion contract passed the clean `808a626e65eddab113737a27a7c2345555b1c241` portable candidate; the final stable commit must repeat the portable suite. | - -No advertised domain other than Utilities is classified as fully qualified by the current evidence. -The broad native smoke is evidence only for the cases named above and is not a domain-wide pass. -Kernel and DBVM unavailability on ordinary Windows configurations is an expected supported outcome and is not evidence that successful kernel operation works. -LBR, shared allocation, DBK map or unmap, caller-selected CR3 access, raw kernel allocation, and broader native-host support remain unsupported unless separately designed and qualified. - -## P1 stable inclusion - -All three P1 features are intended for stable v2.0.0 when their acceptance criteria in [acceptance.md](acceptance.md) are met. -`pointer_get_access_info` and `pointer_read_chains` are included in the planned stable contract. -`exec_compile_csharp` passed bounded compiler feasibility on the clean local candidate recorded in [compiler-checkpoint.md](compiler-checkpoint.md). -Unavailable-compiler, shared-temp expiry, reload, separate injection, and final stable-package qualification remain open. -If a P1 feature proves infeasible, it requires an explicit linked follow-up, the recorded blocking evidence, and a reviewed roadmap decision before contract freeze. -No P1 feature may be silently deferred to fit a work session or release schedule. - -## Compatibility policy - -The stable v2.0.0 contract freezes tool names, arguments, result and error shapes, resource URIs, prompts, and gateway routing semantics after the selected P1 changes land. -Additive optional fields and new tools may be added in compatible v2 patch or minor work when old callers keep their behavior. -Removing or renaming a tool, changing an existing field meaning, changing pointer-chain order, or changing host-effect reporting requires a future reviewed breaking contract. -Future deprecations require release-note notice, keep the old behavior through the current major version, and remove it only in a reviewed breaking release. -Before that freeze, the current working tree proposes the explicit beta-to-stable corrections in release-notes.md: nullable omissions, corrected collector/effect descriptions and removal of the prefetchable exports resource in favor of its tool. -They are intentional compatibility changes awaiting final contract review, not an assertion of additive compatibility; requirement 2.07 remains partial until their disposition and migration guidance are accepted. -Configuration keeps the current precedence of built-in defaults, deployment settings, user settings, and dedicated environment overrides. -The four capability switches remain exposure controls and do not become a sandbox. -File and table roots remain empty by default. -Migration from beta.2 must preserve user configuration, tables, unrelated plugins, Cheat Engine settings, and rollback paths. -The beta tags remain unchanged, and stable publication uses a new `v2.0.0` tag at the exact final binary source commit. - -## Release-stopping defects - -The release stops for a crash, corruption, cross-instance effect, cross-target effect, stale-epoch work, unreported partial or unknown mutation, leaked owned state, broken install or upgrade, incorrect package identity, or wrong success reporting. -The release also stops when cleanup claims success after an orphan, a destructive capability bypasses its gate, an uncertain mutation is replayed automatically, or a supported workflow cannot be recovered using its documented path. -An optional capability that is honestly unavailable is not a release-stopping defect when its refusal and limitation match the reviewed contract. diff --git a/externals/CheatEngine.Client b/externals/CheatEngine.Client index 5c0d305..101b13e 160000 --- a/externals/CheatEngine.Client +++ b/externals/CheatEngine.Client @@ -1 +1 @@ -Subproject commit 5c0d3057c040920665579ca3b0f464f3cd865317 +Subproject commit 101b13e2caa8bfdfd5cec6ae2f96b9e32dfe5691 diff --git a/externals/CheatEngine.SDK b/externals/CheatEngine.SDK index 7c5b681..1e45f32 160000 --- a/externals/CheatEngine.SDK +++ b/externals/CheatEngine.SDK @@ -1 +1 @@ -Subproject commit 7c5b681a4d8055129d7d51b28931e2f6c6d87361 +Subproject commit 1e45f32edfba80e4a7ef8f62f404e617067cbfed diff --git a/libs/CheatEngine.Mcp.Core/Jobs/JobRegistry.cs b/libs/CheatEngine.Mcp.Core/Jobs/JobRegistry.cs index 7a16b59..759fa60 100644 --- a/libs/CheatEngine.Mcp.Core/Jobs/JobRegistry.cs +++ b/libs/CheatEngine.Mcp.Core/Jobs/JobRegistry.cs @@ -254,8 +254,9 @@ public LuaJob StartLua(string kind, TimeSpan timeToLive, int buffe /// /// Starts a managed job on the thread pool. The slot is reserved atomically and the work starts after the job is - /// registered; it never runs on Cheat Engine's main thread and is never waited for there. Each dispatch the work - /// makes must stay within the dispatch budget. + /// registered only while its tracked resource remains active and the activation accepts starts; it never runs on + /// Cheat Engine's main thread and is never waited for there. Each dispatch the work makes must stay within the + /// dispatch budget. /// /// The item type. /// The job kind, a lowercase identifier of at most 32 characters, such as pointermap. @@ -289,7 +290,27 @@ public ManagedJob StartManaged(string kind, TimeSpan timeToLive, i throw; } - job.Run(work, OnManagedJobEnded, _dispatch.Client.Stopping); + bool started; + try + { + started = _resources.TryStartTracked(job, + () => job.Run(work, OnManagedJobEnded, _dispatch.Client.Stopping)); + } + catch + { + ResourceReleaseOutcome release = job.Release(CancellationToken.None); + Remove(job, release.IsComplete); + throw; + } + + if (!started) + { + ResourceReleaseOutcome release = job.Release(CancellationToken.None); + Remove(job, release.IsComplete); + throw CheatEngineToolException.Busy( + "The plugin activation is stopping, or the job was released before its work could start.", StopHint); + } + return job; } @@ -510,11 +531,11 @@ private void Register(McpJob job) lock (_lock) { _reserved--; + // Track before publishing to the registry, under the same lock. If deactivation closed resource tracking, + // the started Lua record remains unmanaged for TargetResources.ReleaseAll to clean from the ledger. + _resources.Track(job, () => Remove(job, job.CleanupError is null)); _jobs.Add(job.Id, job); } - - // Released by TargetResources: a completed stop leaves a tombstone, a failed cleanup stays with its Lua entry. - _resources.Track(job, () => Remove(job, job.CleanupError is null)); } /// Drops a job and its items; a completed stop leaves a tombstone until the job's TTL would have ended. diff --git a/libs/CheatEngine.Mcp.Core/Jobs/LuaJobKernelScripts.cs b/libs/CheatEngine.Mcp.Core/Jobs/LuaJobKernelScripts.cs index af024b6..937d478 100644 --- a/libs/CheatEngine.Mcp.Core/Jobs/LuaJobKernelScripts.cs +++ b/libs/CheatEngine.Mcp.Core/Jobs/LuaJobKernelScripts.cs @@ -701,8 +701,8 @@ local function synchronized(_, ...) """; /// - /// Releases, newest first, the entries that hold host state and that no managed handle tracks, stopping at the first - /// failed cleanup, or only lists them. Arguments: namespace, the tracked ids, whether to release. + /// Releases this activation's untracked entries and, when requested, entries from earlier activations, stopping at + /// the first failed cleanup. Arguments: namespace, the tracked ids, whether to include earlier activations. /// internal const string StateReleaseUnmanaged = Kernel + "\n" + """ stateCheckNamespace(a[1]) @@ -714,22 +714,20 @@ local function synchronized(_, ...) end) local released = {} local failed - if a[3] then - for _, entry in ipairs(entries) do - if entry.cleanupError == nil then - local done - if entry.family == 'job' then - jobFinish(entry, 'stopped') - done = jobRelease(entry) - else - done = resourceRelease(entry, 'released') - end - if not done then - failed = stateDescribe(entry, now) - break - end - released[#released + 1] = stateDescribe(entry, now) - end + for _, entry in ipairs(entries) do + if (entry.namespace == a[1] or a[3]) and entry.cleanupError == nil then + local done + if entry.family == 'job' then + jobFinish(entry, 'stopped') + done = jobRelease(entry) + else + done = resourceRelease(entry, 'released') + end + if not done then + failed = stateDescribe(entry, now) + break + end + released[#released + 1] = stateDescribe(entry, now) end end local remaining = {} diff --git a/libs/CheatEngine.Mcp.Core/Jobs/ManagedJob.cs b/libs/CheatEngine.Mcp.Core/Jobs/ManagedJob.cs index c8c609f..bdfd79f 100644 --- a/libs/CheatEngine.Mcp.Core/Jobs/ManagedJob.cs +++ b/libs/CheatEngine.Mcp.Core/Jobs/ManagedJob.cs @@ -40,6 +40,7 @@ public sealed class ManagedJob : McpJob private const int ActivationEnd = 3; private readonly CancellationTokenSource _cancellation = new(); + private readonly Lock _startLock = new(); private readonly TimeProvider _time; private readonly JobWriter _writer; private Task _completion = Task.CompletedTask; @@ -47,6 +48,7 @@ public sealed class ManagedJob : McpJob private ITimer? _expiryTimer; private int _state = (int) JobState.Running; private int _stopReason; + private bool _started; internal ManagedJob(JobStart start, TimeProvider time) : base(start) @@ -150,28 +152,39 @@ internal override void CancelWithoutJoin() RequestStop(ActivationEnd); } - /// Starts the work on the thread pool. + /// Starts the work on the thread pool unless the job was already stopped. /// The work. /// Runs on the work's thread once the job reached its final state. /// The activation's stopping token. - internal void Run(Func, CancellationToken, Task> work, Action ended, + /// when the work was queued; otherwise the job had already stopped. + internal bool Run(Func, CancellationToken, Task> work, Action ended, CancellationToken stopping) { ArgumentNullException.ThrowIfNull(work); ArgumentNullException.ThrowIfNull(ended); - TimeSpan due = ExpiresUtc - _time.GetUtcNow(); - ITimer timer = _time.CreateTimer(static state => ((ManagedJob) state!).Expire(), this, - due > TimeSpan.Zero ? due : TimeSpan.Zero, Timeout.InfiniteTimeSpan); - Volatile.Write(ref _expiryTimer, timer); - try - { - Volatile.Write(ref _completion, - Task.Run(() => RunAsync(work, ended, stopping), CancellationToken.None)); - } - catch + lock (_startLock) { - Interlocked.Exchange(ref _expiryTimer, null)?.Dispose(); - throw; + if (State is not JobState.Running || IsEnded) + { + return false; + } + + TimeSpan due = ExpiresUtc - _time.GetUtcNow(); + ITimer timer = _time.CreateTimer(static state => ((ManagedJob) state!).Expire(), this, + due > TimeSpan.Zero ? due : TimeSpan.Zero, Timeout.InfiniteTimeSpan); + Volatile.Write(ref _expiryTimer, timer); + try + { + Volatile.Write(ref _completion, + Task.Run(() => RunAsync(work, ended, stopping), CancellationToken.None)); + _started = true; + return true; + } + catch + { + Interlocked.Exchange(ref _expiryTimer, null)?.Dispose(); + throw; + } } } @@ -191,6 +204,7 @@ private async Task RunAsync(Func, CancellationToken, Task> work { try { + token.ThrowIfCancellationRequested(); await work(_writer, token).ConfigureAwait(false); final = InterruptedAfterWork() ?? JobState.Completed; } @@ -281,13 +295,33 @@ private JobState Finalize(JobState proposed) /// Asks a running job to stop; the first reason wins. Cancellation callbacks run on the thread pool. /// Why it stops. - private void RequestStop(int reason) + private bool RequestStop(int reason) { - Interlocked.CompareExchange(ref _stopReason, reason, NoStop); - if (Interlocked.CompareExchange(ref _state, (int) JobState.Stopping, (int) JobState.Running) == - (int) JobState.Running) + bool cancel = false; + bool endedBeforeStart = false; + lock (_startLock) + { + Interlocked.CompareExchange(ref _stopReason, reason, NoStop); + if (Interlocked.CompareExchange(ref _state, (int) JobState.Stopping, (int) JobState.Running) == + (int) JobState.Running) + { + if (_started) + { + cancel = true; + } + else + { + Volatile.Write(ref _state, (int) Interrupted()); + endedBeforeStart = true; + } + } + } + + if (cancel) { _ = _cancellation.CancelAsync(); } + + return endedBeforeStart; } } diff --git a/libs/CheatEngine.Mcp.Core/Targets/McpStateLedger.cs b/libs/CheatEngine.Mcp.Core/Targets/McpStateLedger.cs index ba31e8b..06eff1a 100644 --- a/libs/CheatEngine.Mcp.Core/Targets/McpStateLedger.cs +++ b/libs/CheatEngine.Mcp.Core/Targets/McpStateLedger.cs @@ -116,18 +116,18 @@ internal LuaStateSnapshot Snapshot(CancellationToken cancellationToken) /// /// Releases, newest first, the entries that hold host state and that no managed handle tracks: every entry of an - /// earlier activation and any untracked entry of this one. It stops at the first failed cleanup, which stays - /// recorded for manual recovery. With false it only lists them. Inside a dispatch. + /// untracked entries of this activation, plus earlier activations when is true. + /// It stops at the first failed cleanup, which stays recorded for manual recovery. Inside a dispatch. /// /// The ids this activation tracks in . - /// Whether to release them or only list them. + /// Whether to release entries from earlier activations too. /// The token of the enclosing dispatch body. /// What was released and what remains. - internal LuaStateRelease ReleaseUnmanaged(IReadOnlyCollection managedIds, bool release, + internal LuaStateRelease ReleaseUnmanaged(IReadOnlyCollection managedIds, bool includeOrphans, CancellationToken cancellationToken) { return Dispatch.ExecuteLua("mcp_state_release", LuaJobKernelScripts.StateReleaseUnmanaged, - StateJsonContext.Default.LuaStateRelease, cancellationToken, Namespace, managedIds, release); + StateJsonContext.Default.LuaStateRelease, cancellationToken, Namespace, managedIds, includeOrphans); } /// diff --git a/libs/CheatEngine.Mcp.Core/Targets/TargetResources.cs b/libs/CheatEngine.Mcp.Core/Targets/TargetResources.cs index 8c3617d..11e5540 100644 --- a/libs/CheatEngine.Mcp.Core/Targets/TargetResources.cs +++ b/libs/CheatEngine.Mcp.Core/Targets/TargetResources.cs @@ -34,6 +34,7 @@ public sealed class TargetResources : ITargetTransitionGuard private readonly List _entries = []; private readonly Lock _lock = new(); private readonly TimeProvider _time; + private bool _acceptingTracks = true; private long _sequence; /// Creates the activation's resource list. @@ -70,6 +71,21 @@ public int Count } } + /// Atomically closes resource registration before activation cleanup begins. + /// + /// An in-flight request may finish its Cheat Engine dispatch just before deactivation and only then register its + /// ownership handle. Closing this gate linearizes that registration with cleanup: an earlier track is included in + /// the release snapshot, and a later Lua-state track is rejected while its ledger entry remains eligible for + /// unmanaged cleanup. Existing tracked resources remain releasable. + /// + internal void StopAcceptingTracks() + { + lock (_lock) + { + _acceptingTracks = false; + } + } + /// /// Refuses a target change while a tracked resource or an unmanaged Lua state entry holds host state; ended /// resources that hold none are ignored, and a reselection of the selected process is always allowed. Inside the @@ -191,6 +207,11 @@ public void Track(ITargetResource resource, Action? onReleased = null) string id = resource is ClientLeaseResource lease ? lease.Id : resource.Descriptor.Id; lock (_lock) { + if (!_acceptingTracks) + { + throw new InvalidOperationException("The plugin activation is stopping and no new resources can be tracked."); + } + if (_entries.Exists(entry => ReferenceEquals(entry.Resource, resource) || string.Equals(entry.Id, id, StringComparison.Ordinal))) { @@ -201,6 +222,25 @@ public void Track(ITargetResource resource, Action? onReleased = null) } } + /// Starts managed work only while its ownership is still tracked and the activation accepts starts. + /// The registered resource whose start is protected. + /// Queues the work without waiting for it or dispatching to Cheat Engine. + /// when the resource was tracked and its start callback ran. + internal bool TryStartTracked(ITargetResource resource, Func start) + { + ArgumentNullException.ThrowIfNull(resource); + ArgumentNullException.ThrowIfNull(start); + lock (_lock) + { + if (!_acceptingTracks || !_entries.Exists(entry => ReferenceEquals(entry.Resource, resource))) + { + return false; + } + + return start(); + } + } + /// Stops tracking a resource that its owner released; its release callback does not run. /// The resource. /// when it was tracked. @@ -308,12 +348,13 @@ public IReadOnlyList ReportEndedResources(CancellationToken ca /// /// Releases every tracked resource newest first, because an older one may be a dependency of a newer one, and stops /// at the first release that does not complete. A retryable handle stays tracked for a later attempt; one that - /// needs manual recovery is reported and forgotten. Then, through the ledger, it releases the unmanaged Lua state - /// entries when is set, or only lists them. Inside a dispatch; it never throws + /// needs manual recovery is reported and forgotten. Then, through the ledger, it releases untracked entries from + /// this activation and releases earlier-activation entries only when is set. + /// Inside a dispatch; it never throws /// for a failed cleanup: use to report an incomplete release. /// /// The token of the enclosing dispatch body. - /// Whether to release the orphans of earlier activations and untracked Lua state too. + /// Whether to also release orphans from earlier activations. /// What was released, the first incomplete release and what still holds host state. public ReleaseAllResult ReleaseAll(CancellationToken cancellationToken, bool includeOrphans = false) { @@ -352,6 +393,8 @@ public ReleaseAllResult ReleaseAll(CancellationToken cancellationToken, bool inc return new ReleaseAllResult(released, null, RemainingManaged()); } + // Always clean untracked state owned by this activation. Earlier-activation orphans remain listed unless the + // caller explicitly opted in to releasing them. LuaStateRelease state = Ledger.ReleaseUnmanaged(ManagedIds(), includeOrphans, cancellationToken); released.AddRange(state.Released.Select(entry => new ReleasedResource(Ledger.Describe(entry), ResourceReleaseOutcome.Released()))); diff --git a/srcs/CheatEngine.Mcp.Plugin/CheatEngineMcpPluginBuilderExtensions.cs b/srcs/CheatEngine.Mcp.Plugin/CheatEngineMcpPluginBuilderExtensions.cs index 1e3b703..e317d4c 100644 --- a/srcs/CheatEngine.Mcp.Plugin/CheatEngineMcpPluginBuilderExtensions.cs +++ b/srcs/CheatEngine.Mcp.Plugin/CheatEngineMcpPluginBuilderExtensions.cs @@ -69,6 +69,7 @@ internal ICheatEngineMcpBuilder AddCheatEngineMcpExecution(IConfiguration config mcp.Services.AddSingleton(Options.Create(features)); mcp.Services.AddOptions() .Bind(configuration.GetSection(McpExecutionOptions.SectionName)); + mcp.Services.TryAddScoped(); mcp.Services.TryAddScoped(); return mcp.AddExecutionServices(); } diff --git a/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs b/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs index 4f796b1..ddc5aff 100644 --- a/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs +++ b/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs @@ -38,13 +38,18 @@ internal static class PluginLuaToolRuntime return item.Caption == a[1] """; - internal static bool ShowPluginStatus(ICheatEngineClient client, string state, string details, bool existingOnly) + internal static bool ShowPluginStatus(ICheatEngineClient client, string state, string details, bool existingOnly, + PluginLifecycleLuaScope? lifecycleScope = null) { ArgumentNullException.ThrowIfNull(client); if (!client.Dispatcher.IsMainThread) { throw new InvalidOperationException("The MCP status must be updated on Cheat Engine's main thread."); } + if (state == "Disabled" && lifecycleScope?.IsDeactivationNotification(client) != true) + { + throw new InvalidOperationException("The disabled MCP status can only be reported during deactivation notification."); + } LuaToolOperation operation = new("mcp_status", LuaToolRuntime.BuildSource(StatusUpdateSource, ["MCP: " + state, details, existingOnly])); @@ -56,6 +61,24 @@ internal static bool ShowPluginStatus(ICheatEngineClient client, string state, s return result is true; } + internal static bool IsLifecycleCleanupOperation(string operation) + { + return operation is "mcp_job_stop" or "mcp_state_release_resource" or "mcp_state_release"; + } + + internal static LuaJsonResult ExecuteLifecycleCleanup(string operation, string source, + JsonTypeInfo resultType, LuaJsonBufferPool buffers, LuaOpaqueValueHandling opaque) + { + LuaJsonOperation request = new(operation, source, resultType, buffers, opaque); + if (request.TryExecuteInRuntime(out LuaJsonResult result, out CheatEngineFailure failure)) + { + return result; + } + + failure.Throw(); + throw new InvalidOperationException("A failed Lua cleanup did not throw its recorded failure."); + } + internal static T Execute(ICheatEngineClient client, string operation, string body, JsonTypeInfo resultType, CancellationToken cancellationToken, params ReadOnlySpan arguments) { @@ -234,6 +257,11 @@ public bool TryExecute(ILuaExecutionContext context, out LuaJsonResult result { ArgumentNullException.ThrowIfNull(context); context.ThrowIfExpired(); + return TryExecuteInRuntime(out result, out failure); + } + + internal bool TryExecuteInRuntime(out LuaJsonResult result, out CheatEngineFailure failure) + { result = default; if (!TryAcquire(out LuaRuntimeOperation acquired, out failure)) { @@ -379,14 +407,104 @@ private object ReadTable(LuaState state, int index, int depth) return values; } } + +} + +/// Tracks one activation's synchronous fixed-Lua deactivation windows without process-wide mutable state. +internal sealed class PluginLifecycleLuaScope +{ + private ActiveScope? _active; + + internal IDisposable BeginResourceCleanup(ICheatEngineClient client) + { + return Begin(client, ScopeKind.ResourceCleanup, + "MCP resource cleanup requires the stopping activation's Cheat Engine main thread."); + } + + internal IDisposable BeginDeactivationNotification(ICheatEngineClient client) + { + return Begin(client, ScopeKind.DeactivationNotification, + "MCP lifecycle notification requires the stopping activation's Cheat Engine main thread."); + } + + internal bool IsResourceCleanup(ICheatEngineClient client) + { + return IsCurrent(client, ScopeKind.ResourceCleanup); + } + + internal bool IsDeactivationNotification(ICheatEngineClient client) + { + return IsCurrent(client, ScopeKind.DeactivationNotification); + } + + private Scope Begin(ICheatEngineClient client, ScopeKind kind, string error) + { + ArgumentNullException.ThrowIfNull(client); + if (!client.Stopping.IsCancellationRequested || !client.Dispatcher.IsMainThread) + { + throw new InvalidOperationException(error); + } + + ActiveScope? previous = Volatile.Read(ref _active); + ActiveScope scope = new(client, kind, Environment.CurrentManagedThreadId, previous); + Volatile.Write(ref _active, scope); + return new Scope(this, scope); + } + + private bool IsCurrent(ICheatEngineClient client, ScopeKind kind) + { + ActiveScope? scope = Volatile.Read(ref _active); + return scope is not null && scope.Kind == kind && ReferenceEquals(scope.Client, client) + && scope.ThreadId == Environment.CurrentManagedThreadId + && client.Stopping.IsCancellationRequested && client.Dispatcher.IsMainThread; + } + + private void Exit(ActiveScope scope) + { + if (!ReferenceEquals(Volatile.Read(ref _active), scope)) + { + throw new InvalidOperationException("MCP lifecycle Lua scopes must be disposed in reverse order."); + } + + Volatile.Write(ref _active, scope.Previous); + } + + private enum ScopeKind + { + ResourceCleanup, + DeactivationNotification + } + + private sealed record ActiveScope(ICheatEngineClient Client, ScopeKind Kind, int ThreadId, ActiveScope? Previous); + + private sealed class Scope(PluginLifecycleLuaScope owner, ActiveScope scope) : IDisposable + { + private int _disposed; + + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) == 0) + { + owner.Exit(scope); + } + } + } } /// Provides Core with fixed Lua execution through the active Plugin Client. -internal sealed class PluginFixedLuaExecutor(ICheatEngineClient client) : IFixedLuaExecutor +internal sealed class PluginFixedLuaExecutor(ICheatEngineClient client, + PluginLifecycleLuaScope? lifecycleLuaScope = null) + : IFixedLuaExecutor { public LuaJsonResult Execute(string operation, string source, JsonTypeInfo resultType, LuaJsonBufferPool buffers, LuaOpaqueValueHandling opaque, CancellationToken cancellationToken) { + if (!cancellationToken.CanBeCanceled && lifecycleLuaScope?.IsResourceCleanup(client) == true + && PluginLuaToolRuntime.IsLifecycleCleanupOperation(operation)) + { + return PluginLuaToolRuntime.ExecuteLifecycleCleanup(operation, source, resultType, buffers, opaque); + } + return PluginLuaToolRuntime.ExecuteRaw(client, operation, source, resultType, buffers, opaque, cancellationToken); } diff --git a/srcs/CheatEngine.Mcp.Plugin/McpServerModule.cs b/srcs/CheatEngine.Mcp.Plugin/McpServerModule.cs index 79466aa..8eefaec 100644 --- a/srcs/CheatEngine.Mcp.Plugin/McpServerModule.cs +++ b/srcs/CheatEngine.Mcp.Plugin/McpServerModule.cs @@ -1,6 +1,8 @@ using CheatEngine.Client; using CheatEngine.Client.Modules; +using CheatEngine.Mcp.Core.Targets; using CheatEngine.Mcp.Hosting.Backend; +using CheatEngine.Mcp.Plugin.Lua; namespace CheatEngine.Mcp.Plugin; @@ -12,8 +14,11 @@ namespace CheatEngine.Mcp.Plugin; internal sealed class McpServerModule( IMcpBackendHostFactory backends, McpPrimitiveTargets targets, - McpStatusIndicator status) - : ICheatEngineClientModule, IDisposable + McpStatusIndicator status, + TargetResources resources, + PluginLifecycleLuaScope lifecycleLuaScope) + : ICheatEngineClientModule, ICheatEngineClientModuleDeactivationNotification, + ICheatEngineClientResourceCleanup, IDisposable { private const int Created = 0; private const int Starting = 1; @@ -23,6 +28,7 @@ internal sealed class McpServerModule( private IMcpBackendHost? _server; private int _lifecycleState; + private int _reportDisabledStatus; public void OnEnabled(ICheatEngineClient client) { @@ -82,18 +88,48 @@ public void OnEnabled(ICheatEngineClient client) public void OnDisabling(ICheatEngineClient client) { int state = Interlocked.Exchange(ref _lifecycleState, Stopping); + resources.StopAcceptingTracks(); IMcpBackendHost? server = Volatile.Read(ref _server); server?.StopAccepting(); if (server is not null && state == Running) { - status.Report(client, "Disabled"); + Volatile.Write(ref _reportDisabledStatus, 1); + } + } + + public void OnDeactivated(ICheatEngineClient client) + { + if (Interlocked.Exchange(ref _reportDisabledStatus, 0) == 0) + { + return; + } + + // The status menu belongs to the CE session, not this activation. Update its fixed text after all module + // shutdown callbacks and before releasing activation resources. + using (lifecycleLuaScope.BeginDeactivationNotification(client)) + { + client.Dispatcher.Invoke(() => status.Report(client, "Disabled", lifecycleLuaScope: lifecycleLuaScope)); + } + } + + public void OnReleasingResources(ICheatEngineClient client) + { + if (resources.Count == 0 && resources.Ledger is null) + { + return; + } + + // This stage runs after module shutdown and before Client leases drain. Keep normal Client dispatcher admission, + // while permitting only MCP-owned fixed cleanup scripts through the attached SDK runtime. + using (lifecycleLuaScope.BeginResourceCleanup(client)) + { + client.Dispatcher.Invoke(() => resources.ReleaseAll(CancellationToken.None).ThrowIfIncomplete()); } - // Hosting now drains its global lease stack in reverse creation order. } public void Dispose() { - // Hosting disposes modules after draining leases. Never join HTTP workers on CE's main thread. + // Hosting releases module-owned resources before Client leases, then disposes modules. Never join HTTP workers on CE's main thread. Interlocked.Exchange(ref _lifecycleState, Disposed); IMcpBackendHost? server = Interlocked.Exchange(ref _server, null); if (server is not null) diff --git a/srcs/CheatEngine.Mcp.Plugin/McpStatusIndicator.cs b/srcs/CheatEngine.Mcp.Plugin/McpStatusIndicator.cs index 67bb018..ce8d867 100644 --- a/srcs/CheatEngine.Mcp.Plugin/McpStatusIndicator.cs +++ b/srcs/CheatEngine.Mcp.Plugin/McpStatusIndicator.cs @@ -14,12 +14,16 @@ internal sealed partial class McpStatusIndicator( ILogger logger) { /// Shows a state; a failure is logged, never thrown, so it cannot change the lifecycle outcome. - internal void Report(ICheatEngineClient client, string state, string? endpoint = null) + internal void Report(ICheatEngineClient client, string state, string? endpoint = null, + PluginLifecycleLuaScope? lifecycleLuaScope = null) { try { string details = Describe(state, discovery.Value.InstanceName, log, endpoint); - bool updated = state == "Disabled" ? ShowDisabled(client, details) : Show(client, state, details); + bool updated = state == "Disabled" + ? ShowDisabled(client, details, lifecycleLuaScope ?? throw new InvalidOperationException( + "The disabled status requires its lifecycle notification scope.")) + : Show(client, state, details); if (updated) { LogStatusShown(logger, state); @@ -36,9 +40,10 @@ internal static bool Show(ICheatEngineClient client, string state, string detail return PluginLuaToolRuntime.ShowPluginStatus(client, state, details, false); } - internal static bool ShowDisabled(ICheatEngineClient client, string details) + internal static bool ShowDisabled(ICheatEngineClient client, string details, PluginLifecycleLuaScope lifecycleLuaScope) { - return PluginLuaToolRuntime.ShowPluginStatus(client, "Disabled", details, true); + ArgumentNullException.ThrowIfNull(lifecycleLuaScope); + return PluginLuaToolRuntime.ShowPluginStatus(client, "Disabled", details, true, lifecycleLuaScope); } internal static string Describe(string state, string instanceName, PluginLog log, string? endpoint = null) diff --git a/srcs/CheatEngine.Mcp.Plugin/README.md b/srcs/CheatEngine.Mcp.Plugin/README.md index 29ac67e..efefaa3 100644 --- a/srcs/CheatEngine.Mcp.Plugin/README.md +++ b/srcs/CheatEngine.Mcp.Plugin/README.md @@ -99,6 +99,8 @@ sequenceDiagram CE->>Plugin: OnDisabling Plugin->>Registry: withdraw this activation record Plugin->>Backend: reject new requests with 503 + CE->>Plugin: OnDeactivated + Plugin->>CE: update the session-long Disabled status item Plugin->>Backend: stop asynchronously during disposal ``` @@ -108,10 +110,13 @@ endpoint in the status item. A startup failure withdraws and stops any partially created backend before the enable failure is returned to Cheat Engine. `OnDisabling` immediately withdraws this activation's discovery record and -asks the backend to reject new requests. It does not wait for HTTP workers on -Cheat Engine's main thread. Client Hosting drains activation-owned leases, then -`Dispose` initiates the bounded backend shutdown in the background. Repeated -shutdown calls share the backend's shutdown task. +asks the backend to reject new requests. It does not run Lua or wait for HTTP +workers on Cheat Engine's main thread. The separate deactivation notification +updates the session-long Disabled status item after shutdown callbacks finish; +the item remains in Cheat Engine after this activation ends. Client Hosting +then releases module-owned and Client-owned resources, and `Dispose` initiates +the bounded backend shutdown in the background. Repeated shutdown calls share +the backend's shutdown task. Each activation receives its own generated ID and bearer token. After the listener binds, the backend publishes a descriptor containing its loopback diff --git a/srcs/CheatEngine.Mcp.Tools/Debugger/DebuggerLuaScripts.cs b/srcs/CheatEngine.Mcp.Tools/Debugger/DebuggerLuaScripts.cs index 6f30b7c..5c71edc 100644 --- a/srcs/CheatEngine.Mcp.Tools/Debugger/DebuggerLuaScripts.cs +++ b/srcs/CheatEngine.Mcp.Tools/Debugger/DebuggerLuaScripts.cs @@ -6,6 +6,15 @@ namespace CheatEngine.Mcp.Tools.Debugger; /// Fixed, bounded Lua bodies for debugger functions missing from CheatEngine.Client. internal static class DebuggerLuaScripts { + internal const string BreakpointIdNormalization = """ + local function normalizeBreakpointHandle(value) + local id = type(value) == 'table' and value.ID or value + assert(type(id) == 'number' and math.type(id) == 'integer' and id > 0, + 'Cheat Engine did not return a usable breakpoint ID') + return value + end + """; + internal const string Status = DebuggerStateScripts.ReadContextStopped + "\n" + """ local function read(name, ...) local value = _G[name](...) @@ -170,7 +179,7 @@ local function attached() """; // Arguments: namespace, resource id, address expression, size, trigger, method, thread id, one-shot. - internal const string SetBreakpoint = LuaJobKernelScripts.Kernel + "\n" + """ + internal const string SetBreakpoint = LuaJobKernelScripts.Kernel + "\n" + BreakpointIdNormalization + "\n" + """ assert(debug_isDebugging(), 'Debugger is not attached') local address = getAddressSafe(a[3]) assert(address ~= nil, 'The breakpoint address could not be resolved') @@ -184,18 +193,38 @@ local function attached() end local method = a[6] == 'default' and nil or methods[a[6]] assert(a[6] == 'default' or method ~= nil, 'Unknown breakpoint method') + local breakpointProcessId = getOpenedProcessID() + assert(math.type(breakpointProcessId) == 'integer' and breakpointProcessId > 0, + 'No process is selected') local breakpointId = nil + local breakpointHandle = nil + local breakpointAccepted = false local function release(entry) - if breakpointId ~= nil then - assert(debug_removeBreakpointByID(breakpointId) ~= false, - 'Cheat Engine rejected removal of the MCP-owned breakpoint') - breakpointId = nil - end + assert(getOpenedProcessID() == entry.processId, + 'The selected target changed before the owned breakpoint could be safely removed') + if not breakpointAccepted then return end + local ownedHandle = breakpointHandle or breakpointId + assert(ownedHandle ~= nil, + 'Cheat Engine accepted the MCP-owned breakpoint without a usable ID; manual recovery is required') + local usable, validation = pcall(normalizeBreakpointHandle, ownedHandle) + assert(usable, validation) + local removed, failure = debug_removeBreakpointByID(ownedHandle) + assert(removed, failure or 'Cheat Engine rejected removal of the MCP-owned breakpoint') + breakpointId = nil + breakpointHandle = nil + breakpointAccepted = false end local function oneShot() - local removed = breakpointId == nil or debug_removeBreakpointByID(breakpointId) ~= false - assert(removed, 'Cheat Engine rejected removal of the one-shot breakpoint') + local ownedHandle = breakpointHandle or breakpointId + assert(breakpointAccepted and ownedHandle ~= nil, + 'Cheat Engine accepted the one-shot breakpoint without a usable ID; manual recovery is required') + assert(getOpenedProcessID() == breakpointProcessId, + 'The selected target changed before the one-shot breakpoint could be safely removed') + local removed, failure = debug_removeBreakpointByID(ownedHandle) + assert(removed, failure or 'Cheat Engine rejected removal of the one-shot breakpoint') breakpointId = nil + breakpointHandle = nil + breakpointAccepted = false resourceForget(a[1], a[2]) return 1 end @@ -221,14 +250,26 @@ local function oneShot() else accepted, id = debug_setBreakpoint(address, a[4], trigger, method, callback) end - assert(accepted == true and id ~= nil, 'Cheat Engine rejected the breakpoint') - breakpointId = id + assert(accepted == true, 'Cheat Engine rejected the breakpoint') + breakpointAccepted = true + breakpointHandle = id + local normalized, normalizedId = pcall(normalizeBreakpointHandle, id) + if not normalized then + local tracked, trackingFailure = pcall(resourceRecord, a[1], a[2], 'breakpoint', + {address = address, size = a[4], name = 'MCP debugger breakpoint', detail = a[5]}, release) + assert(tracked, 'Cheat Engine created a breakpoint with an unusable ID and its cleanup could not be tracked: ' + .. tostring(normalizedId) .. '; ' .. tostring(trackingFailure)) + error(normalizedId, 0) + end + breakpointId = normalizedId local recorded, record = pcall(resourceRecord, a[1], a[2], 'breakpoint', {address = address, size = a[4], name = 'MCP debugger breakpoint', detail = a[5]}, release) if not recorded then - local removed = debug_removeBreakpointByID(breakpointId) + assert(getOpenedProcessID() == breakpointProcessId, + 'The selected target changed before the untracked breakpoint could be safely removed') + local removed, failure = debug_removeBreakpointByID(breakpointHandle) + assert(removed, failure or 'Breakpoint creation succeeded but state recording failed and cleanup was rejected') breakpointId = nil - assert(removed ~= false, 'Breakpoint creation succeeded but state recording failed and cleanup was rejected') error(record, 0) end return {resourceId = a[2], address = string.format('%X', address), trigger = a[5], size = a[4], @@ -399,7 +440,7 @@ local accepted // Arguments: namespace, job id, buffer limit, ttl ms, address, trigger, size, aggregate by instruction, group by // effective address. - internal static string StartCapture => LuaJobKernelScripts.Strategies + "\n" + $$""" + internal static string StartCapture => LuaJobKernelScripts.Strategies + "\n" + BreakpointIdNormalization + "\n" + $$""" local function captureContext(trigger, includeStack) local wide = targetIs64Bit() local ip = wide and RIP or EIP @@ -493,11 +534,23 @@ local function captureGroup(current, key, context, effective, size) job.groupKeys = {} job.total = 0 job.onStop = function(current) - if current.breakpointId ~= nil and getOpenedProcessID() == current.processId then - assert(debug_removeBreakpointByID(current.breakpointId) ~= false, - 'Cheat Engine rejected removal of the capture breakpoint') + local breakpointAccepted = current.breakpointAccepted == true + if breakpointAccepted or current.breakpointId ~= nil or current.breakpointHandle ~= nil then + assert(getOpenedProcessID() == current.processId, + 'The selected target changed before the capture breakpoint could be safely removed') + end + local breakpointHandle = current.breakpointHandle or current.breakpointId + if breakpointAccepted then + assert(breakpointHandle ~= nil, + 'Cheat Engine accepted the capture breakpoint without a usable ID; manual recovery is required') + local usable, validation = pcall(normalizeBreakpointHandle, breakpointHandle) + assert(usable, validation) + local removed, failure = debug_removeBreakpointByID(breakpointHandle) + assert(removed, failure or 'Cheat Engine rejected removal of the capture breakpoint') + current.breakpointAccepted = false end current.breakpointId = nil + current.breakpointHandle = nil end job.callback = jobEvent(job, function(current) if getOpenedProcessID() ~= current.processId then @@ -520,8 +573,10 @@ elseif a[8] then return 0 end, 0) local accepted, id = debug_setBreakpoint(address, a[7], triggers[a[6]], job.callback) - assert(accepted == true and id ~= nil, 'Cheat Engine rejected the capture breakpoint') - job.breakpointId = id + assert(accepted == true, 'Cheat Engine rejected the capture breakpoint') + job.breakpointAccepted = true + job.breakpointHandle = id + job.breakpointId = normalizeBreakpointHandle(id) end) return {jobId = job.id, processId = job.processId, address = string.format('%X', address), lifetimeSeconds = math.floor(a[4] / 1000)} @@ -530,7 +585,7 @@ elseif a[8] then // Arguments: namespace, job id, buffer limit, ttl ms, address, mode, maximum steps, condition register, condition value, include stack. // Without a condition register the trace runs for the maximum steps; with one it also stops after the first // context whose register (or IP) equals the condition value, the entry context included. - internal const string StartTrace = LuaJobKernelScripts.Strategies + "\n" + """ + internal const string StartTrace = LuaJobKernelScripts.Strategies + "\n" + BreakpointIdNormalization + "\n" + """ local function traceContext(includeStack) local wide = targetIs64Bit() local ip = wide and RIP or EIP @@ -577,12 +632,25 @@ local function conditionReached(context) job.total = 0 job.onStop = function(current) if current.hook ~= nil and _G.debugger_onBreakpoint == current.hook then _G.debugger_onBreakpoint = nil end - if current.breakpointId ~= nil then + local breakpointAccepted = current.breakpointAccepted == true + if breakpointAccepted or current.breakpointId ~= nil or current.breakpointHandle ~= nil then assert(getOpenedProcessID() == current.processId, 'The selected target changed before the trace entry breakpoint could be safely removed') - assert(debug_removeBreakpointByID(current.breakpointId) ~= false, - 'Cheat Engine rejected removal of the trace entry breakpoint') + end + local breakpointHandle = current.breakpointHandle or current.breakpointId + if breakpointAccepted then + assert(breakpointHandle ~= nil, + 'Cheat Engine accepted the trace entry breakpoint without a usable ID; manual recovery is required') + local usable, validation = pcall(normalizeBreakpointHandle, breakpointHandle) + assert(usable, validation) + assert(getOpenedProcessID() == current.processId, + 'The selected target changed before the trace entry breakpoint could be safely removed') + local removed, failure = debug_removeBreakpointByID(breakpointHandle) + assert(removed, failure or 'Cheat Engine rejected removal of the trace entry breakpoint') + -- CE can defer removal requested from the active breakpoint callback until continue. current.breakpointId = nil + current.breakpointHandle = nil + current.breakpointAccepted = false end end local function append(current, context) @@ -633,15 +701,17 @@ .. tostring(timer)):sub(1, 4096) assert(_G.debugger_onBreakpoint == nil, 'Another debugger_onBreakpoint hook appeared during trace setup') _G.debugger_onBreakpoint = job.hook local accepted, id = debug_setBreakpoint(address, 1, bptExecute) - assert(accepted == true and id ~= nil, 'Cheat Engine rejected the trace entry breakpoint') - job.breakpointId = id + assert(accepted == true, 'Cheat Engine rejected the trace entry breakpoint') + job.breakpointAccepted = true + job.breakpointHandle = id + job.breakpointId = normalizeBreakpointHandle(id) end) return {jobId = job.id, processId = job.processId, address = string.format('%X', address), lifetimeSeconds = math.floor(a[4] / 1000)} """; // Arguments: namespace, job id, buffer limit, ttl ms, address. - internal const string StartRunTo = LuaJobKernelScripts.Strategies + "\n" + """ + internal const string StartRunTo = LuaJobKernelScripts.Strategies + "\n" + BreakpointIdNormalization + "\n" + """ assert(debug_isDebugging(), 'Debugger is not attached') assert(debug_getContext(false), 'Debugger has no stopped context') assert(type(debug_removeBreakpointByID) == 'function', @@ -652,11 +722,23 @@ .. tostring(timer)):sub(1, 4096) job.processId = getOpenedProcessID() assert(math.type(job.processId) == 'integer' and job.processId > 0, 'No process is selected') job.onStop = function(current) - if current.breakpointId ~= nil and getOpenedProcessID() == current.processId then - assert(debug_removeBreakpointByID(current.breakpointId) ~= false, - 'Cheat Engine rejected removal of the run-to breakpoint') + local breakpointAccepted = current.breakpointAccepted == true + if breakpointAccepted or current.breakpointId ~= nil or current.breakpointHandle ~= nil then + assert(getOpenedProcessID() == current.processId, + 'The selected target changed before the run-to breakpoint could be safely removed') + end + local breakpointHandle = current.breakpointHandle or current.breakpointId + if breakpointAccepted then + assert(breakpointHandle ~= nil, + 'Cheat Engine accepted the run-to breakpoint without a usable ID; manual recovery is required') + local usable, validation = pcall(normalizeBreakpointHandle, breakpointHandle) + assert(usable, validation) + local removed, failure = debug_removeBreakpointByID(breakpointHandle) + assert(removed, failure or 'Cheat Engine rejected removal of the run-to breakpoint') + current.breakpointId = nil + current.breakpointHandle = nil + current.breakpointAccepted = false end - current.breakpointId = nil end job.callback = jobEvent(job, function(current) if getOpenedProcessID() ~= current.processId then @@ -667,8 +749,10 @@ .. tostring(timer)):sub(1, 4096) return 1 end, 1) local accepted, id = debug_setBreakpoint(address, 1, bptExecute, job.callback) - assert(accepted == true and id ~= nil, 'Cheat Engine rejected the run-to breakpoint') - job.breakpointId = id + assert(accepted == true, 'Cheat Engine rejected the run-to breakpoint') + job.breakpointAccepted = true + job.breakpointHandle = id + job.breakpointId = normalizeBreakpointHandle(id) end) debug_continueFromBreakpoint(co_run) return {jobId = job.id, processId = job.processId, address = string.format('%X', address), diff --git a/srcs/CheatEngine.Mcp.Tools/Mono/MonoTools.cs b/srcs/CheatEngine.Mcp.Tools/Mono/MonoTools.cs index 41c2e86..2cc8f23 100644 --- a/srcs/CheatEngine.Mcp.Tools/Mono/MonoTools.cs +++ b/srcs/CheatEngine.Mcp.Tools/Mono/MonoTools.cs @@ -66,12 +66,15 @@ public MonoAttachResult Attach(CancellationToken cancellationToken = default) // The Lua body records the attachment under this id, so a later detach can tell it from one Cheat Engine made. string id = _resources.NextId("mono"); - MonoAttachResult result = _dispatch.RunLua(CheatEngineToolNames.MonoAttach, MonoLuaScripts.Attach, - MonoJsonContext.Default.MonoAttachResult, cancellationToken, id); - MonoAttachment attachment = new(id, _dispatch); - _resources.Track(attachment, () => _attachment = null); - _attachment = attachment; - return result; + return _dispatch.Run(CheatEngineToolNames.MonoAttach, token => + { + MonoAttachResult result = _dispatch.ExecuteLua(CheatEngineToolNames.MonoAttach, MonoLuaScripts.Attach, + MonoJsonContext.Default.MonoAttachResult, token, id); + MonoAttachment attachment = new(id, _dispatch); + _resources.Track(attachment, () => _attachment = null); + _attachment = attachment; + return result; + }, cancellationToken); } /// Detaches the Mono collector connection and releases this activation's attachment resource. diff --git a/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoop.cs b/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoop.cs index 5916bef..ca58dbe 100644 --- a/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoop.cs +++ b/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoop.cs @@ -26,12 +26,14 @@ internal int ThreadId get; } internal IntPtr CodeStart => _memory; - internal IntPtr EntryAddress => IntPtr.Add(_memory, 9); + internal IntPtr EntryAddress => IntPtr.Add(_memory, 18); internal IntPtr CaptureAddress => EntryAddress; - internal IntPtr RunToAddress => IntPtr.Add(_memory, 23); + internal IntPtr RunToAddress => IntPtr.Add(_memory, 32); internal IntPtr CodeEnd => IntPtr.Add(_memory, DebuggerExecutionLoopEncoding.Length); internal IntPtr ProgressAddress => IntPtr.Add(_memory, ProgressOffset); internal IntPtr HitCounterAddress => IntPtr.Add(_memory, HitOffset); + internal IntPtr PauseAddress => IntPtr.Add(_memory, 0x10C); + internal IntPtr PauseAcknowledgedAddress => IntPtr.Add(_memory, 0x110); internal static DebuggerExecutionLoop Start() { diff --git a/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoopEncoding.cs b/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoopEncoding.cs index b2aab3b..2b9734c 100644 --- a/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoopEncoding.cs +++ b/tests/CheatEngine.Mcp.LiveTarget/DebuggerExecutionLoopEncoding.cs @@ -2,18 +2,21 @@ namespace CheatEngine.Mcp.LiveTarget; internal static class DebuggerExecutionLoopEncoding { - internal const int Length = 55; + internal const int Length = 76; internal static byte[] Encode(nint address, nint sleep) { - byte[] code = [0x83, 0x3D, 0, 0, 0, 0, 0, 0x75, 0x2D, 0xF0, 0xFF, 0x05, 0, 0, 0, 0, - 0xF0, 0xFF, 0x05, 0, 0, 0, 0, 0x90, 0x48, 0x83, 0xEC, 0x28, 0xB9, 0x01, 0, 0, 0, - 0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, - 0xE9, 0xCA, 0xFF, 0xFF, 0xFF, 0xC3]; + byte[] code = [0x83, 0x3D, 0, 0, 0, 0, 0, 0x75, 0x42, 0x83, 0x3D, 0, 0, 0, 0, 0, + 0x75, 0x2D, 0xF0, 0xFF, 0x05, 0, 0, 0, 0, 0xF0, 0xFF, 0x05, 0, 0, 0, 0, 0x90, + 0x48, 0x83, 0xEC, 0x28, 0xB9, 0x01, 0, 0, 0, 0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, + 0xFF, 0xD0, 0x48, 0x83, 0xC4, 0x28, 0xE9, 0xC1, 0xFF, 0xFF, 0xFF, + 0xF0, 0xFF, 0x05, 0, 0, 0, 0, 0xE9, 0xD6, 0xFF, 0xFF, 0xFF, 0xC3]; Patch(code, 2, checked((long) address + 0x100), checked((long) address + 7)); - Patch(code, 12, checked((long) address + 0x104), checked((long) address + 16)); - Patch(code, 19, checked((long) address + 0x108), checked((long) address + 23)); - BitConverter.GetBytes((long) sleep).CopyTo(code, 35); + Patch(code, 11, checked((long) address + 0x10C), checked((long) address + 16)); + Patch(code, 21, checked((long) address + 0x104), checked((long) address + 25)); + Patch(code, 28, checked((long) address + 0x108), checked((long) address + 32)); + BitConverter.GetBytes((long) sleep).CopyTo(code, 44); + Patch(code, 66, checked((long) address + 0x110), checked((long) address + 70)); return code; } diff --git a/tests/CheatEngine.Mcp.LiveTarget/Program.cs b/tests/CheatEngine.Mcp.LiveTarget/Program.cs index aad8784..2504c11 100644 --- a/tests/CheatEngine.Mcp.LiveTarget/Program.cs +++ b/tests/CheatEngine.Mcp.LiveTarget/Program.cs @@ -87,7 +87,9 @@ private static int Main(string[] args) runToAddress = Address(debuggerExecution.RunToAddress), codeEnd = Address(debuggerExecution.CodeEnd), progressAddress = Address(debuggerExecution.ProgressAddress), - hitCounterAddress = Address(debuggerExecution.HitCounterAddress) + hitCounterAddress = Address(debuggerExecution.HitCounterAddress), + pauseAddress = Address(debuggerExecution.PauseAddress), + pauseAcknowledgedAddress = Address(debuggerExecution.PauseAcknowledgedAddress) } }); File.WriteAllText(manifestPath + ".tmp", manifest); diff --git a/tests/CheatEngine.Mcp.Tests/Core/JobRegistryTests.cs b/tests/CheatEngine.Mcp.Tests/Core/JobRegistryTests.cs index 40b83dd..92bcb3b 100644 --- a/tests/CheatEngine.Mcp.Tests/Core/JobRegistryTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Core/JobRegistryTests.cs @@ -26,6 +26,7 @@ public async Task StartManaged_ConcurrentStarts_NeverExceedMaxJobsAndRefuseTheRe StateTestHarness harness = new(new McpExecutionOptions { MaxJobs = 4 }); ConcurrentBag> started = []; ConcurrentBag refused = []; + TaskCompletionSource allStarted = new(TaskCreationOptions.RunContinuationsAsynchronously); int ran = 0; Parallel.For(0, 32, _ => @@ -35,7 +36,10 @@ public async Task StartManaged_ConcurrentStarts_NeverExceedMaxJobsAndRefuseTheRe started.Add(harness.Jobs.StartManaged("probe", TimeSpan.FromSeconds(60), 8, async (_, token) => { - Interlocked.Increment(ref ran); + if (Interlocked.Increment(ref ran) == 4) + { + allStarted.TrySetResult(); + } await Task.Delay(Timeout.Infinite, token).ConfigureAwait(false); })); } @@ -51,7 +55,14 @@ public async Task StartManaged_ConcurrentStarts_NeverExceedMaxJobsAndRefuseTheRe (error.Error.Kind, error.Error.HostEffect, error.Error.Retryable))); Assert.Equal(4, harness.Jobs.Count); Assert.Equal(4, harness.Resources.Count); - harness.Jobs.Dispose(); + try + { + await allStarted.Task.WaitAsync(Wait, Token); + } + finally + { + harness.Jobs.Dispose(); + } await Task.WhenAll(started.Select(static job => job.Completion)).WaitAsync(Wait, Token); Assert.Equal(4, Volatile.Read(ref ran)); Assert.All(started, job => Assert.Equal(JobState.Cancelled, job.State)); @@ -83,6 +94,81 @@ public void StartManaged_BeforeRunThrows_RemovesTheUnstartedJob() Assert.Equal(0, harness.Resources.Count); } + [Fact] + public void StartManaged_RegistrationClosed_DoesNotPublishOrStartTheJob() + { + StateTestHarness harness = new(); + int workRuns = 0; + harness.Resources.StopAcceptingTracks(); + + Assert.Throws(() => harness.Jobs.StartManaged("probe", + TimeSpan.FromSeconds(60), 8, (_, _) => + { + Interlocked.Increment(ref workRuns); + return Task.CompletedTask; + })); + + Assert.Equal((0, 0, 0), (harness.Jobs.Count, harness.Resources.Count, workRuns)); + } + + [Fact] + public async Task StartManaged_TrackWinsBeforeClose_DoesNotStartAfterCleanup() + { + StateTestHarness harness = new(); + TaskCompletionSource registered = new(TaskCreationOptions.RunContinuationsAsynchronously); + TaskCompletionSource continueStart = new(TaskCreationOptions.RunContinuationsAsynchronously); + int workRuns = 0; + Task> start = Task.Run(() => harness.Jobs.StartManaged("probe", + TimeSpan.FromSeconds(60), 8, (_, _) => + { + Interlocked.Increment(ref workRuns); + return Task.CompletedTask; + }, _ => + { + registered.TrySetResult(true); + continueStart.Task.GetAwaiter().GetResult(); + })); + + await registered.Task.WaitAsync(Wait, Token); + harness.Resources.StopAcceptingTracks(); + ReleaseAllResult cleanup = harness.Resources.ReleaseAll(Token); + + Assert.True(cleanup.IsComplete); + Assert.Equal((0, 0), (harness.Jobs.Count, harness.Resources.Count)); + continueStart.TrySetResult(true); + CheatEngineToolException refusal = await Assert.ThrowsAsync(async () => + await start.WaitAsync(Wait, Token)); + + Assert.Equal(ToolErrorKind.Busy, refusal.Error.Kind); + Assert.Equal(0, workRuns); + Assert.Equal("probe", Assert.Single(cleanup.Released).Resource.Kind); + } + + [Fact] + public void StartLua_RegistrationClosesAfterStart_LeavesTheLuaJobForUnmanagedCleanup() + { + StateTestHarness harness = new(); + string? startedId = null; + harness.Answer(_ => new LuaStateRelease( + [StateTestHarness.Entry(startedId!, "job", "stopped", false)], null, [])); + + InvalidOperationException failure = Assert.Throws(() => + harness.Jobs.StartLua("capture", TimeSpan.FromSeconds(60), 8, + StateTestJsonContext.Default.Int64, started => + { + startedId = started.Id; + // The start callback models CE creating the ledger entry before the HTTP worker registers the handle. + harness.Resources.StopAcceptingTracks(); + })); + + Assert.Contains("no new resources", failure.Message, StringComparison.Ordinal); + Assert.Equal((0, 0), (harness.Jobs.Count, harness.Resources.Count)); + ReleaseAllResult cleanup = harness.Resources.ReleaseAll(Token); + Assert.True(cleanup.IsComplete); + Assert.Equal(startedId, Assert.Single(cleanup.Released).Resource.Id); + Assert.Equal("mcp_state_release", Assert.Single(harness.LuaCalls).Operation); + } + [Fact] public async Task StartManaged_TtlExpiresWithoutAnotherRegistryOperation() { diff --git a/tests/CheatEngine.Mcp.Tests/Core/McpStateLedgerTests.cs b/tests/CheatEngine.Mcp.Tests/Core/McpStateLedgerTests.cs index d560d2c..6017e04 100644 --- a/tests/CheatEngine.Mcp.Tests/Core/McpStateLedgerTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Core/McpStateLedgerTests.cs @@ -116,6 +116,25 @@ public void ReleaseAll_IncludeOrphans_ReleasesTrackedResourcesFirstThenTheUnmana Assert.Contains("[3] = true", harness.LuaCalls.Last().Source, StringComparison.Ordinal); } + [Fact] + public void ReleaseAll_WithNoManagedHandles_ReleasesOwnUntrackedStateAndLeavesEarlierActivationOrphans() + { + StateTestHarness harness = new(); + string own = $"pause-{harness.Ledger!.Namespace}-1"; + string orphan = $"pause-{Foreign}-1"; + harness.Answer(_ => new LuaStateRelease( + [StateTestHarness.Entry(own, "resource", "released", false)], null, + [StateTestHarness.Entry(orphan, "resource", "active", true)])); + + ReleaseAllResult result = harness.Resources.ReleaseAll(Token); + + Assert.False(result.IsComplete); + Assert.Equal(own, Assert.Single(result.Released).Resource.Id); + Assert.Equal(orphan, Assert.Single(result.Remaining).Id); + Assert.Equal(0, harness.Resources.Count); + Assert.Equal("mcp_state_release", Assert.Single(harness.LuaCalls).Operation); + } + [Fact] public void TrackState_FailedLuaCleanup_IsReportedThenBlocksUntilAcknowledged() { diff --git a/tests/CheatEngine.Mcp.Tests/Core/TargetResourcesTests.cs b/tests/CheatEngine.Mcp.Tests/Core/TargetResourcesTests.cs index cf19ad1..13f7869 100644 --- a/tests/CheatEngine.Mcp.Tests/Core/TargetResourcesTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Core/TargetResourcesTests.cs @@ -37,6 +37,27 @@ public void Track_ClientLease_IssuesANamespacedIdAndABoundedDescriptor() Assert.Throws(() => resources.Track(tracked)); } + [Fact] + public void StopAcceptingTracks_RejectsLateLuaOwnershipButReleaseAllCleansItsLedgerEntry() + { + StateTestHarness state = new(); + TargetResources resources = state.Resources; + string id = $"pause-{resources.Namespace}-1"; + state.Answer(_ => new LuaStateRelease([StateTestHarness.Entry(id, "resource", "released", false)], null, [])); + + resources.StopAcceptingTracks(); + + InvalidOperationException failure = Assert.Throws(() => + resources.TrackState(id, "pause")); + Assert.Contains("no new resources", failure.Message, StringComparison.Ordinal); + Assert.Empty(resources.List()); + ReleaseAllResult result = resources.ReleaseAll(Token); + + Assert.True(result.IsComplete); + Assert.Equal(id, Assert.Single(result.Released).Resource.Id); + Assert.Equal("mcp_state_release", Assert.Single(state.LuaCalls).Operation); + } + [Fact] public void ReleaseAll_CompleteResources_ReleasesNewestFirstAndForgetsThem() { diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/DebuggerExecutionLoopBytesTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/DebuggerExecutionLoopBytesTests.cs index 9218c5b..8b2a2e4 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/DebuggerExecutionLoopBytesTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/DebuggerExecutionLoopBytesTests.cs @@ -11,13 +11,19 @@ public void Loop_UsesPublishedInstructionOffsetsAndRipRelativeCounters() const long sleep = 0x0000_7FFB_1234_5678; byte[] code = DebuggerExecutionLoopEncoding.Encode(unchecked((nint) start), unchecked((nint) sleep)); - Assert.Equal(55, code.Length); - Assert.Equal((byte) 0xF0, code[9]); // capture: lock inc [rip+hits] - Assert.Equal((byte) 0x90, code[23]); // run-to/step: published NOP - Assert.Equal((byte) 0xC3, code[54]); + Assert.Equal(76, code.Length); + Assert.Equal((byte) 0xF0, code[18]); // capture: lock inc [rip+hits] + Assert.Equal((byte) 0x90, code[32]); // run-to/step: published NOP + Assert.Equal((byte) 0xC3, code[75]); Assert.Equal(start + 0x100, start + 7 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(2, 4))); - Assert.Equal(start + 0x104, start + 16 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(12, 4))); - Assert.Equal(start + 0x108, start + 23 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(19, 4))); - Assert.Equal(sleep, BinaryPrimitives.ReadInt64LittleEndian(code.AsSpan(35, 8))); + Assert.Equal(start + 0x10C, start + 16 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(11, 4))); + Assert.Equal(start + 0x104, start + 25 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(21, 4))); + Assert.Equal(start + 0x108, start + 32 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(28, 4))); + Assert.Equal(start + 0x110, start + 70 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(66, 4))); + Assert.Equal(sleep, BinaryPrimitives.ReadInt64LittleEndian(code.AsSpan(44, 8))); + Assert.Equal(75, 9 + unchecked((sbyte) code[8])); + Assert.Equal(63, 18 + unchecked((sbyte) code[17])); + Assert.Equal(0, 63 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(59, 4))); + Assert.Equal(33, 75 + BinaryPrimitives.ReadInt32LittleEndian(code.AsSpan(71, 4))); } } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualification.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualification.cs new file mode 100644 index 0000000..7ab3fd5 --- /dev/null +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualification.cs @@ -0,0 +1,486 @@ +using System.Net; +using System.Net.Sockets; +using System.Runtime.Versioning; +using System.Text.Json.Nodes; + +using CheatEngine.Mcp.Core.Contract; + +namespace CheatEngine.Mcp.Tests.LiveQualification; + +/// +/// Exercises disable/enable while the activation owns both a target-memory lease and a debugger callback. This is +/// deliberately separate from the clean three-cycle lifecycle qualification: each cleanup oracle is observed while +/// the old listener is down, before a replacement activation can hide it. +/// +[SupportedOSPlatform("windows")] +internal static class LiveActivePluginLifecycleQualification +{ + private static readonly TimeSpan Deadline = TimeSpan.FromSeconds(15); + private const string AllocationName = "active-lifecycle-lease"; + + internal static async Task RunAsync(LiveQualificationInputs inputs) + { + LiveSandboxSession? sandbox = null; + LiveLifecycleSession? connection = null; + Exception? primary = null; + List cleanup = []; + bool disableAttempted = false; + bool disabled = false; + bool reenabled = false; + bool pauseRequested = false; + bool debuggerAttached = false; + bool allocationCreated = false; + bool allocationFreedWhileDisabled = false; + bool removalProbeInstalled = false; + string? firstCaptureId = null; + string? secondCaptureId = null; + string? pauseAddress = null; + ulong breakpointAddress = 0; + try + { + sandbox = await LiveSandboxSession.StartAsync(inputs, LiveSandboxOptions.Smoke with + { + LifecycleQualification = true, + SeedPluginChecklistAtStartup = true, + ActivePluginLifecycleQualification = true, + DebuggerQualification = true + }); + connection = await LiveLifecycleSession.ConnectAsync(sandbox); + await connection.AttachBothAsync(); + await connection.VerifyBothAsync(); + LiveMcpInstanceClient instance = connection.Gateway.Bind(sandbox.HostA.InstanceId); + LiveDebuggerExecutionQualification.Manifest manifest = sandbox.HostA.DebuggerExecution + ?? throw new InvalidDataException("The active lifecycle target omitted its debugger loop manifest."); + manifest.Validate(sandbox.HostA.TargetProcessId); + pauseAddress = manifest.PauseAddress; + breakpointAddress = manifest.Entry; + + JsonNode allocation = await Call(instance, CheatEngineToolNames.MemoryAllocate, new Dictionary + { + ["name"] = AllocationName, + ["size"] = 4096, + ["executable"] = false + }); + allocationCreated = true; + RecordDiagnostic(sandbox, "plugin_lifecycle_active_allocation", allocation.DeepClone()); + ulong allocationAddress = ParseHex(Text(allocation, "address")); + TargetMemoryProbe.MemoryObservation allocationObservation = TargetMemoryProbe.Inspect(sandbox.HostA.TargetProcessId, + allocationAddress, sizeof(int)); + RecordDiagnostic(sandbox, "plugin_lifecycle_active_allocation_prewrite", new + { + address = Hex(allocationAddress), + allocationObservation + }); + JsonNode canary = await Call(instance, CheatEngineToolNames.MemoryWrite, new Dictionary + { + ["address"] = Hex(allocationAddress), + ["valueType"] = "int32", + ["value"] = "1398030675", + ["verify"] = true + }); + Assert.True(canary["verified"]!.GetValue()); + Assert.True(TargetMemoryProbe.IsCommittedAndCovering(sandbox.HostA.TargetProcessId, allocationAddress), + "The active lifecycle allocation was not committed over its canary before disable."); + debuggerAttached = true; + JsonNode attached = await Call(instance, CheatEngineToolNames.DebuggerAttach, + new Dictionary { ["interface"] = "windows" }); + Assert.True(attached["attached"]!.GetValue()); + JsonNode capture = await Call(instance, CheatEngineToolNames.DebuggerStartCapture, new Dictionary + { + ["address"] = Hex(manifest.Entry), + ["trigger"] = "execute", + ["size"] = 1, + ["aggregateByInstruction"] = true, + ["maximumHits"] = 8, + ["lifetimeSeconds"] = 60 + }); + firstCaptureId = Text(capture, "jobId"); + await RequireCapturedHitsAsync(instance, firstCaptureId, 2); + await RequireRunningCaptureAsync(instance, firstCaptureId); + pauseRequested = true; + await RequirePausedAsync(instance, manifest); + await RequirePausedCountersStableAsync(instance, manifest); + await RequireHealthyDebuggerAsync(instance); + JsonNode jobsBeforeDisable = await Call(instance, CheatEngineToolNames.RuntimeListJobs); + JsonNode resourcesBeforeDisable = await Call(instance, CheatEngineToolNames.RuntimeListResources); + Assert.Contains(jobsBeforeDisable["jobs"]!.AsArray(), job => Text(job!, "jobId") == firstCaptureId); + Assert.Contains(resourcesBeforeDisable["resources"]!.AsArray(), resource => + Text(resource!, "kind") == "debugcapture"); + RecordDiagnostic(sandbox, "plugin_lifecycle_active_pre_disable_owned_state", new + { + captureJobId = firstCaptureId, + jobs = jobsBeforeDisable.DeepClone(), + resources = resourcesBeforeDisable.DeepClone() + }); + long hitsBeforeResume = await ReadCounterAsync(instance, manifest.HitCounterAddress); + await SetPauseAsync(instance, manifest.PauseAddress, false); + pauseRequested = false; + long hitsAfterResume = await RequireCounterAdvanceAsync(instance, manifest, hitsBeforeResume); + RecordDiagnostic(sandbox, "plugin_lifecycle_active_target_running_before_disable", new + { + hitsBeforeResume, + hitsAfterResume + }); + string oldEndpoint = sandbox.HostA.Endpoint; + string oldInstance = sandbox.HostA.InstanceId; + + removalProbeInstalled = true; + await sandbox.SetBreakpointRemovalProbeAsync("A", true, TestContext.Current.CancellationToken); + disableAttempted = true; + await sandbox.ReloadPluginAsync("A", false, TestContext.Current.CancellationToken); + disabled = true; + firstCaptureId = null; + await connection.RequireInstancesAsync(sandbox.HostB.InstanceId); + LivePluginDisabledObservation disabledObservation = await sandbox.ObservePluginDisabledAsync("A", manifest.Entry, + TestContext.Current.CancellationToken); + Assert.True(disabledObservation.CleanupRemovalSucceeded, + "The native remover did not return boolean true successfully for the owned breakpoint."); + Assert.False(disabledObservation.BreakpointPresent); + await RequirePortReleasedAsync(oldEndpoint); + Assert.True(TargetMemoryProbe.IsFree(sandbox.HostA.TargetProcessId, allocationAddress), + "The old activation allocation remained committed after plugin disable."); + allocationFreedWhileDisabled = true; + await sandbox.SetBreakpointRemovalProbeAsync("A", false, TestContext.Current.CancellationToken); + removalProbeInstalled = false; + + await sandbox.ReloadPluginAsync("A", true, TestContext.Current.CancellationToken); + reenabled = true; + disabled = false; + await connection.RequireInstancesAsync(sandbox.HostA.InstanceId, sandbox.HostB.InstanceId); + await connection.RejectStaleAsync(oldInstance); + instance = connection.Gateway.Bind(sandbox.HostA.InstanceId); + JsonNode jobs = await Call(instance, CheatEngineToolNames.RuntimeListJobs); + Assert.Empty(jobs["jobs"]!.AsArray()); + JsonNode resources = await Call(instance, CheatEngineToolNames.RuntimeListResources); + Assert.Empty(resources["resources"]!.AsArray()); + debuggerAttached = true; + await Call(instance, CheatEngineToolNames.DebuggerAttach, new Dictionary { ["interface"] = "windows" }); + await RequireNoBreakpointAtAsync(instance, manifest.Entry); + await SetPauseAsync(instance, manifest.PauseAddress, false); + pauseRequested = false; + JsonNode resumedCapture = await Call(instance, CheatEngineToolNames.DebuggerStartCapture, new Dictionary + { + ["address"] = Hex(manifest.Entry), + ["trigger"] = "execute", + ["size"] = 1, + ["aggregateByInstruction"] = true, + ["maximumHits"] = 2, + ["lifetimeSeconds"] = 15 + }); + secondCaptureId = Text(resumedCapture, "jobId"); + await RequireCapturedHitsAsync(instance, secondCaptureId, 1); + JsonNode releasedCapture = await Call(instance, CheatEngineToolNames.RuntimeStopJob, + new Dictionary { ["jobId"] = secondCaptureId }); + Assert.True(releasedCapture["released"]!.GetValue()); + secondCaptureId = null; + await RequireNoBreakpointAtAsync(instance, manifest.Entry); + await Call(instance, CheatEngineToolNames.DebuggerDetach); + debuggerAttached = false; + await connection.VerifyBothAsync(); + sandbox.Record("plugin_lifecycle_active_completed", new + { + allocation, + canary, + capture, + oldEndpoint, + oldInstance, + allocationFreedWhileDisabled, + oldListenerReleasedWhileDisabled = true, + pluginManagerDisabledObservation = disabledObservation, + oldCallbackAbsentAfterReenable = true, + resumedCapture, + releasedCapture + }); + } + catch (Exception exception) + { + primary = exception; + } + finally + { + if (sandbox is not null && removalProbeInstalled) + { + try + { + await sandbox.SetBreakpointRemovalProbeAsync("A", false, CancellationToken.None); + removalProbeInstalled = false; + } + catch (Exception exception) { cleanup.Add(exception); } + } + if (primary is not null && sandbox is not null && connection is not null && allocationCreated + && !allocationFreedWhileDisabled && !disableAttempted) + { + try + { + LiveMcpInstanceClient cleanupInstance = connection.Gateway.Bind(sandbox.HostA.InstanceId); + JsonNode freed = await Call(cleanupInstance, CheatEngineToolNames.MemoryFree, + new Dictionary { ["name"] = AllocationName }); + Assert.True(freed["release"]!["isComplete"]!.GetValue()); + RecordDiagnostic(sandbox, "plugin_lifecycle_active_allocation_failure_cleanup", new + { + attempted = true, + freed + }); + } + catch (Exception exception) + { + cleanup.Add(exception); + RecordDiagnostic(sandbox, "plugin_lifecycle_active_allocation_failure_cleanup", new + { + attempted = true, + error = exception.ToString() + }); + } + } + if (sandbox is not null && connection is not null && disableAttempted && !reenabled) + { + try + { + LivePluginDisabledObservation observed = await sandbox.ObservePluginStateAsync("A", breakpointAddress, + TestContext.Current.CancellationToken); + if (observed.PluginChecked) + { + sandbox.ClearPendingPluginIdentity("A"); + } + else + { + await sandbox.ReloadPluginAsync("A", true, TestContext.Current.CancellationToken); + } + reenabled = true; + disabled = false; + } + catch (Exception exception) { cleanup.Add(exception); } + } + if (connection is not null && sandbox is not null && (reenabled || !disableAttempted) && !disabled + && (pauseRequested || debuggerAttached || firstCaptureId is not null || secondCaptureId is not null)) + { + try + { + LiveMcpInstanceClient cleanupInstance = connection.Gateway.Bind(sandbox.HostA.InstanceId); + await CleanupDebuggerAsync(cleanupInstance, pauseAddress, pauseRequested, debuggerAttached, firstCaptureId, secondCaptureId, cleanup); + pauseRequested = false; + debuggerAttached = false; + firstCaptureId = null; + secondCaptureId = null; + } + catch (Exception exception) { cleanup.Add(exception); } + } + await LiveLifecycleQualification.DisposeOrderedAsync(primary, + connection is null ? null : connection.DisposeAsync, + null, + sandbox is null ? null : () => { sandbox.MarkPassed(); return ValueTask.CompletedTask; }, + sandbox is null ? null : sandbox.DisposeAsync, cleanup); + } + if (cleanup.Count != 0) + { + throw primary is null ? new AggregateException(cleanup) : new AggregateException([primary, .. cleanup]); + } + if (primary is not null) + { + System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(primary).Throw(); + } + } + + private static void RecordDiagnostic(LiveSandboxSession sandbox, string name, object value) + { + try + { + sandbox.Record(name, value); + } + catch { } + } + + private static async Task RequireCapturedHitsAsync(LiveMcpInstanceClient instance, string jobId, long requiredHits) + { + using CancellationTokenSource timeout = new(Deadline); + while (true) + { + JsonNode page = await Call(instance.WithCancellation(timeout.Token), CheatEngineToolNames.DebuggerPollCapture, + new Dictionary { ["jobId"] = jobId, ["limit"] = 8 }); + if (page["hits"]!.AsArray().Any(hit => hit!["hitCount"]!.GetValue() >= requiredHits)) + { + return; + } + await Task.Delay(50, timeout.Token); + } + } + + private static async Task RequireRunningCaptureAsync(LiveMcpInstanceClient instance, string jobId) + { + JsonNode jobs = await Call(instance, CheatEngineToolNames.RuntimeListJobs); + JsonNode job = Assert.Single(jobs["jobs"]!.AsArray(), candidate => candidate is not null && Text(candidate, "jobId") == jobId)!; + Assert.Equal("debugcapture", Text(job, "kind")); + Assert.Equal("running", Text(job, "state")); + } + + private static async Task RequirePausedAsync(LiveMcpInstanceClient instance, LiveDebuggerExecutionQualification.Manifest manifest) + { + await SetPauseAsync(instance, manifest.PauseAddress, true); + using CancellationTokenSource timeout = new(Deadline); + while (true) + { + JsonNode acknowledged = await Call(instance.WithCancellation(timeout.Token), CheatEngineToolNames.MemoryRead, + new Dictionary { ["address"] = manifest.PauseAcknowledgedAddress, ["valueType"] = "int32" }); + if (long.Parse(Text(acknowledged, "value"), System.Globalization.CultureInfo.InvariantCulture) > 0) + { + return; + } + await Task.Delay(50, timeout.Token); + } + } + + private static async Task RequirePausedCountersStableAsync(LiveMcpInstanceClient instance, LiveDebuggerExecutionQualification.Manifest manifest) + { + long hits = await ReadCounterAsync(instance, manifest.HitCounterAddress); + long progress = await ReadCounterAsync(instance, manifest.ProgressAddress); + await Task.Delay(100, TestContext.Current.CancellationToken); + Assert.Equal(hits, await ReadCounterAsync(instance, manifest.HitCounterAddress)); + Assert.Equal(progress, await ReadCounterAsync(instance, manifest.ProgressAddress)); + } + + private static async Task RequireCounterAdvanceAsync(LiveMcpInstanceClient instance, + LiveDebuggerExecutionQualification.Manifest manifest, long initial) + { + using CancellationTokenSource timeout = new(Deadline); + while (true) + { + long current = await ReadCounterAsync(instance, manifest.HitCounterAddress); + if (current > initial) + { + return current; + } + if (timeout.IsCancellationRequested) + { + throw new TimeoutException("The owned target did not resume its heartbeat before the active-plugin disable check."); + } + await Task.Delay(50, timeout.Token); + } + } + + private static async Task RequireHealthyDebuggerAsync(LiveMcpInstanceClient instance) + { + JsonNode status = await Call(instance, CheatEngineToolNames.DebuggerGetStatus); + Assert.True(status["stateValid"]!.GetValue()); + Assert.True(status["attached"]!.GetValue()); + Assert.False(status["broken"]!.GetValue()); + Assert.False(status["stepping"]!.GetValue()); + } + + private static async Task ReadCounterAsync(LiveMcpInstanceClient instance, string address) + { + JsonNode value = await Call(instance, CheatEngineToolNames.MemoryRead, new Dictionary { ["address"] = address, ["valueType"] = "int32" }); + return long.Parse(Text(value, "value"), System.Globalization.CultureInfo.InvariantCulture); + } + + private static async Task CleanupDebuggerAsync(LiveMcpInstanceClient instance, string? pauseAddress, bool pauseRequested, + bool debuggerAttached, string? firstCaptureId, string? secondCaptureId, List cleanup) + { + using CancellationTokenSource timeout = new(Deadline); + foreach (string jobId in new[] { firstCaptureId, secondCaptureId }.Where(static id => id is not null).Cast().Distinct(StringComparer.Ordinal)) + { + try + { + await Call(instance.WithCancellation(timeout.Token), CheatEngineToolNames.RuntimeStopJob, new Dictionary { ["jobId"] = jobId }); + } + catch (Exception exception) { cleanup.Add(exception); } + } + if (pauseRequested && pauseAddress is not null) + { + try + { + await SetPauseAsync(instance, pauseAddress, false); + } + catch (Exception exception) { cleanup.Add(exception); } + } + if (debuggerAttached) + { + try + { + await Call(instance, CheatEngineToolNames.DebuggerDetach); + } + catch (Exception exception) { cleanup.Add(exception); } + } + } + + private static async Task SetPauseAsync(LiveMcpInstanceClient instance, string pauseAddress, bool paused) + { + JsonNode write = await Call(instance, CheatEngineToolNames.MemoryWrite, new Dictionary + { + ["address"] = pauseAddress, + ["valueType"] = "int32", + ["value"] = paused ? "1" : "0", + ["verify"] = true + }); + Assert.True(write["verified"]!.GetValue()); + } + + private static async Task RequireNoBreakpointAtAsync(LiveMcpInstanceClient instance, ulong address) + { + using CancellationTokenSource deadline = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); + deadline.CancelAfter(TimeSpan.FromSeconds(8)); + JsonNode? last = null; + while (true) + { + last = await Call(instance.WithCancellation(deadline.Token), CheatEngineToolNames.DebuggerListBreakpoints, + new Dictionary { ["limit"] = 1024 }); + if (!last["breakpoints"]!.AsArray().Any(item => item is not null && + string.Equals(Text(item, "address"), Hex(address), StringComparison.OrdinalIgnoreCase))) + { + return; + } + + await Task.Delay(TimeSpan.FromMilliseconds(50), deadline.Token); + } + } + + private static async Task RequirePortReleasedAsync(string endpoint) + { + Uri uri = new(endpoint, UriKind.Absolute); + Assert.True(uri.IsLoopback && uri.Port is > 0 and <= 65535, "The old backend endpoint is not a loopback TCP endpoint."); + using CancellationTokenSource timeout = new(Deadline); + while (true) + { + using TcpClient client = new(); + try + { + await client.ConnectAsync(uri.Host, uri.Port, timeout.Token); + } + catch (SocketException) { break; } + if (timeout.IsCancellationRequested) + { + throw new TimeoutException("The old backend endpoint continued accepting TCP connections after plugin disable."); + } + await Task.Delay(50, timeout.Token); + } + IPAddress address = uri.HostNameType == UriHostNameType.IPv6 ? IPAddress.IPv6Loopback : IPAddress.Loopback; + using TcpListener listener = new(address, uri.Port); + listener.Server.ExclusiveAddressUse = true; + listener.Start(); + } + + private static async Task Call(LiveMcpInstanceClient instance, string tool, IReadOnlyDictionary? arguments = null) + { + JsonNode? result = await instance.CallToolAsync(tool, arguments); + return result ?? throw new InvalidOperationException($"{tool} returned no structured result."); + } + + private static string Text(JsonNode node, string name) => node[name]?.GetValue() + ?? throw new InvalidDataException($"The result omitted {name}."); + private static string Hex(ulong value) => $"0x{value:X}"; + internal static ulong ParseHex(string text) + { + if (string.IsNullOrWhiteSpace(text)) + { + throw new InvalidDataException("The allocation returned an empty hexadecimal address."); + } + + string digits = text.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? text[2..] : text; + if (!ulong.TryParse(digits, System.Globalization.NumberStyles.AllowHexSpecifier, + System.Globalization.CultureInfo.InvariantCulture, out ulong address) || address == 0) + { + throw new InvalidDataException("The allocation returned an invalid hexadecimal address."); + } + + return address; + } +} diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualificationTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualificationTests.cs new file mode 100644 index 0000000..a4f06f1 --- /dev/null +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveActivePluginLifecycleQualificationTests.cs @@ -0,0 +1,100 @@ +using System.Runtime.Versioning; + +namespace CheatEngine.Mcp.Tests.LiveQualification; + +[SupportedOSPlatform("windows")] +public sealed class LiveActivePluginLifecycleQualificationTests +{ + [Theory] + [InlineData("296A0FA0000", 0x296A0FA0000UL)] + [InlineData("0x296A0FA0000", 0x296A0FA0000UL)] + [InlineData("0X296A0FA0000", 0x296A0FA0000UL)] + public void ParseHex_AcceptsPrefixedAndUnprefixedAllocationAddresses(string text, ulong expected) + { + Assert.Equal(expected, LiveActivePluginLifecycleQualification.ParseHex(text)); + } + + [Theory] + [InlineData("")] + [InlineData("0x")] + [InlineData("not-hex")] + [InlineData("0")] + public void ParseHex_RejectsInvalidAddresses(string text) + { + Assert.Throws(() => LiveActivePluginLifecycleQualification.ParseHex(text)); + } + + [Fact] + public void ParseResponse_RecognizesTheDisabledStatusMenuAndAbsentBreakpoint() + { + LivePluginDisabledObservation observation = LivePluginDisabledObservation.ParseResponse( + ObservationResponse("disabled", "absent", "4321", "1", "number:1=number:22136"), 17, "0x1234", 4321); + + Assert.False(observation.PluginChecked); + Assert.True(observation.StatusMenuPresent); + Assert.True(observation.StatusMenuDisabled); + Assert.False(observation.BreakpointPresent); + Assert.Equal(4321, observation.OpenedProcessId); + Assert.Equal(1, observation.BreakpointCount); + Assert.Equal("number:1=number:22136", Assert.Single(observation.BreakpointSamples)); + Assert.False(observation.CleanupRemovalSucceeded); + } + + [Theory] + [InlineData("wrong")] + [InlineData("ok\n17\nobserve\n0x1234\ndisabled\npresent\nabsent")] + public void ParseResponse_RejectsMalformedOrLegacyStatusObservations(string response) + { + Assert.Throws(() => + LivePluginDisabledObservation.ParseResponse(response, 17, "0x1234", 4321)); + } + + [Fact] + public void ParseResponse_RetainsBreakpointPresenceAsAFailedCleanupObservation() + { + LivePluginDisabledObservation observation = LivePluginDisabledObservation.ParseResponse( + ObservationResponse("disabled", "present", "4321", "1", "number:1=number:4660"), 17, "0x1234", 4321); + + Assert.True(observation.StatusMenuDisabled); + Assert.True(observation.BreakpointPresent); + Assert.Equal("number:1=number:4660", Assert.Single(observation.BreakpointSamples)); + } + + [Fact] + public void ParseResponse_ReportsWhetherTheNativeBreakpointRemovalSucceeded() + { + LivePluginDisabledObservation failed = LivePluginDisabledObservation.ParseResponse( + ObservationResponse("disabled", "absent", "4321", "0", "", ["called", "number", "88", "false", + "boolean", "false", "", "1", "before", "0", "", "fields="]), 17, "0x1234", 4321); + LivePluginDisabledObservation succeeded = LivePluginDisabledObservation.ParseResponse( + ObservationResponse("disabled", "absent", "4321", "0", "", ["called", "table", "table:88", "true", + "boolean", "true", "", "1", "before", "0", "", "fields="]), 17, "0x1234", 4321); + + Assert.False(failed.CleanupRemovalSucceeded); + Assert.True(succeeded.CleanupRemovalSucceeded); + } + + [Theory] + [InlineData("not-a-pid", "1", "number:4660")] + [InlineData("0", "-1", "")] + [InlineData("4321", "0", "number:4660")] + [InlineData("9999", "1", "number:4660")] + public void ParseResponse_RejectsInvalidProcessAndBreakpointDiagnostics(string processId, string count, string samples) + { + string response = ObservationResponse("disabled", "absent", processId, count, samples); + + Assert.Throws(() => LivePluginDisabledObservation.ParseResponse(response, 17, "0x1234", 4321)); + } + + private static string ObservationResponse(string status, string breakpoint, string processId, string count, string samples, + string[]? cleanupProbeFields = null) + { + cleanupProbeFields ??= ["uninstalled", "", "", "", "", "", "", "", "", "", "", ""]; + return string.Join('\n', new[] + { + "ok", "17", "observe", "0x1234", "disabled", status, breakpoint, processId, count, samples + } + .Concat(cleanupProbeFields) + .Append($"raw:{count}:{samples}")); + } +} diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerEventOracleTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerEventOracleTests.cs index ea8591e..d8a0f53 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerEventOracleTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerEventOracleTests.cs @@ -6,15 +6,15 @@ namespace CheatEngine.Mcp.Tests.LiveQualification; [SupportedOSPlatform("windows")] public sealed class LiveDebuggerEventOracleTests { - private static readonly LiveDebuggerExecutionQualification.Manifest Owned = new(42, 7, 0x1000, 0x1009, 0x1017, 0x1037, "1108", "1104"); + private static readonly LiveDebuggerExecutionQualification.Manifest Owned = new(42, 7, 0x1000, 0x1012, 0x1020, 0x104C, "1108", "1104", "110C", "1110"); [Fact] public void BreakpointListPresence_UsesExactAddressRegardlessOfOwner() { - JsonNode page = JsonNode.Parse("""{"breakpoints":[{"address":"1009","owned":false},{"address":"0x1017","owned":true}]}""")!; + JsonNode page = JsonNode.Parse("""{"breakpoints":[{"address":"1012","owned":false},{"address":"0x1020","owned":true}]}""")!; - Assert.True(LiveDebuggerExecutionQualification.HasBreakpointAt(page, 0x1009)); - Assert.True(LiveDebuggerExecutionQualification.HasBreakpointAt(page, 0x1017)); + Assert.True(LiveDebuggerExecutionQualification.HasBreakpointAt(page, 0x1012)); + Assert.True(LiveDebuggerExecutionQualification.HasBreakpointAt(page, 0x1020)); Assert.False(LiveDebuggerExecutionQualification.HasBreakpointAt(page, 0x100A)); } @@ -110,7 +110,7 @@ public void TracePage_RejectsIncompleteOrForeignEvidence(string mutation) switch (mutation) { case "wrong second instruction": - page["steps"]![1]!["ip"] = "1009"; + page["steps"]![1]!["ip"] = "1012"; break; case "foreign thread": page["steps"]![2]!["threadId"] = 8L; @@ -144,7 +144,7 @@ public void TracePage_RejectsIncompleteOrForeignEvidence(string mutation) private static JsonNode CapturePage() { JsonNode page = Page("capture-owned", "debugcapture", "running", 1, 2); - JsonNode context = Context("1009", 0); + JsonNode context = Context("1012", 0); page["hits"] = new JsonArray(new JsonObject { ["context"] = context, @@ -161,9 +161,9 @@ private static JsonNode TracePage(int count) page["job"]!["progressTotal"] = (long) count; page["steps"] = count switch { - 1 => new JsonArray(Context("1009")), - 2 => new JsonArray(Context("1009"), Context("1010")), - 3 => new JsonArray(Context("1009"), Context("1010"), Context("1017")), + 1 => new JsonArray(Context("1012")), + 2 => new JsonArray(Context("1012"), Context("1019")), + 3 => new JsonArray(Context("1012"), Context("1019"), Context("1020")), _ => throw new ArgumentOutOfRangeException(nameof(count)) }; return page; diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualification.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualification.cs index 19854df..459c549 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualification.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualification.cs @@ -15,13 +15,14 @@ internal static partial class LiveDebuggerExecutionQualification private static readonly TimeSpan Poll = TimeSpan.FromMilliseconds(50); internal sealed record Manifest(int ProcessId, int ThreadId, ulong CodeStart, ulong Entry, ulong RunTo, ulong CodeEnd, - string ProgressAddress, string HitCounterAddress) + string ProgressAddress, string HitCounterAddress, string PauseAddress, string PauseAcknowledgedAddress) { internal void Validate(int expectedProcessId) { - if (ProcessId != expectedProcessId || ThreadId <= 0 || CodeStart == 0 || CodeEnd != checked(CodeStart + 55) || + if (ProcessId != expectedProcessId || ThreadId <= 0 || CodeStart == 0 || CodeEnd != checked(CodeStart + 76) || LiveDebuggerExecutionQualification.ParseHex(HitCounterAddress) != checked(CodeStart + 0x104) || LiveDebuggerExecutionQualification.ParseHex(ProgressAddress) != checked(CodeStart + 0x108) || - Entry != checked(CodeStart + 9) || RunTo != checked(CodeStart + 23)) + LiveDebuggerExecutionQualification.ParseHex(PauseAddress) != checked(CodeStart + 0x10C) || LiveDebuggerExecutionQualification.ParseHex(PauseAcknowledgedAddress) != checked(CodeStart + 0x110) || + Entry != checked(CodeStart + 18) || RunTo != checked(CodeStart + 32)) { throw new InvalidDataException("The owned debugger execution manifest is inconsistent."); } @@ -32,7 +33,8 @@ internal static Manifest ReadManifest(JsonNode value, int processId) { Manifest result = new(processId, value["threadId"]!.GetValue(), ParseHex(Text(value, "codeStart")), ParseHex(Text(value, "entryAddress")), ParseHex(Text(value, "runToAddress")), ParseHex(Text(value, "codeEnd")), - Text(value, "progressAddress"), Text(value, "hitCounterAddress")); + Text(value, "progressAddress"), Text(value, "hitCounterAddress"), Text(value, "pauseAddress"), + Text(value, "pauseAcknowledgedAddress")); result.Validate(processId); return result; } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualificationTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualificationTests.cs index 348ea1d..9443ffc 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualificationTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerExecutionQualificationTests.cs @@ -8,13 +8,15 @@ public sealed class LiveDebuggerExecutionQualificationTests [Fact] public void Manifest_RejectsOutOfRangeThreadAndAddresses() { - LiveDebuggerExecutionQualification.Manifest valid = new(42, 7, 0x1000, 0x1009, 0x1017, 0x1037, "0x1108", "0x1104"); + LiveDebuggerExecutionQualification.Manifest valid = new(42, 7, 0x1000, 0x1012, 0x1020, 0x104C, "0x1108", "0x1104", "0x110C", "0x1110"); valid.Validate(42); Assert.Throws(() => (valid with { ThreadId = 0 }).Validate(42)); Assert.Throws(() => (valid with { Entry = 0x1037 }).Validate(42)); Assert.Throws(() => (valid with { CodeEnd = 0x1000 }).Validate(42)); Assert.Throws(() => (valid with { ProcessId = 43 }).Validate(42)); Assert.Throws(() => (valid with { ProgressAddress = "2000" }).Validate(42)); + Assert.Throws(() => (valid with { PauseAddress = "0x110D" }).Validate(42)); + Assert.Throws(() => (valid with { PauseAcknowledgedAddress = "0x110C" }).Validate(42)); Assert.Throws(() => (valid with { Entry = 0x100A }).Validate(42)); Assert.Throws(() => (valid with { RunTo = 0x1018 }).Validate(42)); } @@ -22,7 +24,7 @@ public void Manifest_RejectsOutOfRangeThreadAndAddresses() [Fact] public void Manifest_RangeIsHalfOpen() { - LiveDebuggerExecutionQualification.Manifest manifest = new(42, 7, 0x1000, 0x1009, 0x1017, 0x1037, "0x1108", "0x1104"); + LiveDebuggerExecutionQualification.Manifest manifest = new(42, 7, 0x1000, 0x1012, 0x1020, 0x104C, "0x1108", "0x1104", "0x110C", "0x1110"); Assert.True(manifest.Entry >= manifest.CodeStart && manifest.Entry < manifest.CodeEnd); Assert.Throws(() => (manifest with { RunTo = manifest.CodeEnd }).Validate(42)); } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerTransitionMemoryTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerTransitionMemoryTests.cs index 66f37dd..b7e8ac4 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerTransitionMemoryTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveDebuggerTransitionMemoryTests.cs @@ -15,14 +15,15 @@ public void Read_SnapshotsOnlyThePublishedEntryAndCounters() try { byte[] bytes = new byte[0x1000]; - bytes[9] = 0xCC; + bytes[18] = 0xCC; BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(0x104), 123u); BinaryPrimitives.WriteUInt32LittleEndian(bytes.AsSpan(0x108), 456u); Marshal.Copy(bytes, 0, allocation, bytes.Length); using Process process = Process.GetCurrentProcess(); ulong codeStart = checked((ulong) allocation); - LiveDebuggerExecutionQualification.Manifest manifest = new(process.Id, 1, codeStart, codeStart + 9, - codeStart + 23, codeStart + 55, $"0x{codeStart + 0x108:X}", $"0x{codeStart + 0x104:X}"); + LiveDebuggerExecutionQualification.Manifest manifest = new(process.Id, 1, codeStart, codeStart + 18, + codeStart + 32, codeStart + 76, $"0x{codeStart + 0x108:X}", $"0x{codeStart + 0x104:X}", + $"0x{codeStart + 0x10C:X}", $"0x{codeStart + 0x110:X}"); Assert.Equal(new LiveDebuggerTransitionMemory.Snapshot(0xCC, 123, 456), LiveDebuggerTransitionMemory.Read(process, manifest)); @@ -37,8 +38,8 @@ public void Read_SnapshotsOnlyThePublishedEntryAndCounters() public void Read_RejectsManifestForAnotherProcessBeforeNativeRead() { using Process process = Process.GetCurrentProcess(); - LiveDebuggerExecutionQualification.Manifest wrong = new(process.Id + 1, 1, 0x1000, 0x1009, 0x1017, 0x1037, - "0x1108", "0x1104"); + LiveDebuggerExecutionQualification.Manifest wrong = new(process.Id + 1, 1, 0x1000, 0x1012, 0x1020, 0x104C, + "0x1108", "0x1104", "0x110C", "0x1110"); Assert.Throws(() => LiveDebuggerTransitionMemory.Read(process, wrong)); } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LivePluginBridge.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LivePluginBridge.cs index 319ff44..e0b5d1a 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LivePluginBridge.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LivePluginBridge.cs @@ -1,6 +1,6 @@ namespace CheatEngine.Mcp.Tests.LiveQualification; -/// Fixed CE Settings/Plugins control bridge; only enable and disable records are accepted. +/// Fixed lifecycle bridge with bounded breakpoint-cleanup probing. internal static class LivePluginBridge { internal const string Template = """ @@ -13,12 +13,118 @@ local function pluginError(nonce,action,reason) local message=tostring(reason):gsub('[\r\n]+',' '):gsub('[^ -~]','?'):sub(1,1024) pluginResponse('error\n'..nonce..'\n'..action..'\n'..message) end + local function pluginSafe(value) + return tostring(value):gsub('[^%w:%.%-%_ ]','?'):sub(1,32) + end + local function pluginBreakpointSnapshot() + local success,list=pcall(debug_getBreakpointList) + if not success or type(list)~='table' then return -1, success and 'not-table' or pluginSafe(list) end + local count=0 + local samples={} + for key,value in pairs(list) do + count=count+1 + if #samples<8 then samples[#samples+1]=type(key)..':'..pluginSafe(key)..'='..type(value)..':'..pluginSafe(value) end + end + table.sort(samples) + return count,table.concat(samples,';') + end + local function pluginBreakpointIdFields(value) + if type(value)~='table' then return '' end + local function fields(tableValue) + local samples={} + local key=nil + for _=1,8 do + local item + key,item=next(tableValue,key) + if key==nil then break end + samples[#samples+1]=type(key)..':'..pluginSafe(key)..'='..type(item)..':'..pluginSafe(item) + end + table.sort(samples) + return table.concat(samples,';') + end + local metatable=getmetatable(value) + return 'fields='..fields(value)..';metatableType='..type(metatable).. + ';metatableFields='..(type(metatable)=='table' and fields(metatable) or '') + end + local function pluginInstallRemovalProbe() + local key='__cheatengine_mcp_lifecycle_removal_probe' + assert(rawget(_G,key)==nil, 'breakpoint removal probe is already installed') + assert(type(debug_removeBreakpointByID)=='function', 'breakpoint remover unavailable') + local state={original=debug_removeBreakpointByID,receipt=nil} + state.wrapper=function(id,...) + local beforeCount,beforeSamples=pluginBreakpointSnapshot() + local results=table.pack(pcall(state.original,id,...)) + local afterCount,afterSamples=pluginBreakpointSnapshot() + state.receipt={idType=type(id),id=pluginSafe(id),succeeded=results[1], + returnType=type(results[2]),returnValue=pluginSafe(results[2]), + failure=not results[1] and pluginSafe(results[2]) or results[3]~=nil and pluginSafe(results[3]) or '', + beforeCount=beforeCount,beforeSamples=beforeSamples,afterCount=afterCount,afterSamples=afterSamples, + idFields=pluginBreakpointIdFields(id)} + if not results[1] then error(results[2],0) end + return table.unpack(results,2,results.n) + end + rawset(_G,key,state) + debug_removeBreakpointByID=state.wrapper + assert(debug_removeBreakpointByID==state.wrapper, 'breakpoint remover probe could not be installed') + end + local function pluginRemoveRemovalProbe() + local key='__cheatengine_mcp_lifecycle_removal_probe' + local state=rawget(_G,key) + if state==nil then return end + assert(debug_removeBreakpointByID==state.wrapper, 'breakpoint remover changed while its probe was installed') + debug_removeBreakpointByID=state.original + rawset(_G,key,nil) + end + local function pluginObserve(nonce,address) + local success,enabled,status,breakpoint,openedProcessId,breakpointCount,breakpointSamples=pcall(function() + local f=assert(getSettingsForm(), 'settings form unavailable') + local clb=assert(f.findComponentByName('clbPlugins'), 'plugins checklist unavailable') + assert(clb.Items.Count==1, 'expected exactly one owned plugin row') + assert(tostring(clb.Items[0])==__BASENAME__..':'..__DISPLAY__, 'owned MCP plugin row not found') + local requested=assert(tonumber(address), 'invalid exact breakpoint address') + local list=assert(debug_getBreakpointList(), 'breakpoint list unavailable') + local found=false + local count=0 + local samples={} + for key,value in pairs(list) do + count=count+1 + if tonumber(value)==requested then found=true end + if #samples<16 then samples[#samples+1]=type(key)..':'..pluginSafe(key)..'='..type(value)..':'..pluginSafe(value) end + end + table.sort(samples) + local status=getMainForm().Menu.findComponentByName('CheatEngineMcpStatus') + local statusState='absent' + if status~=nil then statusState=status.Caption=='MCP: Disabled' and 'disabled' or 'other' end + return clb.Checked[0], statusState, found, getOpenedProcessID(), count, table.concat(samples,';') + end) + if not success then pluginError(nonce,'observe',enabled); return end + local state=rawget(_G,'__cheatengine_mcp_lifecycle_removal_probe') + local receipt=state~=nil and state.receipt or nil + local probeStatus=state==nil and 'uninstalled' or receipt==nil and 'installed-no-call' or 'called' + local rawCount,rawSamples=pluginBreakpointSnapshot() + pluginResponse('ok\n'..nonce..'\nobserve\n'..address..'\n'..(enabled and 'enabled' or 'disabled')..'\n'..status..'\n'..(breakpoint and 'present' or 'absent')..'\n'..tostring(openedProcessId)..'\n'..tostring(breakpointCount)..'\n'..breakpointSamples.. + '\n'..probeStatus..'\n'..(receipt and receipt.idType or '')..'\n'..(receipt and receipt.id or '').. + '\n'..(receipt and tostring(receipt.succeeded) or '')..'\n'..(receipt and receipt.returnType or '').. + '\n'..(receipt and receipt.returnValue or '')..'\n'..(receipt and receipt.failure or '').. + '\n'..(receipt and tostring(receipt.beforeCount) or '')..'\n'..(receipt and receipt.beforeSamples or '').. + '\n'..(receipt and tostring(receipt.afterCount) or '')..'\n'..(receipt and receipt.afterSamples or '').. + '\n'..(receipt and receipt.idFields or '').. + '\nraw:'..tostring(rawCount)..':'..rawSamples) + end local function pluginControl() local request=io.open(__REQUEST__,'r') if not request then return end local command=request:read('*a'); request:close(); assert(os.remove(__REQUEST__),'plugin request dequeue failed') - local nonce,action=command:match('^(%d+)\n(%a+)\n$') - assert(nonce and (action=='enable' or action=='disable'), 'invalid fixed plugin request') + local nonce,action,address=command:match('^(%d+)\n(%a+)\n(0x[0-9A-F]+)\n$') + if not nonce then nonce,action=command:match('^(%d+)\n([%a_]+)\n$') end + assert(nonce and (action=='enable' or action=='disable' or action=='observe' + or action=='install_probe' or action=='remove_probe'), 'invalid fixed plugin request') + if action=='observe' then assert(address, 'missing exact breakpoint address'); pluginObserve(nonce,address); return end + if action=='install_probe' or action=='remove_probe' then + local success,reason=pcall(action=='install_probe' and pluginInstallRemovalProbe or pluginRemoveRemovalProbe) + if not success then pluginError(nonce,action,reason); return end + pluginResponse('ok\n'..nonce..'\n'..action); return + end local success,reason=pcall(function() assert((action=='enable') ~= pluginEnabled, 'plugin requested state is already active') local f=assert(getSettingsForm(), 'settings form unavailable') diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptIn.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptIn.cs index 4fbd407..610e0ed 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptIn.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptIn.cs @@ -78,6 +78,7 @@ internal static LiveQualificationDecision Evaluate(Func variabl "compiler-injection" => LiveQualificationScenario.CompilerInjection, "lifecycle" => LiveQualificationScenario.Lifecycle, "plugin-lifecycle" => LiveQualificationScenario.PluginLifecycle, + "plugin-lifecycle-active" => LiveQualificationScenario.PluginLifecycleActive, "workflows" => LiveQualificationScenario.Workflows, "inspection" => LiveQualificationScenario.Inspection, "state-workflows" => LiveQualificationScenario.StateWorkflows, @@ -98,7 +99,7 @@ internal static LiveQualificationDecision Evaluate(Func variabl }; if (selectedScenario is null) { - return Refuse($"{ScenarioVariable} must be smoke, compiler, compiler-extended, compiler-injection, lifecycle, plugin-lifecycle, workflows, inspection, state-workflows, assembler, debugger, debugger-basic, debugger-events, debugger-execution, kernel-status, execution, symbol-fixture, process-create, mono, performance, soak, or dispatch-diagnostic."); + return Refuse($"{ScenarioVariable} must be smoke, compiler, compiler-extended, compiler-injection, lifecycle, plugin-lifecycle, plugin-lifecycle-active, workflows, inspection, state-workflows, assembler, debugger, debugger-basic, debugger-events, debugger-execution, kernel-status, execution, symbol-fixture, process-create, mono, performance, soak, or dispatch-diagnostic."); } LiveQualificationScenario scenario = selectedScenario.Value; @@ -148,7 +149,7 @@ internal static LiveQualificationDecision Evaluate(Func variabl { return Refuse($"{AssemblerOptInVariable} must contain the exact acknowledgement {AssemblerAcknowledgement}."); } - if ((scenario is LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution) && + if ((scenario is LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution or LiveQualificationScenario.PluginLifecycleActive) && variables(DebuggerOptInVariable) != DebuggerAcknowledgement) { return Refuse($"{DebuggerOptInVariable} must contain the exact acknowledgement {DebuggerAcknowledgement}."); @@ -182,7 +183,7 @@ internal static LiveQualificationDecision Evaluate(Func variabl { return Refuse($"{TargetArchitectureVariable} must be x64 or x86."); } - if ((scenario is LiveQualificationScenario.Execution or LiveQualificationScenario.SymbolFixture or LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution or LiveQualificationScenario.Mono) && architecture != "x64") + if ((scenario is LiveQualificationScenario.Execution or LiveQualificationScenario.SymbolFixture or LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution or LiveQualificationScenario.PluginLifecycleActive or LiveQualificationScenario.Mono) && architecture != "x64") { return Refuse("The fixed execution qualification supports only the owned x64 target."); } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptInTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptInTests.cs index 5040983..bbc9c2b 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptInTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationOptInTests.cs @@ -242,6 +242,38 @@ public void Evaluate_StandardQualificationScenario_UsesOnlyStandardAcknowledgeme Assert.Equal((LiveQualificationScenario) expected, decision.Inputs!.Scenario); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("true")] + public void Evaluate_ActivePluginLifecycleRequiresDebuggerAcknowledgementBeforeInspection(string? acknowledgement) + { + Dictionary variables = Authorized(); + variables[LiveQualificationOptIn.ScenarioVariable] = "plugin-lifecycle-active"; + variables[LiveQualificationOptIn.DebuggerOptInVariable] = acknowledgement; + + LiveQualificationDecision decision = LiveQualificationOptIn.Evaluate(variables.GetValueOrDefault, + Repository, LocalData, Installation, static _ => throw new InvalidOperationException("Must refuse before inspection.")); + + Assert.False(decision.IsAuthorized); + Assert.Contains(LiveQualificationOptIn.DebuggerOptInVariable, decision.Refusal, StringComparison.Ordinal); + } + + [Fact] + public void Evaluate_ActivePluginLifecycleWithDebuggerAcknowledgement_AuthorizesX64Only() + { + Dictionary variables = Authorized(); + variables[LiveQualificationOptIn.ScenarioVariable] = "plugin-lifecycle-active"; + variables[LiveQualificationOptIn.DebuggerOptInVariable] = LiveQualificationOptIn.DebuggerAcknowledgement; + + LiveQualificationDecision decision = Evaluate(variables); + + Assert.True(decision.IsAuthorized, decision.Refusal); + Assert.Equal(LiveQualificationScenario.PluginLifecycleActive, decision.Inputs!.Scenario); + variables[LiveQualificationOptIn.TargetArchitectureVariable] = "x86"; + Assert.False(Evaluate(variables).IsAuthorized); + } + [Theory] [InlineData("AobOnly", (int) LiveDispatchDiagnosticCase.AobOnly)] [InlineData("NamedScanThenAob", (int) LiveDispatchDiagnosticCase.NamedScanThenAob)] diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationScenario.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationScenario.cs index 7ee79e7..059826d 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationScenario.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveQualificationScenario.cs @@ -9,6 +9,7 @@ internal enum LiveQualificationScenario CompilerInjection, Lifecycle, PluginLifecycle, + PluginLifecycleActive, Performance, Soak, DispatchDiagnostic, diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptions.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptions.cs index 07b5ecc..c010952 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptions.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptions.cs @@ -8,7 +8,7 @@ internal sealed record LiveSandboxOptions(bool CompilerQualification, bool Enabl internal bool RequiresCapabilityConfiguration => CompilerQualification || LifecycleQualification || PerformanceQualification || SoakQualification || DispatchDiagnostic || WorkflowQualification || InspectionQualification || StateWorkflowsQualification || AssemblerQualification || DebuggerQualification || DebuggerEventsQualification || ExecutionQualification || SymbolFixtureQualification || ProcessCreateQualification || MonoQualification - || KernelStatusQualification; + || KernelStatusQualification || ActivePluginLifecycleQualification; internal LiveCompilerVariant Variant { get; init; } = LiveCompilerVariant.Normal; internal CompilerTempTopology TempTopology { get; init; } = CompilerTempTopology.Isolated; @@ -24,6 +24,10 @@ internal bool SeedPluginChecklistAtStartup { get; init; } + internal bool ActivePluginLifecycleQualification + { + get; init; + } internal bool WorkflowQualification { get; init; @@ -104,7 +108,8 @@ internal void ValidateFor(LiveQualificationScenario scenario) bool symbolFixture = scenario == LiveQualificationScenario.SymbolFixture; bool processCreate = scenario == LiveQualificationScenario.ProcessCreate; bool mono = scenario == LiveQualificationScenario.Mono; - bool debugger = scenario is LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution; + bool activePluginLifecycle = scenario == LiveQualificationScenario.PluginLifecycleActive; + bool debugger = scenario is LiveQualificationScenario.Debugger or LiveQualificationScenario.DebuggerBasic or LiveQualificationScenario.DebuggerEvents or LiveQualificationScenario.DebuggerExecution or LiveQualificationScenario.PluginLifecycleActive; bool debuggerEvents = scenario == LiveQualificationScenario.DebuggerEvents; bool kernelStatus = scenario == LiveQualificationScenario.KernelStatus; bool valid = Enum.IsDefined(scenario) && Enum.IsDefined(Variant) && Enum.IsDefined(TempTopology) @@ -121,8 +126,9 @@ internal void ValidateFor(LiveQualificationScenario scenario) && (!execution || (!CompilerQualification && !EnableManagedInjection)) && (!symbolFixture || (EnableTargetCodeExecution && !CompilerQualification && !EnableManagedInjection)) && (!injection || EnableTargetCodeExecution) - && LifecycleQualification == (scenario is LiveQualificationScenario.Lifecycle or LiveQualificationScenario.PluginLifecycle) - && SeedPluginChecklistAtStartup == (scenario == LiveQualificationScenario.PluginLifecycle) + && LifecycleQualification == (scenario is LiveQualificationScenario.Lifecycle or LiveQualificationScenario.PluginLifecycle or LiveQualificationScenario.PluginLifecycleActive) + && SeedPluginChecklistAtStartup == (scenario is LiveQualificationScenario.PluginLifecycle or LiveQualificationScenario.PluginLifecycleActive) + && ActivePluginLifecycleQualification == activePluginLifecycle && WorkflowQualification == (scenario == LiveQualificationScenario.Workflows) && InspectionQualification == (scenario == LiveQualificationScenario.Inspection) && StateWorkflowsQualification == (scenario == LiveQualificationScenario.StateWorkflows) diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptionsTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptionsTests.cs index 9a184d8..0b90d17 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptionsTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxOptionsTests.cs @@ -105,6 +105,13 @@ public void ValidateFor_ExactScenarioOptions_Accept() LifecycleQualification = true, SeedPluginChecklistAtStartup = true }).ValidateFor(LiveQualificationScenario.PluginLifecycle); + (LiveSandboxOptions.Smoke with + { + LifecycleQualification = true, + SeedPluginChecklistAtStartup = true, + ActivePluginLifecycleQualification = true, + DebuggerQualification = true + }).ValidateFor(LiveQualificationScenario.PluginLifecycleActive); Assert.Throws(() => (LiveSandboxOptions.Smoke with { LifecycleQualification = true @@ -120,6 +127,24 @@ public void ValidateFor_ExactScenarioOptions_Accept() LifecycleQualification = true, WorkflowQualification = true }).ValidateFor(LiveQualificationScenario.PluginLifecycle)); + Assert.Throws(() => (LiveSandboxOptions.Smoke with + { + LifecycleQualification = true, + SeedPluginChecklistAtStartup = true, + ActivePluginLifecycleQualification = true + }).ValidateFor(LiveQualificationScenario.PluginLifecycleActive)); + Assert.Throws(() => (LiveSandboxOptions.Smoke with + { + LifecycleQualification = true, + DebuggerQualification = true, + ActivePluginLifecycleQualification = true + }).ValidateFor(LiveQualificationScenario.PluginLifecycleActive)); + Assert.Throws(() => (LiveSandboxOptions.Smoke with + { + LifecycleQualification = true, + SeedPluginChecklistAtStartup = true, + DebuggerQualification = true + }).ValidateFor(LiveQualificationScenario.PluginLifecycleActive)); (LiveSandboxOptions.Smoke with { WorkflowQualification = true @@ -207,6 +232,7 @@ public void ValidateFor_SmokeRejectsEveryExtraCapabilityAndLifetime() LiveSandboxOptions.Smoke with { DebuggerQualification = true }, LiveSandboxOptions.Smoke with { DebuggerEventsQualification = true }, LiveSandboxOptions.Smoke with { DebuggerTransitionDiagnostic = true }, + LiveSandboxOptions.Smoke with { ActivePluginLifecycleQualification = true }, LiveSandboxOptions.Smoke with { KernelStatusQualification = true }, LiveSandboxOptions.Smoke with { ProcessCreateQualification = true }, LiveSandboxOptions.Smoke with { MonoQualification = true }, diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.Lifecycle.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.Lifecycle.cs index 8a95c42..25e3468 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.Lifecycle.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.Lifecycle.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Globalization; using System.Runtime.Versioning; using CheatEngine.Mcp.Tests.LiveQualification.Infrastructure; @@ -38,7 +39,6 @@ internal async Task ReloadPluginAsync(string hostName, bool ena ? "Plugin enable requires a successful prior disable transition." : "The owned plugin discovery identity is unavailable before disable."); } - PrepareOwnedPluginRegistryForSettingsReload(hostName, currentEnabled: !enabled); long nonce = Interlocked.Increment(ref _pluginControlNonce); string request = Path.Combine(_layout.RunDirectory, $"plugin-request-{hostName}.txt"); @@ -54,6 +54,12 @@ internal async Task ReloadPluginAsync(string hostName, bool ena string temporary = request + ".tmp"; RequireNoReparseAncestors(temporary); await File.WriteAllTextAsync(temporary, $"{nonce}\n{(enabled ? "enable" : "disable")}\n", cancellationToken); + if (!enabled) + { + // The UI request is about to become observable. Retain this exact identity only after all preparation has + // succeeded, so malformed/missing replies can recover an unload without treating preflight failure as one. + _pendingPluginIdentities[hostName] = previous; + } File.Move(temporary, request, false); await WaitUntilAsync(() => File.Exists(response), host.Process, TimeSpan.FromSeconds(15)); RequireNoReparseAncestors(response); @@ -102,10 +108,6 @@ internal async Task ReloadPluginAsync(string hostName, bool ena }; _pendingPluginIdentities.Remove(hostName); } - else - { - _pendingPluginIdentities[hostName] = previous; - } string currentInstanceId = host.Public.InstanceId; string currentEndpoint = host.Public.Endpoint; @@ -118,6 +120,149 @@ internal async Task ReloadPluginAsync(string hostName, bool ena Record($"plugin_{hostName}_{(enabled ? "enable" : "disable")}", cycle); return cycle; } + + /// Uses the fixed CE Settings/Plugins bridge to observe disabled plugin and cleanup state. + internal async Task ObservePluginDisabledAsync(string hostName, ulong breakpointAddress, + CancellationToken cancellationToken = default) + { + Stopwatch elapsed = Stopwatch.StartNew(); + int observations = 0; + LivePluginDisabledObservation observation; + do + { + observation = await ObservePluginStateAsync(hostName, breakpointAddress, cancellationToken); + observations++; + if (observation.PluginChecked || !observation.StatusMenuPresent || !observation.StatusMenuDisabled) + { + break; + } + + if (!observation.BreakpointPresent) + { + Record($"plugin_{hostName}_disabled_cleanup_wait", new + { + elapsedMilliseconds = elapsed.Elapsed.TotalMilliseconds, + observations, + breakpointAbsent = true, + observation + }); + return observation; + } + + if (elapsed.Elapsed >= TimeSpan.FromSeconds(8)) + { + break; + } + + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationToken); + } + while (true); + + Record($"plugin_{hostName}_disabled_cleanup_wait", new + { + elapsedMilliseconds = elapsed.Elapsed.TotalMilliseconds, + observations, + breakpointAbsent = false, + observation + }); + throw new InvalidOperationException(string.Create(CultureInfo.InvariantCulture, + $"The fixed CE Plugin Manager bridge did not observe the plugin unchecked, its status menu marked Disabled, and its owned breakpoint absent within {elapsed.Elapsed.TotalMilliseconds:F0} ms (checked={observation.PluginChecked}, statusMenuPresent={observation.StatusMenuPresent}, statusMenuDisabled={observation.StatusMenuDisabled}, breakpointPresent={observation.BreakpointPresent}).")); + } + + internal async Task ObservePluginStateAsync(string hostName, ulong breakpointAddress, + CancellationToken cancellationToken = default) + { + if (!_hosts.TryGetValue(hostName, out OwnedHost? host) || host.Process is null || host.Stopped) + { + throw new InvalidOperationException($"Owned host '{hostName}' is not running."); + } + long nonce = Interlocked.Increment(ref _pluginControlNonce); + string request = Path.Combine(_layout.RunDirectory, $"plugin-request-{hostName}.txt"); + string response = Path.Combine(_layout.RunDirectory, $"plugin-response-{hostName}.txt"); + RequireInsideRun(request); + RequireInsideRun(response); + RequireNoReparseAncestors(request); + RequireNoReparseAncestors(response); + if (File.Exists(response)) + { + File.Delete(response); + } + string temporary = request + ".tmp"; + RequireNoReparseAncestors(temporary); + string address = $"0x{breakpointAddress:X}"; + await File.WriteAllTextAsync(temporary, $"{nonce}\nobserve\n{address}\n", cancellationToken); + File.Move(temporary, request, false); + await WaitUntilAsync(() => File.Exists(response), host.Process, TimeSpan.FromSeconds(15)); + RequireNoReparseAncestors(response); + FileInfo responseFacts = new(response); + if (responseFacts.Length is <= 0 or > 4096) + { + throw new InvalidOperationException("The fixed CE Plugin Manager observation response has an invalid bounded length."); + } + LivePluginDisabledObservation observation = LivePluginDisabledObservation.ParseResponse( + await File.ReadAllTextAsync(response, cancellationToken), nonce, address, host.Public.TargetProcessId); + Record($"plugin_{hostName}_observed", observation); + Record($"plugin_{hostName}_observed_{nonce}", observation); + return observation; + } + + /// Temporarily wraps the native breakpoint remover inside an owned CE host for one cleanup observation. + internal async Task SetBreakpointRemovalProbeAsync(string hostName, bool installed, + CancellationToken cancellationToken = default) + { + if (!_hosts.TryGetValue(hostName, out OwnedHost? host) || host.Process is null || host.Stopped) + { + throw new InvalidOperationException($"Owned host '{hostName}' is not running."); + } + + string action = installed ? "install_probe" : "remove_probe"; + long nonce = Interlocked.Increment(ref _pluginControlNonce); + string request = Path.Combine(_layout.RunDirectory, $"plugin-request-{hostName}.txt"); + string response = Path.Combine(_layout.RunDirectory, $"plugin-response-{hostName}.txt"); + RequireInsideRun(request); + RequireInsideRun(response); + RequireNoReparseAncestors(request); + RequireNoReparseAncestors(response); + if (File.Exists(response)) + { + File.Delete(response); + } + + string temporary = request + ".tmp"; + RequireNoReparseAncestors(temporary); + await File.WriteAllTextAsync(temporary, $"{nonce}\n{action}\n", cancellationToken); + File.Move(temporary, request, false); + await WaitUntilAsync(() => File.Exists(response), host.Process, TimeSpan.FromSeconds(15)); + RequireNoReparseAncestors(response); + FileInfo responseFacts = new(response); + if (responseFacts.Length is <= 0 or > 4096) + { + throw new InvalidOperationException("The fixed CE Plugin Manager cleanup-probe response has an invalid bounded length."); + } + + string[] fields = (await File.ReadAllTextAsync(response, cancellationToken)) + .Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'); + if (fields.Length == 4 && fields[0] == "error" && long.TryParse(fields[1], out long failedNonce) + && failedNonce == nonce && fields[2] == action && fields[3].Length is > 0 and <= 1024) + { + throw new InvalidOperationException($"The fixed CE Plugin Manager bridge rejected {action}: {fields[3]}"); + } + if (fields.Length != 3 || fields[0] != "ok" || !long.TryParse(fields[1], out long received) + || received != nonce || fields[2] != action) + { + throw new InvalidOperationException("The fixed CE Plugin Manager cleanup-probe bridge returned an invalid response."); + } + + Record($"plugin_{hostName}_removal_probe_{(installed ? "installed" : "removed")}", new + { + nonce, + action, + processId = host.Public.ProcessId, + completedAt = DateTimeOffset.UtcNow + }); + } + + internal void ClearPendingPluginIdentity(string hostName) => _pendingPluginIdentities.Remove(hostName); /// Restarts one private CE installation and its owned target without disturbing the second host. public async Task RestartHostAsync(string name) { @@ -399,4 +544,60 @@ internal sealed record LivePluginCycle(int ProcessId, int TargetProcessId, long string PreviousInstanceId, string CurrentInstanceId, string PreviousEndpoint, string CurrentEndpoint, bool InstanceIdChanged, bool EndpointChanged, bool AccessTokenChanged, DateTimeOffset CompletedAt); +internal sealed record LivePluginDisabledObservation(long Nonce, string BreakpointAddress, bool PluginChecked, + bool StatusMenuPresent, bool StatusMenuDisabled, bool BreakpointPresent, int OpenedProcessId, int BreakpointCount, + string[] BreakpointSamples, string[] CleanupProbeFields, string RawBreakpointSnapshot, DateTimeOffset ObservedAt) +{ + internal bool CleanupRemovalSucceeded => CleanupProbeFields.Length == 12 + && CleanupProbeFields[0] == "called" && CleanupProbeFields[3] == "true" + && CleanupProbeFields[4] == "boolean" && CleanupProbeFields[5] == "true" + && CleanupProbeFields[6].Length == 0; + + internal static LivePluginDisabledObservation ParseResponse(string response, long expectedNonce, string expectedAddress, + int expectedProcessId) + { + string[] fields = response.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'); + if (fields.Length != 23 || fields[0] != "ok" || !long.TryParse(fields[1], out long received) || received != expectedNonce + || fields[2] != "observe" || fields[3] != expectedAddress || fields[4] is not ("enabled" or "disabled") + || fields[5] is not ("disabled" or "other" or "absent") || fields[6] is not ("present" or "absent") + || !int.TryParse(fields[7], out int openedProcessId) || openedProcessId < 0 + || !int.TryParse(fields[8], out int breakpointCount) || breakpointCount < 0 || fields[9].Length > 2048 + || fields[10] is not ("uninstalled" or "installed-no-call" or "called") + || fields[18].Length > 1024 || fields[20].Length > 1024 || fields[21].Length > 1536 || fields[22].Length > 1200 + || !fields[22].StartsWith("raw:", StringComparison.Ordinal)) + { + throw new InvalidOperationException("The fixed CE Plugin Manager bridge returned an invalid bounded lifecycle observation."); + } + if (expectedProcessId <= 0 || openedProcessId != expectedProcessId) + { + throw new InvalidOperationException("The fixed CE Plugin Manager observation did not match the owned target process."); + } + + string[] breakpointSamples = fields[9].Length == 0 ? [] : fields[9].Split(';'); + if (breakpointSamples.Length > 16 || breakpointSamples.Length > breakpointCount + || breakpointSamples.Any(static sample => sample.Length is 0 or > 128)) + { + throw new InvalidOperationException("The fixed CE Plugin Manager breakpoint sample is invalid."); + } + string[] cleanupProbeFields = fields[10..22]; + if (fields[10] == "called" + && (fields[11].Length is 0 or > 32 || fields[12].Length is 0 or > 32 + || fields[13] is not ("true" or "false") || fields[14].Length is 0 or > 32 + || fields[15].Length > 32 || fields[16].Length > 32 + || !int.TryParse(fields[17], out int beforeCount) || beforeCount < -1 + || !int.TryParse(fields[19], out int afterCount) || afterCount < -1)) + { + throw new InvalidOperationException("The fixed CE Plugin Manager cleanup receipt is invalid."); + } + if (fields[10] != "called" && fields[11..21].Any(static field => field.Length != 0)) + { + throw new InvalidOperationException("The fixed CE Plugin Manager returned receipt fields without a remover call."); + } + + return new LivePluginDisabledObservation(expectedNonce, expectedAddress, fields[4] == "enabled", + fields[5] != "absent", fields[5] == "disabled", fields[6] == "present", openedProcessId, + breakpointCount, breakpointSamples, cleanupProbeFields, fields[22], DateTimeOffset.UtcNow); + } +} + internal sealed record PendingPluginIdentity(string InstanceId, string Endpoint, string AccessToken); diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.cs index 3cc2f88..5bb189e 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/LiveSandboxSession.cs @@ -990,7 +990,7 @@ private Process StartTarget(string manifest, string name, bool soakLifetime = fa { start.ArgumentList.Add("--soak"); } - else if (_options.DebuggerQualification) + else if (_options.DebuggerQualification || _options.ActivePluginLifecycleQualification) { start.ArgumentList.Add("--debugger-loop"); } @@ -1008,7 +1008,7 @@ private LiveTargetManifest ReadTargetManifest(JsonNode manifest, int processId, int width = manifest["pointerWidth"]!.GetValue(); LiveDebuggerExecutionQualification.Manifest? debuggerExecution = manifest["debuggerExecution"] is { } loop ? LiveDebuggerExecutionQualification.ReadManifest(loop, processId) : null; - if (_options.DebuggerQualification && debuggerExecution is null) + if ((_options.DebuggerQualification || _options.ActivePluginLifecycleQualification) && debuggerExecution is null) { throw new InvalidDataException("The debugger target omitted its owned advancing thread manifest."); } diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/McpLiveQualificationTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/McpLiveQualificationTests.cs index 5073f9f..c101120 100644 --- a/tests/CheatEngine.Mcp.Tests/LiveQualification/McpLiveQualificationTests.cs +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/McpLiveQualificationTests.cs @@ -66,6 +66,11 @@ public async Task OneGateway_TwoOwnedInstances_RoutesStateAndSurvivesOneHostShut await LiveLifecycleQualification.RunAsync(inputs); return; } + if (inputs.Scenario == LiveQualificationScenario.PluginLifecycleActive) + { + await LiveActivePluginLifecycleQualification.RunAsync(inputs); + return; + } if (inputs.Scenario == LiveQualificationScenario.Soak) { await LiveSoakQualification.RunAsync(inputs); diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbe.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbe.cs new file mode 100644 index 0000000..5aaa6aa --- /dev/null +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbe.cs @@ -0,0 +1,172 @@ +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace CheatEngine.Mcp.Tests.LiveQualification; + +/// Independent read-only allocation cleanup oracle for an owned target process. +[SupportedOSPlatform("windows")] +internal static partial class TargetMemoryProbe +{ + private const uint ProcessQueryInformation = 0x0400; + private const uint ProcessVmRead = 0x0010; + private const uint MemoryFree = 0x10000; + private const uint MemoryCommit = 0x1000; + private const uint MemoryPrivate = 0x20000; + internal const int MaximumDiagnosticReadLength = 16; + + internal static bool IsFree(int processId, ulong address) + { + MemoryObservation observation = Inspect(processId, address, 0); + if (!observation.QuerySucceeded) + { + throw new Win32Exception(observation.QueryError, "VirtualQueryEx failed for the owned allocation."); + } + return observation.State == MemoryFree; + } + + internal static bool IsCommittedAndCovering(int processId, ulong address) + { + MemoryObservation observation = Inspect(processId, address, 0); + if (!observation.QuerySucceeded) + { + throw new Win32Exception(observation.QueryError, "VirtualQueryEx failed for the owned allocation."); + } + return Covers(observation.State, observation.Type, observation.BaseAddress, observation.AllocationBase, + checked((nuint) observation.RegionSize), address); + } + + /// Records the exact allocation state and, when requested, a bounded direct read from the owned target. + internal static MemoryObservation Inspect(int processId, ulong address, int readLength) + { + ValidateInspectionRequest(address, readLength); + nint raw = OpenProcess(ProcessQueryInformation | (readLength == 0 ? 0U : ProcessVmRead), false, processId); + if (raw == 0) + { + return MemoryObservation.OpenProcessFailed(Marshal.GetLastWin32Error()); + } + using ProcessHandle process = new(raw); + if (VirtualQueryEx(process, unchecked((nint) address), out MemoryBasicInformation info, + checked((nuint) Marshal.SizeOf())) == 0) + { + return MemoryObservation.VirtualQueryFailed(Marshal.GetLastWin32Error()); + } + + ulong baseAddress = unchecked((ulong) info.BaseAddress); + ulong allocationBase = unchecked((ulong) info.AllocationBase); + ulong regionSize = checked((ulong) info.RegionSize); + bool readRangeCovered = CoversReadRange(baseAddress, info.RegionSize, address, readLength); + if (!readRangeCovered) + { + return new MemoryObservation(true, 0, baseAddress, allocationBase, info.AllocationProtect, info.PartitionId, + regionSize, info.State, info.Protect, info.Type, false, false, false, false, 0, 0, string.Empty); + } + + byte[] buffer = new byte[readLength]; + nuint bytesRead = 0; + bool readAttempted = readLength != 0; + bool readReturned = !readAttempted || ReadProcessMemory(process, unchecked((nint) address), buffer, + checked((nuint) buffer.Length), out bytesRead); + int readError = readReturned ? 0 : Marshal.GetLastWin32Error(); + ulong bytesReadValue = readLength == 0 ? 0 : checked((ulong) bytesRead); + int recordedLength = checked((int) Math.Min(bytesReadValue, (ulong) buffer.Length)); + return new MemoryObservation( + QuerySucceeded: true, + QueryError: 0, + BaseAddress: baseAddress, + AllocationBase: allocationBase, + AllocationProtect: info.AllocationProtect, + PartitionId: info.PartitionId, + RegionSize: regionSize, + State: info.State, + Protect: info.Protect, + Type: info.Type, + ReadRangeCovered: true, + ReadAttempted: readAttempted, + ReadReturned: readReturned, + ReadSucceeded: readReturned && bytesReadValue == (ulong) readLength, + ReadError: readError, + BytesRead: bytesReadValue, + BytesHex: Convert.ToHexString(buffer.AsSpan(0, recordedLength))); + } + + internal static void ValidateInspectionRequest(ulong address, int readLength) + { + if (address == 0 || address > (ulong) nint.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(address), "The target address must be a positive native pointer."); + } + ArgumentOutOfRangeException.ThrowIfNegative(readLength); + ArgumentOutOfRangeException.ThrowIfGreaterThan(readLength, MaximumDiagnosticReadLength); + if ((ulong) readLength > (ulong) nint.MaxValue - address) + { + throw new ArgumentOutOfRangeException(nameof(readLength), "The diagnostic read would exceed the native address range."); + } + } + + internal static bool Covers(uint state, uint type, ulong baseAddress, ulong allocationBase, nuint regionSize, ulong address) => + state == MemoryCommit && type == MemoryPrivate && allocationBase != 0 && address >= baseAddress && address - baseAddress < regionSize; + + internal static bool CoversReadRange(ulong baseAddress, nuint regionSize, ulong address, int readLength) => + readLength >= 0 && address >= baseAddress && address - baseAddress <= regionSize + && (ulong) readLength <= (ulong) regionSize - (address - baseAddress); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial nint OpenProcess(uint access, [MarshalAs(UnmanagedType.Bool)] bool inherit, int processId); + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial nuint VirtualQueryEx(nint process, nint address, out MemoryBasicInformation information, nuint length); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool ReadProcessMemory(nint process, nint address, [Out] byte[] buffer, nuint size, out nuint bytesRead); + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CloseHandle(nint handle); + + private struct MemoryBasicInformation + { + public nint BaseAddress; + public nint AllocationBase; + public uint AllocationProtect; + public ushort PartitionId; + public nuint RegionSize; + public uint State; + public uint Protect; + public uint Type; + } + + internal sealed record MemoryObservation( + bool QuerySucceeded, + int QueryError, + ulong BaseAddress, + ulong AllocationBase, + uint AllocationProtect, + ushort PartitionId, + ulong RegionSize, + uint State, + uint Protect, + uint Type, + bool ReadRangeCovered, + bool ReadAttempted, + bool ReadReturned, + bool ReadSucceeded, + int ReadError, + ulong BytesRead, + string BytesHex) + { + internal static MemoryObservation OpenProcessFailed(int error) => new(false, error, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, string.Empty); + internal static MemoryObservation VirtualQueryFailed(int error) => new(false, error, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, false, 0, 0, string.Empty); + } + + private readonly struct ProcessHandle(nint value) : IDisposable + { + private nint Handle => value; + public static implicit operator nint(ProcessHandle handle) => handle.Handle; + public void Dispose() + { + if (value != 0) + { + CloseHandle(value); + } + } + } +} diff --git a/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbeTests.cs b/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbeTests.cs new file mode 100644 index 0000000..035727a --- /dev/null +++ b/tests/CheatEngine.Mcp.Tests/LiveQualification/TargetMemoryProbeTests.cs @@ -0,0 +1,48 @@ +using System.Runtime.Versioning; + +namespace CheatEngine.Mcp.Tests.LiveQualification; + +[SupportedOSPlatform("windows")] +public sealed class TargetMemoryProbeTests +{ + [Fact] + public void ValidateInspectionRequest_AllowsZeroLengthButRejectsOversizedLength() + { + TargetMemoryProbe.ValidateInspectionRequest(0x1000, 0); + Assert.Throws(() => + TargetMemoryProbe.ValidateInspectionRequest(0x1000, TargetMemoryProbe.MaximumDiagnosticReadLength + 1)); + } + + [Theory] + [InlineData(0UL, 1)] + [InlineData(0x8000000000000000UL, 1)] + [InlineData(0x7FFFFFFFFFFFFFFFUL, 2)] + public void ValidateInspectionRequest_RejectsInvalidOrOverflowingNativeRanges(ulong address, int readLength) + { + Assert.Throws(() => TargetMemoryProbe.ValidateInspectionRequest(address, readLength)); + } + + [Theory] + [InlineData(0x1000UL, 0x1000UL, 0x1FFCUL, 4, true)] + [InlineData(0x1000UL, 0x1000UL, 0x1FFCUL, 5, false)] + [InlineData(0x1000UL, 0x1000UL, 0x2000UL, 0, true)] + [InlineData(0x1000UL, 0x1000UL, 0x2000UL, 1, false)] + public void CoversReadRange_RequiresTheEntireReadToFitInTheQueriedRegion(ulong baseAddress, ulong regionSize, + ulong address, int readLength, bool expected) + { + Assert.Equal(expected, TargetMemoryProbe.CoversReadRange(baseAddress, checked((nuint) regionSize), address, readLength)); + } + + [Theory] + [InlineData(0x1000U, 0x20000U, 0x1000UL, 0x1000UL, 0x1000UL, 0x1800UL, true)] + [InlineData(0x1000U, 0x20000U, 0x1000UL, 0x1000UL, 0x1000UL, 0x2000UL, false)] + [InlineData(0x10000U, 0x20000U, 0x1000UL, 0x1000UL, 0x1000UL, 0x1800UL, false)] + [InlineData(0x1000U, 0x10000U, 0x1000UL, 0x1000UL, 0x1000UL, 0x1800UL, false)] + [InlineData(0x1000U, 0x20000U, 0x1000UL, 0UL, 0x1000UL, 0x1800UL, false)] + public void Covers_RequiresCommittedPrivateRegionAndAllocationBaseCoveringTheExactAddress(uint state, uint type, + ulong baseAddress, ulong allocationBase, ulong regionSize, + ulong address, bool expected) + { + Assert.Equal(expected, TargetMemoryProbe.Covers(state, type, baseAddress, allocationBase, checked((nuint) regionSize), address)); + } +} diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaAsmDebuggerV2Tests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaAsmDebuggerV2Tests.cs index 13d9ea2..9274802 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaAsmDebuggerV2Tests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaAsmDebuggerV2Tests.cs @@ -503,8 +503,13 @@ public void DebuggerV2_AggregatedCapture_EvictsItsGroupIndexWithTheBoundedRing() targetIs64Bit = function() return true end getOpenedProcessID = function() return 77 end getAddressSafe = function(_) return 0x401000 end + breakpointAddresses = {} + debug_getBreakpointList = function() return breakpointAddresses end debug_removeBreakpointByID = function(_) return true end - debug_setBreakpoint = function(_, _, _, callback) captureCallback = callback; return true, 88 end + debug_setBreakpoint = function(address, _, _, callback) + captureCallback = callback; table.insert(breakpointAddresses, address) + return true, {ID = 88, PID = 77, DTID = 9} + end """); LuaJsonResult result = ReadJson(LuaToolRuntime.BuildSource(DebuggerLuaScripts.StartCapture, diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerAdapterQualificationTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerAdapterQualificationTests.cs index 9baecf3..1bed6f9 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerAdapterQualificationTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerAdapterQualificationTests.cs @@ -6,6 +6,7 @@ using CheatEngine.Client.Processes; using CheatEngine.Client.Results; using CheatEngine.Client.Runtime; +using CheatEngine.Mcp.Core.Contract; using CheatEngine.Mcp.Core.Features; using CheatEngine.Mcp.Core.Jobs; using CheatEngine.Mcp.Tests.Support; @@ -96,11 +97,20 @@ public void DebuggerAdapter_BreakpointLifecycle_TracksThenReleasesTheKnownOwnedB InstallStubs(JobHostStubs + "\n" + """ bptExecute, bptAccess, bptWrite, bpmDebugRegister, bpmInt3, bpmException = 1, 2, 3, 4, 5, 6 debug_isDebugging = function() return true end + getOpenedProcessID = function() return 77 end getAddressSafe = function(value) observedExpression = value; return 0x401000 end + breakpointAddresses = {} + debug_getBreakpointList = function() return breakpointAddresses end debug_setBreakpoint = function(address, size, trigger, method, callback) - observedBreakpoint = {address, size, trigger, method, callback}; return true, 88 + observedBreakpoint = {address, size, trigger, method, callback}; table.insert(breakpointAddresses, address) + return true, {ID = 88, PID = 77, DTID = 9} + end + debug_removeBreakpointByID = function(id) + removalArgumentType = type(id) + removedBreakpoint = type(id) == 'table' and id.ID or id + table.remove(breakpointAddresses) + return true end - debug_removeBreakpointByID = function(id) removedBreakpoint = id; return true end targetIs64Bit = function() return true end """); using DebuggerToolHarness harness = CreateDebuggerTools(); @@ -118,6 +128,39 @@ public void DebuggerAdapter_BreakpointLifecycle_TracksThenReleasesTheKnownOwnedB Assert.True(deleted.Released); Assert.Equal(set.ResourceId, deleted.ResourceId); Assert.Equal(88L, ReadGlobal("removedBreakpoint")); + Assert.Equal("table", ReadGlobal("removalArgumentType")); + } + + [Fact] + public void DebuggerAdapter_BreakpointAcceptedWithoutId_RetainsLedgerEntryForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + """ + bptExecute, bptAccess, bptWrite = 1, 2, 3 + debug_isDebugging = function() return true end + getOpenedProcessID = function() return 77 end + targetIs64Bit = function() return true end + getAddressSafe = function(_) return 0x401000 end + debug_setBreakpoint = function() return true, nil end + breakpointRemovalCalls = 0 + debug_removeBreakpointByID = function(_) + breakpointRemovalCalls = breakpointRemovalCalls + 1 + return true + end + """); + ToolDispatch dispatch = CreateNativeDispatch(new McpFeatureOptions()); + + Assert.Throws(() => dispatch.RunLua(CheatEngineToolNames.DebuggerSetBreakpoint, + DebuggerLuaScripts.SetBreakpoint, DebuggerJsonContext.Default.DebuggerBreakpointSet, Token, + OwnNamespace, "breakpoint-bbbb22-1", "401000", 1L, "execute", "default", null, false)); + LuaStateRelease cleanup = ReadKernel(LuaJobKernelScripts.StateReleaseUnmanaged, + StateJsonContext.Default.LuaStateRelease, OwnNamespace, Array.Empty(), false); + + Assert.NotNull(cleanup.Failed); + Assert.Equal("breakpoint-bbbb22-1", cleanup.Failed.Id); + Assert.Contains("without a usable ID", cleanup.Failed.CleanupError, StringComparison.Ordinal); + Assert.Contains("breakpoint-bbbb22-1", Assert.Single(cleanup.Remaining).Id); + Assert.Equal(0L, ReadGlobal("breakpointRemovalCalls")); } [Theory] @@ -326,6 +369,76 @@ public void DebuggerAdapter_CaptureTraceAndRunTo_CreateBoundedJobsWithKnownRecei Assert.Equal(3L, ReadGlobal("lastContinue")); } + [Fact] + public void DebuggerAdapter_RunTo_StopAfterSelectedTargetChanges_RetainsJobWithoutRemovingFromTheNewTarget() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + """ + selectedProcessId = 77 + getOpenedProcessID = function() return selectedProcessId end + debug_isDebugging = function() return true end + debug_getContext = function(extra) assert(extra == false); return {} end + getAddressSafe = function(_) return 0x401000 end + bptExecute = 1 + co_run = 0 + debug_continueFromBreakpoint = function(_) end + breakpointAddresses = {} + debug_getBreakpointList = function() return breakpointAddresses end + debug_setBreakpoint = function(address, ...) table.insert(breakpointAddresses, address); return true, {ID = 88, PID = 77, DTID = 9} end + breakpointRemovalCalls = 0 + debug_removeBreakpointByID = function(id) + breakpointRemovalCalls = breakpointRemovalCalls + 1 + removedBreakpoint = id + return true + end + """); + + DebuggerRunToStarted runTo = CreateNativeDispatch(new McpFeatureOptions()).RunLua( + CheatEngineToolNames.DebuggerRunTo, DebuggerLuaScripts.StartRunTo, + DebuggerJsonContext.Default.DebuggerRunToStarted, Token, OwnNamespace, "debugrunto-bbbb22-1", 1, 10_000L, + "401000"); + InstallStubs("selectedProcessId = 78"); + LuaJobStop stopped = ReadKernel(LuaJobKernelScripts.Stop, StateJsonContext.Default.LuaJobStop, + OwnNamespace, runTo.JobId); + LuaJobPage retained = PollJob(runTo.JobId, 0, 1); + + Assert.Equal((77, "401000"), (runTo.ProcessId, runTo.Address)); + Assert.Equal((true, true, false, "stopped"), (stopped.Found, stopped.WasRunning, stopped.Released, stopped.State)); + Assert.Contains("selected target changed", stopped.CleanupError, StringComparison.OrdinalIgnoreCase); + Assert.Equal((JobState.Stopped, true), (retained.Job.State, retained.Job.RequiresManualRecovery)); + Assert.Equal(0L, ReadGlobal("breakpointRemovalCalls")); + } + + [Fact] + public void DebuggerAdapter_RunTo_AcceptedBreakpointWithoutId_RetainsJobForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + """ + getOpenedProcessID = function() return 77 end + debug_isDebugging = function() return true end + debug_getContext = function(extra) assert(extra == false); return {} end + getAddressSafe = function(_) return 0x401000 end + bptExecute = 1 + debug_setBreakpoint = function() return true, nil end + runToRemovalCalls = 0 + debug_removeBreakpointByID = function(_) + runToRemovalCalls = runToRemovalCalls + 1 + return true + end + """); + ToolDispatch dispatch = CreateNativeDispatch(new McpFeatureOptions()); + + Assert.Throws(() => dispatch.RunLua(CheatEngineToolNames.DebuggerRunTo, + DebuggerLuaScripts.StartRunTo, DebuggerJsonContext.Default.DebuggerRunToStarted, Token, + OwnNamespace, "debugrunto-bbbb22-1", 1, 10_000L, "401000")); + LuaJobPage retained = PollJob("debugrunto-bbbb22-1", 0, 1); + + Assert.Equal(JobState.Failed, retained.Job.State); + Assert.True(retained.Job.RequiresManualRecovery); + Assert.Contains("without a usable ID", retained.Job.CleanupError, StringComparison.Ordinal); + Assert.Equal(0L, ReadGlobal("runToRemovalCalls")); + } + private static DebuggerToolHarness CreateDebuggerTools(bool withProcess = false) { IOptions options = Options.Create(new McpExecutionOptions()); diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerBreakpointArityTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerBreakpointArityTests.cs index 27288c0..33f4460 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerBreakpointArityTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerBreakpointArityTests.cs @@ -20,21 +20,29 @@ public void DebuggerAdapter_SetBreakpoint_OmitsAbsentOptionalSlots(bool threadSp InstallStubs(JobHostStubs + "\n" + """ bptExecute, bptAccess, bptWrite, bpmDebugRegister, bpmInt3, bpmException = 1, 2, 3, 4, 5, 6 debug_isDebugging = function() return true end + getOpenedProcessID = function() return 77 end getAddressSafe = function(_) return 0x401000 end + breakpointAddresses = {} + debug_getBreakpointList = function() return breakpointAddresses end local function observe(threadSpecific, ...) local arguments = table.pack(...) observedArgumentCount = arguments.n local first = threadSpecific and 2 or 1 observedThread = threadSpecific and arguments[1] or 0 + table.insert(breakpointAddresses, arguments[first]) assert(arguments[first] == 0x401000 and arguments[first + 1] == 1 and arguments[first + 2] == bptExecute, 'Wrong breakpoint address, size or trigger') observedMethod = type(arguments[first + 3]) == 'number' and arguments[first + 3] or nil observedCallback = type(arguments[arguments.n]) == 'function' and arguments[arguments.n] or nil - return true, 88 + return true, {ID = 88, PID = 77, DTID = 9} end debug_setBreakpoint = function(...) return observe(false, ...) end debug_setBreakpointForThread = function(...) return observe(true, ...) end - debug_removeBreakpointByID = function(id) removedBreakpoint = id; return true end + debug_removeBreakpointByID = function(id) + removedBreakpoint = type(id) == 'table' and id.ID or id + table.remove(breakpointAddresses) + return true + end targetIs64Bit = function() return true end """); using DebuggerToolHarness harness = CreateDebuggerTools(); diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerCaptureTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerCaptureTests.cs index 0ccfe42..560a7c4 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerCaptureTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerCaptureTests.cs @@ -27,19 +27,42 @@ public sealed partial class NativeLuaToolRuntimeTests debug_getContext = function(_) return false end targetIsX86 = function() return true end targetIs64Bit = function() return wide end - getOpenedProcessID = function() return 77 end + selectedProcessId = 77 + getOpenedProcessID = function() return selectedProcessId end symbols = {['401000'] = 0x401000, ['game.exe'] = 0x140000000, ['my module.dll'] = 0x7FF000000000} getAddressSafe = function(name) return symbols[name] end disassemble = function(_) return 'instruction' end breakpointCalls = 0 - debug_removeBreakpointByID = function(_) return true end + breakpointRemovalCalls = 0 + breakpointRemovalResult = true + breakpointRemovalFailure = nil + breakpointRemovalDeferred = false + breakpointRemovalArgumentType = nil + breakpointAddresses = {} + debug_getBreakpointList = function() return breakpointAddresses end + debug_removeBreakpointByID = function(id) + breakpointRemovalCalls = breakpointRemovalCalls + 1 + breakpointRemovalArgumentType = type(id) + removedBreakpoint = type(id) == 'table' and id.ID or id + if breakpointRemovalResult and not breakpointRemovalDeferred then + for index = #breakpointAddresses, 1, -1 do + if breakpointAddresses[index] == breakpointAddress then + table.remove(breakpointAddresses, index) + break + end + end + end + return breakpointRemovalResult, breakpointRemovalFailure + end debug_setBreakpoint = function(address, size, trigger, callback) breakpointCalls = breakpointCalls + 1 breakpointAddress = address + table.insert(breakpointAddresses, address) breakpointTrigger = trigger if callback ~= nil then captureCallback = callback end - return true, 88 + if returnNilBreakpointHandle then return true, nil end + return true, breakpointHandle or {ID = 88, PID = 77, DTID = 9} end instructionOpcode = 'mov' instructionParameters = '[rax],edx' @@ -127,6 +150,23 @@ public void DebuggerV2_EffectiveAddressCapture_GroupsHitsByTheOperandAddress(str Assert.Empty(LuaFeatureScan.Scan(DebuggerLuaScripts.StartCapture)); } + [Fact] + public void DebuggerV2_Capture_AcceptedBreakpointWithoutId_RetainsJobForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + InstallStubs("returnNilBreakpointHandle = true"); + + Assert.Throws(() => StartEffectiveAddressCapture(8L)); + + LuaJobPage retained = PollJob(CaptureJobId, 0, 1); + Assert.Equal(JobState.Failed, retained.Job.State); + Assert.True(retained.Job.RequiresManualRecovery); + Assert.Contains("without a usable ID", retained.Job.CleanupError, StringComparison.Ordinal); + Assert.Equal((1L, 0L, 1L), + (ReadGlobal("breakpointCalls"), ReadGlobal("breakpointRemovalCalls"), EvaluateLua("#breakpointAddresses"))); + } + [Theory] [InlineData("lea", "rax,[rbx+10]", ToolErrorKind.InvalidArgument, "only computes an address")] [InlineData("nop", "dword ptr [rax+rax+00]", ToolErrorKind.InvalidArgument, "only computes an address")] @@ -158,6 +198,21 @@ public void DebuggerV2_EffectiveAddressCapture_RefusesAnOperandItCannotEvaluateB .GetProperty("exists").GetBoolean()); } + [Fact] + public void DebuggerV2_Capture_UnusableBreakpointId_RetainsTheJobForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + InstallStubs("breakpointHandle = {PID = 77, DTID = 9}"); + + Assert.Throws(() => StartEffectiveAddressCapture(8L)); + LuaJobPage retained = PollJob(CaptureJobId, 0, 1); + + Assert.Equal((JobState.Failed, true), (retained.Job.State, retained.Job.RequiresManualRecovery)); + Assert.Contains("usable breakpoint ID", retained.Job.CleanupError, StringComparison.Ordinal); + Assert.Equal((1L, 0L), (ReadGlobal("breakpointCalls"), ReadGlobal("breakpointRemovalCalls"))); + } + [Fact] public void DebuggerV2_EffectiveAddressCapture_EvictsItsGroupIndexWithTheBoundedRing() { @@ -209,6 +264,82 @@ public void DebuggerV2_EffectiveAddressCapture_MissingOperandRegister_FailsTheJo Assert.Empty(page.Items); } + [Fact] + public void DebuggerV2_Capture_StopAfterSelectedTargetChanges_RetainsJobWithoutRemovingFromTheNewTarget() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + StartEffectiveAddressCapture(8L); + InstallStubs("selectedProcessId = 78"); + + LuaJobStop stopped = ReadKernel(LuaJobKernelScripts.Stop, StateJsonContext.Default.LuaJobStop, + OwnNamespace, CaptureJobId); + LuaJobPage retained = PollJob(CaptureJobId, 0, 1); + + Assert.Equal((true, true, false, "stopped"), + (stopped.Found, stopped.WasRunning, stopped.Released, stopped.State)); + Assert.Contains("selected target changed", stopped.CleanupError, StringComparison.OrdinalIgnoreCase); + Assert.Equal((JobState.Stopped, true), (retained.Job.State, retained.Job.RequiresManualRecovery)); + Assert.Equal(0L, ReadGlobal("breakpointRemovalCalls")); + } + + [Fact] + public void DebuggerV2_Capture_FalseyBreakpointRemovalResult_RetainsTheJobForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + StartEffectiveAddressCapture(8L); + InstallStubs("breakpointRemovalResult = nil; breakpointRemovalFailure = 'breakpoint removal failed'"); + + LuaJobStop stopped = ReadKernel(LuaJobKernelScripts.Stop, StateJsonContext.Default.LuaJobStop, + OwnNamespace, CaptureJobId); + LuaJobPage retained = PollJob(CaptureJobId, 0, 1); + + Assert.Equal((true, true, false, "stopped"), + (stopped.Found, stopped.WasRunning, stopped.Released, stopped.State)); + Assert.Contains("breakpoint removal failed", stopped.CleanupError, StringComparison.Ordinal); + Assert.Equal((JobState.Stopped, true), (retained.Job.State, retained.Job.RequiresManualRecovery)); + Assert.Equal((88L, 1L, "table"), (ReadGlobal("removedBreakpoint"), ReadGlobal("breakpointRemovalCalls"), + ReadGlobal("breakpointRemovalArgumentType"))); + } + + [Fact] + public void DebuggerV2_Capture_TruthyBreakpointRemoval_AllowsCheatEngineDeferredListCleanup() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + StartEffectiveAddressCapture(8L); + InstallStubs("breakpointRemovalDeferred = true"); + + LuaJobStop stopped = ReadKernel(LuaJobKernelScripts.Stop, StateJsonContext.Default.LuaJobStop, + OwnNamespace, CaptureJobId); + + Assert.True(stopped.Released); + Assert.Equal((88L, 1L, "table"), (ReadGlobal("removedBreakpoint"), ReadGlobal("breakpointRemovalCalls"), + ReadGlobal("breakpointRemovalArgumentType"))); + Assert.Equal(1L, EvaluateLua("#breakpointAddresses")); + + // CE keeps an inactive, marked-for-deletion breakpoint in its list until the debugger thread's later cleanup pass. + InstallStubs("table.remove(breakpointAddresses)"); + Assert.Equal(0L, EvaluateLua("#breakpointAddresses")); + } + + [Fact] + public void DebuggerV2_Capture_RemovalReadbackPreservesAnExistingBreakpointAtTheSameAddress() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs); + InstallStubs("table.insert(breakpointAddresses, 0x401000)"); + StartEffectiveAddressCapture(8L); + + LuaJobStop stopped = ReadKernel(LuaJobKernelScripts.Stop, StateJsonContext.Default.LuaJobStop, + OwnNamespace, CaptureJobId); + + Assert.True(stopped.Released); + Assert.Equal(1L, EvaluateLua("#breakpointAddresses")); + Assert.Equal("table", ReadGlobal("breakpointRemovalArgumentType")); + } + // Cheat Engine's 64-bit operand text names 64-bit registers even under an address-size override (67), so only the // legacy prefixes in the instruction bytes reveal that the CPU truncates the address to 32 bits. [Theory] diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerTraceTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerTraceTests.cs index bba8440..2eb9f2e 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerTraceTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaDebuggerTraceTests.cs @@ -63,7 +63,7 @@ public sealed partial class NativeLuaToolRuntimeTests end debug_removeBreakpointByID = function(id) breakpointRemovalCalls = breakpointRemovalCalls + 1 - removedBreakpoint = id + removedBreakpoint = type(id) == 'table' and id.ID or id if not breakpointRemovalAccepted then return false end if traceBreakpointDeferredRemoval then traceBreakpointRemovalPending = true @@ -80,7 +80,8 @@ public sealed partial class NativeLuaToolRuntimeTests breakpointTrigger = arguments[3] breakpointCallback = arguments[4] traceBreakpointListed = true - return breakpointAccepted, breakpointAccepted and 88 or nil + if returnNilBreakpointHandle then return breakpointAccepted, nil end + return breakpointAccepted, breakpointAccepted and {ID = 88, PID = 77, DTID = 9} or nil end """; @@ -153,6 +154,24 @@ .. Enumerable.Range(1, contexts.Length).Select(static n => n.ToString("X", Cultu LuaFixedScriptAssert.NeverLoadsCode(DebuggerLuaScripts.StartTrace); } + [Fact] + public void DebuggerV2_Trace_AcceptedBreakpointWithoutId_RetainsJobForManualRecovery() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\n" + CaptureHostStubs + "\n" + TraceHostStubs + "\n" + TraceBreakpointStub); + InstallStubs("returnNilBreakpointHandle = true"); + + Assert.Throws(() => StartTrace(5L, null, null)); + + LuaJobPage retained = PollJob(TraceJobId, 0, 1); + Assert.Equal(JobState.Failed, retained.Job.State); + Assert.True(retained.Job.RequiresManualRecovery); + Assert.Contains("without a usable ID", retained.Job.CleanupError, StringComparison.Ordinal); + Assert.Equal((1L, 0L, true), + (ReadGlobal("breakpointCalls"), ReadGlobal("breakpointRemovalCalls"), ReadGlobal("traceBreakpointListed"))); + Assert.Null(ReadGlobal("debugger_onBreakpoint")); + } + [Fact] public void DebuggerV2_Trace_DefersTerminalBreakpointCleanupUntilTheCallbackReturns() { diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs index a1503cc..170443c 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs @@ -1,10 +1,12 @@ using System.Reflection; using CheatEngine.Client; +using CheatEngine.Client.Dispatching; using CheatEngine.Client.Lua; using CheatEngine.Client.Results; using CheatEngine.Mcp.Core.Contract; using CheatEngine.Mcp.Core.Features; +using CheatEngine.Mcp.Core.Lua; using CheatEngine.Mcp.Tests.Support; using CheatEngine.SDK.Lua.Interop.Api; @@ -32,6 +34,36 @@ public void Execute_Prelude_FormatsHexBytesAndNumbersAndExposesTheBudget() "NaN", "Infinity", "-Infinity", 0.5, 7, LuaToolRuntime.DefaultBudgetMilliseconds, false), probe); } + [Fact] + public void FixedLuaExecutor_StoppingCleanupUsesTheAttachedRuntimeOnlyForReleaseScripts() + { + using RuntimeScope scope = CreateScope(); + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, _) => + method.Name == "get_IsMainThread" + ? true + : throw new NotSupportedException($"Unexpected dispatcher call: {method.Name}.")); + ILuaClient lua = ClientTestDouble.Create((_, _) => + throw new InvalidOperationException("The Client Lua facade must stay closed during disable.")); + ICheatEngineClient client = ClientTestDouble.Client(dispatcher, stopping.Token, + (nameof(ICheatEngineClient.Lua), lua)); + PluginLifecycleLuaScope lifecycleLuaScope = new(); + PluginFixedLuaExecutor executor = new(client, lifecycleLuaScope); + using (lifecycleLuaScope.BeginResourceCleanup(client)) + { + LuaJsonResult result = executor.Execute("mcp_job_stop", + LuaToolRuntime.BuildSource("return {'released'}", []), TestJsonContext.Default.StringArray, + new LuaJsonBufferPool(), LuaOpaqueValueHandling.Reject, CancellationToken.None); + + Assert.Equal(["released"], result.Value!); + Assert.False(result.IsError); + Assert.Throws(() => executor.Execute("debugger_get_status", + LuaToolRuntime.BuildSource("return {'blocked'}", []), TestJsonContext.Default.StringArray, + new LuaJsonBufferPool(), LuaOpaqueValueHandling.Reject, CancellationToken.None)); + } + } + [Fact] public void Execute_Prelude_ExpiresAfterTheBudgetAcrossTheTickWrap() { diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaLivePluginBridgeTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaLivePluginBridgeTests.cs index 03839f0..52f65ab 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaLivePluginBridgeTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaLivePluginBridgeTests.cs @@ -4,6 +4,15 @@ namespace CheatEngine.Mcp.Tests.NativeLua; public sealed partial class NativeLuaToolRuntimeTests { + [Fact] + public void LivePluginBridge_ObserveProtocolCarriesOneExactBreakpointAddress() + { + string source = LivePluginBridge.Render("request", "response", Quote); + Assert.Contains("debug_getBreakpointList()", source, StringComparison.Ordinal); + Assert.Contains("(0x[0-9A-F]+)", source, StringComparison.Ordinal); + Assert.Contains("missing exact breakpoint address", source, StringComparison.Ordinal); + } + [Fact] public void LivePluginBridge_Disable_UsesOnlySpaceKeyMessagesAndSettingsOk() { @@ -61,6 +70,47 @@ public void LivePluginBridge_Enable_UsesThePreviouslyDisabledState() Assert.Equal(1L, EvaluateLua("okClicks")); } + [Fact] + public void LivePluginBridge_ObserveReportsDisabledPluginWithoutStatusHook() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(Stubs("10\nobserve\n0x7FF600001012\n", "CheatEngine.Mcp.dll:CheatEngine.Mcp", pluginChecked: false, + statusHookPresent: false)); + RunPluginBridge("pluginEnabled=false; pluginControl()"); + string[] fields = ((string) EvaluateLua("response")!).Split('\n'); + Assert.Equal(23, fields.Length); + Assert.Equal("ok", fields[0]); + Assert.Equal("uninstalled", fields[10]); + Assert.Equal("", fields[21]); + Assert.Equal("raw:0:", fields[22]); + Assert.Equal(0L, EvaluateLua("#messages")); + Assert.Equal(0L, EvaluateLua("okClicks")); + } + + [Fact] + public void LivePluginBridge_RemovalProbe_RecordsIdAndReturnErrorThenRestoresOriginal() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(Stubs("11\ninstall_probe\n", "CheatEngine.Mcp.dll:CheatEngine.Mcp") + "\n" + $$""" + debug_removeBreakpointByID=function(id) return nil,'CE refused '..tostring(type(id)=='table' and id.ID or id) end + debug_getBreakpointList=function() return {[1]=0x7FF600001012} end + """); + RunPluginBridge("pluginEnabled=false; originalRemover=debug_removeBreakpointByID; pluginControl(); first,second=debug_removeBreakpointByID({ID=88,id=88}); probeState=rawget(_G,'__cheatengine_mcp_lifecycle_removal_probe'); probeReceipt=probeState.receipt; response=''; pluginCommand='12\\nremove_probe\\n'; pluginControl()"); + Assert.Equal("ok\n12\nremove_probe", EvaluateLua("response")); + Assert.StartsWith("table:", (string) EvaluateLua("probeReceipt.id")!, StringComparison.Ordinal); + Assert.Equal("table", EvaluateLua("probeReceipt.idType")); + Assert.True((bool) EvaluateLua("probeReceipt.succeeded")!); + Assert.Equal("CE refused 88", EvaluateLua("probeReceipt.failure")); + Assert.Contains("string:ID=number:88", (string) EvaluateLua("probeReceipt.idFields")!, StringComparison.Ordinal); + Assert.Equal("nil", EvaluateLua("probeReceipt.returnValue")); + Assert.Equal(1L, EvaluateLua("probeReceipt.beforeCount")); + Assert.Equal(1L, EvaluateLua("probeReceipt.afterCount")); + Assert.True((bool) EvaluateLua("debug_removeBreakpointByID==originalRemover")!); + Assert.True((bool) EvaluateLua("rawget(_G,'__cheatengine_mcp_lifecycle_removal_probe')==nil")!); + Assert.Null(EvaluateLua("first")); + Assert.Equal("CE refused 88", EvaluateLua("second")); + } + [Theory] [InlineData(0, 2, "TformSettings")] [InlineData(1, 0, "TUnexpectedSettings")] @@ -91,9 +141,9 @@ public void LivePluginBridge_LongMultibyteError_RemainsBoundedAscii() } private static string Stubs(string command, string row, int count = 1, int? countAfterReload = null, - string settingsClass = "TformSettings") => $$""" - response=''; messages={}; okClicks=0; settingsFormRequested=false - io={open=function(_,mode) if mode=='r' then return {read=function() return {{Quote(command)}} end,close=function() end} end return {write=function(_,v) response=response..v end,close=function() end} end} + string settingsClass = "TformSettings", bool pluginChecked = false, bool statusHookPresent = false) => $$""" + response=''; messages={}; okClicks=0; settingsFormRequested=false; pluginCommand={{Quote(command)}} + io={open=function(_,mode) if mode=='r' then return {read=function() return pluginCommand end,close=function() end} end return {write=function(_,v) response=response..v end,close=function() end} end} os={remove=function() return true end,rename=function() return true end} local items={[0]={{Quote(row)}},Count={{count}}} reloadSettingsFromRegistry=function() @@ -101,9 +151,18 @@ private static string Stubs(string command, string row, int count = 1, int? coun items.Count={{countAfterReload ?? count}} end local clb={Items=items,Handle=99} + clb.Checked={} + clb.Checked[0]={{pluginChecked.ToString().ToLowerInvariant()}} local pc,tab,ok={},{},{doClick=function() okClicks=okClicks+1 end} local f={ClassName={{Quote(settingsClass)}},findComponentByName=function(n) return ({pcSetting=pc,Plugins=tab,clbPlugins=clb,btnOK=ok})[n] end} getSettingsForm=function() settingsFormRequested=true; return f end + getOpenedProcessID=function() return 77 end + debug_getBreakpointList=function() return {} end + getMainForm=function() + local menu={} + menu.findComponentByName=function(_) return {{(statusHookPresent ? "true" : "nil")}} end + return {Menu=menu} + end sendMessage=function(...) table.insert(messages,{...}) end """; diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStateLedgerTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStateLedgerTests.cs index 8a7350c..8b7142d 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStateLedgerTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStateLedgerTests.cs @@ -1,10 +1,15 @@ using System.Text.Json; +using CheatEngine.Client; +using CheatEngine.Client.Dispatching; using CheatEngine.Mcp.Core.Contract; using CheatEngine.Mcp.Core.Features; using CheatEngine.Mcp.Core.Jobs; +using CheatEngine.Mcp.Core.Targets; +using CheatEngine.Mcp.Plugin; using CheatEngine.Mcp.Tests.Support; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace CheatEngine.Mcp.Tests.NativeLua; @@ -26,6 +31,43 @@ public sealed partial class NativeLuaToolRuntimeTests private static readonly string[] AcknowledgedIds = ["pause-aaaa11-1", "speedhack-bbbb22-1"]; + [Fact] + public void ResourceCleanup_LedgerOnlyState_UsesTheStoppingActivationRuntime() + { + using RuntimeScope scope = CreateScope(); + InstallStubs(JobHostStubs + "\ngetOpenedProcessID = function() return 77 end"); + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, arguments) => + { + if (method.Name == "get_IsMainThread") + { + return true; + } + + if (method.Name.StartsWith("Invoke", StringComparison.Ordinal) && arguments?[0] is Delegate callback) + { + return callback.DynamicInvoke(); + } + + throw new NotSupportedException($"Unexpected lifecycle dispatcher member: {method.Name}."); + }); + ICheatEngineClient client = ClientTestDouble.Client(dispatcher, stopping.Token); + using TestActivation activation = new(client); + TargetResources resources = activation.Services.GetRequiredService(); + string id = $"pause-{resources.Namespace}-1"; + Record(resources.Namespace, id, "pause"); + + McpServerModule module = activation.Module; + module.OnDisabling(client); + Assert.Throws(() => resources.TrackState(id, "pause")); + module.OnReleasingResources(client); + + Assert.Equal($"{id}:released;", ReadGlobal("released")); + Assert.Empty(ReadKernel(LuaJobKernelScripts.StateSnapshot, + StateJsonContext.Default.LuaStateSnapshot, resources.Namespace).Entries); + } + [Fact] public void StateLedger_RecordedEffects_AreDescribedAndReleasedOnceOrKeptWithTheirCleanupError() { @@ -105,16 +147,16 @@ public void StateLedger_ReleaseUnmanaged_ReleasesOrphansNewestFirstAndStopsAtThe string ownTracked = CreateJob("trace", 8, 60000, 0); InstallStubs("clock = 40; failRelease = 'speedhack-aaaa11-1'"); - LuaStateRelease listed = ReadKernel(LuaJobKernelScripts.StateReleaseUnmanaged, + LuaStateRelease ownedOnly = ReadKernel(LuaJobKernelScripts.StateReleaseUnmanaged, StateJsonContext.Default.LuaStateRelease, OwnNamespace, new[] { ownTracked }, false); - Assert.Empty(listed.Released); - Assert.Equal(["pause-bbbb22-1", "pause-aaaa11-3", orphanJob, "speedhack-aaaa11-1"], - listed.Remaining.Select(static entry => entry.Id).ToArray()); + Assert.Equal(["pause-bbbb22-1"], ownedOnly.Released.Select(static entry => entry.Id).ToArray()); + Assert.Equal(["pause-aaaa11-3", orphanJob, "speedhack-aaaa11-1"], + ownedOnly.Remaining.Select(static entry => entry.Id).ToArray()); LuaStateRelease released = ReadKernel(LuaJobKernelScripts.StateReleaseUnmanaged, StateJsonContext.Default.LuaStateRelease, OwnNamespace, new[] { ownTracked }, true); - Assert.Equal(["pause-bbbb22-1", "pause-aaaa11-3", orphanJob], + Assert.Equal(["pause-aaaa11-3", orphanJob], released.Released.Select(static entry => entry.Id).ToArray()); Assert.Equal(("speedhack-aaaa11-1", "restore refused"), (released.Failed!.Id, released.Failed.CleanupError)); Assert.Equal("speedhack-aaaa11-1", Assert.Single(released.Remaining).Id); diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStatusIndicatorTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStatusIndicatorTests.cs index adf1e9f..95b4005 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStatusIndicatorTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaStatusIndicatorTests.cs @@ -25,8 +25,17 @@ public void McpStatus_EnableDisableReenable_UpdatesOneMenuAndItsDetails() InstallStubs("statusItem.OnClick()"); Assert.Equal(enabledDetails, ReadGlobal("shownMessage")); - // Lifecycle UI must work without Client Lua dispatch; this double exposes no Lua service. - McpStatusIndicator.ShowDisabled(ClientTestDouble.Client(), "This instance is disabled."); + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, _) => + method.Name == "get_IsMainThread" ? true : throw new NotSupportedException()); + ICheatEngineClient stoppingClient = ClientTestDouble.Client(dispatcher, stopping.Token); + PluginLifecycleLuaScope lifecycleLuaScope = new(); + // Final status reporting has a dedicated lifecycle scope and does not expose the Client Lua facade. + using (lifecycleLuaScope.BeginDeactivationNotification(stoppingClient)) + { + McpStatusIndicator.ShowDisabled(stoppingClient, "This instance is disabled.", lifecycleLuaScope); + } Assert.Equal("MCP: Disabled", ReadStatusCaption()); InstallStubs("statusItem.OnClick()"); Assert.Equal("This instance is disabled.", ReadGlobal("shownMessage")); @@ -44,8 +53,19 @@ public void McpStatus_DisableWithoutIndicator_DoesNotCreateResources() { using RuntimeScope scope = CreateScope(); InstallStatusMenu(); - - McpStatusIndicator.ShowDisabled(ClientTestDouble.Client(), "Disabled"); + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, _) => + method.Name == "get_IsMainThread" ? true : throw new NotSupportedException()); + ICheatEngineClient client = ClientTestDouble.Client(dispatcher, stopping.Token); + PluginLifecycleLuaScope lifecycleLuaScope = new(); + + Assert.Throws(() => + McpStatusIndicator.ShowDisabled(ClientTestDouble.Client(), "Disabled", lifecycleLuaScope)); + using (lifecycleLuaScope.BeginDeactivationNotification(client)) + { + McpStatusIndicator.ShowDisabled(client, "Disabled", lifecycleLuaScope); + } Assert.Equal(0L, ReadGlobal("createdMenus")); Assert.Equal(0L, ReadGlobal("addedMenus")); @@ -85,6 +105,7 @@ public void McpStatus_ServerStartFails_KeepsFailureVisibleDuringRollback() Assert.Contains("Could not start the MCP server", failure.Message); Assert.Equal("MCP: Start failed", ReadStatusCaption()); module.OnDisabling(client); + module.OnDeactivated(client); Assert.Equal("MCP: Start failed", ReadStatusCaption()); InstallStubs("statusItem.OnClick()"); string shown = Assert.IsType(ReadGlobal("shownMessage")); diff --git a/tests/CheatEngine.Mcp.Tests/Plugin/McpModuleLifecycleTests.cs b/tests/CheatEngine.Mcp.Tests/Plugin/McpModuleLifecycleTests.cs index 95a4b58..0444740 100644 --- a/tests/CheatEngine.Mcp.Tests/Plugin/McpModuleLifecycleTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Plugin/McpModuleLifecycleTests.cs @@ -6,6 +6,7 @@ using CheatEngine.Client; using CheatEngine.Client.Dispatching; +using CheatEngine.Mcp.Core.Targets; using CheatEngine.Mcp.Plugin; using CheatEngine.Mcp.Tests.Support; @@ -366,7 +367,8 @@ public async Task Enable_UnavailableLogLocation_KeepsTheActualModuleRunningAndDi activation.Services.GetRequiredService>()); McpStatusIndicator status = new(discovery, unavailableLog, activation.Services.GetRequiredService>()); - module = new McpServerModule(factory, activation.Targets, status); + module = new McpServerModule(factory, activation.Targets, status, + activation.Services.GetRequiredService(), new PluginLifecycleLuaScope()); InstanceRegistry registry = new(instances); using HttpClient http = new(); @@ -404,7 +406,8 @@ public void Enable_PartiallyStartedBackendStopsBeforeItRethrowsTheStartupFailure using TestActivation activation = new(client); PartiallyStartedBackend backend = new(); McpServerModule module = new(new FailingBackendFactory(backend), activation.Targets, - activation.Services.GetRequiredService()); + activation.Services.GetRequiredService(), + activation.Services.GetRequiredService(), new PluginLifecycleLuaScope()); InvalidOperationException failure = Assert.Throws(() => module.OnEnabled(client)); @@ -424,7 +427,8 @@ public void Enable_SecondCall_IsRejectedBeforeCreatingAnotherBackend() StartedBackend backend = new(); CountingBackendFactory factory = new(backend); McpServerModule module = new(factory, activation.Targets, - activation.Services.GetRequiredService()); + activation.Services.GetRequiredService(), + activation.Services.GetRequiredService(), new PluginLifecycleLuaScope()); try { module.OnEnabled(client); @@ -444,6 +448,90 @@ public void Enable_SecondCall_IsRejectedBeforeCreatingAnotherBackend() Assert.All(accessed, member => Assert.Contains(member, AllowedClientMembers)); } + [Fact] + public void ResourceCleanup_ReleasesTrackedMcpResourcesBeforeClientLeaseDrain() + { + List accessed = []; + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineClient client = CleanupClient(accessed, stopping); + using TestActivation activation = new(client); + TargetResources resources = new(); + TrackedResource owned = new(); + resources.Track(owned); + StartedBackend backend = new(); + McpServerModule module = new(new CountingBackendFactory(backend), activation.Targets, + activation.Services.GetRequiredService(), resources, new PluginLifecycleLuaScope()); + try + { + module.OnEnabled(client); + + module.OnDisabling(client); + + Assert.Equal(0, owned.ReleaseCalls); + Assert.Equal(1, resources.Count); + Assert.Throws(() => resources.Track(new TrackedResource())); + Assert.Equal(1, resources.Count); + Assert.DoesNotContain("Invoke", accessed); + Assert.Equal(1, backend.StopAcceptingCalls); + + module.OnDeactivated(client); + + Assert.Equal(1, accessed.Count(member => member == "Invoke")); + Assert.Equal(1, backend.StopAcceptingCalls); + + module.OnReleasingResources(client); + + Assert.Equal(1, owned.ReleaseCalls); + Assert.Equal(0, resources.Count); + Assert.Contains("Invoke", accessed); + Assert.Equal(1, backend.StopAcceptingCalls); + } + finally + { + module.Dispose(); + } + + Assert.Equal(2, accessed.Count(member => member == "Invoke")); + Assert.All(accessed.Where(member => member != "Invoke"), member => Assert.Contains(member, AllowedClientMembers)); + } + + [Fact] + public void ResourceCleanup_WithNoManagedEntriesStillReleasesLedgerOwnedState() + { + List accessed = []; + using CancellationTokenSource stopping = new(); + stopping.Cancel(); + ICheatEngineClient client = CleanupClient(accessed, stopping); + using TestActivation activation = new(client); + StateTestHarness state = new(); + string id = $"pause-{state.Ledger!.Namespace}-1"; + state.Answer(_ => new LuaStateRelease( + [StateTestHarness.Entry(id, "resource", "released", false)], null, [])); + StartedBackend backend = new(); + McpServerModule module = new(new CountingBackendFactory(backend), activation.Targets, + activation.Services.GetRequiredService(), state.Resources, + new PluginLifecycleLuaScope()); + try + { + module.OnEnabled(client); + module.OnDisabling(client); + Assert.Throws(() => state.Resources.TrackState(id, "pause")); + + Assert.Equal(0, state.Resources.Count); + module.OnReleasingResources(client); + + Assert.Equal("mcp_state_release", Assert.Single(state.LuaCalls).Operation); + Assert.Empty(state.Resources.List()); + } + finally + { + module.Dispose(); + } + + Assert.All(accessed.Where(member => member != "Invoke"), member => Assert.Contains(member, AllowedClientMembers)); + } + [Fact] public async Task Disabling_DuringEnable_StopsTheBackendBeforeItCanRun() { @@ -452,7 +540,8 @@ public async Task Disabling_DuringEnable_StopsTheBackendBeforeItCanRun() using TestActivation activation = new(client); BlockingBackend backend = new(); McpServerModule module = new(new BlockingBackendFactory(backend), activation.Targets, - activation.Services.GetRequiredService()); + activation.Services.GetRequiredService(), + activation.Services.GetRequiredService(), new PluginLifecycleLuaScope()); try { Task enabling = Task.Run(() => module.OnEnabled(client), TestContext.Current.CancellationToken); @@ -475,16 +564,36 @@ public async Task Disabling_DuringEnable_StopsTheBackendBeforeItCanRun() private static ICheatEngineClient StrictClient(List accessed, CancellationTokenSource? stopping = null) { - ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, _) => + return CreateClient(accessed, stopping, allowDispatch: false); + } + + private static ICheatEngineClient CleanupClient(List accessed, CancellationTokenSource stopping) + { + return CreateClient(accessed, stopping, allowDispatch: true); + } + + private static ICheatEngineClient CreateClient(List accessed, CancellationTokenSource? stopping, + bool allowDispatch) + { + ICheatEngineDispatcher dispatcher = ClientTestDouble.Create((method, arguments) => { lock (accessed) { accessed.Add(method.Name); } - return method.Name == "get_IsMainThread" - ? true - : throw new NotSupportedException($"Lifecycle callbacks must not dispatch Client work: {method.Name}."); + if (allowDispatch && method.Name.StartsWith("Invoke", StringComparison.Ordinal) + && arguments?[0] is Delegate callback) + { + return callback.DynamicInvoke(); + } + + if (method.Name == "get_IsMainThread") + { + return true; + } + + throw new NotSupportedException($"Lifecycle callbacks must not dispatch Client work: {method.Name}."); }); return ClientTestDouble.Create((method, _) => { @@ -523,6 +632,7 @@ private static async Task WaitUntilStoppedAsync(HttpClient http, string endpoint throw new TimeoutException("The disposed module left its MCP listener running."); } + private static async Task AssertPublishedIdentityAsync(HttpClient http, InstanceDescriptor instance) { using HttpRequestMessage request = new(HttpMethod.Get, new Uri(new Uri(instance.Endpoint), "instance")); @@ -533,6 +643,31 @@ private static async Task AssertPublishedIdentityAsync(HttpClient http, Instance Assert.Equal(instance.InstanceId, body["instanceId"]!.GetValue()); } + private sealed class TrackedResource : ITargetResource + { + private bool _released; + + public TargetResourceDescriptor Descriptor => new("test-activation-1", "test", TargetResourceCategory.LuaState, + _released ? TargetResourceState.Ended : TargetResourceState.Active, DateTimeOffset.UtcNow); + + public bool IsEnded => _released; + + public bool HoldsHostState => !_released; + + public int ReleaseCalls + { + get; + private set; + } + + public ResourceReleaseOutcome Release(CancellationToken cancellationToken) + { + ReleaseCalls++; + _released = true; + return ResourceReleaseOutcome.Released(); + } + } + private sealed class FailingBackendFactory(PartiallyStartedBackend backend) : IMcpBackendHostFactory { public string BaseUrl => "http://127.0.0.1:45678/"; diff --git a/tests/CheatEngine.Mcp.Tests/README.md b/tests/CheatEngine.Mcp.Tests/README.md index f1e759a..5274085 100644 --- a/tests/CheatEngine.Mcp.Tests/README.md +++ b/tests/CheatEngine.Mcp.Tests/README.md @@ -50,7 +50,7 @@ It is opt-in, refuses to run in CI, requires Cheat Engine and DebugView to be cl Follow [Live qualification](../../CONTRIBUTING.md#live-qualification) exactly, including publishing the matching configuration and supplying the required acknowledgement environment variable. Do not include this category in an ordinary local test run. -The compiler-only qualification is a separately approved scenario. It is never selected by ordinary VL runs: set `CHEATENGINE_MCP_LIVE_QUALIFICATION_SCENARIO` to `compiler` and `CHEATENGINE_MCP_LIVE_CODE_EXECUTION_QUALIFICATION` to its exact acknowledgement from [the compiler policy](../../docs/qualification/v2.0.0/compiler-probe-policy.md). It refuses CI, a non-clean/unidentified candidate, a saved Cheat Engine `Don't use tempdir` override, or a missing reviewed Release distribution before a private run directory or Cheat Engine process is created. It compiles only the policy's fixed payloads and does not authorize injection or invocation. +The compiler-only qualification is a separately approved scenario. It is never selected by ordinary VL runs: set `CHEATENGINE_MCP_LIVE_QUALIFICATION_SCENARIO` to `compiler` and `CHEATENGINE_MCP_LIVE_CODE_EXECUTION_QUALIFICATION` to its exact acknowledgement from [the compiler policy](../../docs/qualification/all-tools/compiler-probe-policy.md). It refuses CI, a non-clean/unidentified candidate, a saved Cheat Engine `Don't use tempdir` override, or a missing reviewed Release distribution before a private run directory or Cheat Engine process is created. It compiles only the policy's fixed payloads and does not authorize injection or invocation. The `assembler` and `debugger` scenarios also require their own exact acknowledgement in addition to the ordinary live opt-in. Their fixed cases and cleanup requirements are described in @@ -71,7 +71,7 @@ It does not replay the failing resource/scanner prefix or qualify the full workf The `process-create` scenario uses the ordinary live acknowledgement with all four optional gates disabled. It also requires the installed .NET Framework compiler to build its fixed owned fixture. It produces its own session receipts. The `mono` scenario uses the privately extracted x64 Mono runtime and a dedicated fixed-Mono acknowledgement; -only target-code execution is enabled. See the [runner contracts](../../docs/qualification/v2.0.0/runners.md). +only target-code execution is enabled. See the [runner contracts](../../docs/qualification/all-tools/runners.md). The report's `-LiveSummaries` parameter combines separate sessions only when their product binaries match; each tool's operation and cleanup receipts must belong to the same passing session. diff --git a/tests/CheatEngine.Mcp.Tests/Support/StateTestHarness.cs b/tests/CheatEngine.Mcp.Tests/Support/StateTestHarness.cs index 643f0ff..4a15233 100644 --- a/tests/CheatEngine.Mcp.Tests/Support/StateTestHarness.cs +++ b/tests/CheatEngine.Mcp.Tests/Support/StateTestHarness.cs @@ -22,6 +22,7 @@ internal sealed class StateTestHarness { private readonly ConcurrentDictionary> _answers = new(); private int _dispatches; + private Action? _afterDispatch; internal StateTestHarness(McpExecutionOptions? options = null, bool onMainThread = true, bool withLedger = true) { @@ -40,7 +41,9 @@ internal StateTestHarness(McpExecutionOptions? options = null, bool onMainThread } Interlocked.Increment(ref _dispatches); - return callback.DynamicInvoke(); + object? result = callback.DynamicInvoke(); + Interlocked.Exchange(ref _afterDispatch, null)?.Invoke(); + return result; } throw new NotSupportedException($"No dispatcher behavior was configured for {method.Name}."); @@ -108,6 +111,13 @@ internal JobRegistry Jobs /// How many dispatches were requested. internal int Dispatches => Volatile.Read(ref _dispatches); + /// Runs once after the next dispatcher callback returns, before its caller observes the result. + internal Action? AfterDispatch + { + get => Volatile.Read(ref _afterDispatch); + set => Volatile.Write(ref _afterDispatch, value); + } + /// Answers every Lua operation whose result is , given its complete source. internal void Answer(Func answer) { diff --git a/tests/CheatEngine.Mcp.Tests/Tools/Debugger/DebuggerToolsTests.cs b/tests/CheatEngine.Mcp.Tests/Tools/Debugger/DebuggerToolsTests.cs index 3b6bb10..9a6fea0 100644 --- a/tests/CheatEngine.Mcp.Tests/Tools/Debugger/DebuggerToolsTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Tools/Debugger/DebuggerToolsTests.cs @@ -536,10 +536,14 @@ public void FixedScripts_TrackOnlyOwnedBreakpointsAndBoundJobCleanup() { Assert.Contains("pcall(resourceRecord, a[1], a[2], 'breakpoint'", DebuggerLuaScripts.SetBreakpoint, StringComparison.Ordinal); + Assert.Contains("getOpenedProcessID() == breakpointProcessId", DebuggerLuaScripts.SetBreakpoint, + StringComparison.Ordinal); Assert.Contains("resourceRelease(matched, 'deleted')", DebuggerLuaScripts.DeleteBreakpoint, StringComparison.Ordinal); Assert.Contains("job.onStop", DebuggerLuaScripts.StartCapture, StringComparison.Ordinal); - Assert.Contains("debug_removeBreakpointByID(current.breakpointId)", DebuggerLuaScripts.StartCapture, + Assert.Contains("debug_removeBreakpointByID(breakpointHandle)", DebuggerLuaScripts.StartCapture, + StringComparison.Ordinal); + Assert.DoesNotContain("assertBreakpointAddressCount", DebuggerLuaScripts.StartCapture, StringComparison.Ordinal); Assert.Contains("_G.debugger_onBreakpoint = nil", DebuggerLuaScripts.StartTrace, StringComparison.Ordinal); diff --git a/tests/CheatEngine.Mcp.Tests/Tools/Mono/MonoToolsTests.cs b/tests/CheatEngine.Mcp.Tests/Tools/Mono/MonoToolsTests.cs index 2a20eaf..cb85346 100644 --- a/tests/CheatEngine.Mcp.Tests/Tools/Mono/MonoToolsTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Tools/Mono/MonoToolsTests.cs @@ -385,6 +385,21 @@ public void AttachThenDetach_ReleasesTheActivationOwnedCollectorResource() Assert.Equal((2, 0), (harness.Dispatches, harness.Resources.Count)); } + [Fact] + public void Attach_TracksOwnershipBeforeDispatchCompletes() + { + StateTestHarness harness = new(); + harness.Answer(static _ => new MonoAttachResult(true, false, 7, + ["collector_injected"])); + harness.AfterDispatch = harness.Resources.StopAcceptingTracks; + + MonoAttachResult result = Tools(harness).Attach(Token); + + Assert.True(result.Attached); + Assert.Equal("mono", Assert.Single(harness.Resources.List()).Kind); + Assert.Equal(1, harness.Dispatches); + } + [Fact] public void Detach_AttachmentThatEndedOutsideMcp_ReleasesOnlyTheResourceAndReportsIt() { From 0e452a4d87bca9aafbe7d0ebcef8634326b842c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=96=A4=20ShadowNine=20=F0=9F=92=9B?= Date: Sun, 25 Oct 2026 16:08:09 +0100 Subject: [PATCH 2/2] Fix shutdown review findings and reconcile source dependencies --- Directory.Build.targets | 8 +- THIRD-PARTY-NOTICES.md | 8 +- docs/qualification/all-tools/README.md | 23 +++++- externals/CheatEngine.Client | 2 +- externals/CheatEngine.SDK | 2 +- .../Lua/PluginLuaToolRuntime.cs | 3 +- .../Contract/SourceBuildProfileTests.cs | 78 +++++++++++++++++++ .../NativeLua/NativeLuaExecuteTests.cs | 14 +++- .../Investigation/InvestigationToolsTests.cs | 12 ++- 9 files changed, 132 insertions(+), 18 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index 16a798a..5e715e9 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -3,8 +3,12 @@ DependsOnTargets="IncludeTransitiveProjectReferences"> + Condition="$([System.String]::Copy('%(ProjectReference.FullPath)').Contains('/externals/')) or $([System.String]::Copy('%(ProjectReference.FullPath)').Contains('\externals\'))"> + + Platform=AnyCPU + Configuration=$(Configuration) + %(ProjectReference.AdditionalProperties);ShouldUnsetParentConfigurationAndPlatform=false + (the recorded submodule commit) | -| CheatEngine.SDK | (the recorded submodule commit) | +| CheatEngine.Client | (the recorded submodule commit) | +| CheatEngine.SDK | (the recorded submodule commit) | | Microsoft.Extensions.AI.Abstractions 10.10.1 | (the source commit of the package) | | ModelContextProtocol 2.2.0 | and (the source commit of the packages) | | .NET runtime packs 10.0.13 | `LICENSE.TXT` and `THIRD-PARTY-NOTICES.TXT` of the runtime packs | diff --git a/docs/qualification/all-tools/README.md b/docs/qualification/all-tools/README.md index 83d3128..de2f65d 100644 --- a/docs/qualification/all-tools/README.md +++ b/docs/qualification/all-tools/README.md @@ -39,10 +39,25 @@ work queue. Do not repeat unchanged diagnostic runs or mark an environment block ## Reproduce the evidence report -Use [the test guide](../../../tests/CheatEngine.Mcp.Tests/README.md) to create fresh portable -and NativeLua TRX reports. Then supply their actual paths to -[`eng/Write-AllToolQualificationReport.ps1`](../../../eng/Write-AllToolQualificationReport.ps1), -using `-GatewayTrx`, `-PortableTrx`, `-NativeLuaTrx` and a new `-OutputDirectory`. +After a locked restore and Release build, create a portable TRX from the repository root: + +```powershell +dotnet test --project tests/CheatEngine.Mcp.Tests -c Release --no-restore --no-build --filter-not-trait Category=LiveQualification --filter-not-trait Category=NativeLua --fail-skips on --report-trx --report-trx-filename portable.trx --results-directory artifacts/all-tools/portable +``` + +The output is `artifacts/all-tools/portable/portable.trx`. This suite includes gateway-routing +tests, so supply that same file to **both** `-GatewayTrx` and `-PortableTrx` when calling +[`eng/Write-AllToolQualificationReport.ps1`](../../../eng/Write-AllToolQualificationReport.ps1): + +```powershell +$portableTrx = './artifacts/all-tools/portable/portable.trx' +./eng/Write-AllToolQualificationReport.ps1 -GatewayTrx $portableTrx -PortableTrx $portableTrx -OutputDirectory './artifacts/all-tools/portable-report' +``` + +Use [the test guide](../../../tests/CheatEngine.Mcp.Tests/README.md) to create a separate +NativeLua TRX and pass its actual path with `-NativeLuaTrx` when that suite has been run. +Use a new output directory for each evidence report; the portable-only example does not +establish NativeLua or native-host qualification. For native qualification, pass the exact candidate's session summaries with `-LiveSummaries` as a PowerShell array. Do not combine different plugin/gateway hashes or change a frozen candidate during a live session. diff --git a/externals/CheatEngine.Client b/externals/CheatEngine.Client index 101b13e..bc60c2d 160000 --- a/externals/CheatEngine.Client +++ b/externals/CheatEngine.Client @@ -1 +1 @@ -Subproject commit 101b13e2caa8bfdfd5cec6ae2f96b9e32dfe5691 +Subproject commit bc60c2dfc906b22bb0d2185ca3775142377d03b2 diff --git a/externals/CheatEngine.SDK b/externals/CheatEngine.SDK index 1e45f32..524979c 160000 --- a/externals/CheatEngine.SDK +++ b/externals/CheatEngine.SDK @@ -1 +1 @@ -Subproject commit 1e45f32edfba80e4a7ef8f62f404e617067cbfed +Subproject commit 524979c2d5f9de984afd95a4dbebebc92f2ae894 diff --git a/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs b/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs index ddc5aff..73dc40a 100644 --- a/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs +++ b/srcs/CheatEngine.Mcp.Plugin/Lua/PluginLuaToolRuntime.cs @@ -63,7 +63,8 @@ internal static bool ShowPluginStatus(ICheatEngineClient client, string state, s internal static bool IsLifecycleCleanupOperation(string operation) { - return operation is "mcp_job_stop" or "mcp_state_release_resource" or "mcp_state_release"; + return operation is "mcp_job_stop" or "mcp_state_release_resource" or "mcp_state_release" + or CheatEngineToolNames.MonoDetach; } internal static LuaJsonResult ExecuteLifecycleCleanup(string operation, string source, diff --git a/tests/CheatEngine.Mcp.Tests/Contract/SourceBuildProfileTests.cs b/tests/CheatEngine.Mcp.Tests/Contract/SourceBuildProfileTests.cs index 7c75d0f..949ccf2 100644 --- a/tests/CheatEngine.Mcp.Tests/Contract/SourceBuildProfileTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Contract/SourceBuildProfileTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.Text.Json.Nodes; using System.Xml.Linq; using CheatEngine.Mcp.Tests.Support; @@ -8,6 +9,83 @@ namespace CheatEngine.Mcp.Tests.Contract; /// Exercises source dependency boundaries without restoring or compiling fixture projects. public sealed class SourceBuildProfileTests { + [Theory] + [InlineData("Debug")] + [InlineData("Release")] + public async Task SourceReferencesUseOnePlatformAndConfigurationForDirectAndTransitiveDependencies(string configuration) + { + string root = Directory.CreateTempSubdirectory("CheatEngine.Mcp.SourceConfiguration-").FullName; + try + { + string project = Path.Combine(root, "Configuration.proj"); + XDocument fixture = new(new XElement("Project", + new XElement("PropertyGroup", new XElement("Configuration", configuration)), + new XElement("ItemGroup", + new XElement("ProjectReference", new XAttribute("Include", "externals/CheatEngine.SDK/Engine.csproj"), + new XElement("AdditionalProperties", "Existing=preserved")), + new XElement("ProjectReference", new XAttribute("Include", "Unrelated.csproj"))), + new XElement("Import", new XAttribute("Project", Path.Combine(RepositoryPaths.Root, "Directory.Build.targets"))), + new XElement("Target", new XAttribute("Name", "IncludeTransitiveProjectReferences"), + new XElement("ItemGroup", + new XElement("ProjectReference", new XAttribute("Include", "externals/CheatEngine.Client/Core.csproj"), + new XElement("AdditionalProperties", "Existing=preserved")))), + new XElement("Target", new XAttribute("Name", "AssignProjectConfiguration")))); + fixture.Save(project); + ProcessStartInfo start = new("dotnet") + { + WorkingDirectory = RepositoryPaths.Root, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + string[] arguments = ["msbuild", project, "-t:AssignProjectConfiguration", "-getItem:ProjectReference", + "-nologo", "-verbosity:quiet", "-nodeReuse:false"]; + foreach (string argument in arguments) + { + start.ArgumentList.Add(argument); + } + + using CancellationTokenSource deadline = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken); + deadline.CancelAfter(TimeSpan.FromSeconds(30)); + using Process process = Process.Start(start)!; + Task stdout = process.StandardOutput.ReadToEndAsync(deadline.Token); + Task stderr = process.StandardError.ReadToEndAsync(deadline.Token); + try + { + await process.WaitForExitAsync(deadline.Token); + string output = await stdout; + Assert.True(process.ExitCode == 0, output + await stderr); + JsonArray references = JsonNode.Parse(output)!["Items"]!["ProjectReference"]!.AsArray(); + Assert.Equal(3, references.Count); + foreach (int index in new[] { 0, 2 }) + { + JsonNode reference = references[index]!; + Assert.Equal("Platform=AnyCPU", reference["SetPlatform"]!.GetValue()); + Assert.Equal($"Configuration={configuration}", reference["SetConfiguration"]!.GetValue()); + Assert.Equal("Existing=preserved;ShouldUnsetParentConfigurationAndPlatform=false", + reference["AdditionalProperties"]!.GetValue()); + } + + Assert.Null(references[1]!["SetPlatform"]); + Assert.Null(references[1]!["SetConfiguration"]); + Assert.Null(references[1]!["AdditionalProperties"]); + } + finally + { + if (!process.HasExited) + { + process.Kill(true); + await process.WaitForExitAsync(CancellationToken.None); + } + } + } + finally + { + await TestDirectory.DeleteAsync(root); + } + } + [Theory] [InlineData(true, false, "CEMCP003")] [InlineData(false, true, null)] diff --git a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs index 170443c..c540e30 100644 --- a/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs +++ b/tests/CheatEngine.Mcp.Tests/NativeLua/NativeLuaExecuteTests.cs @@ -34,8 +34,12 @@ public void Execute_Prelude_FormatsHexBytesAndNumbersAndExposesTheBudget() "NaN", "Infinity", "-Infinity", 0.5, 7, LuaToolRuntime.DefaultBudgetMilliseconds, false), probe); } - [Fact] - public void FixedLuaExecutor_StoppingCleanupUsesTheAttachedRuntimeOnlyForReleaseScripts() + [Theory] + [InlineData("mcp_job_stop")] + [InlineData("mcp_state_release_resource")] + [InlineData("mcp_state_release")] + [InlineData(CheatEngineToolNames.MonoDetach)] + public void FixedLuaExecutor_StoppingCleanupUsesTheAttachedRuntimeOnlyForReleaseScripts(string operation) { using RuntimeScope scope = CreateScope(); using CancellationTokenSource stopping = new(); @@ -52,7 +56,7 @@ public void FixedLuaExecutor_StoppingCleanupUsesTheAttachedRuntimeOnlyForRelease PluginFixedLuaExecutor executor = new(client, lifecycleLuaScope); using (lifecycleLuaScope.BeginResourceCleanup(client)) { - LuaJsonResult result = executor.Execute("mcp_job_stop", + LuaJsonResult result = executor.Execute(operation, LuaToolRuntime.BuildSource("return {'released'}", []), TestJsonContext.Default.StringArray, new LuaJsonBufferPool(), LuaOpaqueValueHandling.Reject, CancellationToken.None); @@ -62,6 +66,10 @@ public void FixedLuaExecutor_StoppingCleanupUsesTheAttachedRuntimeOnlyForRelease LuaToolRuntime.BuildSource("return {'blocked'}", []), TestJsonContext.Default.StringArray, new LuaJsonBufferPool(), LuaOpaqueValueHandling.Reject, CancellationToken.None)); } + + Assert.Throws(() => executor.Execute(operation, + LuaToolRuntime.BuildSource("return {'outside cleanup'}", []), TestJsonContext.Default.StringArray, + new LuaJsonBufferPool(), LuaOpaqueValueHandling.Reject, CancellationToken.None)); } [Fact] diff --git a/tests/CheatEngine.Mcp.Tests/Tools/Investigation/InvestigationToolsTests.cs b/tests/CheatEngine.Mcp.Tests/Tools/Investigation/InvestigationToolsTests.cs index 9274370..81260b3 100644 --- a/tests/CheatEngine.Mcp.Tests/Tools/Investigation/InvestigationToolsTests.cs +++ b/tests/CheatEngine.Mcp.Tests/Tools/Investigation/InvestigationToolsTests.cs @@ -176,8 +176,16 @@ public async Task Create_ConcurrentNamesAtRevisionLimit_OnlyOneCanPublish() Task second = Task.Run(() => TryCreate("second"), Token); object[] results = await Task.WhenAll(first, second); - Assert.Single(results.OfType()); - Assert.Equal(ToolErrorKind.LimitExceeded, Assert.Single(results.OfType()).Error.Kind); + InvestigationNote winner = Assert.Single(results.OfType()); + CheatEngineToolException failure = Assert.Single(results.OfType()); + if (failure.Error.Kind == ToolErrorKind.InvalidState) + { + // Loading 4095 revisions can outlast the bounded directory lease wait on a busy runner. + Assert.Equal("The note directory is busy; retry after the current operation finishes.", failure.Error.Message); + string losingName = winner.Name == "first" ? "second" : "first"; + failure = Assert.IsType(TryCreate(losingName)); + } + Assert.Equal(ToolErrorKind.LimitExceeded, failure.Error.Kind); Assert.Equal(InvestigationTools.MaximumFiles, Directory.GetFiles(_root, "cemcp-note-*.json").Length); }