Skip to content

Commit 8420dbf

Browse files
Merge pull request #12 from sofiapetrusenko/phase-3b2-polarity
Phase 3b-2: polarity as a declared input; the §(e) falsifier fired
2 parents 89dfe19 + 71cf5b5 commit 8420dbf

35 files changed

Lines changed: 3081 additions & 190 deletions

‎.github/workflows/ci.yml‎

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
11
name: CI
22

3+
# Triggers, and why this shape. `push:` with no branch filter keeps every branch covered from
4+
# its first push, which is how DEBT E1 was closed and how this project actually works: a phase
5+
# branch is pushed and looked at before a PR exists. `pull_request:` is kept ONLY for forks --
6+
# a fork's push events do not reach this repository, so without it a fork PR would run nothing.
7+
#
8+
# The `if:` on each job is what removes the duplication (DEBT E3). Without it, a same-repository
9+
# PR runs the whole workflow twice for one commit: once for the push to the branch, once for the
10+
# pull_request event. The condition runs a job on any push, and on a pull_request only when the
11+
# head repository is a different one -- so same-repo work is covered exactly once, by its push,
12+
# and fork PRs are covered exactly once, by the pull_request event.
13+
#
14+
# WHAT THIS COSTS, stated rather than discovered later. A `pull_request` build checks out the
15+
# merge commit (`refs/pull/N/merge`); a `push` build checks out the branch head. Skipping
16+
# `pull_request` for same-repository PRs therefore means a PR is only ever tested at its branch
17+
# head, so a branch that is green against the `main` it was cut from and broken against the
18+
# `main` it will land on goes untested until the merge. That is the price of not running every
19+
# job twice, it is accepted deliberately, and the cheap partial mitigation is to rebase before
20+
# merging. Reinstating `pull_request` for same-repo PRs restores merge-commit testing and the
21+
# duplication together -- they are the same switch.
322
on:
423
push:
524
pull_request:
625

726
jobs:
827
checks:
28+
# See the trigger comment above: push always, pull_request only from a fork.
29+
if: >-
30+
github.event_name != 'pull_request'
31+
|| github.event.pull_request.head.repo.full_name != github.repository
932
runs-on: ubuntu-latest
1033
steps:
1134
- uses: actions/checkout@v4
@@ -115,19 +138,12 @@ jobs:
115138
- name: Dev-split eval
116139
run: python -m evals.run --config configs/default.yaml
117140

118-
# Every dev-split figure quoted in NOTES.md and configs/*.yaml is transcribed from
119-
# evals/dev_sweeps.json, and tests/test_recorded_figures.py checks the
120-
# transcription on every pytest run. This step checks the other half: that the
121-
# record itself still reproduces from the committed gold set. It re-runs every recorded
122-
# sweep: about nine and a quarter minutes of CPU on an arm64 developer machine (two
123-
# measurements, 9m09s and 9m19s of CPU time; this is compute, not I/O). Background
124-
# estimation dominates it -- about 1.4 s per image, once per distinct background parameter
125-
# set swept -- and the QC/normalization records add one further pipeline pass, about 43 s
126-
# of that total. That is why this is a CI step and not
127-
# part of pytest. Three review cycles were lost to figures that had silently stopped
128-
# reproducing.
129-
- name: Recorded dev-split figures still reproduce
130-
run: python -m evals.sweep --check
141+
# `Recorded dev-split figures still reproduce` (python -m evals.sweep --check) used to
142+
# run here, unconditionally, and was by a wide margin the slowest thing in CI. It moved
143+
# to .github/workflows/figures.yml in Phase 3b-2 so that it runs only when a path that
144+
# can change a figure changes. It was NOT weakened and it was not made optional: the same
145+
# command runs, on the same trigger shape, gated on paths rather than on nothing. DEBT E3
146+
# carries the reasoning and what remains open.
131147

132148
# DEBT E1: "nobody has ever followed the documented install-and-run path on another
133149
# machine". This job is that path, on a runner that has never seen this repository.
@@ -146,6 +162,10 @@ jobs:
146162
# path exists, it is not run here: substituting one would test the better path and leave the
147163
# documented one unverified, which is the whole of E1.
148164
install-path:
165+
# Same trigger discipline as `checks`; see the comment on the `on:` block.
166+
if: >-
167+
github.event_name != 'pull_request'
168+
|| github.event.pull_request.head.repo.full_name != github.repository
149169
runs-on: ubuntu-latest
150170
steps:
151171
# A fresh clone into an empty workspace: no cached venv, no prior artifacts.
@@ -242,7 +262,8 @@ jobs:
242262
run: |
243263
python -m venv .venv && . .venv/bin/activate
244264
pip install -r requirements.txt
245-
python -m pipeline run data/images/dev_02.png --config configs/default.yaml --out results/
265+
python -m pipeline run data/images/dev_02.png --config configs/default.yaml --out results/ \
266+
--polarity bright_on_dark
246267
247268
# The result document is the deliverable of that path, so the path is not verified until
248269
# the document is. Validated against the committed schema with the jsonschema that

‎.github/workflows/figures.yml‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Recorded figures
2+
3+
# DEBT E3. `python -m evals.sweep --check` re-measures every recorded dev-split figure from the
4+
# committed gold set. It is load-bearing -- it is what catches a figure that has silently stopped
5+
# reproducing, and three review cycles were lost to exactly that -- and it is also the slowest
6+
# thing in CI by a wide margin: about nine and a quarter minutes of CPU on an arm64 developer
7+
# machine (two measurements, 9m09s and 9m19s of CPU time; this is compute, not I/O). Wall-clock
8+
# on the runner, over all 33 non-zero runs of this step in the repository's Actions history,
9+
# read 2026-08-24: 4m54s to 7m42s, median 6m59s. An earlier note gave 6m23s-7m41s "as read from
10+
# step timings", which was one pair of runs written as though it were the survey. Background estimation dominates it, about
11+
# 1.4 s per image once per distinct background parameter set swept, and the QC/normalization
12+
# records add one further pipeline pass, about 43 s of that total.
13+
#
14+
# So it is scoped rather than removed. A recorded figure is a function of six things: the gold set
15+
# the sweep actually reads (data/ -- evals/sweep.py's DATA_DIR is Path("data"), so the images and
16+
# truth records live there, NOT in synth/), the generator that writes it (synth/), the parameters
17+
# swept (configs/), the measurement code (pipeline/), the sweep and its record (evals/), and the
18+
# dependency versions everything above runs on (requirements.txt). A commit that touches none of
19+
# those six cannot move a figure, and running the sweep against it is the waste E3 describes.
20+
#
21+
# The count has been wrong twice, so it is worth saying how to check rather than trusting it:
22+
# the list below is the authority, and this sentence is a description of it. If they disagree,
23+
# the list is right.
24+
#
25+
# data/ is in the list because leaving it out was a live hole rather than a tidiness point: a
26+
# commit that regenerates the gold set, or edits one image or one truth record, touches no other
27+
# pattern, so the check would not run and a recorded figure could silently stop reproducing --
28+
# the exact failure this step exists to catch, reachable through the change that scoped it.
29+
# requirements.txt is the sixth for a reason one step removed from the other five: a numpy or
30+
# OpenCV version can move a figure without a line of this repository's own code changing.
31+
#
32+
# This lives in its own workflow file rather than as an `if:` on a step, because `paths:` is a
33+
# native trigger filter and an `if:` would need the changed-file list computed by hand -- a shell
34+
# diff against a base ref that is wrong on a branch's first push and on a force-push. The native
35+
# filter has no such edge, and no third-party action is introduced to get it.
36+
#
37+
# THE TRADE-OFF, stated rather than discovered later: a workflow skipped by `paths:` reports no
38+
# status at all, not a passing one. If this job is ever made a required status check on `main`,
39+
# a documentation-only PR will wait forever for a check that will never run. It is not required
40+
# today. Making it required means replacing the `paths:` filter with an always-running job that
41+
# decides internally whether to do the work -- which is the shell-diff design this file avoids,
42+
# and it should be taken deliberately, not as a side effect of turning on branch protection.
43+
#
44+
# `.gitignore`, docs, and every workflow file EXCEPT this one are deliberately not in the list:
45+
# none of them can change a measurement. This file is in the list, because a change to the filter
46+
# itself must run the job it just re-scoped -- otherwise a filter edited to exclude something
47+
# would take effect without the sweep ever running under the new rule.
48+
on:
49+
push:
50+
paths:
51+
- "evals/**"
52+
- "configs/**"
53+
- "pipeline/**"
54+
- "synth/**"
55+
- "data/**"
56+
- "requirements.txt"
57+
- ".github/workflows/figures.yml"
58+
pull_request:
59+
paths:
60+
- "evals/**"
61+
- "configs/**"
62+
- "pipeline/**"
63+
- "synth/**"
64+
- "data/**"
65+
- "requirements.txt"
66+
- ".github/workflows/figures.yml"
67+
68+
jobs:
69+
recorded-figures:
70+
# The same de-duplication as ci.yml, and for the same reason: without it a same-repository
71+
# PR runs this nine-minute job twice for one commit. Push covers same-repo work;
72+
# pull_request covers forks, whose pushes never reach this repository.
73+
# The same merge-commit trade-off ci.yml records applies here; see the note there.
74+
if: >-
75+
github.event_name != 'pull_request'
76+
|| github.event.pull_request.head.repo.full_name != github.repository
77+
runs-on: ubuntu-latest
78+
steps:
79+
- uses: actions/checkout@v4
80+
81+
- uses: actions/setup-python@v5
82+
with:
83+
python-version: "3.11"
84+
85+
# No `if [ -f requirements.txt ]` guard and no pytest: this job runs one command, that
86+
# command needs the runtime dependencies and nothing else, and a missing requirements.txt
87+
# must fail here rather than be skipped into a run against whatever the image ships.
88+
# ci.yml's install carries the guard for historical reasons; this file is new and does not.
89+
- name: Install dependencies
90+
run: |
91+
python -m pip install --upgrade pip
92+
pip install -r requirements.txt
93+
94+
# Every dev-split figure quoted in NOTES.md and configs/*.yaml is transcribed from
95+
# evals/dev_sweeps.json, and tests/test_recorded_figures.py checks the transcription on
96+
# every pytest run -- which still happens on every push, in ci.yml. This step checks the
97+
# other half: that the record itself still reproduces from the committed gold set.
98+
- name: Recorded dev-split figures still reproduce
99+
run: python -m evals.sweep --check

0 commit comments

Comments
 (0)