chore: harden SDD governance workflow
This commit is contained in:
@@ -5,6 +5,8 @@ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
workspace=${WEALTH_LEDGER_WORKSPACE:-$(cd "$script_dir/../.." && pwd -P)}
|
||||
failures=0
|
||||
|
||||
bash "$script_dir/verify-openspec-version.sh" "$workspace/specs/openspec/.cli-version"
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
failures=$((failures + 1))
|
||||
@@ -21,6 +23,27 @@ require_file_contains() {
|
||||
fi
|
||||
}
|
||||
|
||||
require_schema_edge() {
|
||||
local file=$1
|
||||
local artifact=$2
|
||||
local dependency=$3
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
fail "missing required file: ${file#$workspace/}"
|
||||
return
|
||||
fi
|
||||
|
||||
if ! awk -v artifact="$artifact" -v dependency="$dependency" '
|
||||
$0 == " - id: " artifact { in_artifact = 1; in_requires = 0; next }
|
||||
in_artifact && /^ - id:/ { in_artifact = 0; in_requires = 0 }
|
||||
in_artifact && $0 == " requires:" { in_requires = 1; next }
|
||||
in_artifact && in_requires && $0 == " - " dependency { found = 1 }
|
||||
END { exit(found ? 0 : 1) }
|
||||
' "$file"; then
|
||||
fail "${file#$workspace/} must require $artifact <- $dependency"
|
||||
fi
|
||||
}
|
||||
|
||||
store_file="$workspace/specs/.openspec-store/store.yaml"
|
||||
require_file_contains "$store_file" 'id: wealth-ledger-specs'
|
||||
|
||||
@@ -29,6 +52,27 @@ if [[ ! -d "$canonical_root" ]]; then
|
||||
fail "missing canonical OpenSpec root: specs/openspec"
|
||||
fi
|
||||
|
||||
config_file="$canonical_root/config.yaml"
|
||||
require_file_contains "$config_file" 'schema: wealth-ledger-sdd'
|
||||
|
||||
schema_file="$canonical_root/schemas/wealth-ledger-sdd/schema.yaml"
|
||||
require_file_contains "$schema_file" 'name: wealth-ledger-sdd'
|
||||
require_schema_edge "$schema_file" 'specs' 'proposal'
|
||||
require_schema_edge "$schema_file" 'design' 'specs'
|
||||
require_schema_edge "$schema_file" 'tasks' 'design'
|
||||
require_file_contains "$schema_file" 'requires: [ tasks ]'
|
||||
|
||||
if [[ -d "$workspace/specs" ]]; then
|
||||
if ! schema_validation=$(cd "$workspace/specs" && openspec schema validate wealth-ledger-sdd 2>&1); then
|
||||
fail "wealth-ledger-sdd schema validation failed: $schema_validation"
|
||||
fi
|
||||
if ! schema_source=$(cd "$workspace/specs" && openspec schema which wealth-ledger-sdd --json 2>&1); then
|
||||
fail "wealth-ledger-sdd schema resolution failed: $schema_source"
|
||||
elif ! grep -Fq '"source": "project"' <<<"$schema_source"; then
|
||||
fail "wealth-ledger-sdd must resolve from the project-local schema"
|
||||
fi
|
||||
fi
|
||||
|
||||
openspec_roots=()
|
||||
while IFS= read -r root; do
|
||||
openspec_roots+=("$root")
|
||||
@@ -72,6 +116,7 @@ entrypoints=(
|
||||
for relative_path in "${entrypoints[@]}"; do
|
||||
file="$workspace/$relative_path"
|
||||
require_file_contains "$file" '--store wealth-ledger-specs'
|
||||
require_file_contains "$file" 'bash specs/scripts/verify-openspec-version.sh'
|
||||
if [[ -f "$file" ]] && grep -Eqi 'nearest local openspec|global default(store)?|defaultstore|without a store|store: pointer|--store <id>|--store "<id>"|when applicable' "$file"; then
|
||||
fail "$relative_path permits conditional OpenSpec store selection"
|
||||
fi
|
||||
@@ -84,18 +129,75 @@ for relative_path in "${entrypoints[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
skill_entrypoints=(
|
||||
.opencode/skills/openspec-apply-change/SKILL.md
|
||||
.opencode/skills/openspec-archive-change/SKILL.md
|
||||
.opencode/skills/openspec-continue-change/SKILL.md
|
||||
.opencode/skills/openspec-explore/SKILL.md
|
||||
.opencode/skills/openspec-new-change/SKILL.md
|
||||
.opencode/skills/openspec-propose/SKILL.md
|
||||
.opencode/skills/openspec-sync-specs/SKILL.md
|
||||
.opencode/skills/openspec-verify-change/SKILL.md
|
||||
)
|
||||
for relative_path in "${skill_entrypoints[@]}"; do
|
||||
require_file_contains "$workspace/$relative_path" 'generatedBy: "1.10.0"'
|
||||
done
|
||||
|
||||
one_artifact_entrypoints=(
|
||||
.opencode/commands/opsx-continue.md
|
||||
.opencode/commands/opsx-explore.md
|
||||
.opencode/commands/opsx-propose.md
|
||||
.opencode/skills/openspec-continue-change/SKILL.md
|
||||
.opencode/skills/openspec-explore/SKILL.md
|
||||
.opencode/skills/openspec-propose/SKILL.md
|
||||
)
|
||||
for relative_path in "${one_artifact_entrypoints[@]}"; do
|
||||
file="$workspace/$relative_path"
|
||||
require_file_contains "$file" 'Create at most ONE planning artifact per invocation'
|
||||
require_file_contains "$file" 'STOP for explicit Change Owner approval'
|
||||
if [[ -f "$file" ]] && grep -Eqi 'generate all artifacts|create every artifact|until every requested artifact|continue until every requested artifact' "$file"; then
|
||||
fail "$relative_path permits multi-artifact generation without owner checkpoints"
|
||||
fi
|
||||
done
|
||||
|
||||
approval_blocker_entrypoints=(
|
||||
.opencode/commands/opsx-apply.md
|
||||
.opencode/commands/opsx-archive.md
|
||||
.opencode/skills/openspec-apply-change/SKILL.md
|
||||
.opencode/skills/openspec-archive-change/SKILL.md
|
||||
)
|
||||
for relative_path in "${approval_blocker_entrypoints[@]}"; do
|
||||
file="$workspace/$relative_path"
|
||||
require_file_contains "$file" 'explicit Change Owner approval'
|
||||
require_file_contains "$file" 'invalidate dependent approvals'
|
||||
done
|
||||
|
||||
archive_entrypoints=(
|
||||
.opencode/commands/opsx-archive.md
|
||||
.opencode/skills/openspec-archive-change/SKILL.md
|
||||
)
|
||||
for relative_path in "${archive_entrypoints[@]}"; do
|
||||
file="$workspace/$relative_path"
|
||||
require_file_contains "$file" 'affected delivery area'
|
||||
require_file_contains "$file" 'semantic verification'
|
||||
require_file_contains "$file" 'independent read-only review'
|
||||
require_file_contains "$file" 'Do not ask for or accept a confirmation override.'
|
||||
if [[ -f "$file" ]] && grep -Eqi 'archive complete \(with warnings\)|don.t block archive on warnings|archived with [0-9]+ incomplete' "$file"; then
|
||||
fail "$relative_path permits archive with incomplete work"
|
||||
fi
|
||||
done
|
||||
|
||||
verification_entrypoints=(
|
||||
.opencode/commands/opsx-verify.md
|
||||
.opencode/skills/openspec-verify-change/SKILL.md
|
||||
)
|
||||
for relative_path in "${verification_entrypoints[@]}"; do
|
||||
file="$workspace/$relative_path"
|
||||
require_file_contains "$file" 'affected delivery area'
|
||||
require_file_contains "$file" 'workspace verifier'
|
||||
require_file_contains "$file" 'independent read-only review'
|
||||
done
|
||||
|
||||
for relative_path in docs/superpowers/plans superpowers/plans .superpowers; do
|
||||
if [[ -e "$workspace/$relative_path" ]]; then
|
||||
fail "prohibited framework planning location: $relative_path"
|
||||
|
||||
@@ -21,6 +21,10 @@ write_file() {
|
||||
|
||||
write_file "specs/.openspec-store/store.yaml" $'version: 1\nid: wealth-ledger-specs'
|
||||
mkdir -p "$fixture/specs/openspec"
|
||||
write_file "specs/openspec/.cli-version" '1.10.0'
|
||||
write_file "specs/openspec/config.yaml" 'schema: wealth-ledger-sdd'
|
||||
mkdir -p "$fixture/specs/openspec/schemas"
|
||||
cp -R "$script_dir/../openspec/schemas/wealth-ledger-sdd" "$fixture/specs/openspec/schemas/wealth-ledger-sdd"
|
||||
|
||||
for path in AGENTS.md frontend/AGENTS.md backend/AGENTS.md specs/AGENTS.md docs/AGENTS.md; do
|
||||
write_file "$path" $'OpenSpec store: wealth-ledger-specs\nUse --store wealth-ledger-specs\nSDD artifacts only in specs/openspec\nreferences/** is read-only.'
|
||||
@@ -46,13 +50,36 @@ entrypoints=(
|
||||
)
|
||||
|
||||
for path in "${entrypoints[@]}"; do
|
||||
write_file "$path" 'openspec status --store wealth-ledger-specs'
|
||||
content=$'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs'
|
||||
if [[ "$path" == .opencode/skills/* ]]; then
|
||||
content=$'generatedBy: "1.10.0"\n'"$content"
|
||||
fi
|
||||
if [[ "$path" == *continue* || "$path" == *explore* || "$path" == *propose* ]]; then
|
||||
content="$content"$'\nCreate at most ONE planning artifact per invocation.\nSTOP for explicit Change Owner approval.'
|
||||
fi
|
||||
if [[ "$path" == *apply* || "$path" == *archive* ]]; then
|
||||
content="$content"$'\nRequire explicit Change Owner approval and invalidate dependent approvals after upstream edits.'
|
||||
fi
|
||||
write_file "$path" "$content"
|
||||
done
|
||||
|
||||
archive_gate='openspec status --store wealth-ledger-specs
|
||||
archive_gate='bash specs/scripts/verify-openspec-version.sh
|
||||
openspec status --store wealth-ledger-specs
|
||||
Require affected delivery area gates, semantic verification and independent read-only review.
|
||||
Require explicit Change Owner approval and invalidate dependent approvals after upstream edits.
|
||||
Do not ask for or accept a confirmation override.'
|
||||
write_file ".opencode/commands/opsx-archive.md" "$archive_gate"
|
||||
write_file ".opencode/skills/openspec-archive-change/SKILL.md" "$archive_gate"
|
||||
write_file ".opencode/skills/openspec-archive-change/SKILL.md" $'generatedBy: "1.10.0"\n'"$archive_gate"
|
||||
|
||||
verification_gate='bash specs/scripts/verify-openspec-version.sh
|
||||
openspec status --store wealth-ledger-specs
|
||||
Check every affected delivery area with the workspace verifier and require independent read-only review.'
|
||||
write_file ".opencode/commands/opsx-verify.md" "$verification_gate"
|
||||
write_file ".opencode/skills/openspec-verify-change/SKILL.md" $'generatedBy: "1.10.0"\n'"$verification_gate"
|
||||
|
||||
apply_gate='bash specs/scripts/verify-openspec-version.sh
|
||||
openspec status --store wealth-ledger-specs
|
||||
Require explicit Change Owner approval and invalidate dependent approvals after upstream edits.'
|
||||
|
||||
run_verifier() {
|
||||
WEALTH_LEDGER_WORKSPACE="$fixture" bash "$verifier"
|
||||
@@ -60,6 +87,28 @@ run_verifier() {
|
||||
|
||||
run_verifier
|
||||
|
||||
write_file "specs/openspec/.cli-version" '9.9.9'
|
||||
if run_verifier; then
|
||||
echo "expected wrong OpenSpec version contract to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file "specs/openspec/.cli-version" '1.10.0'
|
||||
|
||||
write_file "specs/openspec/config.yaml" 'schema: spec-driven'
|
||||
if run_verifier; then
|
||||
echo "expected non-project default schema to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file "specs/openspec/config.yaml" 'schema: wealth-ledger-sdd'
|
||||
|
||||
write_file "specs/openspec/schemas/wealth-ledger-sdd/schema.yaml" $'name: wealth-ledger-sdd\nversion: 1'
|
||||
if run_verifier; then
|
||||
echo "expected incomplete project schema graph to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
rm -rf "$fixture/specs/openspec/schemas/wealth-ledger-sdd"
|
||||
cp -R "$script_dir/../openspec/schemas/wealth-ledger-sdd" "$fixture/specs/openspec/schemas/wealth-ledger-sdd"
|
||||
|
||||
mkdir -p "$fixture/frontend/openspec"
|
||||
if run_verifier; then
|
||||
echo "expected additional OpenSpec root to fail" >&2
|
||||
@@ -67,41 +116,62 @@ if run_verifier; then
|
||||
fi
|
||||
rmdir "$fixture/frontend/openspec"
|
||||
|
||||
write_file ".opencode/commands/opsx-apply.md" 'Without a store, commands act on the nearest local openspec root.'
|
||||
write_file ".opencode/commands/opsx-apply.md" $'bash specs/scripts/verify-openspec-version.sh\nWithout a store, commands act on the nearest local openspec root.'
|
||||
if run_verifier; then
|
||||
echo "expected implicit store selection to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-apply.md" 'openspec status --store wealth-ledger-specs'
|
||||
write_file ".opencode/commands/opsx-apply.md" "$apply_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-apply.md" $'openspec status --store wealth-ledger-specs\nFor another store, append --store "<id>" when applicable.'
|
||||
write_file ".opencode/commands/opsx-apply.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nFor another store, append --store "<id>" when applicable.'
|
||||
if run_verifier; then
|
||||
echo "expected conditional store selection to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-apply.md" 'openspec status --store wealth-ledger-specs'
|
||||
write_file ".opencode/commands/opsx-apply.md" "$apply_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-apply.md" $'openspec status --store wealth-ledger-specs\nRun `openspec status --change "example" --json` before continuing.'
|
||||
write_file ".opencode/commands/opsx-apply.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nRun `openspec status --change "example" --json` before continuing.'
|
||||
if run_verifier; then
|
||||
echo "expected unscoped OpenSpec invocation to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-apply.md" 'openspec status --store wealth-ledger-specs'
|
||||
write_file ".opencode/commands/opsx-apply.md" "$apply_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-apply.md" $'openspec status --store wealth-ledger-specs\nUse artifact graph (openspec status --change "example" --json) before continuing.'
|
||||
write_file ".opencode/commands/opsx-apply.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nUse artifact graph (openspec status --change "example" --json) before continuing.'
|
||||
if run_verifier; then
|
||||
echo "expected parenthesized unscoped OpenSpec invocation to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-apply.md" 'openspec status --store wealth-ledger-specs'
|
||||
write_file ".opencode/commands/opsx-apply.md" "$apply_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-archive.md" $'openspec status --store wealth-ledger-specs\nDon\x27t block archive on warnings - just inform and confirm.'
|
||||
write_file ".opencode/commands/opsx-propose.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nCreate every artifact in the required set.\nSTOP for explicit Change Owner approval.'
|
||||
if run_verifier; then
|
||||
echo "expected multi-artifact proposal guidance to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-propose.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nCreate at most ONE planning artifact per invocation.\nSTOP for explicit Change Owner approval.'
|
||||
|
||||
write_file ".opencode/commands/opsx-archive.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nDon\x27t block archive on warnings - just inform and confirm.'
|
||||
if run_verifier; then
|
||||
echo "expected archive override guidance to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-archive.md" "$archive_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-archive.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs\nDo not ask for or accept a confirmation override.'
|
||||
if run_verifier; then
|
||||
echo "expected archive without required verification to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-archive.md" "$archive_gate"
|
||||
|
||||
write_file ".opencode/commands/opsx-verify.md" $'bash specs/scripts/verify-openspec-version.sh\nopenspec status --store wealth-ledger-specs'
|
||||
if run_verifier; then
|
||||
echo "expected semantic verifier without affected-area review to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
write_file ".opencode/commands/opsx-verify.md" "$verification_gate"
|
||||
|
||||
mkdir -p "$fixture/docs/superpowers/plans"
|
||||
if run_verifier; then
|
||||
echo "expected prohibited planning location to fail" >&2
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
contract_file=${1:-"$script_dir/../openspec/.cli-version"}
|
||||
|
||||
if [[ ! -f "$contract_file" ]]; then
|
||||
printf 'OpenSpec version contract is missing: %s\n' "$contract_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
expected_version=$(tr -d '[:space:]' < "$contract_file")
|
||||
if [[ -z "$expected_version" ]]; then
|
||||
printf 'OpenSpec version contract is empty: %s\n' "$contract_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v openspec >/dev/null 2>&1; then
|
||||
printf 'OpenSpec CLI %s is required, but openspec is not available in PATH.\n' "$expected_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
actual_version=$(openspec --version 2>/dev/null | tr -d '\r\n')
|
||||
if [[ "$actual_version" != "$expected_version" ]]; then
|
||||
printf 'Unsupported OpenSpec CLI version: expected %s, actual %s.\n' "$expected_version" "${actual_version:-unknown}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'OpenSpec CLI version verified: %s\n' "$actual_version"
|
||||
Executable
+193
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
printf 'Usage: %s <change-name>\n' "${0##*/}" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
change=$1
|
||||
if [[ ! "$change" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
||||
printf 'Invalid OpenSpec change name: %s\n' "$change" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
workspace=$(cd "$script_dir/../.." && pwd -P)
|
||||
change_dir="$workspace/specs/openspec/changes/$change"
|
||||
metadata_file="$change_dir/.openspec.yaml"
|
||||
design_file="$change_dir/design.md"
|
||||
|
||||
for required_file in "$metadata_file" "$design_file"; do
|
||||
if [[ ! -f "$required_file" ]]; then
|
||||
printf 'Missing canonical change input: %s\n' "${required_file#$workspace/}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
areas=(frontend backend specs docs)
|
||||
affected_flags=(0 0 0 0)
|
||||
map_statuses=('' '' '' '')
|
||||
map_reasons=('' '' '' '')
|
||||
contract_status=''
|
||||
contract_reason=''
|
||||
|
||||
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
|
||||
|
||||
printf 'Delivery Map verified:'
|
||||
for index in 0 1 2 3; do
|
||||
printf ' %s=%s' "${areas[$index]}" "${map_statuses[$index]}"
|
||||
done
|
||||
printf '\n'
|
||||
printf 'Contract Gate Decision verified: runtime-openapi=%s\n' "$contract_status"
|
||||
|
||||
run_gate() {
|
||||
local label=$1
|
||||
shift
|
||||
printf '\n==> %s\n' "$label"
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_gate 'OpenSpec CLI contract' bash "$workspace/specs/scripts/verify-openspec-version.sh"
|
||||
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"
|
||||
run_gate 'OpenSpec authority' bash "$workspace/specs/scripts/verify-openspec-authority.sh"
|
||||
|
||||
if [[ "${affected_flags[0]}" -eq 1 ]]; then
|
||||
run_gate 'Frontend canonical verification' npm --prefix "$workspace/frontend" run verify
|
||||
fi
|
||||
|
||||
if [[ "${affected_flags[1]}" -eq 1 ]]; then
|
||||
run_gate 'Backend canonical verification' npm --prefix "$workspace/backend" run verify
|
||||
fi
|
||||
|
||||
if [[ "${affected_flags[3]}" -eq 1 ]]; then
|
||||
run_gate 'Docs canonical verification' npm --prefix "$workspace/docs" run verify
|
||||
fi
|
||||
|
||||
if [[ "$contract_status" == "required" ]]; then
|
||||
run_gate 'Runtime OpenAPI contract' bash "$workspace/specs/scripts/verify-runtime-openapi.sh"
|
||||
fi
|
||||
|
||||
printf '\nWorkspace verification passed for %s.\n' "$change"
|
||||
Reference in New Issue
Block a user