Skip to content

fix(runtime): count package-level connection usages in all T and read them as objects - #1560

Merged
HuiJun merged 12 commits into
developfrom
fix/extent-namespace-connection-usages
Oct 27, 2026
Merged

HuiJun merged 12 commits into
developfrom
fix/extent-namespace-connection-usages

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Closes #1543. A connection, interface or allocation usage a package declares was omitted from all T and read as an undetermined value, while the same usage nested in a part counted and read as one object with its ends attached. The reporter's model answered size(all Q::Link) = 1, Q::outer = <undetermined>, Q::r violated.

Root cause: one predicate, two readers. objectUsageKind (runtime/instance.go) did not admit ast.UsageConnection, ast.UsageInterface, ast.UsageAllocation, so objectFeature, isOccurrenceUsage and namesOneObject were all false for a package-level connection: the extent census modelUsages (runtime/extent.go, objectFeature || namesOneObject) never listed it as a root, and the bare read Q::outer fell through occurrenceReference → resolvedWithoutValue → withoutValue to an undetermined value whose count size() returned. The nested walk uses extentHeldFeature, which did admit connections — hence inner counted and outer did not.

The fix is at the predicate:

  • objectUsageKind admits UsageConnection, UsageInterface, UsageAllocation (a ConnectionUsage is a PartUsage).
  • occurrenceOf routes a namespace-level connector usage through the existing connector path (namespaceConnectorOccurrence → materializeConnectorAs), so its ends are attached to what the connect clause names and the occurrence is recorded as for any namespace root; attachConnectorEnd reads the ends of an owner-less connector in the usage's own namespace scope (a nested connector without an owner is still refused).
  • materializeMember (the one way a namespace path makes an object of a usage, under the next identity or a reserved one: occurrenceOf, materializeMembers for a [n] collection in occurrencesOf, a bind class in resolveNamespaceClass and a namespace subsetting, and requiredMember for a collection past eagerLowerBound made as it is reached) materializes an owner-less connector usage's object with its ends attached (materializeConnectorStarting), leaving its classifier behaviors for the caller to start once the usage denotes it — as every one of those paths already does for a part, so a behavior reading its own usage reaches the connector being made; each of those paths spans one journal and run boundary (connectorJournal) over attaching, recording and starting a connector usage's objects, so a start that fails is undone whole, and the older behaviors the connector woke answer only once it is committed and the path has recorded the usage and cleared its own guards (namespaceCollecting, resolvingNamespaceClasses), their failure their own with the connector kept — as a nested connector's start does — so connection pair : Link[2] connect … denotes two connectors each attached, bind c = d over two package-level connections shares one connector with its ends whichever is read first, and a [0..*] one denotes none. A member a bind joins to a connector made for another usage must attach as its own connect clause says (checkBoundConnectorEnds); bind c = d over connect x to y and connect x to z is errBindingConflict.
  • extentHeldFeature drops the three kinds objectFeature now covers (action/state/flow stay there).

Decision table of the widened predicate's callers

Caller Decision
objectFeature (condition.go) → holdsObjects, extentHoldsObjects, extentHeldMember, namespaceObjectUsage, validation descent Widened: a connection holds its end features as a part holds its parts; the nested connector path (IsConnectorObjectUsage, checked before CompositeTypeOf) is unchanged, and the full runtime, model and corpus suites show no movement but the sweep baseline below.
isOccurrenceUsage → namesOneObject, namesObjects, untypedOccurrenceUsage, denotedObjects, :: reads (eval.go, signal.go, calc_usage.go, assign_chain.go) Widened: this is the read the issue is about; Q::outer is one object, Q::outer.a, ==, != work as nested.
namesOneObject → occurrenceOf Widened, with the connector branch added so the object is a connector with ends, not a bare composite (without it Q::outer.a was a fresh object and Q::outer.a == Q::sys.x false).
namesObjects → occurrencesOf → materializeMembers / requiredMember → materializeMember Widened: [0..*] none, [n] n connectors each attached (a nested connection c : Link[2] is still refused by materializeConnectorFeatureValue as on develop — see the question below).
resolveNamespaceClass, namespacedSubsetObjects → materializeMembers → materializeMember Connector-aware through the shared helper (Devin Review found the bind c = d case: the class made a bare object with no ends); members bound to a connector made for another usage are checked to attach alike.
modelUsages (census roots) Follows objectFeature/namesOneObject; no special case.
extentCandidates, extentHeldFeature Nested walk unchanged in behaviour; the three kinds moved from the local switch to objectFeature.
startClassifierBehaviors Runs for the connector as materializeConnectorAs already does for nested ones.
Lowering / codegen No user of objectUsageKind outside internal/exec/runtime (grep -rn objectUsageKind — all in the runtime package); lowered IR is untouched.

Out of scope, measured: package-level action / state / flow

Unchanged. Before and after (/tmp/sysml-dev built from a clean worktree at develop 48f60b4b8, vs this head), on a package with action a : Act; state s : Sta; flow f of Sig from src.o to dst.i; and the same three nested in part holder:

size(all P::Act) = 1   (holder.ha only)
size(all P::Sta) = 1   (holder.hs only)
P::a = <undetermined>
P::s = <undetermined>
P::f = <undetermined>
size(P::f) = 1          (a count, not an object)

diff of the two outputs is empty. Reading them as objects would start their behaviors as namespace roots (occurrenceOf → startClassifierBehaviors); recommended follow-up: decide whether a package-level performance is a representative object whose behavior runs, or stays model-level, and apply the same predicate route either way. The compliance row now states this.

Specification basis

Verified against the rendered pinned PDFs (build/spec-docs/sysml.txt, kerml.txt):

  • SysML v2.1 Beta 2 §8.4.9.2 Connection Usages: "A ConnectionUsage is a PartUsage and a ConnectorAsUsage"; §8.4.10.2 "An InterfaceUsage is a kind of ConnectionUsage"; §8.4.11.2 "An AllocationUsage is a kind of binary ConnectionUsage … the semantics … are the same as for a ConnectionUsage". So a package-level connection usage denotes an instance of its connection definition.
  • KerML 1.1 Beta 2 §7.4.9.2 Operator Expressions, extent expressions: all "evaluates to a sequence of all instances of the named type"; §7.3.2.1 "the set of things classified by a type is the extent of the type".
  • SysML v2.1 Beta 2 §7.6.3: the implicit [1..1] needs the usage to be an item usage "except if it is a connection usage" (condition 1) and owned by "a definition or another usage (not a package)" (condition 2); a package-level connection fails both, as a package-level part fails the second. The representative-object policy row of docs/project/spec-compliance.md (one object for a valueless usage stating and inheriting no multiplicity, none for [0..*]) is applied to it unchanged.
  • The pilot cannot referee all: it is not model-level evaluable there (docs/project/pilot-execution-referee.md), so no pilot agreement is claimed.

Moves the all T row and the representative-object row of docs/project/spec-compliance.md (text only, no status change).

How it was verified

Reporter's command, before (develop 48f60b4b8, clean worktree build):

✓ size(all Q::Thing)
  = 1
✓ size(all Q::Link)
  = 1
✓ size(Q::outer)
  = 1
✓ Q::outer
  = <undetermined>
✗ Requirement Q::r failed
  Required condition evaluated to false: size(all Link) == 2
  standing: violated (witnessed: 1 run under reverse)

After (make build-sysml on this head):

✓ package Q
✓ size(all Q::Thing)
  = 1
✓ size(all Q::Link)
  = 2
✓ size(Q::outer)
  = 1
✓ Q::outer
  = Instance(ID: 3)
✓ Requirement Q::r satisfied
  standing: holds (observed: 1 run under reverse)

Tests:

  • internal/exec/runtime/testdata/conformance/extent_namespace_connections.sysml (+.expected.json): size(all Thing) == 1, size(all Link) == 5 (nested inner, package-level outer, the two of pair : Link[2], the one c1/c2 share through bind c1 = c2; none : Link[0..*] contributes nothing), size(pair) == 2, pair#(1) != pair#(2), pair#(2).a === sys.x, c1 == c2, c1.a === sys.x, c2.b === sys.y, size(all Tie) == 1 (package-level only, no nested sibling), size(all Ifc) == 1, size(all Aloc) == 1, size(outer) == 1, outer == outer, outer != sys.inner, outer.a === sys.x, outer.b === sys.y, sys.inner.a === sys.x, size(none) == 0, wire.x === sys.px, alloc.y === sys.y.
  • extent_namespace_connections_requirement.sysml: the reporter's model, Q::r satisfied through the requirement path (same evaluator the Go client's VerifyRequirement uses; confirmed by the conformance requirement type).
  • extent_test.go:TestNamespaceConnectorUsagesBoundAndCounted: bind c = d read through c first is one connector with both ends (c === d, c.a === sys.x, d.b === sys.y), pair : Link[2] is two connectors each attached, many : Link[1001] (held lazily past eagerLowerBound) attaches many#(1) and many#(1001) as they are reached, all Link counts 1004 and every live Link holds two ends.
  • extent_test.go:TestNamespaceConnectorBehaviorReadsItsOwnUsage: Link exhibits a state whose entry reads c and pair — the behavior reaches the connectors being made (seen = 3 on each), not a cycle.
  • extent_test.go:TestNamespaceConnectorWhoseStartFailsLeavesNoTrace: the namespace twin of connector_test.go:testConnectorWhoseStartFailsLeavesNoTrace — a package-level connector (and a [2] one) whose behavior writes to an older object, sends it a signal and then fails leaves no write, message or object behind.
  • extent_test.go:TestNamespaceConnectorAnsweredByAFailingBehaviorIsKept: the namespace twin of testConnectorAnsweredByAFailingBehaviorIsKept — an older listener failing to answer a package-level connector's signal reports its own failure; the connector is kept, recorded, attached, not rerun.
  • extent_test.go:TestNamespaceSubsettedConnectorIsReadableByTheBehaviorsItWakes: a listener woken by the connector filling a subsetted links : Link[2] reads size(links) as 2, not a cycle.
  • extent_test.go:TestNamespaceConnectorUsageDenotesOneObject: all Link/Ifc/Aloc before any read, outer read twice is the same object, occurrences[outer] holds it once, both ends attached.
  • robustness_extent_namespace_connections_test.go:TestRuntimeRobustnessExtentNamespaceConnections: an end naming an unresolvable feature (on one connector, on a [2] collection, on a connector a bind joins to another), an extent reaching it, two bound connectors attached to different features, and a connector naming itself each return a typed errConnectorEnd/errBindingConflict/ErrCyclicFeatureValue, no Link left behind, no occurrence recorded.

Both conformance fixtures were red on develop before the fix (no value for feature outer; requirement satisfied = false, want true).

Baseline movement, accounted against develop

docs/project/library-and-prefix-sweep-outcomes.txt (re-recorded with -update-library-prefix-sweep), 3 rows changed, 61 added, 0 removed otherwise:

  • object Probe::cn1, object Probe::if1, object Probe::al1: undetermined <undetermined> → object:Probe::cn1 / if1 / al1 — the three package-level connector probes (connection cn1 : Cn1 connect p2 to p2;, interface if1 : If1 connect p1.pt to p1.pt;, allocation al1 : Al1 allocate p2 to p2;) now denote objects.
  • +61 read rows: the library and model features the sweep reads off every object it reaches, now read off those three — the same Items::Item/Parts::Part set the package-level Probe::p2 already shows (boundingShapes, isSolid constant true, self/start the object, done undetermined, the rest empty), plus the connector's own ends (cn1.e1/e2 → object:Probe::p2, if1.p1/p2 → Probe::Pt, al1.a1/a2 → Probe::p2) and the library connector ends source/target (BinaryConnection, BinaryInterface, Allocation) bound to them; if1.participant is undetermined as for nested interfaces.
  • object Probe::fl1 (package-level flow) stays undetermined — the out-of-scope kinds did not move.

No other fixture, golden or corpus expectation moved. origin/develop (25c9d42f7, #1556) is merged in; the one conflict was textual, in the multiplicity-default row of docs/project/spec-compliance.md (develop's solver sentence kept, this PR's representative-object row kept), and the sweep baseline did not move on the merged tree.

Gate (corpora fetched, OPENSYSML_REQUIRE_TRAINING_CORPUS/PILOT_CORPORA/SPEC_EXAMPLES/PILOT_LIBRARY_XMI/PSSM_SUITE=1): go build ./..., go vet ./..., gofmt -l . empty, go test ./... — every package passes; cmd/sysml and tests/codegen hit Go's 10 m default under the parallel run (goroutine dump, no test failure) and pass alone with -count=1 -timeout 40m (233 s and 268 s). make build-sysml rebuilt and ran the reporter's command above. make docs-counts and python3 scripts/changelog.py check clean.

Question for the maintainer

A package-level connection pair : Link[2] connect sys.x to sys.y; now denotes two connectors each attached to sys.x/sys.y (as action a[2] is two performances), while the same usage nested in a part is still refused by materializeConnectorFeatureValue ("a connector of more than one object has no set of ends to attach"), as on develop. The nested rule is out of this PR's scope; it could follow the same route (n connectors, each attached) in a follow-up if that reading is wanted.

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

…es in all T (#1543)

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@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 and others added 2 commits October 26, 2026 23:53
…ount them in all T (#1543)

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ots and representative objects (#1543)

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 27, 2026 00:35
…ce-connection-usages

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

# Conflicts:
#	docs/project/spec-compliance.md
devin-ai-integration[bot]

This comment was marked as resolved.

…h a binding class or a [n] collection (#1543)

materializeMembers, the helper every namespace path makes its objects through,
now materializes an owner-less connector usage through materializeConnectorAs, so
a connection a bind joins before it is read, and a [2] connection, hold their
ends as a bare read does; a member bound to a connector made for another usage
must attach as its own connect clause says, else the binding conflicts.

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

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits October 27, 2026 00:59
…r collection's members (#1543)

materializeMember is the one way a namespace path makes an object of a usage, under the
next identity or a reserved one: requiredMember, which makes the members of a collection
past eagerLowerBound as they are reached, now goes through it too, so connection pair :
Link[1001] holds its ends on every member as a [2] does.

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

This comment was marked as resolved.

…behaviors start (#1543)

materializeMember leaves the behaviors of the object it makes for the caller to start
once the usage denotes it, as every namespace path already does for a part, so a
behavior of the connector reading its own usage reaches the connector being made
instead of re-entering its materialization as a cycle.

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

This comment was marked as resolved.

…start fails (#1543)

Every namespace path that makes a connector usage's objects (occurrenceOf, occurrencesOf,
a binding class, a namespace subsetting) now opens one journal over attaching, recording
and starting them, so a connector whose own behavior wrote to an older object before
failing leaves no write, message or object behind, as a nested connector's start does.

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

This comment was marked as resolved.

…s start (#1543)

The journal a namespace path spans over a connector usage's attach, record and start
now comes with a run boundary, as a nested connector's does: the older behaviors the
connector woke answer only once it is committed, a failure of theirs is their own and
the connector is kept, rather than the rollback undoing their writes under a machine
that already moved.

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

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits October 27, 2026 01:54
…once its usage is recorded (#1543)

connectorJournal's commit only makes the connector durable; the drain of the older
behaviors it woke runs once the namespace path has recorded the usage and cleared its
own guards, so a behavior reading a subsetted or bound usage it was woken through finds
the recorded objects rather than a false cycle.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…fies them

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 47f96ce into develop Oct 27, 2026
30 checks passed
@HuiJun
HuiJun deleted the fix/extent-namespace-connection-usages branch October 27, 2026 03:05
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.

all T omits a package-level connection usage, though the usage reads as one value

1 participant