feat: harden OpenSpec SDD workflow
This commit is contained in:
@@ -25,134 +25,14 @@ for required_file in "$metadata_file" "$design_file"; do
|
||||
fi
|
||||
done
|
||||
|
||||
areas=(frontend backend specs docs)
|
||||
affected_flags=(0 0 0 0)
|
||||
map_statuses=('' '' '' '')
|
||||
map_reasons=('' '' '' '')
|
||||
contract_status=''
|
||||
contract_reason=''
|
||||
# shellcheck source=lib/delivery-map.sh
|
||||
source "$script_dir/lib/delivery-map.sh"
|
||||
wl_parse_delivery_map "$metadata_file" "$design_file"
|
||||
|
||||
area_index() {
|
||||
case "$1" in
|
||||
frontend) printf '0' ;;
|
||||
backend) printf '1' ;;
|
||||
specs) printf '2' ;;
|
||||
docs) printf '3' ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
while IFS= read -r area; do
|
||||
[[ -n "$area" ]] || continue
|
||||
if ! index=$(area_index "$area"); then
|
||||
printf 'Unknown affected area in %s: %s\n' "${metadata_file#$workspace/}" "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${affected_flags[$index]}" -eq 1 ]]; then
|
||||
printf 'Duplicate affected area in %s: %s\n' "${metadata_file#$workspace/}" "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
affected_flags[$index]=1
|
||||
done < <(
|
||||
awk '
|
||||
$0 == "affected_areas:" { in_areas = 1; next }
|
||||
in_areas && /^ - / { sub(/^ - /, ""); print; next }
|
||||
in_areas { exit }
|
||||
' "$metadata_file"
|
||||
)
|
||||
|
||||
if [[ "${affected_flags[2]}" -ne 1 ]]; then
|
||||
printf 'Delivery Map violation: specs must be affected for every versioned change.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
trim() {
|
||||
local value=$1
|
||||
value=${value#"${value%%[![:space:]]*}"}
|
||||
value=${value%"${value##*[![:space:]]}"}
|
||||
printf '%s' "$value"
|
||||
}
|
||||
|
||||
while IFS='|' read -r _ raw_area raw_status raw_reason _; do
|
||||
area=$(trim "$raw_area")
|
||||
case "$area" in
|
||||
frontend|backend|specs|docs) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
index=$(area_index "$area")
|
||||
|
||||
if [[ -n "${map_statuses[$index]}" ]]; then
|
||||
printf 'Delivery Map contains duplicate row for %s.\n' "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
status=$(trim "$raw_status")
|
||||
status=${status//\`/}
|
||||
reason=$(trim "$raw_reason")
|
||||
map_statuses[$index]=$status
|
||||
map_reasons[$index]=$reason
|
||||
done < "$design_file"
|
||||
|
||||
while IFS='|' read -r _ raw_gate raw_status raw_reason _; do
|
||||
gate=$(trim "$raw_gate")
|
||||
[[ "$gate" == "runtime-openapi" ]] || continue
|
||||
if [[ -n "$contract_status" ]]; then
|
||||
printf 'Contract Gate Decision contains duplicate runtime-openapi row.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
contract_status=$(trim "$raw_status")
|
||||
contract_status=${contract_status//\`/}
|
||||
contract_reason=$(trim "$raw_reason")
|
||||
done < "$design_file"
|
||||
|
||||
for index in 0 1 2 3; do
|
||||
area=${areas[$index]}
|
||||
status=${map_statuses[$index]}
|
||||
reason=${map_reasons[$index]}
|
||||
if [[ -z "$status" ]]; then
|
||||
printf 'Delivery Map is missing area: %s\n' "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${affected_flags[$index]}" -eq 1 ]]; then
|
||||
if [[ "$status" != "affected" ]]; then
|
||||
printf 'Delivery Map mismatch: %s is listed as affected in metadata but has status %s.\n' "$area" "$status" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$reason" || "$reason" == "-" || "$reason" == "—" || "$reason" == *"<!--"* ]]; then
|
||||
printf 'Delivery Map requires an architectural outcome for affected area %s.\n' "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [[ "$status" != "N/A" ]]; then
|
||||
printf 'Delivery Map mismatch: %s is absent from affected_areas but has status %s.\n' "$area" "$status" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$reason" || "$reason" == "-" || "$reason" == "—" || "$reason" == *"<!--"* ]]; then
|
||||
printf 'Delivery Map requires a concrete N/A reason for %s.\n' "$area" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "$contract_status" ]]; then
|
||||
printf 'Contract Gate Decision is missing runtime-openapi.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$contract_status" != "required" && "$contract_status" != "N/A" ]]; then
|
||||
printf 'Invalid runtime-openapi decision: %s. Expected required or N/A.\n' "$contract_status" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$contract_reason" || "$contract_reason" == "-" || "$contract_reason" == "—" || "$contract_reason" == *"<!--"* ]]; then
|
||||
printf 'Contract Gate Decision requires a concrete runtime-openapi reason.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$contract_status" == "required" ]]; then
|
||||
if [[ "${affected_flags[0]}" -ne 1 || "${affected_flags[1]}" -ne 1 ]]; then
|
||||
printf 'runtime-openapi cannot be required unless frontend and backend are affected.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
areas=("${wl_areas[@]}")
|
||||
affected_flags=("${wl_affected_flags[@]}")
|
||||
map_statuses=("${wl_map_statuses[@]}")
|
||||
contract_status=$wl_contract_status
|
||||
|
||||
printf 'Delivery Map verified:'
|
||||
for index in 0 1 2 3; do
|
||||
@@ -169,6 +49,7 @@ run_gate() {
|
||||
}
|
||||
|
||||
run_gate 'OpenSpec CLI contract' bash "$workspace/specs/scripts/verify-openspec-version.sh"
|
||||
run_gate 'OpenSpec planning structure' bash "$workspace/specs/scripts/verify-openspec-planning.sh" "$change"
|
||||
run_gate 'OpenSpec project schema' bash -c 'cd "$1" && openspec schema validate wealth-ledger-sdd' _ "$workspace/specs"
|
||||
run_gate 'OpenSpec change validation' bash -c 'cd "$1" && openspec validate "$2" --type change --strict --store wealth-ledger-specs' _ "$workspace/specs" "$change"
|
||||
run_gate 'OpenSpec main specs' bash -c 'cd "$1" && openspec validate --specs --strict --store wealth-ledger-specs' _ "$workspace/specs"
|
||||
|
||||
Reference in New Issue
Block a user