docs(specs): harden OpenSpec authority lifecycle
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
#!/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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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'
|
||||
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
|
||||
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
|
||||
|
||||
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" '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
|
||||
|
||||
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'
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
verifier="$script_dir/verify-openspec-authority.sh"
|
||||
|
||||
if [[ ! -f "$verifier" ]]; then
|
||||
echo "expected verifier at $verifier" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fixture=$(mktemp -d)
|
||||
trap 'rm -rf "$fixture"' EXIT
|
||||
|
||||
write_file() {
|
||||
local relative_path=$1
|
||||
local content=$2
|
||||
mkdir -p "$fixture/$(dirname "$relative_path")"
|
||||
printf '%s\n' "$content" > "$fixture/$relative_path"
|
||||
}
|
||||
|
||||
write_file "specs/.openspec-store/store.yaml" $'version: 1\nid: wealth-ledger-specs'
|
||||
mkdir -p "$fixture/specs/openspec"
|
||||
|
||||
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.'
|
||||
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 path in "${entrypoints[@]}"; do
|
||||
write_file "$path" 'openspec status --store wealth-ledger-specs'
|
||||
done
|
||||
|
||||
archive_gate='openspec status --store wealth-ledger-specs
|
||||
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"
|
||||
|
||||
run_verifier() {
|
||||
WEALTH_LEDGER_WORKSPACE="$fixture" bash "$verifier"
|
||||
}
|
||||
|
||||
run_verifier
|
||||
|
||||
mkdir -p "$fixture/frontend/openspec"
|
||||
if run_verifier; then
|
||||
echo "expected additional OpenSpec root to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
rmdir "$fixture/frontend/openspec"
|
||||
|
||||
write_file ".opencode/commands/opsx-apply.md" 'Without 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" $'openspec 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" $'openspec 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" $'openspec 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-archive.md" $'openspec 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"
|
||||
|
||||
mkdir -p "$fixture/docs/superpowers/plans"
|
||||
if run_verifier; then
|
||||
echo "expected prohibited planning location to fail" >&2
|
||||
exit 1
|
||||
fi
|
||||
rmdir "$fixture/docs/superpowers/plans"
|
||||
rmdir "$fixture/docs/superpowers"
|
||||
|
||||
mkdir -p "$fixture/references/example/superpowers/plans"
|
||||
run_verifier
|
||||
|
||||
echo "verify-openspec-authority tests passed"
|
||||
Reference in New Issue
Block a user