mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
fix(rpi): harden hook install and release audit gates
This commit is contained in:
@@ -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>`.
|
||||
@@ -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."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user