From 5e2187a9c6ddcc54f6e11bdd8e8abaca37adbd92 Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Sat, 26 Sep 2026 12:32:33 -0600 Subject: [PATCH 1/2] Adds the waiting loan's diff cases, one per class Three authored cases under conformance/cases/diff/ take a loan that waits in awaiting_return with its due-date timer handed to the host, while its chart is edited so that the check_in state routing after the wait gains a damaged outcome: a damaged key, an in_repair final state and the transition to it. Each case carries expect_diff and expect_compatible_at for the position the loan waits at: - loan_waiting_across_a_damaged_arm: awaiting_return keeps its id; the pair is compatible and the predicate answers true. - loan_waiting_across_a_mapped_rename: awaiting_return becomes awaiting_check_in, diffed with a mapping; the pair is mapped and the predicate answers false, since it takes no mapping. - loan_waiting_across_a_regrouping: awaiting_return keeps its id but is gathered with overdue under a new compound state; the pair is breaking and the predicate answers false. The corpus is re-emitted with mix statifier.corpus: the statifier suite goes from 28 to 31 cases and the corpus hash moves. The three cases are ratcheted in with mix test.baseline add, so the registry's statifier claim names them. conformance/README.md describes the loan pairs beside the hold_queue ones. No lib/ change. No changelog fragment: conformance fixtures. Refs: st-j1x8 --- conformance/README.md | 24 ++++++-- .../loan_waiting_across_a_damaged_arm.json | 43 +++++++++++++ .../loan_waiting_across_a_damaged_arm.scxml | 33 ++++++++++ ...loan_waiting_across_a_damaged_arm.to.scxml | 37 +++++++++++ .../loan_waiting_across_a_mapped_rename.json | 51 ++++++++++++++++ .../loan_waiting_across_a_mapped_rename.scxml | 33 ++++++++++ ...an_waiting_across_a_mapped_rename.to.scxml | 37 +++++++++++ .../loan_waiting_across_a_regrouping.json | 61 +++++++++++++++++++ .../loan_waiting_across_a_regrouping.scxml | 33 ++++++++++ .../loan_waiting_across_a_regrouping.to.scxml | 39 ++++++++++++ conformance/corpus/statifier.json | 3 + conformance/manifest.json | 4 +- conformance/registry.json | 5 +- test/passing_tests.json | 5 +- 14 files changed, 398 insertions(+), 10 deletions(-) create mode 100644 conformance/cases/diff/loan_waiting_across_a_damaged_arm.json create mode 100644 conformance/cases/diff/loan_waiting_across_a_damaged_arm.scxml create mode 100644 conformance/cases/diff/loan_waiting_across_a_damaged_arm.to.scxml create mode 100644 conformance/cases/diff/loan_waiting_across_a_mapped_rename.json create mode 100644 conformance/cases/diff/loan_waiting_across_a_mapped_rename.scxml create mode 100644 conformance/cases/diff/loan_waiting_across_a_mapped_rename.to.scxml create mode 100644 conformance/cases/diff/loan_waiting_across_a_regrouping.json create mode 100644 conformance/cases/diff/loan_waiting_across_a_regrouping.scxml create mode 100644 conformance/cases/diff/loan_waiting_across_a_regrouping.to.scxml diff --git a/conformance/README.md b/conformance/README.md index 32ade30d..6f51389e 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -128,22 +128,22 @@ with a reference host's cases. The cases under `conformance/cases/diff/` are in this world too, and they prove the two questions a host asks before it moves an execution from one -chart to an edited one; nothing in them moves anything. Each pairs the -`hold_queue` chart with an edit of it: the host object carries the edited -chart as `to_source` and `expect_diff`, the class and reasons +chart to an edited one; nothing in them moves anything. Each pairs a +`hold_queue` or a `loan` chart with an edit of it: the host object carries +the edited chart as `to_source` and `expect_diff`, the class and reasons `Statifier.Chart.diff/3` answers (ADR-0072 decisions 1 and 2), with the `mapping` it is given where a case gives one. The emitter runs these cases as it runs any host case; the class and the reasons, order included, are compared by this repository's test suite (`test/corpus/diff_cases_test.exs`), because nothing in `lib/` calls either -function (ADR-0072 decision 6). There is one pair per class: the same bytes -(identical), an added `` key (compatible), and `awaiting_pickup` +function (ADR-0072 decision 6). Four `hold_queue` pairs are one per class: +the same bytes (identical), an added `` key (compatible), and `awaiting_pickup` renamed to `ready_for_pickup` with a mapping (mapped) and without one (breaking). A case may also carry `expect_compatible_at`, what `Statifier.Position.compatible_at?/3` answers (ADR-0072 decision 4) at the position its steps leave the chart in: `Statifier.Position.export/1` holds atoms, sets and tuples, so the case states no position, and every such -case's steps put `p-1`'s hold in `awaiting_pickup` with its pickup timer +hold case's steps put `p-1`'s hold in `awaiting_pickup` with its pickup timer handed to the host. The predicate answers true when only `idle` is edited (a breaking pair harmless at this position), false when a transition of `awaiting_pickup` itself gains content (a compatible pair with no reasons), @@ -153,6 +153,18 @@ state, and false for the rename with or without a mapping. `schema/case.json` gives each reason as an object: `reason`, then the members that reason carries. +Three more pairs take the loan while it waits for its copy to come back: a +`loan` chart whose execution starts in `awaiting_return` with its due-date +timer handed to the host, and whose `check_in` state routes the returned +copy after the wait. Each edit gives `check_in` a damaged outcome (a new +`damaged` key, a new `in_repair` final state and the transition to it), +and each is one class: with `awaiting_return` kept by id the pair is +compatible and the predicate answers true; with it renamed to +`awaiting_check_in` and a mapping given the pair is mapped and the +predicate answers false, since it takes no mapping; with it kept by id but +gathered with `overdue` under a new compound state the pair is breaking +and the predicate answers false. + The authored cases that came before the library world keep their own domain: the four under `cases/send/` and the one under `cases/system_variables/` are fixtures for the behaviour they pin, not diff --git a/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json new file mode 100644 index 00000000..68e9d7e9 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json @@ -0,0 +1,43 @@ +{ + "description": "A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. The waiting step is renamed only in the name a reader sees: awaiting_return keeps its id, and nothing in it changes. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.", + "initial_configuration": [ + "awaiting_return" + ], + "steps": [], + "host": { + "send_types": [ + "library:timer", + "library:route" + ], + "expect_sends": [ + { + "type": "library:timer", + "target": "loan", + "event": { + "name": "loan.due" + }, + "delay_ms": 1814400000, + "send_id": "due" + } + ], + "expect_diff": { + "class": "compatible", + "reasons": [ + { + "reason": "state_added", + "state": "in_repair" + }, + { + "reason": "transition_added", + "state": "check_in", + "t_index": 3 + }, + { + "reason": "data_added", + "data_id": "damaged" + } + ] + }, + "expect_compatible_at": true + } +} diff --git a/conformance/cases/diff/loan_waiting_across_a_damaged_arm.scxml b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.scxml new file mode 100644 index 00000000..ebb7a131 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.scxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/cases/diff/loan_waiting_across_a_damaged_arm.to.scxml b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.to.scxml new file mode 100644 index 00000000..da18a8d1 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.to.scxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/cases/diff/loan_waiting_across_a_mapped_rename.json b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.json new file mode 100644 index 00000000..2588df54 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.json @@ -0,0 +1,51 @@ +{ + "description": "The same edit with the waiting state's id changed as well: awaiting_return becomes awaiting_check_in, diffed with a mapping from the old id to the new. The mapping resolves the renamed state and its transitions match through it, so the pair is mapped, naming the one mapped state before the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: it takes no mapping, and awaiting_return does not resolve in the new chart.", + "initial_configuration": [ + "awaiting_return" + ], + "steps": [], + "host": { + "send_types": [ + "library:timer", + "library:route" + ], + "expect_sends": [ + { + "type": "library:timer", + "target": "loan", + "event": { + "name": "loan.due" + }, + "delay_ms": 1814400000, + "send_id": "due" + } + ], + "mapping": { + "awaiting_return": "awaiting_check_in" + }, + "expect_diff": { + "class": "mapped", + "reasons": [ + { + "reason": "state_mapped", + "state": "awaiting_return", + "to_state": "awaiting_check_in" + }, + { + "reason": "state_added", + "state": "in_repair" + }, + { + "reason": "transition_added", + "state": "check_in", + "t_index": 3 + }, + { + "reason": "data_added", + "data_id": "damaged" + } + ] + }, + "expect_compatible_at": false + } +} diff --git a/conformance/cases/diff/loan_waiting_across_a_mapped_rename.scxml b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.scxml new file mode 100644 index 00000000..ebb7a131 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.scxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/cases/diff/loan_waiting_across_a_mapped_rename.to.scxml b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.to.scxml new file mode 100644 index 00000000..90c39ae4 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_mapped_rename.to.scxml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/cases/diff/loan_waiting_across_a_regrouping.json b/conformance/cases/diff/loan_waiting_across_a_regrouping.json new file mode 100644 index 00000000..0ef340d0 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_regrouping.json @@ -0,0 +1,61 @@ +{ + "description": "The same damaged outcome with the waiting state kept by id but moved: the edit gathers awaiting_return and overdue under a new compound state checked_out. Each of the two can be active and now has another parent, so the pair is breaking, the two parent changes reported before the added checked_out and the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: the active state's parent differs, and awaiting_return without checked_out is not a legal configuration of the new chart.", + "initial_configuration": [ + "awaiting_return" + ], + "steps": [], + "host": { + "send_types": [ + "library:timer", + "library:route" + ], + "expect_sends": [ + { + "type": "library:timer", + "target": "loan", + "event": { + "name": "loan.due" + }, + "delay_ms": 1814400000, + "send_id": "due" + } + ], + "expect_diff": { + "class": "breaking", + "reasons": [ + { + "reason": "state_changed", + "state": "awaiting_return", + "fields": [ + "parent" + ] + }, + { + "reason": "state_changed", + "state": "overdue", + "fields": [ + "parent" + ] + }, + { + "reason": "state_added", + "state": "checked_out" + }, + { + "reason": "state_added", + "state": "in_repair" + }, + { + "reason": "transition_added", + "state": "check_in", + "t_index": 3 + }, + { + "reason": "data_added", + "data_id": "damaged" + } + ] + }, + "expect_compatible_at": false + } +} diff --git a/conformance/cases/diff/loan_waiting_across_a_regrouping.scxml b/conformance/cases/diff/loan_waiting_across_a_regrouping.scxml new file mode 100644 index 00000000..ebb7a131 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_regrouping.scxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/cases/diff/loan_waiting_across_a_regrouping.to.scxml b/conformance/cases/diff/loan_waiting_across_a_regrouping.to.scxml new file mode 100644 index 00000000..e6c35930 --- /dev/null +++ b/conformance/cases/diff/loan_waiting_across_a_regrouping.to.scxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/corpus/statifier.json b/conformance/corpus/statifier.json index 01f3d934..93bba398 100644 --- a/conformance/corpus/statifier.json +++ b/conformance/corpus/statifier.json @@ -9,6 +9,9 @@ {"id":"statifier/diff/hold_waiting_at_an_edited_state","suite":"statifier","spec":"diff","conformance":null,"description":"p-1's hold waits in awaiting_pickup while the hold_queue chart is edited at that state: its copy.collected transition gains a library:route send of loan.requested to the patron chart. The diff does not compare a transition's content, so the pair is compatible with no reasons; but the active state's own transition is no longer byte-identical, so at this position the predicate answers false.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","expect_diff":{"class":"compatible","reasons":[]},"expect_compatible_at":false}}, {"id":"statifier/diff/hold_waiting_through_an_idle_edit","suite":"statifier","spec":"diff","conformance":null,"description":"p-1's hold waits in awaiting_pickup while the hold_queue chart is edited in idle only: idle's copy.available condition is rewritten from pending > 0 to pending >= 1. The diff compares a condition by its text, so the pair is breaking; but awaiting_pickup's own transitions and onexit are byte-identical in both charts, so at this position the predicate answers true. A breaking pair can be harmless to one execution.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"transition_removed","state":"idle","t_index":1},{"reason":"transition_added","state":"idle","t_index":1}]},"expect_compatible_at":true}}, {"id":"statifier/diff/hold_waiting_under_an_edited_ancestor","suite":"statifier","spec":"diff","conformance":null,"description":"The hold_queue chart with idle and awaiting_pickup inside a compound state open, whose own copy.disputed transition moves the copy to held_for_review. p-1's hold waits in awaiting_pickup while the edit adds a pending > 0 condition to open's copy.disputed transition and changes nothing inside awaiting_pickup. open is an ancestor of the active state, so it is in the configuration and its transition is selectable from there: the predicate answers false. The diff is breaking, open's transition (t_index 1) matching nothing on either side.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"transition_removed","state":"open","t_index":1},{"reason":"transition_added","state":"open","t_index":1}]},"expect_compatible_at":false}}, + {"id":"statifier/diff/loan_waiting_across_a_damaged_arm","suite":"statifier","spec":"diff","conformance":null,"description":"A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. The waiting step is renamed only in the name a reader sees: awaiting_return keeps its id, and nothing in it changes. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","expect_diff":{"class":"compatible","reasons":[{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":true}}, + {"id":"statifier/diff/loan_waiting_across_a_mapped_rename","suite":"statifier","spec":"diff","conformance":null,"description":"The same edit with the waiting state's id changed as well: awaiting_return becomes awaiting_check_in, diffed with a mapping from the old id to the new. The mapping resolves the renamed state and its transitions match through it, so the pair is mapped, naming the one mapped state before the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: it takes no mapping, and awaiting_return does not resolve in the new chart.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","mapping":{"awaiting_return":"awaiting_check_in"},"expect_diff":{"class":"mapped","reasons":[{"reason":"state_mapped","state":"awaiting_return","to_state":"awaiting_check_in"},{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":false}}, + {"id":"statifier/diff/loan_waiting_across_a_regrouping","suite":"statifier","spec":"diff","conformance":null,"description":"The same damaged outcome with the waiting state kept by id but moved: the edit gathers awaiting_return and overdue under a new compound state checked_out. Each of the two can be active and now has another parent, so the pair is breaking, the two parent changes reported before the added checked_out and the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: the active state's parent differs, and awaiting_return without checked_out is not a legal configuration of the new chart.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"state_changed","state":"awaiting_return","fields":["parent"]},{"reason":"state_changed","state":"overdue","fields":["parent"]},{"reason":"state_added","state":"checked_out"},{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":false}}, {"id":"statifier/library/hold_queue_available_with_no_holds","suite":"statifier","spec":"library","conformance":null,"description":"No patron holds copy c-1 when it becomes available, so the guard pending > 0 is false and copy.available is taken by the unguarded transition back to idle. No pickup timer is sent: with the guard removed, the chart would enter awaiting_pickup and send one.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"copy.available"},"configuration":["idle"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[]}}, {"id":"statifier/library/hold_queue_collected_cancels_timer","suite":"statifier","spec":"library","conformance":null,"description":"p-1 places a hold on c-1, the copy becomes available and the pickup timer is sent, and p-1 collects the copy: copy.collected moves the chart back to idle. The chart cancels the pickup timer on leaving awaiting_pickup, but this case asserts nothing about that cancel: a registered send type's observable here is expect_sends, which has no shape for a cancel, so it carries only the timer sent on entry.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]},{"event":{"name":"copy.collected"},"configuration":["idle"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}]}}, {"id":"statifier/library/hold_queue_expiry_moves_to_next","suite":"statifier","spec":"library","conformance":null,"description":"p-1 then p-2 place holds on c-1 and the copy becomes available, sending the first pickup timer. p-1 does not collect: the first pickup.expired finds more than one patron waiting, re-enters awaiting_pickup and sends a fresh timer for p-2. p-2 does not collect either: the second pickup.expired finds one waiting, so the chart returns to idle and no third timer is sent. The second expiry is what shows the count went two, one, zero.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]},{"event":{"name":"pickup.expired"},"configuration":["awaiting_pickup"]},{"event":{"name":"pickup.expired"},"configuration":["idle"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"},{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}]}}, diff --git a/conformance/manifest.json b/conformance/manifest.json index 3d46d1ba..2eda4beb 100644 --- a/conformance/manifest.json +++ b/conformance/manifest.json @@ -1,5 +1,5 @@ { - "corpus_hash": "sha256:c79612f0e9609bdc85bbd3d8d2dcb14840ec14922d1b93a53cafa80e08529b34", + "corpus_hash": "sha256:1f37dc11800df21346323f024ef0ec0feeddb02d70744c127915008468319cb5", "suites": [ { "suite": "scion", @@ -14,7 +14,7 @@ { "suite": "statifier", "file": "corpus/statifier.json", - "case_count": 28 + "case_count": 31 } ], "upstreams": [ diff --git a/conformance/registry.json b/conformance/registry.json index 0b5cbae4..073ea4a8 100644 --- a/conformance/registry.json +++ b/conformance/registry.json @@ -1,6 +1,6 @@ { "implementation": "statifier-ex", - "corpus_hash": "sha256:c79612f0e9609bdc85bbd3d8d2dcb14840ec14922d1b93a53cafa80e08529b34", + "corpus_hash": "sha256:1f37dc11800df21346323f024ef0ec0feeddb02d70744c127915008468319cb5", "claims": ["scion","statifier","w3c-mandatory","w3c-optional"], "entries": [ {"case_id":"scion/actionSend/send1","suite":"scion"}, @@ -130,6 +130,9 @@ {"case_id":"statifier/diff/hold_waiting_at_an_edited_state","suite":"statifier"}, {"case_id":"statifier/diff/hold_waiting_through_an_idle_edit","suite":"statifier"}, {"case_id":"statifier/diff/hold_waiting_under_an_edited_ancestor","suite":"statifier"}, + {"case_id":"statifier/diff/loan_waiting_across_a_damaged_arm","suite":"statifier"}, + {"case_id":"statifier/diff/loan_waiting_across_a_mapped_rename","suite":"statifier"}, + {"case_id":"statifier/diff/loan_waiting_across_a_regrouping","suite":"statifier"}, {"case_id":"statifier/library/hold_queue_available_with_no_holds","suite":"statifier"}, {"case_id":"statifier/library/hold_queue_collected_cancels_timer","suite":"statifier"}, {"case_id":"statifier/library/hold_queue_expiry_moves_to_next","suite":"statifier"}, diff --git a/test/passing_tests.json b/test/passing_tests.json index ae2428a8..228a1d50 100644 --- a/test/passing_tests.json +++ b/test/passing_tests.json @@ -4,7 +4,7 @@ "test/mix/**/*_test.exs", "test/statifier/**/*_test.exs" ], - "last_updated": "2026-09-24", + "last_updated": "2026-09-26", "scion_tests": [ "test/scion_tests/action_send/send1_test.exs", "test/scion_tests/action_send/send2_test.exs", @@ -135,6 +135,9 @@ "conformance/cases/diff/hold_waiting_at_an_edited_state.json", "conformance/cases/diff/hold_waiting_through_an_idle_edit.json", "conformance/cases/diff/hold_waiting_under_an_edited_ancestor.json", + "conformance/cases/diff/loan_waiting_across_a_damaged_arm.json", + "conformance/cases/diff/loan_waiting_across_a_mapped_rename.json", + "conformance/cases/diff/loan_waiting_across_a_regrouping.json", "conformance/cases/library/hold_queue_available_with_no_holds.json", "conformance/cases/library/hold_queue_collected_cancels_timer.json", "conformance/cases/library/hold_queue_expiry_moves_to_next.json", From b66318a39cebb4585f0baa936b3770e5aa355a4e Mon Sep 17 00:00:00 2001 From: JohnnyT Date: Sat, 26 Sep 2026 12:40:18 -0600 Subject: [PATCH 2/2] Corrects the compatible loan case's description loan_waiting_across_a_damaged_arm said the waiting step was renamed in the name a reader sees. The pair has no rename of any kind: awaiting_return is byte-identical in both charts, and a chart carries no reader-facing name. The sentence now says awaiting_return is unchanged, id and content alike, and that the damaged outcome is the only edit. Corpus re-emitted with mix statifier.corpus; --check passes. Refs: st-j1x8 --- conformance/cases/diff/loan_waiting_across_a_damaged_arm.json | 2 +- conformance/corpus/statifier.json | 2 +- conformance/manifest.json | 2 +- conformance/registry.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json index 68e9d7e9..5cf5353e 100644 --- a/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json +++ b/conformance/cases/diff/loan_waiting_across_a_damaged_arm.json @@ -1,5 +1,5 @@ { - "description": "A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. The waiting step is renamed only in the name a reader sees: awaiting_return keeps its id, and nothing in it changes. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.", + "description": "A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. awaiting_return is unchanged, id and content alike: the damaged outcome is the only edit. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.", "initial_configuration": [ "awaiting_return" ], diff --git a/conformance/corpus/statifier.json b/conformance/corpus/statifier.json index 93bba398..0f0f89da 100644 --- a/conformance/corpus/statifier.json +++ b/conformance/corpus/statifier.json @@ -9,7 +9,7 @@ {"id":"statifier/diff/hold_waiting_at_an_edited_state","suite":"statifier","spec":"diff","conformance":null,"description":"p-1's hold waits in awaiting_pickup while the hold_queue chart is edited at that state: its copy.collected transition gains a library:route send of loan.requested to the patron chart. The diff does not compare a transition's content, so the pair is compatible with no reasons; but the active state's own transition is no longer byte-identical, so at this position the predicate answers false.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","expect_diff":{"class":"compatible","reasons":[]},"expect_compatible_at":false}}, {"id":"statifier/diff/hold_waiting_through_an_idle_edit","suite":"statifier","spec":"diff","conformance":null,"description":"p-1's hold waits in awaiting_pickup while the hold_queue chart is edited in idle only: idle's copy.available condition is rewritten from pending > 0 to pending >= 1. The diff compares a condition by its text, so the pair is breaking; but awaiting_pickup's own transitions and onexit are byte-identical in both charts, so at this position the predicate answers true. A breaking pair can be harmless to one execution.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"transition_removed","state":"idle","t_index":1},{"reason":"transition_added","state":"idle","t_index":1}]},"expect_compatible_at":true}}, {"id":"statifier/diff/hold_waiting_under_an_edited_ancestor","suite":"statifier","spec":"diff","conformance":null,"description":"The hold_queue chart with idle and awaiting_pickup inside a compound state open, whose own copy.disputed transition moves the copy to held_for_review. p-1's hold waits in awaiting_pickup while the edit adds a pending > 0 condition to open's copy.disputed transition and changes nothing inside awaiting_pickup. open is an ancestor of the active state, so it is in the configuration and its transition is selectable from there: the predicate answers false. The diff is breaking, open's transition (t_index 1) matching nothing on either side.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"hold.placed"},"configuration":["idle"]},{"event":{"name":"copy.available"},"configuration":["awaiting_pickup"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"hold_queue","event":{"name":"pickup.expired"},"delay_ms":604800000,"send_id":"pickup"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n \n\n \n \n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"transition_removed","state":"open","t_index":1},{"reason":"transition_added","state":"open","t_index":1}]},"expect_compatible_at":false}}, - {"id":"statifier/diff/loan_waiting_across_a_damaged_arm","suite":"statifier","spec":"diff","conformance":null,"description":"A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. The waiting step is renamed only in the name a reader sees: awaiting_return keeps its id, and nothing in it changes. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","expect_diff":{"class":"compatible","reasons":[{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":true}}, + {"id":"statifier/diff/loan_waiting_across_a_damaged_arm","suite":"statifier","spec":"diff","conformance":null,"description":"A loan waits in awaiting_return with its due-date timer handed to the host while the loan chart is edited: the check-in step that routes after the wait gains a damaged outcome, a condition on a new damaged key sending the copy to a new in_repair final state ahead of the returned arm. awaiting_return is unchanged, id and content alike: the damaged outcome is the only edit. The diff reports the three additions and no breaking reason, so the pair is compatible; awaiting_return's transitions and onexit are byte-identical in both charts, so at this position the predicate answers true.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","expect_diff":{"class":"compatible","reasons":[{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":true}}, {"id":"statifier/diff/loan_waiting_across_a_mapped_rename","suite":"statifier","spec":"diff","conformance":null,"description":"The same edit with the waiting state's id changed as well: awaiting_return becomes awaiting_check_in, diffed with a mapping from the old id to the new. The mapping resolves the renamed state and its transitions match through it, so the pair is mapped, naming the one mapped state before the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: it takes no mapping, and awaiting_return does not resolve in the new chart.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","mapping":{"awaiting_return":"awaiting_check_in"},"expect_diff":{"class":"mapped","reasons":[{"reason":"state_mapped","state":"awaiting_return","to_state":"awaiting_check_in"},{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":false}}, {"id":"statifier/diff/loan_waiting_across_a_regrouping","suite":"statifier","spec":"diff","conformance":null,"description":"The same damaged outcome with the waiting state kept by id but moved: the edit gathers awaiting_return and overdue under a new compound state checked_out. Each of the two can be active and now has another parent, so the pair is breaking, the two parent changes reported before the added checked_out and the three additions the damaged outcome makes. A loan waits in awaiting_return with its due-date timer handed to the host, and at that position the predicate answers false: the active state's parent differs, and awaiting_return without checked_out is not a legal configuration of the new chart.","required_features":["basic_states","cancel_elements","compound_states","data_elements","datamodel","event_transitions","eventless_transitions","final_states","onentry_actions","onexit_actions","send_delay_expressions","send_elements","send_param_elements"],"source":"\n\n \n \n \n\n \n \n \n \n \n \n \n \n \n \n\n \n \n \n\n \n \n \n\n \n \n \n \n \n \n \n\n","initial_configuration":["awaiting_return"],"steps":[],"host":{"send_types":["library:timer","library:route"],"expect_sends":[{"type":"library:timer","target":"loan","event":{"name":"loan.due"},"delay_ms":1814400000,"send_id":"due"}],"to_source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n\n","expect_diff":{"class":"breaking","reasons":[{"reason":"state_changed","state":"awaiting_return","fields":["parent"]},{"reason":"state_changed","state":"overdue","fields":["parent"]},{"reason":"state_added","state":"checked_out"},{"reason":"state_added","state":"in_repair"},{"reason":"transition_added","state":"check_in","t_index":3},{"reason":"data_added","data_id":"damaged"}]},"expect_compatible_at":false}}, {"id":"statifier/library/hold_queue_available_with_no_holds","suite":"statifier","spec":"library","conformance":null,"description":"No patron holds copy c-1 when it becomes available, so the guard pending > 0 is false and copy.available is taken by the unguarded transition back to idle. No pickup timer is sent: with the guard removed, the chart would enter awaiting_pickup and send one.","required_features":["assign_elements","basic_states","cancel_elements","compound_states","conditional_transitions","data_elements","datamodel","event_transitions","onentry_actions","onexit_actions","send_delay_expressions","send_elements","targetless_transitions"],"source":"\n\n \n \n \n \n\n \n \n \n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n","initial_configuration":["idle"],"steps":[{"event":{"name":"copy.available"},"configuration":["idle"]}],"host":{"send_types":["library:timer","library:route"],"expect_sends":[]}}, diff --git a/conformance/manifest.json b/conformance/manifest.json index 2eda4beb..1be8ce19 100644 --- a/conformance/manifest.json +++ b/conformance/manifest.json @@ -1,5 +1,5 @@ { - "corpus_hash": "sha256:1f37dc11800df21346323f024ef0ec0feeddb02d70744c127915008468319cb5", + "corpus_hash": "sha256:c15e87820da654e8dde6ae97bed1631051cf6456c461cf67e7b617a36b24b02b", "suites": [ { "suite": "scion", diff --git a/conformance/registry.json b/conformance/registry.json index 073ea4a8..1c435657 100644 --- a/conformance/registry.json +++ b/conformance/registry.json @@ -1,6 +1,6 @@ { "implementation": "statifier-ex", - "corpus_hash": "sha256:1f37dc11800df21346323f024ef0ec0feeddb02d70744c127915008468319cb5", + "corpus_hash": "sha256:c15e87820da654e8dde6ae97bed1631051cf6456c461cf67e7b617a36b24b02b", "claims": ["scion","statifier","w3c-mandatory","w3c-optional"], "entries": [ {"case_id":"scion/actionSend/send1","suite":"scion"},