feat(fuml): translate classes, structural features, signals and active-class behavior in the referee's emitter - #413
Merged
Conversation
… actions A fUML Class becomes a part def with its generals as supertypes and every attribute at its exact multiplicity; CreateObjectAction is `new T()` on the result pin and starts no behavior; Read/Add/Remove/ClearStructuralFeatureAction read or assign the object's feature as the reference implementation computes it (replacing add, first-position insert, indexed insert and remove, unique copies dropped, first or every copy removed) and hand the object on through the result pin. Untyped parameters stay untyped; an inout parameter's two nodes are told apart by their edges. The referee materializes class-typed inputs as the reference does (an object per class with defaulted features) and renders objects by type and feature, numbered by first mention, so object outputs compare. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
The referee baseline records 20 pass, 0 fail, 31 not-expressible and 4 differs-by-design: TestGeneralizationAssembly, TestClassObjectCreator, TestClassWriterReader, TestClassAttributeWriter and TestClassAttributeValueRemover move from not-expressible to pass, each adjudicated in docs/project/fuml-referee.md with its construct map row. A CreateObjectAction whose classifier is an activity is refused naming the activity rather than reported as an unknown class. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… in the referee's emitter A fUML Signal with its attributes and generalizations is an `attribute def` specializing its generals, declared before the classes and activities of the closure; a SendSignalAction is an action sending `new <Signal>(...)` to the object at its target pin with one argument pin per attribute, inherited ones included; an AcceptEventAction of one SignalEvent is an `accept` node whose result pin is the instance received. A specialized signal satisfies an accept of its general through the runtime's conformance matching. The lowering now records an accept's payload parameter as the node's output feature and the executor binds the accepted value on the accept's own performance, so an object flow out of the accept pin carries the instance on. The suite's signal activities also use an activity as a class, so their reasons sharpen and their buckets stay; the referee record and the baseline's reasons are updated, the counts unchanged. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…er-objects-and-signals
…er-objects-and-signals Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/exec/runtime/robustness_accept_payload_test.go # internal/exec/runtime/testdata/conformance/accept_payload_flows_from_pin.expected.json # internal/exec/runtime/testdata/conformance/accept_payload_flows_from_pin.sysml
HuiJun
marked this pull request as ready for review
September 19, 2026 22:51
…er-objects-and-signals Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # tools/referee/fuml/emit.go
…d signal attributes, lower accept triggers without a payload pin An unordered output holding objects is spelled in an order its values fix, not the order they arrived in, so equal multisets compare equal whichever object reached the parameter first. A signal attribute typed by a class is a reference to an object of that class, spelled as a class attribute is, and the class closure follows a signal's attributes so the referenced class is declared. An accept whose trigger is a condition or a time (accept when/at/after) produces no value, so it declares no output feature; only a message payload is the accept's output pin. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… removeAt on scalars A class or signal of the model named Integer, Boolean, String or Real is that classifier wherever the model references it; the primitive is then spelled ScalarValues::<name> so the package-local part def does not shadow it. A RemoveStructuralFeatureValueAction with a removeAt pin on a single-valued feature empties it at position 1 regardless of the value pin, as the reference implementation removes by position. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A class's or signal's effective attributes held both an inherited property and the one redefining it, so a specialized signal with one logical attribute expected two argument pins and a redefined class attribute was declared twice. The reader keeps each property's redefinedProperty references, AllAttributes drops what another effective property redefines, and the emitter spells the redefinition as `:>>`. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…t includingAt validate every insertion position An external type reference (an href fragment from the UML primitive types or the fUML library) names no class or signal of the model, whatever XMI id a local classifier carries; ClassOf, SignalOf, primitive and the classifier's behavior lookup all return none for it. A positioned add hands every position but `*` to includingAt, so a position of 0 is the runtime's out-of-range error rather than an insertion first. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…mention Two recorded objects alike in every feature but held by different parameters numbered by the order the unordered parameter met them, so an equivalent object graph could render with swapped aliases on the two sides. The renderer now builds each side's object graph, refines the objects by type, feature values and holders until the classes settle, and numbers them in class order, falling back to first mention only for objects nothing tells apart. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
A fUML signal instance is a value and the reference implementation's record gives it no identity, so a signal one run instance delivers to two outputs must spell whole in both, not as an alias of its first mention. The run-side renderer keys objects by instance and signals by mention, re-entering an instance already being read by identity so a cyclic reference stays bounded. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… not a copy Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…er-objects-and-signals
…er-objects-and-signals
…nd SignalOf A reference carrying an XMI id names that element and no other: a miss on the class map no longer falls back to a class sharing the name of the signal the id names, and symmetrically for signals. Name lookup serves only references without an id. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…er-objects-and-signals
…er-objects-and-signals
…er-objects-and-signals
…zing occurrences Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ect numbering on the partition A run object whose declared feature cannot be read no longer spells as comparison text: renderOutputs returns the runtime's error and compare files it with the run's errors, so the row reads as a run error rather than as outputs differing. Canonical numbering refines from each object's class so far, making each round a split of the one before; a stable class count is then a settled partition, and objects the same graph mentions in different orders number alike. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…xplicit perform A class's owned behavior is emitted as an action def nested in its part def and its classifier behavior as a declared action member no creation starts; an activity instantiated as an object becomes such a part def around its own body, ReadSelfAction in an owned behavior binds `this` to the owner and StartObjectBehaviorAction becomes `perform object.classifierBehavior.start;`. An owned behavior's referee row inherits the verdict of the activity that starts an object of its owner. The lowering gives a perform naming a behavior's `start` its own effect kind and the runtime starts the behavior as the object's own execution: journaled and rolled back whole on failure, a second start of a running behavior a no-op, an inherited member found on the specialized object, and the object's behaviors drained again once the enclosing top-level performance ends so a message sent after the start wakes an accept the behavior parked at. A weight other than 1 and an object-flow cycle through control nodes stay typed translation refusals. ActiveClassBehaviorSender, ActiveClassBehavior and TestSpecializedSignalSend move to pass: 23 pass, 0 fail, 28 not-expressible, 4 differs-by-design. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
ActiveClassBehaviorSender, ActiveClassBehavior and TestSpecializedSignalSend move from not-expressible to pass: 23 pass, 0 fail, 28 not-expressible, 4 differs-by-design, the pin and digests unchanged. A behavior a type declares for its objects to start carries whether it names the element holding its body, as an exhibited or performed one does. Co-Authored-By: jason.han <hanhuijun@gmail.com>
An activity the model instantiates as an object is its part definition wherever a reference names it by id, even when it bears a ScalarValues name; the scalar is then spelled qualified beside it, as it is beside a class or signal of that name. The renderer reads the features of an object of an activity classifier from the activity's attributes instead of rendering it unknown. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…, not the model's names The renderer and the input defaults looked a runtime type name up among the model's classes, then signals, then activities, so an object of an activity that shares its name with a class or a signal took the namesake's attributes, and an activity-typed input parameter had no default at all. Both now resolve through the translation's closure, whose package declares one definition per name: the renderer reads the attributes of the part or attribute definition the emitted model declares under the type's name, and an input typed by any classifier the package declares — a class, an instantiated activity or a signal — defaults to a fresh instance whose attributes hold their types' defaults, as Environment.makeValue does. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…avior through callers of its starter A `perform` chain ending in `start` is lowered to a start effect only where the `start` is the shot every behavior has: an action a part def declares under that name is performed as any other feature. Lowering resolves the operand through the scope tree; a path it cannot follow is left as written, so a start of a non-behavior still reaches the runtime's typed refusal. The fUML referee files a class's owned behavior by the activities whose run starts an object of the class: the activity holding the start and every activity calling it, transitively, of which those the record executes carry the row. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A value a streaming flow carries to a target not yet under way waited under the source performance and pin alone, so two flow declarations out of one pin into one target pin collapsed into one delivery, and a second performance of a nested action definition with a required input ran with it unbound. Each flow declaration is a transfer of its own: the runtime, the held image and the smt encoder key the staged place by source performance, pin and flow. Two flows naming one pin by its inherited and redefining name now deliver twice; ForkMergeData on the fUML suite reaches its recorded output again. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
…zing objects Only a behavior's or step's parameter is referential; a port definition's `in item` is a flow item the port holds, so an exhibited state machine's parameter can bind from it. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A message the started behavior sends could wake an older parked behavior inside the start's journal; a failure then rolled back the writes and the bus but left that behavior past its accept. The start now drains within a run boundary, as a store does, and the woken behaviors run after commit. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…er-objects-and-signals
6 tasks
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.
What and why
The fUML referee's emitter (
tools/referee/fuml/emit.go) translated only the primitive-valued activities of the reference implementation's test models; nine expressible activities stayednot-expressiblewith anot yet translated:reason because they create objects, write structural features, accept signals or run a class's behavior. This PR grows the emitter over those constructs — classes and structural features, signals, active classes — one rule per construct with its SysML v2 spelling recorded indocs/project/fuml-referee.md, extends the referee so object-valued inputs and outputs execute and compare, and gives the runtime what the rules need: an accepted signal flowing on from the accept's result pin, and a declared behavior started on an explicitperform.Classes, object creation, structural features
Class→part def, generals as:>supertypes, each attribute at its exact declared multiplicity ([0..*] ordered nonunique,[0..3], …; the UML default[1..1]unwritten). A redefining property replaces the one it redefines; classifiers resolve by ID first and only then by name, and external type references never resolve to local classifiers.CreateObjectAction→action N { out result : T = new T(); }. Creation starts no behavior;StartObjectBehaviorActiondoes (below).ReadStructuralFeatureAction→out result … = object.f;at the feature's multiplicity.AddStructuralFeatureValueAction/RemoveStructuralFeatureValueAction/ClearStructuralFeatureAction→assign object.f := …;with the reference implementation's semantics: a replacing add or a single-valued feature takes the value; a unique feature drops its old copy; an unpositioned add inserts first (the reference'sFirstChoiceStrategy),insertAtinserts at that one-based position with*appending and0out of range; a remove drops every copy (isRemoveDuplicates), the copy atremoveAt, or the first copy; clear empties. The object flows on through the result pin.String) are declared without a type; aninoutparameter's input and output nodes are told apart by their edges.tools/referee/fuml/run.go,render.go) materializes class-typed inputs the way the reference'sEnvironment.makeValuedoes (an object per class, features defaulted recursively) and renders object outputs by class and feature values, numbered by graph refinement rather than arrival order so identical objects compare by reference structure; a feature read that fails is a run error, not text.Signals
Signal→attribute defwith its attributes, specializing its generals, declared before the classes and activities of the closure; a class-typed signal attribute references its class.SendSignalAction→ an action sendingnew <Signal>(…)to the object at its target pin, one argument pin per attribute, inherited ones included.AcceptEventActionof oneSignalEvent→ anacceptnode whose result pin is the instance received; an untyped result pin takes the trigger's signal type; a specialized signal satisfies an accept of its general through the runtime's conformance matching.internal/ir/lower/action_graph.go) records an accept's payload as the node's output feature, and the executor (internal/exec/runtime/action_executor.go) binds the accepted value on the accept's own performance, soflow receiver.msg to consumer.valuecarries the instance on. An accept whose trigger is awhen/at/aftercondition has no payload and declares no output.Active classes
action defnested in itspart def; its classifier behavior → the memberaction classifierBehavior : <Behavior>;, a declaration no creation starts.ReadSelfActionin such a behavior →this; at the top level of an activity performed on its own it stays a typedTranslateError(self there is the performance, not an object).TestSpecializedSignalSendcreates and startsTestSignalReceiver) → apart defof the activity's name around anaction defof its body, when the body's shape is representable; a call of a class's owned behavior stays a typedTranslateError.StartObjectBehaviorAction→action N { in object : T; perform object.classifierBehavior.start; }, the classifier behavior found on the object's class or inherited from a general; a start passing arguments, of an object of a class without classifier behavior, or without an object pin is a typedTranslateError.ActiveClassBehaviorthroughActiveClassBehaviorSender) —failwhen one fails, elsepasswhen one passes, each reason naming the starter and, when the start is reached through a call, the activity holding it.perform obj.beh.start;lowers to a start effect (lower.EffectStart) only where the operand, resolved through the scope tree, is a behavior the object holds or a feature declaring nostartof its own — an action a part def declares asaction start : Launch;is performed as any other feature — and the effect is whatinternal/exec/runtime/start_behavior.goruns as the object's own execution —thisthe object, its writes on the object's features, a message sent afterwards waking an accept it parks at, the object outliving the behavior's completion. Object behaviors are drained after the enclosing top-level action completes, so a sender's signals reach a behavior it started in the same run. A second start of a running behavior starts nothing more; a start on no one object or of a member that is no behavior of the object is a typed error; a start that fails is undone whole — its own work: an older parked behavior a message of the started one wakes is drained only once the start is kept, within a run boundary as a store's, so its move is never inside the start's undo; the trace records the start as the object's execution. A performed action that names no separate body (perform action a { … }) keeps resolving to itself.Runtime fixes the referee surfaced
flow Action_A.result to Action_B.input;routes of a fUML fork duplicating a token collapsed into one delivery and the second performance of the nestedCopierran with its requiredinputunbound. Eachflowdeclaration is a transfer of its own:internal/exec/runtime/action_frame.gostagekeys the waiting place by source performance, pin and flow declaration (held images carry the flow), andinternal/exec/smt/encode.gostagedSlotkeys the encoding's slot the same way, so a later write along the same flow still replaces its earlier one while two flows each stage the write.action_flow_streaming_aliased_source_pin(a pin named by its inherited and its redefining name, two flows) states the contract and expects two deliveries;action_flow_streaming_two_flows_one_pin_to_callandstream_stage_test.golock the fork shape.ForkMergeDatareaches its recorded0, 0again; it staysdiffers-by-designfor per-token re-firing alone. Fragmentchanges/unreleased/streaming-flow-per-declaration-transfer.fixed.md; the streaming-flow row ofspec-compliance.mdcarries the rule.inparameter bind to the object it is performed on.semantics.IsBehaviorParameterrestricts this to parameters a behavior or step owns, so a port definition'sin item cmd : Cmd;keeps its composite value and an exhibited state machine's parameter binds fromengineControl.cmd(exhibited_state_self_target).Not translated
An edge weight other than 1 and an object-flow cycle through control nodes remain typed translation refusals naming the edge; no activity of the pinned suite has either (all 439 test-model and 93 exception-model weights are
1), so no row depends on them.TestSignalReceiverstaysnot-expressible: it reads self as a top-level activity performed on its own.Referee
./scripts/download-fuml-suite.sh && go run -C tools ./cmd/fuml-referee -jobs 8:Moved to
pass:TestGeneralizationAssembly,TestClassObjectCreator,TestClassWriterReader,TestClassAttributeWriter,TestClassAttributeValueRemover(objects and structural features);TestSpecializedSignalSend,ActiveClassBehaviorSender,ActiveClassBehavior(signals and active classes). Every moved row is adjudicated indocs/project/fuml-referee.md; the classifier's 27 rows did not move; the fixed expected record forActiveClassBehavior(skipped, runs only as part of its owner) is unchanged. The baseline's pin — tagv1.5.0a, commit45e50633…, the four digests — is unchanged; only itsdevelopprovenance advanced with the merge ofdevelop.Specification basis
fUML 1.5 (formal/2021-07-01) § 8.6 Actions —
CreateObjectAction,ReadSelfAction, the structural feature actions,SendSignalAction,AcceptEventAction,StartObjectBehaviorAction— and § 8.8 object-behavior startup (creation starts nothing; an explicit start does), as the pinned reference implementation (v1.5.0a) executes them.docs/project/spec-compliance.md: the fUML referee row carries the counts above, and a new row records the declared-behavior start (passive construction, explicitperform obj.beh.start;,this, message wake-up, owner preservation, duplicate start, inherited behavior, typed failures, rollback, trace).docs/internals/design/precise-semantics-alignment.mdstates the same lifecycle.How it was verified
tools/referee/fuml/emit_test.go:TestEmitClasses,TestEmitObjectCreationAndFeatureWrites,TestEmitFeatureReads,TestEmitReadSelf,TestEmitActivityAsObject,TestExecuteStartedObject,TestExecuteObjects,TestExecuteUnorderedObjects,TestExecuteIdenticalObjectsNumberByReference,TestEmitSignals,TestEmitRedefinedProperties,TestEmitSignalTypedAttribute,TestEmitClassTypedSignalAttribute,TestEmitPrimitiveNamesakeAndPositionedScalarRemove,TestEmitExternalTypesNeverLocalClassifiers,TestEmitIDReferencesNeverResolveByName,TestExecuteInsertAtZeroIsOutOfRange,TestEmitSendSignal,TestEmitAcceptEvent,TestRenderSignalValues,TestRenderNumbersObjectsByStructureNotArrival,TestExecuteSignals,TestEmitRefusesWeightsAndCycleson hand-built models; every emitted model passesValidate.referee_test.go:TestRefereeOwnedBehavior,TestRefereeOwnedBehaviorStartedThroughACall.internal/ir/lower:TestPerformOfStartLowersToStartEffect,TestPerformOfBehaviorStaysPerform,TestPerformOfDeclaredStartActionStaysPerform,TestAcceptNodePayloadIsItsOutputPin.internal/exec/runtime:TestRuntimeRobustnessClassifierBehaviorStart(passive construction, start as the object, wake by a later message, second start, inherited behavior, start on no object, start of no behavior, an action declared asstartperformed rather than started, failing start undone, trace),TestStartedActionAwaitingAMessageIsWokenByASibling,TestRuntimeRobustnessAcceptPayload, conformance pairsaccept_payload_flows_from_pin,action_flow_streaming_two_flows_one_pin_to_call,action_flow_streaming_aliased_source_pin(two deliveries),stream_stage_test.go:TestStagedStreams(same-flow replacement, distinct flows side by side);internal/semantic/semantics/shape_test.go:TestIsBehaviorParameter.go build ./...,go vet ./...,gofmt -l .(empty),go test ./...(root andtools/),make lint,make docs-check,python3 scripts/changelog.py check,mkdocs build --strict— all clean on the merged head.OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test -count=1 ./tests/corpus -run 'TestTrainingExamples|TestPilotCorpora'clean.go run -C tools ./cmd/fuml-referee -jobs 8 -checkclean;-jsontwice byte-identical.Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (compliance rows need nothing: the census is counted at docs build)F4,K5) in the body, docs, or changelog