Skip to content

docs(compliance): state the representative-object policy for a feature with no stated multiplicity - #1550

Merged
HuiJun merged 2 commits into
developfrom
docs/representative-object-policy
Oct 26, 2026
Merged

HuiJun merged 2 commits into
developfrom
docs/representative-object-policy

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

What and why

Documentation only. The KerML default multiplicity is 0..* for counting, conformance, SMT bounds and query plans, but the runtime keeps a policy the compliance map did not yet state: a feature that states and inherits no multiplicity and holds no value materializes one representative object, a declared [0..*] materializes none, and no count is judged against the defaulted feature (EffectiveFeature.MultiplicityDefaulted in internal/exec/runtime/shape.go). The maintainer accepted that policy; this PR records it as a row of docs/project/spec-compliance.md.

  • New row under Instantiation and Feature Values, right after the default-multiplicity row: what the clauses constrain (the number of values a feature may hold) and do not (how many objects a representative instance holds for a valueless feature), what OpenSysML does, and why reading 0..* literally for the representative instance is not an option. Status ⚠️ Approximate, marked tool decision, citing runtime/shape.go featureRange/representativeRange/EffectiveFeature.MultiplicityDefaulted, runtime/instance.go checkAdmits, runtime/write_conformance.go writeTargetIn, and the tests that pin it: conformance multiplicity_default_kerml, robustness_default_multiplicity_test.go:TestRuntimeRobustnessDefaultMultiplicity (all four subtests), instance_test.go:TestInstantiate_UnboundedDefaultIsNotAScalar, conformance multiplicity_unbounded_single_bound.
  • The default-multiplicity row's closing sentence now points at the new row.
  • The model-level undetermined row cited "§7.3.4.1: a feature with no multiplicity holds exactly one value" for the [1] it carries for a bare attribute u;; that reading contradicts the default-multiplicity row, so it now says what the [1] is — the representative one value of the new row — with no clause claimed for it. The behaviour it pins (undetermined_test.go, [1] for u) is unchanged.
  • Changelog fragment changes/unreleased/representative-object-policy.changed.md.

Second commit — the remaining claims on the page that described an unstated multiplicity as the specification's assumed 1..1, each verified against write_conformance.go writeTargetIn, checkTargetHeld, checkWrite and shape.go featureRange/statedMultiplicity on develop 0286c9845 with a throwaway write probe (an action-def-owned attribute written by its own body through executeActionSource, and a part-def-owned attribute written by its nested action through the execution-conformance harness; both probes removed before committing):

  • Instantiation map, redefinition-and-subsetting row: "keeps the assumed one value the run time materializes" → the one representative object, citing the new row.
  • Instantiation map, nested-performance row: "the runtime's assumed one value for a valueless concrete feature … the runtime's materialization contract" → the one representative object of the new row, a tool decision.
  • Write-conformance row: "a non-parameter feature with none stated has assumed 1..1 (KerML 1.1 §7.3.4.2) unless …" → judged against what it states or inherits and the implicit [1..1] where §7.6.3 declares it; one stating and inheriting none has the 0..* default. The probe shows the write side honours that on one path and not the other, and the row now says so instead of claiming the fix(runtime): default an undeclared feature multiplicity to 0..* per KerML §7.4.12 #1542 semantics throughout: attribute x : Real :> many; on a part def written (3.0, 4.0) by its nested action holds both values (Instance.SetFeatureValue → checkAdmits, which judges no count where MultiplicityDefaulted); the same declaration on an action def written by the action's own body is refused with multiplicity upper bound 1 (ActionExecutor.setFeature → mirrorOccurrence → Instance.SetFeatureValue → Context.checkWrite, which passes countJudged = true unconditionally, so the representative 1..1 is judged; writeTargetIn itself reports countJudged = false for it). attribute z : Real; on a part def (implicit [1..1]) still refuses two, as it should. The row's status stays ⚠️ Approximate and its parenthetical names the split. This is a code finding, not fixed here (docs-only change): Context.checkWrite should consult the target's countJudged/the feature's MultiplicityDefaulted as checkAdmits does.
  • No other "assumed 1..1" / "exactly one value" claim about an unstated multiplicity remains on the page, and the page no longer names AssumedRange at all; the remaining "exactly one" rows are the implicit [1..1], operands declared [1] and the one result of an invocation (KerML §7.4.9), which are clauses, not the default.

No code, test, fixture or baseline changes. ErrNoValue wording from the merged unbound-result change is untouched and consistent (an instantiated object holding no required value is still <unset> / ErrNoValue when used; the new row is about the count of objects materialized, not the value a data-typed feature holds).

Specification basis

  • KerML 1.1 Beta 2 §7.4.12 Multiplicities: a multiplicity specifies the "cardinalities (number of instances) of a type"; §7.4.5.2: 0..* is "the usual default" (binary association ends excepted).
  • SysML v2.1 Beta 2 §7.6.3: "If no tighter constraint is inherited, the effective default is the most general multiplicity [0..*]", with the implicit [1..1] under its three conditions.
  • None of these says what an instantiation creates for a valueless feature under the default; the row records the one object as the tool's decision, so the status is ⚠️ Approximate rather than ✅.

Behaviour the row states, checked by hand on this build (bin/sysml -e):

Model Result
KerML class C { feature x : Real = (1.0, 2.0); } feature c : C; → K::c Instance(ID: 1) (one C)
→ K::c.x [1.0, 2.0]
feature cs : C[0..*]; → K::cs []
SysML part def Q { attribute k : Real = 3.0; } part pr : Q; → P::pr / P::pr.k Instance(ID: 1) / 3.0
part p0 : Q[0..*]; → P::p0 / P::p0.k [] / []
part p2 : Q[2]; → P::p2.k [3.0, 3.0]
package-level attribute u : Real; → P::u <undetermined>

How it was verified

Doc gates on this branch:

go run -C tools ./cmd/doc-counts -check          doc-counts: already current
go run -C tools ./cmd/validation-census -check   217 constraints, census document, compliance map and manifest agree
python3 scripts/check-doc-links.py               0 broken link(s)
python3 scripts/check-doc-ids.py                 No internal work-item labels in reader-facing documentation
python3 scripts/check-doc-figures.py             every quoted oracle figure names the round it measured
python3 scripts/changelog.py check               ok
mkdocs build --strict                            ok (22 s)
go build ./...                                   ok
go vet ./...                                     ok
gofmt -l .                                       (empty)
go test ./...                                    ok — 120 packages pass with the training, pilot and spec-example
                                                 corpora fetched; cmd/sysml and tests/codegen hit Go's 10 m default
                                                 timeout under full-suite load (wall-clock tests, no failing assertion)
                                                 and pass alone: cmd/sysml 456 s, tests/codegen 491 s (-timeout 40m)

Dry-merges in detached worktrees (changelog.py check, doc-counts -check, check-doc-links.py on each merged tree):

Checklist

  • make test and make lint pass locally (docs-only; go build, go vet, gofmt clean, go test ./... as above)
  • Tests added or updated for the change (none needed: the row cites the tests that already pin the behaviour)
  • 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

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/7c671f5178ec48acad2a65f195a7d82c
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/7c671f5178ec48acad2a65f195a7d82c?variant=devin
Requested by: @HuiJun

…e with no stated multiplicity

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
devin-ai-integration Bot marked this pull request as ready for review October 26, 2026 21:21

@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: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

…ill read an assumed 1..1 into an unstated multiplicity

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