Commit Graph
5 Commits
Author SHA1 Message Date
funkpower 83447db964 fix a crash, a false changelog entry, and three things 1.0 would freeze
Two adversarial reviews of the release. Both said do not ship, and they were
right twice over.

The seven-day view panicked. elide() cut a title at cut.len() / 2, a byte
index into a string it had counted in characters, and every movable feast
title carries an em dash: at fourteen terminal widths, 80 among them, it
aborted with exit 101 on the README's own headline command. tests/elide.rs now
cuts every title the program can print at every width it can be asked to print
at.

The changelog's first Calendar entry was false. It said a vigil or polyeleos
saint reaches fish in the three lesser fasts; the code gives wine and oil to
every rank but great, and caps the Dormition Fast at wine and oil whatever the
rank. A reader in the Dormition Fast was told this release let a saint bring
fish. docs/versioning.md makes that section a contract, so a wrong entry there
is worse than a wrong comment.

The Python bindings read the config file where the CLI reads the environment
first, so TYPIKON_JURISDICTION moved one and not the other and the two
answered for different churches on the same machine. CI now asserts they
agree, and runs the README example checker, which was standing between the
docs and reality with nothing running it.

Three changes to the surface, made now because after 1.0.0 they are a major
version: binds is null rather than the string none where a church legislates
no binding fast at all; iCalendar UIDs carry the tradition and the discipline,
which two feeds of the same day were sharing while saying different things;
and Date's Julian Day Number is behind an accessor rather than a public field.

fastLabel, emoji and reason are documented as presentation. The fields are
stable, the wording is not, and freezing three human-facing strings for a
major version was not the intent.
2026-09-18 13:46:57 -04:00
funkpower 18df039bbc branches only; tags are release.yml's business 2026-09-18 12:51:45 -04:00
funkpower bccf5c4ad8 clone by hand; the runner has no node
Every run died at the first step: "node: executable file not found in $PATH".
actions/checkout is a JavaScript action, and the runner executes it with node
*inside the job container* — which here is a Rust image that has never carried
one. actions/cache and actions/upload-artifact are the same.

The usual answer is an image carrying node as well, which for a Rust project
means installing the toolchain on every run. Cloning by hand is four lines and
keeps the lean image, and the repository is public so it needs no credential.

release.yml collapses to a single job for the same reason. Build and publish were
split across four, which meant handing wheels between them through
upload-artifact — another JS action. Everything a release needs fits in one job,
so nothing has to be handed over at all.
2026-09-18 12:50:56 -04:00
funkpower 3e3b585b2a a jurisdiction is not just a key any more
describe() in Python was answering a different fast than the same date on the
command line: xerophagy on a Wednesday of the Great Fast read as wine_oil. Not a
cosmetic difference in a tool whose whole job is to say what may be eaten.

The cause is that Jurisdiction grew a tradition and a discipline, and config
carries all three — main.rs reads the saved jurisdiction, then the saved
tradition, then whether the Typikon's full discipline was asked for, and builds
an overridden Jurisdiction from the lot. The bindings resolved the key and
stopped, so everything the other two decide was silently dropped.

They now take the same three steps, and tradition= and typikon= are arguments, so
the library can say what the command line can. Pascha does not take them: it
follows the paschalion alone and neither can move it.

The overrides are interned rather than leaked per call. main.rs leaks one per run
and exits; a library gets called in a loop, and a leak per call adds up. The
combinations are bounded by jurisdictions x traditions x discipline, so the map
cannot grow past that.

The parity test spread widens to seven dates across both fasts and both
calendars, and clippy now runs under the feature as well — the binding code only
exists there, so a bare clippy run had never looked at it.
2026-09-18 12:33:56 -04:00
funkpower 792fd1d26f build it on tags, not on trust
Tag-driven releases. main is always releasable, a vX.Y.Z tag on it builds the
sdist and the manylinux wheels and publishes them; Cargo.toml carries the version
and the first job refuses to go on if the tag disagrees with it, because PyPI
will not let a version be reused even after it is yanked.

CI fires on push and on nothing else. This forge is public and takes pull
requests from strangers, and a runner executes whatever the workflow checks out,
so a pull_request trigger would run unread code from a stranger on the runner
host. Push-only means the runner only ever runs what someone with push rights put
there. A pull request therefore gets no automatic check: read it, push the branch,
let it run then.

No cargo fmt gate. The tables here are aligned by hand and rustfmt wants one entry
per line, which loses the shape that makes MONTHS and JURISDICTIONS readable — a
gate that fights the house style is a gate that gets ignored. Clippy stays, and
passes.

The Python job builds the bindings against the CLI it just built and runs them
against each other, because they are one engine and the only way that stays true
is if something checks.

docs/releasing.md covers what does not exist yet: Actions is off on this forge on
purpose, there is no runner, and PyPI's trusted publishing does not speak to
Forgejo — so a long-lived token has to live here, project-scoped rather than
account-wide so a compromised forge costs this package rather than all of them.
2026-09-18 12:03:43 -04:00