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"
|
||||
|
||||
Reference in New Issue
Block a user