Skip to content

fix: run the version floors in generate/check/upgrade; normalize the bgfx remote; floor the 2.x core line (#739, #742) - #746

Merged
apotema merged 2 commits into
mainfrom
fix/739-742-floors
Sep 18, 2026
Merged

apotema merged 2 commits into
mainfrom
fix/739-742-floors

Conversation

@apotema

@apotema apotema commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Closes #739. Closes #742.

Both issues harden the floor validators #736/#733 added to init, and both touch the same tables — one PR.

The tables themselves moved from init_cmd.zig to a new src/version_floors.zig (which also drops init_cmd.zig from 1089 to 575 lines). init_cmd re-exports the public names, so there is still exactly ONE bgfx_core_floors and ONE trio_floors; every door now runs them.


Gap 1 (#742.1) — isOfficialBgfx compared the raw repo string

parseProjectConfig keeps PluginDep.repo verbatim and the fetch path clones github.com/…/labelle-bgfx, https://github.com/…/labelle-bgfx and ….git alike — but the materials gate compared against the bare literal, so an alternate spelling of the OFFICIAL repo was treated as a custom provider and skipped contractViolation.

BEFORE (standalone, pre-fix binary) — examples/condenser (owns materials/) with .backend_package = .{ … .repo = "https://github.com/labelle-toolkit/labelle-bgfx.git", .version = "0.20.0" }:

$ labelle-assembler install --project-root …/matspell_pre
info:   fetching backend bgfx 0.20.0 (remote)      ← the spelling clones fine
$ labelle-assembler generate --project-root …/matspell_pre
info: labelle-assembler: generated .labelle/bgfx_desktop/
PRE_GEN_EXIT=0        ← slipped through; left to the MATERIAL_CONTRACT_VERSION guard

FIX — config.normalizeRemote / config.sameRemote: strip the scheme, strip a trailing / and .git, case-fold the host, compare the path exactly. local: paths are returned untouched and compare verbatim. material_pipeline.isOfficialBgfx (and the new generate-time gate) use it.

AFTER:

$ labelle-assembler generate --project-root …/matspell_post
error: materials/: game-owned materials require material contract v2
  (labelle-bgfx >= 0.21.0 on labelle-core >= 2.0.0); project pins labelle-bgfx 0.20.0.
  Bump the pin, or drop it to take the default.
error: labelle-assembler: generate failed: MaterialContractUnsupported
POST_GEN_EXIT=1

A genuinely custom provider (github.com/acme/…, local:…) is still left to the generated contract guard — the normalization does not widen the gate.


Gap 2 (#742.2) — no reverse floors for the 2.x core line

Every trio rule keyed off a NEW gfx or engine, so the new CORE against an old engine/gfx passed. Added, ordered after the gfx/engine-subject rules so an old core under a new gfx keeps naming gfx as the subject:

  • core >= 2.0.0 ⇒ engine >= 3.0.0 (compile break)
  • core >= 2.0.0 ⇒ gfx >= 2.0.0 (compile break)

BEFORE (standalone, pre-fix):

$ labelle-assembler init initpre --engine-version=2.12.2 --gfx-version=1.30.1
info: labelle-assembler: created project 'initpre' in initpre/
PRE_EXIT=0
$ grep _version initpre/project.labelle
    .core_version = "2.0.0",  .engine_version = "2.12.2",  .gfx_version = "1.30.1",

AFTER:

$ labelle-assembler init initpost --engine-version=2.12.2 --gfx-version=1.30.1
error: labelle-assembler init: labelle-core 2.0.0 requires labelle-engine >= 3.0.0;
  got core 2.0.0 / engine 2.12.2 / gfx 1.30.1 (core 2.0.0 removed the PixelWater
  contract an engine below 3.0.0 still compiles against). Pass --engine-version=3.0.0
  (the curated set is core 2.0.0 / engine 3.0.0 / gfx 2.0.0).
POST_EXIT=2        ← nothing written

Gap 3 (#739) — the validators beyond init

generate / check

version_floors.enforce(cfg, …) runs in generate() before any target file is written, and in cmdCheck before the lint. configBackendCoreFloorViolation resolves effectiveBackendPackage() — an explicit .backend_package, else the builtin provider the .backend tag is shorthand for — so the explicit-package case, which init has no flag for and could never reach, is covered here.

BEFORE (standalone, pre-fix) — a project hand-edited to core 1.32.0 / engine 2.12.2 / gfx 1.30.1 under the builtin bgfx 0.21.0:

$ labelle-assembler generate --project-root …/handedit_pre
info: labelle-assembler: generated .labelle/bgfx_desktop/
PRE_GEN_EXIT=0        ("generate failed" count: 0)

$ cd …/handedit_pre/.labelle/bgfx_desktop && zig build
…/.labelle/deps/labelle-bgfx/src/gfx/material_store.zig:3:16: error: root source file
  struct 'root' has no member named 'shader_material'
…/.labelle/deps/labelle-bgfx/src/gfx/texture.zig:604:18: error: no field named 'shader'
  in struct 'backend_contract.Material'
PRE_BUILD_EXIT=1      ← the #731-class failure, deep inside a dependency

AFTER:

$ labelle-assembler generate --project-root …/handedit_post
error: labelle-assembler generate: bgfx 0.21.0 requires labelle-core >= 2.0.0; got 1.32.0
  (the backend names core's `shader_material` contract ungated and asserts material
  contract v2, which only core >= 2.0.0 declares — it fails to compile).
  Pass --core-version=2.0.0, the core it was released against.
error: labelle-assembler: generate failed: VersionFloorViolation
POST_GEN_EXIT=1

$ labelle-assembler check --project-root …/handedit_post
error: labelle-assembler check: bgfx 0.21.0 requires labelle-core >= 2.0.0; got 1.32.0 (…)
CHECK_EXIT=2

upgrade — a different shape

generate/check judge a config that already exists; upgrade must judge the config it is about to write. The command was restructured to decide every prospective pin first (next_core / next_engine / next_gfx / next_cli), validate, and only then rewrite — so a refusal leaves project.labelle byte-identical.

One further difference: the gate refuses only when the upgrade actually moves a floor-bearing pin. upgrade cli on a project that is already below a floor cannot make it worse, and blocking it would stop an unrelated bump — so that case warns and proceeds. upgrade all / bare upgrade still snap the trio to the curated set, which repairs such a project.

BEFORE / AFTER (standalone):

# pre-fix
$ labelle-assembler upgrade --project-root …/up_pre core 1.32.0
info: labelle-assembler: upgrading core to 1.32.0
PRE_EXIT=0        → .core_version = "1.32.0"

# post-fix
$ labelle-assembler upgrade --project-root …/up_post core 1.32.0
error: … bgfx 0.21.0 requires labelle-core >= 2.0.0; got 1.32.0 (…)
error: … labelle-gfx 2.0.0 requires labelle-core >= 2.0.0; got core 1.32.0 / engine 3.0.0 / gfx 2.0.0 (…)
error: labelle-assembler upgrade: VersionFloorViolation — project.labelle left unchanged
POST_EXIT=2       → .core_version = "2.0.0"  (untouched)

# post-fix, unrelated bump on an already-broken project
$ labelle-assembler upgrade --project-root …/up_cli3 cli 1.61.2
warning: … pins are already below a hard floor and cannot build — run
  'labelle-assembler upgrade all' to snap them to the compatible set
info: labelle-assembler: upgrading cli to 1.61.2      EXIT=0

# post-fix, upgrade all REPAIRS it
$ labelle-assembler upgrade --project-root …/up_all all      EXIT=0
    .core_version = "2.0.0", .engine_version = "3.0.0", .gfx_version = "2.0.0"

Verification method

The assembler prefers SIBLING checkouts over pins (findRepoRoot walks up from the executable), so an in-tree run proves nothing. Every result above comes from the whole checkout mirrored outside the toolkit tree (…/scratchpad/pre|post/labelle-assembler, binary included), driven with a scratch LABELLE_HOME so packages are fetched from the registry at the pinned tags. Each fix has a negative control: the pre-fix binary exhibits the bug, the post-fix binary does not. Every generate log was grepped for generate failed, and exit codes were captured separately from any pipeline.

Regression evidence (standalone, post-fix, zig build — not generate-only)

project install generate zig build
init --backend=bgfx (defaults) 0 0 0
examples/condenser (bgfx 0.21.0 + materials) 0 0 0
examples/material-demo (bgfx 0.20.0 / core 1.32.0) 0 0 0
examples/pack-city (bgfx 0.20.0 / core 1.32.0) 0 0 0
examples/tile-explorer (raylib) 0 0 —
examples/sprite-runner (sokol) 0 0 —
examples/camera-builtin (null) 0 0 —

(#732: CI stops at generate for bgfx games, which is how this class of break hides — hence the full builds.)

Tests

  • zig build test --summary all → 2926/2938 passed (12 skipped), exit 0 (baseline on main: 2917/2929, exit 0) — +9.
  • zig build test-materials → exit 0. Note the test-materials name filter does not collect material_pipeline tests (test-materials step never runs material_pipeline tests (name filter excludes them) #741), so the new ones were checked through the full suite.
  • All six new tests force-failed in one pass and confirmed collected: 2917/2938 passed (12 skipped, 9 failed), exit 1 — then restored.

New tests: remote normalization (every accepted spelling official, forks/other hosts/case-differing paths still custom, local: untouched); isOfficialBgfx + contractViolation on the https://….git spelling; the 2.x reverse floors (including that an old core under a new gfx still names gfx); the resolved-config gate for the builtin provider and an explicit .backend_package, asserting WHICH table refused and that a curated floor warns rather than refusing.

https://claude.ai/code/session_01XBGRLojiysEr5zy1U5mbP6


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…e bgfx remote, floor the 2.x core line (#739, #742)

Two holes left by #736's `init`-only validators, closed against the same
tables — no second copy of the floors.

#742.1 — `isOfficialBgfx` compared the RAW repo string, so the
`https://…/labelle-bgfx.git` spelling of the official repo (which the
fetch path clones happily) was classified as a CUSTOM provider, skipped
`contractViolation`, and failed at the generated
`MATERIAL_CONTRACT_VERSION` guard instead of at generate with the pin
named. `config.normalizeRemote`/`sameRemote` strip the scheme and a
`.git` suffix and case-fold the host; `local:` paths compare verbatim.

#742.2 — the trio rules all keyed off a NEW gfx or engine, so
`init --engine-version=2.12.2 --gfx-version=1.30.1` under the default
core 2.0.0 scaffolded a set the table's own docs call incompatible. Two
reverse rules (core >= 2.0.0 floors engine >= 3.0.0 and gfx >= 2.0.0),
ordered after the gfx/engine-subject rules so an old core under a new
gfx keeps naming gfx as the subject.

#739 — the floors moved from `init_cmd.zig` to `version_floors.zig`
(which also drops init_cmd under the 1000-line line) and now run at
every door that writes or resolves the pairing:

  * `generate`/`check` — `configBackendCoreFloorViolation` resolves the
    EFFECTIVE backend package, so an explicit `.backend_package` (which
    `init` has no flag for, so it was unreachable before) is judged too,
    in every spelling of the official remote. A non-official provider
    carries its own semver and is left to the generated contract guard.
  * `upgrade` — validated on the PROSPECTIVE pins, before the rewrite
    lands, so a refusal leaves project.labelle untouched. It refuses only
    when the upgrade moves a floor-bearing pin: `upgrade cli` on an
    already-below-floor project warns and proceeds rather than blocking
    an unrelated bump, and `upgrade all` still REPAIRS such a project.

`enforce` splits into a pure `verdict` (so tests assert which table
refused, without emitting error logs) and the logging wrapper the
commands call. The #736 severity split is unchanged: compile break
refuses, curated floor warns.

Verified STANDALONE (whole checkout mirrored outside the toolkit,
scratch LABELLE_HOME, registry packages), each with a negative control
on the pre-fix binary:
  * bgfx 0.20.0 via `https://…/labelle-bgfx.git` + materials/: before
    generate exit 0; after, refused naming the 0.20.0 pin.
  * `init --engine-version=2.12.2 --gfx-version=1.30.1`: before exit 0
    and a scaffolded project; after exit 2, nothing written, all three
    pins + the floor named.
  * hand-edited core 1.32.0 / engine 2.12.2 / gfx 1.30.1 under builtin
    bgfx 0.21.0: before generate exit 0 and `zig build` died with
    "root source file struct 'root' has no member named 'shader_material'"
    inside labelle-bgfx; after, refused at generate.
  * `upgrade core 1.32.0`: before rewrote the pin; after exit 2,
    unchanged.
  * Regression: default `init --backend=bgfx` and the condenser /
    material-demo / pack-city examples generate AND fully `zig build`
    (exit 0); tile-explorer, sprite-runner, camera-builtin generate.

Tests: 2926/2938 passed (12 skipped), exit 0 — +9 over the 2917/2929
baseline; all six new tests force-failed and confirmed collected.

Closes #739
Closes #742

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBGRLojiysEr5zy1U5mbP6
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

  • Ask an admin to enable usage-based reviews

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Next included review available in 5 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available. Your 61 included PR review attempts over the past 7 days set your current allowance at 4 reviews per hour.

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 2334f0c0-a00c-4e2c-903e-0617a0c330e0

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc6510 and 582d0c1.

📒 Files selected for processing (8)
  • src/cache/fetch.zig
  • src/cache_cmd.zig
  • src/check_cmd.zig
  • src/config.zig
  • src/init_cmd.zig
  • src/material_pipeline.zig
  • src/root.zig
  • src/version_floors.zig

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-18T19:53:56.880792Z 582d0c1 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 838744472d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/version_floors.zig Outdated
Comment on lines +428 to +431
const bp = cfg.effectiveBackendPackage() orelse return null;
if (!isOfficialProvider(cfg.backend, bp)) return null;
if (!config.isSemverVersion(bp.version)) return null;
return providerCoreFloorViolation(cfg.backend, bp.version, cfg.core_version);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Identify explicit providers without consulting the ignored enum

When a project selects the official bgfx repository solely through .backend_package, ProjectConfig specifies that the default .backend enum is ignored, but this code still compares the package against builtinProvider(cfg.backend) and then uses that enum to choose the floor table. With the normal default .backend = .raylib, an explicit bgfx 0.21.0 package paired with core 1.32.0 is therefore classified as non-official, so generate, check, and upgrade all miss the compile-breaking bgfx/core floor. Determine the provider type from the resolved package repository rather than the ignored enum.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 582d0c1. isOfficialProvider(cfg.backend, bp) is replaced by officialBackendOf(bp), which walks the Backend values, compares bp.repo against each builtin provider's repo through sameRemote, and returns the tag the PACKAGE identifies. configBackendCoreFloorViolation selects the floor table with that, never with cfg.backend.

Standalone negative control (checkout mirrored outside the toolkit tree with a scratch LABELLE_HOME, so sibling checkouts cannot mask the pins) — your exact config, .backend left at its default:

.backend_package = .{ .name = "bgfx", .repo = "github.com/labelle-toolkit/labelle-bgfx", .version = "0.21.0" },
.core_version = "1.32.0",
binary labelle-assembler check
pre-fix exit 0 — labelle check: no packs found — nothing to lint
post-fix exit 2 — bgfx 0.21.0 requires labelle-core >= 2.0.0; got 1.32.0 … Pass --core-version=2.0.0

The new unit test asserts the mechanism, not just the verdict: officialBackendOf(bp) must return .bgfx, and it pins that the bgfx repo really does differ from the raylib remote so the test cannot pass for the wrong reason. A genuine third-party provider (github.com/acme/bgfx) is still left alone.

Comment thread src/config.zig Resolved
Comment thread src/version_floors.zig Outdated
Comment on lines +350 to +351
pub fn trioFloorViolation(core_version: []const u8, engine_version: []const u8, gfx_version: []const u8) error{UnparsableVersionPin}!?TrioViolation {
if (!config.isSemverVersion(core_version) or !config.isSemverVersion(engine_version) or !config.isSemverVersion(gfx_version)) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Evaluate floors that do not involve a non-release pin

If any one of the three pins is non-semver, this early return suppresses every trio rule, including rules whose subject and requirement are both known release pins. For example, core_version = "local:../core", engine 3.0.0, and gfx 1.30.1 bypasses validation even though the table independently declares that engine 3.x requires gfx >= 2.0.0; likewise, a local gfx pin hides the known engine-3/core-1 incompatibility. Skip only rules whose own subject or required pin cannot be parsed so generate, check, and upgrade still reject the provably incompatible release pair.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 582d0c1. The blanket early return is gone; each rule is skipped only when ITS OWN subject or requirement pin is unparseable (a rule reads exactly two of the three pins — the third is only quoted in the diagnostic). Decidability is per rule, not per trio.

Standalone negative control, your first example verbatim:

.core_version = "local:../labelle-core", .engine_version = "3.0.0", .gfx_version = "1.30.1"
binary labelle-assembler check
pre-fix exit 0
post-fix exit 2 — labelle-engine 3.0.0 requires labelle-gfx >= 2.0.0; got core local:../labelle-core / engine 3.0.0 / gfx 1.30.1 … Pass --gfx-version=2.0.0

Your second example is covered too: a local GFX pin with core 2.0.0 / engine 2.12.2 now reports the core-2.x/engine-3.x break. The new test asserts WHICH rule fired (subject and requirement package, plus severity), not merely that something was returned, and pins the negative side — an undecidable trio (local: on all three) and a local: pin over a coherent pair both still return null, so the change cannot invent violations.

One existing assertion encoded the old blanket behaviour: trioFloorViolation("local:../labelle-core", "2.12.2", "2.0.0") expected null, but gfx 2.0.0 under engine 2.12.2 is a compile break the table states without reference to core. It now asserts that violation, with the coherent 3.0.0/2.0.0 pair kept beside it as the negative control.

Full suite 2928/2940, exit 0 (+2 tests); force-failing each takes it red.

Comment thread src/root.zig Outdated
Comment on lines 371 to 374
try version_floors.enforce(cfg, "labelle-assembler generate");

const backend_manifest_name = manifest_detect.detectV2ManifestName(allocator, cfg, game_dir);
try manifest_splice.requireManifestIfExternal(allocator, cfg, game_dir, backend_manifest_name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the manifest validation error ordering

For an external provider that both lacks its required manifest and has incompatible release pins, this newly inserted floor check exits before requireManifestIfExternal, despite the surrounding invariant that a manifest-less external must report that clearer configuration error first. Such a project is told to change framework versions even though the selected provider is unusable regardless, and only discovers the missing-manifest problem afterward. Move the floor enforcement after requireManifestIfExternal while keeping both checks ahead of target-file emission.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 582d0c1 — version_floors.enforce now runs AFTER detectV2ManifestName + requireManifestIfExternal, still ahead of validateProviderContracts and of any target-file emission. The invariant is spelled out at the call site: a configuration error that invalidates the provider outright precedes an error about how the provider is pinned.

Standalone negative control — a manifest-less local: external backend plus a trio compile break (core 2.0.0 / engine 2.12.2 / gfx 1.30.1), labelle-assembler generate:

binary first error
pre-fix labelle-core 2.0.0 requires labelle-engine >= 3.0.0 … → generate failed: VersionFloorViolation
post-fix external backend 'acme' … ships no backend.manifest.zon → generate failed: ExternalBackendNeedsManifest

And the reorder did not drop the floor: dropping a backend.manifest.zon into that provider and re-running post-fix brings back VersionFloorViolation with the same diagnostic, exit 1.

…on, restore the manifest-first ordering, evaluate decidable trio rules (#746 review)

Four review findings, all verified standalone against a pre-fix binary
(mirrored checkout outside the toolkit tree with a scratch LABELLE_HOME,
so sibling checkouts cannot mask the pins).

1. [P1] Explicit providers were identified via the IGNORED `.backend`
   enum. When a project selects the official bgfx repo solely through
   `.backend_package`, `ProjectConfig` documents that `.backend` is
   ignored and it sits at its meaningless `.raylib` default — yet
   `isOfficialProvider(cfg.backend, bp)` compared the bgfx package
   against the RAYLIB remote, answered "third party", and dropped the
   floor. `officialBackendOf(bp)` now derives the backend from the
   resolved package alone.
   Control: bgfx 0.21.0 + core 1.32.0 via `.backend_package` —
   `check` pre-fix exit 0 ("nothing to lint"), post-fix exit 2 naming
   `bgfx 0.21.0 requires labelle-core >= 2.0.0`.

2. [P2] `normalizeRemote` did not strip `?query`/`#fragment` (or a
   `git+` prefix outside the one `git+https://` spelling), while the
   fetch path's `repoHostPath` did — so
   `git+https://github.com/labelle-toolkit/labelle-bgfx?ref=main`
   FETCHED the official backend and was then judged custom. The
   divergence was the bug, so `repoHostPath` now delegates to
   `config.normalizeRemote`: one normalizer, and the identity a gate
   compares is by construction the identity the fetcher resolves.
   Control: that exact repo string, `.backend = .bgfx` so finding 1
   cannot confound — pre-fix exit 0, post-fix exit 2.

3. [P2] The floor check ran BEFORE `requireManifestIfExternal`, so an
   external provider that both lacked its manifest and carried bad pins
   was told to change framework versions while the real blocker — a
   provider unusable at any version — went unmentioned. Moved after it,
   still ahead of any target-file emission.
   Control: manifest-less `local:` provider + a trio compile break —
   pre-fix reports VersionFloorViolation, post-fix reports
   ExternalBackendNeedsManifest; adding the manifest brings the floor
   error back, so the reorder did not drop the check.

4. [P2] One non-semver pin suppressed EVERY trio rule. Each rule reads
   exactly two pins, so decidability is per rule: a rule is skipped only
   when its own subject or requirement is unparseable.
   Control: `core_version = "local:../labelle-core"`, engine 3.0.0, gfx
   1.30.1 — pre-fix exit 0, post-fix exit 2 naming
   `labelle-engine 3.0.0 requires labelle-gfx >= 2.0.0`.

Two in-tree assertions that encoded the old blanket behaviour were
updated and now assert WHICH rule fires, not merely that the result is
null.

`zig build test --summary all`: 2928 pass / 2940 total, exit 0 (was
2926/2938 — +2, the new tests). Force-failing each takes the suite to
2 fail, exit 1.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBGRLojiysEr5zy1U5mbP6
@apotema

apotema commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

All four review findings addressed in 582d0c1.

Every fix was verified standalone — the checkout mirrored outside labelle-toolkit/ with a scratch LABELLE_HOME, so the assembler's sibling-checkout preference cannot mask the pins — with a pre-fix binary built from the branch tip as the negative control.

# finding control pre-fix post-fix
1 (P1) provider identified via the ignored .backend enum official bgfx 0.21.0 via .backend_package only, core 1.32.0 check exit 0 exit 2, bgfx 0.21.0 requires labelle-core >= 2.0.0
2 (P2) normalizeRemote kept ?query/#fragment git+https://github.com/labelle-toolkit/labelle-bgfx?ref=main (with .backend = .bgfx, so #1 cannot confound) check exit 0 exit 2, same floor named
3 (P2) floor check preceded requireManifestIfExternal manifest-less local: provider + trio compile break generate → VersionFloorViolation → ExternalBackendNeedsManifest
4 (P2) one non-semver pin suppressed every trio rule core = "local:../labelle-core", engine 3.0.0, gfx 1.30.1 check exit 0 exit 2, labelle-engine 3.0.0 requires labelle-gfx >= 2.0.0

Notes on the shape of each fix:

  • Fix: rewrite relative .path deps when copying local libs (#129) #1 officialBackendOf(bp) derives the backend from the resolved package alone (walks the Backend values, compares repos through sameRemote). cfg.backend is never consulted for this decision.
  • feat: scan animations/ directory for .zon animation definitions #2 The divergence was the bug, so there is now one helper: cache/fetch.zig's repoHostPath delegates to config.normalizeRemote. The identity a gate compares is by construction the identity the fetcher resolves. normalizeRemote also gained the general git+ strip (it previously handled only the one git+https:// spelling).
  • feat: Android build generation (#133) #3 Reordered after requireManifestIfExternal, still ahead of validateProviderContracts and any target-file emission. Verified the reorder did not drop the check: adding the manifest brings VersionFloorViolation back.
  • fix: use b.graph.host for Zig 0.15.2 in iOS build template #4 Per-rule decidability — a rule reads exactly two pins, so it is skipped only when its own subject or requirement is unparseable. Two in-tree assertions encoded the old blanket behaviour and were updated; they now assert which rule fires, with the coherent pair kept beside them as a negative control.

Tests: zig build test --summary all → 2928 pass / 2940 total, exit 0 (was 2926/2938 — +2). Force-failing each new test individually takes the suite to 2 fail, exit 1, with the tests named.

No overlap with examples/condenser/ (#744), ci.yml / repo build.zig / test/ (#745), or src/material_schema.zig / src/material_build.zig (#747/#748). Files touched: src/version_floors.zig, src/config.zig, src/cache/fetch.zig, src/root.zig.

https://claude.ai/code/session_01XBGRLojiysEr5zy1U5mbP6

@apotema
apotema merged commit ced9241 into main Sep 18, 2026
8 checks passed
@apotema
apotema deleted the fix/739-742-floors branch September 18, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant