Skip to content

Document three shipped behaviours the spec left invisible or misdescribed #507

Document three shipped behaviours the spec left invisible or misdescribed

Document three shipped behaviours the spec left invisible or misdescribed #507

Workflow file for this run

name: Spec sync
# Enforces that a change to a header sub-domain (include/morph/<sub>/**) is
# accompanied by a change to the matching design-spec folder (docs/spec/<sub>/**),
# so the specs cannot silently drift behind the code they describe.
#
# The gate is skipped when the PR carries the label "no docs update" — a visible,
# reviewable opt-out for changes that genuinely need no spec update.
on:
pull_request:
branches:
- master
# `labeled`/`unlabeled` so adding or removing the escape-hatch label re-runs
# the gate instead of leaving a stale result.
types: [opened, synchronize, reopened, labeled, unlabeled]
# Supersede a run a newer commit on the same ref has made obsolete -- see the
# note in ci.yml. This one is pull_request-only, so it supersedes on a force-push
# or a re-label rather than on a merge burst.
concurrency:
group: spec-sync-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
spec-sync:
name: Header ↔ spec sync
runs-on: ubuntu-24.04
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no docs update') }}
steps:
- name: Checkout (full history)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Enforce header ↔ spec synchronization
run: |
set -euo pipefail
# The base commit, without asking git to compute one.
#
# `pull_request` checks out `refs/pull/N/merge` -- a merge commit whose
# first parent *is* the base branch commit the PR was merged against.
# So HEAD^1 is the base by construction, needs no network, and cannot
# go stale while the job runs.
#
# It replaces `git fetch --no-tags --depth=1 origin "$BASE_REF"`
# followed by `git merge-base HEAD "origin/$BASE_REF"`, which failed
# roughly one run in twenty for a reason unrelated to specs
# (morph#186). Two things were wrong with it. The re-fetch was a
# no-op: actions/checkout above already runs with `fetch-depth: 0`, so
# `origin/$BASE_REF` is present, complete and current, and the fetch
# updated no ref -- its only observable effect was writing
# `.git/shallow`, grafting history at the fetched tip. And once the
# repository was shallow, `git merge-base` could no longer see the
# merge base whenever master had advanced between the PR event and
# this job starting, so it exited 1 with no output at all and
# `set -e` aborted the step before a single path was examined.
#
# The gate then reported "Header ↔ spec sync: failure" on pull
# requests that touched no header whatsoever, which is worse than
# useless -- it teaches reviewers to merge past it. Note it was never
# re-runnable green either: `github.sha` stays pinned to the event
# while `origin/$BASE_REF` only moves further ahead.
if ! base="$(git rev-parse --verify HEAD^1 2>/dev/null)"; then
echo "::error::Could not determine the base commit (HEAD has no first parent)."
echo "This gate compares against HEAD^1, the base side of the pull_request merge ref."
echo "If this workflow is ever run outside a pull_request event, that assumption does not hold."
exit 1
fi
echo "Comparing against base $base (HEAD^1, the base side of the merge ref)"
changed="$(git diff --name-only "$base" HEAD)"
echo "── Changed files ──"
echo "$changed"
echo "───────────────────"
# Sub-domains that have a mirrored spec folder. include/morph/detail/**
# and include/morph/qt/** have no specs and are intentionally exempt.
subdomains="core journal offline session forms util"
missing=""
for sub in $subdomains; do
if echo "$changed" | grep -qE "^include/morph/$sub/"; then
if ! echo "$changed" | grep -qE "^docs/spec/$sub/"; then
missing="$missing $sub"
fi
fi
done
if [ -n "$missing" ]; then
echo ""
echo "::error::Header changes without a matching spec update in:$missing"
for sub in $missing; do
echo " - include/morph/$sub/** changed but docs/spec/$sub/** did not."
done
echo ""
echo "Update the design spec(s) for the affected sub-domain(s), or add the"
echo "label 'no docs update' to this PR if no spec change is warranted."
exit 1
fi
echo "Spec sync OK: every touched header sub-domain has a matching spec change."