$ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot function Read-Utf8([string]$relativePath) { Get-Content -LiteralPath (Join-Path $root $relativePath) -Raw -Encoding UTF8 } function Assert-Contains([string]$source, [string]$expected, [string]$page) { if (-not $source.Contains($expected)) { throw "$page missing F business-flow anchor: $expected" } } $contracts = [ordered]@{ 'pages/family/f03-feed-detail.vue' = @( 'feedState', 'appApi.getFeedDetail', 'appApi.getFeedComments', 'appApi.createFeedComment', 'feedTypeLabel(feed.type)', 'returnTo("F01", { genealogyId: genealogyId.value })' ) 'pages/family/f04-article-list.vue' = @( 'listState', 'appApi.getArticles', 'openArticle', 'createArticle', 'openPage("F05"', 'openPage("F06"' ) 'pages/family/f05-article-detail.vue' = @( 'articleState', 'appApi.getArticleDetail', 'article-state--${articleState}', 'returnTo("F04", { genealogyId: genealogyId.value })' ) 'pages/family/f07-album-list.vue' = @( 'listState', 'appApi.getAlbums', 'appApi.createAlbum', 'openAlbum', 'pickAndUploadImage', 'openPage("F08"' ) } foreach ($entry in $contracts.GetEnumerator()) { $source = Read-Utf8 $entry.Key foreach ($anchor in $entry.Value) { Assert-Contains $source $anchor $entry.Key } foreach ($forbidden in @('listFamilyArticleFixtures', 'findFamilyArticleFixture', 'listFamilyAlbumFixtures', 'localAlbumPreview', 'feed-detail-contract-note', 'data/mock')) { if ($source.Contains($forbidden)) { throw "$($entry.Key) retains retired F business-flow implementation: $forbidden" } } } Write-Output 'F-BUSINESS-FLOW-CONTRACT PASS'