$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' } $json = Get-Content -Raw -Encoding UTF8 -LiteralPath $documents[0].FullName | ConvertFrom-Json $path = '/genealogy/app/feedback' $pathProperty = $json.paths.PSObject.Properties[$path] if (-not $pathProperty) { throw 'Feedback POST path is missing' } $operation = $pathProperty.Value.post if (-not $operation) { throw 'Feedback POST operation is missing' } if ($operation.requestBody.required -ne $true) { throw 'Feedback request body must be required' } if ($operation.requestBody.content.'application/json'.schema.'$ref' -ne '#/components/schemas/FeedbackBody') { throw 'Feedback operation must consume FeedbackBody' } $hasSaToken = $false foreach ($securityRequirement in @($operation.security)) { if ($securityRequirement.PSObject.Properties.Name -contains 'SaToken') { $hasSaToken = $true } } if (-not $hasSaToken) { throw 'Feedback operation must require SaToken' } $body = $json.components.schemas.FeedbackBody $fields = @($body.properties.PSObject.Properties.Name | Sort-Object) if (($fields -join ',') -ne 'contactInfo,feedbackContent,feedbackType') { throw "FeedbackBody fields drifted: $($fields -join ',')" } $required = @($body.required | Sort-Object) if (($required -join ',') -ne 'feedbackContent') { throw "FeedbackBody required fields drifted: $($required -join ',')" } foreach ($field in $fields) { if ($body.properties.$field.type -ne 'string') { throw "FeedbackBody.$field must be a string" } } if ((@($body.properties.feedbackType.enum) -join ',') -ne 'advice,bug,complaint,other') { throw 'FeedbackBody.feedbackType must use advice,bug,complaint,other' } Write-Output 'FEEDBACK-OPENAPI-CONTRACT PASS'