$ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot $documents = @(Get-ChildItem -LiteralPath $root -File -Filter '*.openapi.json') if ($documents.Count -ne 1) { throw 'Exactly one current OpenAPI JSON export is required at the repository root' } $documentPath = $documents[0].FullName $document = Get-Content -Raw -Encoding UTF8 -LiteralPath $documentPath | ConvertFrom-Json function Get-Operation { param([string]$Path, [string]$Method) $pathProperty = $document.paths.PSObject.Properties[$Path] if (-not $pathProperty) { throw "Missing lineage path: $Path" } $operation = $pathProperty.Value.PSObject.Properties[$Method] if (-not $operation) { throw "Missing lineage operation: $Method $Path" } return $operation.Value } function Assert-ClientHeader { param([object]$Operation, [string]$Label) $clientId = @($Operation.parameters | Where-Object { $_.in -eq 'header' -and $_.name -eq 'clientid' -and $_.required -eq $true }) if ($clientId.Count -ne 1) { throw "$Label must require the clientid header" } $hasSaToken = @($Operation.security | Where-Object { $_.PSObject.Properties.Name -contains 'SaToken' }).Count -gt 0 if (-not $hasSaToken) { throw "$Label must require SaToken" } } $listPath = '/genealogy/app/genealogies/{genealogyId}/lineage/persons' $detailPath = '/genealogy/app/genealogies/{genealogyId}/lineage/persons/{personId}' $pagePath = '/genealogy/app/genealogies/{genealogyId}/lineage/persons/page' foreach ($target in @( @{ Path = $listPath; Method = 'get' }, @{ Path = $listPath; Method = 'post' }, @{ Path = $detailPath; Method = 'get' }, @{ Path = $detailPath; Method = 'put' }, @{ Path = $pagePath; Method = 'get' } )) { $operation = Get-Operation -Path $target.Path -Method $target.Method Assert-ClientHeader -Operation $operation -Label "$($target.Method.ToUpper()) $($target.Path)" } $create = Get-Operation -Path $listPath -Method 'post' $requestSchema = $create.requestBody.content.'application/json'.schema if ($requestSchema.'$ref' -ne '#/components/schemas/LineagePersonBody') { throw 'Lineage person create must consume LineagePersonBody' } $body = $document.components.schemas.LineagePersonBody if (@($body.required) -notcontains 'name') { throw 'LineagePersonBody must require name' } foreach ($field in @('name', 'generation', 'appUserId', 'fatherId', 'motherId', 'avatarOssId', 'birthLunar', 'deathLunar', 'personStatus')) { if (-not $body.properties.PSObject.Properties[$field]) { throw "LineagePersonBody is missing $field" } } $avatar = $body.properties.avatarOssId if (($avatar.type -join ',') -ne 'string,null' -or $avatar.pattern -ne '^[1-9][0-9]*$') { throw 'LineagePersonBody.avatarOssId must use the decimal string OSS ID contract' } foreach ($field in @('birthLunar', 'deathLunar', 'personStatus', 'sex')) { $schema = $body.properties.$field if ($schema.type -ne 'string') { throw "LineagePersonBody.$field must be a dictionary string" } if ($schema.enum) { throw "LineagePersonBody.$field enum changed; update the T-series selector contract" } } Write-Output 'LINEAGE-OPENAPI-CONTRACT PASS'