Repository navigation
fix(runtime): default an undeclared feature multiplicity to 0..* per KerML §7.4.12 - #1542
Merged
Merged
Conversation
…KerML §7.4.12 A feature that declares and inherits no multiplicity has the usual default 0..* (KerML 1.1 §7.4.12, §7.4.5.2; SysML v2 §7.6.3); only an eligible SysML usage owned by a type, and an end feature, has the implicit [1..1]. Model.EffectiveMultiplicityOf and GoverningMultiplicityOf fall back to the new Model.DefaultMultiplicityOf instead of AssumedRange, which is now only the implicit [1..1]. The runtime holds a feature the default governs to one representative value and judges no count against it (EffectiveFeature.MultiplicityDefaulted), so a two-valued KerML feature is held rather than refused; the implicit [1..1] is also inherited through redefinition and by a requirement subject. Consumers that refuse a feature holding several objects judge a stated multiplicity only. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Contributor
Author
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
…y field Co-Authored-By: jason.han <hanhuijun@gmail.com>
…oved Co-Authored-By: jason.han <hanhuijun@gmail.com>
…y association's [1..1] Co-Authored-By: jason.han <hanhuijun@gmail.com>
This was referenced Oct 26, 2026
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
A feature that declares no multiplicity and inherits none was held to an assumed
1..1everywhere, so a two-valued KerML feature was refused:sysml -validatewas clean, butsysml -e K::c.xfailed withmultiplicity violation: 2 value(s) bound to a feature with multiplicity upper bound 1. It now answers[1.0, 2.0].The default is fixed at its source and every consumer of
AssumedRange/EffectiveMultiplicityOfwas audited:semantics/multiplicity.go: newModel.DefaultMultiplicityOf—0..*, or the implicit[1..1]whereModel.ImplicitlyDeclaresOne(an eligible SysML usage perImplicitMultiplicityApplies, or an end feature: every SysML end, and a KerML end of a binary association or connector, whose ends redefineBinaryLink's[1..1]ends; a KerML end of an n-ary association keeps0..*).EffectiveMultiplicityOfandGoverningMultiplicityOffall back to it;AssumedRangeis now documented as the implicit[1..1]only, and the "exactly one result" uses in the runtime (undetermined.go,collections.go) spell that asCountRange(1).DeclaresEndFeaturemoves fromcheck/passesinto semantics.governingMultiplicitynow also inherits the implicit[1..1]through redefinition (attribute grossMass :>> masswheremassis a type-owned attribute), andStatedMultiplicityOf(declared, inherited, or implicit; not ok under the bare default) is the question for consumers that refuse a feature holding several objects: theassignment-chain-step-not-one-objectpass and the SMT translator's chain and holder checks (solve/reference.go,solve/nested.go) judge a stated multiplicity only, and a variation point stands for its one selection whatever it redefines.runtime/shape.go):featureRangereports whether the range is defaulted; a defaulted feature keepsrepresentativeRange()(one value) as the range a representative instance holds it to, andEffectiveFeature.MultiplicityDefaultedmakescheckAdmitsjudge no count against it — the rule the write path already had (writeTarget.countJudged, "never an unstated non-parameter range"), extended to default values. Inheritance ininheritedMultiplicitycounts a redefined feature's implicit[1..1]as stated and follows role redefinitions, so a requirementsubjectinheritsRequirementCheck::subj : Anything[1]rather than the old fallback.ir/queryplan): a query parameter stating no multiplicity still binds exactly one value —query defis this project's construct, not a clause's, and its documented contract (required parameters,[1..1]bindings) is unchanged.passes.checkValueCount) already judged declared/redefined bounds only; sizing (valuesGoverning→CollectionValues) keeps one value for a feature stating none, which is what the runtime's representative instance holds.Runtime policy, not a clause: the specification constrains the count (
0..*); it does not say how many objects a representative instance materializes. Taking0..*literally there would make every undeclared non-§7.6.3 usage hold nothing (feature c : C;andpart p : P;at package level would give[], a part def'sstate/actionusages would stop being single occurrences), so a feature the default governs still materializes one object where nothing values it, exactly as before — and a declared[0..*]still materializes none, as ondevelop.Behaviour before → after (
bin/sysml -e):class C { feature x : Real = (1.0, 2.0); } feature c : C;→K::c.x[1.0, 2.0]attribute top : Real = (1.0, 2.0)[1.0, 2.0][1.0, 2.0]attribute b : Real :> many = (1.0, 2.0)undermany : Real[0..*][1.0, 2.0][1.0, 2.0]part def P { attribute a : Real = (1.0, 2.0); } part p : P;→p.apart p0 : P[0..*];/part p2 : P[2];/part pr : P;→.k[]/[3.0, 3.0]/3.0Specification basis
1..1throughBinaryLink, n-ary ends must declare it); §8.4.4.12.1 (inherited constraints).[0..*]; implicit[1..1]for an attribute/item/part/port usage owned by a definition or usage with no owned subsetting or redefinition), §7.13.1 (end features default to1..1regardless).docs/project/spec-compliance.mddefault-multiplicity row:docs/project/pilot-execution-referee.mdw6d:held-undeclared-multinow attributes the divergence to §7.6.3 rather than to a KerML 1.0 assumption.How it was verified
Four layers:
internal/exec/runtime/testdata/conformance/multiplicity_default_kerml.{kerml,expected.json}(two values held, one held,()held as no value,Real[1]still refused);multiplicity_default_assumed(the §7.6.3 case) unchanged. The harness and its round-trip suites (fixtures.ModelPath) now accept a.kermlcase beside the.sysmlones.TestDefaultMultiplicityFollowsTheClause(KerML class/package features, binary vs ternary association ends, SysML attribute/part/action/connection/subsetting/package/end usages);TestMultiplicityOfANonUsagenow expects the unbounded range for a definition and a nil symbol (neither is a usage the implicit[1..1]is declared for).robustness_default_multiplicity_test.go(TestRuntimeRobustnessDefaultMultiplicity): KerML defaults of 0/1/2 values and an expression producing two are held, a declared[1]and the SysML implicit[1..1]still returnErrMultiplicityViolation.Oracles (both exit 0,
git statusshows no baseline moved):go run -C tools ./cmd/pilot-exec-diff— bucket counts unchanged (500 cases; disagree 37, differs-by-design 5, …). The referee corpus has no KerML case, and its undeclared-multiplicity rows (w6d:undeclared-multi,:undeclared-multi-typed,:held-undeclared-multi) are SysML: the package-level ones already answered both values, and the held one is a part-def-owned attribute under §7.6.3, which stays deliberately ours. So no row moved; agreement did not increase here because the gap never reached this corpus.go run -C tools ./cmd/pilot-diff—920 file(s), 521 fully agreeing; 1098 agreed diagnostic(s), 359 only ours, 2450 only the pilot's, the committed headline.docs/project/pilot-differential-baseline.jsonanddocs/project/api-json-differential-baseline.jsoneach move one line,provenance.inputs[examples].digest, becauseexamples/self-model/execution.sysmlnow counts the effective feature's eleventh field; no diagnostic row or count moved in either (-updateof both, diff is the digest line alone).Gates:
go build ./...,go vet ./...,gofmt -l .(empty),go test ./...with the training, pilot and spec-example corpora fetched,python3 scripts/changelog.py check,mkdocs build --strict— results below.Dry-merge against PR #1529 (
chore/pilot-pin-2026-09, head8a100b317) andorigin/develop(ab1a9cf9a) in detached worktrees: both merge clean; on each merged treego build ./..., the touched runtime and semantics tests,./internal/exec/solve,./tools/referee/exec/...and the changelog check pass (results below).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 changelogLink to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/12b6af85f9eb4ebdba2012b9c1dabbac
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/12b6af85f9eb4ebdba2012b9c1dabbac?variant=devin
Requested by: @HuiJun