Remediate the 2026-09-22 audit and overhaul CI/CD - #86
Conversation
Promote the migration style rules that dotnet format can apply safely (IDE0008, IDE0011, IDE0040, IDE0044, IDE0055, IDE0065, IDE0090, IDE0161) from suggestion to warning, as the .editorconfig comment planned for a dedicated reformat pass, and apply dotnet format to the whole solution. EnforceCodeStyleInBuild and TreatWarningsAsErrors now keep the tree formatted; IDE0005 and IDE1006 keep their previous severities. No behavior change.
Add CheatEngine.SDK.Repository.Tests, a fast project that only reads committed files, with its first contract: every project on disk is in the solution unless an explicit, reasoned exclusion says otherwise. Later remediation lots add their documentation, workflow, qualification and catalogue contracts there instead of reviving script-only gates. Pre-register the package versions the parallel remediation lots need (PublicApiAnalyzers, Microsoft.Sbom.Targets, MTP HangDump and CrashDump, YamlDotNet) so they do not all edit Directory.Packages.props, make the Engine.Tests FakeHost partial so lots can extend it in separate files, and record the formatting commit in .git-blame-ignore-revs.
dotnet format left LF-only files untouched while the IDE0055 build check expects the platform newline, so a file written with LF endings failed the build without being fixable by the formatter. Declare end_of_line = crlf to match .gitattributes, keeping the two LF-pinned native bridge inputs on LF.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 Walkthrough📝 WalkthroughMerge Risk: 🟡 Moderate · up to This work-in-progress branch reworks resource ownership, allocation, Auto Assembler, and symbol cleanup reporting. Two outcome-reporting gaps remain. A custom allocator failure can be reported as "never ran" even though it may already have freed memory. After a detach, a patch release reports an engine failure instead of the documented lifecycle error. Earlier concerns also remain open: CI no longer checks native bridge reproducibility, and several documents and release notes are inaccurate. Resolve these, or explicitly accept them, before merging. 🚥 Pre-merge checks | ✅ 6 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (6 passed)
Full details: Public Api Documentation And ChangelogExplanation The PR changes public API and behavior, but the Unreleased changelog does not cover the changes. The diff adds the public Resolution Add complete Comment |
AotProbe and LiveProbe are restored and built on their own, so the solution-wide formatting pass skipped them and the CI aot job now failed on IDE0055 and IDE0011 once those rules became build errors. Apply dotnet format whitespace and style to both projects. LiveProbe still does not compile for an unrelated, pre-existing reason; the qualification work brings it into the solution and fixes it.
Validate-CeSurfaceCatalog.py and Validate-EngineeringManifest.py read inputs under documentations/ (the surface catalogue JSON, backlog.json, ARCHIVE_RECONCILIATION.md and the work-item pages). Commit 4020a32 deleted that tree and the CI job that ran them, so the scripts and their unit tests in eng/tests/ can no longer run and nothing references them. Repository rules are enforced by C# tests instead of scripts (audit F14, .coderabbit.yaml "no script-only gates"): the documentation integrity tests replace the link checks, the Lua surface catalogue returns with its own C# tests, and the protected-operation catalogue check moves to C# with the ABI work. The engineering backlog is retired. With no Python file left in the tree, the __pycache__/ and *.py[cod] ignore patterns go too.
Commit 4020a32 removed the documentations/ tree, and the maintainer decided not to restore it (audit F14, ADR-12, SRCREG-06). The pages that still need a destination get one under docs/, rebuilt from the audit rather than restored, and every page carries the header "Recreated 2026-09 from the audit, not the historical documentations/ tree". docs/README.md is the index: it names the audit only by the SHA-256 of its MANIFESTE.md, lists the rebuilt areas, summarises the C0-C4 evidence levels, and records the retired paths (as code spans, never links) with their replacement, so statements that relied on them stay "declared, not recovered" (DeclaredRepo) instead of silently disappearing. It also maps the unresolvable architecture-review scenario ids (R25/T049-T050, R26/T051-T052, R34/T067-T068/T076) to the closest audit scenarios (Q09/Q10, Q19, Q30). Qualification, ABI/NativeAOT and catalogue content arrives with later work, so those pages are placeholders that name their owning work and wave. They exist now so that re-pointed links resolve immediately. The NativeAOT placeholder already states the restriction Microsoft documents for Native AOT libraries: unloading them with FreeLibrary is not supported (https://learn.microsoft.com/dotnet/core/deploying/native-aot/libraries). The solution lists the new pages under /docs/, /docs/abi/ and /docs/catalog/; the qualification pages are listed by the qualification work together with its own files.
Commit 4020a32 removed documentations/**, and 33 relative links (plus one code-span path to native/cheatengine-sdk-lua-bridge/AUDIT.md) were left pointing at it (audit F14, ADR-12, A09-15, A09-16, A21-22). They are re-pointed, not restored: - ROADMAP: the 24 SDK-0xx work-item links become plain identifiers (titles kept, tables re-padded), and a pointer to the retired- documentation table replaces the sentence that sent readers to live issues: no issue or Project tracks these work items. - The capability-matrix links split into the concerns the audit separates: per-surface provenance and semantics go to the Lua surface catalogue (docs/catalog), executed host evidence to the qualification matrix (docs/qualification, value scans = Q25/Q26). - The source index link goes to the support profile, which records the celua.txt digest; the live-probe templates to the local qualification protocol; ADR-006 to the NativeAOT profile restrictions (docs/abi); SDK-005 to the Q09/Q10 coexistence scenarios; AUDIT.md to the bridge README. - The advanced-domain and structure-family pointers go to the deferred families of the catalogue, without claiming it records them already. Targets that later work fills in are placeholder pages, so every link resolves now. Where a wrap had split "and" / "the" onto its own line in the address-list example, the paragraph is re-flowed.
Several READMEs stated things that no committed file, test or workflow supports (audit A00-03, AR-01, ADR-12e, AX07-15, SRC03-06). Reword them to what is true today, without linking to anything that does not exist: - exemples/api: CESDK9101 does not only stop x86. The packaged target (unchanged since 1.0.0) accepts an unset PlatformTarget, AnyCPU or x64 and rejects every other explicit value, as PlatformTargetTests proves for x86, ARM, ARM64, Itanium and an unknown value. - LiveProbe: "the only automated validation of this project is compilation" is false; no workflow builds it (it is outside the solution). The qualification work states its compile check once the project joins the solution. - Hosting and Coexistence: the architecture-review scenario ids R25/T049-T050, R26/T051-T052 and R34/T067-T068/T076 only resolved in a retired archive page. They become the audit scenarios Q09/Q10, Q19 and Q30, still "not executed"; docs/README.md records the mapping. - Abi: the source index that recorded the installed-file hashes was retired. Those hashes stay declarations (DeclaredRepo) until the support profile re-measures them, instead of reading as verified. A sweep of every tracked Markdown file for "100 %", "complete/full coverage", "fully supported", "all public Lua", "every CE version or profile", "7.5.1" and "exact CE 7.7 source" finds nothing to neutralize.
The dead documentations/ links rotted because the checks that caught them were Python scripts that CI stopped running. Repository rules live in C# tests instead (.coderabbit.yaml, "no script-only gates"), and this one is blocking and offline: it runs in both build-test legs through dotnet test --solution, reads files only, never starts a process and never reaches the network (audit F14, ADR-12, A21-32, A23-F14-4). DocumentationIntegrityTests applies one rule per test to every Markdown file of the working tree: - relative links, images, reference definitions and HTML href/src resolve with exact case (Windows, where CI runs, is case-insensitive, so every segment is matched against the directory entries), never above the root or into build output; - #fragments match a heading (GitHub slug rules, duplicate suffixes) or an explicit <a id|name> anchor of the target page; - no absolute local path (drive, file: URI, user profile folder); - no link into the retired documentations/ tree, and only the docs index names retired pages; - blob/main and tree/main links to this repository resolve, and so do the frozen links of the README published in 1.0.0 (ADR-12); - packed READMEs use absolute https:// links only, since nuget.org cannot resolve relative ones (https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges); - docs/ pages carry the "Recreated 2026-09" header, placeholders name their lot and wave, and the docs index links every top-level entry; - no "100 %", "complete/full coverage", "fully supported" or "fully compatible" claim unless the line negates it (A00-03, A20-14). MarkdownDocument is a dependency-free CommonMark subset parser (adding Markdig would touch central package versions and lock files): fenced code, code spans, HTML comments and backslash escapes are opaque, link text may span lines (a line-by-line parser missed the LiveProbe link), and every link maps back to the line of its destination. Repository- specific values live in DocumentationConventions so the Client can mirror the design. MarkdownDocumentTests exercises the parser and every rule on in-memory pages, which proves each gate fails on its regression.
The repository is about to commit NuGet lock files. The SDK's implicit packages (Microsoft.NET.ILLink.Tasks, the ILCompiler packages) are recorded in those locks and move with the SDK version, so a roll-forward to another feature band would break every locked restore. global.json therefore requires 10.0.401 exactly (rollForward: disable) and names the install command in sdk.errorMessage, available since the .NET 10 SDK: https://learn.microsoft.com/dotnet/core/tools/global-json#rollforward https://learn.microsoft.com/dotnet/core/tools/global-json#errormessage AnalysisLevel moves from latest-recommended to the explicit pin 10.0-recommended (private property _CheatEngineSdkPinnedAnalysisLevel). On SDK 10.0.401 'latest' resolves to 10.0, so the selected NetAnalyzers global configs are identical before and after (checked with -getItem:EditorConfigFiles on a library, a Roslyn component and a test project) and the Debug build stays at zero warnings. A newer SDK band can no longer add CA/IDE errors without a reviewed commit. CESDK9004 (Directory.Build.targets, BeforeBuild) fails any project whose evaluated AnalysisLevel differs from the pin, which covers project-level and command-line overrides. ToolchainPinTests (Repository.Tests/Toolchain) keeps the committed values honest: exact SDK version with rollForward disable and no prerelease, an errorMessage naming the pinned version and install command, a release-shaped analysis level that moves with the SDK major/minor, and no other MSBuild file setting AnalysisLevel.
The previous policy relied on TreatWarningsAsErrors and did not say what happens to low and moderate advisories. It now follows the documented "dedicated audit pipeline" pattern: https://learn.microsoft.com/nuget/concepts/auditing-packages#running-nuget-audit-in-ci - Ordinary builds: NuGetAudit true, mode all, level low. NU1903 (high) and NU1904 (critical) are appended to WarningsAsErrors, so they fail restore even in a project that turns TreatWarningsAsErrors off. NU1901/NU1902 (low/moderate) and NU1900/NU1905 (audit source trouble) stay visible warnings, so an advisory published overnight does not turn every required check red without a commit. - restore -p:AuditPipeline=true (the scheduled strict audit): every NU1900-NU1905 code is an error. CESDK9009 guards the policy in three places: - BeforeBuild in every project: NuGetAudit/NuGetAuditMode/NuGetAuditLevel weakened, NU1903/NU1904 (every audit code in AuditPipeline mode) listed in NoWarn or WarningsNotAsErrors, or missing from WarningsAsErrors. Lists are compared as items split on ';', ',' and whitespace, so NU19031 never matches and case does not matter. - Before CollectNuGetAuditSuppressions (inside restore): a NuGetAuditSuppress item must be declared in Directory.Build.props with Justification and Expires (yyyy-MM-dd) metadata; the AuditPipeline run fails once Expires is past, ordinary builds never fail on the calendar. - Before GenerateNuspec of a packable project: a stable (non-prerelease) version cannot be packed while any suppression exists, so the release path never suppresses. Directory.Solution.targets asserts, for CI solution restores, that RestoreProjectsAuditedCount + RestoreSkippedCount equals RestoreProjectCount (documented "ensure restore audited projects" check). It is imported for CheatEngine.SDK.slnx: a forced restore with CI=true logs 35 audited of 35, a no-op restore 35 up to date, and -p:NuGetAudit=false fails with CESDK9009. ToolchainPinTests gains text checks for the policy, the solution-level assertion and the suppression rules.
Every public API change must now be explicit before the 2.0 domain work changes the surface. Microsoft.CodeAnalysis.PublicApiAnalyzers (5.6.0, already pinned in Directory.Packages.props) is referenced by the six libs/ projects through eng/Shipping.props; src/CheatEngine.SDK declares no type and is not tracked. RS0016/RS0017 keep their default warning severity, which TreatWarningsAsErrors turns into build errors. PublicAPI.Shipped.txt is the surface of the published CheatEngine.SDK 1.0.0 package, not of the tagged sources alone. It was generated in a temporary detached worktree at v1.0.0 (a6fefb9) with the same SDK 10.0.401 and Roslyn 5.9.0: dotnet format analyzers --diagnostics RS0016 wrote the hand-written surface, and the five EngineApi-generated declarations of CheatEngine.SDK.Engine.Generated.MemoryScalars were taken from the RS0016 messages, because the fixer cannot edit generated documents although the analyzer does track them. That worktree then built RS0016/RS0017-clean in Release, and its seven assemblies were dumped with System.Reflection.Metadata and diffed against the dump of lib/net10.0 of the published nupkg (lock contentHash n7nHqZ8v...gA==): 155 types and 1375 members on both sides, 0 removed, 0 added, 0 changed, and every 1.0.0 type appears in the Shipped files. PublicAPI.Unshipped.txt holds the delta to HEAD: 668 additions from the same fixer, and the nine *REMOVED* lines RS0017 reported, which are exactly the known breaks: the AddressResolutionOptions constructor, UseHostSymbolTable accessors and Deconstruct; the typed function-pointer Callback fields of AddressListPluginInit and DisassemblerContextPluginInit (now void*); and the renumbered MemoryAccessFailure members DestinationTooSmall, WriteFailed and InvalidResult. No RS0026/RS0027 fired. Files are ordinally sorted after the #nullable enable header so they merge by union and sort. CESDK9003 (Directory.Build.targets) fails a libs/ project without both files, because a missing file silently disables tracking. PublicApiFileTests (Repository.Tests/PublicApi) check that every library has both files and nothing else does, the header, ordinal order without duplicates or blank lines, no *REMOVED* marker in Shipped, every removed line repeating a Shipped line exactly, and no Unshipped line redeclaring a live Shipped one.
main already breaks the published 1.0.0 API, and the maintainer decided that the next release is 2.0.0 with every break declared. Version line: MinVerMinimumMajorMinor goes from 1.0 to 2.0, so untagged commits pack as 2.0.0-alpha.0.N. MinVer derives AssemblyVersion from the major (2.0.0.0), and the GeneratedCode attribute in consumers' generated files changes accordingly. Baseline: src/CheatEngine.SDK sets PackageValidationBaselineVersion 1.0.0 next to EnablePackageValidation, so every pack compares lib/net10.0 with the published package (downloaded at restore as a PackageDownload): https://learn.microsoft.com/dotnet/fundamentals/apicompat/package-validation/baseline-version-validator CompatibilitySuppressions.xml was generated once with -p:ApiCompatGenerateSuppressionFile=true and reviewed: nine baseline suppressions, all Left == Right. CP0002 for the AddressResolutionOptions constructor, UseHostSymbolTable accessors and Deconstruct; CP0011 for MemoryAccessFailure.DestinationTooSmall, WriteFailed and InvalidResult; and, contrary to the expectation that ApiCompat has no rule for a field type change, CP0002 on the F: DocIds of AddressListPluginInit.Callback and DisassemblerContextPluginInit.Callback (typed function pointer to void*). No CP0003 (assembly version 1.0.0.0 to 2.0.0.0) is reported. Guards (Directory.Build.targets, before GenerateNuspec, packable only): - CESDK9006: package validation off, no baseline or strict mode; in a CI build (ContinuousIntegrationBuild), any of ApiCompatGenerateSuppression- File, GenerateCompatibilitySuppressionFile, ApiCompatPermitUnnecessary- Suppressions, DisablePackageBaselineValidation, RunApiCompat=false or a custom PackageValidationBaselinePath. Any CPxxxx or PKVxxx code in NoWarn (ApiCompat honors NoWarn) is refused as well. - CESDK9007 (after MinVer): a suppression file with baseline suppressions requires a package major above the baseline's, so the old -p:MinVerVersionOverride=1.0.0 rehearsal fails. Verified: a CI-like pack produces exactly one CheatEngine.SDK.2.0.0-alpha.0.51.nupkg with ApiCompat green; removing a CP0011 or CP0002 entry fails the pack; a stale entry fails it ("Unnecessary suppressions found"); MinVerVersionOverride=1.1.0 fails with CESDK9007; CI + ApiCompatGenerateSuppressionFile and strict mode fail with CESDK9006; a 2.0.0 override packs. eng/api/ records the rest of the contract: - apicompat-invisible-changes.txt: *REMOVED* lines ApiCompat cannot see (none today), plus the attribute-only changes invisible to both tools. - client-consumed-sdk-types.txt: the Client's SDK-type allowlist and the enums it translates, copied from Client 881c14c with provenance. - client-induced-breaks.txt: the suppressions touching those types (the AddressResolutionOptions and MemoryAccessFailure entries), for the Client's docs/migration/sdk-2.0.md. - README.md: public API tracking, the baseline and its regeneration command (integrator only), the Client flag, the enum policy, the 1.0.0 source-versus-package identities, the toolchain pin and the NuGet audit policy, and the guard table. Tests (Repository.Tests/PublicApi): - CompatibilitySuppressionTests (trait Qualification=Q48, SDK-side C0 evidence only): suppressions are baseline, same-assembly CP0001/CP0002/ CP0011 entries; every suppression names a *REMOVED* line and every *REMOVED* line is suppressed or declared invisible (matched by type and member name); the induced-break list is exactly derived; every consumed type resolves or is marked unresolved. - EnumContractTests: the nine CE-constant enums, CheatEngineArchitecture and TargetAbi keep their 1.0.0 members; the 23 enums added since 1.0.0 are classified; status/outcome enums start with a neutral zero member (Unknown; Unspecified, Uninitialized, NotAttempted tolerated) except eight pending ones owned by S-SCAN, S-RT, S-RES and S-GEN-A, a list the ratchet test only lets shrink.
The release must be able to attest what the package contains, and a consumer must be able to trace the package back to its commit. SBOM: src/CheatEngine.SDK references Microsoft.Sbom.Targets 4.1.13 (already pinned; a development dependency, PrivateAssets="all", so the nuspec keeps zero dependencies) and sets GenerateSBOM unconditionally, so local, fixture and CI packs carry the same inventory at _manifest/spdx_2.2/manifest.spdx.json (SPDX 2.2, the tool default) with its .sha256 sidecar. Supplier CheatEngineNet, namespace under the repository URL, license fetching off, tool verbosity Warning. The build-component scan stays on the project folder, which holds no package manifest: the SBOM describes the package and each of its 27 other entries with SHA-256/SHA-1, and lists no build-only tool as a shipped component. The tool's "no packages detected" line is expected (no NuGet dependency) and is console output, not an MSBuild warning. Microsoft.Sbom.Targets reads $(Version) at evaluation for the SBOM package version, before MinVer sets it, which produced "1.0.0"; CheatEngineSdkAlignSbomPackageVersion re-points it to $(PackageVersion) after MinVer. CESDK9008 fails a pack without GenerateSBOM or without the package reference. The nupkg is not byte-reproducible (random document namespace part, creation time, re-zip); eng/api/README.md states that reproducibility is promised for the hashed assemblies and the bridge. Repository metadata: PublishRepositoryUrl=true, recommended with the SDK-integrated Source Link (https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink), keeps <repository type url commit> in the nuspec as in 1.0.0. PackagedUmbrellaFixture gains two read-only accessors, PackagePath and Nuspec, assigned in ReadPackedNupkg (additive, for S-CI-REL's rework). SupplyChainPackageTests (collection PackagedUmbrellaSuite, trait Category=Packaging) checks the SBOM identity and sidecar, the SHA-256 of every shipped assembly and of the bridge, SBOM inventory == package entries, no repository contract file packed, the MinVer line, the <major>.0.0.0 assembly versions, the nuspec repository commit and Source Link to raw.githubusercontent.com at that commit. The whole tests/CheatEngine.SDK.Tests project passes locally (74 tests, 1 min 24 s). Pack cost added by this lot: SBOM about 4.7 s, package validation about 0.4 s; a fixture-equivalent pack takes 11 s, far below the 3-minute PackTimeout, which stays unchanged.
Version and content must be locked for every project (audit ch.21 exit criteria), including the projects CI builds outside the solution, and the lock files must only ever be written by one reproducible procedure. Directory.Build.props sets RestorePackagesWithLockFile=true for every project. RestoreLockedMode is deliberately not set in MSBuild: locked mode is passed by the CI entry points, and a global locked mode would make the --force-evaluate restores of the lock check fail with NU1005: https://learn.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies https://learn.microsoft.com/nuget/reference/errors-and-warnings/nu1005 CESDK9005 (BeforeBuild, every project) fails a project that builds with RestorePackagesWithLockFile or ManagePackageVersionsCentrally other than true. With a lock file present, restore reports NU1005 first; with --no-restore the guard itself fires (both verified). eng/Update-LockFiles.ps1 [-Verify] (pwsh 7, strict mode, every dotnet and git exit code checked, comment-based help; PSScriptAnalyzer 1.25.0 reports no finding at any severity): - refuses a non-Windows host (AOT win-x64 sections record host-specific ILCompiler packages) and any SDK other than global.json's, printing the install command; - enumerates projects from git (tracked plus untracked non-ignored *.csproj) and the slnx <Project> entries, with an empty but explicit exclusion list; - restores CheatEngine.SDK.slnx, then each out-of-solution project, with --force-evaluate; gives an unchanged lock its committed bytes back and keeps the committed final-newline state of a changed one; verifies with --locked-mode; - checks the structure from evaluated properties (dotnet msbuild -getProperty): a lock per project, version 2 under CPM, no CentralTransitive in a version 1 lock, a <tfm>/<rid> section per runtime identifier with runtime.<rid>.Microsoft.DotNet.ILCompiler when PublishAot is true, and no CheatEngine.* package from a feed; - default mode lists the changed locks; -Verify fails, naming the files and the command, on a diff or an untracked lock. A first run generated 37 lock files in 56 s with every check green; they are committed separately. eng/api/README.md documents the procedure, the Dependabot workflow and the guard.
First introduction of the lock files, generated by ./eng/Update-LockFiles.ps1 on Windows with .NET SDK 10.0.401 and nothing else: 37 packages.lock.json, one per tracked project, including the two projects outside the solution (tests/CheatEngine.SDK.AotProbe and tests/CheatEngine.SDK.LiveProbe). All are format version 2 (Central Package Management everywhere); CentralTransitive entries appear only under tests/, where eng/Tests.props enables transitive pinning. The three runtime-specific projects (AotProbe, NativeAotLibraryProbe, NativeAotLoaderHarness) carry a net10.0/win-x64 section, and the two Native AOT ones lock runtime.win-x64.Microsoft.DotNet.ILCompiler, so 'dotnet publish --no-restore' keeps working. No lock resolves a CheatEngine.* package: the ApiCompat 1.0.0 baseline is a PackageDownload. The files keep NuGet's format without a final newline. From now on the integrator is the only writer of these files and regenerates them with the script (never by hand, never on a merge). LockFileTests (Repository.Tests/LockFiles) mirror the script's structural checks offline: a lock per project, supported version and version 2 under CPM, no CentralTransitive in a version 1 lock, the RID section and ILCompiler package of Native AOT projects, no CheatEngine.* package, and NuGet's no-final-newline ending.
Right after the --force-evaluate regeneration every project is up to date, so a plain `dotnet restore --locked-mode` takes NuGet's no-op path and never compares the lock files with the project inputs. Observed: with a hand-edited lock (resolved or requested version changed), a no-op locked restore still exits 0, while `--locked-mode --force` fails with NU1403 (content hash) or NU1004 (inconsistent lock). The script now passes --force to its verification restores; --force is compatible with locked mode (only --force-evaluate is not, NU1005). -Verify still passes on the committed tree (32 s, no diff), and a committed lock edit makes -Verify fail naming the file. PSScriptAnalyzer 1.25.0: no finding.
SbomGenerationVerbosity=Warning was meant to keep the SBOM tool's warnings while dropping its information output. Measured on a pack, the tool prints the same component-detection and telemetry lines at Warning and at Error, so the setting changed nothing and its comment was wrong. The property is removed and eng/api/README.md now records the observed facts: the scan enumerates the six files of src/CheatEngine.SDK, including packages.lock.json, which is not a component source, and reports no package.
LiveProbe did not compile: LiveProbeState.cs imported the test namespace CheatEngine.SDK.LiveProbe.Tests only so that its compile-linked copy in LiveProbe.Tests could find the ProbeHostGlobals stub (CS0234). Move the stub into the plugin's own namespace, LiveProbe, and drop the import, so both projects resolve the same type name. With that error gone, the remaining IDE0008 (var) findings surfaced and are fixed by dotnet format. Add the project to CheatEngine.SDK.slnx with its x64 platform mapping, exactly the entry `dotnet sln add -s tests` produces, applied as a single hunk instead of the serializer's whole-file rewrite so parallel solution edits stay mergeable. Remove its now stale exclusion from SolutionInventoryTests. CI compiles the harness from now on; nothing in CI loads or runs it.
The exact-host qualification scenarios need facts that only the loaded plugin can report, and some of them need a controlled failure. Add them to the harness, all opt-in and inert without the existing fail-closed gate: - ce77_live_probe_status() now also reports the plugin id, epoch, reported exports size (Q03) and PluginHost.LastInitRecordArgument next to the raw second bootstrap integer, still without interpretation (Q04); ce77_live_probe_status_json() returns the same facts, the assembly locations, MVIDs and Hosting load context (Q40) and the fault decisions as one ce77-live-probe-status-v1 object for the runner's driver. - ce77_live_probe_throw_managed_exception() throws inside the generated thunk so a pcall can record the catchable Lua error (Q14 at C3). - liveprobe.fault.json next to the plugin selects FactoryCreate, OnEnable or OnDisable as the stage that throws (Q06, Q08). It is read once per enable, without Lua, only when authorized; unknown content is ignored and reported. - -p:LiveProbeNonAsciiName=true builds a variant whose name mixes Latin-1 and non-cp1252 characters, to observe how CE 7.7 decodes the ANSI name (Q05.a, AnsiNameBuffer). - ce77_live_probe_pump_messages(seconds) pumps host messages from admitted main-thread work so the operator can untick the plugin during a callback; it reports the phases it saw (Q07, an observation only). The new logic lives in Lua-free files compile-linked into LiveProbe.Tests, with tests for the gate, the stage selection and the raw status values. Both READMEs move to the SDK section layout, document the hooks and point to the local qualification protocol.
Qualification results need machine-checkable shapes before anything can cite them. Add four JSON Schema draft 2020-12 documents under docs/qualification/schemas, every object closed: - support-profile: the documentary public-source profile and the qualifiable CE 7.7.0.10621 x64 managed-hostfxr profile, the Checkpoint A decisions, the unsupported routes and dated measurements. - qualification-matrix: one row per Q01-Q48 scenario and sub-row, with the scenario block (preconditions, operation, expected, expected category) and one cell per level. C0-C2 cells take executed, traited CI tests; C3/C4 cells take committed receipts only, name the profile and, once executed, the tree and package they came from. - qualification-receipt: one redacted C3/C4 run with operator, load route, tree, exact CI package identities, host, bridge, per-file bundle hashes, target, HKCU diff (names only), timings and its event log. - qualification-events: the structured transcript committed next to a receipt. They follow the frozen contract in shared-contracts sections 2.0-2.3 with the ratified additions A-SQUAL-1 to A-SQUAL-4, including the rule that a hash naming a committed JSON document is computed after CRLF-to-LF normalization. Other lots and the Client copy these files, so they land before the validator that enforces them.
Repository rules are C# tests, never script-only gates, and Repository.Tests takes no package. Add a validator for exactly the JSON Schema 2020-12 keywords the v0 schemas use (type, const, enum, pattern, required, properties, additionalProperties, items, minItems, minimum, allOf, anyOf, if/then/else, local $ref, boolean schemas), with ECMA-262 patterns, plus the semantic rules a schema cannot express: matrix structure and sub-row aggregation, profile citations, evidence kinds, receipt resolution and freshness, Automated evidence resolved to traited methods of solution test modules, receipt identity and preflight, event-log redaction, and the LF-normalized hash rule. QualificationSchemaTests pins the schemas: draft 2020-12, repository $id, closed objects, only implemented keywords, and every required and enum list equal to the validator constants, so schema and validator cannot drift. A self-test proves the validator reports each violation kind.
A qualification result must name the exact host it ran on, and the public Cheat Engine source must never stand in for the 7.7 binary (F01, ADR-02). Add docs/qualification/support-profile.json and its page with two profiles: - ce-public-src-ec45d5f, documentary and never qualifiable: the public source that declares 7.5.1 and the historical CLR bootstrap. - ce-7.7.0.10621-x64-managed-hostfxr, the only qualifiable profile: exact executable hash and version, excluded variants, Lua module, celua.txt, the locally modified ce.runtimeconfig.json, observed runtimes, the profile-relevant HKCU names (no data), LocalProcess as the only qualified backend, the authorized targets and plugin contract version 6. The page also separates the identities (public source, binary, SDK tree, v1.0.0 tag, consumed 1.0.0 package), the SDK-branch and Client 1.0.0 tuples with their three package identities, the runtime policy warning, the proposed Checkpoint A decisions CPA-1 to CPA-3, the unsupported routes and the dated measurement record. Hashes were measured read-only on 2026-09-23; no test reads the installation. SupportProfileTests pin the documents to the schema, to the committed Lua fixture, the LiveProbe authorization constants, AbiConstants.SdkVersion and the checked-in bridge, reject a qualifiable documentary profile, and enforce canonical JSON, no local path and no global score.
A receipt is only evidence when it names everything the result depends on (A20-03, A20-09, A04-22, ADR-12). Add QualificationReceiptTests with a complete sample receipt and event log as raw string literals, and refusal proofs for each rule the audit and the plan state: - citing the documentary public-source profile (A00-05, A17-03); - any drive, user-profile or file:// path, so receipts stay distributable (F14); - a Passed receipt without passKind, and a pass kind on a non-passed receipt (A20-11); - a missing tree hash, package hash, content hash or pull request field (A20-05, PR-SEQ-19); - a local pack instead of the CI artifact or nuget.org, and a CI artifact without its run; - an id that does not encode start time, scenario and package, and a fixture-level receipt; - a run on another host unless recorded as a justified NotApplicable. Committed receipts, none today, must validate where they are committed, match their event log's LF-normalized hash, carry no local path or raw debug output, and be cited by the matrix cell they qualify.
The audit executed nothing, and the existing tests are resources, not
results, until each one is mapped to the scenario it evidences (A20-01,
QUAL-MAT-2). Add only [Trait("Qualification", "Qxx")] attribute lines to
the test methods whose assertions match the scenario's success criterion
in analyses/20: Q01, Q02, Q03, Q04, Q05, Q05.a, Q06, Q07, Q08, Q11, Q12,
Q14, Q15, Q16, Q17 and Q39.
Every tagged test was read against its criterion first. Three candidates
stay untagged because they do not prove their scenario:
DisablePluginTests.OnDisable_throwing_is_logged_but_reports_TRUE_after_the_plugin_is_disabled
(TRUE after a failed plugin cleanup is the opposite of Q08's "no false
cleanly disabled" evidence), and ReentrancyTests
Enable_nested_in_OnEnable_is_refused_and_does_not_replace_the_context and
Enable_nested_in_OnDisable_is_refused_and_the_disable_stands (nested
enables, not the re-entrant disable of Q07). InitializeManagedTests
Second_call_is_idempotent_and_writes_the_same_bytes_including_the_name_pointer
joins Q05 because it proves the stable-name half of that scenario.
The four modules pass with --fail-skips on, and --filter-trait
"Qualification=Q07" selects exactly the five Q07 methods.
The audit's 48 scenarios are exit criteria, not results, and nothing in the repository said which of them any evidence covers. Add docs/qualification/matrix.json: one row per Q01-Q48 plus 14 sub-rows (Q05.a, Q08.a, Q09.a-b, Q30.a-e, Q31.a, Q32.a-d), each with its audit wording, owner, required levels copied from analyses/20, findings, a scenario block (preconditions, operation, expected result, expected category) and one cell per level. Seeding is conservative. C0-C2 cells are Passed only where the traited tests of the previous commit ran green on this head (Q01-Q08, Q05.a, Q11, Q12, Q14-Q17, Q39), with RefusalVerified where the expected outcome is a refusal. Every C3/C4 cell is NotExecuted on the qualifiable profile, except the profile decisions: Q38 C3, Q39 C3, Q42 C3/C4 and the file-as-process, CEServer, x86-host and ARM sub-rows are NotApplicable with a justification. Client-owned rows (Q33, Q43-Q45) are NotApplicable here and point to the Client matrix. Parents equal their sub-rows' aggregate as the contract defines it. QualificationMatrixTests enforce the audit oracle, schema, structure, profiles, pass kinds, evidence kinds, aggregation, receipt resolution and freshness, Automated evidence resolved to traited methods of solution test modules, and trait/matrix parity in both directions. The README explains levels, statuses, the hash and freshness rules and how receipts are read; its matrix summary and the support profile's Not executed list are generated from the matrix and checked by tests.
The host spike scanned Cheat Engine's tutorial programs, whose global match counts moved with their loaded modules, so a scan result could not be compared with anything. Add tests/CheatEngine.SDK.QualificationTarget, a console program the runner attaches Cheat Engine to (Q25-Q32 inputs): - a 16-byte marker held as constant data of the image, eight pinned heap copies 64 bytes apart, and a region with 20000 non-overlapping repetitions of a second pattern computed at run time; - Int32 and Int64 cells that the stdin command step advances by one; - one JSON ready record (cheatengine-qualification-target/v0) with PID, architecture, pointer size, image base, every region with its pattern and a count the target measures itself, and the value addresses. It references no SDK project, writes no file except an optional ready file, and exits on exit or end of input. It is published with Native AOT for win-x64 and win-x86 because the lab machine has no x86 .NET 10 runtime; cross-publishing win-x86 from x64 works with the installed MSVC toolset (verified; https://learn.microsoft.com/dotnet/core/deploying/native-aot/#platform-architecture-restrictions). Both images hold the marker twice, which the measured count reports. The project joins the solution (the entry `dotnet sln add -s tests` writes, as one hunk), so CI compiles it. Solution restores now download the win-x86 runtime, NativeAOT runtime, apphost and ILCompiler packs (77.6 MB); DisableTransitiveFrameworkReferenceDownloads avoids a further 49 MB of ASP.NET Core and Windows Desktop packs. QualificationProjectShape tests pin the shape of the target and of LiveProbe: in the solution, never a test module, never packed, no SDK reference, x64 plugin.
C3/C4 evidence needs a repeatable way to run the exact Cheat Engine host without touching the installation or the operator's settings, and it must never run in CI. Add eng/qualification: - Invoke-LocalQualification.ps1: the CI guard is the first statement (exit 3); a read-only preflight compares the installed executable, Lua module, runtimeconfig and celua.txt with support-profile.json (exit 4) and refuses an elevated runner, another Cheat Engine instance, build load or a dirty tree unless explicitly allowed (exit 5); runs hold the Global\ce-lab mutex, mirror the installation into a sandbox verified file by file, build every harness from the exact package in a throw-away consumer with an isolated NUGET_PACKAGES and check its closure (plugin entry point, SDK assemblies, deps.json without project entries, runtimeconfig, packaged bridge), record the bridge fingerprint, publish the qualification target, export HKCU, drive Cheat Engine through a generated autorun driver with a watchdog and operator handshakes, clean up, restore HKCU only on a non-empty difference with no other instance running (exit 7 otherwise), and write one redacted receipt and event log per scenario, or a smoke report that is never a receipt. - QualificationRunner.psm1: the pure helpers (LF hash rule, registry export parsing and name-only diff, redaction, event-log bounding, receipt id and assembly, Lua literals, bundle closure, pass-rule evaluation) so tests can exercise them without Cheat Engine. - driver/zz_cesdk_qualification.template.lua: a recorder that runs one step per timer tick under pcall, persists its progress across a Lua state reset and appends one JSON event per line. - scenarios.json: the Checkpoint B plan (C3 Q02-Q08, Q05.a, Q14, Q15, Q18, Q19, Q40; C4 Q09.a/Q09.b), with Q17 kept Manual and Q39 NotApplicable, each with a declarative pass rule. LocalQualificationRunnerTests run pwsh (failing, never skipping, when it is absent): the guard for CI, GITHUB_ACTIONS and TF_BUILD, a scope-aware AST proof that nothing writes into the installation, strict mode, no workflow reference, receipt assembly validated by the C# receipt rules, redaction, name-only registry diffs, closure refusals, scenario/harness consistency, and every generated driver compiled with the committed Lua 5.3 module. PSScriptAnalyzer reports no warning or error.
dotnet sln add tests/CheatEngine.SDK.NativeAotLoaderHarness.Tests, the only slnx entry that survives the maintainer's pivot (the eng/abi and docs/abi folder entries the old snapshot added no longer apply, since neither path exists on the current tip).
dotnet format --include (scoped to the files this lot touched) fixes the IDE0055/MA0002 findings left by writing the restored files with git show (which bypasses the eol=crlf checkout filter). It also adds StringComparer.Ordinal to a batch of Assert.Contains calls in ClassicSlotRegistryDocumentTests.cs (MA0002). One file, tests/CheatEngine.SDK.NativeAotLoaderHarness/LibraryProbeContract.cs, is compiled into two projects (the harness and its new Tests project via <Compile Link>); formatting it through both project contexts in one solution-wide dotnet format call made the tool insert literal 'Unmerged change' conflict markers into the file. Restored it from the preserved pre-format commit and re-verified with dotnet format scoped to each project individually (no further change needed). dotnet build CheatEngine.SDK.slnx -c Debug now succeeds with 0 warnings, 0 errors.
TestMethodTraits.Of only matched a void-returning method; broaden it to Task/ValueTask so it finds CheckStackFailureProcessTests.Direct_checkstack_growth_with_a_rejecting_allocator_returns_zero_and_recovers (an async Task test), restoring Checkstack_proof_record_matches_the_bundled_lua_and_the_lf_pinned_bridge_sources. Re-apply the libs/CheatEngine.SDK.Lua.Interop/README.md Q23 paragraph and Promise bullet: the second worktree recreation (see the final report) restored this file from its pre-edit committed content and the edit was not redone until this pass, so Retired_live_ids_are_absent correctly caught the regression. tests/CheatEngine.SDK.Repository.Tests now passes 174/175; the one remaining failure (Every_project_has_a_committed_lock_file, for the new NativeAotLoaderHarness.Tests project) is the expected, integrator-owned lock-file gap reported in requestsForOtherFiles.
The lot added tests/CheatEngine.SDK.NativeAotLoaderHarness.Tests to CheatEngine.SDK.slnx without a packages.lock.json (lot agents never regenerate lock files; that is the integrator's job). Generate it with dotnet restore --force-evaluate and verify --locked-mode for this project, the full solution and both out-of-slnx AOT/LiveProbe projects.
Wave 2 landed five lots on this branch (s-gen-a, s-rt, s-res, s-scan, s-abi); the Unreleased section still reflected the pre-Wave-2, pre-pivot state. Drop the qualification-support-profile bullet: the maintainer's no-custom-scripting pivot deleted docs/qualification/ and eng/qualification/ before Wave 2 started, so the feature it described no longer exists on this branch. Reword the documentation bullet the same way (no docs/ index remains to link). Add Added/Changed/Fixed entries for what the five lots actually shipped: LuaOptional<T> and the optional/variadic EngineApi grammar (S-GEN-A); bounded, deadline-terminable and first-found AOB scans plus the NoResult zero-match model (S-SCAN); TargetReleaseStatus.RefusedRuntimeChanged (S-RES); RuntimeInfo.TryDeriveTargetArchitecture and the PointerSize.FromArchitecture obsoletion (S-RT); and the CESDK9102/ CESDK0006/CESDK1020 diagnostics (S-ABI, S-RT). Call out the shared Unknown = 0 renumbering of every SDK-owned outcome enum added since 1.0.0 as one breaking-but-unreported-by-ApiCompat entry, since S-GEN-A, S-RT and S-SCAN each applied the same orchestrator decision to a different enum.
S-ABI published the F02 NativeAOT plugin restrictions in libs/CheatEngine.SDK.Abi/README.md and the packed src/CheatEngine.SDK/ README.md, but the root README's Requirements section, the page a plugin author reads first, never mentioned that a NativeAOT plugin DLL is not a supported profile. Add one line there and link to the packed README's "Load profiles and limits" section instead of the originally proposed docs/abi/nativeaot-profile.md, which does not exist under the no-docs/ pivot.
The maintainer's no-custom-scripting pivot dropped S-CAT-A's docs/catalog/ deliverable (it is now moot; its target no longer exists on this branch), but this README still pointed coverage measurement at that path. Describe the Lua surface catalogue as a concept only, the same way libs/CheatEngine.SDK.Engine/README.md already does, instead of naming a path that was never created.
WI-4 (branch N): the Checkpoint B Q05.a receipt is not available on this branch (no local Cheat Engine qualification run). Per the O5 recommendation, keep the existing ANSI encoding of AnsiNameBuffer unchanged and document the ANSI-vs-UTF8 question as open pending a future host-based qualification run, in the type's XML remarks and the Hosting README. No code or test behaviour changes.
WI-5: HostLog.IdentifyOnEnable (programmatic) and the CHEATENGINE_SDK_IDENTIFY_ON_ENABLE=1 environment variable opt an enable attempt into a single, bounded CheatEngineSdkIdentification log entry, written before the exports record is copied so it survives a later bind or construction failure. It reports SDK version/commit/consistency, the Hosting assembly MVID and a redacted load-context token, the plugin id and factory assembly, the raw (never interpreted) bootstrap argument, the bridge fingerprint/hash, the bound Lua module file name/hash, and the CE process file/version and runtime/arch - fixed key order, each value bounded to 128 characters (the bridge fingerprint's fixed 129-character shape is the single documented exception), the whole entry to 1024. The builder calls no Lua API and constructs no plugin. No local Cheat Engine qualification run is available on this branch, so every fact here is recorded as diagnostic only, never as a qualified observation.
WI-5: covers the opt-in gate (programmatic and environment-seam), emission before the exports record is copied (including when the Lua bind fails), the fixed key order and per-value/whole-entry bounds, the sdk.version/sdk.commit and bridge.fingerprint field shapes, the Lua-module file name and hash reported without a directory, that no value contains a directory separator, a drive root or the current user name, that building the line calls no Lua API and constructs no plugin, and the raw host argument being recorded without interpretation. Traited Qualification=Q46 where the test evidences that scenario.
A C++20 host that drives the coexistence A/B plugin protocol through the real managed (hostfxr) load path (component and default ALC routes), measuring Hosting-instance identity, MVID and ALC relationships instead of assuming them. Never starts, reads or references an installed Cheat Engine. build.ps1 mirrors tests/native-abi-fixture/build.ps1's VsDevCmd pattern and writes a manifest of every input's SHA-256 plus the observed toolchain versions. Verified end to end: separate-folder and shared-folder component routes both isolate each plugin's Hosting instance; the default-ALC route measurably rejects the second plugin's factory registration.
Appended only, at the end of the existing identity string: the Checkpoint B runner and receipts parse the existing prefix and keep working unmodified. Lets a C2 or exact-host observer tell whether two enabled plugins share one PluginHost static instance (distinct type handles) and whether a re-enable advanced the SDK's attach epoch.
NativeHostEmulatorTests runs the emulator against the prebuilt Coexistence plugin outputs (never a ProjectReference, so no packages.lock.json changes) for the separate-folder, shared-folder and default-ALC scenarios, gated by CESDK_NATIVE_HOST_EMULATOR_DIR/_REQUIRED exactly like the native ABI fixture's own pair. Verified green end to end against a locally patched build of the Coexistence plugins (see the lot report); red as committed today because tests/CheatEngine.SDK.LivePlugin.Coexistence/CoexistencePlugin.props never opts into entry-point generation for a direct ProjectReference build, which is outside this lot's owned files.
Confirms build.ps1 checks the exit code after cl.exe, writes only under its own output directory, and that no file under tests/native-host-emulator references an installed Cheat Engine.
Implements F04/ADR-07's conservative concurrency default and the 2.0
resetLuaState decision (WI-1, WI-3). LuaRuntime.AcquireOperation()-family
calls now refuse a worker thread with LuaAdmissionStatus.ThreadNotAdmitted
before the host's state provider ever runs, unless the caller is the
captured main thread, already inside admitted work, or the single
documented default exception (MainThreadDispatcher's synchronize hand-off,
now routed through AcquireOperationForMainThreadDispatch). A worker opts in
through LuaRuntime.AdmitWorkerThreads(), gated [Experimental("CESDK5001")]
until Q19 passes at C3 and C4.
Every attachment is now stamped with a private-registry universe marker,
checked raw and allocation-free on each admitted acquisition. A mismatch
means the host replaced its Lua state outside the SDK-owned reset path:
LuaRuntime.ExternalStateResetDetected becomes true, every admission path
refuses with ExternalStateReset until the next Attach, and Detach/Attach
abandon live callbacks and host subscriptions instead of unregistering
into the replacement registry (A08-22). There remains no public reset API.
No local CE qualification run is available for either the worker opt-in or
the reset-detection observation, so both are documented as unqualified
pending a future host-based validation run.
WI-2 (F04/A23-F04-3): the "one per assembly load context (one per plugin)" and "one load context hosts one plugin" phrasing asserted a host loader cardinality this SDK copy has never observed. Reword every occurrence to the fact this SDK copy actually owns (one binding per loaded assembly instance) and defer the load-context/plugin equivalence to Q09 evidence. Add ConcurrencyContractDocumentationTests to keep both claims out of libs/** and tests/** going forward.
WI-6 (A24-21, SRC02-06): a sink that calls HostLog.Write from its own Write, directly or through a path that logs, no longer recurses towards an uncatchable StackOverflowException. A per-thread guard drops the reentrant write and counts it (HostLog.DroppedReentrantEntries); other threads are unaffected. Move the second-factory-registration rejection log in PluginHost.TryRegisterFactory outside the registration gate, so a sink reacting to it never runs while that lock is held. Document the containment contract on IHostLogSink and HostLog, and update EnablePluginTests' existing reentrant-disable assertion for the new drop-and-count behaviour instead of the log entry it used to observe.
The bootstrap argument is proven never to change the 36-byte record write, for int.MinValue through int.MaxValue (A04-01). A free-counter seam on LuaModuleLocator.BindLocated proves repeated enabling releases exactly the one loader reference each already-bound lookup adds, never more and never less (A05-01). Disable now has a test that pumps a worker genuinely blocked inside the host's real Lua synchronize call, not only a MainThreadDispatcher.Dispatch override, before it detaches (A08-28), and the existing GUI-thread pump test is tagged Qualification=Q07 to match. Per the maintainer's pivot there is no docs/qualification/matrix.json on this branch, so the brief's "Matrix" bullet is dropped; the Qualification traits above are the part that still applies.
Hosting README gains a Promise item naming the three WI-8 exit tests. tests/CheatEngine.SDK.Hosting.Tests/README.md, which predated this lot's merge and named no individual test, gains bullets for the threading default, external-reset detection, log containment, opt-in identification, the native host emulator and the WI-8 exit tests contributed by the three merged S-HOST worktrees.
CoexistencePlugin.props built PluginA/PluginB through a direct ProjectReference chain and never set CheatEngineSdkGenerateEntryPoint or registered it as a CompilerVisibleProperty, unlike the packaged src/CheatEngine.SDK/build/CheatEngine.SDK.props asset. The EntryPoint generator therefore stayed silent by its own documented design and neither plugin assembly carried a CESDK.CESDK bootstrap type, so NativeHostEmulatorTests reported skipped instead of ok for every bootstrap/enable fact (5 of 9 failing). Mirror the packaged asset's two-line opt-in on the fixture props. Verified locally: rebuilt Debug (0 warnings/errors), rebuilt the native host emulator, and reran NativeHostEmulatorTests with CESDK_NATIVE_HOST_EMULATOR_DIR/REQUIRED=true set: 9/9 pass. Update the emulator README's "known local dependency" section to describe the fix instead of the prior skipped-not-failed caveat. Authorized by the binding orchestrator decision on S-HOST WI-7 CoexistencePlugin.props (2026-09-23), granting exactly this two-block XML change as a direct follow-up before integration.
The CoexistencePlugin.props fix in the previous commit makes the EntryPoint generator emit CESDK.CESDK.CEPluginInitialize for both coexistence fixtures, so LivePluginClosureTests now finds a real entry point where it previously expected none. The test's own contract is that the pending list only shrinks once a bundle gains the entry point, so remove both coexistence projects from it and update the class doc comment; CheatEngine.SDK.LivePlugin is unaffected and stays pending. Full-solution Debug suite: 4097/4097 passing (was 4095/4097).
Adds the native-host-emulator job to ci.yml (build tests/native-host- emulator/build.ps1, upload its artifact, feed it to the Debug build- test leg and gate.needs), matching the id and name the workflow contract already reserved for it. Updates the frozen build-test needs assertion to match. Documents everything Wave 2 shipped: the Lua concurrency contract and CESDK5001 in CONTRIBUTING.md and .coderabbit.yaml, the CESDK5xxx identifier range in the analyzer catalog, and the CHANGELOG entries for the threading default, external-reset detection, the identification opt-in, log containment, and the native host emulator.
|



Why
The CheatEngineNet audit of 2026-09-22 (pinned on
a56b6c1) concluded that the SDK has a substantial low-level base to keep and consolidate, and that the next steps are: qualify the loading profile before extending, align the consumed package, fix the Client divergences, and measure Lua coverage honestly (four distinct measures). This branch implements that work together with a professional CI/CD overhaul. The Client branch with the same name carries the Client side.Plan
.git-blame-ignore-revs), repository-test scaffolding, security settings, context for sub-agentspr-policy, Dependabot), release chain (draft-first, SBOM, provenance, tuple manifest)Evidence discipline
Qualification levels C0–C4 are kept distinct: a managed or fixture test is never presented as a Cheat Engine host qualification. Nothing is published or tagged from this branch.
Summary
void*.LuaOptional<T>, strict integer marshalling, and explicit Lua operation statuses. Updated binding generators and analyzers to support optional arguments and results, variadic results, and Outcome calls, with diagnostics for invalid shapes and look-alike contract types.