fix(rpi): harden hook install and release audit gates

This commit is contained in:
Boden Fuller
2026-05-03 09:31:42 -04:00
parent 43bda4b98e
commit 3693fdde34
13 changed files with 548 additions and 80 deletions
+63 -25
View File
@@ -1,32 +1,70 @@
{
"schema_version": 1,
"run_id": "20260503T041538Z",
"objective": "Analyze Go code for consistency and architecture, then auto-fix mechanically justified maintainability issues for AI maintainers.",
"tracker_mode": "beads",
"epic_id": "soc-lb4e",
"complexity": "full",
"plan_path": ".agents/plans/2026-05-03-go-architecture-ai-maintainability.md",
"research_path": ".agents/research/2026-05-03-go-architecture-ai-maintainability.md",
"ranked_packet_path": ".agents/rpi/ranked-packet.json",
"test_levels": ["L0", "L1", "L2"],
"run_id": "discovery-2026-05-03-repo-operating-loop-hardening",
"objective": "Plan hardening for validation mutation, Codex hook install parity, .agents policy, canonical root cleanliness, release audit artifact scoping, and bd/Dolt boundedness.",
"epic_id": "soc-0qz1",
"plan_path": ".agents/plans/2026-05-03-repo-operating-loop-hardening.md",
"research_path": ".agents/research/2026-05-03-repo-operating-loop-hardening.md",
"design_path": ".agents/design/2026-05-03-design-repo-operating-loop-hardening.md",
"pre_mortem_path": ".agents/pre-mortem/2026-05-03-repo-operating-loop-hardening.md",
"contract_surfaces": [
"Go path containment",
"AgentOps state-path resolver",
"Go formatting",
"Go tests"
"docs/contracts/agents-write-surfaces.md",
"docs/contracts/repo-execution-profile.md",
"docs/contracts/repo-execution-profile.json",
"hooks/codex-hooks.json",
"docs/runbooks/bd-server-mode-closeout.md"
],
"done_criteria": [
"soc-lb4e child issues closed or updated",
"focused Go tests pass",
"gofmt reports no drift",
"state-path resolver coverage count decreases",
"changes committed and pushed"
"primary_issues": [
"soc-0qz1.2",
"soc-0qz1.8",
"soc-0qz1.4",
"soc-0qz1.6",
"soc-0qz1.3",
"soc-0qz1.5",
"soc-0qz1.7"
],
"related_existing_issues": [
"soc-eh1z",
"soc-ff2p.4",
"soc-owed.7"
],
"validation_commands": [
"cd cli && go test ./internal/daemon ./internal/paths ./internal/lifecycle ./internal/overnight",
"gofmt -l $(find cli -name '*.go' -not -path '*/vendor/*')",
"bash scripts/check-paths-resolver-coverage.sh",
"cd cli && go test ./...",
"cd cli && make build"
]
"bats tests/scripts/check-agents-write-surfaces.bats",
"bash scripts/check-agents-write-surfaces.sh",
"bash scripts/check-contract-compatibility.sh",
"bash tests/scripts/test-codex-plugin-install.sh",
"bats tests/hooks/hook-stdin-contracts.bats",
"bats tests/scripts/release-artifacts.bats",
"bash tests/scripts/test-worktree-disposition.sh",
"bash scripts/validate-bd-closeout-contract.sh",
"scripts/pre-push-gate.sh --fast --scope worktree"
],
"tracker_mode": "beads",
"tracker_health": {
"healthy": false,
"mode": "beads",
"reason": "bd commands worked but one create emitted JSON and then timed out under gtimeout; plan includes soc-0qz1.7 to bound this behavior."
},
"done_criteria": [
"Fast validation leaves tracked .agents knowledge stable by default.",
"Installed Codex hooks source their helpers correctly from plugin cache.",
"Repo-local .agents lifecycle classifications are documented and enforced.",
"Canonical root dirty state is classified into generated, tracked-policy, user/operator edit, or unknown categories.",
"Release audit validation scopes historical local artifacts without weakening official target-release checks.",
"bd/Dolt closeout commands have portable timeout guidance and preserve emitted JSON on timeout."
],
"complexity": "full",
"pre_mortem_verdict": "WARN",
"test_levels": {
"required": [
"L0",
"L1",
"L2"
],
"recommended": [
"L3"
],
"rationale": "The initiative spans shell gates, install packaging, hook execution, Git/worktree behavior, release artifacts, docs contracts, and tracker behavior. Installed-plugin and closeout flows should get system-level proof."
},
"discovery_timestamp": "2026-05-03T13:10:00Z"
}
@@ -0,0 +1,44 @@
---
id: phase-2-summary-2026-05-03-repo-operating-loop-hardening-crank
type: rpi-phase-summary
date: 2026-05-03
phase: implementation
epic: soc-0qz1
status: PARTIAL
---
# Phase 2 Summary: Repo Operating Loop Hardening Crank
RPI entered from `--from=crank` using epic `soc-0qz1` and the discovery packet
at `.agents/rpi/runs/discovery-2026-05-03-repo-operating-loop-hardening/execution-packet.json`.
## Completed Slices
| Issue | Result |
| --- | --- |
| `soc-0qz1.4` | Closed. Codex plugin install now preserves `../lib` helper layout for installed hooks, and `dangerous-git-guard.sh` parses force-push option tokens without blocking safe branch/ref names containing `-f`. |
| `soc-0qz1.6` | Closed. Release audit artifact validation now has `all`, `latest`, `changed`, and `target` modes; pre-push fast mode validates changed release audits instead of stale local latest artifacts. |
| `soc-0qz1.7` | Closed. bd/Dolt closeout runbook now includes a portable timeout wrapper and hung-after-output handling; the closeout contract validator enforces those docs. |
## Validation Run During Crank
- `bats tests/hooks/hook-stdin-contracts.bats`
- `bash tests/scripts/test-codex-plugin-install.sh`
- `bash -n hooks/dangerous-git-guard.sh scripts/install-codex-plugin.sh tests/scripts/test-codex-plugin-install.sh`
- `shellcheck --severity=error hooks/dangerous-git-guard.sh scripts/install-codex-plugin.sh tests/scripts/test-codex-plugin-install.sh`
- `bash scripts/validate-embedded-sync.sh`
- `bash scripts/validate-hook-preflight.sh`
- `bats tests/scripts/release-artifacts.bats`
- `bash -n scripts/validate-release-audit-artifacts.sh scripts/pre-push-gate.sh`
- `shellcheck --severity=error scripts/validate-release-audit-artifacts.sh scripts/pre-push-gate.sh`
- `bash scripts/validate-bd-closeout-contract.sh`
- `markdownlint docs/runbooks/bd-server-mode-closeout.md`
## Remaining Epic Work
- `soc-0qz1.2` `.agents` lifecycle policy.
- `soc-0qz1.3` hermetic/non-mutating validation.
- `soc-0qz1.5` canonical-root dirty-state classification.
- `soc-0qz1.8` repo execution profile validation lane metadata.
Crank verdict: `<promise>PARTIAL</promise>`.
+56 -34
View File
@@ -1,45 +1,67 @@
{
"schema_version": 1,
"goal": "Create bd issues for all actionable 2026-05-02 audit findings",
"generated_at": "2026-05-02T23:01:13-04:00",
"applied_findings": [
"generated_at": "2026-05-03T13:10:00Z",
"goal": "harden AgentOps repo operating loop determinism",
"epic_id": "soc-0qz1",
"ranked_items": [
{
"path": ".agents/findings/f-2026-04-25-001.md",
"reason": "Worktree disposition remains a release blocker after validation evidence passes."
"rank": 1,
"id": "soc-0qz1.4",
"title": "Fix Codex plugin hook packaging and dangerous-git parsing",
"why_now": "Concrete installed-runtime bug with direct push-workflow impact and limited file ownership."
},
{
"path": ".agents/findings/f-2026-05-02-005.md",
"reason": "Silent Edit revert should be tracked as upstream evidence filing, not repo-source repair."
"rank": 2,
"id": "soc-0qz1.6",
"title": "Scope release audit artifact validation to relevant artifacts",
"why_now": "Concrete false-blocking gate from local-only historical artifacts; narrow validator/test slice."
},
{
"path": ".agents/findings/f-2026-05-02-006.md",
"reason": "Semver tag filtering is a concrete release-helper bug."
"rank": 3,
"id": "soc-0qz1.2",
"title": "Define repo-local .agents mutability policy and write-surface contract",
"why_now": "Policy vocabulary needed before broad validation mutation changes."
},
{
"rank": 4,
"id": "soc-0qz1.8",
"title": "Codify repo execution profile no-mutation validation lanes",
"why_now": "Machine-readable lane metadata prevents future prompt-only policy drift."
},
{
"rank": 5,
"id": "soc-0qz1.3",
"title": "Make validation hermetic and non-mutating by default",
"why_now": "Central objective, but safest after policy and lane metadata are clear."
},
{
"rank": 6,
"id": "soc-0qz1.5",
"title": "Classify canonical root cleanliness and worktree disposition blockers",
"why_now": "Needs .agents policy vocabulary to classify root dirt without hiding policy edits."
},
{
"rank": 7,
"id": "soc-0qz1.7",
"title": "Bound bd and Dolt closeout command behavior",
"why_now": "Important automation reliability fix; can proceed independently once ownership is assigned."
}
],
"existing_tracker_coverage": [
"soc-0vm9",
"soc-sros",
"soc-0pzj",
"soc-owed"
],
"created_or_updated_beads": [
"soc-ff2p",
"soc-ff2p.2",
"soc-ff2p.3",
"soc-ff2p.4",
"soc-ff2p.5",
"soc-ff2p.6",
"soc-ff2p.7",
"soc-ff2p.8",
"soc-0pzj"
],
"new_issue_candidates": [
"Filter release tag discovery to semver release tags",
"Make tests/run-all.sh --all reliable under live runtime failures",
"Resolve release worktree disposition and .agents churn blocker",
"Isolate release validation from .agents/findings metadata churn",
"Review security toolchain warning before v2.40 tag",
"File upstream Claude Code silent Edit revert report",
"Decide historical changelog coverage floor"
"applied_items": [
{
"path": ".agents/patterns/2026-05-01-state-path-resolver.md",
"type": "pattern",
"reason": "All .agents-writing hooks and helpers should use canonical state path resolution."
},
{
"path": ".agents/research/2026-04-29-ci-tests-hooks-noise-audit.md",
"type": "research",
"reason": "Local validation needs behavior fixtures that catch real drift."
},
{
"path": ".agents/plans/2026-05-02-ai-native-zero-trust-release-process.md",
"type": "plan",
"reason": "Release artifact scoping must coordinate with the larger zero-trust release evidence epic."
}
]
}
+60 -7
View File
@@ -9,14 +9,70 @@ INPUT=$(cat)
# Source shared helpers for structured failure output (from plugin install dir, not repo root)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/../lib/hook-helpers.sh"
if [[ -f "$SCRIPT_DIR/../lib/hook-helpers.sh" ]]; then
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/../lib/hook-helpers.sh"
elif [[ -f "$SCRIPT_DIR/hook-helpers.sh" ]]; then
# Backward-compatible fallback for older plugin caches that copied helpers
# into hooks/ directly.
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/hook-helpers.sh"
else
echo "Warning: AgentOps hook helper missing; dangerous-git-guard skipped." >&2
exit 0
fi
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
ROOT="$(cd "$ROOT" 2>/dev/null && pwd -P 2>/dev/null || printf '%s' "$ROOT")"
extract_command() {
if command -v jq >/dev/null 2>&1; then
printf '%s' "$INPUT" | jq -r '.tool_input.command // .command // empty' 2>/dev/null && return 0
fi
printf '%s' "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//'
}
git_push_uses_force() {
local command="$1"
local token
local saw_git=0
local saw_push=0
local -a tokens
# This guard only needs enough tokenization to distinguish option tokens from
# branch/ref names. Quoted whitespace in ref names is outside normal git use.
read -r -a tokens <<< "$command"
for token in "${tokens[@]}"; do
token="${token#\'}"
token="${token%\'}"
token="${token#\"}"
token="${token%\"}"
if [[ "$saw_git" -eq 0 ]]; then
[[ "$token" == "git" || "$token" == */git ]] && saw_git=1
continue
fi
if [[ "$saw_push" -eq 0 ]]; then
[[ "$token" == "push" ]] && saw_push=1
continue
fi
[[ "$token" == "--" ]] && return 1
case "$token" in
--force-with-lease|--force-with-lease=*) ;;
--force|--force=*) return 0 ;;
-[!-]*)
[[ "$token" == *f* ]] && return 0
;;
esac
done
return 1
}
# Extract tool_input.command from JSON
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//')
COMMAND="$(extract_command)"
# Hot path: no git, no problem
echo "$COMMAND" | grep -q "git" || exit 0
@@ -29,11 +85,8 @@ if echo "$COMMAND" | grep -qE 'git\s+commit' && git diff --cached --name-only 2>
echo "Warning: .agents/ files are staged for commit. Consider: git reset HEAD .agents/" >&2
fi
# Allow-list (checked before block-list)
echo "$COMMAND" | grep -qE 'push.*--force-with-lease' && exit 0
# Block-list with safe alternatives
if echo "$COMMAND" | grep -qE 'push\s+.*(-f|--force)'; then
if git_push_uses_force "$COMMAND"; then
write_failure "dangerous_git" "git push --force" 2 "force push blocked"
echo "Blocked: force push. Use --force-with-lease instead." >&2
exit 2
+41 -5
View File
@@ -30,13 +30,32 @@ closeout look uniform.
## Standard Closeout
Use a bounded wrapper for every bd/Dolt closeout command. On macOS this repo
expects GNU `gtimeout` from coreutils; on Linux, `timeout` is usually present.
```bash
bd_timeout() {
local seconds="${1:-8}"
shift
if command -v gtimeout >/dev/null 2>&1; then
gtimeout "${seconds}s" "$@"
return $?
fi
if command -v timeout >/dev/null 2>&1; then
timeout "${seconds}s" "$@"
return $?
fi
"$@"
}
```
Run this after any `bd update`, `bd close`, dependency edit, or issue creation:
```bash
bd vc status
bd dolt commit -m "tracker: <summary>" # if tracker changes are pending
bd dolt remote list
bd dolt push # only when a real remote is configured
bd_timeout 8 bd vc status
bd_timeout 8 bd dolt commit -m "tracker: <summary>" # if tracker changes are pending
bd_timeout 8 bd dolt remote list
bd_timeout 8 bd dolt push # only when a real remote is configured
```
Then complete the repo closeout:
@@ -50,6 +69,23 @@ git status
`git push` remains mandatory for repository changes. `bd dolt push` is
conditional on a real remote.
## Command Timeout Outcomes
If a bd command times out before producing output, treat it as failed and retry
once after checking `bd vc status`. If a bd write command emits valid JSON and
then times out, treat the result as indeterminate-success:
1. Preserve the emitted stdout in the session notes.
2. Re-read the emitted issue ID with `bd_timeout 8 bd show <id> --json`.
3. If the readback matches the requested state, continue and record
"bd command timed out after emitting JSON; readback confirmed".
4. If readback fails or shows stale state, retry once, then file a linked
follow-up issue with the command, timeout, stdout, and stderr.
If `bd dolt remote list` prints "No remotes configured." and then times out,
record "bd Dolt remote list timed out after no-remote output", skip `bd dolt
push`, and continue to the mandatory Git push.
## No Remote Is Configured
If `bd dolt push` reports that no remote is configured:
@@ -68,7 +104,7 @@ failure without improving backup or team sync.
If `bd show` or `bd ready` reads stale issues, missing issues, or an unexpected
database:
1. Run `bd vc status`.
1. Run `bd_timeout 8 bd vc status`.
2. Inspect the active database and project id from the workspace metadata or
bd diagnostic output.
3. Verify the served database exists before mutating tracker state.
+60 -7
View File
@@ -9,14 +9,70 @@ INPUT=$(cat)
# Source shared helpers for structured failure output (from plugin install dir, not repo root)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/../lib/hook-helpers.sh"
if [[ -f "$SCRIPT_DIR/../lib/hook-helpers.sh" ]]; then
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/../lib/hook-helpers.sh"
elif [[ -f "$SCRIPT_DIR/hook-helpers.sh" ]]; then
# Backward-compatible fallback for older plugin caches that copied helpers
# into hooks/ directly.
# shellcheck source=../lib/hook-helpers.sh
. "$SCRIPT_DIR/hook-helpers.sh"
else
echo "Warning: AgentOps hook helper missing; dangerous-git-guard skipped." >&2
exit 0
fi
ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
ROOT="$(cd "$ROOT" 2>/dev/null && pwd -P 2>/dev/null || printf '%s' "$ROOT")"
extract_command() {
if command -v jq >/dev/null 2>&1; then
printf '%s' "$INPUT" | jq -r '.tool_input.command // .command // empty' 2>/dev/null && return 0
fi
printf '%s' "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//'
}
git_push_uses_force() {
local command="$1"
local token
local saw_git=0
local saw_push=0
local -a tokens
# This guard only needs enough tokenization to distinguish option tokens from
# branch/ref names. Quoted whitespace in ref names is outside normal git use.
read -r -a tokens <<< "$command"
for token in "${tokens[@]}"; do
token="${token#\'}"
token="${token%\'}"
token="${token#\"}"
token="${token%\"}"
if [[ "$saw_git" -eq 0 ]]; then
[[ "$token" == "git" || "$token" == */git ]] && saw_git=1
continue
fi
if [[ "$saw_push" -eq 0 ]]; then
[[ "$token" == "push" ]] && saw_push=1
continue
fi
[[ "$token" == "--" ]] && return 1
case "$token" in
--force-with-lease|--force-with-lease=*) ;;
--force|--force=*) return 0 ;;
-[!-]*)
[[ "$token" == *f* ]] && return 0
;;
esac
done
return 1
}
# Extract tool_input.command from JSON
COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/^"command"[[:space:]]*:[[:space:]]*"//;s/"$//')
COMMAND="$(extract_command)"
# Hot path: no git, no problem
echo "$COMMAND" | grep -q "git" || exit 0
@@ -29,11 +85,8 @@ if echo "$COMMAND" | grep -qE 'git\s+commit' && git diff --cached --name-only 2>
echo "Warning: .agents/ files are staged for commit. Consider: git reset HEAD .agents/" >&2
fi
# Allow-list (checked before block-list)
echo "$COMMAND" | grep -qE 'push.*--force-with-lease' && exit 0
# Block-list with safe alternatives
if echo "$COMMAND" | grep -qE 'push\s+.*(-f|--force)'; then
if git_push_uses_force "$COMMAND"; then
write_failure "dangerous_git" "git push --force" 2 "force push blocked"
echo "Blocked: force push. Use --force-with-lease instead." >&2
exit 2
+4
View File
@@ -464,6 +464,10 @@ if [[ -f "${HOOKS_SRC}/codex-hooks.json" ]]; then
done
# Copy shared helpers
if [[ -d "${REPO_ROOT}/lib" ]]; then
PLUGIN_LIB_DST="${PLUGIN_CACHE_ROOT}/lib"
mkdir -p "$PLUGIN_LIB_DST"
cp "${REPO_ROOT}/lib/"*.sh "$PLUGIN_LIB_DST/" 2>/dev/null || true
# Compatibility for older hooks that sourced helpers from hooks/ directly.
cp "${REPO_ROOT}/lib/"*.sh "$HOOKS_DST/" 2>/dev/null || true
fi
+1 -1
View File
@@ -1010,7 +1010,7 @@ fi
# --- 25b. Release audit artifact refs ---
if needs_release_audit_artifact_check; then
if [[ -x scripts/validate-release-audit-artifacts.sh ]]; then
if release_audit_artifacts_output="$(scripts/validate-release-audit-artifacts.sh 2>&1)"; then
if release_audit_artifacts_output="$(RELEASE_AUDIT_CHANGED_PATHS="$(changed_paths)" scripts/validate-release-audit-artifacts.sh --mode changed 2>&1)"; then
pass "release audit artifacts"
else
fail "release audit artifacts"
+4
View File
@@ -48,6 +48,10 @@ if require_file "docs/runbooks/bd-server-mode-closeout.md"; then
|| fail "runbook must include bd dolt remote list"
grep -q 'no remote is configured' "$repo_root/docs/runbooks/bd-server-mode-closeout.md" \
|| fail "runbook must explain the no-remote case"
grep -q 'bd_timeout' "$repo_root/docs/runbooks/bd-server-mode-closeout.md" \
|| fail "runbook must include the bd_timeout bounded-command wrapper"
grep -q 'timed out after emitting JSON' "$repo_root/docs/runbooks/bd-server-mode-closeout.md" \
|| fail "runbook must explain hung-after-output bd write behavior"
fi
if require_file "docs/documentation-index.md"; then
+93 -1
View File
@@ -3,6 +3,59 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
MODE="all"
TARGET_RELEASE=""
usage() {
cat <<'EOF'
Usage: scripts/validate-release-audit-artifacts.sh [--mode all|latest|changed|target] [--target-release V]
Modes:
all Validate all audits; missing non-latest local artifact dirs are skipped.
latest Validate only the latest audit and require its artifacts.
changed Validate only audit files listed in RELEASE_AUDIT_CHANGED_PATHS.
target Validate the audit for --target-release and require its artifacts.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
MODE="${2:-}"
shift 2
;;
--target-release)
TARGET_RELEASE="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
case "$MODE" in
all|latest|changed|target) ;;
*)
echo "Invalid --mode: $MODE" >&2
usage >&2
exit 2
;;
esac
if [[ "$MODE" == "target" ]]; then
if [[ -z "$TARGET_RELEASE" ]]; then
echo "--target-release is required with --mode target" >&2
exit 2
fi
TARGET_RELEASE="${TARGET_RELEASE#v}"
fi
extract_version() {
local audit="$1"
@@ -137,17 +190,45 @@ if (( ${#audit_files[@]} > 0 )); then
latest_audit="${audit_files[$((${#audit_files[@]} - 1))]}"
fi
audit_selected() {
local audit="$1"
local version="$2"
local rel_path
rel_path="${audit#"$REPO_ROOT"/}"
case "$MODE" in
all)
return 0
;;
latest)
[[ "$audit" == "$latest_audit" ]]
;;
target)
[[ "$version" == "$TARGET_RELEASE" ]]
;;
changed)
printf '%s\n' "${RELEASE_AUDIT_CHANGED_PATHS:-}" | grep -Fxq "$rel_path"
;;
esac
}
failures=()
selected_count=0
for audit in "${audit_files[@]}"; do
version="$(extract_version "$audit")"
[[ -n "$version" ]] || continue
if ! audit_selected "$audit" "$version"; then
continue
fi
selected_count=$((selected_count + 1))
artifact_dir="$(extract_artifact_dir "$audit")"
[[ -n "$artifact_dir" ]] || continue
artifact_dir="${artifact_dir%/}"
manifest="$REPO_ROOT/$artifact_dir/release-artifacts.json"
if [[ ! -f "$manifest" && ! -d "$REPO_ROOT/$artifact_dir" && "$audit" != "$latest_audit" ]]; then
if [[ "$MODE" == "all" && ! -f "$manifest" && ! -d "$REPO_ROOT/$artifact_dir" && "$audit" != "$latest_audit" ]]; then
continue
fi
@@ -164,6 +245,17 @@ for audit in "${audit_files[@]}"; do
fi
done
if [[ "$MODE" == "changed" && "$selected_count" -eq 0 ]]; then
echo "release audit artifact validation passed: no changed release audits to validate."
exit 0
fi
if [[ "$MODE" == "target" && "$selected_count" -eq 0 ]]; then
printf 'release audit artifact validation failed:\n' >&2
printf ' - no release audit found for target release %s\n' "$TARGET_RELEASE" >&2
exit 1
fi
if (( ${#failures[@]} > 0 )); then
printf 'release audit artifact validation failed:\n' >&2
printf ' - %s\n' "${failures[@]}" >&2
+18
View File
@@ -170,6 +170,24 @@ AOEOF
[ "$status" -eq 0 ]
}
@test "dangerous-git-guard: branch names containing -f are allowed" {
run bash -c 'cd "$1" && printf "%s" "$2" | bash "$3" 2>&1' \
-- "$MOCK_REPO" '{"tool_input":{"command":"git push origin feature-fix"}}' "$HOOKS_DIR/dangerous-git-guard.sh"
[ "$status" -eq 0 ]
}
@test "dangerous-git-guard: ref names containing -f are allowed" {
run bash -c 'cd "$1" && printf "%s" "$2" | bash "$3" 2>&1' \
-- "$MOCK_REPO" '{"tool_input":{"command":"git push origin refs/heads/fix-forward"}}' "$HOOKS_DIR/dangerous-git-guard.sh"
[ "$status" -eq 0 ]
}
@test "dangerous-git-guard: force after remote is blocked" {
run bash -c 'cd "$1" && printf "%s" "$2" | bash "$3" 2>&1' \
-- "$MOCK_REPO" '{"tool_input":{"command":"git push origin -f main"}}' "$HOOKS_DIR/dangerous-git-guard.sh"
[ "$status" -eq 2 ]
}
@test "dangerous-git-guard: hard reset blocked" {
run bash -c 'cd "$1" && printf "%s" "$2" | bash "$3" 2>&1' \
-- "$MOCK_REPO" '{"tool_input":{"command":"git reset --hard HEAD~1"}}' "$HOOKS_DIR/dangerous-git-guard.sh"
+26
View File
@@ -203,3 +203,29 @@ EOF
[ "$status" -eq 0 ]
[[ "$output" == *"release audit artifact validation passed"* ]]
}
@test "validate-release-audit-artifacts changed mode skips missing latest when no audit changed" {
write_audit "2.40.0" "20260427T020612Z" "2026-05-03"
run env RELEASE_AUDIT_CHANGED_PATHS="scripts/pre-push-gate.sh" \
"$FAKE_REPO/scripts/validate-release-audit-artifacts.sh" --mode changed
[ "$status" -eq 0 ]
[[ "$output" == *"no changed release audits to validate"* ]]
}
@test "validate-release-audit-artifacts changed mode rejects changed audit with missing artifacts" {
write_audit "2.40.0" "20260427T020612Z" "2026-05-03"
run env RELEASE_AUDIT_CHANGED_PATHS="docs/releases/2026-05-03-v2.40.0-audit.md" \
"$FAKE_REPO/scripts/validate-release-audit-artifacts.sh" --mode changed
[ "$status" -eq 1 ]
[[ "$output" == *"no release-artifacts.json and no complete versioned artifact fallback"* ]]
}
@test "validate-release-audit-artifacts target mode remains strict for missing target artifacts" {
write_audit "2.40.0" "20260427T020612Z" "2026-05-03"
run "$FAKE_REPO/scripts/validate-release-audit-artifacts.sh" --mode target --target-release 2.40.0
[ "$status" -eq 1 ]
[[ "$output" == *"no release-artifacts.json and no complete versioned artifact fallback"* ]]
}
@@ -23,6 +23,8 @@ setup_fixture() {
mkdir -p \
"$fixture/.codex-plugin" \
"$fixture/hooks" \
"$fixture/lib" \
"$fixture/plugins" \
"$fixture/skills-codex/research" \
"$fixture/skills-codex/heal-skill/scripts"
@@ -47,6 +49,31 @@ EOF
}
]
}
EOF
/bin/cp "$ROOT/hooks/dangerous-git-guard.sh" "$fixture/hooks/dangerous-git-guard.sh"
/bin/cp "$ROOT/lib/hook-helpers.sh" "$fixture/lib/hook-helpers.sh"
/bin/cp "$ROOT/lib/ao-paths.sh" "$fixture/lib/ao-paths.sh"
/bin/cp "$ROOT/lib/chain-parser.sh" "$fixture/lib/chain-parser.sh"
cat > "$fixture/hooks/codex-hooks.json" <<'EOF'
{
"$schema": "../schemas/hooks-manifest.v1.schema.json",
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ${AGENTOPS_PLUGIN_ROOT:-~/.codex/plugins/cache/agentops}/hooks/dangerous-git-guard.sh",
"timeout": 5
}
]
}
]
}
}
EOF
cat > "$fixture/skills-codex/research/SKILL.md" <<'EOF'
@@ -181,6 +208,56 @@ test_installs_plugin_cache_and_config() {
fi
}
test_installs_codex_hooks_with_lib_layout() {
local fixture="$TMP_DIR/hooks-layout"
local codex_home="$TMP_DIR/hooks-home"
local plugin_root="$codex_home/plugins/cache/agentops-marketplace/agentops/local"
local mock_repo="$TMP_DIR/hooks-mock-repo"
setup_fixture "$fixture"
if ! run_install "$fixture" "$codex_home"; then
fail "install with Codex hooks should succeed"
return
fi
[[ -x "$plugin_root/hooks/dangerous-git-guard.sh" ]] || {
fail "dangerous-git-guard copied into plugin hooks cache"
return
}
[[ -f "$plugin_root/lib/hook-helpers.sh" ]] || {
fail "hook helpers copied into plugin lib cache"
return
}
[[ -f "$plugin_root/lib/ao-paths.sh" ]] || {
fail "ao-paths copied into plugin lib cache"
return
}
mkdir -p "$mock_repo"
git -C "$mock_repo" init -q >/dev/null 2>&1
if ! (
cd "$mock_repo" &&
printf '%s' '{"tool_input":{"command":"git push origin feature-fix"}}' |
bash "$plugin_root/hooks/dangerous-git-guard.sh" >/dev/null
); then
fail "installed dangerous-git-guard allows safe branch names containing -f"
return
fi
if (
cd "$mock_repo" &&
printf '%s' '{"tool_input":{"command":"git push -f origin main"}}' |
bash "$plugin_root/hooks/dangerous-git-guard.sh" >/dev/null 2>&1
); then
fail "installed dangerous-git-guard should block real force push"
return
fi
pass "installs Codex hooks with usable ../lib helper layout"
}
test_archives_agentops_codex_home_skills_without_touching_custom_skills() {
local fixture="$TMP_DIR/archive"
local codex_home="$TMP_DIR/archive-home"
@@ -255,6 +332,7 @@ EOF
echo "== test-codex-plugin-install =="
test_installs_plugin_cache_and_config
test_installs_codex_hooks_with_lib_layout
test_archives_agentops_codex_home_skills_without_touching_custom_skills
test_archives_home_agents_agentops_skills_without_touching_custom_skills