feat(agent-repo,airouter): seed from upstream + label-gate + diff verify

Three load-bearing fixes for the airouter dogfood pipeline, derived from
the 2026-05-08 batch-3 dogfood postmortem (gotchas-airouter.md items 27-30):

1. agent-repo/v1/init.sh — seed fresh task branches from
   /workspace/reference/main/ (the upstream clone) rather than the agent
   repo's stale main. This was THE killer for batch 3: the
   agent-runtimes-agents fork has been frozen at 2026-05-04 since the
   "Fork cleanup" PR, so every agent started from old state, missing
   recent test files and the M16/M22 scripts to delete. The fork remains
   the push remote (so finalize.sh works); only the working-tree seed
   moves to the upstream reference. Falls back to fork main when the
   reference clone isn't available (preserves legacy behavior). Tagged
   AR-14a.

2. requires_labels on contexts/composites — airouter context + both
   airouter composites declare requires_labels: [airouter] so the
   dispatcher's _collect_supported_harnesses (with the matching agent-
   runtimes change) advertises them only on dispatchers carrying the
   airouter label. Stops the main dispatcher from claiming airouter-
   labeled tasks and dying at init time. Composites that wrap label-
   restricted contexts MUST redeclare their own requires_labels — no
   auto-traversal of layers (kept simple).

3. agent-repo/v1/finalize.sh — AR-21 diff-against-upstream verification.
   New env-var protocol:
     - AGENT_EXPECTED_CHANGED_FILES (comma-separated paths that MUST
       appear in `git diff <ref/main>..HEAD`)
     - AGENT_FORBIDDEN_CHANGED_FILES (paths that MUST NOT appear)
   finalize.sh fails the task (exit 1) if either invariant is violated;
   the branch is still pushed for forensics so the operator can inspect.
   Catches BOTH the false-success mode (item 30 — agent reports succeeded
   but never changed the target file) AND the destructive-Write mode
   (item 21 — task 4a2f2988 stripped 9 unrelated functions). Also writes
   diff_verified, diff_mismatch, diff_changed_files into ci_metadata.json.

CRS pulls all three on next CP poll — no agent-runtimes image rebuild
needed for the framework parts. The matching dispatcher poller filter
ships in agent-runtimes (separate commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-05-08 15:45:41 +12:00
parent 45ec4af0e9
commit 07309567d5
5 changed files with 137 additions and 6 deletions

View File

@@ -38,17 +38,57 @@ for ref in refs:
"
fi
# Clone agent repo working branch (AR-14)
# Clone agent repo working branch (AR-14).
#
# AR-14a (2026-05-08): seed fresh task branches from /workspace/reference/main/
# rather than the agent-repo fork's main, so a stale fork (e.g. periodic
# "Fork cleanup" PRs that reset main) doesn't poison every fresh task with
# old project state. Existing AGENT_BRANCH cherry-picks remain unchanged
# (continuing prior work). Pre-existing operator workaround in
# memory/gotchas-airouter.md item 27.
if [ -n "${AGENT_REPO_URL:-}" ] && [ -n "${AGENT_BRANCH:-}" ]; then
echo "Cloning agent working repo: $AGENT_REPO_URL (branch: $AGENT_BRANCH)"
if git clone --depth 1 --branch "$AGENT_BRANCH" "$AGENT_REPO_URL" /workspace/project 2>/dev/null; then
echo "Cloned existing branch $AGENT_BRANCH"
echo "Cloned existing branch $AGENT_BRANCH (continuing prior work)"
else
echo "Branch $AGENT_BRANCH does not exist, creating fresh clone..."
# Clone default branch, then checkout new branch
if git clone --depth 1 "$AGENT_REPO_URL" /workspace/project; then
echo "Branch $AGENT_BRANCH does not exist — seeding fresh branch from upstream reference"
# Full clone (not --depth 1) so we get a working remote for finalize.sh push.
if git clone "$AGENT_REPO_URL" /workspace/project; then
cd /workspace/project
git checkout -b "$AGENT_BRANCH"
REF_REPO=/workspace/reference/main
if [ -d "$REF_REPO/.git" ]; then
# Seed working tree from the upstream reference clone — AR-14a.
# Reference is read-only (chmod a-w), but git can still read it
# as a local-path remote for fetch + reset.
#
# Use a temporary remote name so we don't collide with 'origin'.
git remote add upstream-ref "$REF_REPO"
git fetch upstream-ref --depth 1 2>&1 | head -3 || {
echo "WARNING: failed to fetch from upstream reference; falling back to fork main" >&2
git remote remove upstream-ref 2>/dev/null
git checkout -b "$AGENT_BRANCH"
}
if git rev-parse upstream-ref/HEAD >/dev/null 2>&1; then
UPSTREAM_REF="upstream-ref/HEAD"
elif git rev-parse upstream-ref/main >/dev/null 2>&1; then
UPSTREAM_REF="upstream-ref/main"
else
UPSTREAM_REF=""
fi
if [ -n "$UPSTREAM_REF" ]; then
git checkout -b "$AGENT_BRANCH" "$UPSTREAM_REF"
git remote remove upstream-ref
echo "Seeded $AGENT_BRANCH from $REF_REPO ($(git log --oneline -1)) — AR-14a"
else
git remote remove upstream-ref 2>/dev/null
git checkout -b "$AGENT_BRANCH"
echo "WARNING: upstream-ref had no resolvable HEAD; using fork main (may be stale)" >&2
fi
else
# No reference clone available — fall back to fork main.
git checkout -b "$AGENT_BRANCH"
echo "WARNING: $REF_REPO/.git not found; using fork main (may be stale)" >&2
fi
else
echo "ERROR: Failed to clone agent repo $AGENT_REPO_URL" >&2
exit 1