Repository navigation
fix(runtime): count a nested requirement as a required constraint of its container - #1539
Merged
Merged
Conversation
…its container A composite requirement usage nested in a requirement definition or usage is a subrequirement (SysML v2 §7.21.2; Requirements.sysml subrequirements :> requirementChecks, constraints): the check on the container counts its conditions among the container's required constraints, bound to the container's subject when the usage declares no subject value of its own and keeping one it declares, through any depth of nesting. The semantics expose the derivation (Model.IsSubrequirement) the runtime reads, so a failing subrequirement fails its container on -requirement, result reads, gRPC VerifyRequirement and the MCP verify_requirement tool instead of leaving the container holding vacuously. 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)".
|
…ts own A subrequirement is one constraint of its container whose value is its own check, allTrue(assumptions()) implies allTrue(constraints()), so a false assumption inside it no longer makes the container's other constraints pass vacuously, and one stating no required condition leaves its container as vacuous as it is; the SMT translation reads the group the same way. Only a subrequirement owning no subject declaration inherits the container's subject: one declaring `subject s : Veh;` that nothing binds reports the unbound subject instead of judging the container's. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…vacuity A requirement assumed by reference brings its subrequirements along as assumptions, which require nothing of the referencing requirement: one stating no required condition of its own still holds vacuously without evaluating them. Code comments on the subrequirement paths condensed to two lines. Co-Authored-By: jason.han <hanhuijun@gmail.com>
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 composite requirement usage nested in a requirement definition or usage is a subrequirement: automatically one of the container's required constraints, and when it declares no subject of its own, bound to the container's subject. Before this change the runtime collected only
require/assumemembers of a requirement body, so a nestedrequirement light { require constraint { v.mass < 10.0 } }contributed nothing to its container:r.resultstayedtrue,sysml -requirement P::rreported the requirement as holding vacuously, and the gRPC/MCP verdicts said the same — a silent false pass for every grouped requirement.sysml -requirement P::r✓ Requirement P::r satisfied(vacuous)✗ Requirement P::r failed — Required condition evaluated to false: v.mass < 10.0, exit 1-e P::r.result(andbox.rr.resulton a part-held usage)truefalseVerifyRequirement/ MCPverify_requirementrequirement descent { requirement thrust : ThrustMargin; }checked directly)could not be evaluated: no value for feature engine(not covered)requirefails beside a subrequirement assumingfalseresult = trueresult = false— the assumption guards the subrequirement alonesubject s : Veh;that nothing bindsno value for feature sThe fix sits where membership and subject binding are derived, not in a special case at evaluation time:
internal/semantic/semantics/nested.go: the implicit-subsetting rule that already namesRequirements::RequirementCheck::subrequirementsfor a composite requirement usage in the requirement family is split intonestedSubsettingFQNand exposed asModel.IsSubrequirement, answered from the declarations alone (so it holds whether or not the library is loaded).internal/exec/runtime/condition.go: condition collection treats a subrequirement as one required constraint of its container (appendSubrequirementConditions): aConditiongroup markedSubrequirementwhose value is the subrequirement's own check over its whole chain —allTrue(assumptions()) implies allTrue(constraints())(subrequirementHoldsAt), so its assumptions guard it alone and never the container's other constraints, and one stating no required condition, or one brought in byassume r;, leaves the container as vacuous as it is (requiresAny) — with the same cycle guard and parameter masking a namedrequire/assumeconstraint gets;constraintScopebinds the subrequirement's inherited subject names to the container's subject value only when the usage owns no subject declaration (subrequirementSubject,SubjectsOf), leaving one it owns alone whether bound (subject e = v.eng;) or not (subject s : Veh;is reported unbound).conditionOwnerOflets a usage body supply its conditions through a subrequirement the way it does through a named constraint.internal/exec/solve/translate.go: the SMT translation reads a subrequirement group the same way, as the implication its own check is (assertedCondition), so the solver and the evaluator keep agreeing on the differential conformance corpus.Covered: a subrequirement without its own subject (bound to the container's), a typed one inheriting its definition's subject (the clause's
requirement driving : DrivingRequirement;form), one with its ownsubject(kept), nesting two levels deep, a subrequirement under a requirement usage,assume constraintvsrequire constraintinside the subrequirement, theresultread, and the CLI, gRPC and MCP verdict surfaces.Not changed here (stated as still open in the compliance row): a requirement usage is not materialized as an object of its own, so the reflective reads
r.subrequirements/r.constraintsstill answer[]andr.light.resultstays<undetermined>, although the container'sresultnow counts it.Specification basis
SysML v2.1 Beta 2 §7.21.2 Requirements ("A composite requirement usage nested in a requirement definition or usage is a subrequirement … Subrequirements are considered to automatically be required constraints of the containing requirement definition or usage … if a subject parameter is not explicitly declared for a subrequirement, it is assumed to have the same subject as its containing requirement definition or usage, with its subject bound to that of the container", with the
VehicleRequirementsGroupexample); §8.3.21.9checkRequirementUsageSubrequirementSpecialization;Requirements.sysmlabstract requirement subrequirements[0..*] :> requirementChecks, constraintsandRequirementCheck::result = allTrue(assumptions()) implies allTrue(constraints()).An
assume constraintinside a subrequirement belongs to the subrequirement's own check: its value within the container isallTrue(assumptions()) implies allTrue(constraints())over what it states, in the check verdict and theresultread alike — a false assumption makes the subrequirement hold vacuously and nothing else (subrequirement_assumption_scoped,subrequirement_assumption_result), a true one leaves its required conditions to decide (subrequirement_assume_require). The container's own assumptions keep the check path's existing trusted-precondition reading; only what the subrequirement states is scoped by this change.Moves⚠️ , "Open: … not yet materialized as a member of its container's
docs/project/spec-compliance.md: the nested-usage subject-binding row (subrequirementsandconstraints…") now states the fixed behaviour and the remaining reflective-read gap; the "Nested requirements" runtime row cites the subrequirement fixtures.How it was verified
Four layers:
tests/parser/testdata/parse/subrequirement_container_subject.{sysml,golden}subrequirement_container_subject,subrequirement_typed_inherits_subject,subrequirement_explicit_subject,subrequirement_two_levels,subrequirement_under_usage,subrequirement_assume_require,subrequirement_assumption_scoped,subrequirement_assumption_result,subrequirement_assumed_reference,subrequirement_owned_subject,subrequirement_result_read(no trace: no ordering is involved)internal/exec/runtime/robustness_subrequirement_test.go:container_subject_unbound(typed unbound-subject error, no vacuous pass),naming_its_container(require r;inside r's subrequirement isErrNoConditions, returns, no hang) andowned_subject_unbound(an ownedsubject s : Veh;nothing binds isErrNoValuenamings, not judged on the container's subject)cmd/sysml/subrequirement_test.go(-requirementexit 1 namingv.mass < 10.0, exit 0 when every subrequirement holds),internal/frontend/grpc/subrequirement_test.go(VerifyRequirementby two subjects),runtimetools_test.go(verify_requirementonDemo::massGroup)Models whose verdicts could move:
examples/holds no requirement usage nested in a requirement (scanned every.sysml).docs/manual/examples/:cookbook.sysmlCookbook::pointingRequirementandrequirements.sysmlRequirements::spec::r2nest doc-only subrequirements, so-requirementon them is byte-identical to develop's (they still hold vacuously, the vacuity note included);trace-2-hierarchy.sysmldescent/touchdownare satisfied through their subrequirements (satisfy specification.descent.thrust by engine…) —-requirement=LanderHierarchy::specification::mass, every-satisfyverdict and the renderedHierarchyReportare byte-identical to develop's, while checkingdescentortouchdownitself directly now reportsno value for feature engine(the group's own subject is unbound; its subrequirements read it) instead of a vacuous pass.Dry-merge of the head against
origin/develop(c1cad1303) and against PR #1529's head (8a100b317), each in a detached worktree: both merge clean, the only file either side and this branch both touch isdocs/project/spec-compliance.md(different rows, auto-merged); on each merged treeTestExecutionConformance,TestRuntimeRobustnessSubrequirement,TestReturnParameter*,internal/exec/solve,cmd/sysml -run TestRequirementand the training-corpus gate (TestTrainingExamples*,TestRequirementDefinitionsFile) all pass.Gates, run serially with the training/pilot/spec-example/library-XMI/PSSM corpora required (
OPENSYSML_REQUIRE_*=1):go build ./...✅,go vet ./...✅,gofmt -l .empty ✅,go test -count=1 -timeout 30m ./...✅ on the final tree, every packageok(an earlier run had caughtinternal/exec/solveTestDifferentialConformanceCorpusdisagreeing with the evaluator onsubrequirement_assumption_result, which is what thetranslate.gochange fixes),python3 scripts/changelog.py check✅,mkdocs build --strict✅,make lint✅.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/ff3b5848a88b4fdf8f4331d3dbeae409
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/ff3b5848a88b4fdf8f4331d3dbeae409?variant=devin
Requested by: @HuiJun