mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
feat(push): converge release contract (age-h433.53)
This commit is contained in:
@@ -814,7 +814,7 @@
|
||||
"name": "push",
|
||||
"source_skill": "skills/push",
|
||||
"source_hash": "f28fcd367aac3f9806cf4504a94ef7f4eef497bd79ad1fbe762811a48fffc977",
|
||||
"generated_hash": "e6f1ba97d779ef0473c61fca4821d0824f5858665592be23d07cfbc43212cbb5"
|
||||
"generated_hash": "e824258a766878e78597f886182aad249a56c0c8425c8834c35d015f710638da"
|
||||
},
|
||||
{
|
||||
"name": "rch",
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"source_skill": "skills/push",
|
||||
"layout": "modular",
|
||||
"source_hash": "f28fcd367aac3f9806cf4504a94ef7f4eef497bd79ad1fbe762811a48fffc977",
|
||||
"generated_hash": "e6f1ba97d779ef0473c61fca4821d0824f5858665592be23d07cfbc43212cbb5"
|
||||
"generated_hash": "e824258a766878e78597f886182aad249a56c0c8425c8834c35d015f710638da"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,21 @@ description: "Validate, commit, and push."
|
||||
|
||||
Ship a change to `main` **in this repo** with proof. AgentOps lands work by **direct push to main** — PR-per-change is retired here. The release authority is the **local cockpit gate** plus a **CONFIRMED cross-family pawl verdict** bound to the commit.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No verdict means no push.** Require a CONFIRMED commit-bound pawl verdict because green producer tests cannot independently authorize release.
|
||||
- **Keep reviewed identity explicit.** Commit every repair and re-earn the verdict before landing because a verdict for an earlier feature SHA proves a different artifact; after landing, distinguish the reviewed feature from an optional canonical provenance-bind tip.
|
||||
- **Contain the write surface.** Stage only bead-owned files and exclude credentials/private ledgers because direct-main landing makes accidental inclusion immediately public.
|
||||
- **Consult the pawl before raising the andon.** WARN, FAIL, or REFUTED repairs and reruns automatically because ordinary rejection is useful evidence, not a breaker; only a breaker may enter HOLD or consume one helper.
|
||||
|
||||
## Breaker State Machine
|
||||
|
||||
- **Ordinary rejection — `WARN|FAIL|REFUTED -> AUTO-REDO`:** repair, recommit, and rerun gate plus pawl; plain rejection never enters HOLD and never consumes the helper lane.
|
||||
- **Breaker — `BREAKER -> HOLD -> ONE-HELPER`:** pause landing in HOLD and route exactly one bounded helper consultation.
|
||||
- **Recovered — `HELPER-UNSTUCK -> AUTO-REDO`:** leave HOLD, resume repair, and re-earn gate plus independent verdict before landing.
|
||||
- **Helper escalation — `HELPER-ESCALATE -> HUMAN`:** stop automation and surface the helper's escalation to the human operator.
|
||||
- **Direct human lane — `REFUSAL-LANE|EXPLICIT-JUDGMENT|EXHAUSTED-BUDGET -> HUMAN`:** stop automation and route directly to the human operator with the helper skipped.
|
||||
|
||||
## The invariant — no verdict = no push
|
||||
|
||||
**Never push without a CONFIRMED, commit-bound pawl verdict.** This replaces the old "never push to main without permission" guardrail: direct-main IS the routine path for THIS repo, and the pawl verdict IS the permission. A push carrying no CONFIRMED verdict is refused by the pre-push hook (`scripts/check-pawl-pre-push.sh`); the ONLY waiver is a `#trivial` docs/provenance-only commit. **No verdict = not done.** (PR flow survives ONLY for external repos — see below.)
|
||||
@@ -43,6 +58,8 @@ Dispatches the refuter (fresh-context, read-only, verdict-only) against the HEAD
|
||||
|
||||
### Step 4: Land — deterministic single-shot push
|
||||
|
||||
**Checkpoint:** confirm HEAD cites the bead, the fast gate passed on that exact HEAD, and the CONFIRMED verdict is commit-bound before invoking the land command. After landing, run the landed verifier so a canonical provenance-bind tip is separated from its reviewed feature parent without trusting either a marker or path alone.
|
||||
|
||||
```bash
|
||||
bash scripts/pawl-land.sh <bead>
|
||||
```
|
||||
@@ -51,7 +68,15 @@ Fetch + rebase onto current `origin/main`, restamp the CONFIRMED verdict onto th
|
||||
|
||||
### Step 5: Report
|
||||
|
||||
Files changed, suites run, verdict disposition + bound SHA, and the landed tip.
|
||||
Run `bash skills-codex/push/scripts/verify-landed.sh <bead>`, then report files changed, suites run, verdict disposition, reviewed SHA, and landed tip. The verifier fetches `main`, proves local/remote tip identity, recognizes `HEAD^` only for the exact canonical auto-bind subject plus its non-empty provenance-ledger-only diff, requires the reviewed commit to cite the bead, and checks the real pawl verdict against that reviewed SHA.
|
||||
|
||||
## Output Specification
|
||||
|
||||
- **Path:** remote `origin` ref `refs/heads/main`, with proof at `.agents/pawl-verdicts/<bead>.json`.
|
||||
- **Filename convention:** `<bead>.json` for the pawl verdict bound to the reviewed commit contained by the pushed ref.
|
||||
- **Serialization/schema format:** landed-tip and reviewed-commit Git SHAs plus the JSON pawl-verdict schema bound to the reviewed SHA.
|
||||
- **Validator command:** set `BEAD="<bead-id>"`, then run `bash skills-codex/push/scripts/verify-landed.sh "$BEAD"`; do not replace this with inline `HEAD`/`HEAD^` guessing.
|
||||
- **Downstream handoff:** send both verifier-reported `tip=<sha>` and `reviewed=<sha>`, verdict path, suites run, and remote-ref proof to closeout; close the tracker only after this helper succeeds.
|
||||
|
||||
## External repos (PR flow only)
|
||||
|
||||
@@ -65,6 +90,13 @@ The PR-per-change flow survives **only for external repos** (upstream forks) whe
|
||||
- On a rebase conflict, do NOT force-push — `pawl-land.sh` aborts; resolve locally and re-gate.
|
||||
- Do NOT run `ao codex stop` after the remote push. If session closeout is needed, finish it through `$validate`, `$post-mortem`, or `$handoff` before entering `$push`.
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
- The commit subject cites the bead, the diff contains only owned files, and sensitive/private paths are absent from the index.
|
||||
- Deterministic tests and `ao gate check --fast --scope head` pass on the exact commit reviewed by the pawl.
|
||||
- The verdict is CONFIRMED, independent, cross-family where required, and bound to the verifier-resolved reviewed SHA contained by the landed tip.
|
||||
- The landed verifier proves `HEAD == origin/main` and reports both tip and reviewed SHAs; ordinary rejection stays in AUTO-REDO and only the breaker state machine reaches HOLD or HUMAN.
|
||||
|
||||
## Local Resources
|
||||
|
||||
### scripts/
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERIFY="$SKILL_DIR/scripts/verify-landed.sh"
|
||||
REPO_ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
|
||||
PAWL="$REPO_ROOT/scripts/pawl-verdict.sh"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
new_fixture() {
|
||||
local name="$1"
|
||||
FIXTURE="$TMP/$name"
|
||||
REMOTE="$TMP/$name.git"
|
||||
git init --bare --quiet "$REMOTE"
|
||||
git init --quiet "$FIXTURE"
|
||||
git -C "$FIXTURE" config user.email test@example.com
|
||||
git -C "$FIXTURE" config user.name Test
|
||||
git -C "$FIXTURE" remote add origin "$REMOTE"
|
||||
mkdir -p "$FIXTURE/scripts/lib" "$FIXTURE/schemas" "$FIXTURE/.agents/pawl-verdicts"
|
||||
cp "$PAWL" "$FIXTURE/scripts/pawl-verdict.sh"
|
||||
cp "$REPO_ROOT/scripts/lib/diff-identity.sh" "$FIXTURE/scripts/lib/diff-identity.sh"
|
||||
cp "$REPO_ROOT/schemas/pawl-verdict.v1.schema.json" "$FIXTURE/schemas/pawl-verdict.v1.schema.json"
|
||||
echo init >"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md scripts schemas
|
||||
git -C "$FIXTURE" commit --quiet -m "chore: fixture init"
|
||||
git -C "$FIXTURE" branch -M main
|
||||
}
|
||||
|
||||
commit_feature() {
|
||||
local bead="$1"
|
||||
echo "$bead" >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "feat(test): exercise landed verifier ($bead)"
|
||||
FEATURE_SHA="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
}
|
||||
|
||||
seed_verdict() {
|
||||
local bead="$1" sha="$2"
|
||||
local evidence="$FIXTURE/evidence-$bead.txt"
|
||||
printf 'fresh-context deterministic fixture evidence\n' >"$evidence"
|
||||
(cd "$FIXTURE" && AGENTOPS_REPO_ROOT="$FIXTURE" PAWL_AUTOBIND=0 \
|
||||
bash "$FIXTURE/scripts/pawl-verdict.sh" write "$bead" 0 \
|
||||
--disposition CONFIRMED --head "$sha" --author-context author-ctx \
|
||||
--refuter "claude:CONFIRMED:fresh-reviewer-ctx:$evidence" \
|
||||
--dir "$FIXTURE/.agents/pawl-verdicts" >/dev/null 2>&1)
|
||||
}
|
||||
|
||||
push_tip() {
|
||||
git -C "$FIXTURE" push --quiet -u origin HEAD:main
|
||||
}
|
||||
|
||||
expect_pass() {
|
||||
local bead="$1" expected_reviewed="$2" out
|
||||
out="$(bash "$VERIFY" "$bead" --repo "$FIXTURE")" || fail "$bead unexpectedly failed"
|
||||
[[ "$out" == *"tip=$(git -C "$FIXTURE" rev-parse HEAD)"* ]] || fail "$bead omitted tip SHA"
|
||||
[[ "$out" == *"reviewed=$expected_reviewed"* ]] || fail "$bead resolved the wrong reviewed SHA: $out"
|
||||
}
|
||||
|
||||
expect_fail() {
|
||||
local bead="$1" label="$2"
|
||||
if bash "$VERIFY" "$bead" --repo "$FIXTURE" >"$TMP/out" 2>&1; then
|
||||
fail "$label unexpectedly passed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Normal feature tip: review and remote identity bind directly to HEAD.
|
||||
new_fixture normal
|
||||
commit_feature age-push-normal
|
||||
seed_verdict age-push-normal "$FEATURE_SHA"
|
||||
push_tip
|
||||
expect_pass age-push-normal "$FEATURE_SHA"
|
||||
|
||||
# Valid canonical auto-bind tip: remote is bind, while pawl binds the feat parent.
|
||||
new_fixture bind
|
||||
commit_feature age-push-bind
|
||||
seed_verdict age-push-bind "$FEATURE_SHA"
|
||||
mkdir -p "$FIXTURE/docs/provenance"
|
||||
printf '{"edge":"bind"}\n' >"$FIXTURE/docs/provenance/ledger.jsonl"
|
||||
git -C "$FIXTURE" add docs/provenance/ledger.jsonl
|
||||
git -C "$FIXTURE" commit --quiet -m "chore(provenance): bind pawl CONFIRMED verdict for age-push-bind #trivial"
|
||||
push_tip
|
||||
expect_pass age-push-bind "$FEATURE_SHA"
|
||||
|
||||
# Marker-only is not a bind: code in the tip keeps reviewed=HEAD and the parent
|
||||
# verdict must not authorize it.
|
||||
new_fixture marker-only
|
||||
commit_feature age-push-marker
|
||||
seed_verdict age-push-marker "$FEATURE_SHA"
|
||||
echo code >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "chore(provenance): bind pawl CONFIRMED verdict for age-push-marker #trivial"
|
||||
push_tip
|
||||
expect_fail age-push-marker "marker-only invalid tip"
|
||||
MARKER_TIP="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
grep -Fq -- "reviewed=$MARKER_TIP" "$TMP/out" || fail "marker-only tip was misclassified as an auto-bind"
|
||||
|
||||
# Provenance-only without the marker is not misclassified as an auto-bind.
|
||||
new_fixture provenance-only
|
||||
commit_feature age-push-provenance
|
||||
seed_verdict age-push-provenance "$FEATURE_SHA"
|
||||
mkdir -p "$FIXTURE/docs/provenance"
|
||||
printf '{"edge":"ordinary-doc"}\n' >"$FIXTURE/docs/provenance/ledger.jsonl"
|
||||
git -C "$FIXTURE" add docs/provenance/ledger.jsonl
|
||||
git -C "$FIXTURE" commit --quiet -m "docs(provenance): document age-push-provenance"
|
||||
PROVENANCE_TIP="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
push_tip
|
||||
expect_fail age-push-provenance "provenance-only no-marker tip"
|
||||
grep -Fq -- "reviewed=$PROVENANCE_TIP" "$TMP/out" || fail "provenance-only tip was misclassified as an auto-bind"
|
||||
|
||||
# A local tip not equal to freshly fetched origin/main fails before verdict use.
|
||||
new_fixture stale
|
||||
commit_feature age-push-stale
|
||||
seed_verdict age-push-stale "$FEATURE_SHA"
|
||||
push_tip
|
||||
echo local >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "fix(test): local-only age-push-stale"
|
||||
expect_fail age-push-stale "stale remote"
|
||||
grep -Fq "stale remote identity" "$TMP/out" || fail "stale remote failure was not explicit"
|
||||
|
||||
# A verdict cannot compensate for the reviewed commit citing the wrong bead.
|
||||
new_fixture wrong-bead
|
||||
commit_feature age-push-other
|
||||
seed_verdict age-push-wrong-bead "$FEATURE_SHA"
|
||||
push_tip
|
||||
expect_fail age-push-wrong-bead "wrong bead citation"
|
||||
grep -Fq "does not cite bead age-push-wrong-bead" "$TMP/out" || fail "wrong bead citation was not explicit"
|
||||
|
||||
# A verdict for the requested bead but an older SHA fails the real pawl check.
|
||||
new_fixture wrong-head
|
||||
commit_feature age-push-wrong-head
|
||||
seed_verdict age-push-wrong-head "$(git -C "$FIXTURE" rev-parse HEAD^)"
|
||||
push_tip
|
||||
expect_fail age-push-wrong-head "wrong verdict head"
|
||||
grep -Fq "does not authorize reviewed=$FEATURE_SHA" "$TMP/out" || fail "wrong verdict head was not explicit"
|
||||
|
||||
echo "OK: verify-landed fixtures"
|
||||
@@ -1,7 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SKILL="$SKILL_DIR/SKILL.md"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# Backticks and command substitutions are exact Markdown markers, never shell execution.
|
||||
# shellcheck disable=SC2016
|
||||
MARKERS=(
|
||||
'## Constraints'
|
||||
'- **No verdict means no push.** Require a CONFIRMED commit-bound pawl verdict because green producer tests cannot independently authorize release.'
|
||||
'- **Consult the pawl before raising the andon.** WARN, FAIL, or REFUTED repairs and reruns automatically because ordinary rejection is useful evidence, not a breaker; only a breaker may enter HOLD or consume one helper.'
|
||||
'## Breaker State Machine'
|
||||
'- **Ordinary rejection — `WARN|FAIL|REFUTED -> AUTO-REDO`:** repair, recommit, and rerun gate plus pawl; plain rejection never enters HOLD and never consumes the helper lane.'
|
||||
'- **Breaker — `BREAKER -> HOLD -> ONE-HELPER`:** pause landing in HOLD and route exactly one bounded helper consultation.'
|
||||
'- **Recovered — `HELPER-UNSTUCK -> AUTO-REDO`:** leave HOLD, resume repair, and re-earn gate plus independent verdict before landing.'
|
||||
'- **Helper escalation — `HELPER-ESCALATE -> HUMAN`:** stop automation and surface the helper'\''s escalation to the human operator.'
|
||||
'- **Direct human lane — `REFUSAL-LANE|EXPLICIT-JUDGMENT|EXHAUSTED-BUDGET -> HUMAN`:** stop automation and route directly to the human operator with the helper skipped.'
|
||||
'**Checkpoint:** confirm HEAD cites the bead, the fast gate passed on that exact HEAD, and the CONFIRMED verdict is commit-bound before invoking the land command. After landing, run the landed verifier so a canonical provenance-bind tip is separated from its reviewed feature parent without trusting either a marker or path alone.'
|
||||
'## Output Specification'
|
||||
'- **Path:** remote `origin` ref `refs/heads/main`, with proof at `.agents/pawl-verdicts/<bead>.json`.'
|
||||
'- **Filename convention:** `<bead>.json` for the pawl verdict bound to the reviewed commit contained by the pushed ref.'
|
||||
'- **Serialization/schema format:** landed-tip and reviewed-commit Git SHAs plus the JSON pawl-verdict schema bound to the reviewed SHA.'
|
||||
'- **Validator command:** set `BEAD="<bead-id>"`, then run `bash skills-codex/push/scripts/verify-landed.sh "$BEAD"`; do not replace this with inline `HEAD`/`HEAD^` guessing.'
|
||||
'- **Downstream handoff:** send both verifier-reported `tip=<sha>` and `reviewed=<sha>`, verdict path, suites run, and remote-ref proof to closeout; close the tracker only after this helper succeeds.'
|
||||
'## Quality Checklist'
|
||||
'- The commit subject cites the bead, the diff contains only owned files, and sensitive/private paths are absent from the index.'
|
||||
'- Deterministic tests and `ao gate check --fast --scope head` pass on the exact commit reviewed by the pawl.'
|
||||
'- The verdict is CONFIRMED, independent, cross-family where required, and bound to the verifier-resolved reviewed SHA contained by the landed tip.'
|
||||
'- The landed verifier proves `HEAD == origin/main` and reports both tip and reviewed SHAs; ordinary rejection stays in AUTO-REDO and only the breaker state machine reaches HOLD or HUMAN.'
|
||||
)
|
||||
|
||||
validate_contract() {
|
||||
local file="$1" marker
|
||||
[[ -s "$file" ]] || return 1
|
||||
for marker in "${MARKERS[@]}"; do
|
||||
grep -Fqx -- "$marker" "$file" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
delete_one_negative_fixture() {
|
||||
local marker variant="$TMP/missing-marker.md"
|
||||
for marker in "${MARKERS[@]}"; do
|
||||
grep -Fvx -- "$marker" "$SKILL" >"$variant"
|
||||
if validate_contract "$variant"; then
|
||||
echo "push contract validator accepted a missing marker: $marker" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Verify SKILL.md exists and has frontmatter
|
||||
[[ -f "$SKILL_DIR/SKILL.md" ]] || { echo "FAIL: missing SKILL.md"; exit 1; }
|
||||
[[ -x "$SKILL_DIR/scripts/verify-landed.sh" ]] || { echo "FAIL: missing executable landed verifier"; exit 1; }
|
||||
[[ -x "$SKILL_DIR/scripts/test-verify-landed.sh" ]] || { echo "FAIL: missing executable landed verifier fixtures"; exit 1; }
|
||||
head -1 "$SKILL_DIR/SKILL.md" | grep -q "^---$" || { echo "FAIL: missing frontmatter"; exit 1; }
|
||||
validate_contract "$SKILL"
|
||||
delete_one_negative_fixture
|
||||
bash "$SKILL_DIR/scripts/test-verify-landed.sh" >/dev/null
|
||||
echo "OK: $(basename "$SKILL_DIR")"
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: verify-landed.sh <bead-id> [--repo <path>] [--remote <name>]" >&2
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || { usage; exit 2; }
|
||||
BEAD="$1"
|
||||
shift
|
||||
REPO=""
|
||||
REMOTE="origin"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo) [[ $# -ge 2 ]] || { usage; exit 2; }; REPO="$2"; shift 2 ;;
|
||||
--remote) [[ $# -ge 2 ]] || { usage; exit 2; }; REMOTE="$2"; shift 2 ;;
|
||||
*) usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$REPO" ]]; then
|
||||
REPO="$(git rev-parse --show-toplevel 2>/dev/null)" || {
|
||||
echo "verify-landed: not inside a git worktree" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
REPO="$(cd "$REPO" && pwd -P)"
|
||||
PAWL_VERDICT="$REPO/scripts/pawl-verdict.sh"
|
||||
VERDICT_DIR="$REPO/.agents/pawl-verdicts"
|
||||
|
||||
[[ -x "$PAWL_VERDICT" || -f "$PAWL_VERDICT" ]] || {
|
||||
echo "verify-landed: missing $PAWL_VERDICT" >&2
|
||||
exit 2
|
||||
}
|
||||
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
|
||||
echo "verify-landed: $REPO is not a git worktree" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
git -C "$REPO" fetch --quiet "$REMOTE" main || {
|
||||
echo "verify-landed: could not fetch $REMOTE main" >&2
|
||||
exit 1
|
||||
}
|
||||
TIP="$(git -C "$REPO" rev-parse HEAD)"
|
||||
REMOTE_TIP="$(git -C "$REPO" rev-parse "refs/remotes/$REMOTE/main" 2>/dev/null)" || {
|
||||
echo "verify-landed: missing refs/remotes/$REMOTE/main after fetch" >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ "$TIP" != "$REMOTE_TIP" ]]; then
|
||||
echo "verify-landed: stale remote identity: tip=$TIP remote=$REMOTE_TIP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The auto-bind commit has one canonical, two-factor signature: the exact
|
||||
# generated subject for this bead AND a non-empty diff containing only the
|
||||
# provenance ledger. A marker alone or a provenance-only commit alone never
|
||||
# changes which SHA was reviewed.
|
||||
is_canonical_autobind() {
|
||||
local sha="$1" subject changed
|
||||
subject="$(git -C "$REPO" log -1 --format=%s "$sha")"
|
||||
[[ "$subject" == "chore(provenance): bind pawl CONFIRMED verdict for $BEAD #trivial" ]] || return 1
|
||||
changed="$(git -C "$REPO" diff-tree --no-commit-id --no-renames --name-only -r "$sha")" || return 1
|
||||
[[ "$changed" == "docs/provenance/ledger.jsonl" ]]
|
||||
}
|
||||
|
||||
REVIEWED="$TIP"
|
||||
if is_canonical_autobind "$TIP"; then
|
||||
REVIEWED="$(git -C "$REPO" rev-parse "$TIP^")" || {
|
||||
echo "verify-landed: canonical bind tip has no parent: $TIP" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
MESSAGE="$(git -C "$REPO" log -1 --format=%B "$REVIEWED")"
|
||||
CITED="$(grep -oE '[[:alnum:]]+-[[:alnum:]][[:alnum:].-]*' <<<"$MESSAGE" | sed 's/[.,;:]*$//' | sort -u || true)"
|
||||
if ! grep -Fqx -- "$BEAD" <<<"$CITED"; then
|
||||
echo "verify-landed: reviewed commit $REVIEWED does not cite bead $BEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! (cd "$REPO" && AGENTOPS_REPO_ROOT="$REPO" PAWL_AUTOBIND=0 \
|
||||
bash "$PAWL_VERDICT" check "$BEAD" 0 --dir "$VERDICT_DIR" --head "$REVIEWED"); then
|
||||
echo "verify-landed: pawl verdict does not authorize reviewed=$REVIEWED for bead=$BEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'verify-landed: tip=%s reviewed=%s bead=%s\n' "$TIP" "$REVIEWED" "$BEAD"
|
||||
+33
-1
@@ -38,6 +38,21 @@ output_contract: git commit + push
|
||||
|
||||
Ship a change to `main` **in this repo** with proof. AgentOps lands work by **direct push to main** — PR-per-change is retired here. The release authority is the **local cockpit gate** plus a **CONFIRMED cross-family pawl verdict** bound to the commit.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **No verdict means no push.** Require a CONFIRMED commit-bound pawl verdict because green producer tests cannot independently authorize release.
|
||||
- **Keep reviewed identity explicit.** Commit every repair and re-earn the verdict before landing because a verdict for an earlier feature SHA proves a different artifact; after landing, distinguish the reviewed feature from an optional canonical provenance-bind tip.
|
||||
- **Contain the write surface.** Stage only bead-owned files and exclude credentials/private ledgers because direct-main landing makes accidental inclusion immediately public.
|
||||
- **Consult the pawl before raising the andon.** WARN, FAIL, or REFUTED repairs and reruns automatically because ordinary rejection is useful evidence, not a breaker; only a breaker may enter HOLD or consume one helper.
|
||||
|
||||
## Breaker State Machine
|
||||
|
||||
- **Ordinary rejection — `WARN|FAIL|REFUTED -> AUTO-REDO`:** repair, recommit, and rerun gate plus pawl; plain rejection never enters HOLD and never consumes the helper lane.
|
||||
- **Breaker — `BREAKER -> HOLD -> ONE-HELPER`:** pause landing in HOLD and route exactly one bounded helper consultation.
|
||||
- **Recovered — `HELPER-UNSTUCK -> AUTO-REDO`:** leave HOLD, resume repair, and re-earn gate plus independent verdict before landing.
|
||||
- **Helper escalation — `HELPER-ESCALATE -> HUMAN`:** stop automation and surface the helper's escalation to the human operator.
|
||||
- **Direct human lane — `REFUSAL-LANE|EXPLICIT-JUDGMENT|EXHAUSTED-BUDGET -> HUMAN`:** stop automation and route directly to the human operator with the helper skipped.
|
||||
|
||||
## The invariant — no verdict = no push
|
||||
|
||||
**Never push without a CONFIRMED, commit-bound pawl verdict.** This replaces the old "never push to main without permission" guardrail: direct-main IS the routine path for THIS repo, and the pawl verdict IS the permission. A push carrying no CONFIRMED verdict is refused by the pre-push hook (`scripts/check-pawl-pre-push.sh`); the ONLY waiver is a `#trivial` docs/provenance-only commit. **No verdict = not done.**
|
||||
@@ -79,6 +94,8 @@ Use `--scope staged` for a review-only pass before committing; certify with `--s
|
||||
|
||||
### Step 4: Land — deterministic single-shot push
|
||||
|
||||
**Checkpoint:** confirm HEAD cites the bead, the fast gate passed on that exact HEAD, and the CONFIRMED verdict is commit-bound before invoking the land command. After landing, run the landed verifier so a canonical provenance-bind tip is separated from its reviewed feature parent without trusting either a marker or path alone.
|
||||
|
||||
```bash
|
||||
bash scripts/pawl-land.sh <bead>
|
||||
```
|
||||
@@ -87,7 +104,15 @@ Fetches + rebases onto current `origin/main` (fixes the catch-22 where origin ad
|
||||
|
||||
### Step 5: Report
|
||||
|
||||
Files changed, suites run, verdict disposition + bound SHA, and the landed tip.
|
||||
Run `bash skills/push/scripts/verify-landed.sh <bead>`, then report files changed, suites run, verdict disposition, reviewed SHA, and landed tip. The verifier fetches `main`, proves local/remote tip identity, recognizes `HEAD^` only for the exact canonical auto-bind subject plus its non-empty provenance-ledger-only diff, requires the reviewed commit to cite the bead, and checks the real pawl verdict against that reviewed SHA.
|
||||
|
||||
## Output Specification
|
||||
|
||||
- **Path:** remote `origin` ref `refs/heads/main`, with proof at `.agents/pawl-verdicts/<bead>.json`.
|
||||
- **Filename convention:** `<bead>.json` for the pawl verdict bound to the reviewed commit contained by the pushed ref.
|
||||
- **Serialization/schema format:** landed-tip and reviewed-commit Git SHAs plus the JSON pawl-verdict schema bound to the reviewed SHA.
|
||||
- **Validator command:** set `BEAD="<bead-id>"`, then run `bash skills/push/scripts/verify-landed.sh "$BEAD"`; do not replace this with inline `HEAD`/`HEAD^` guessing.
|
||||
- **Downstream handoff:** send both verifier-reported `tip=<sha>` and `reviewed=<sha>`, verdict path, suites run, and remote-ref proof to closeout; close the tracker only after this helper succeeds.
|
||||
|
||||
## External repos (PR flow only)
|
||||
|
||||
@@ -100,6 +125,13 @@ The PR-per-change flow survives **only for external repos** (upstream forks, non
|
||||
- Stage only files relevant to the work; no `git add -A` unless explicitly requested. Never `git add _beads` (private nested ledger).
|
||||
- On a rebase conflict, do NOT force-push — `pawl-land.sh` aborts; resolve locally and re-gate.
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
- The commit subject cites the bead, the diff contains only owned files, and sensitive/private paths are absent from the index.
|
||||
- Deterministic tests and `ao gate check --fast --scope head` pass on the exact commit reviewed by the pawl.
|
||||
- The verdict is CONFIRMED, independent, cross-family where required, and bound to the verifier-resolved reviewed SHA contained by the landed tip.
|
||||
- The landed verifier proves `HEAD == origin/main` and reports both tip and reviewed SHAs; ordinary rejection stays in AUTO-REDO and only the breaker state machine reaches HOLD or HUMAN.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERIFY="$SKILL_DIR/scripts/verify-landed.sh"
|
||||
REPO_ROOT="$(cd "$SKILL_DIR/../.." && pwd)"
|
||||
PAWL="$REPO_ROOT/scripts/pawl-verdict.sh"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
new_fixture() {
|
||||
local name="$1"
|
||||
FIXTURE="$TMP/$name"
|
||||
REMOTE="$TMP/$name.git"
|
||||
git init --bare --quiet "$REMOTE"
|
||||
git init --quiet "$FIXTURE"
|
||||
git -C "$FIXTURE" config user.email test@example.com
|
||||
git -C "$FIXTURE" config user.name Test
|
||||
git -C "$FIXTURE" remote add origin "$REMOTE"
|
||||
mkdir -p "$FIXTURE/scripts/lib" "$FIXTURE/schemas" "$FIXTURE/.agents/pawl-verdicts"
|
||||
cp "$PAWL" "$FIXTURE/scripts/pawl-verdict.sh"
|
||||
cp "$REPO_ROOT/scripts/lib/diff-identity.sh" "$FIXTURE/scripts/lib/diff-identity.sh"
|
||||
cp "$REPO_ROOT/schemas/pawl-verdict.v1.schema.json" "$FIXTURE/schemas/pawl-verdict.v1.schema.json"
|
||||
echo init >"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md scripts schemas
|
||||
git -C "$FIXTURE" commit --quiet -m "chore: fixture init"
|
||||
git -C "$FIXTURE" branch -M main
|
||||
}
|
||||
|
||||
commit_feature() {
|
||||
local bead="$1"
|
||||
echo "$bead" >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "feat(test): exercise landed verifier ($bead)"
|
||||
FEATURE_SHA="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
}
|
||||
|
||||
seed_verdict() {
|
||||
local bead="$1" sha="$2"
|
||||
local evidence="$FIXTURE/evidence-$bead.txt"
|
||||
printf 'fresh-context deterministic fixture evidence\n' >"$evidence"
|
||||
(cd "$FIXTURE" && AGENTOPS_REPO_ROOT="$FIXTURE" PAWL_AUTOBIND=0 \
|
||||
bash "$FIXTURE/scripts/pawl-verdict.sh" write "$bead" 0 \
|
||||
--disposition CONFIRMED --head "$sha" --author-context author-ctx \
|
||||
--refuter "claude:CONFIRMED:fresh-reviewer-ctx:$evidence" \
|
||||
--dir "$FIXTURE/.agents/pawl-verdicts" >/dev/null 2>&1)
|
||||
}
|
||||
|
||||
push_tip() {
|
||||
git -C "$FIXTURE" push --quiet -u origin HEAD:main
|
||||
}
|
||||
|
||||
expect_pass() {
|
||||
local bead="$1" expected_reviewed="$2" out
|
||||
out="$(bash "$VERIFY" "$bead" --repo "$FIXTURE")" || fail "$bead unexpectedly failed"
|
||||
[[ "$out" == *"tip=$(git -C "$FIXTURE" rev-parse HEAD)"* ]] || fail "$bead omitted tip SHA"
|
||||
[[ "$out" == *"reviewed=$expected_reviewed"* ]] || fail "$bead resolved the wrong reviewed SHA: $out"
|
||||
}
|
||||
|
||||
expect_fail() {
|
||||
local bead="$1" label="$2"
|
||||
if bash "$VERIFY" "$bead" --repo "$FIXTURE" >"$TMP/out" 2>&1; then
|
||||
fail "$label unexpectedly passed"
|
||||
fi
|
||||
}
|
||||
|
||||
# Normal feature tip: review and remote identity bind directly to HEAD.
|
||||
new_fixture normal
|
||||
commit_feature age-push-normal
|
||||
seed_verdict age-push-normal "$FEATURE_SHA"
|
||||
push_tip
|
||||
expect_pass age-push-normal "$FEATURE_SHA"
|
||||
|
||||
# Valid canonical auto-bind tip: remote is bind, while pawl binds the feat parent.
|
||||
new_fixture bind
|
||||
commit_feature age-push-bind
|
||||
seed_verdict age-push-bind "$FEATURE_SHA"
|
||||
mkdir -p "$FIXTURE/docs/provenance"
|
||||
printf '{"edge":"bind"}\n' >"$FIXTURE/docs/provenance/ledger.jsonl"
|
||||
git -C "$FIXTURE" add docs/provenance/ledger.jsonl
|
||||
git -C "$FIXTURE" commit --quiet -m "chore(provenance): bind pawl CONFIRMED verdict for age-push-bind #trivial"
|
||||
push_tip
|
||||
expect_pass age-push-bind "$FEATURE_SHA"
|
||||
|
||||
# Marker-only is not a bind: code in the tip keeps reviewed=HEAD and the parent
|
||||
# verdict must not authorize it.
|
||||
new_fixture marker-only
|
||||
commit_feature age-push-marker
|
||||
seed_verdict age-push-marker "$FEATURE_SHA"
|
||||
echo code >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "chore(provenance): bind pawl CONFIRMED verdict for age-push-marker #trivial"
|
||||
push_tip
|
||||
expect_fail age-push-marker "marker-only invalid tip"
|
||||
MARKER_TIP="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
grep -Fq -- "reviewed=$MARKER_TIP" "$TMP/out" || fail "marker-only tip was misclassified as an auto-bind"
|
||||
|
||||
# Provenance-only without the marker is not misclassified as an auto-bind.
|
||||
new_fixture provenance-only
|
||||
commit_feature age-push-provenance
|
||||
seed_verdict age-push-provenance "$FEATURE_SHA"
|
||||
mkdir -p "$FIXTURE/docs/provenance"
|
||||
printf '{"edge":"ordinary-doc"}\n' >"$FIXTURE/docs/provenance/ledger.jsonl"
|
||||
git -C "$FIXTURE" add docs/provenance/ledger.jsonl
|
||||
git -C "$FIXTURE" commit --quiet -m "docs(provenance): document age-push-provenance"
|
||||
PROVENANCE_TIP="$(git -C "$FIXTURE" rev-parse HEAD)"
|
||||
push_tip
|
||||
expect_fail age-push-provenance "provenance-only no-marker tip"
|
||||
grep -Fq -- "reviewed=$PROVENANCE_TIP" "$TMP/out" || fail "provenance-only tip was misclassified as an auto-bind"
|
||||
|
||||
# A local tip not equal to freshly fetched origin/main fails before verdict use.
|
||||
new_fixture stale
|
||||
commit_feature age-push-stale
|
||||
seed_verdict age-push-stale "$FEATURE_SHA"
|
||||
push_tip
|
||||
echo local >>"$FIXTURE/README.md"
|
||||
git -C "$FIXTURE" add README.md
|
||||
git -C "$FIXTURE" commit --quiet -m "fix(test): local-only age-push-stale"
|
||||
expect_fail age-push-stale "stale remote"
|
||||
grep -Fq "stale remote identity" "$TMP/out" || fail "stale remote failure was not explicit"
|
||||
|
||||
# A verdict cannot compensate for the reviewed commit citing the wrong bead.
|
||||
new_fixture wrong-bead
|
||||
commit_feature age-push-other
|
||||
seed_verdict age-push-wrong-bead "$FEATURE_SHA"
|
||||
push_tip
|
||||
expect_fail age-push-wrong-bead "wrong bead citation"
|
||||
grep -Fq "does not cite bead age-push-wrong-bead" "$TMP/out" || fail "wrong bead citation was not explicit"
|
||||
|
||||
# A verdict for the requested bead but an older SHA fails the real pawl check.
|
||||
new_fixture wrong-head
|
||||
commit_feature age-push-wrong-head
|
||||
seed_verdict age-push-wrong-head "$(git -C "$FIXTURE" rev-parse HEAD^)"
|
||||
push_tip
|
||||
expect_fail age-push-wrong-head "wrong verdict head"
|
||||
grep -Fq "does not authorize reviewed=$FEATURE_SHA" "$TMP/out" || fail "wrong verdict head was not explicit"
|
||||
|
||||
echo "OK: verify-landed fixtures"
|
||||
@@ -1,7 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SKILL="$SKILL_DIR/SKILL.md"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# Backticks and command substitutions are exact Markdown markers, never shell execution.
|
||||
# shellcheck disable=SC2016
|
||||
MARKERS=(
|
||||
'## Constraints'
|
||||
'- **No verdict means no push.** Require a CONFIRMED commit-bound pawl verdict because green producer tests cannot independently authorize release.'
|
||||
'- **Consult the pawl before raising the andon.** WARN, FAIL, or REFUTED repairs and reruns automatically because ordinary rejection is useful evidence, not a breaker; only a breaker may enter HOLD or consume one helper.'
|
||||
'## Breaker State Machine'
|
||||
'- **Ordinary rejection — `WARN|FAIL|REFUTED -> AUTO-REDO`:** repair, recommit, and rerun gate plus pawl; plain rejection never enters HOLD and never consumes the helper lane.'
|
||||
'- **Breaker — `BREAKER -> HOLD -> ONE-HELPER`:** pause landing in HOLD and route exactly one bounded helper consultation.'
|
||||
'- **Recovered — `HELPER-UNSTUCK -> AUTO-REDO`:** leave HOLD, resume repair, and re-earn gate plus independent verdict before landing.'
|
||||
'- **Helper escalation — `HELPER-ESCALATE -> HUMAN`:** stop automation and surface the helper'\''s escalation to the human operator.'
|
||||
'- **Direct human lane — `REFUSAL-LANE|EXPLICIT-JUDGMENT|EXHAUSTED-BUDGET -> HUMAN`:** stop automation and route directly to the human operator with the helper skipped.'
|
||||
'**Checkpoint:** confirm HEAD cites the bead, the fast gate passed on that exact HEAD, and the CONFIRMED verdict is commit-bound before invoking the land command. After landing, run the landed verifier so a canonical provenance-bind tip is separated from its reviewed feature parent without trusting either a marker or path alone.'
|
||||
'## Output Specification'
|
||||
'- **Path:** remote `origin` ref `refs/heads/main`, with proof at `.agents/pawl-verdicts/<bead>.json`.'
|
||||
'- **Filename convention:** `<bead>.json` for the pawl verdict bound to the reviewed commit contained by the pushed ref.'
|
||||
'- **Serialization/schema format:** landed-tip and reviewed-commit Git SHAs plus the JSON pawl-verdict schema bound to the reviewed SHA.'
|
||||
'- **Validator command:** set `BEAD="<bead-id>"`, then run `bash skills/push/scripts/verify-landed.sh "$BEAD"`; do not replace this with inline `HEAD`/`HEAD^` guessing.'
|
||||
'- **Downstream handoff:** send both verifier-reported `tip=<sha>` and `reviewed=<sha>`, verdict path, suites run, and remote-ref proof to closeout; close the tracker only after this helper succeeds.'
|
||||
'## Quality Checklist'
|
||||
'- The commit subject cites the bead, the diff contains only owned files, and sensitive/private paths are absent from the index.'
|
||||
'- Deterministic tests and `ao gate check --fast --scope head` pass on the exact commit reviewed by the pawl.'
|
||||
'- The verdict is CONFIRMED, independent, cross-family where required, and bound to the verifier-resolved reviewed SHA contained by the landed tip.'
|
||||
'- The landed verifier proves `HEAD == origin/main` and reports both tip and reviewed SHAs; ordinary rejection stays in AUTO-REDO and only the breaker state machine reaches HOLD or HUMAN.'
|
||||
)
|
||||
|
||||
validate_contract() {
|
||||
local file="$1" marker
|
||||
[[ -s "$file" ]] || return 1
|
||||
for marker in "${MARKERS[@]}"; do
|
||||
grep -Fqx -- "$marker" "$file" || return 1
|
||||
done
|
||||
}
|
||||
|
||||
delete_one_negative_fixture() {
|
||||
local marker variant="$TMP/missing-marker.md"
|
||||
for marker in "${MARKERS[@]}"; do
|
||||
grep -Fvx -- "$marker" "$SKILL" >"$variant"
|
||||
if validate_contract "$variant"; then
|
||||
echo "push contract validator accepted a missing marker: $marker" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Verify SKILL.md exists and has frontmatter
|
||||
[[ -f "$SKILL_DIR/SKILL.md" ]] || { echo "FAIL: missing SKILL.md"; exit 1; }
|
||||
[[ -x "$SKILL_DIR/scripts/verify-landed.sh" ]] || { echo "FAIL: missing executable landed verifier"; exit 1; }
|
||||
[[ -x "$SKILL_DIR/scripts/test-verify-landed.sh" ]] || { echo "FAIL: missing executable landed verifier fixtures"; exit 1; }
|
||||
head -1 "$SKILL_DIR/SKILL.md" | grep -q "^---$" || { echo "FAIL: missing frontmatter"; exit 1; }
|
||||
validate_contract "$SKILL"
|
||||
delete_one_negative_fixture
|
||||
bash "$SKILL_DIR/scripts/test-verify-landed.sh" >/dev/null
|
||||
echo "OK: $(basename "$SKILL_DIR")"
|
||||
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
echo "usage: verify-landed.sh <bead-id> [--repo <path>] [--remote <name>]" >&2
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || { usage; exit 2; }
|
||||
BEAD="$1"
|
||||
shift
|
||||
REPO=""
|
||||
REMOTE="origin"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--repo) [[ $# -ge 2 ]] || { usage; exit 2; }; REPO="$2"; shift 2 ;;
|
||||
--remote) [[ $# -ge 2 ]] || { usage; exit 2; }; REMOTE="$2"; shift 2 ;;
|
||||
*) usage; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$REPO" ]]; then
|
||||
REPO="$(git rev-parse --show-toplevel 2>/dev/null)" || {
|
||||
echo "verify-landed: not inside a git worktree" >&2
|
||||
exit 2
|
||||
}
|
||||
fi
|
||||
REPO="$(cd "$REPO" && pwd -P)"
|
||||
PAWL_VERDICT="$REPO/scripts/pawl-verdict.sh"
|
||||
VERDICT_DIR="$REPO/.agents/pawl-verdicts"
|
||||
|
||||
[[ -x "$PAWL_VERDICT" || -f "$PAWL_VERDICT" ]] || {
|
||||
echo "verify-landed: missing $PAWL_VERDICT" >&2
|
||||
exit 2
|
||||
}
|
||||
git -C "$REPO" rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
|
||||
echo "verify-landed: $REPO is not a git worktree" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
git -C "$REPO" fetch --quiet "$REMOTE" main || {
|
||||
echo "verify-landed: could not fetch $REMOTE main" >&2
|
||||
exit 1
|
||||
}
|
||||
TIP="$(git -C "$REPO" rev-parse HEAD)"
|
||||
REMOTE_TIP="$(git -C "$REPO" rev-parse "refs/remotes/$REMOTE/main" 2>/dev/null)" || {
|
||||
echo "verify-landed: missing refs/remotes/$REMOTE/main after fetch" >&2
|
||||
exit 1
|
||||
}
|
||||
if [[ "$TIP" != "$REMOTE_TIP" ]]; then
|
||||
echo "verify-landed: stale remote identity: tip=$TIP remote=$REMOTE_TIP" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The auto-bind commit has one canonical, two-factor signature: the exact
|
||||
# generated subject for this bead AND a non-empty diff containing only the
|
||||
# provenance ledger. A marker alone or a provenance-only commit alone never
|
||||
# changes which SHA was reviewed.
|
||||
is_canonical_autobind() {
|
||||
local sha="$1" subject changed
|
||||
subject="$(git -C "$REPO" log -1 --format=%s "$sha")"
|
||||
[[ "$subject" == "chore(provenance): bind pawl CONFIRMED verdict for $BEAD #trivial" ]] || return 1
|
||||
changed="$(git -C "$REPO" diff-tree --no-commit-id --no-renames --name-only -r "$sha")" || return 1
|
||||
[[ "$changed" == "docs/provenance/ledger.jsonl" ]]
|
||||
}
|
||||
|
||||
REVIEWED="$TIP"
|
||||
if is_canonical_autobind "$TIP"; then
|
||||
REVIEWED="$(git -C "$REPO" rev-parse "$TIP^")" || {
|
||||
echo "verify-landed: canonical bind tip has no parent: $TIP" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
MESSAGE="$(git -C "$REPO" log -1 --format=%B "$REVIEWED")"
|
||||
CITED="$(grep -oE '[[:alnum:]]+-[[:alnum:]][[:alnum:].-]*' <<<"$MESSAGE" | sed 's/[.,;:]*$//' | sort -u || true)"
|
||||
if ! grep -Fqx -- "$BEAD" <<<"$CITED"; then
|
||||
echo "verify-landed: reviewed commit $REVIEWED does not cite bead $BEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! (cd "$REPO" && AGENTOPS_REPO_ROOT="$REPO" PAWL_AUTOBIND=0 \
|
||||
bash "$PAWL_VERDICT" check "$BEAD" 0 --dir "$VERDICT_DIR" --head "$REVIEWED"); then
|
||||
echo "verify-landed: pawl verdict does not authorize reviewed=$REVIEWED for bead=$BEAD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'verify-landed: tip=%s reviewed=%s bead=%s\n' "$TIP" "$REVIEWED" "$BEAD"
|
||||
Reference in New Issue
Block a user