Repository navigation
Add migration inventory and public API baseline tooling (Phase 0) - #3
Merged
Merged
Conversation
Redth
force-pushed
the
redth-migration-inventory-tooling
branch
from
August 26, 2026 19:38
96281a5 to
b7c2a93
Compare
Redth
force-pushed
the
redth-migration-inventory-tooling
branch
from
August 26, 2026 19:42
b7c2a93 to
f2895a5
Compare
Redth
added a commit
that referenced
this pull request
Aug 26, 2026
Found while trial-merging PR #3 (inventory tooling) onto this branch: its three new projects - ApiDump, SourceInventory and Migration.Tooling.Tests - target net10.0, which the repository does not support (eng/baselines.json > policy.minimumDotNet = 11.0, supportsDotNet10 = false). The failure mode is subtle enough to be worth writing down. A net10.0 project BUILDS fine with the pinned .NET 11 SDK, so nothing complains at compile time. Its testhost then fails at run time: You must install or update .NET to run this application. Framework: 'Microsoft.NETCore.App', version '10.0.0' The following frameworks were found: 11.0.0-preview.7.26381.103 ...unless the machine also happens to carry a .NET 10 runtime - which GitHub's hosted images do. So it passes in CI and fails for anyone whose environment matches global.json exactly. Green for the wrong reason. The existing NoProjectFallsBackToANeutralTargetFramework test only scanned src/, so it could not have caught this. The new test walks every .csproj in the repository, parses each TFM, and compares against the floor declared in baselines.json - so the policy is enforced from one source of truth rather than restated per directory. Tests: 33 -> 34. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Redth
force-pushed
the
redth-migration-inventory-tooling
branch
3 times, most recently
from
August 27, 2026 00:07
c817eac to
e28428b
Compare
Adds deterministic, offline-runnable tooling for the dotnet/maui -> Maui.Tizen extraction, coordinated with the foundation/import branch's eng/baselines.json, eng/manifests, and eng/api-baselines layout: - eng/tools/SourceInventory: walks a dotnet/maui source checkout and classifies every Tizen-specific-path file and every shared file with a Tizen conditional- compile branch or platform-identity reference, grouping by component (Core handlers/platform, Controls, Essentials, Blazor, Maps, Graphics, Resizetizer, templates/tests) with initial move/rename/rebuild/keep-upstream/upstream- cleanup-candidate/exclude disposition hints. Also recovers Tizen-named files present only in the last-published (9.0.120) tree (e.g. the legacy top-level src/Compatibility renderer stack, removed upstream before net11.0). - eng/tools/ApiDump: reads a managed assembly's public API surface purely via System.Reflection.Metadata (never loads/executes it), so no Tizen workload is required. Used to capture the last-published MAUI 9.0.120 net9.0-tizen7.0 assemblies' public type/member surface. - eng/scripts: cross-platform pwsh orchestration to (re)generate the source inventory, the 9.0.120 API baseline, and the net11 net-tizen PublicAPI.Shipped/Unshipped.txt inputs, plus an offline validation entrypoint. - eng/manifests/source-disposition schema/summary and eng/api-baselines/published-9.0.120 and net11-publicapi-inputs are generated, checked-in artifacts with recorded source URLs/commits/versions. - docs/type-collision-classification.md documents when an extracted Tizen type may preserve its Microsoft.Maui.* name versus requiring a Tizen-prefixed standalone name, per the naming policy pinned in eng/baselines.json. - tests/Migration.Tooling.Tests: offline xUnit checks that schema-validate the manifest and re-hash every generated artifact against its recorded checksum and against eng/baselines.json, so a stale or hand-edited artifact fails CI without needing network access or a Tizen workload. Source baseline: dotnet/maui net11.0 at ee4d06cde6b49e297631b08426a33fb34f3152ef (contains PR 36657, 0b3bb76d2dd68d76b7c1302f43a76270d5949564, the mandated minimum API floor). Published baseline: 9.0.120 at c1f4f7d879f6126029009902289efd6a4bb1bda9. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Per coordinator direction, adopts the foundation branch's (redth-tizen-foundation-import)
canonical eng/manifests/source-disposition.schema.json, eng/baselines.json, and
eng/api-baselines/{net9.0-tizen7.0,net11.0-publicapi} directory names as authoritative,
replacing this branch's earlier parallel schema/paths:
- eng/tools/SourceInventory now emits the foundation's entry shape exactly: sourceRef
(sourceBaseline|behaviorBaseline|both), area (freeform upstream area string), package
(the 10-value Maui.Tizen.* enum), kind (tizen-specific|shared-conditional|project|asset),
disposition (move|rename|rebuild|keep-upstream|exclude|pending-audit), targetPath (computed
from eng/import/normalize-layout.sh's move_children mapping, required whenever disposition
implies a relocation), targetNamespace (only ever a preserved Microsoft.Maui.* namespace, per
the schema's pattern constraint), collisionRisk, and notes -- validated end-to-end against
the schema for all 542 generated entries.
- Added a "pending-audit" disposition to the schema for the legacy top-level
src/Compatibility/** stack (present at 9.0.120 only): per the coordinator's ruling, the
question is not "is it on net11.0" (it isn't) but "does a net11 Tizen handler depend on
implementation that exists only here", which this generator cannot answer -- so the drop
is recorded as a reviewed conclusion to reach, not a default the generator asserted.
- Renamed the API baseline output directories to the committed eng/api-baselines/
net9.0-tizen7.0/ and net11.0-publicapi/ (previously published-9.0.120/ and
net11-publicapi-inputs/ on this branch), matching eng/api-baselines/README.md's contract.
- Removed docs/type-collision-classification.md and eng/README.md: superseded by the
foundation's docs/architecture.md (same collision rules, already landed) and
eng/manifests/README.md + eng/api-baselines/README.md (same layout/ownership docs).
- Wired eng/tools/ApiDump, eng/tools/SourceInventory, and tests/Migration.Tooling.Tests into
eng/build-workload-free.sh's workload-free build/test lane and into Maui.Tizen.slnx, so CI
actually runs the 16 offline migration-tooling tests alongside the existing 20 repository
invariant tests. Verified with a full ./eng/build-workload-free.sh run (all checks pass).
- Added JsonSchema.Net and coverlet.collector to Directory.Packages.props (central package
management) for tests/Migration.Tooling.Tests; that project now follows the repo's other
test project conventions (no inline package versions, GenerateDocumentationFile=false).
Regenerated all three artifact sets from the pinned baselines and re-validated: 542
source-disposition entries (303 move, 107 rebuild, 70 pending-audit, 45 keep-upstream,
17 exclude) schema-valid; 7-package/1,688-type net9.0-tizen7.0 API dump; 18
PublicAPI.{Shipped,Unshipped}.txt files across 9 net11 projects.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ity, script hardening Per the coordinator's scope arbitration and independent review: - Reverted the source-disposition schema to be byte-identical to the foundation's canonical eng/manifests/source-disposition.schema.json (removed the earlier "pending-audit" addition). It is treated as immutable input from here on; tests/Migration.Tooling.Tests/SchemaImmutabilityTests.cs asserts the disposition/kind enums, the shared-conditional move/rename restriction, and every manifest entry's disposition stay within the schema's own five/four-value enums. - Reverted the legacy top-level src/Compatibility/** stack (absent from net11.0) to disposition "exclude" -- not "rebuild", not a new disposition value. Split its "area" into Compatibility.Core/.Material/.Maps (48/17/5 files) so the audit note is actionable per sub-stack without changing the disposition itself. src/Controls/src/Core/ Compatibility/** (still present on net11.0) remains the separate "rebuild" candidate; both distinctions are now covered by dedicated tests. - eng/tools/SourceInventory: added a duplicate-path hard rejection (non-zero exit, not just a downstream test), an EntryProvenance.historicalPaths field populated from eng/import/tizen-paths.txt's documented historical layouts, and removed the wall-clock generatedUtc timestamp (only emitted when SOURCE_DATE_EPOCH is set) so two runs against the same inputs now produce byte-identical output -- verified by running it twice and diffing. - eng/tools/ApiDump: nested types are now qualified by their full declaring-type chain (e.g. "Button+VisualStateManagerOverride") instead of a bare simple name that collided across unrelated types; delegates record their Invoke parameter/return signature; enums record their underlying primitive type and each member's numeric constant value. - eng/scripts/Get-MauiSourceSnapshot.ps1: atomic extraction via a staging directory (a killed run can never leave a half-extracted snapshot in place), always clears -OutDir before writing, and writes a .mt-snapshot.json provenance marker (repo, ref, file count, a streamed SHA-256 tree hash) that every consuming script now validates a caller-supplied -PrimaryRoot/-LegacyRoot against, rejecting an unverified or mismatched directory instead of silently scanning it. Fixed a Move-Item destination-exists bug that was nesting the extracted tree one level too deep. - eng/scripts/generate-api-baseline.ps1: nupkg downloads are hash-pinned to a sidecar file on first download and re-verified against it on every reuse (an arbitrary nonempty cache file is never trusted); each package's NuGet signature is checked for presence and decoded with SignedCms for structural (non-corrupt) validity; the expected Tizen assembly filename per package is asserted exactly rather than "any .dll present"; output is fully cleared before regeneration; and package/assembly/output SHA-256 hashes are all recorded in manifest.json. Verified byte-identical output across two runs. - Removed the remaining wall-clock timestamp from fetch-net11-publicapi-inputs.ps1's manifest for the same reproducibility reason. - Rebased onto the foundation branch's current tip (26d06e2): resolved the Maui.Tizen.slnx conflict (both PackageGraphProbe and Migration.Tooling.Tests belong in /tests/), moved the migration-tooling-tests invocation inside build-workload-free.sh's BUILD_OK guard so it can no longer run --no-build after a failed build, and retargeted all three of this branch's own projects (ApiDump, SourceInventory, Migration.Tooling.Tests) from net10.0 to net11.0 to satisfy the repository's own .NET 11 floor (eng/baselines.json policy.minimumDotNet) and the new NoProjectTargetsBelowTheDotNetFloor invariant test. Regenerated all three artifact sets from verified source snapshots and re-validated: 542 source-disposition entries (303 move, 107 rebuild, 87 exclude, 45 keep-upstream), all schema-valid; 7-package/1,688-type net9.0-tizen7.0 API dump, every package signed and hash-pinned; 18 PublicAPI.{Shipped,Unshipped}.txt files across 9 net11 projects. Full ./eng/build-workload-free.sh passes end to end. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…pmentPackageBaseline bump The coordinator is upgrading eng/baselines.json's source.developmentPackageBaseline to 11.0.0-preview.7.26426.4 @ bedd1b18b7682193e05b47267509cec8c49c6853 (the dnceng dotnet11 feed), while the source import baseline stays pinned to ee4d06 (no re-import). The three gesture/badge API commits recorded in source.sourceBaseline.knownGapAfterThisPin (#37420, #37671, #37755) are now available via the published package, resolving what was previously an API-surface-only gap between the source import and current net11.0. Development_package_baseline_shares_the_required_ancestor_commit asserted strict equality between developmentPackageBaseline.nuspecRepositoryCommit and requiredAncestor.commit, which is exactly the invariant this intentional bump breaks by design: the package baseline is meant to move forward past the source-import pin over time. Replaced it with Development_package_baseline_commit_is_a_documented_ref, which accepts either the required-ancestor commit or the commit already recorded as knownGapAfterThisPin.observedAgainst -- i.e. any ref this manifest already explains, not an exact match. An arbitrary, undocumented commit still fails (verified locally with a deadbeef sentinel before restoring eng/baselines.json). Verified against both the current eng/baselines.json and a locally-simulated post-bump version (version 11.0.0-preview.7.26426.4, commit bedd1b18b7...) -- confirmed via a real NuGet download that this version's nuspec repository commit is exactly bedd1b18b7682193e05b47267509cec8c49c6853, matches across Microsoft.Maui.Core and Microsoft.Maui.Controls.Core, and still carries no Tizen TFM. Does not touch eng/baselines.json itself, Directory.Packages.props, or docs/migration.md/PROVENANCE.md (foundation-owned) -- only this repository's own test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…eal signature verification 1. Snapshot provenance (eng/scripts/Get-MauiSourceSnapshot.ps1 and its consumers) Test-SnapshotMarker/Assert-VerifiedSnapshot previously only compared the .mt-snapshot.json marker's claimed repository/ref against what was requested -- it never recomputed the tree hash from the files actually on disk, so a marker claiming the right repo/ref next to a directory that had since had a file added, removed, or modified would be silently accepted. Extracted the marker read/write/verify logic into eng/scripts/lib/Snapshot.ps1 (Get-SnapshotTreeHash, New-SnapshotMarker, Test-SnapshotIntegrity), and made Test-SnapshotIntegrity ALWAYS recompute the tree hash from the current file system state and compare it against the marker before reporting ok. Get-MauiSourceSnapshot.ps1 (cache-hit check), generate-source-inventory.ps1 (Assert-VerifiedSnapshot), and fetch-net11-publicapi-inputs.ps1 (its inline caller-supplied-root check) all now call this one shared function, so there is a single place this guarantee is implemented rather than three copies that can drift. Added eng/tests/test-snapshot-verification.ps1: offline regression tests (synthetic directories, no network) covering a fresh snapshot verifying successfully, a missing marker, a wrong repo/ref, a file MODIFIED after the marker was written, a file ADDED after the marker was written, a file DELETED after the marker was written, and a determinism check (two independently-created snapshots of identical content produce the same tree hash). Wired into eng/build-workload-free.sh alongside the existing test-workload-detection.sh regression lane. 2. NuGet authenticity (eng/scripts/generate-api-baseline.ps1) The previous Test-PackageSignature called System.Security.Cryptography.Pkcs.SignedCms.Check Signature() on the isolated .signature.p7s zip entry in isolation. That only proves the signature blob itself is an internally well-formed, self-consistent PKCS#7 structure -- it does not recompute a hash over the package's actual current content and compare it to the hash embedded inside the signature, so a package modified after signing (while its original, untouched .signature.p7s entry is left in place) still "passes". Verified this concretely: a package with one byte flipped in a non-signature entry's compressed data (structure otherwise untouched) passes a bare SignedCms.CheckSignature() call but is correctly rejected (NU3008) by NuGet's own IntegrityVerificationProvider. Added eng/tools/PackageVerify: a small tool that performs REAL NuGet package signature verification via NuGet.Packaging.Signing (IntegrityVerificationProvider + SignatureTrustAndValidityVerificationProvider), mirroring what `dotnet nuget verify --all` does, since the `dotnet nuget verify` CLI itself was unusable in this environment's SDK install. Prints a JSON verification result (isSigned, signatureType, isValid, errors, warnings) that generate-api-baseline.ps1 parses. Replaced trust-on-first-use entirely with a checked-in trust anchor, eng/api-baselines/net9.0-tizen7.0-package-trust.json: every one of the 7 packages generate-api-baseline.ps1 downloads must already have a pinned SHA-256 there (established once, under review, by downloading each package fresh from nuget.org and independently verifying isSigned=true/isValid=true via PackageVerify before recording its hash) or the script refuses to run at all. A downloaded or cached file whose SHA-256 does not match its pin is now a hard failure regardless of whether its signature still verifies -- the local .nupkg cache is purely a performance shortcut, never itself a trust source. Verified all three scenarios directly: cold cache (fresh download, hash matches pin), warm cache (reused file, hash re-verified against pin before reuse), and a tampered cached file (hash mismatch, generation refuses to proceed). Added tests/Migration.Tooling.Tests/PackageVerifyTests.cs, which runs the REAL built PackageVerify tool (not a reimplementation of its logic) against an already-restored, real signed NuGet package (NuGet.Packaging itself, resolved dynamically from Directory.Packages.props so the version never drifts -- no new network dependency, since restore already fetched it) and a byte-tampered copy created with the exact original-signature-intact technique described above, asserting the original verifies and the tampered copy fails with NU3008. tests/Migration.Tooling.Tests/PackageTrustAnchorTests.cs checks the trust anchor's structure (exactly the 7 expected packages, valid-looking hashes, no duplicates). Updated PublishedApiBaselineTests.cs for the renamed/added manifest.json fields and to cross-check every recorded hash against the trust anchor. Regenerated eng/api-baselines/net9.0-tizen7.0 end to end (cold cache, warm cache, and a simulated tamper case) -- every per-assembly API dump JSON file is byte-identical to what was already committed; only manifest.json changed, to add the new pinnedNupkgSha256/signatureType/ signatureIntegrityAndTrustValid fields the strengthened verification now records. Ran ./eng/build-workload-free.sh end to end: PackageVerify and the two new offline test files are wired into the CI lane, all local checks pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Redth
force-pushed
the
redth-migration-inventory-tooling
branch
from
August 27, 2026 00:10
e28428b to
89833f6
Compare
…d of newline delimiter Independent security review of the two hardening fixes came back clean, but flagged one low-priority defense-in-depth note: Get-SnapshotTreeHash's prior encoding concatenated each file as "relativePath\n<content>" with no length prefix. Since POSIX filesystems legally permit a literal newline byte inside a filename, this delimiter-based scheme was not strictly injective -- in principle, two different (path, content) partitions of the same underlying byte stream could produce identical concatenations and therefore identical hashes. Replaced the encoding with explicit 8-byte length prefixes for both the path and the content before their bytes, making the full file-list encoding unambiguous regardless of what bytes appear in any path or file content, with no assumption about delimiter-safety. Added a regression test to eng/tests/test-snapshot-verification.ps1 constructing the exact adversarial case a delimiter-based scheme would be vulnerable to (a single file named 'a<LF>b' with content 'c', versus two files 'a' with content 'b' and 'b' with content 'c' -- both of which concatenate to the same 'a\nb\nc' byte stream under the old scheme) and asserting the two trees now hash differently. This changes the tree-hash algorithm, but since .mt-snapshot.json markers are generated at runtime into temporary cache directories and never committed to source control, no checked-in artifact is affected -- verified via a full re-run of ./eng/build-workload-free.sh (all checks pass, including all 8 snapshot verification regression tests). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 0, independent-and-reviewable tooling for the
dotnet/maui->Redth/Maui.Tizenextraction. No runtime/package scaffold, no git-history import -- just deterministic inventory + API baseline tooling and the checked-in artifacts it produces, designed to merge cleanly alongside the separate foundation/history-import branch (coordinated live with that session oneng/baselines.json,eng/manifests/,eng/api-baselines/, andeng/tools/paths/shapes).What's here
eng/tools/SourceInventory— walks adotnet/mauicheckout and classifies every Tizen-specific-path file and every shared file containing a Tizen conditional-compile branch (#if TIZEN) or platform-identity reference (DevicePlatform.Tizen, etc.), grouped by component (Core handlers, Core platform, Controls, Essentials, Blazor, Maps, Graphics, Resizetizer, templates/tests) withmove/rename/rebuild/keep-upstream/upstream-cleanup-candidate/excludedisposition hints. Pure file-system + regex scan — no build, no Tizen workload.eng/tools/ApiDump— reads a managed assembly's public API surface purely viaSystem.Reflection.Metadata(never loads/executes it) — no Tizen workload needed. Used against the last-published MAUI 9.0.120net9.0-tizen7.0assemblies.eng/scripts/*.ps1— cross-platformpwshorchestration to regenerate each artifact set and an offline validation entrypoint.eng/manifests/source-disposition.{json,schema.json,summary.md}andeng/api-baselines/{published-9.0.120,net11-publicapi-inputs}/**— generated, checked-in artifacts recording source URLs/commits/versions.docs/type-collision-classification.md— rules for when an extracted Tizen type may keep itsMicrosoft.Maui.*name vs. requiring a Tizen-prefixed standalone name, per the naming policy pinned ineng/baselines.json.tests/Migration.Tooling.Tests— offline xUnit checks: JSON-Schema validation of the manifest, and re-hashing every generated artifact against its recorded checksum and againsteng/baselines.json, so a stale/hand-edited artifact fails CI without network or a Tizen workload.Pinned baselines
dotnet/mauinet11.0@ee4d06cde6b49e297631b08426a33fb34f3152ef0b3bb76d2dd68d76b7c1302f43a76270d5949564(confirmed ancestor of the source baseline)9.0.120@ source commitc1f4f7d879f6126029009902289efd6a4bb1bda911.0.0-preview.7.26418.3from thednceng/dotnet11public feed, repository commit0b3bb76d2d...(confirms Tizen has already been fully dropped from net11 shipping packages — nonet11.0-tizen*TFM exists in any of Core/Controls.Core/Controls.Xaml/Controls.Maps/Essentials/Graphics at this version, andMicrosoft.Maui.Controls.Compatibilityisn't published at all)Generated counts (net11 primary + 9.0.120 legacy-recovery scan)
src/Compatibility/**renderer stack and dropped Tizen templates), 44 upstream-cleanup-candidate, 11 rebuild (legacy Compatibility-era renderers)collisionReviewNeededfor the type-collision doc's review processPublicAPI.{Shipped,Unshipped}.txtfiles collected across 9 net11 projectsData-quality note
Initial investigation surfaced a naming collision worth flagging for reviewers:
src/Controls/src/Core/Compatibility/**(an in-tree legacy renderer shim, still present and unchanged on net11.0) is distinct from the separate top-levelsrc/Compatibility/**project (the old Xamarin.Forms.Platform.Compatibility stack, fully removed before net11.0). Both are captured correctly in the manifest with different dispositions (rebuildvs.exclude), but it's an easy mix-up when reasoning about "Compatibility" by name alone.Validation
Both tools build cleanly (
dotnet build), and the full artifact set was regenerated from a clean checkout via the threeeng/scripts/generate-*/fetch-*scripts to confirm reproducibility before committing.Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com