Repository navigation
feat(runtime): execute repeated action steps beyond plain successions - #834
Conversation
…reads and bindings Co-Authored-By: jason.han <hanhuijun@gmail.com>
… on parts Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…uarded successions Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…get end Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…nd compliance map Co-Authored-By: jason.han <hanhuijun@gmail.com>
…odec Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
…ed it A join reached per performance consumes the earliest sibling arrival and mints the token that performs it, so the tried token neither moves nor changes the token count and the step looked unacted. Compare the token-ID counter before and after the step too, so the explore slot and the check replay see the synchronization as that token's act. Pin every checkable repeated-step conformance case with a check expectation; a case with a check expectation may carry an explore budget without outcomes. Co-Authored-By: jason.han <hanhuijun@gmail.com>
The runtime reaches a repeated step in one step, splits the token into its siblings in the next, then performs the step in a third. The encoding placed the siblings on the arriving move, compressing the split into the arrival, so every witness step and choice was off by one and witnesses would not replay. Arrivals into a repeated step now land the token pending and the next move mints its siblings, mirroring the runtime, and the SMT witness tests pin that violated properties over repeated steps replay. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…arget ends Co-Authored-By: jason.han <hanhuijun@gmail.com>
|
I tested the built CLI/REPL at a75f494 across 129 commands. The full command matrix changed only in the five results the two fixes target.
|
…e the successions derive it Co-Authored-By: jason.han <hanhuijun@gmail.com>
…han proved or bounded Co-Authored-By: jason.han <hanhuijun@gmail.com>
…tics from the spec Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ends force the repeated step's A control node that declares no multiplicity takes the executor's one-performance reading beside a repeated step, so a written [*] into a fork or decision is a barrier and a written [*] out of a join or merge fans out. The four derived crossings — a bijective crossing into a join or out of a fork, the lone incoming edge of a merge, the lone outgoing edge of a decision — still fix the node's count to the step's, and a merge or decision carrying another succession is unsatisfiable under count one through the mandated 0..1 ends. Restores the fork-barrier, decision-barrier and merge-fanout conformance fixtures to running, and the corresponding SMT witness and outcome cases. The conformance README notes that an exploration budget raise is pinned for completeness only and never changes a standing. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…es beside repeated steps Co-Authored-By: jason.han <hanhuijun@gmail.com>
The default 1024-run budget leaves the check-agrees exploration of the merge per-performance fixture incomplete; raise it like the join per-performance fixture's. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…it makes the siblings Co-Authored-By: jason.han <hanhuijun@gmail.com>
…g the check search Co-Authored-By: jason.han <hanhuijun@gmail.com>
… instead of refusing it Co-Authored-By: jason.han <hanhuijun@gmail.com>
…s stay ambiguous Co-Authored-By: jason.han <hanhuijun@gmail.com>
|
This branch now conflicts with
To resolve: merge current Planned merge order for the execution PRs: #844 → #850 → #838 → (#851 → #853 → #857) → #830 → #833 → #842 → #837 → #834 → #816. Re-run the full gate ( |
|
Hold on pushes: please don't push to this branch, including |
…-coverage Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/exec/runtime/explore_queue.go # internal/exec/runtime/statements.go # internal/workspace/libs/stdlib.snapshot # tests/export/behavior_test.go
…d body statements Co-Authored-By: jason.han <hanhuijun@gmail.com>
… feat/repeated-step-coverage Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # docs/project/spec-compliance.md # docs/reference/rdf-mapping.md # internal/check/passes/behavior/action_step_multiplicity.go # internal/exec/runtime/action_frame.go # internal/exec/runtime/classifier_behavior.go # internal/exec/runtime/conformance_test.go # internal/exec/runtime/held_image_behavior.go # internal/exec/runtime/statements.go # internal/exec/runtime/testdata/conformance/README.md # internal/exec/smt/encode.go # internal/exec/smt/support.go # internal/ir/lower/action_graph.go # internal/ir/lower/step_multiplicity.go # internal/workspace/libs/stdlib.snapshot
…herited loop body The repeated-step coverage change lifts the block-body multiplicity refusal, so a repeated step in a loop body is refused only when its body states no succession: the inherited [2] case now expects the order-open finding, and an ordered inherited loop body is covered as supported at both the pass and the runtime (tick performs its inherited count per pass). Co-Authored-By: jason.han <hanhuijun@gmail.com>
…he transition member A keywordless first a if g then [m] b matches atMultiplicityFirstSuccession (the then-[ probe fires before the guard check), so it fell into the succession-usage parse and errored instead of reaching parseTransitionMember's then-[m] handling. An if at depth 0 before then means the member is a GuardedSuccession: bail so the guarded-succession dispatch reads it. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…on members The keywordless first a if g then [m] b is a GuardedSuccession and parses as a TransitionMember like the succession-keyword spelling, so both edges are counted there instead of one under InitialNode. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A redefining step declaring no multiplicity takes the redefined step's [n], and the effective count is encoded like a declared one, so the inherited-step case is a count test rather than a refusal test. Co-Authored-By: jason.han <hanhuijun@gmail.com>
On a pending split move the fork's actor landing, its none-retire, and its sibling placements applied unconditionally, contradicting the split's own constraints so a fan-out from a repeated step proved vacuously. Landings, the retire, placements, nextID, fails and full now apply under the same gate succeed uses, with the barrier retirement for a gate-false move; under pending only the split's constraints apply. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…epeated step falseGuardLeavesRepeated skipped an inherited [n] because it read the declared-multiplicity table; it now gates on the effective HasStepMultiplicity, so a redefining step that takes the redefined step's count refuses the same way. Co-Authored-By: jason.han <hanhuijun@gmail.com>
classifierPerformanceCount answers the count both the deferred and the eager attachment loops then mint; it now applies the same step budget splitRepeatedStep enforces, so a huge declared count fails with ErrActionStepLimitExceeded before any behavior is allocated. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Trims the comments added with the inherited-step cases to at most two lines each. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… feat/repeated-step-coverage
sameUnconditionalActionEdge compared ends, guard, name and carrier but not the written end multiplicities, so an owned plain succession restating the same ends swallowed the inherited edge and dropped its [*]..[1] crossing. An edge whose ends state multiplicities is now never the same unconditional edge as one without them or with different ones. Co-Authored-By: jason.han <hanhuijun@gmail.com>
sameUnconditionalActionEdge compared written end multiplicities by node identity, so a derived action restating an inherited succession verbatim kept a second edge and the runtime had two orderings over the same ends. The comparison now evaluates each end's range in its own declaring scope and deduplicates only when both ends agree; an unevaluable bound still keeps both edges. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… feat/repeated-step-coverage Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/workspace/libs/stdlib.snapshot
… feat/repeated-step-coverage Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/ir/lower/action_graph.go
What and why
Exact action-step multiplicity (
a[n]) executed only between plain successions; every other shape around a repeated step was refused withaction-step-multiplicity-unsupported. This PR executes the shapes KerML/SysML determine and keeps a typed refusal for the ones they leave open. Unchanged: the strict refusal of an ambiguous plainthenaround a repeated step,[0]as a no-op, refusal of non-fixed counts ([0..1],[*]),ErrIntegerUnaddressablefor bounds beyond 64 bits, and validate/run/explore/check agreement (the SMT engine now refuses every shapeCheckSteprefuses).Stack: #830 has merged into
develop, so this PR now targetsdevelopdirectly. Repeated-step coverage uses #830's effective count (HasStepMultiplicity,StepCount,CheckStep) everywhere, so an inherited or redefined step's count is never computed a second way.Now executes
a[n]in awhile/for/ifbodynperformances per body pass,[0]none. Each repetition runs as one move, so explore and check report the result as observed, not proved or bounded, with a typed reason (their interleavings are not explored).perform action run[n]on a partndistinct occurrences, each with its own behaviour, kept through held images. The count is checked against the same step budget a repeated step's split uses, so a hugenfails withErrActionStepLimitExceededbefore any behaviour is allocated.PerformedActionsOf(run)returns alln, so a request to runrunon that part is ambiguous (ErrAmbiguousAction, "performs run 2 times").a.xread outsideax, duplicates kept, in repetition-index order. Reading before allnend givesErrNodeNotPerformed. Inside a performance,xis that performance's own value.action a[n] { in x = c; }x.bind a.x = e,esingle-valuede's value. An out-pin requires allnvalues to agree, otherwiseErrBindingConflict.a[n]→ join, or → merge as its only incoming successionntimes, once per performance ofa(derived below). The other edges at the node are checked under that count.succession first [*] a then f;into a fork or decisionfperforms once, under the sametallyinfirst [*] a then [1] tally. Its ends do not force another count.then [*] afirst p if g then [*] a;(guard into a written target end)gholds, every performance is ordered afterp. The parser now admits that end (GuardedSuccession/GuardedTargetSuccession→TransitionSuccession→ConnectorEndOwnedCrossMultiplicityMember). It goes through the AST codec,DeclaredSuccessionsand RDF export/import.sysml -engine smt)a[n]is encoded as the runtime runs it:n-1sibling tokens placed in free slots on the split move that follows arrival (from a fork too), a retire-until-last barrier (or per-performance crossing into a join/merge),[0]as a pass-through, and a false guard into a written target end as a failing move. A fan-out from a repeated step honours the same pending gatesucceeddoes, so on the pending move only the split's constraints apply.Still refused, and why
flow from a.out to b.inand connections at a repeated pinTransfers.kerml: flow ends have no multiplicity in the grammar and a flow defaults to[0..*](SysML §7.6.3), so how many transfers there are, and from which performances, is undetermined.bind a.x = ewith a multi-valuedeinto in-pinse's values goes to which performance is open.a[n]GuardedSuccessionhas no source end you can write, so KERML-29 leaves it open.a[n]with no written target endaction-step-order-opennperformances, but nothing orders them. The check reads #830's effective count, so a redefining step that inherits[n]refuses the same way. Validate warns when the guard is literalfalse. Into a single performance (a[1]) the false guard just prunes the edge (guard_false_single).thenfroma[n]into a fork or decision, or from a join or merge intoa[n]thenpolicy)a-side end is not mandated.0..1ends cannot takencrossings.a[n]ntimes, and its predecessor, which performs once, cannot orderncrossings.a[n]in awhile/for/ifbody next to another member, with no succession writtenaction-step-order-openthen. This also covers an inherited[n].Specification basis
a[n]isnperformances per performance of the action, body pass or part that featuresa.semantics.ImplicitMultiplicityApplies/EffectiveParameterRangeimplement it: the implicit[1..1]reaches only owned attribute, item, part and port usages. Any other usage takes what it subsets or redefines, else[0..*]. A control node is an action usage specializingActions::Action::controls : ControlAction[0..*], andmergesis[0..*].forks,joinsanddecisionsdeclare nothing, so they inherit[0..*]. An unwritten control node's count is therefore[0..*], and only its successions can fix it:a[n]→ join (both ends mandated1..1): a bijection, sonjoin performances.a[n]→ merge as its only incoming succession: target1..1plusControlPerformances.kermlMergePerformance::incomingHBLink : HappensBefore[1], son.a[n], and a lone-outgoing decision →a[n](DecisionPerformance::outgoingHBLink[1]), are derived the same way. Both then refuse at the node's predecessor.tallybarrier therefore differ only in their mandated ends, not in their defaults.[0..*], so running it once per token arrival is the executor's reading, not a derived rule. Its compliance row is nowsemantics.AssumedRangecomment no longer attributes[1..1]to KerML §7.4.5, which says "the usual default of 0..*".1..1;1..1;1..1; into a merge: source0..1;1..1; out of a decision: target0..1.TransitionPerformances.kerml(guarded successions):HappensBefore;SelfLink) and KerML §7.4.11 (feature values).ControlFunctions.kerml'.': source and result[0..*], nonunique.LoopPerformance/IfThenPerformance: each body pass is a performance of its own.Parts::performedActionsandOccurrences::enactedPerformances: the basis for part-level performed actions.The derivation is recorded in
docs/project/behavior-semantic-oracle.md(repeated action steps), the SMT encoding indocs/internals/design/smt-model-checking.md, and the RDF form indocs/reference/rdf-mapping.md. Indocs/project/spec-compliance.md:How it was verified
Parser: keywordless
first p if g then [*] b;parses as aGuardedSuccessionagain, the same assuccession first p if g then [*] a;. The multiplicity-first succession probe now stops at a top-levelifbeforethen, so the member is no longer claimed as a plain succession. Theguarded_succession_target_multiplicitygolden moves fromInitialNodetoTransitionMember. The old golden was wrong:InitialNodeMemberis just'first' QualifiedName ';'and has no guard or target end.Lowering: inherited-succession deduplication uses fix(lower): perform inherited action steps and inherit step multiplicity #830's end-multiplicity comparison (
sameActionEdgeMultiplicities, built oncrossingRange). This PR's earlier copy of the same check is removed, so there is one comparison. Before this, an owned plainfirst a then b;swallowed an inheritedsuccession first [*] a then [1] b;, and a verbatim restatement offirst [1] a then [1] bkept two edges.TestToActionGraphKeepsMultiplicityInheritedSuccession,TestToActionGraphKeepsDifferingMultiplicityInheritedSuccessionandTestToActionGraphDeduplicatesRestatedMultiplicitySuccessioncover this.Inherited steps:
action-step-order-openfor its unchanged model (inherited repeated step in an unordered loop body is refused as open order). This PR lifts the block-body multiplicity refusal, and that body states no succession.tick[2]againstbumpwithsuccession first [*] tick then [1] bump;and runsticktwice per pass. A plainthenthere stays refused under the strict policy.TestAnalyzeCountsInheritedRepeatedActionStepsencodes an inheriteda[3]with count 3.[3], and theperform action run[n]budget.SMT fan-out:
TestEncodeRepeatedStepFanOutCompleteschecks that the fan-out fixture completes (seenByQ = seenByR = sum = 3).TestEngineWitnessesRepeatedStepFanOutchecks that a false property over it is violated, with a witness the runtime replays.Parser goldens:
action_step_multiplicity_loop_body,perform_action_multiplicity,guarded_succession_target_multiplicity.Execution conformance fixtures:
action_step_multiplicity_while_body,for_body,if_body,unordered_loop_body;part_perform,external_read,pin_value,bind_input,bind_output;join_per_performance,merge_per_performance;fork_barrier,decision_barrier,merge_fanout, which run under the one-performance reading;while_body,unordered_loop_bodyandmerge_per_performancekeepexploreBudget: {"runs": 8192}, andjoin_per_performancekeeps2048. The default 1024 runs leave their exploration incomplete. The budget is pinned for completeness only and does not affect the observed standing;loop_body_race: the admitted set is{c = 1, c = 2}. Exploration reaches onlyc = 2, and the fixture pins the observed standing through a newexploreNotesschema field.while_body,for_body,if_bodyandunordered_loop_bodycarry the same note, with outcomes unchanged;guard_true,guard_false,guard_false_single,fork_into_repeated.Trace goldens: the loop body and the five control-node cases.
Robustness:
robustness_repeated_step_coverage_test.go:TestRuntimeRobustnessRepeatedStepCoveragecovers:perform action run[0], and per-inner-pass counting forwhilenested infor;[0..2]in a loop staying not-fixed;a[2]unaffected;Check-engine coverage: every accepted control-node, exact, zero, guard-true, external-read and pin/binding fixture has a
.check.expected.json.TestCheckConformanceOracles,TestCheckAgreesWithExploreOverTheConformanceCorpusandTestCheckWitnessesReplayOverTheConformanceCorpusrun them through the explicit-state check, exploration and witness replay.exploreBudgetwithoutoutcomeswhen the case has a check expectation (join_per_performanceneeds 1680 exploration runs). New schema-test rows pin both the refusal and the allowance.-engine checkalready refuseswhile/for/ifbodies ondevelopwithout any repetition (token 1 is not one the step may move). That is a pre-existing limitation, not in scope here; run and explore agree on those fixtures.Other unit tests:
CheckSteprefusal);sysx:sourceTextstripped.Results:
At the latest head,
go build/vet/gofmt, the lower, behavior-pass, SMT, analysis and runtime packages, the runtime race suite (conformance, traces, robustness),./tests/...,make docs-checkand the changelog check were rerun: all ok.Corpus gates ran with
OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1 OPENSYSML_REQUIRE_PSSM_SUITE=1, after all four download scripts:./tests/...,./cmd/sysml/...,./internal/exec/analysis/...,./internal/exec/solve/...,./internal/frontend/repl/...,./internal/workspace/model/...: ok;go test -C tools ./referee/pssm/... ./oracle/errata/...: ok;go run -C tools ./cmd/pilot-diff -check: 381 files, 343 fully agreeing, baseline reproduced.SMT referee corpus: 7 encoded, 10 refused, 7 agreeing (unchanged by the
CheckStepagreement fix). No baseline moved, so nothing was regenerated, andtraining_examples_expected.txtis untouched.Two defects that end-to-end CLI testing found are fixed here:
stepTokenNoting).Pendingmove, andTestEngineWitnessesReplayOverRepeatedStepspins that the witnesses replay (exact, fork barrier, merge fan-out, join and merge per performance).A flat
a[2] { t := c; c := t + 1; }outside any block still explores as proved overc = 2only. That is the leaf-body indivisibility the separate body-interleaving change addresses, and it holds ondeveloptoo. Merged with that change, the flat case reaches{c = 1, c = 2}, but the block-body cases do not, which is why they report observed here.The SMT engine already refuses any nested block flow, so it never encodes a repeated step inside a loop or if body.
internal/workspace/libs/stdlib.snapshotis regenerated withmake stdlib-snapshot, because the AST codec now encodes the succession target ends.Two existing tests now assert shapes that are newly supported. Neither was weakened:
TestAnalyzeRefusesRepeatedActionStepsbecameTestAnalyzeCountsRepeatedActionSteps.TestFirstThenWithADeclaringEndIsRefusednow grafts its[2]bound onto the source end.then [2] bis legal grammar, and the source end still has no notation.Checklist
make testandmake lintpass locallychanges/unreleased/repeated-step-coverage.added.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (no gate count moved)F4,K5) in the body, docs, or changelogLink to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/72bb3b97dfe04df688e76e4a623130eb
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/72bb3b97dfe04df688e76e4a623130eb?variant=devin
Requested by: @HuiJun