$ErrorActionPreference = 'Stop' $source = Get-Content 'pages/profile/m01-profile-home.vue' -Raw -Encoding UTF8 $profiles = Get-Content 'styles/adaptive-frame-profiles.scss' -Raw -Encoding UTF8 $required = @( 'profile-state--ready', 'profile-state--error', '@include adaptive.adaptive-profile-summary;', '@include adaptive.adaptive-scroll-button(primary);', 'brand-seal.png', 'auth-divider-knot.png', 'chevron-right.png', 'root-header-hall.png', 'a01-icon-lock-v1.png', 'notice.png', 'AppButton', 'import { openPage } from "@/utils/navigation.js";', 'routeKey: "M03"', 'routeKey: "M06"', 'routeKey: "M10"', 'routeKey: "M08"', 'routeKey: "M09"', 'openPage("M02", {}, "M01")', 'openPage("N01", {}, "M01")', 'openPage(item.routeKey, {}, "M01")' ) foreach ($token in $required) { if (-not $source.Contains($token)) { throw "M01 missing contract token: $token" } } foreach ($forbiddenNavigation in @('uni.navigateTo', 'uni.navigateBack', 'uni.redirectTo', 'uni.reLaunch', 'getCurrentPages(', ']*\s)?class="(?:profile-hero__frame|profile-scroll-notice__skin|profile-error__skin)"') { throw 'M01 decorative frames must be container backgrounds instead of positioned image layers' } if ($source -match 'position\s*:') { throw 'M01 must keep content and local decoration in flex/grid document flow' } foreach ($token in @( 'class="profile-hero__content"', 'grid-area: 1 / 1' )) { if (-not $source.Contains($token)) { throw "M01 missing document-flow token: $token" } } if ($profiles -notmatch '(?s)@mixin\s+adaptive-profile-summary\s*\{.*?m01-profile-summary-card\.png') { throw 'The adaptive profile owner must retain the M01 summary-card artwork' } if ($profiles -notmatch '(?s)@mixin\s+adaptive-scroll-button\s*\(\$type\).*?a01-scroll-primary-v3\.png') { throw 'The adaptive profile owner must retain the primary scroll-button artwork' } if ($source -match '100%\s+100%\s+no-repeat|border-image-slice\s*:') { throw 'M01 must consume adaptive frame geometry without page-local stretching or slicing' } Write-Output 'M01-MODULE-BASELINE-CONTRACT PASS'