Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions .github/workflows/checkpoint-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Checkpoint Audit

on:
schedule:
- cron: "0 7 * * *" # 07:00 UTC daily (GitHub does not honor sub-daily cron reliably)
workflow_dispatch:
inputs:
window:
description: 'git --since window to audit'
default: '24 hours ago'

permissions:
contents: read
actions: read # required for actions/upload-artifact under a restricted token (matches e2e.yml)

concurrency:
group: checkpoint-audit-${{ github.ref }}
cancel-in-progress: true

jobs:
audit:
runs-on: ubuntu-latest
steps:
# The checkpoint remote (entireio/cli-checkpoints) is public, so the audit's
# read-only `git ls-remote` needs no credentials. If it is ever made private,
# set ENTIRE_CHECKPOINT_TOKEN on the audit step from a token with read access.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Fetch all branches
run: git fetch --all --prune

# Capture the script's exit code (0 = all present, 1 = missing, 2 =
# remote/setup error) without failing the step, so the report, Slack, and
# gate steps below can distinguish "missing" from "could not run".
- name: Run checkpoint audit
id: audit
env:
AUDIT_WINDOW: ${{ github.event.inputs.window || '24 hours ago' }}
run: |
set +e
scripts/checkpoint-audit.sh
echo "code=$?" >> "$GITHUB_OUTPUT"
exit 0

- name: Upload audit report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: checkpoint-audit-report
path: |
checkpoint-audit-report.md
checkpoint-audit-report.json
retention-days: 7
if-no-files-found: ignore

# Distinct wording per outcome: exit 1 asserts missing checkpoints (a report
# artifact exists); exit 2 is a remote/setup failure with no report, so the
# alert must not claim checkpoints are missing (avoids false positives).
- name: Compose Slack message
id: msg
if: ${{ always() && steps.audit.outputs.code != '0' }}
run: |
if [ "${{ steps.audit.outputs.code }}" = "1" ]; then
echo 'text=:red_circle: *Checkpoint audit: missing checkpoints*\n\nOne or more commits reference a checkpoint that is not on the checkpoint remote. See the report artifact for the full table.' >> "$GITHUB_OUTPUT"
else
echo 'text=:red_circle: *Checkpoint audit failed to run*\n\nThe audit could not complete (remote or setup error); no report was produced. See the run log.' >> "$GITHUB_OUTPUT"
fi

- name: Notify Slack
if: ${{ always() && steps.audit.outputs.code != '0' }}
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
with:
webhook: ${{ secrets.E2E_SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"attachments": [
{
"color": "#d50200",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "${{ steps.msg.outputs.text }}\n\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run details>"
}
}
]
}
]
}

- name: Fail the job on audit error
if: ${{ steps.audit.outputs.code != '0' }}
run: |
echo "::error::checkpoint audit exited ${{ steps.audit.outputs.code }} (1 = missing checkpoints, 2 = remote/setup error)"
exit 1
Comment thread
gtrrz-victor marked this conversation as resolved.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,7 @@ tmp/
entire-external-cp-test/
/skills/
/git-remote-entire

# Runtime output from scripts/checkpoint-audit.sh (never commit)
checkpoint-audit-report.md
checkpoint-audit-report.json
4 changes: 4 additions & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ mise run test:e2e:canary
description = "Run formatting, linting, and CI tests"
depends = ["fmt", "lint", "test:ci"]

[tasks."checkpoint:audit"]
description = "Audit that recent commits' checkpoints exist on the checkpoint remote (set ENTIRE_CHECKPOINT_TOKEN)"
run = "scripts/checkpoint-audit.sh"

[tasks."build:windows"]
description = "Cross-compile for Windows amd64"
run = "CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o entire.exe ./cmd/entire/"
Expand Down
183 changes: 183 additions & 0 deletions scripts/checkpoint-audit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/usr/bin/env bash
set -euo pipefail

# Audit checkpoint availability on the checkpoint remote.
#
# Walks every commit created within a time window across all branches, extracts
# each `Entire-Checkpoint:` trailer, and checks whether the matching checkpoint
# ref exists on the checkpoint remote (github.com/<CHECKPOINT_REPO>). Any commit
# whose checkpoint ref is missing from the remote is reported with context and
# makes the script exit non-zero.
#
# This catches the failure mode where a commit reaches origin but its git-refs
# checkpoint ref (refs/entire/checkpoints/<shard>/<id>) was never pushed to the
# checkpoint remote — the checkpoint is then unrecoverable on any other machine
# (e.g. `entire trail resume` / `entire explain` fail with "checkpoint not found").
#
# Trailer parsing and ref-set membership use git's own primitives rather than
# hand-rolled regex/shard math: `git show --format='%(trailers:...)'` parses
# trailers (handling squash-merge commits with multiple checkpoint trailers), and
# the checkpoint ID is simply the ref leaf, so membership is a plain string-set
# lookup that works for both legacy-hex and ULID IDs.
#
# Env (all optional except the token for a private remote):
# ENTIRE_CHECKPOINT_TOKEN GitHub token with read access to the checkpoint repo.
# Sent as an RFC 7617 basic auth header (matching the
# CLI), so it never appears in a remote URL.
# CHECKPOINT_REPO owner/repo of the checkpoint remote
# (default: entireio/cli-checkpoints).
# AUDIT_WINDOW git `--since` window (default: "24 hours ago").
# AUDIT_REPORT_FILE markdown report sink (default: checkpoint-audit-report.md).
# AUDIT_JSON_FILE machine-readable report sink (default: checkpoint-audit-report.json).
# GITHUB_STEP_SUMMARY if set, the markdown report is appended to it.
#
# Exit codes: 0 = all present, 1 = one or more checkpoints missing, 2 = setup or
# remote error (a broken remote must never read as "0 missing").

CHECKPOINT_REPO="${CHECKPOINT_REPO:-entireio/cli-checkpoints}"
AUDIT_WINDOW="${AUDIT_WINDOW:-24 hours ago}"
AUDIT_REPORT_FILE="${AUDIT_REPORT_FILE:-checkpoint-audit-report.md}"
AUDIT_JSON_FILE="${AUDIT_JSON_FILE:-checkpoint-audit-report.json}"
TOKEN="${ENTIRE_CHECKPOINT_TOKEN:-}"

CHECKPOINT_URL="https://github.com/${CHECKPOINT_REPO}.git"

REMOTE_IDS_FILE=$(mktemp "${TMPDIR:-/tmp}/checkpoint-audit.XXXXXX")
ROWS_FILE=$(mktemp "${TMPDIR:-/tmp}/checkpoint-audit.XXXXXX")
trap 'rm -f "$REMOTE_IDS_FILE" "$ROWS_FILE"' EXIT

# json_str emits a JSON string literal. Git commit subjects can legally contain
# tabs and other control characters, so escape backslash, double quote, and the
# C0 controls that would otherwise produce invalid JSON.
json_str() {
local s=${1//\\/\\\\}
s=${s//\"/\\\"}
s=${s//$'\t'/\\t}
s=${s//$'\r'/\\r}
s=${s//$'\n'/\\n}
s=${s//$'\b'/\\b}
s=${s//$'\f'/\\f}
printf '"%s"' "$s"
}

# 1. Enumerate the checkpoint refs present on the remote (names only, no object
# transfer). The ID is the ref leaf: refs/entire/checkpoints/<shard>/<ID>.
echo "Enumerating checkpoint refs on ${CHECKPOINT_REPO} ..." >&2
if [ -n "$TOKEN" ]; then
auth_b64=$(printf 'x-access-token:%s' "$TOKEN" | base64 | tr -d '\n')
# Standard header name/scheme casing, matching the CLI (Authorization: Basic).
remote_refs=$(git -c "http.extraheader=Authorization: Basic ${auth_b64}" \
ls-remote "$CHECKPOINT_URL" 'refs/entire/checkpoints/*') || {
echo "::error::failed to ls-remote ${CHECKPOINT_REPO} (check ENTIRE_CHECKPOINT_TOKEN and repo access)" >&2
exit 2
}
else
remote_refs=$(git ls-remote "$CHECKPOINT_URL" 'refs/entire/checkpoints/*') || {
echo "::error::failed to ls-remote ${CHECKPOINT_REPO} (no ENTIRE_CHECKPOINT_TOKEN set; is the repo private?)" >&2
exit 2
}
fi

printf '%s\n' "$remote_refs" \
| awk '$2 ~ /^refs\/entire\/checkpoints\// { id = $2; sub(/.*\//, "", id); print id }' \
| sort -u > "$REMOTE_IDS_FILE"
remote_count=$(grep -c . "$REMOTE_IDS_FILE" || true)

# 2. Candidate commits: every branch commit in the window (local heads + remotes),
# de-duplicated while preserving order. A git-log failure (e.g. run outside a
# git repo) is a setup error, not "0 missing" — surface it as exit 2 rather
# than letting git's raw 128 escape.
if ! commits=$(git log --branches --remotes --since="$AUDIT_WINDOW" --format='%H'); then
echo "::error::failed to enumerate commits (is this a git repository?)" >&2
exit 2
fi
commits=$(printf '%s\n' "$commits" | awk '!seen[$0]++')

# 3. For each commit, diff its checkpoint trailers against the remote set.
commit_count=0
cp_count=0
missing_count=0
while IFS= read -r sha; do
[ -z "$sha" ] && continue
commit_count=$((commit_count + 1))
cps=$(git show -s --format='%(trailers:key=Entire-Checkpoint,valueonly=true)' "$sha")
while IFS= read -r cp; do
cp="${cp//[[:space:]]/}"
[ -z "$cp" ] && continue
cp_count=$((cp_count + 1))
if grep -Fxq "$cp" "$REMOTE_IDS_FILE"; then
continue
fi
missing_count=$((missing_count + 1))
meta=$(git show -s --format='%h%x1f%an%x1f%aI%x1f%s' "$sha")
short=${meta%%$'\x1f'*}; meta=${meta#*$'\x1f'}
author=${meta%%$'\x1f'*}; meta=${meta#*$'\x1f'}
cdate=${meta%%$'\x1f'*}; subject=${meta#*$'\x1f'}
# `|| true`: grep exits 1 when every branch is filtered out (or there are
# none), which would otherwise abort the whole run under `set -e`. Join with
# a single-char delimiter then expand to ", " — `paste -sd', '` treats the
# delimiter as a circular char list and would alternate "," and " ".
branches=$(git branch -a --contains "$sha" --format='%(refname:short)' 2>/dev/null \
| sed -e 's#^remotes/##' -e 's#^origin/##' \
| grep -v '^entire/' \
| awk 'NF && !s[$0]++' \
| paste -sd',' - \
| sed 's/,/, /g' || true)
printf '%s\x1e%s\x1e%s\x1e%s\x1e%s\x1e%s\n' \
"$cp" "$short" "$author" "${branches:-?}" "$cdate" "$subject" >> "$ROWS_FILE"
done <<EOF
$cps
EOF
done <<EOF
$commits
EOF

# 4. Render the markdown report (stdout + file + optional step summary).
{
echo "# Checkpoint availability audit"
echo
echo "- Window: commits since \`${AUDIT_WINDOW}\`"
echo "- Checkpoint remote: \`${CHECKPOINT_REPO}\` (${remote_count} refs present)"
echo "- Scanned: ${commit_count} commit(s), ${cp_count} checkpoint trailer(s)"
echo "- **Missing from remote: ${missing_count}**"
echo
if [ "$missing_count" -gt 0 ]; then
echo "| Checkpoint | Commit | Author | Branch(es) | Date | Subject |"
echo "|---|---|---|---|---|---|"
while IFS=$'\x1e' read -r cp short author branches cdate subject; do
# Escape every free-form cell: author names and (legal) branch names can
# contain a pipe, which would otherwise break the markdown table.
author=${author//|/\\|}
branches=${branches//|/\\|}
subject=${subject//|/\\|}
echo "| \`${cp}\` | \`${short}\` | ${author} | ${branches} | ${cdate} | ${subject} |"
done < "$ROWS_FILE"
else
echo "All checkpoints referenced in the window are present on the remote. :white_check_mark:"
fi
} | tee "$AUDIT_REPORT_FILE"

if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
cat "$AUDIT_REPORT_FILE" >> "$GITHUB_STEP_SUMMARY"
fi

# 5. Machine-readable report for the artifact.
{
echo "["
first=1
while IFS=$'\x1e' read -r cp short author branches cdate subject; do
if [ "$first" -eq 1 ]; then first=0; else echo ","; fi
printf ' {"checkpoint":%s,"commit":%s,"author":%s,"branches":%s,"date":%s,"subject":%s}' \
"$(json_str "$cp")" "$(json_str "$short")" "$(json_str "$author")" \
"$(json_str "$branches")" "$(json_str "$cdate")" "$(json_str "$subject")"
done < "$ROWS_FILE"
echo
echo "]"
} > "$AUDIT_JSON_FILE"

if [ "$missing_count" -gt 0 ]; then
echo "::error::${missing_count} checkpoint(s) missing from ${CHECKPOINT_REPO}" >&2
exit 1
fi
echo "All ${cp_count} checkpoint(s) present on ${CHECKPOINT_REPO}." >&2
exit 0
Loading