#!/usr/bin/env bash set -euo pipefail 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" expected_version=$(tr -d '[:space:]' < "$workspace/specs/openspec/.cli-version") fail() { printf 'FAIL: %s\n' "$1" >&2 failures=$((failures + 1)) } require_file_contains() { local file=$1 local text=$2 if [[ ! -f "$file" ]]; then fail "missing required file: ${file#$workspace/}" elif ! grep -Fq -- "$text" "$file"; then fail "${file#$workspace/} must contain: $text" 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' canonical_root="$workspace/specs/openspec" 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_file_contains "$schema_file" 'version: 2' 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 ! projection_validation=$(WEALTH_LEDGER_WORKSPACE="$workspace" bash "$script_dir/verify-agent-projection.sh" 2>&1); then fail "agent projection verification failed: $projection_validation" fi 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") done < <( find "$workspace" \ \( -path "$workspace/references" -o -path "$workspace/.git" -o -path '*/node_modules' -o -path '*/dist' -o -path '*/coverage' \) -prune -o \ -type d -name openspec -print | sort ) if [[ ${#openspec_roots[@]} -ne 1 || ${openspec_roots[0]:-} != "$canonical_root" ]]; then fail "expected exactly one project-owned OpenSpec root at specs/openspec" fi agent_files=(AGENTS.md frontend/AGENTS.md backend/AGENTS.md specs/AGENTS.md docs/AGENTS.md) for relative_path in "${agent_files[@]}"; do file="$workspace/$relative_path" require_file_contains "$file" 'wealth-ledger-specs' require_file_contains "$file" '--store wealth-ledger-specs' require_file_contains "$file" 'specs/openspec' done entrypoints=( .opencode/commands/opsx-apply.md .opencode/commands/opsx-archive.md .opencode/commands/opsx-continue.md .opencode/commands/opsx-explore.md .opencode/commands/opsx-new.md .opencode/commands/opsx-propose.md .opencode/commands/opsx-sync.md .opencode/commands/opsx-verify.md .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 "${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 |--store ""|when applicable' "$file"; then fail "$relative_path permits conditional OpenSpec store selection" fi if [[ -f "$file" ]]; then while IFS= read -r line; do if [[ $line =~ (^|[^[:alnum:]_])openspec[[:space:]]+(new[[:space:]]+change[[:space:]]+|status[[:space:]]+|instructions[[:space:]]+|list[[:space:]]+|show[[:space:]]+|validate[[:space:]]+|archive[[:space:]]+|doctor[[:space:]]+|context[[:space:]]+|schemas[[:space:]]+|view[[:space:]]+) ]] && [[ $line != *'--store wealth-ledger-specs'* ]]; then fail "$relative_path contains an unscoped OpenSpec invocation: $line" fi done < "$file" 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: \"$expected_version\"" 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 planning_entrypoints=( .opencode/commands/opsx-apply.md .opencode/commands/opsx-archive.md .opencode/commands/opsx-continue.md .opencode/commands/opsx-explore.md .opencode/commands/opsx-propose.md .opencode/commands/opsx-verify.md .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-propose/SKILL.md .opencode/skills/openspec-verify-change/SKILL.md ) for relative_path in "${planning_entrypoints[@]}"; do require_file_contains "$workspace/$relative_path" 'bash specs/scripts/verify-openspec-planning.sh' 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" fi done if [[ $failures -gt 0 ]]; then exit 1 fi printf 'OpenSpec authority verified: wealth-ledger-specs at specs/openspec\n'