Skip to content

measure the shortest line window that decides a seam #81

measure the shortest line window that decides a seam

measure the shortest line window that decides a seam #81

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
# One job per QUESTION, not one per command. `gates` answers "is this
# tree well-formed?" — everything that needs no other revision, so a
# fresh clone answers it. `differential` answers "did this change
# behaviour?" — everything that does, which is why it needs the whole
# history and a base commit.
#
# Every gate carries `if: ${{ !cancelled() }}` so one failing gate
# never hides the others — a push that breaks lint AND a minimum
# should say so in one run, not across two.
jobs:
gates:
name: Gates (head only)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
- run: bun run check
if: ${{ !cancelled() }}
- run: bun run lint
if: ${{ !cancelled() }}
- run: bun run fmt:check
if: ${{ !cancelled() }}
- run: bun run build
if: ${{ !cancelled() }}
# The suite, under v8 line coverage, with each file's RECORDED
# MINIMUM coverage checked afterwards — ONE suite run, not two.
# Measured at 2.8s plain against 4.0s with coverage on 8,577
# tests, so the check costs about a second and belongs in the
# blocking job rather than in a step of its own. The mutation
# half of the same minimums file is checked by `bun run mutate`,
# which is ~11 minutes and stays manual.
- run: bun run coverage
if: ${{ !cancelled() }}
# The scorecard, head-only: thirteen absolute gates plus the shape
# census's five rules, in about two seconds, needing no base and no
# network. It has been enforceable since the day it was written
# and was enforced by nobody; this line is the whole point of the
# workstream that added it.
- run: bun run metrics
if: ${{ !cancelled() }}
# The exhaustive depth-5 list-shape sweep — 111,121 documents,
# each formatted twice and rendered on both sides against a
# 158-entry allowlist, ~30s. It used to ride inside `bun run
# test`, where it was 25.6s of a 26.1s suite; it left the default
# suite ON CONDITION that it became a blocking step here and the
# prelude to every mutation run, so nothing about what is
# enforced changed. Head-only and needs no base, so it belongs in
# this job.
- run: bun run test:deeply-nested-lists
if: ${{ !cancelled() }}
# Our AST's BLOCK STRUCTURE against the oracle's, over the
# conformance corpus and the depth-4 sweep product, gated on
# scripts/block-structure-corpus.json and
# scripts/block-structure-sweep.json. About a second, head-only
# and needing no base, so it sits beside the deep sweep. The
# default suite runs the CORPUS half only; this step is what
# covers the SWEEP half, where 931 of the 932 divergences no
# other net sees actually live. `--depth 5` is available for
# anyone who wants the deeper product: it adds 16 signatures and
# no new root cause. A passing run prints an Asciidoctor object
# dump on stderr for one corpus document; see docs/harnesses.md.
- run: bun run block-structure
if: ${{ !cancelled() }}
# Every source citation in a comment - a Ruby file and line, or a
# line of the oracle build - against the file it names. Reads
# vendor/asciidoctor-ruby/ and node_modules only, takes under a
# second, needs no base: a head-only gate like the two above. It
# is its own step rather than a metrics row because it is its own
# question with its own exit-code contract, exactly as
# block-structure is.
- run: bun run citation-check
if: ${{ !cancelled() }}
# The other direction: every `<file>:<line>` this repository
# writes about ITSELF - the `what` field of a score-minimums
# exception, the coverage-deferral comments in eslint.config.js -
# against the line it names, plus every `src/...ts` path a `src`
# comment names against a file that exists. Reads the working
# tree only, takes well under a second, needs no base: a
# head-only gate beside the one above, and its own question with
# its own exit-code contract for the same reason that one is.
- run: bun run internal-citations
if: ${{ !cancelled() }}
differential:
name: Differential (vs base)
runs-on: ubuntu-latest
# FIRST ITERATION ONLY. The differential harnesses have never run
# on this machine shape, and a harness whose own footing is untested
# must not be able to block a merge on its first day. Flipping this
# to blocking is a one-line change, and the trigger for it is
# stated: three consecutive green runs on merges to main.
continue-on-error: true
steps:
# `fetch-depth: 0` because every harness here materializes
# another revision with `git archive`, and a shallow clone does
# not have one.
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
- run: bun install --frozen-lockfile
# The base is a SHA this workflow computes, never a branch name
# and never `github.event.pull_request.base.sha`. Two reasons,
# both learned here: this repository is jj-managed with a
# colocated `.git` and is routinely on no branch at all, so
# nothing may key on one; and the PR event's base sha is the base
# branch's TIP, not the merge base, so a long-lived branch would
# be diffed against commits it never contained.
- name: Resolve the base revision
id: base
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
git fetch --no-tags origin "${{ github.base_ref }}"
base=$(git merge-base FETCH_HEAD HEAD)
else
base=$(git rev-parse HEAD^)
fi
echo "sha=$base" >> "$GITHUB_OUTPUT"
echo "base is $base"
# Serialized inside ONE job on purpose. Each of these four
# materializes the base into $TMPDIR, and three of them run
# `bun install --frozen-lockfile` in it; run as parallel jobs
# they would each pay that install, and run as parallel steps
# they would pay it concurrently on one runner's disk.
#
# Parity runs the ledger gate: any case whose formatted output or
# AST differs from the base must be declared by a
# "Parity-Diff: <family> <id>" trailer in the message of a commit
# in base..HEAD. Declarations expire on their own - a commit that
# moved bytes leaves the range as soon as the base advances past
# it - so there is no file to reset. See docs/harnesses.md.
- name: parity — every corpus document and fixture, bytes and AST
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run:
bun run parity -- --base "${{ steps.base.outputs.sha }}"
--expected-diffs-trailers HEAD
- name: shape-diff — the standing grid, with per-diff render proofs
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run: bun run shape-diff -- --base "${{ steps.base.outputs.sha }}"
- name: shape-diff — the heading-adjacency grid
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run:
bun run shape-diff -- --base "${{ steps.base.outputs.sha }}" --grid
heading-adjacency
- name: shape-diff — the list-run grid
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run:
bun run shape-diff -- --base "${{ steps.base.outputs.sha }}" --grid
list-run
# The four generated domains the list-shape sweep's alphabet
# cannot spell - a hard-break line, and constructs broken across
# two source lines - as SET DIFFERENCES on both measures, so a
# domain that fixes as many documents as it breaks cannot report
# the same count and pass. About ten seconds over 12,170
# documents, and the only step here that can see a reflow hold
# rule regress. See docs/harnesses.md.
- name: probe-domains — the shape classes every other net is blind to
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run: bun run probe-domains -- --base "${{ steps.base.outputs.sha }}"
- name: metrics — the ratchets
if: ${{ !cancelled() && steps.base.outputs.sha != '' }}
run: bun run metrics -- --base "${{ steps.base.outputs.sha }}"