Skip to content

feat(runtime): dynamic object creation and destruction - #468

Merged
HuiJun merged 17 commits into
developfrom
feature/dynamic-object-lifecycle
Sep 21, 2026
Merged

HuiJun merged 17 commits into
developfrom
feature/dynamic-object-lifecycle

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

What and why

An object was materialized once, from its declaration, and nothing destroyed it: new T(…) built a message-shaped value for send, an object written into a feature was classified by it but owned by nothing, destroy refused an object whose state machine was under way (ErrOccurrenceLifetime), and a destroyed object stayed in all T. This is the object-model item the roadmap's addressed-send item (E7) waits on: a context must hold more than one object of one usage and be able to destroy one.

Now an object created while a behavior runs is a first-class occurrence of the run:

  • evalConstructorContext.constructObject: materialize, then startClassifierBehaviors, so a new Car(…) has its own identity, begins its life where it is made, is classified by Car and runs the behaviors Car exhibits/performs. Two new Car(…) in one context are two objects; a loop creates one per iteration.
  • SetFeatureValueContext.holdWritten: the holding feature's type classifies the written object (classifyHeld), and a composite feature (not ref) adopts an ownerless object as a portion of the owner (owner/ownerFeature, journaled), so destroy of the whole ends it.
  • destroy: ends the occurrence and its portions, then endBehaviorsWith terminates the state machine it exhibits and the actions it performs where they stand; a destroy reached from inside one of them unwinds it as terminate does. The checkMayEnd/performanceUnderWay refusal is gone.
  • objectsOf skips destroyed objects, so all T releases them; a feature still naming one keeps the value and reading through it is ErrOccurrenceDestroyed.
  • nextRunnableBehavior discards a pending behavior whose object was destroyed before its first run; performanceOccurrence resolves an already-held performance occurrence without dereferencing the destroyed object, so a held image of such a graph materializes.

Spellings are the specifications' only: KerML §7.4.9 new T(args) in any expression position and OccurrenceFunctions::create/addNew/addNewAt. SysML v2 defines no other textual constructor for an occurrence of a definition; the compliance record says so. The addressed send … to <expr> that picks the second object is not part of this change.

Specification basis

KerML 1.0 §7.4.9 (InstantiationExpression/ConstructorExpression), SysML v2 §7.6 (feature values, occurrence usages), Kernel Semantic Library Occurrences.kerml (an occurrence's life; OccurrenceFunctions::create/destroy). docs/project/spec-compliance.md: a new Dynamic object creation row in the Instantiation map, the create/destroy row and the all T row updated to the new semantics, and the "Dynamic object creation/destruction" bullet removed from Major Features Not Implemented. docs/project/roadmap.md records the item as landed and unblocks E7.

How it was verified

  • Conformance object_created_by_constructor and object_destroyed_at_runtime, each with a trace golden (create: Car #n per object; destroy: Car #5 followed by the terminated machine, no later run).
  • robustness_object_lifecycle_test.go TestRuntimeRobustnessObjectLifecycle: two objects of one usage distinct and held, creation in a loop, destroy through a reference, destroy ends the created object's machine, destroy twice refused, destroy of the whole ends created parts, deterministic explore over creating branches, a destroy/read race reaching exactly two outcomes.
  • TestDestroyRefusedWhilePerforming became TestDestroyEndsTheMachinePerformed.
  • go build ./..., go vet ./..., gofmt -l . empty, python3 scripts/changelog.py check, python3 scripts/check-doc-ids.py, and go test ./... with OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1 all green (the one timing test TestFormattingDiffIsCheaperThanFormatting failed once under the full parallel run and passes with -count=3 alone).
  • REPL: after two new Car the fleet lists [Instance(ID: 5), Instance(ID: 7)] in %features and size(all test::Car) counts them.

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

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration
devin-ai-integration Bot force-pushed the feature/dynamic-object-lifecycle branch 2 times, most recently from c3b7d2b to eff211a Compare September 20, 2026 20:03
@devin-ai-integration
devin-ai-integration Bot changed the base branch from develop to feature/streaming-flows September 20, 2026 20:03
@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #476 September 20, 2026 20:04
@devin-ai-integration
devin-ai-integration Bot force-pushed the feature/dynamic-object-lifecycle branch from eff211a to 4a50d1e Compare September 20, 2026 20:09
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@HuiJun
HuiJun force-pushed the feature/dynamic-object-lifecycle branch from 5adf252 to 26197a9 Compare September 21, 2026 01:30
@HuiJun
HuiJun force-pushed the feature/dynamic-object-lifecycle branch from 26197a9 to 7a39997 Compare September 21, 2026 02:13
Base automatically changed from feature/streaming-flows to develop September 21, 2026 05:44
An object created by `new T(args)` while a behavior runs is a first-class occurrence of the run: it has its own identity, begins its life where it is made, is classified by its type and starts the behaviors its type exhibits or performs, so a context holds several objects of one usage and a loop creates one per iteration. Writing an object into a feature classifies it by the feature's type and, where the feature is composite and the object owns no whole yet, adopts it as a portion of the owner.

`destroy` ends the occurrence and its portions, terminates the state machine it exhibits and the actions it performs where they stand instead of refusing with ErrOccurrenceLifetime, and releases the object from `all T`; a feature still naming it keeps the value and reading through it is ErrOccurrenceDestroyed. A behavior ended before its first run takes no run, and a snapshot of a graph holding a destroyed object materializes.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration Bot and others added 13 commits September 20, 2026 22:44
…ite feature drops

A `send new T(...)` now goes through constructObject, so the message object starts the behaviors T exhibits or performs like any other constructed object. A composite feature that stops holding an object it owned releases it, so the composite feature it is written into next owns it and ends it on destroy.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ailed constructor's argument objects

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…eep destroyed objects out of the check roots

Co-Authored-By: jason.han <hanhuijun@gmail.com>
… no adoption to undo

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ack destroy revives the executors it ended

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…eature holds it

Co-Authored-By: jason.han <hanhuijun@gmail.com>
… the object

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…every argument is stored

Every hold under a store defers the behaviors it starts, and constructObject writes the
arguments under one store boundary (Context.storedTogether), so a behavior the first argument
starts reads the later ones. Intrinsic materialization, which binding resolution uses to inspect
an endpoint, is under the same boundary as the ordinary materialization.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ed object was addressed

A composite write that would make the holder a portion of itself is refused
as ErrOccurrenceLifetime before anything is released, adopted or stored.
Destroying an object drops the bus messages addressed to it and its portions;
the journal restores them when the destruction is rolled back.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…a check

carriersUnder passes over a destroyed object and what it holds, as
rootInstances already does for a destroyed root, so a global constraint is
not read on a stale part nor made ambiguous by it.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…all T

A `=` value that read a feature of an object `destroy` ends, `isDuring` of it,
or an extent `all T` is unmaterialized when the lives change (an object created,
destroyed, or a performance begun or ended), so reading it again finds the object
destroyed or the extent as it is now rather than the cached result. The lives are
a dependency source the derivation journal rolls back with the lifetimes.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
A journal's mark now captures the trace recorder, so a store or constructor
rolled back because a behavior it started failed leaves no records of that
behavior's run; a committed store keeps its trace.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Every change of the lives unmaterializes the settled values that read them,
a life begun while another value derives included; only the value deriving
as the change is made is spared.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun force-pushed the feature/dynamic-object-lifecycle branch from 7a39997 to ab453f0 Compare September 21, 2026 05:44
devin-ai-integration[bot]

This comment was marked as resolved.

…, ended wholes adopt nothing

A held image records that a derived value read the lives and, where it is
materialized, links it to the destination's lives (rolled back with a
failed materialization). An object its home's composite feature drops moves
home to another composite feature still holding it. A composite write giving
an ended whole a live portion is refused as ErrOccurrenceLifetime.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…es no portion ending after it

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 45b06dc into develop Sep 21, 2026
15 checks passed
@HuiJun
HuiJun deleted the feature/dynamic-object-lifecycle branch September 21, 2026 07:34
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