Skip to content

Phase 2, theme 2: number limits (refuse, never clamp; NUM-01..05) and the exit-code-3 rule - #31

Merged
guijacquemet merged 2 commits into
mainfrom
claude/phase2-qupath-number-limits
Oct 13, 2026
Merged

guijacquemet merged 2 commits into
mainfrom
claude/phase2-qupath-number-limits

Conversation

@guijacquemet

Copy link
Copy Markdown
Contributor

Theme 2 of the wrong-science wave for QuPath: number limits (SPEC_WRONG_SCIENCE section 6, NUM-01..05) and the worker exit-code rule (Tools PR #124). Reference: Napari #55.

This PR is based on PR #30 (claude/phase2-qupath-retention), not on main: it needs the parts.txt file split, the runs test app and the changed run_gui_ci.sh of that PR. Merge #30 as a merge commit first and retarget this PR to main.

What changes

Number limits: refuse, never clamp; the text is the value

  • Before, a number typed into a box was put at the nearest limit by the box itself: a typed 9007199254740993 was sent as 9007199254740991, 1.0000000000000002e15 as 1e15, and a declared Max of 10 turned a typed 11 into 10 (and the README documented it as a feature).
  • New LcNumbers (LcNumbers.groovy): LcIntegerFactory and LcDoubleFactory (replacing LcLongValueFactory and JavaFX's DoubleSpinnerValueFactory) whose converters refuse text outside the limits (LcNumberError), and number getters that read the text in the box: a whole number as an exact integer (BigInteger, never a double), a decimal number compared with the limits as its exact decimal text (so 1.00000000000000001e15, which a double rounds to exactly 1e15, is refused too). The request carries exactly what was typed.
  • A number beyond a limit starts no run and sends nothing; the status line is the worker's template, read from conformance.json: 'L' must be <= 9007199254740991 / 'L' must be >= -9007199254740991; a declared Max gives 'Capped' must be <= 10; a decimal limit is written the Python way ('Ratio' must be <= 1000000000000000.0). The text stays in the box (so what is refused is what is seen), the status says it when the box loses focus or Enter is pressed (Enter is handled, instead of raising the converter's error inside JavaFX), and again when Run is pressed. The arrows stop at the limits.
  • Text that is not a number is refused with a sentence too ('L': '12x' is not a number, 'L': '5.0' is not a whole number, 'L' must be a finite number for nan/inf, 'L' is required for an empty box); +5 and 007 are 5 and 7, surrounding spaces are not part of the number.
  • README: the clamping sentence is replaced and a "Number limits" section says the rules (and which sentences are this host's own wording).

Exit code 3 is an import failure only with the marker (Tools PR #124)

  • LcWorker.hintForExit called any exit code 3 "the app's tool module failed to import". It now needs the worker's last words to contain cannot import tool module (the Tools marker, log.IMPORT_FAILED_MARKER); the Windows C runtime also ends a process with 3 for abort() and a raised SIGSEGV. Code 3 without the marker gets the plain sentence the worker process stopped unexpectedly (exit code 3); a missing package is still named first.

Tests

  • New gui_test_numbers_body.groovy (101 checks): NUM-01..05 read from conformance.json (limits form_integer_max, unbounded_float; templates number_above_maximum/number_below_minimum; the case ids) and run through the real dialog against the Numbers tool of the runs test app, which reports the text and Python type of what its request carried; also checked: the run record has the exact integer, Copy as command has it, a refusal starts no run (no new record folder, same worker) and leaves the text in the box and the value unchanged, a number beyond a long, an exponent beyond BigDecimal, decimals a double cannot tell apart, the text rules, an optional number, focus lost and Enter, the arrows, and the helper functions (Python float text, exact read-back).
  • gui_test_parity_host_body.groovy: new checks for the exit-code rule, and the live crash tests (fake interpreters that print and exit 3 with and without the marker).

Existing tests changed (spec supersedes them)

  • gui_test_parity_form_body.groovy: a_typed_integer_beyond_the_limit_stops_at_it and ..._on_enter (they asserted the clamping) now assert the refusal, the status sentence and that the text is kept; a_typed_non_integer_is_said_and_the_value_kept also asserts that the run refuses it.
  • gui_test_parity_host_body.groovy: hint_tool_import_failed (code 3 with an empty tail was an import failure) and live_crash_import_failed_with_the_last_words (the fake interpreter now prints the marker); hint_tool_import_failed_needs_the_marker replaces the first.
  • tests/run_gui_ci.sh: the new body and its scratch home.
    Nothing was skipped or loosened.

Sabotage checks

(see the comment on this PR)

Spec gaps and conservative choices

  • The spec gives only the integer sentence. The float sentence ('L' must be <= 1000000000000000.0, Python repr of the limit), the sentences for text that is not a number and the is required sentence for an empty box are this host's wording.
  • A declared bound is used as declared (an integer bound beyond 2^53 - 1 is cut to it, as before); a tool default outside the limits is shown as it is and refused at Run (it is no longer silently pulled to the limit).
  • A linked pixel size (PixelSizeOf) is a text field with its own rules (positive, finite) and does not use these limits.
  • Whitespace around a number is ignored and +5/007 are accepted (the HOST_PARITY number_text sketch rejects surrounding spaces; it is a proposal, not decided, and a GUI box is friendlier this way).

Not verified

  • Windows and macOS; no human at a display (private Xvfb, real QuPath 0.7.0 / Java 25); the JavaFX Spinner key handling was exercised by firing the Enter key event at the editor, not with a real keyboard.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN


Generated by Claude Code

…ode 3 needs the import marker

A number typed into a form box is read as its text (a whole number as an exact integer, a decimal one compared
with the limits as exact decimal text). Beyond a limit (default or declared) it is refused with the worker's
sentence ('L' must be <= 9007199254740991), no run starts, nothing is sent, nothing is put at the limit.
The worker hint calls exit code 3 an import failure only when the last output has `cannot import tool module`.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN

Copy link
Copy Markdown
Contributor Author

Sabotage checks

Each row: production code broken in a copy of the final tree of this PR (a regular expression replaced one place), the PR's own body run on it (gui_test_numbers_body, and gui_test_parity_host_body for the two exit-code rows). Every row fails the body.

sabotage (production code broken in a copy) what the test run said
a typed integer above the limit is clamped to the maximum instead of refused 10 failed check(s): NUM-02_is_refused_with_the_sentence, NUM-02_starts_no_run_and_sends_nothing, NUM-02_a_number_beyond_a_long_is_refused_with_the_sentence ...
a typed integer below the limit is clamped to the minimum 5 failed check(s): NUM-03_is_refused_with_the_sentence, NUM-03_starts_no_run_and_sends_nothing, NUM-03_a_number_beyond_a_long_is_refused_with_the_sentence ...
decimals are compared with the limit as doubles (not as exact text) 2 failed check(s): NUM-04_a_text_a_double_rounds_to_1e15_is_refused_with_the_sentence, NUM-04_a_text_a_double_rounds_to_1e15_starts_no_run_and_sends_nothing
the sentence says must be < {maximum} 13 failed check(s): the_sentences_are_the_conformance_templates, NUM-02_is_refused_with_the_sentence, NUM-02_a_number_beyond_a_long_is_refused_with_the_sentence ...
the whole-number getter reads the spinner's value, not the typed text 35 failed check(s): NUM-01_the_request_carries_exactly_that_number, NUM-01_the_run_record_has_the_exact_integer, NUM-01_copy_as_command_has_the_exact_integer ...
a declared Max of a whole-number parameter is ignored 3 failed check(s): NUM-05_11_is_refused_with_the_sentence, NUM-05_11_starts_no_run_and_sends_nothing, the_arrow_stops_at_a_declared_maximum
the whole-number limit is 2^31 - 1 again 2 failed check(s): the_limits_are_the_conformance_limits, NUM-01_runs
the decimal limit is 1e12 again 12 failed check(s): the_limits_are_the_conformance_limits, NUM-04_1e15_runs, NUM-04_1e15_the_request_carries_exactly_that_number ...
exit code 3 alone is an import failure (no marker needed) 3 failed check(s): hint_tool_import_failed_needs_the_marker, hint_code_3_without_the_marker_is_the_plain_sentence, live_crash_code_3_without_the_marker_is_not_called_an_import_failure
the import marker is cannot import instead of cannot import tool module 1 failed check(s): hint_the_marker_is_the_tools_marker
Enter in a number box does not say the refusal 1 failed check(s): enter_says_the_sentence_too
the arrow can pass the upper limit test aborted with an exception (rc=1 after 92 checks)
a decimal box does not refuse nan / inf text 2 failed check(s): text_nan_is_refused_with_the_sentence, text_infinity_is_refused_with_the_sentence
a programmatic value does not rewrite the typed text 23 failed check(s): NUM-04_1e15_runs, NUM-04_1e15_the_request_carries_exactly_that_number, NUM-04_1_0000000000000002e15_is_refused_with_the_sentence ...
Run reports a refused number as Check 'L': ... instead of the sentence alone 20 failed check(s): NUM-02_is_refused_with_the_sentence, NUM-03_is_refused_with_the_sentence, NUM-02_a_number_beyond_a_long_is_refused_with_the_sentence ...

Generated by Claude Code

…th-number-limits

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
@guijacquemet
guijacquemet changed the base branch from claude/phase2-qupath-retention to main October 13, 2026 19:02
@guijacquemet
guijacquemet merged commit a415d0a into main Oct 13, 2026
2 checks passed
@guijacquemet
guijacquemet deleted the claude/phase2-qupath-number-limits branch October 14, 2026 16:16
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