engine: Position is an integer cell — rounds and clamps to i16 - #22
Open
Morgandri1 wants to merge 2 commits into
Open
engine: Position is an integer cell — rounds and clamps to i16#22Morgandri1 wants to merge 2 commits into
Morgandri1 wants to merge 2 commits into
Conversation
|
Deployment failed for project wheel with the following error: Learn More: https://vercel.com/morgandri1s-projects?upgradeToPro=build-rate-limit |
Morgandri1
force-pushed
the
sdk/position-integer-clamp
branch
from
September 6, 2026 18:12
052e08f to
303cfdb
Compare
Morgandri1
added a commit
that referenced
this pull request
Sep 7, 2026
Rust's f64::round is half-away-from-zero; JS Math.round is half-up. They agree everywhere except a negative exact half: Math.round(-10.5) is -10, and the engine will store -11. Board coordinates go negative the moment you pan, so that is reachable, and it is one cell of permanent disagreement on every dragged node — invisible on screen, and exactly the drift the position gate is meant to catch. Fixed before #22 lands rather than after, so the engine's arithmetic and ours are written from the same rule instead of converging on a bug report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Hw5v8WJBYWzZ1SYaD5xctn
Morgandri1
added a commit
that referenced
this pull request
Sep 7, 2026
Rounding cannot -- 0.5 cells/axis, one cell is one CSS px, canvas.tsx caps zoom at 1.8, so 1.27 px worst case. That is geometry and holds for any board. Clamping is unbounded and is a question about the DATA, not the code, so it can only be answered by looking at the rows that exist. Takes a sqlite path or a live engine URL, names any node outside the i16 bound and prints how far it will jump in pixels. Verified in both directions: exit 0 on a clean board, exit 1 naming a runaway node. Written because I asked PM for a SQL query and a request someone has to translate is a request that waits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
added 2 commits
September 7, 2026 00:20
… way in
Implements the operator ruling in ARCHITECTURE.md (position cell size and
clamping, 2026-09-06): Position{x,y} is now i16, with a custom Deserialize
that accepts any JSON number, rounds to the nearest cell, and clamps to
i16::MIN..=i16::MAX before a value is ever stored or compared. Fixes the
POS-is-an-integer/POS-rounds-and-clamps failures currently red on main
(qa/integration/test_node_position.py).
Position's exported wire type became integer/int16 in the previous commit (the operator ruling). JSON Schema's type: integer keyword correctly rejects a non-whole number like -1234.5 by spec, even though the engine's Deserialize impl still accepts and rounds it. That's an engine-level behavior the static schema check can't express, so this fixture is marked _pending the same way tool_source_postman.json already is, pointing at the wheel-core unit test that actually covers rounding.
Morgandri1
force-pushed
the
sdk/position-integer-clamp
branch
from
September 7, 2026 00:21
303cfdb to
7b411ee
Compare
Morgandri1
pushed a commit
that referenced
this pull request
Sep 7, 2026
PM approved landing this: additive to QA's docker-based ENG-starts- without-shm gate (qa/integration/test_engine_shm.py), not a duplicate -- same failure mode, different technique, no docker required. QA's own comment says the directory-at-the-shm-path fixture "verified to reproduce the production symptom exactly," and that fixture is a filesystem fact, not a container one, so it reproduces here too. Seeds a database already in WAL (the shape that actually crash-looped production -- journal_mode persists in the header, so every boot rediscovers it), blocks the -shm path with a directory, then asserts open_configured(path, false) -- the engine's own mode -- both returns Ok AND accepts a real write, with the pre-existing row intact. Mutation- checked: reverting mode_holds to a bare read-back (dropping the `BEGIN IMMEDIATE; COMMIT` proof) reproduces the exact symptom -- settles back on "wal", the mode that just failed. Does not close qa/BUGS.md #22 -- that stays QA's call once they confirm 1163cc9 live against a fresh image (PM is asking them directly).
Morgandri1
pushed a commit
that referenced
this pull request
Sep 7, 2026
PM approved landing this: additive to QA's docker-based ENG-starts- without-shm gate (qa/integration/test_engine_shm.py), not a duplicate -- same failure mode, different technique, no docker required. QA's own comment says the directory-at-the-shm-path fixture "verified to reproduce the production symptom exactly," and that fixture is a filesystem fact, not a container one, so it reproduces here too. Seeds a database already in WAL (the shape that actually crash-looped production -- journal_mode persists in the header, so every boot rediscovers it), blocks the -shm path with a directory, then asserts open_configured(path, false) -- the engine's own mode -- both returns Ok AND accepts a real write, with the pre-existing row intact. Mutation- checked: reverting mode_holds to a bare read-back (dropping the `BEGIN IMMEDIATE; COMMIT` proof) reproduces the exact symptom -- settles back on "wal", the mode that just failed. Does not close qa/BUGS.md #22 -- that stays QA's call once they confirm 1163cc9 live against a fresh image (PM is asking them directly).
Morgandri1
added a commit
that referenced
this pull request
Sep 7, 2026
…raction Content review of the rescued tree, per PM. All four pieces were GENUINELY NEW — none of this text is on main — and the classification is per piece rather than per commit, because the histories diverged and replaying them would have carried a claim its own author withdrew. CODE (still needed, verified against main): coverage_gate printed `r.stderr[-2000:] or r.stdout[-2000:]` on failure — stderr only when stderr is non-empty, and only its last 2000 chars. cargo puts the failing test's name and panic in the MIDDLE of a long stream, so the slice reliably cut the one thing worth reading. I hit this myself tonight: I could not read a coverage failure from CI and dug through the whole log twice. Main still had the bad slice; it now finds where the failure starts. BUG-022 CLOSED, by two people who each had half the proof. That session proved the mechanism at the crate level (mode_holds before the fast path, directory blocking -shm) and wrote down honestly that it could NOT verify through the docker fixture, having no docker binary. That gap is closed from the other side: ENG-starts-without-shm is green through the fixture, engine-shm 12/12. Neither session could have produced both halves. BUG-026 CLOSED by measurement — POS-* is 34/34 since #22 landed. Main's entry still said "17 red", true when filed and not any more. BUG-034, THE COLLISION, resolved by renumbering: that session filed the table-columns finding as 026; main had already given 026 to the position ruling. Different bugs, same number. Renumbering is the whole fix. AND THE PART THAT WAS ACTUALLY IRREPLACEABLE — the uncommitted file. Commit c9f0f6f claims the columns bug CONFIRMED, citing a CI run. The uncommitted correction RETRACTS it: PR #12 predates PR #20 and tested a route that 404s unconditionally, so neither run was evidence about columns at all. Landing the commits without the uncommitted file would have put a CONFIRMED claim on main that its own author had already withdrawn, and sent the next person hunting a bug the evidence never supported. All three states are recorded — claimed, retracted, then settled green by measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
Morgandri1
added a commit
that referenced
this pull request
Sep 7, 2026
…elete
Content review complete. All four pieces were genuinely new; none of the text
was on main. Classified per piece rather than per commit, because replaying
the commits alone would have landed a CONFIRMED claim its own author had
already retracted in the uncommitted file.
coverage_gate the blind stderr tail-slice that hid the failing test's name
BUG-022 CLOSED — crate-level proof from that session, fixture-level
proof from this one; neither had both halves
BUG-026 CLOSED by measurement — POS-* 34/34 since #22
BUG-034 the 026 collision, renumbered; claim, retraction, and the
measurement that finally settled it
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1nLndrG8rrdr5ar7P6WkN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Position{x,y}changes fromf64toi16, with a customDeserializethat accepts any JSON number (an in-flight drag can still send a float), rounds to the nearest cell, and clamps toi16::MIN..=i16::MAXbefore the value is ever stored, compared, or serialized back out.docs/schema/*.json(position.json's field type is nowinteger/int16;additionalProperties: falsepreserved via#[serde(deny_unknown_fields)]on the struct even thoughDeserializeis hand-written).node.rs,json_shape.rs) and added four new tests for round/clamp/JSON-parse/deny-unknown-fields, mutation-checked (revertedclamp_cellto a bare cast, confirmed the round/clamp tests fail).Why
qa/integration/test_node_position.py'sPOS-is-an-integer/POS-rounds-and-clampscases are currently failing onmain's own tip — confirmed on a fresh CI run ofmain(not caused by any of my other open PRs).wheel-core::Positionhad no rounding/clamping logic anywhere; only the web-side UI clamp had landed.Test plan
cargo test -p wheel-core(55 lib + 63 integration tests, including the new Position tests) — greencargo test -p wheel-engine -p wheel-cli -p wheel-api— green, no code changes needed outside wheel-core (Position flows throughPosition::new(f64, f64)unchanged at every call site)cargo build --workspace— cleancargo fmt --check -p wheel-core,cargo clippy -p wheel-core --lib --tests— cleanqa/integration/test_node_position.pyPOS-* cases should go green