box/.github/workflows/ci.yml
dan-claude-bot 08d099cce2 fix: verdict contract, head-bound approvals, serialized reconcile — and a testable state machine
Round-1 blockers, all three reviewers concurring:
- COMMENTED agreement now counts: agreement_signal recognizes the live bots'
  durable markers (Verdict: Approve / I agree with everything / leading ) —
  the gate to needs-human can actually close. Formal verdicts remain the
  contract (CONTRIBUTING), this is the documented transitional workaround.
- Every counting verdict is bound to the head SHA; a stale approval parks the
  PR in addressing (agent owes re-request) instead of promoting unreviewed
  code. CHANGES_REQUESTED blocks at any head, per GitHub's own semantic.
- reconcile serializes under ONE job-level concurrency group; scope stays
  per-PR. No more cron-vs-event race on the request-the-human-once guard.
- Sweep resilience: per-PR subshell (one failure logs and continues), label
  edits warn instead of wedging; the self-heal claim now matches reality
  (dispatch-only bootstrap).
- The state machine is extracted pure (globals in, state out) and sourceable:
  test/labels-reconcile.sh proves 14 fixture transitions — comment-only
  agreement, stale approval, comment-without-verdict, human precedence and
  human-block — wired into CI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:31:00 +00:00

64 lines
2.8 KiB
YAML

name: ci
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: shellcheck
# -x follows `source`/`.` directives; box has no lib split today, but the
# flag costs nothing and keeps the invocation identical to rig's.
# globstar so a script in a new subdirectory is linted without anyone
# remembering to edit this list; bin/* covers the extensionless entrypoint
# (bin/box). The file list is printed so under-coverage shows up in the log.
run: |
shopt -s globstar
files=(bin/* **/*.sh)
printf 'shellcheck: %s\n' "${files[@]}"
shellcheck -x "${files[@]}"
- name: cli tests
run: bash test/cli.sh
- name: labels state-machine tests
run: bash test/labels-reconcile.sh
# The multi-user rehearsal, on a REAL incus — a GitHub runner is root on a
# disposable VM, which is exactly the substrate the rehearsal needs. It runs
# in container mode: the tier's mechanics (grant, confinement, the network
# contract, revoke) are identical for containers and VMs — the nft bridge
# drop, the ACL, dns.mode=none and port_isolation all bind to boxnet, not
# to the instance type. What container mode canNOT validate is the VM trust
# boundary itself; that stays a real-hardware ritual (drill/RUNS.md), same
# as the full drill. So: every PR proves the tier's semantics, and a
# release still proves the boundary.
rehearsal:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: install incus
run: |
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y incus
- name: global install, from this checkout (the #71 layout)
# Not install.sh: it installs a RELEASE (REPO@REF), and CI must prove
# the code under review. Same tree, same layout, same a+rX.
run: |
sudo cp -r . /opt/box
sudo rm -rf /opt/box/.git
sudo chmod -R a+rX /opt/box
sudo ln -sf /opt/box/bin/box /usr/local/bin/box
- name: setup-host
run: sudo bash /opt/box/host/setup-host.sh
- name: doctor — the baseline is provable before anything is judged
run: sudo BOX_TIER=admin bash /opt/box/drill/doctor.sh
- name: multi-user rehearsal (criteria a-l, container mode)
run: sudo BOX_MULTIUSER_REHEARSAL=1 bash /opt/box/drill/multiuser.sh --yes --container
# NOT run here: the full drill (drill/drill.sh). It rehearses the whole
# surface — cold template mints, expose, migration — and wants a real host
# and the better part of an hour. The rehearsal job above is the CI-shaped
# slice of the same discipline: isolation claims are still tested on a real
# daemon, never reasoned about (docs/box-design.md).