Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,17 @@ dependencies = [
# not evidence that an API is present, which is the whole reason that test exists.
#
# RAISED TO 0.5.0 for crucible.validation.monitor (EdgeBaseline / edge_monitor),
# which orchestrate.decay and EdgeDecayTrigger are built on. Unlike the 0.3.0 note
# above, this constraint is NOT yet true: at the time of writing the monitor sits in
# crucible's [Unreleased] and the newest published crucible is 0.4.0. This must not
# merge before crucible 0.5.0 is on PyPI, or `pip install crucible-stack` resolves
# to a crucible without the monitor and `import crucible_stack.orchestrate` raises.
"crucible>=0.5.0",
# which orchestrate.decay and EdgeDecayTrigger are built on. That note used to warn
# the constraint was not yet true; crucible 0.5.0 published 2026-08-02, so it is.
#
# RAISED TO 0.6.0 for Thresholds.monitor_arl0_years, which --arl0-years constructs.
# This one was briefly WRONG rather than aspirational: the flag merged while the
# floor still said 0.5.0, whose Thresholds has no such field, so a fresh
# `pip install crucible-stack` could resolve a crucible that raises TypeError the
# first time anyone passed it. Nothing caught it, because every local checkout
# already had 0.6.0 installed and a floor is only exercised by the version you do
# NOT have. Raise the floor in the same change as the API, never afterwards.
"crucible>=0.6.0",
]

[project.optional-dependencies]
Expand Down
39 changes: 39 additions & 0 deletions tests/test_crucible_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,42 @@ def test_edge_monitor_still_refuses_to_rebuild_its_own_baseline():
"trades", "baseline", "design", "thresholds"}, (
"edge_monitor's signature changed. The decay trigger depends on there being no "
"parameter from which a baseline could be rebuilt; re-check before relaxing this.")


def test_the_false_alarm_budget_can_be_set_in_years():
"""`--arl0-years` builds `Thresholds(monitor_arl0_years=...)`, added in crucible
0.6.0. On 0.5.0 that field does not exist and the dataclass raises TypeError the
first time anyone passes the flag, which is a long way from the version constraint
that allowed it."""
from crucible.validation import Thresholds
try:
t = Thresholds(monitor_arl0_years=10.0)
except TypeError as exc: # pragma: no cover
pytest.fail(
f"the installed crucible predates the calendar-time budget ({exc}). "
"--arl0-years requires Thresholds.monitor_arl0_years; install crucible "
">= 0.6.0.")
assert t.monitor_arl0_years == 10.0
# and the trades fallback is still there for a baseline with no known firing rate
assert getattr(t, "monitor_arl0_trades", None) is not None


def test_the_floor_is_not_lower_than_the_apis_this_package_uses():
"""The floor is a claim about the OLDEST crucible that works, and the tests above
only ever run against whatever is installed, which is normally the newest. So check
the declared floor itself, and fail when someone adds an API without raising it.

Keep `MIN` in step with the `crucible>=` pin in pyproject.toml and with the newest
crucible feature this package touches.
"""
import pathlib
import re

MIN = "0.6.0" # bump with the pin when a newer API is adopted
text = (pathlib.Path(__file__).resolve().parents[1] / "pyproject.toml").read_text()
m = re.search(r'"crucible>=([0-9.]+)"', text)
assert m, "no crucible floor found in pyproject.toml"
assert m.group(1) == MIN, (
f"pyproject pins crucible>={m.group(1)} but this package uses APIs added in "
f"{MIN}. A floor satisfied by a crucible that cannot run the code is worse than "
"no floor: it resolves, installs, and fails at the call site.")
Loading