Skip to content

feat(fuml): translate classes, structural features, signals and active-class behavior in the referee's emitter - #413

Open
devin-ai-integration[bot] wants to merge 11 commits into
developfrom
feature/fuml-emitter-objects-and-signals
Open

devin-ai-integration[bot] wants to merge 11 commits into
developfrom
feature/fuml-emitter-objects-and-signals

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

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 stayed not-expressible with a not 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, one construct group per commit, and extends the referee so object-valued inputs and outputs execute and compare.

Draft — work in progress. The status of each group:

Done: classes, object creation, structural features

  • fUML Classpart def, generals as :> supertypes, each attribute at its exact declared multiplicity ([0..*] ordered nonunique, [0..3], …; the UML default [1..1] unwritten). Classes are resolved by ID first and then by name, so a type reference with a usable ID but no name resolves.
  • CreateObjectActionaction N { out result : T = new T(); } — creation starts no behavior (that is StartObjectBehaviorAction, under active classes below).
  • ReadStructuralFeatureActionout result … = object.f; at the feature's multiplicity.
  • AddStructuralFeatureValueAction / RemoveStructuralFeatureValueAction / ClearStructuralFeatureActionassign 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's FirstChoiceStrategy), insertAt inserts at that position with * appending; a remove drops every copy (isRemoveDuplicates), the copy at removeAt, or the first copy; clear empties. The original object flows on through the result pin.
  • ReadSelfActionthis inside a class-owned behavior; at the top level it stays a typed TranslateError ("reads self in an activity no class owns").
  • Untyped parameters (the reference runs them as String) are declared without a type; an inout parameter's input and output nodes are told apart by their edges.
  • The referee (tools/referee/fuml/run.go) now materializes class-typed inputs the way the reference's Environment.makeValue does (an object per class, features defaulted recursively) and renders object outputs by type and feature, numbered by first mention, so Object/Reference records compare.

Done: signals

  • fUML Signalattribute def with its attributes, specializing its generals, declared before the classes and activities of the closure.
  • SendSignalAction → an action sending new <Signal>(…) to the object at its target pin, one argument pin per attribute, inherited ones included.
  • AcceptEventAction of one SignalEvent → 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.
  • Runtime: the lowering (internal/ir/lower/action_graph.go) records an accept's payload parameter 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, so an object flow out of the accept pin carries the instance on. Conformance pair accept_payload_flows_from_pin and TestRuntimeRobustnessAcceptPayload cover it.
  • No row moves yet: the suite's two signal activities also use an activity as a class (TestSignalReceiver is a top-level activity with an attribute that TestSpecializedSignalSend instantiates and starts), so their reasons sharpen and their buckets stay until the active-class group lands.

Merged: develop's layering relayout

origin/develop is merged in (internal/core/runtimeinternal/exec/runtime, internal/core/lowerinternal/ir/lower, corpus gates → tests/corpus). Every file this PR adds or edits is at its new path; the runtime's added conformance pair and robustness test were relocated by hand. The relayout changed nothing in substance for this PR — only paths.

Referee so far (go run -C tools ./cmd/fuml-referee -jobs 8):

bucket before after
pass 15 20
fail 0 0
not-expressible 36 31
differs-by-design 4 4

Moved to pass: TestGeneralizationAssembly, TestClassObjectCreator, TestClassWriterReader, TestClassAttributeWriter, TestClassAttributeValueRemover, adjudicated in docs/project/fuml-referee.md; the baseline is updated and -check is clean. The classifier's 27 rows did not move. Four rows remain the emitter's: ActiveClassBehavior, ActiveClassBehaviorSender, TestSignalReceiver, TestSpecializedSignalSend.

Remaining

  • Active classes: a class's owned behavior emitted as the part def's performed/exhibited behavior, StartObjectBehaviorAction starting it asynchronously (never creation), an activity used as a class (ActiveClassBehavior, ActiveClassBehaviorSender, TestSignalReceiver, TestSpecializedSignalSend).
  • Edge weights and object-flow cycles through control nodes, if the runtime supports them; otherwise a fail with the reason.
  • Final docs/project/fuml-referee.md / spec-compliance.md / changelog pass, -update / -check, -json determinism, full gates, un-draft.

Specification basis

fUML 1.5 (formal/2021-07-01) § 8.6 Actions — CreateObjectAction, ReadSelfAction, the structural feature actions, SendSignalAction, AcceptEventAction — as the pinned reference implementation (v1.5.0a) executes them; § 8.8 for the object-behavior startup still to come. The spec-compliance.md row citing the fUML referee carries the counts above.

How it was verified

  • tools/referee/fuml/emit_test.go: TestEmitClasses, TestEmitObjectCreationAndFeatureWrites, TestEmitFeatureReads, TestEmitReadSelf, TestEmitRefusesAnActivityAsObject, TestExecuteObjects, TestEmitSignals, TestEmitSignalTypedAttribute, TestEmitSendSignal, TestEmitAcceptEvent, TestExecuteSignals on hand-built models; every emitted model passes Validate.
  • internal/exec/runtime: conformance pair testdata/conformance/accept_payload_flows_from_pin.{sysml,expected.json}, robustness_accept_payload_test.go.
  • go build ./..., go vet ./..., gofmt -l . (empty), go test ./... (root and tools/), make lint, make docs-check, python3 scripts/changelog.py check, mkdocs build --strict — all clean after the merge.
  • OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test -count=1 ./tests/corpus -run 'TestTrainingExamples|TestPilotCorpora' clean.
  • ./scripts/download-fuml-suite.sh && go run -C tools ./cmd/fuml-referee -jobs 8 -check clean; -json twice byte-identical; the baseline's provenance (tag, commit, digests) unchanged.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

… 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>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 5 commits September 18, 2026 19:14
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

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
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
devin-ai-integration[bot]

This comment was marked as resolved.

…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>
devin-ai-integration[bot]

This comment was marked as resolved.

… 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>
devin-ai-integration[bot]

This comment was marked as resolved.

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>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread tools/referee/fuml/model.go
Comment thread tools/referee/fuml/emit.go Outdated
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant