2 Commits
Author SHA1 Message Date
bvandeusenandClaude Opus 5 c0370069e0 release: the first release describes the product; it has nothing to diff against
Build images / sign-extension (push) Successful in 4s
CI / lint (push) Successful in 4s
CI / extension-version (push) Successful in 5s
Build images / build-ml (push) Successful in 7s
Build images / build-agent (push) Successful in 8s
Build images / build-web (push) Successful in 7s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 36s
CI / integration (push) Successful in 1m42s
Step 7 needs a release that reads as "what is FabledCurator and how do I run
it". What the script would actually have published is "changes since
v26.06.04.0" over 533 commits, truncated to 200 — a release page whose first
screen is the internal build-out that milestone 328 exists to stop shipping,
addressed to a reader who has never seen this project.

Two causes, fixed separately.

**A pre-convention tag is history, not a predecessor.** The 28 `v26.*` tags
were kept when their releases were deleted, so `--match v*` walks ancestry
straight back to one of them. Reachable is not comparable: nobody has run
v26.06.04.0 and its release page no longer exists to compare against. The
match is now `v[0-9][0-9][0-9][0-9].*` — rule 148's shape, which is exactly
the set of tags naming a release a reader could have been running.

**With that narrowed, the first rule-148 tag reaches no predecessor**, and the
old fallback — diff against the whole history — is worse than the problem it
replaced. A release with no predecessor now renders the product overview and
no commit list at all.

The overview is READ OUT OF README.md between `<!-- overview:start -->` and
`<!-- overview:end -->`, not written into the script, for the same reason the
changelog is derived: two hand-maintained descriptions of one product drift
and nothing ever catches it. The release page and the repo front page are one
source. Missing markers are reported as a note and publish anyway, on
cross_checks()'s reasoning — the release is still the useful object.

Every later release goes back to being a changelog, which is what note #3127
§5 says a release is for. MAX_COMMITS still guards the case it now guards:
two real releases far enough apart that the list stops being readable.

Also corrected while marking up the README: "Importing — ingests an existing
library from disk" was still advertising the folder-import feature that
3590c47 documented as deliberately retired. Replaced with what FC actually
does with what arrives — content-hash dedup, sidecar metadata, provenance.

Tests: the two that asserted the old no-predecessor behaviour are rewritten
rather than left; synthetic repos now carry their own copy of the script,
since the overview resolves relative to `__file__` (correct in production,
where release.yml checks out the tag) and would otherwise have every fixture
silently quoting FabledCurator's real README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TTjbZZ6JirCMSaJzQV1RhA
2026-09-02 12:28:48 -04:00
bvandeusen b6b9fd8287 ci: a release publishes a changelog, not an image (318 step 7)
CI / extension-version (push) Successful in 3s
CI / lint (push) Successful in 3s
Build images / build-ml (push) Successful in 4s
extension / lint (push) Successful in 25s
CI / backend-lint-and-test (push) Successful in 30s
CI / integration (push) Successful in 3m52s
Build images / sign-extension (push) Successful in 4s
Build images / build-agent (push) Successful in 5s
Build images / build-web (push) Successful in 4s
CI / frontend-build (push) Successful in 18s
Step 2 took the build consequence away from a `v*` tag — `main` has already
built and published the commit by the time anyone tags it, and rebuilding
would re-push `:c-<sha>`, which rule 145 forbids even when the source
matches. That left the tag with nothing to do at all.

This is the job it has instead. Step 6 put the derived version in the
Settings footer, so an operator can say WHICH build they are running; this
says what is in it that was not in the one they ran last month. Both halves
of one question (note #3127 §5).

The previous release is found by walking ANCESTRY, not by sorting a list.
That is load-bearing here specifically: rule 148 moved the tag shape from
`v26.05.22.0` to `v2026.08.28.2208`, and lexicographically `v2026...` sorts
BEFORE `v26...` — the third character is `0` against `6`. A sorted
implementation would reach back past every new-shape tag to the newest
old-shape one and publish months of commits as "changes since", looking
entirely correct while doing it. `git describe --exclude` is immune to the
shape change, and reachability is the more honest question anyway.

The publisher GETs and PATCHes rather than POSTing and recovering the id
from a 409 — note #3127 §6.7, which is ThoughtSync #2182's bug. A `v*` tag
is created once so the conflict path is rare, but "rare" is how that one
survived to be found somewhere else.

Cross-checks are reported on the release, not enforced. The tag is already
pushed by the time this runs, so failing would leave the operator with a tag,
no release, and a red lane to explain it — while the release is still the
useful object. It says so at the top when the tag names a version the web
image does not report, or when the commit is not on `main` and the `:c-`
rollback refs it lists were never published.

Nothing runs on a schedule and nothing auto-tags on merge. Release tags are
bookmarks (note #3127 §0); FC went twelve weeks without one and nothing was
wrong.

Also here:
- `scripts/` joins the ruff lane. release_notes.py runs only on a tag push,
  so a syntax error there would otherwise surface at the one moment nobody
  wants to be debugging a workflow.
- version.spec.js reads the workflow directory instead of listing three
  files by hand. Its own comment says the assertion should survive consumers
  coming and going; the hardcoded list was the part that could not, and
  release.yml would have joined the directory without joining the check.

Tests build a synthetic history spanning the tag-shape change rather than
leaning on this repo's tags, so the span assertion holds whether or not a
checkout brought the tags along — a span test that quietly skips is worse
than one that fails.
2026-08-28 20:57:25 -04:00