chore: setup openapi contract gate [AC-INIT] #2
|
|
@ -0,0 +1,29 @@
|
|||
name: PR Check (SDD Contract Gate)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'spec/**/openapi.provider.yaml'
|
||||
- 'spec/**/openapi.deps.yaml'
|
||||
- 'src/**'
|
||||
|
||||
jobs:
|
||||
contract-level-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run OpenAPI Contract Level Check
|
||||
env:
|
||||
# For PRs targeting main, enforce provider >= L2
|
||||
REQUIRE_PROVIDER_L2: "1"
|
||||
run: |
|
||||
chmod +x scripts/check-openapi-level.sh
|
||||
./scripts/check-openapi-level.sh
|
||||
|
||||
- name: YAML Lint (Optional)
|
||||
run: |
|
||||
# Simple check if yq or other linter is not available
|
||||
find spec -name "*.yaml" -o -name "*.yml" | xargs -I {} python3 -c "import yaml, sys; yaml.safe_load(open('{}'))" || echo "YAML check skipped or failed"
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
# Check OpenAPI contract levels for multi-module layout.
|
||||
# - For PRs targeting main: require provider contract level >= L2.
|
||||
# - Always require info.x-contract-level exists and is L0-L3.
|
||||
#
|
||||
# Expected locations:
|
||||
# - spec/<module>/openapi.provider.yaml
|
||||
# - spec/<module>/openapi.deps.yaml (optional)
|
||||
|
||||
require_provider_l2="${REQUIRE_PROVIDER_L2:-0}"
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
level_rank() {
|
||||
case "$1" in
|
||||
L0) echo 0;;
|
||||
L1) echo 1;;
|
||||
L2) echo 2;;
|
||||
L3) echo 3;;
|
||||
*) echo -1;;
|
||||
esac
|
||||
}
|
||||
|
||||
extract_level() {
|
||||
# Extracts the first occurrence of info.x-contract-level: L?
|
||||
# Accepts patterns like:
|
||||
# x-contract-level: L2
|
||||
# x-contract-level: "L2"
|
||||
# under info:
|
||||
file="$1"
|
||||
|
||||
awk '
|
||||
BEGIN{in_info=0; level=""}
|
||||
{
|
||||
# detect "info:" at any indentation
|
||||
if ($0 ~ /^[[:space:]]*info:[[:space:]]*$/) { in_info=1; info_indent=match($0,/[^ ]/)-1; next }
|
||||
if (in_info==1) {
|
||||
# if indentation decreases or new top-level key begins, leave info block
|
||||
cur_indent=match($0,/[^ ]/)-1;
|
||||
if (cur_indent <= info_indent && $0 ~ /^[^[:space:]]/ ) { in_info=0 }
|
||||
}
|
||||
if (in_info==1 && level=="" && $0 ~ /^[[:space:]]*x-contract-level:[[:space:]]*/) {
|
||||
line=$0
|
||||
sub(/^[[:space:]]*x-contract-level:[[:space:]]*/,"",line)
|
||||
gsub(/"|\047/,"",line)
|
||||
# strip comments
|
||||
sub(/[[:space:]]*#.*/,"",line)
|
||||
gsub(/[[:space:]]+/,"",line)
|
||||
level=line
|
||||
}
|
||||
}
|
||||
END{print level}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
check_file_level() {
|
||||
file="$1"
|
||||
kind="$2" # provider|deps
|
||||
|
||||
[ -f "$file" ] || die "Missing OpenAPI file: $file"
|
||||
|
||||
level="$(extract_level "$file" | tr -d '\r')"
|
||||
[ -n "$level" ] || die "$file: missing info.x-contract-level (expected under info: x-contract-level: L0|L1|L2|L3)"
|
||||
|
||||
rank="$(level_rank "$level")"
|
||||
[ "$rank" -ge 0 ] || die "$file: invalid x-contract-level '$level' (expected L0|L1|L2|L3)"
|
||||
|
||||
if [ "$kind" = "provider" ] && [ "$require_provider_l2" = "1" ]; then
|
||||
if [ "$rank" -lt 2 ]; then
|
||||
die "$file: provider contract-level must be >= L2 for merge-to-main (current: $level)"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "OK: $file level=$level"
|
||||
}
|
||||
|
||||
# Find all provider openapi files under spec/*
|
||||
provider_files="$(find spec -mindepth 2 -maxdepth 2 -type f -name 'openapi.provider.yaml' 2>/dev/null || true)"
|
||||
[ -n "$provider_files" ] || die "No provider OpenAPI found. Expected at least one spec/<module>/openapi.provider.yaml"
|
||||
|
||||
# Provider files always must have a valid level; for main merges, require >= L2
|
||||
for f in $provider_files; do
|
||||
check_file_level "$f" provider
|
||||
|
||||
done
|
||||
|
||||
# Deps files are optional, but if present must have valid level
|
||||
for d in $(find spec -mindepth 2 -maxdepth 2 -type f -name 'openapi.deps.yaml' 2>/dev/null || true); do
|
||||
check_file_level "$d" deps
|
||||
|
||||
done
|
||||
|
||||
echo "All OpenAPI contract level checks passed."
|
||||
Loading…
Reference in New Issue