Skip to content

feat(ci): version-bump gate — the train's prod-hop version refusal, moved to PR time (backend#1563 Layer 1) - #172

Merged
LukasWodka merged 4 commits into
developfrom
ci/1563-version-bump-gate
Aug 6, 2026
Merged

feat(ci): version-bump gate — the train's prod-hop version refusal, moved to PR time (backend#1563 Layer 1)#172
LukasWodka merged 4 commits into
developfrom
ci/1563-version-bump-gate

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Layer 1 of tracebloc/backend#1563: the release train's version refusal, moved from the prod hop to the PR that causes it.

The train already enforces this. release-train scripts/promote-repo.sh:version_preflight fails a hop with

NOT tagged - v$ver already exists and the delta DOES touch published files

because publishing would put different bytes under a version that has already shipped. Correct, and days late. On 2026-08-05 that refusal cost a whole release leg plus a hand diagnosis (backend#1561 → tracebloc-py-package#389): pyproject.toml said 0.17.0, v0.17.0 was tagged since 2026-08-03, and the staging↔master delta touched 8 files under tracebloc/*. Nobody found out until the hop, and the person who found out was not the person who wrote it.

This PR adds the reusable workflow that asks the same question on the PR. No callers yet — one follow-up PR per repo (each targeting develop) once this is on main, since callers pin @main.

What I measured, not assumed

Parsed out of repos.yml on tracebloc/release-train@main (2026-08-06). Five repos declare a version_file, matching the ticket's list:

repo version_file publish_paths rc_tag
tracebloc-py-package pyproject.toml tracebloc/* pyproject.toml
cli VERSION cmd/* internal/* go.mod go.sum VERSION true
design-system package.json src/* true
data-ingestors tracebloc_ingestor/__init__.py tracebloc_ingestor/*
client client/Chart.yaml client/* true

Every other train repo (backend, tracebloc-engine, client-runtime, averaging-service, frontend-app, tracebloc-website, model-zoo, start-training, .github, docs, e2e-test-agent) declares no version_file and is out of scope for this gate.

How the parsing matches promote-repo.sh

read_version's case table, copied rule for rule — the anchors are the load-bearing part, and each covers a real trap:

extension rule why the anchor matters repo
*.json jq '.version', then ^X.Y.Z(-pre)? not a dependency pin elsewhere in the file design-system
*.yaml / *.yml first ^version: keeps appVersion: out of the answer client
*.toml first ^version *= *" py-package
*.py first ^__version__ *= *["'] a SCHEMA_VERSION constant is not the package version data-ingestors
anything else first ^X.Y.Z at the start of a line an unanchored match reads a version out of a leading comment (backend#1427) cli

Same semver regex, prerelease suffix preserved the same way. publishable_delta is copied too: space-separated globs, matched with shell case against both .filename and .previous_filename (a file renamed out of a published tree left the package, and GitHub reports only its new path), one line per file so the 300-file cap counts files rather than paths, and the same yes / no / cannot-tell trichotomy where cannot-tell is treated exactly like yes (backend#1439).

scripts/tests/version-bump-gate-selftest.sh asserts all five formats against their real shapes, including the traps above. That test is the parity claim; without it "same rules" is an assertion in a comment.

Two deliberate divergences, both strictly tighter

  1. set -f. for pat in $PUBLISH_PATHS is a glob context — bash expands cmd/* against $PWD before the loop body sees it. promote-repo.sh is saved only by running from a checkout where no publish path happens to exist. A coincidence of the working directory is not a property, and the selftest has a case that runs the gate from a directory containing a matching tree.
  2. One paginated matching-refs/tags/v read instead of a per-version git/ref/tags/v$ver probe. That probe collapses 404 (not released) and 403 / rate limit (could not tell) into the same falsy result — it fail-opens on a bad day. One listing either reads or it does not, and it feeds the regression guard too, so both questions come from the same bytes. (Measured: git/ref/tags/v0.10 on cli returns a clean 404, so the endpoint is exact-match — the divergence is about error handling, never about the answer.)

The one place I did not implement the ticket literally

backend#1563 words Layer 1 as "fails when published paths change but the version file is unchanged vs base". Implemented literally, that demands a bump on every published-path PR — and the train has never asked for that. One bump covers a whole release cycle: the first PR after a release must bump, and every later one legitimately rides the already-bumped, still-unreleased version. Reddening all of those measures the wrong thing, and a check that is wrong on the common case gets removed rather than obeyed (code-quality.yml's soft-fail note is the same lesson).

So the enforced rule is version_preflight's, verbatim:

delta touches publish_paths  AND  v<version-on-this-PR> is already tagged   ->  FAIL

Red for exactly the case #1561 hit, green for exactly the cases the train promotes. Flag it if you want the literal reading instead — it is a small change, but I think it would make the check hated.

Behaviour on each edge case

Fails (verdict — soft-fail applies):

  • published delta + version already tagged, unchanged vs base → "Bump <file> in this PR", naming the matched file and the glob it matched
  • published delta + version changed but to an already-released version → a different message: "to one that has already shipped"
  • published delta + untagged version that sorts below the current release → latest_final's regression guard; v$ver is free but GitHub's latest and the installer bootstrap would still point at the higher tag

Fails (malfunction — soft-fail never applies): missing version-file / publish-paths input; missing base/head SHA; unreadable compare; compare with no merge base; empty file list (zero files scanned is a malfunction, not a pass — GitHub omits the list on very large compares, so empty is indistinguishable from "changed nothing"); version file unreadable or unparseable on head; unreadable or unparseable at the merge base; unreadable tag listing; grep exiting >1 while searching it; 300 files with no match (the API's page cap has no truncation flag, so "nothing published changed" cannot be asserted — note the asymmetry: a match is sound on a truncated list, so a large compare that does match still gets a precise answer).

soft-fail downgrades the verdict, never the machinery. A gate that cannot evaluate is broken, and broken must look different from clean in both modes — backend#1409's entire finding was six repos carrying a green check that had never compared anything. Three selftest cases assert exactly this.

Passes (each with a stated reason, never a silent one):

  • docs-only / CI-only / test-only — the delta matches no publish path, so nothing this repo ships changed. Green even with the version stale and already released: that is precisely the no-publishable-delta no-op the train grew in backend#1439, and refusing here would leave such a PR with no green path at all. The message names the file count and the globs it scanned against, so a green check is auditable rather than merely quiet. This is the case the cli comment in repos.yml describes — the 2026-08-04 staging hop refused on two CI-only pin PRs.

  • the PR bumps to an unreleased version

  • the PR changes published files but the base already carries an unreleased bump ("no bump is owed")

  • the PR introduces the version file (absent at the merge base is a definite answer, not a read failure)

  • release-train promotion PRs — head release-train/* or hotfix-backmerge/*, from this repository, into staging/main/master. Evaluated and green with a reason, not skipped. Not a courtesy: the train scopes its preflight per hop via tag_expected, and py-package and data-ingestors declare no rc_tag, so their develop → staging hop deliberately runs no version preflight. Gating that PR here would block a promotion the train is entitled to make — the exact disagreement this file exists to avoid. The cargo PRs were each gated on their way into develop.

    All three conditions are checked because a branch name is not a credential (tightened in be406be/f64a263 after self-review). A head ref is chosen by whoever opens the PR, so release-train/anything alone is an exemption anybody could mint, from a fork included. The base branch and the head repository are properties of a real promotion rather than claims made by its author, and a promotion never targets develop. The fork test demands a confirmed false, not merely "not true" — head.repo is absent on a PR from a deleted fork, and an exemption granted on a field nobody could read is the same fail-open shape as backend#1409. A PR that merely borrows the name is evaluated normally, with a warning naming the mismatch: a bypass that does not apply should be visible, not silently absent.

Override: skip-version-gate label, checked first, ahead of every refusal. public-pii-gate.yml shipped its override below the refusals, so on exactly the PRs that needed rescuing the refusal exited first. It buys a merge, not a release — the train still refuses at the prod hop.

House rules

  • Fails closed, with a named reason per path. "Unreadable" and "verified" never share a message.
  • A step that fails, never a job-level if: — a skipped required check leaves PRs pending forever.
  • No paths: filter on the caller and none here. All the path judgment happens inside the job, where "not applicable" is a green result with a reason rather than an absent one.
  • Nothing to SHA-pin: the gate uses no actions at all. It reads the version file, compare and tag list over the API — which is also what makes the parse byte-identical to the train's and what lets it work on a fork PR head. The selftest workflow does use actions/checkout and actions/setup-python, both pinned to full commit SHAs (D10).
  • No secrets, contents: read only, so callers pass no secrets: line (RFC-BACKEND-1405 Q5).
  • soft-fail defaults true — migration setting, with the four-step ladder to required in the header. Same shape as code-quality.yml.
  • One env-driven run: block, so every attacker-controlled value (branch name, label names) arrives as an environment variable rather than spliced into script text — and so the selftest can extract and execute it. If a future edit interpolates ${{ }} into the body, the selftest fails loudly rather than silently covering less.

Config source, and the drift it implies — stated plainly

version_file and publish_paths live in release-train's repos.yml, which is the authority. This workflow cannot read it: release-train is private, and a reusable called from another repo runs with that repo's GITHUB_TOKEN. Handing five repos a cross-repo PAT to run a PR check costs more than the drift it would close, so the caller declares the values and the job echoes both into the summary on every run, where a wrong one is visible rather than silent. Keeping them in step is a two-file edit, exactly like repos.yml vs repo-inventory.yml's release_train flag.

Test

bash scripts/tests/version-bump-gate-selftest.sh
51 passed, 0 failed

51 cases against a stubbed gh: the #1561 shape, every legitimate pass, every fail-closed path, the soft-fail/machinery split, the override's ordering and its exact-match requirement, rename-out-of-tree, glob safety under a hostile $PWD, and all five repos' version file formats with their wrong-line traps. actionlint -shellcheck and shellcheck are clean; house-rules.sh reports no findings.

Follow-ups (not in this PR)

  1. Callers, one PR per repo against develop, after this reaches main: py-package, cli, design-system, data-ingestors, client.
  2. repo-inventory.yml gains version-bump-gate.yml under reusables: with an entry per repo in the caller PRs, not here — the inventory header's own rule is "land the caller first, flip the entry after", and a required entry added before any caller exists fails the drift guard on day one.
  3. Layer 2 (auto-open the bump PR) stays open on backend#1563. This file only refuses; it never edits a version.

Closes nothing — backend#1563 stays open for Layer 2.

Refs backend#1563, backend#1561, backend#1439, backend#1427, backend#1409.


Note

Medium Risk
The gate will block merges once callers enable it and turn off soft-fail; incorrect version-file/publish-paths or parity drift from the train could false-positive or false-negative on release-critical PRs.

Overview
Adds a reusable workflow_call PR gate that mirrors release-train version_preflight: fail when the PR diff touches publish paths and the version read from the caller’s version-file is already tagged. The job uses gh only (no checkout actions), parses versions the same way as promote-repo.sh, matches publish globs including renames, fail-closes on API/parse ambiguity, defaults soft-fail: true, honors skip-version-gate first, and auto-passes vetted release-train / hotfix-backmerge promotions.

Testing & adoption: New scripts/tests/version-bump-gate-selftest.sh extracts the gate’s env-driven run: block and runs 51 cases against a stubbed gh; version-bump-gate-selftest.yml runs that on changes to the gate. README tells repos with a version_file in repos.yml to add a thin caller (not included here).

Reviewed by Cursor Bugbot for commit 99e9206. Bugbot is set up for automated code reviews on this repo. Configure here.

…PR time (backend#1563 L1)

The release train already refuses to publish changed bytes under an
already-released version (promote-repo.sh:version_preflight). Correct, and
days late: backend#1561 spent a whole release leg discovering that
py-package's pyproject.toml still said 0.17.0 while v0.17.0 was tagged and
the delta touched tracebloc/*.

This asks the same question on the PR that causes it.

  delta touches publish_paths AND v<version-on-this-PR> is already tagged -> red

Not "the version file changed vs base", which #1563 words it as: that would
demand a bump on every published-path PR, and one bump has always covered a
whole release cycle. A check that is wrong on the common case gets removed.

Parity is the point, so read_version and publishable_delta are copied from
promote-repo.sh rule for rule — the per-extension anchors included, since
those are what keep appVersion, a dependency pin and a leading comment out of
the answer. Two deliberate divergences, both strictly tighter: `set -f` (a
publish path is a glob, and promote-repo.sh only survives it by running where
no publish path exists), and one paginated tag listing instead of a
per-version git/ref probe that cannot tell 404 from 403.

Fails closed on every unreadable input, and `soft-fail` does not reach any of
them: it downgrades the verdict, never the machinery. Zero files scanned is a
malfunction. No paths: filter and no job-level if:, so a required check can
never go missing on a PR.

45-case decision table in scripts/tests/, run offline against a stubbed gh,
covering every fail-closed path and all five repos' version file formats.

Callers follow in one PR per repo, once this is on main.
@LukasWodka LukasWodka self-assigned this Aug 6, 2026
@LukasWodka

Copy link
Copy Markdown
Contributor Author

Replayed against production, not just fixtures

The selftest proves the decision table; this proves the gate reads the real repos correctly. I extracted the run: block and ran it, unmodified, against 16 real merged PRs across all five version_file repos, using the live API.

The case this exists for — py-package #380 (fix/1495-sdk-block-hf-models, merged 2026-08-04, one of the PRs backend#1561's diagnosis names):

::error::pyproject.toml still says 0.17.0, v0.17.0 is ALREADY RELEASED, and this PR
changes a published file: tracebloc/validation/rewriter.py (matches 'tracebloc/*').
Bump pyproject.toml in this PR. [...] Highest released final version: 0.17.0.
8 file(s) scanned against [tracebloc/* pyproject.toml].
rc=1

Red, on the PR, four days before the prod hop refused — naming the file to edit. That is the whole ticket.

The passes, all correct, all on real deltas:

PR verdict
py-package #383 (BUGBOT.md) green — No publishable delta, 1 file
py-package #389 (the manual fix for #1561) green — bumped 0.17.0 -> 0.18.0
py-package #388 (release-train/to-master) green — promotion PR, train owns the preflight
cli #461, #459, #457 green — VERSION stays at 0.10.3, CI/docs deltas
design-system #112, #107, #106 green — package.json stays at 1.0.7
client #613 green — client/Chart.yaml stays at 1.9.15
client #608 green — stays at 1.9.14
client #606 green — bumped 1.9.14 -> 1.9.15, publishable delta
data-ingestors #457 green — __init__.py stays at 0.8.3
data-ingestors #454, #452 green — stays at 0.8.2

16 PRs, 15 green, 1 red, zero false positives — and every parser read the right number out of the right line of a real file: the bare VERSION, package.json's .version, Chart.yaml's version: (not its appVersion:), and __init__.py's __version__.

Reproduce any row:

python3 -c "import yaml;print(yaml.safe_load(open('.github/workflows/version-bump-gate.yml'))['jobs']['version-check']['steps'][0]['run'])" > /tmp/gate.sh
export GH_TOKEN=$(gh auth token) REPO_FULL=tracebloc/tracebloc-py-package \
       VERSION_FILE=pyproject.toml PUBLISH_PATHS='tracebloc/* pyproject.toml' \
       SOFT_FAIL=false LABELS='[]'
eval "$(gh pr view 380 --repo tracebloc/tracebloc-py-package \
  --json baseRefOid,headRefOid,headRefName \
  --jq '"export PR_BASE_SHA=\(.baseRefOid) PR_HEAD_SHA=\(.headRefOid) PR_HEAD_REF=\(.headRefName)"')"
bash /tmp/gate.sh

(The run: block being extractable and env-driven is the same property the selftest depends on — it is why this replay is possible at all, and why a future ${{ }} in the body fails the selftest loudly.)

…omotion exemption

Self-review before Bugbot. The release-train exemption keyed on the head ref
alone, and a head ref is chosen by whoever opens the PR: `release-train/anything`
was an exemption anybody could mint, from a fork included. It only skipped a
version check the train re-runs at the prod hop, so nothing could actually
publish through it — but "the gate is disarmed by naming your branch" is not a
property to leave in a gate.

Now all three must hold, and the extra two are facts about a real promotion
rather than claims made by its author: head ref prefix, head in THIS repository
(never a fork), base in staging/main/master. A promotion never targets develop,
so a PR that borrows the name to reach develop is evaluated normally — with a
warning naming the mismatch, because a bypass that does not apply should be
visible rather than silently absent.

Also: an EMPTY version-file read is retried on purpose (it is the exact symptom
of the Contents API lag that left py-package untagged on 2026-07-29), so the
message after three failed attempts now names both possibilities instead of
saying only "could not read".

50 cases, 4 of them new and all four red before this change.
…y 'not true'

head.repo is absent on a PR from a deleted fork, so IS_FORK arrives empty and
'not true' read that as 'same repo' — granting the promotion exemption on a
field nobody could read. Exempting on an unread field is the shape of every
fail-open in backend#1409.

Three-way now: false exempts, true says fork, anything else says the head
repository could not be confirmed. Evaluating a genuine promotion by mistake
costs a re-run; exempting a fork by mistake costs the gate.
@LukasWodka

Copy link
Copy Markdown
Contributor Author

Rollout signal, measured before anyone flips soft-fail: false

Replaying the gate over more history turned up something a reviewer should see before the caller PRs land: design-system would be red on its ordinary workflow today. Four recent merged PRs that touch src/*:

PR verdict
#77 feat/73-tooltip-controlled-click red — package.json still says 1.0.5, v1.0.5 released, src/components/atoms/Tooltip/Tooltip.css
#71 chore/sync-develop-with-main red — same, src/components/atoms/Button/Button.css
#67 feat/65-tooltip-portal-button-cta red — 1.0.2 released, src/components/atoms/Button/Button.css
#63 fix/61-inline-input-icons red — 1.0.2 released, src/components/atoms/Input/Input.test.tsx

These are true positives, not noise. design-system declares rc_tag: true, so tag_expected is true on its staging hop as well as prod — which is exactly the state repos.yml already documents: "a repo with rc_tag becomes un-hoppable the moment its version file equals its last final release, which is what design-system hit on the 2026-07-31 staging hop." Every one of those PRs was a contribution to that refusal. The gate is reporting a real, recurring condition at the moment it is created rather than at the hop.

Three things follow, and none of them changes the code in this PR:

  1. This is why soft-fail defaults to true. Arming this repo as a required check on day one would redden its normal flow before the team has adopted "bump with the change" — the exact way a linter gets removed instead of obeyed. The ladder in the header exists for this case; design-system is the repo that will need step 2 (watch the verdicts) to last longer than a day.
  2. One question for repos.yml, not for this gate. design-system's publish_paths is src/*, and src/ holds *.test.tsx. So a test-only change inside src/ reads as publishable — fix(pii-gate): run under restrictive default token perms (contents-scoped Compare API) #63 is that case. The gate is faithfully reproducing the declaration rather than inventing a rule of its own, which is the whole design goal; if tests are not meant to be publishable, the fix belongs in repos.yml where both layers pick it up at once. Filing that separately rather than special-casing it here.
  3. The other four repos are clean. py-package, cli, data-ingestors and client showed zero false positives across the sample in the comment above — the single red there is backend#1561's own PR.

I would still land the callers with the defaults (report-only) and let a week of verdicts decide when each repo flips.

@LukasWodka

Copy link
Copy Markdown
Contributor Author

Filed the repos.yml question from the comment above as tracebloc/backend#1605 — whether design-system's publish_paths: src/* is meant to cover src/**/*.test.tsx. Kept out of this PR on purpose: the fix, if there is one, belongs in repos.yml where the train's preflight and this gate both read it, so they move together.

@LukasWodka

Copy link
Copy Markdown
Contributor Author

Correction + the replay caveat, dated precisely

I widened the replay to the last 12 merged PRs in client, cli and data-ingestors (36 more PRs) and found two reds — which sent me back to date every red properly. One of the four design-system rows in my previous comment was wrong. Correcting it, because "measure, don't assume" does not stop at the first measurement.

The caveat, which is a property of the gate and not a bug: the gate asks "is v<head version> tagged now?". At PR time a bump PR's target version is by definition untagged — the train cuts the tag after the merge. Replay a bump PR months later and its version is tagged, so it re-judges red. Every artifact below has that one shape.

PR verdict dated
py-package #380 genuine v0.17.0 tagged 2026-08-03, PR merged 2026-08-04, no bump — backend#1561 itself
design-system #63 genuine v1.0.2 tagged 2026-06-17, merged 2026-07-20, 1.0.2 → 1.0.2
design-system #67 genuine v1.0.2 tagged 2026-06-17, merged 2026-07-22, 1.0.2 → 1.0.2
design-system #77 genuine v1.0.5 tagged 2026-07-23, merged 2026-07-28, 1.0.5 → 1.0.5
design-system #71 artifact — I was wrong bumped 1.0.2 → 1.0.5, merged 2026-07-23T09:09:11Z; v1.0.5 tagged 2026-07-23T09:10:30Z — 81 seconds later. Green at the time.
cli #447 artifact chore(release): bump VERSION to 0.10.3, 0.10.2 → 0.10.3, merged 2026-08-03; v0.10.3 published 2026-08-04. Green at the time.
data-ingestors #440 artifact bumped 0.8.1 → 0.8.2, merged 2026-08-03; v0.8.2 published 2026-08-04. Green at the time.

Corrected totals over everything replayed (52 merged PRs across all five repos): 4 genuine reds, 3 replay artifacts, 45 green, 0 false positives. The genuine reds are backend#1561's own PR and three design-system PRs — the rc_tag staging-refusal pattern repos.yml already documents. client, cli, data-ingestors and py-package are otherwise clean, so design-system remains the one repo that should sit on soft-fail: true longest.

The src/**/*.test.tsx question from the previous comment is unaffected: design-system#63 is still a genuine red, still on a test file, and still a repos.yml decision rather than a gate one — tracebloc/backend#1605.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f64a263. Configure here.

Comment thread .github/workflows/version-bump-gate.yml
…r match

Bugbot, .github#172. The yaml/toml/py branches take `head -1` on the LINE,
then run `grep -oE "$semver"` on it -- and grep -oE prints one line per match,
not per input line. A version line carrying a second semver in a trailing
comment

    version = "1.2.3"  # bumped from 1.0.0

therefore parses to the two-line value "1.2.3\n1.0.0". Measured, not
theorised.

Here that corrupts the regression guard: `sort -V | tail -1` answers 1.2.3
while `"$HEAD_VER"` is the whole two-line string, so the `!=` compare is true
and the gate refuses a perfectly legitimate bump. The bare-file branch already
guarded this with a trailing head -1; the other three did not.

The json branch is safe -- jq emits a single value and the pattern is anchored
with ^ -- so it is left alone rather than given a cosmetic head -1.

This same defect is in promote-repo.sh:read_version, which this file copies
rule for rule; it is fixed there in release-train#40. In the train the
consequence is worse than a false refusal: the parsed value is interpolated
into the tag-probe URL, a malformed URL 404s, and the caller reads that as
"this version is not released" -- fail-OPEN on the one check that stops a
re-publish under an existing tag.
@LukasWodka

Copy link
Copy Markdown
Contributor Author

Fixed in 99e92067, and the finding turned out to be bigger than this PR.

Confirmed by measurement, exactly as described — grep -oE prints one line per match, so the line-level head -1 is not enough:

version = "1.2.3"  # bumped from 1.0.0     ->  parses to "1.2.3\n1.0.0"

Here that corrupts the regression guard: sort -V | tail -1 answers 1.2.3 while $HEAD_VER is the whole two-line string, so the != compare is true and the gate refuses a legitimate bump. Added the trailing head -1 to the .yaml, .toml and .py branches. The .json branch is genuinely safe — jq emits a single value and the pattern is ^-anchored — so it is left alone rather than given a cosmetic guard.

The same defect is in promote-repo.sh:read_version, which this file copies rule for rule — so the finding lands against the original too, and there it is worse. The parsed value is interpolated into the tag probe gh api repos/$REPO/git/ref/tags/v$ver; a two-line value builds a malformed URL, the probe 404s, and version_preflight reads that 404 as "this version is not released" — skipping the already-released refusal entirely. Fail-open on the one check that stops a re-publish under an existing tag, versus fail-closed here.

Fixed there in release-train#41 with three regression cases (one per branch, since they are three independent greps). Mutation-checked: 465 pass with the fix; revert the fix and keep the tests and exactly those 3 fail. Both copies now carry the identical parse, which is the parity property this gate is built on.

Good catch — it survived review here precisely because it was faithful to the source.

@LukasWodka
LukasWodka merged commit 16375d2 into develop Aug 6, 2026
8 of 10 checks passed
@LukasWodka
LukasWodka deleted the ci/1563-version-bump-gate branch August 14, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants