feat(b3-6): nightly Docker smoke of dev in its own workflow, plus a timeout on the CI verify job (#1452)

* feat(b3-6): nightly docker smoke on dev — its own workflow, plus a timeout on ci.yml's verify job

dev is not a push trigger, so an image regression on dev is only found when a
dev -> main PR opens. A nightly at 03:00 UTC closes that window.

Not a schedule on ci.yml, which is what the plan proposed: a scheduled run
attaches its check runs to the default branch's tip, so the jobs skipped to
scope the nightly to the smoke would land on main's tip as `skipped` under
seven of the twelve required contexts. verify-gate-evidence.mjs:45-61 keeps
the latest attempt per name and does not count `skipped` as success, and
release.yml's gate-evidence job gates every build and publish job on it — so
a tag cut from a main tip that had sat through one nightly would be refused.
A separate file writes one check run, under a name that is no required
context, and leaves ci.yml's job selection untouched.

The nightly checks out dev explicitly, since a schedule always reads the
workflow from the default branch. Its build and smoke steps are the
server-docker-build ones verbatim — same pinned actions, same commands, same
Server/scripts/docker-smoke.sh that release.yml runs — with a keep-in-sync
comment on both jobs.

ci.yml's only change is `timeout-minutes: 20` on server-docker-build. The
plan asserted that B1-7's guard check already enforced a timeout there; it
does not (check-workflow-guards.mjs audits only the workflows in METERED,
which is claude.yml alone), and the job had none, so it inherited GitHub's
360-minute default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

* docs(b3-6): evidence block for item 8 — the deviation, the gate conflict behind it, and the proof command

Records why the nightly is its own workflow when the item says it is not:
a skipped job still writes a check run (observed on main's tip, where Tauri
Full Build reports `skipped`), a scheduled run attaches to the default
branch's tip, and verify-gate-evidence.mjs:45-61 would then read seven of the
twelve required contexts as skipped on the commit a release is tagged from.

Also: that a schedule only runs from the default branch, so the nightly does
not start until this file reaches main at the next release merge; the
contents of the new workflow against the job it mirrors; the controller's
proof command with the observed-SHA placeholder; and the false premise in the
item's "B1-7's guard check enforces both", which is what the one-line ci.yml
timeout answers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

* docs(b3-6): item 8 — proof recorded (run 33301623322), cache-scope note, comment count

Run 33301623322 fired from the temporary push trigger, now dropped: "Print
checked-out revision" logged event=push on the branch ref, and
git rev-parse HEAD printed 75d64dd412 — dev's
tip at the time, not the branch's, which is what `ref: dev` exists to do.
Build and boot-smoke green.

Two facts the evidence block was missing. A scheduled run has
github.ref = refs/heads/main, so the buildx type=gha cache is scoped to the
default branch while the layers come from dev's tree — the only behavioural
difference from the PR job, and harmless because the cache is
content-addressed. And a red nightly reaches the repository owner, by
GitHub's scheduled-workflow failure email.

ci.yml's release-gate comment said docker-smoke.sh is called "from both
workflows"; it is three now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmiqjgTuov1stBTB6uGkvo

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
J3vb
2026-08-30 14:43:03 +00:00
committed by GitHub
co-authored by Claude Fable 5
parent 1e9ac9a842
commit 746f60f789
3 changed files with 153 additions and 1 deletions
+5 -1
View File
@@ -219,7 +219,8 @@ jobs:
# R-09 / RL-16. The release gate itself is only invoked for real at tag
# time, which is the wrong place to find a bug in it — so its decision
# logic is exercised here, on every pull request, against fixtures. Same
# reason Server/scripts/docker-smoke.sh is called from both workflows.
# reason Server/scripts/docker-smoke.sh is called from all three
# workflows (ci.yml, release.yml, nightly-docker-smoke.yml).
# This also parses the required-check list out of
# b0-dev-branch-protection.sh, so a change to that list's shape fails here
# rather than silently weakening the gate.
@@ -532,10 +533,13 @@ jobs:
# Image build is verification only, so it is skipped on dev to keep day-to-day
# work on the fast check suite. Runs for main pushes and PRs targeting main.
#
# Keep in sync with nightly-docker-smoke.yml's nightly-docker-smoke job.
server-docker-build:
name: Server Docker Build (verify)
if: github.ref_name == 'main' || github.base_ref == 'main'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
@@ -0,0 +1,68 @@
# Nightly Docker boot smoke against dev (B3-6 item 8). dev is not a push
# trigger in ci.yml, so an image regression on dev is otherwise only caught
# once a dev -> main PR opens.
#
# Its own file rather than a schedule on ci.yml, which is what the plan first
# proposed: a scheduled run attaches its check runs to the DEFAULT branch's
# tip, so the jobs that would have to be skipped to scope the nightly to the
# smoke would land on main's tip as `skipped` under names that are required
# contexts. scripts/verify-gate-evidence.mjs:45-61 keeps the latest attempt
# per name and does not count `skipped` as success, so release.yml's
# gate-evidence job would then refuse to tag that commit. The job name below
# matches no required context, and ci.yml is left alone.
#
# A schedule only ever runs from the default branch: this file does nothing
# until it reaches main, and the first nightly follows the next release merge.
name: Nightly Docker Smoke
on:
schedule:
- cron: "0 3 * * *"
workflow_dispatch:
concurrency:
group: nightly-docker-smoke-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
# Keep in sync with ci.yml's server-docker-build job.
nightly-docker-smoke:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
# This file is read from the default branch on a schedule; dev is the
# branch the nightly exists to smoke.
ref: dev
# So every run's log states what was smoked, rather than leaving it to be
# re-derived from the trigger.
- name: Print checked-out revision
env:
EVENT: ${{ github.event_name }}
REF: ${{ github.ref }}
run: |
echo "event=$EVENT ref=$REF"
git rev-parse HEAD
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Build image (no push)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: Server/
push: false
load: true
tags: owncord-smoke:candidate
build-args: VERSION=ci
cache-from: type=gha
cache-to: type=gha,mode=max
# Same script release.yml and ci.yml run.
- name: Boot-smoke Docker image
run: bash Server/scripts/docker-smoke.sh owncord-smoke:candidate