From e91b7508093c8a4ad978105cb484885dde4a77e7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:15:55 +0000 Subject: [PATCH 1/2] feat(check): add the NPR7123 pass, its codes and the npr7123-demo model Co-Authored-By: jason.han --- docs/reference/diagnostics.md | 45 ++ examples/npr7123-demo/npr7123-demo.sysml | 443 ++++++++++++++++++++ internal/check/codes/table_lint.go | 36 ++ internal/check/passes/analyze.go | 1 + internal/check/passes/npr7123.go | 475 ++++++++++++++++++++++ internal/check/passes/npr7123_test.go | 134 ++++++ internal/doc/queryexec/memberpath.go | 71 +++- internal/doc/queryexec/memberpath_test.go | 48 +++ 8 files changed, 1238 insertions(+), 15 deletions(-) create mode 100644 examples/npr7123-demo/npr7123-demo.sysml create mode 100644 internal/check/passes/npr7123.go create mode 100644 internal/check/passes/npr7123_test.go diff --git a/docs/reference/diagnostics.md b/docs/reference/diagnostics.md index 812c3b8448..f8a1ec95e0 100644 --- a/docs/reference/diagnostics.md +++ b/docs/reference/diagnostics.md @@ -2050,6 +2050,11 @@ A transition's `accept when ` or `when ` trigger names something no | [`mosa-interface-no-standard`](#mosa-interface-no-standard) | warning | a modular system interface conforming to no standard | | [`mosa-interface-not-traced`](#mosa-interface-not-traced) | warning | a modular system interface satisfying no requirement | | [`mosa-proprietary-no-rationale`](#mosa-proprietary-no-rationale) | warning | an element marked `@Proprietary` with no rationale | +| [`npr7123-compliance-matrix-missing-requirement`](#npr7123-compliance-matrix-missing-requirement) | warning | a compliance matrix whose document has no compliance entry for an active SE requirement | +| [`npr7123-compliance-missing-justification`](#npr7123-compliance-missing-justification) | warning | a compliance entry marked tailored or not applicable with no justification | +| [`npr7123-review-missing-entrance-or-success-criteria`](#npr7123-review-missing-entrance-or-success-criteria) | warning | a life-cycle review stating no entrance criteria or no success criteria | +| [`npr7123-review-missing-minimum-product`](#npr7123-review-missing-minimum-product) | warning | a section 5.2.2.2 minimum product absent from its review or below the required maturity | +| [`npr7123-semp-missing-process`](#npr7123-semp-missing-process) | warning | a SEMP not addressing all 17 common technical processes | | [`oosem-logical-component-not-allocated`](#oosem-logical-component-not-allocated) | warning | a logical component allocated to nothing | | [`oosem-requirement-not-derived`](#oosem-requirement-not-derived) | warning | a requirement derived from no higher-level requirement | | [`oosem-requirement-not-satisfied`](#oosem-requirement-not-satisfied) | warning | a requirement nothing satisfies | @@ -2104,6 +2109,46 @@ MOSA expects the justification for each proprietary element to be recorded with Specification: MOSA extension library +#### `npr7123-compliance-matrix-missing-requirement` + +**warning** · Method rules · a compliance matrix whose document has no compliance entry for an active SE requirement + +Table H-1 of NPR 7123.1D covers every active `[SE-nn]` requirement, so a `ComplianceMatrix` usage expects its document to hold a `@Compliance`-annotated usage of each definition in `NPR7123::SERequirements`. Add the missing entries, marking each FC, T or NA. + +Specification: NPR7123 extension library (NASA NPR 7123.1D) + +#### `npr7123-compliance-missing-justification` + +**warning** · Method rules · a compliance entry marked tailored or not applicable with no justification + +Table H-1 of NPR 7123.1D asks for a justification wherever a requirement is tailored (`T`) or not applicable (`NA`). Give the `@Compliance` annotation a `justification`. + +Specification: NPR7123 extension library (NASA NPR 7123.1D) + +#### `npr7123-review-missing-entrance-or-success-criteria` + +**warning** · Method rules · a life-cycle review stating no entrance criteria or no success criteria + +NPR 7123.1D section 5.2.2.1 (SE-34) has the technical team define entrance and success criteria for each life-cycle review, so a `LifeCycleReview` usage is expected to nest `EntranceCriterion` and `SuccessCriterion` requirement usages. Subset the transcribed criteria of `NPR7123::ReviewCriteria` into the review's `entranceCriteria` and `successCriteria`, tailoring them as section G.1.2 expects. + +Specification: NPR7123 extension library (NASA NPR 7123.1D) + +#### `npr7123-review-missing-minimum-product` + +**warning** · Method rules · a section 5.2.2.2 minimum product absent from its review or below the required maturity + +NPR 7123.1D section 5.2.2.2 (SE-35 to SE-56, SE-66 to SE-69) names the minimum products the technical team provides at each life-cycle review and their maturity, recorded on the library's work product definitions as `@MaturityExpectation { required = true }`. Nest an item of that product subsetting `reviewedProducts` under the review, with a `maturity` at or above the one asked for, or document the tailoring in the SEMP. + +Specification: NPR7123 extension library (NASA NPR 7123.1D) + +#### `npr7123-semp-missing-process` + +**warning** · Method rules · a SEMP not addressing all 17 common technical processes + +NPR 7123.1D section 6.2.3 (SE-58) has the SEMP define how each of the 17 common technical processes, as tailored, is applied, so a `SEMP` usage expects one nested usage per process definition of the library. Nest the missing processes (`#technicalPlanning action plan;`) with their `tailoring`. + +Specification: NPR7123 extension library (NASA NPR 7123.1D) + #### `oosem-logical-component-not-allocated` **warning** · Method rules · a logical component allocated to nothing diff --git a/examples/npr7123-demo/npr7123-demo.sysml b/examples/npr7123-demo/npr7123-demo.sysml new file mode 100644 index 0000000000..abd01bd81b --- /dev/null +++ b/examples/npr7123-demo/npr7123-demo.sysml @@ -0,0 +1,443 @@ +/* + * Dust Lidar Pathfinder: a small fictional science project modelled with the NPR7123 library. + * Its SEMP tailors the 17 common technical processes, its SRR and PDR state tailored Appendix G + * criteria and the section 5.2.2.2 minimum products at their maturity, a technology carries a TRL, + * and a compliance matrix generates Table H-1 from the model. The gaps the NPR7123 checks report + * are left open on purpose and marked with a doc comment. + */ +package 'Dust Lidar Pathfinder' { + private import NPR7123::*; + private import NPR7123::SERequirements::*; + private import ScalarValues::*; + private import DocumentQueries::*; + + package 'Technical Planning' { + doc /* The SEMP states how each of the 17 common technical processes is applied, as tailored + * (SE-58), and the measures the technical team tracks (SE-60 to SE-63). */ + + #semp part pathfinderSEMP : SEMP { + :>> title = "Dust Lidar Pathfinder Systems Engineering Management Plan"; + :>> maturity = WorkProductMaturity::baseline; + + #stakeholderExpectationsDefinition action defineStakeholderExpectations { + :>> tailoring = "Customized: expectations are elicited in two science-team workshops and recorded in the model."; + } + #technicalRequirementsDefinition action defineRequirements { + :>> tailoring = "Applied as written; requirements are held in the model and baselined at SRR."; + } + #logicalDecomposition action decomposeLogically { + :>> tailoring = "Applied as written at the instrument and subsystem layers."; + } + #designSolutionDefinition action defineDesignSolution { + :>> tailoring = "Applied as written; one design iteration between PDR and CDR."; + } + #productImplementation action implementProducts { + :>> tailoring = "Customized: the lidar transmitter is bought, the receiver and software are made in house."; + } + #productIntegration action integrateProducts { + :>> tailoring = "Applied as written at the subsystem and instrument layers."; + } + #productVerification action verifyProducts { + :>> tailoring = "Applied as written; verification by test at the instrument layer, by analysis below it."; + } + #productValidation action validateProducts { + :>> tailoring = "Applied as written; validation against the science team's expectations."; + } + #productTransition action transitionProducts { + :>> tailoring = "Applied as written for delivery to the host spacecraft."; + } + #technicalPlanning action planTechnicalEffort { + :>> tailoring = "Applied as written; this SEMP is the plan."; + } + #requirementsManagement action manageRequirements { + :>> tailoring = "Customized: the model is the requirements database."; + } + #interfaceManagement action manageInterfaces { + :>> tailoring = "Applied as written for the spacecraft interfaces."; + } + #technicalRiskManagement action manageRisks { + :>> tailoring = "Applied as written with a monthly risk board."; + } + #configurationManagement action manageConfiguration { + :>> tailoring = "Applied as written; the model repository is the configuration baseline."; + } + #technicalDataManagement action manageTechnicalData { + :>> tailoring = "Applied as written."; + } + #technicalAssessment action assessProgress { + :>> tailoring = "Applied as written through the TPMs below and the life-cycle reviews."; + } + #decisionAnalysis action analyseDecisions { + :>> tailoring = "Customized: trade studies are recorded as analysis cases in the model."; + } + } + + #semp part sempOutline : SEMP { + doc /* Deliberate gap: the first outline of the SEMP addresses only the system design processes, + * not all 17 (SE-58). */ + :>> maturity = WorkProductMaturity::preliminary; + #stakeholderExpectationsDefinition action defineStakeholderExpectations; + #technicalRequirementsDefinition action defineRequirements; + #logicalDecomposition action decomposeLogically; + #designSolutionDefinition action defineDesignSolution; + } + + #mop attribute massMargin : Real { + doc /* Mass margin, the leading indicator SE-62 asks for. */ + } + #mop attribute powerMargin : Real { + doc /* Power margin, the leading indicator SE-63 asks for. */ + } + #moe attribute dustDetectionRate : Real { + doc /* Fraction of dust events above threshold the instrument detects. */ + } + } + + package 'Work Products' { + doc /* The section 5.2.2.2 minimum products the project has produced so far, at their maturity. */ + + #stakeholderIdentificationAndExpectationsDefinition item expectations { + :>> maturity = WorkProductMaturity::baseline; + } + #conceptDefinition item concept { + :>> maturity = WorkProductMaturity::baseline; + } + #measureOfEffectivenessDefinition item moeDefinition { + :>> maturity = WorkProductMaturity::approve; + } + #requirementSet item requirements { + :>> maturity = WorkProductMaturity::baseline; + } + #humanSystemsIntegrationApproach item hsiApproach { + :>> maturity = WorkProductMaturity::baseline; + } + #designSolution item preliminaryDesign { + :>> maturity = WorkProductMaturity::preliminary; + } + #integrationPlan item integrationPlanDraft { + :>> maturity = WorkProductMaturity::baseline; + } + #verificationAndValidationPlan item vvPlan { + doc /* Still preliminary at PDR, below the baseline section 5.2.2.2.d(3) asks for. */ + :>> maturity = WorkProductMaturity::preliminary; + } + } + + package 'Life-Cycle Reviews' { + private import 'Technical Planning'::pathfinderSEMP; + private import 'Work Products'::*; + private import NPR7123::ReviewCriteria::*; + + #lifeCycleReview action srr { + doc /* System Requirements Review, Phase A. */ + :>> kind = ReviewKind::SRR; + :>> phase = LifeCyclePhase::phaseA; + :>> status = StatusKind::done; + + requirement entrance1 : SRR::Entrance1 :> entranceCriteria; + requirement entrance2 : SRR::Entrance2 :> entranceCriteria { + doc /* Tailored: the review board is the project's standing engineering review board. */ + } + requirement success1 : SRR::Success1 :> successCriteria; + requirement success2 : SRR::Success2 :> successCriteria; + requirement success3 : SRR::Success3 :> successCriteria; + + #semp part sempAtSRR :> reviewedProducts { + :>> maturity = WorkProductMaturity::baseline; + } + #requirementSet item requirementsAtSRR :> reviewedProducts { + :>> maturity = WorkProductMaturity::baseline; + } + #humanSystemsIntegrationApproach item hsiAtSRR :> reviewedProducts { + :>> maturity = WorkProductMaturity::baseline; + } + + #reviewItem item rfa1 { + :>> kind = ReviewItemKind::requestForAction; + :>> status = StatusKind::closed; + } + } + + #lifeCycleReview action pdr { + doc /* Preliminary Design Review, Phase B. */ + :>> kind = ReviewKind::PDR; + :>> phase = LifeCyclePhase::phaseB; + :>> status = StatusKind::open; + + requirement entrance1 : PDR::Entrance1 :> entranceCriteria; + requirement entrance3 : PDR::Entrance3 :> entranceCriteria; + requirement success1 : PDR::Success1 :> successCriteria; + requirement success2 : PDR::Success2 :> successCriteria { + doc /* Tailored: cost and schedule credibility is assessed by the host mission's PDR board. */ + } + + #designSolution item designAtPDR :> reviewedProducts { + :>> maturity = WorkProductMaturity::preliminary; + } + #integrationPlan item integrationPlanAtPDR :> reviewedProducts { + :>> maturity = WorkProductMaturity::baseline; + } + #verificationAndValidationPlan item vvPlanAtPDR :> reviewedProducts { + doc /* Deliberate gap: section 5.2.2.2.d(3) asks for the V&V Plan baselined at PDR. */ + :>> maturity = WorkProductMaturity::preliminary; + } + } + + #lifeCycleReview action cdr { + doc /* Deliberate gap: the CDR is planned but states no entrance or success criteria yet, and no + * product, so the baseline detailed design section 5.2.2.2.e asks for is not stated. */ + :>> kind = ReviewKind::CDR; + :>> phase = LifeCyclePhase::phaseC; + } + + #peerReview action lidarSoftwarePeerReview { + doc /* Peer review of the receiver software design (Table G-19). */ + requirement entrance1 : PeerReview::Entrance1 :> entranceCriteria; + requirement success1 : PeerReview::Success1 :> successCriteria; + } + + #keyDecisionPoint occurrence kdpC { + :>> designation = "KDP C"; + :>> phase = LifeCyclePhase::phaseB; + } + } + + package Instrument { + part def DustLidar { + part transmitter { + @TechnologyReadiness { + level = TechnologyReadinessLevel::trl6; + assessedAt = ReviewKind::PDR; + basis = "Prototype transmitter demonstrated in thermal vacuum."; + } + } + part receiver; + } + part lidar : DustLidar; + } + + package 'Compliance Entries' { + doc /* The project's compliance with each active SE requirement; the matrix below generates + * Table H-1 from these entries. SE-31 is left out on purpose. */ + + part complianceEntries { + + requirement se06 : SE06 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se07 : SE07 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se08 : SE08 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se09 : SE09 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se10 : SE10 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se11 : SE11 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se12 : SE12 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se13 : SE13 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se14 : SE14 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se15 : SE15 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se16 : SE16 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se17 : SE17 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se18 : SE18 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se19 : SE19 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se20 : SE20 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se21 : SE21 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se22 : SE22 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se23 : SE23 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se24 : SE24 { + @Compliance { comply = ComplyKind::notApplicable; justification = "The instrument is built in house; no contract is let."; } + } + requirement se25 : SE25 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se26 : SE26 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se27 : SE27 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se28 : SE28 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se29 : SE29 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se30 : SE30 { + @Compliance { comply = ComplyKind::notApplicable; justification = "No contract is let."; } + } + requirement se32 : SE32 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se33 : SE33 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se34 : SE34 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se35 : SE35 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se36 : SE36 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se37 : SE37 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se38 : SE38 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se39 : SE39 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se40 : SE40 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se41 : SE41 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se42 : SE42 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se43 : SE43 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se44 : SE44 { + @Compliance { comply = ComplyKind::notApplicable; justification = "The project is a single project, not a program."; } + } + requirement se45 : SE45 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se46 : SE46 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se47 : SE47 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se48 : SE48 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se51 : SE51 { + @Deviation { + requirementId = "SE-51"; + rationale = "Decommissioning and disposal plans are the host spacecraft's."; + approvedBy = "Center Engineering Technical Authority"; + } + @Compliance { comply = ComplyKind::tailored; justification = "Decommissioning is the host spacecraft's; the project delivers inputs to its plan (deviation D-3)."; } + } + requirement se52 : SE52 { + @Compliance { comply = ComplyKind::tailored; justification = "Disposal is the host spacecraft's; the project delivers inputs to its plan (deviation D-3)."; } + } + requirement se53 : SE53 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se54 : SE54 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se55 : SE55 { + @Compliance { comply = ComplyKind::tailored; justification = "Decommissioning is the host spacecraft's (deviation D-3)."; } + } + requirement se56 : SE56 { + @Compliance { comply = ComplyKind::tailored; justification = "Disposal is the host spacecraft's (deviation D-3)."; } + } + requirement se57 : SE57 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se58 : SE58 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se59 : SE59 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se60 : SE60 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se61 : SE61 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se62 : SE62 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se63 : SE63 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se64 : SE64 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se65 : SE65 { + @Compliance { comply = ComplyKind::tailored; } + doc /* Deliberate gap: tailored with no justification. */ + } + requirement se66 : SE66 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se67 : SE67 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se68 : SE68 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + requirement se69 : SE69 { + @Compliance { comply = ComplyKind::fullyCompliant; } + } + + #technicalAuthority part eta : TechnicalAuthority { + :>> kind = TechnicalAuthorityKind::engineering; + :>> holder = "Center Chief Engineer"; + } + } + } + + package 'Compliance Matrix' { + private import 'Compliance Entries'::complianceEntries; + + part def 'Pathfinder Compliance Matrix' :> ComplianceMatrix { + :>> title = "Dust Lidar Pathfinder NPR 7123.1 Compliance Matrix"; + + part scope : Paragraph { + :>> text = "Table H-1 of NPR 7123.1D for the Dust Lidar Pathfinder, generated from the compliance entries of the project model."; + } + + part entries : Section { + :>> title = "Compliance matrix"; + part matrix : Table { + :>> caption = "Compliance with the active SE requirements"; + calc rows : ComplianceRegister { in :>> root = complianceEntries; } + } + } + } + + part complianceMatrix : 'Pathfinder Compliance Matrix'; + } +} diff --git a/internal/check/codes/table_lint.go b/internal/check/codes/table_lint.go index b339382ec7..a01cb2b964 100644 --- a/internal/check/codes/table_lint.go +++ b/internal/check/codes/table_lint.go @@ -37,6 +37,7 @@ var lintCodes = []Entry{ const ( specMOSA = "MOSA extension library" specOOSEM = "OOSEM extension library" + specNPR = "NPR7123 extension library (NASA NPR 7123.1D)" ) // methodCodes are the codes of the method rules: findings of the MOSA and OOSEM @@ -84,6 +85,41 @@ var methodCodes = []Entry{ "Type the connection by a `#modularSystemInterface` definition or mark it so.", Spec: specMOSA, }, + { + Code: "npr7123-review-missing-entrance-or-success-criteria", Tier: TierMethod, Severity: "warning", + Summary: "a life-cycle review stating no entrance criteria or no success criteria", + Meaning: "NPR 7123.1D section 5.2.2.1 (SE-34) has the technical team define entrance and success criteria for each life-cycle review, so a `LifeCycleReview` usage is expected to nest `EntranceCriterion` and `SuccessCriterion` requirement usages. " + + "Subset the transcribed criteria of `NPR7123::ReviewCriteria` into the review's `entranceCriteria` and `successCriteria`, tailoring them as section G.1.2 expects.", + Spec: specNPR, + }, + { + Code: "npr7123-review-missing-minimum-product", Tier: TierMethod, Severity: "warning", + Summary: "a section 5.2.2.2 minimum product absent from its review or below the required maturity", + Meaning: "NPR 7123.1D section 5.2.2.2 (SE-35 to SE-56, SE-66 to SE-69) names the minimum products the technical team provides at each life-cycle review and their maturity, recorded on the library's work product definitions as `@MaturityExpectation { required = true }`. " + + "Nest an item of that product subsetting `reviewedProducts` under the review, with a `maturity` at or above the one asked for, or document the tailoring in the SEMP.", + Spec: specNPR, + }, + { + Code: "npr7123-compliance-missing-justification", Tier: TierMethod, Severity: "warning", + Summary: "a compliance entry marked tailored or not applicable with no justification", + Meaning: "Table H-1 of NPR 7123.1D asks for a justification wherever a requirement is tailored (`T`) or not applicable (`NA`). " + + "Give the `@Compliance` annotation a `justification`.", + Spec: specNPR, + }, + { + Code: "npr7123-compliance-matrix-missing-requirement", Tier: TierMethod, Severity: "warning", + Summary: "a compliance matrix whose document has no compliance entry for an active SE requirement", + Meaning: "Table H-1 of NPR 7123.1D covers every active `[SE-nn]` requirement, so a `ComplianceMatrix` usage expects its document to hold a `@Compliance`-annotated usage of each definition in `NPR7123::SERequirements`. " + + "Add the missing entries, marking each FC, T or NA.", + Spec: specNPR, + }, + { + Code: "npr7123-semp-missing-process", Tier: TierMethod, Severity: "warning", + Summary: "a SEMP not addressing all 17 common technical processes", + Meaning: "NPR 7123.1D section 6.2.3 (SE-58) has the SEMP define how each of the 17 common technical processes, as tailored, is applied, so a `SEMP` usage expects one nested usage per process definition of the library. " + + "Nest the missing processes (`#technicalPlanning action plan;`) with their `tailoring`.", + Spec: specNPR, + }, { Code: "oosem-requirement-not-derived", Tier: TierMethod, Severity: "warning", Summary: "a requirement derived from no higher-level requirement", diff --git a/internal/check/passes/analyze.go b/internal/check/passes/analyze.go index a1c0bc1848..2b9e2a8cb4 100644 --- a/internal/check/passes/analyze.go +++ b/internal/check/passes/analyze.go @@ -82,6 +82,7 @@ func DefaultRegistry() *Registry { reg.Register(BehaviorOrderPass{}) reg.Register(OOSEMMethodPass{}) reg.Register(MOSAPass{}) + reg.Register(NPR7123Pass{}) reg.Register(NestedRedefinitionPass{}) reg.Register(PortTypeMismatchPass{}) return reg diff --git a/internal/check/passes/npr7123.go b/internal/check/passes/npr7123.go new file mode 100644 index 0000000000..01880913b9 --- /dev/null +++ b/internal/check/passes/npr7123.go @@ -0,0 +1,475 @@ +package passes + +import ( + "fmt" + "sort" + "strings" + + "github.com/Open-MBEE/OpenSysML/internal/check/passes/kit" + "github.com/Open-MBEE/OpenSysML/internal/semantic/semantics" + "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" + "github.com/Open-MBEE/OpenSysML/internal/syntax/ast" + "github.com/Open-MBEE/OpenSysML/internal/syntax/diag" +) + +// NPR7123Pass audits the elements a model classifies with the NPR7123 library +// against what NPR 7123.1D states of them; each rule fires only on an element +// of the kind the NPR speaks about, and a model that never touches the library gets no finding. +type NPR7123Pass struct{} + +// Diagnostic codes of the NPR 7123.1D rules. +const ( + CodeNPR7123ReviewMissingCriteria = "npr7123-review-missing-entrance-or-success-criteria" + CodeNPR7123ReviewMissingProduct = "npr7123-review-missing-minimum-product" + CodeNPR7123ComplianceNoJustification = "npr7123-compliance-missing-justification" + CodeNPR7123MatrixMissingRequirement = "npr7123-compliance-matrix-missing-requirement" + CodeNPR7123SEMPMissingProcess = "npr7123-semp-missing-process" +) + +const npr7123Source = "npr7123" + +// Level reports the tier the pass runs at. +func (NPR7123Pass) Level() PassLevel { return LevelConstraint } + +// Run audits one document against the NPR7123 library it uses. +func (NPR7123Pass) Run(ctx *Context, name string, root *ast.RootNamespace) []diag.Diagnostic { + if ctx == nil || ctx.Index == nil || root == nil { + return nil + } + if ctx.Index.DocumentLibraryTier(name).Library() { + return nil + } + rootScope := ctx.Index.DocumentRoot(name) + if rootScope == nil { + return nil + } + a := newNPR7123Audit(ctx) + if a == nil { + return nil + } + a.check(rootScope) + return a.diags +} + +// Library qualified names the pass binds to (see engine-contract.json upstream). +const ( + npr7123LifeCycleReview = "NPR7123::LifeCycleReview" + npr7123EntranceCriterion = "NPR7123::EntranceCriterion" + npr7123SuccessCriterion = "NPR7123::SuccessCriterion" + npr7123WorkProduct = "NPR7123::WorkProduct" + npr7123MaturityExpectation = "NPR7123::MaturityExpectation" + npr7123ReviewKind = "NPR7123::ReviewKind" + npr7123WorkProductMaturity = "NPR7123::WorkProductMaturity" + npr7123Compliance = "NPR7123::Compliance" + npr7123ComplyKind = "NPR7123::ComplyKind" + npr7123ComplianceMatrix = "NPR7123::ComplianceMatrix" + npr7123SERequirement = "NPR7123::SERequirement" + npr7123SERequirements = "NPR7123::SERequirements" + npr7123SEMP = "NPR7123::SEMP" + npr7123TechnicalProcess = "NPR7123::TechnicalProcess" +) + +// npr7123MinimumProduct is one required cell of Table 5-1: the product a +// review kind must present and the maturity section 5.2.2.2 asks for. +type npr7123MinimumProduct struct { + product *symbols.Symbol + maturity string + requirementID string +} + +type npr7123Audit struct { + ctx *Context + model *semantics.Model + // defs holds the library definitions by qualified name. + defs map[string]*symbols.Symbol + // minimumProducts maps a review kind literal name to its required products. + minimumProducts map[string][]npr7123MinimumProduct + // requirements are the active SE requirement definitions, in library order. + requirements []*symbols.Symbol + // processes are the 17 common technical process definitions, in library order. + processes []*symbols.Symbol + // conforms memoizes conformance of a type to a library definition. + conforms map[[2]*symbols.Symbol]bool + diags []diag.Diagnostic +} + +// newNPR7123Audit returns nil when the bundled NPR7123 library is not loaded: +// a workspace package of the same name is not the NPR's vocabulary. +func newNPR7123Audit(ctx *Context) *npr7123Audit { + a := &npr7123Audit{ + ctx: ctx, + model: ctx.Model(), + defs: map[string]*symbols.Symbol{}, + minimumProducts: map[string][]npr7123MinimumProduct{}, + conforms: map[[2]*symbols.Symbol]bool{}, + } + for _, fqn := range []string{ + npr7123LifeCycleReview, npr7123EntranceCriterion, npr7123SuccessCriterion, npr7123WorkProduct, + npr7123MaturityExpectation, npr7123ReviewKind, npr7123WorkProductMaturity, npr7123Compliance, + npr7123ComplyKind, npr7123ComplianceMatrix, npr7123SERequirement, npr7123SERequirements, + npr7123SEMP, npr7123TechnicalProcess, + } { + def := npr7123LibraryElement(ctx, fqn) + if def == nil { + return nil + } + a.defs[fqn] = def + } + library := a.defs[npr7123WorkProduct].OwnerScope + if library == nil { + return nil + } + seen := map[*symbols.Symbol]bool{} + library.ForEachMember(func(member *symbols.Symbol) bool { + if member.Kind == symbols.SymbolAlias || !member.DeclaresDefinition() || seen[member] { + return true + } + seen[member] = true + switch { + case member != a.defs[npr7123TechnicalProcess] && a.conformsTo(member, npr7123TechnicalProcess): + a.processes = append(a.processes, member) + case member != a.defs[npr7123WorkProduct] && a.conformsTo(member, npr7123WorkProduct): + a.noteMinimumProducts(member) + } + return true + }) + if scope := a.defs[npr7123SERequirements].Scope; scope != nil { + scope.ForEachMember(func(member *symbols.Symbol) bool { + if member.Kind != symbols.SymbolAlias && member.DeclaresDefinition() && !seen[member] && a.conformsTo(member, npr7123SERequirement) { + seen[member] = true + a.requirements = append(a.requirements, member) + } + return true + }) + } + return a +} + +// npr7123LibraryElement returns the bundled library element registered under fqn, or nil. +func npr7123LibraryElement(ctx *Context, fqn string) *symbols.Symbol { + for _, def := range ctx.Index.LookupQualified(fqn) { + if def != nil && ctx.Index.Library(def) { + return def + } + } + return nil +} + +// noteMinimumProducts records the required cells of Table 5-1 a work product +// definition carries as @MaturityExpectation annotations. +func (a *npr7123Audit) noteMinimumProducts(product *symbols.Symbol) { + for _, facts := range a.model.AnnotationFactsOf(product) { + if !a.annotationIs(facts, npr7123MaturityExpectation) { + continue + } + required := false + var review, maturity, requirementID string + for _, v := range facts.Values { + switch v.Feature { + case "required": + required = v.Value.Kind == symbols.FilterValueBool && v.Value.Bool + case "review": + review = npr7123Literal(v.Value, npr7123ReviewKind) + case "maturity": + maturity = npr7123Literal(v.Value, npr7123WorkProductMaturity) + case "requirementId": + if v.Value.Kind == symbols.FilterValueString { + requirementID = v.Value.Str + } + } + } + if required && review != "" && maturity != "" { + a.minimumProducts[review] = append(a.minimumProducts[review], + npr7123MinimumProduct{product: product, maturity: maturity, requirementID: requirementID}) + } + } +} + +// npr7123Literal returns the literal name of an enumeration value of enumFQN, or "". +func npr7123Literal(v symbols.FilterValue, enumFQN string) string { + if v.Kind != symbols.FilterValueRef || !strings.HasPrefix(v.RefFQN, enumFQN+"::") { + return "" + } + return strings.TrimPrefix(v.RefFQN, enumFQN+"::") +} + +// annotationIs reports whether an annotation's type is, or conforms to, the library metadata definition. +func (a *npr7123Audit) annotationIs(facts symbols.AnnotationFacts, fqn string) bool { + def := a.defs[fqn] + for _, t := range a.ctx.Index.LookupQualified(facts.TypeFQN) { + if t == def || a.model.Conforms(t, def) { + return true + } + } + return false +} + +// conformsTo reports whether a type is, or specializes, the library definition fqn. +func (a *npr7123Audit) conformsTo(t *symbols.Symbol, fqn string) bool { + def := a.defs[fqn] + key := [2]*symbols.Symbol{t, def} + if ok, seen := a.conforms[key]; seen { + return ok + } + ok := t == def || a.model.Conforms(t, def) + a.conforms[key] = ok + return ok +} + +// isA reports whether a feature is typed, directly or through a keyword, by a +// type conforming to the library definition fqn. +func (a *npr7123Audit) isA(sym *symbols.Symbol, fqn string) bool { + if sym == nil || !sym.IsFeature() { + return false + } + for _, t := range a.model.FeatureTypeSet(sym) { + if a.conformsTo(t, fqn) { + return true + } + } + return false +} + +// nestedFeatures returns the features declared in sym's own body. +func (a *npr7123Audit) nestedFeatures(sym *symbols.Symbol) []*symbols.Symbol { + if sym.Scope == nil { + return nil + } + var out []*symbols.Symbol + seen := map[*symbols.Symbol]bool{} + sym.Scope.ForEachMember(func(member *symbols.Symbol) bool { + if member.Kind != symbols.SymbolAlias && member.IsFeature() && member.OwnerScope == sym.Scope && !seen[member] { + seen[member] = true + out = append(out, member) + } + return true + }) + return out +} + +// literalOf reads an enumeration-valued feature of sym as its literal name, or "". +func (a *npr7123Audit) literalOf(sym *symbols.Symbol, feature, enumFQN string) string { + values, present, err := a.model.DeclaredFeatureValues(sym, feature) + if err != nil || !present { + return "" + } + for _, v := range values { + if lit := npr7123Literal(v, enumFQN); lit != "" { + return lit + } + } + return "" +} + +func (a *npr7123Audit) check(root *symbols.Scope) { + kit.WalkSymbols(a.ctx, root, func(sym *symbols.Symbol) { + a.checkCompliance(sym) + if _, ok := sym.Decl.(*ast.Usage); !ok { + return + } + switch { + case a.isA(sym, npr7123LifeCycleReview): + a.checkReview(sym) + case a.isA(sym, npr7123SEMP): + if !a.reviewedProduct(sym) { + a.checkSEMP(sym) + } + case a.isA(sym, npr7123ComplianceMatrix): + a.checkMatrix(sym, root) + } + }) +} + +// checkReview expects a life-cycle review to state entrance and success +// criteria (section 5.2.2.1, SE-34) and the minimum products of section +// 5.2.2.2 for its kind at the maturity the section asks for. +func (a *npr7123Audit) checkReview(review *symbols.Symbol) { + nested := a.nestedFeatures(review) + entrance, success := false, false + for _, f := range nested { + entrance = entrance || a.isA(f, npr7123EntranceCriterion) + success = success || a.isA(f, npr7123SuccessCriterion) + } + switch { + case !entrance && !success: + a.report(review, CodeNPR7123ReviewMissingCriteria, + "This life-cycle review states no entrance criteria and no success criteria: NPR 7123.1D section 5.2.2.1 (SE-34) has the technical team define both for each review, so nest `EntranceCriterion` and `SuccessCriterion` requirement usages, subsetting `entranceCriteria` and `successCriteria`, from `NPR7123::ReviewCriteria`.") + case !entrance: + a.report(review, CodeNPR7123ReviewMissingCriteria, + "This life-cycle review states no entrance criteria: NPR 7123.1D section 5.2.2.1 (SE-34) has the technical team define entrance criteria for each review, so nest `EntranceCriterion` requirement usages subsetting `entranceCriteria`.") + case !success: + a.report(review, CodeNPR7123ReviewMissingCriteria, + "This life-cycle review states no success criteria: NPR 7123.1D section 5.2.2.1 (SE-34) has the technical team define success criteria for each review, so nest `SuccessCriterion` requirement usages subsetting `successCriteria`.") + } + kind := a.literalOf(review, "kind", npr7123ReviewKind) + for _, minimum := range a.minimumProducts[kind] { + var stated *symbols.Symbol + for _, f := range nested { + if a.isA(f, npr7123WorkProduct) && a.isAProduct(f, minimum.product) { + stated = f + break + } + } + name := a.model.EffectiveNameOf(minimum.product) + if stated == nil { + a.report(review, CodeNPR7123ReviewMissingProduct, fmt.Sprintf( + "This %s states no %s: NPR 7123.1D section 5.2.2.2 (%s) has the technical team provide it at this review at maturity %s, so nest a `%s` item subsetting `reviewedProducts` with its `maturity`.", + kind, name, minimum.requirementID, minimum.maturity, name)) + continue + } + maturity := a.literalOf(stated, "maturity", npr7123WorkProductMaturity) + if maturity != "" && npr7123MaturityRank(maturity) < npr7123MaturityRank(minimum.maturity) { + a.report(stated, CodeNPR7123ReviewMissingProduct, fmt.Sprintf( + "This %s is stated at maturity %s, below the %s NPR 7123.1D section 5.2.2.2 (%s) asks for at %s; mature the product or document the tailoring in the SEMP.", + name, maturity, minimum.maturity, minimum.requirementID, kind)) + } + } +} + +// isAProduct reports whether a feature is typed by the given work product definition or a specialization of it. +func (a *npr7123Audit) isAProduct(f, product *symbols.Symbol) bool { + for _, t := range a.model.FeatureTypeSet(f) { + if t == product || a.model.Conforms(t, product) { + return true + } + } + return false +} + +// npr7123MaturityRank orders the Appendix F vocabulary: a preliminary or +// initial product precedes a baselined or approved one, which precedes its +// final form or an update of it; "approach" states no product yet. +func npr7123MaturityRank(maturity string) int { + switch maturity { + case "initial", "preliminary": + return 1 + case "approve", "baseline": + return 2 + case "final", "update": + return 3 + } + return 0 +} + +// checkCompliance expects a @Compliance marked tailored or notApplicable to +// state the justification Table H-1 asks for. +func (a *npr7123Audit) checkCompliance(sym *symbols.Symbol) { + for _, facts := range a.model.AnnotationFactsOf(sym) { + if !a.annotationIs(facts, npr7123Compliance) { + continue + } + comply, justified := "", false + for _, v := range facts.Values { + switch v.Feature { + case "comply": + comply = npr7123Literal(v.Value, npr7123ComplyKind) + case "justification": + justified = v.Value.Kind == symbols.FilterValueString && v.Value.Str != "" + } + } + if (comply == "tailored" || comply == "notApplicable") && !justified { + a.report(sym, CodeNPR7123ComplianceNoJustification, fmt.Sprintf( + "This compliance entry is marked %s with no justification: NPR 7123.1D Table H-1 asks for the justification of every tailored or not-applicable requirement, so give the `@Compliance` annotation a `justification`.", comply)) + } + } +} + +// checkMatrix expects the document holding a ComplianceMatrix to state a +// compliance entry for every active SE requirement. +func (a *npr7123Audit) checkMatrix(matrix *symbols.Symbol, root *symbols.Scope) { + covered := map[*symbols.Symbol]bool{} + kit.WalkSymbols(a.ctx, root, func(sym *symbols.Symbol) { + if !sym.IsFeature() || !a.hasCompliance(sym) { + return + } + for _, t := range a.model.FeatureTypeSet(sym) { + for _, req := range a.requirements { + if t == req || a.model.Conforms(t, req) { + covered[req] = true + } + } + } + }) + var missing []string + for _, req := range a.requirements { + if !covered[req] { + missing = append(missing, a.model.EffectiveNameOf(req)) + } + } + if len(missing) == 0 { + return + } + sort.Strings(missing) + a.report(matrix, CodeNPR7123MatrixMissingRequirement, fmt.Sprintf( + "This compliance matrix has no compliance entry for %d active NPR 7123.1D requirement(s): %s. Table H-1 covers every active requirement, so add a `@Compliance` usage of each from `NPR7123::SERequirements`.", + len(missing), strings.Join(missing, ", "))) +} + +// hasCompliance reports whether sym carries a @Compliance annotation. +func (a *npr7123Audit) hasCompliance(sym *symbols.Symbol) bool { + for _, facts := range a.model.AnnotationFactsOf(sym) { + if a.annotationIs(facts, npr7123Compliance) { + return true + } + } + return false +} + +// reviewedProduct reports whether sym is a product a life-cycle review states +// it reviews: a stand-in for the product at the review, not the product itself. +func (a *npr7123Audit) reviewedProduct(sym *symbols.Symbol) bool { + owner := sym.OwnerScope + return owner != nil && a.isA(owner.Owner(), npr7123LifeCycleReview) +} + +// checkSEMP expects a SEMP to address each of the 17 common technical processes (SE-58). +func (a *npr7123Audit) checkSEMP(semp *symbols.Symbol) { + addressed := map[*symbols.Symbol]bool{} + var walk func(sym *symbols.Symbol) + walk = func(sym *symbols.Symbol) { + for _, f := range a.nestedFeatures(sym) { + for _, t := range a.model.FeatureTypeSet(f) { + for _, p := range a.processes { + if t == p || a.model.Conforms(t, p) { + addressed[p] = true + } + } + } + walk(f) + } + } + walk(semp) + var missing []string + for _, p := range a.processes { + if !addressed[p] { + missing = append(missing, a.model.EffectiveNameOf(p)) + } + } + if len(missing) == 0 { + return + } + a.report(semp, CodeNPR7123SEMPMissingProcess, fmt.Sprintf( + "This SEMP does not address %d of the 17 common technical processes: %s. NPR 7123.1D section 6.2.3 (SE-58) has the SEMP define how each process, as tailored, is applied, so nest one usage per process (e.g. `#%s action ...`).", + len(missing), strings.Join(missing, ", "), npr7123Keyword(missing[0]))) +} + +// npr7123Keyword spells the library keyword of a definition name: lowerCamel of the name. +func npr7123Keyword(name string) string { + if name == "" { + return name + } + return strings.ToLower(name[:1]) + name[1:] +} + +func (a *npr7123Audit) report(sym *symbols.Symbol, code, message string) { + if sym == nil || sym.Decl == nil { + return + } + a.diags = append(a.diags, diag.Diagnostic{ + Severity: diag.SeverityWarning, + Span: sym.Decl.Span(), + Message: message, + Code: code, + Source: npr7123Source, + }) +} diff --git a/internal/check/passes/npr7123_test.go b/internal/check/passes/npr7123_test.go new file mode 100644 index 0000000000..ce615f2852 --- /dev/null +++ b/internal/check/passes/npr7123_test.go @@ -0,0 +1,134 @@ +package passes + +import "testing" + +// npr7123Model wraps body in a package importing the NPR7123 library. +func npr7123Model(body string) string { + return "package M {\n\tprivate import NPR7123::*;\n\tprivate import NPR7123::SERequirements::*;" + body + "\n}" +} + +var npr7123Codes = []string{ + CodeNPR7123ReviewMissingCriteria, + CodeNPR7123ReviewMissingProduct, + CodeNPR7123ComplianceNoJustification, + CodeNPR7123MatrixMissingRequirement, + CodeNPR7123SEMPMissingProcess, +} + +// A life-cycle review states entrance and success criteria; one stating +// neither, or only one kind, is reported once, and a peer review is not. +func TestNPR7123ReviewMissingCriteria(t *testing.T) { + src := npr7123Model(` + #lifeCycleReview action bare { :>> kind = ReviewKind::TRR; } + #lifeCycleReview action entranceOnly { + :>> kind = ReviewKind::TRR; + requirement e1 : ReviewCriteria::TRR::Entrance1 :> entranceCriteria; + } + #lifeCycleReview action complete { + :>> kind = ReviewKind::TRR; + requirement e1 : ReviewCriteria::TRR::Entrance1 :> entranceCriteria; + requirement s1 : ReviewCriteria::TRR::Success1 :> successCriteria; + } + #peerReview action peer;`) + w8dWantLines(t, src, CodeNPR7123ReviewMissingCriteria, 4, 5) +} + +// A review of a kind with section 5.2.2.2 minimum products is reported for +// each product it does not state, and for one stated below the required +// maturity; a kind without minimum products, or a product at or above it, is not. +func TestNPR7123ReviewMissingMinimumProduct(t *testing.T) { + src := npr7123Model(` + #lifeCycleReview action cdr { + :>> kind = ReviewKind::CDR; + requirement e1 : ReviewCriteria::CDR::Entrance1 :> entranceCriteria; + requirement s1 : ReviewCriteria::CDR::Success1 :> successCriteria; + } + #lifeCycleReview action pdr { + :>> kind = ReviewKind::PDR; + requirement e1 : ReviewCriteria::PDR::Entrance1 :> entranceCriteria; + requirement s1 : ReviewCriteria::PDR::Success1 :> successCriteria; + #designSolution item design :> reviewedProducts { :>> maturity = WorkProductMaturity::preliminary; } + #integrationPlan item plan :> reviewedProducts { :>> maturity = WorkProductMaturity::update; } + #verificationAndValidationPlan item vv :> reviewedProducts { :>> maturity = WorkProductMaturity::preliminary; } + } + #lifeCycleReview action trr { + :>> kind = ReviewKind::TRR; + requirement e1 : ReviewCriteria::TRR::Entrance1 :> entranceCriteria; + requirement s1 : ReviewCriteria::TRR::Success1 :> successCriteria; + }`) + w8dWantLines(t, src, CodeNPR7123ReviewMissingProduct, 4, 15) +} + +// A compliance entry marked tailored or notApplicable states a justification; +// a fully compliant one needs none. +func TestNPR7123ComplianceMissingJustification(t *testing.T) { + src := npr7123Model(` + part entries { + requirement a : SE07 { @Compliance { comply = ComplyKind::fullyCompliant; } } + requirement b : SE08 { @Compliance { comply = ComplyKind::tailored; justification = "deviation D-1"; } } + requirement c : SE09 { @Compliance { comply = ComplyKind::tailored; } } + requirement d : SE10 { @Compliance { comply = ComplyKind::notApplicable; } } + }`) + w8dWantLines(t, src, CodeNPR7123ComplianceNoJustification, 7, 8) +} + +// A compliance matrix is reported while an active SE requirement has no +// compliance entry in its document, and once for the whole matrix. +func TestNPR7123ComplianceMatrixMissingRequirement(t *testing.T) { + src := npr7123Model(` + part entries { + requirement a : SE07 { @Compliance { comply = ComplyKind::fullyCompliant; } } + } + part def Matrix :> ComplianceMatrix; + part matrix : Matrix;`) + w8dWantLines(t, src, CodeNPR7123MatrixMissingRequirement, 8) +} + +// A SEMP addresses all 17 processes, through nested usages at any depth; one +// that misses any is reported, and a SEMP stated as a reviewed product is not. +func TestNPR7123SEMPMissingProcess(t *testing.T) { + src := npr7123Model(` + #semp part partial : SEMP { + #stakeholderExpectationsDefinition action a; + } + #semp part full : SEMP { + #stakeholderExpectationsDefinition action p1; #technicalRequirementsDefinition action p2; + #logicalDecomposition action p3; #designSolutionDefinition action p4; + #productImplementation action p5; #productIntegration action p6; + #productVerification action p7; #productValidation action p8; + #productTransition action p9; #technicalPlanning action p10; + part management { + #requirementsManagement action p11; #interfaceManagement action p12; + #technicalRiskManagement action p13; #configurationManagement action p14; + #technicalDataManagement action p15; #technicalAssessment action p16; + #decisionAnalysis action p17; + } + } + #lifeCycleReview action srr { + :>> kind = ReviewKind::SRR; + requirement e1 : ReviewCriteria::SRR::Entrance1 :> entranceCriteria; + requirement s1 : ReviewCriteria::SRR::Success1 :> successCriteria; + #semp part sempAtSRR :> reviewedProducts { :>> maturity = WorkProductMaturity::baseline; } + #requirementSet item reqs :> reviewedProducts { :>> maturity = WorkProductMaturity::baseline; } + #humanSystemsIntegrationApproach item hsi :> reviewedProducts { :>> maturity = WorkProductMaturity::baseline; } + }`) + w8dWantLines(t, src, CodeNPR7123SEMPMissingProcess, 4) +} + +// A model that never uses the library, or a workspace package merely named +// NPR7123, gets no finding. +func TestNPR7123NeedsTheLibrary(t *testing.T) { + plain := `package M { + action def LifeCycleReview; action review : LifeCycleReview; + part def SEMP; part semp : SEMP; + }` + namesake := `package NPR7123 { + action def LifeCycleReview; action review : LifeCycleReview; + part def SEMP; part semp : SEMP; + }` + for _, src := range []string{plain, namesake} { + for _, code := range npr7123Codes { + w8dWantLines(t, src, code) + } + } +} diff --git a/internal/doc/queryexec/memberpath.go b/internal/doc/queryexec/memberpath.go index e73f6f2500..f6bbcb7da6 100644 --- a/internal/doc/queryexec/memberpath.go +++ b/internal/doc/queryexec/memberpath.go @@ -1,6 +1,7 @@ package queryexec import ( + "github.com/Open-MBEE/OpenSysML/internal/ir/queryplan" "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" "github.com/Open-MBEE/OpenSysML/internal/syntax/source" ) @@ -12,36 +13,76 @@ func parseMemberPath(property string) ([]string, bool) { return segments, ok && len(segments) >= 2 } -// memberPathValues walks a member path from the row element, own members -// first; a segment naming no member makes the path absent on this row. The -// member the last segment names is returned with the values (nil when absent). +// memberPathStep resolves one inner segment on an element: the member it +// names, own members first, else the elements of the metaclass feature it +// names (`type`, `owner`, …), so `type.documentation` reads a definition. +func (e *executor) memberPathStep(current *symbols.Symbol, segment string) []*symbols.Symbol { + if member, ok := e.context.Model.LookupMember(current, segment); ok && member != nil { + return []*symbols.Symbol{member} + } + if elements, ok := e.context.Model.ReflectiveElements(current, segment); ok { + return elements + } + return nil +} + +// memberPathValues walks a member path from the row element; a segment naming +// neither a member nor a metaclass feature makes the path absent on this row. +// The last segment reads a declared feature, else a metaclass feature such as +// documentation. The member the last segment names is returned with the +// values (nil when absent or when it is not a declared member). func (e *executor) memberPathValues(sym *symbols.Symbol, segments []string) ([]Value, bool, *symbols.Symbol, error) { - current := sym + reached := []*symbols.Symbol{sym} for _, segment := range segments[:len(segments)-1] { - member, ok := e.context.Model.LookupMember(current, segment) - if !ok || member == nil { + var next []*symbols.Symbol + for _, current := range reached { + next = append(next, e.memberPathStep(current, segment)...) + } + if len(next) == 0 { return nil, false, nil, nil } - current = member + reached = next + } + last := segments[len(segments)-1] + var values []Value + var member *symbols.Symbol + present := false + for _, current := range reached { + found, ok, err := e.declaredFeatureValues(current, last) + if err != nil { + return nil, false, nil, err + } + if ok { + if m, _ := e.context.Model.LookupMember(current, last); member == nil { + member = m + } + } else { + found, ok, err = e.reflectiveValues(queryplan.Expression{}, current, last) + if err != nil { + return nil, false, nil, err + } + } + if ok { + present = true + values = append(values, found...) + } } - values, present, err := e.declaredFeatureValues(current, segments[len(segments)-1]) - if !present || err != nil { - return values, present, nil, err + if !present { + return nil, false, nil, nil } - member, _ := e.context.Model.LookupMember(current, segments[len(segments)-1]) return values, true, member, nil } // memberPathStop walks a member path as memberPathValues does and reports where -// it stopped: the last member reached and the segment that names no member there. +// it stopped: the last element reached and the segment that names nothing there. func (e *executor) memberPathStop(sym *symbols.Symbol, segments []string) (*symbols.Symbol, string) { current := sym for _, segment := range segments { - member, ok := e.context.Model.LookupMember(current, segment) - if !ok || member == nil { + next := e.memberPathStep(current, segment) + if len(next) == 0 { return current, segment } - current = member + current = next[0] } return current, segments[len(segments)-1] } diff --git a/internal/doc/queryexec/memberpath_test.go b/internal/doc/queryexec/memberpath_test.go index 908f2bddbe..4c31baa97d 100644 --- a/internal/doc/queryexec/memberpath_test.go +++ b/internal/doc/queryexec/memberpath_test.go @@ -617,3 +617,51 @@ calc def Plenty :> Query { t.Fatalf("C cells = %v, want empty, {1,2}", got) } } + +// A segment naming no member of the element reached reads the element-valued +// metaclass feature of that name, so `type.documentation` on a usage reads the +// documentation of its definition; a row whose type has none is an empty cell. +func TestExecuteMemberPathReadsMetaclassFeatureSegment(t *testing.T) { + fixture := loadExecutionFixture(t, ` +package Reqs { + requirement def Documented { + doc /* The statement. */ + } + requirement def Silent; + part holder { + requirement a : Documented; + requirement b : Silent; + } +} +calc def Statements :> Query { + in root : Element; + Project( + source = Descendants(source = root, maxDepth = 1), + properties = ("name", "type.documentation"), + tolerate = true + ) +}`) + result, err := fixture.execute(t, "Statements", Bindings{ + "root": {ElementValue(fixture.symbol(t, "Reqs::holder"))}, + }, Options{}) + if err != nil { + t.Fatalf("execute: %v", err) + } + position := slices.IndexFunc(result.Columns(), func(c Column) bool { return c.Name() == "type.documentation" }) + if position < 0 { + t.Fatalf("no type.documentation column in %v", result.Columns()) + } + var got [][]string + for _, row := range result.Rows() { + var cell []string + for _, v := range row.Cells()[position].Values() { + text, _ := v.String() + cell = append(cell, text) + } + got = append(got, cell) + } + want := [][]string{{"The statement."}, nil} + if !slices.EqualFunc(got, want, slices.Equal) { + t.Fatalf("type.documentation cells = %q, want %q", got, want) + } +} From f203755fe5d8b11ffc9f34bfdefe7415622f74e1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 16:19:43 +0000 Subject: [PATCH 2/2] docs: NPR7123 library design page, demo README, compliance rows and changelog Co-Authored-By: jason.han --- changes/unreleased/npr7123-library.added.md | 1 + docs/project/README.md | 4 + docs/project/constraint-manifest.json | 6 + docs/project/npr7123-library.md | 133 ++++++++++++++++++++ docs/project/spec-compliance.md | 17 +++ examples/npr7123-demo/README.md | 102 +++++++++++++++ mkdocs.yml | 1 + 7 files changed, 264 insertions(+) create mode 100644 changes/unreleased/npr7123-library.added.md create mode 100644 docs/project/npr7123-library.md create mode 100644 examples/npr7123-demo/README.md diff --git a/changes/unreleased/npr7123-library.added.md b/changes/unreleased/npr7123-library.added.md new file mode 100644 index 0000000000..602a41a445 --- /dev/null +++ b/changes/unreleased/npr7123-library.added.md @@ -0,0 +1 @@ +- `NPR7123`, the NASA NPR 7123.1D systems-engineering library from the OpenSysML Extensions Library: the 17 common technical processes, life-cycle reviews with the Appendix G entrance/success criteria, work-product maturity (Table 5-1, Appendix F), TRLs, the 62 active SE requirements and a `ComplianceMatrix` document generating Table H-1; `passes.NPR7123Pass` (constraint tier, warnings, codes `npr7123-*`) audits reviews, minimum products, compliance entries, the compliance matrix and the SEMP; `examples/npr7123-demo` and `docs/project/npr7123-library.md`. Document-query member paths read an element-valued metaclass feature when no member matches, so `type.documentation` reads a usage's definition documentation. diff --git a/docs/project/README.md b/docs/project/README.md index 514a15156c..995dd7bab4 100644 --- a/docs/project/README.md +++ b/docs/project/README.md @@ -102,6 +102,10 @@ within these records and means nothing outside this repository. - **[MOSA library](mosa-library.md)** — the Modular Open Systems Approach as a bundled OpenSysML library: the statute's vocabulary, openness as metadata over the standard model, the interface control document and the warning-only checks +- **[NPR7123 library](npr7123-library.md)** — NASA NPR 7123.1D as a bundled OpenSysML library: + the 17 common technical processes, life-cycle reviews with the Appendix G criteria, work-product + maturity, TRLs, the active SE requirements, the generated compliance matrix and the warning-only + checks - **[StateMachines library](statemachines-library.md)** — pseudostates as bundled `SemanticMetadata`, so a state machine's `choice`, `junction` and history pseudostates spell standard, conforming SysML v2 diff --git a/docs/project/constraint-manifest.json b/docs/project/constraint-manifest.json index e86adac791..cea1d4b79f 100644 --- a/docs/project/constraint-manifest.json +++ b/docs/project/constraint-manifest.json @@ -13550,6 +13550,12 @@ "basis": "extension", "note": "Audits the elements a model classifies with the MOSA extension library against that method's openness rules, beyond KerML and SysML." }, + { + "file": "internal/check/passes/npr7123.go", + "pass": "NPR7123Pass", + "basis": "extension", + "note": "Audits the elements a model classifies with the NPR7123 extension library against what NASA NPR 7123.1D states of them, beyond KerML and SysML." + }, { "file": "internal/check/passes/oosem_method.go", "pass": "OOSEMMethodPass", diff --git a/docs/project/npr7123-library.md b/docs/project/npr7123-library.md new file mode 100644 index 0000000000..b34c248d20 --- /dev/null +++ b/docs/project/npr7123-library.md @@ -0,0 +1,133 @@ +# NPR7123 library — design + +Status: **implemented.** `internal/workspace/libs/stdlib/OpenSysML Libraries/NPR7123.sysml` — +maintained upstream at Open-MBEE/OpenSysML-Extensions-Library and vendored here pinned by +`scripts/extension-libraries-pin.sh` — is bundled with the other OpenSysML extensions, enters the +same conformance, strict-notation and snapshot gates, and is exercised by +`examples/npr7123-demo/npr7123-demo.sysml` (`TestExamplesAnalyseCleanly`). `passes.NPR7123Pass` +audits models written against it. + +## The problem + +NASA NPR 7123.1D, *NASA Systems Engineering Processes and Requirements* (Office of the Chief +Engineer, effective 2023-07-05, [NODIS](https://nodis3.gsfc.nasa.gov/displayDir.cfm?Internal_ID=N_PR_7123_001D_&page_name=main)), +is the procedural requirement every NASA program and project's systems engineering is assessed +against. It names 17 *common technical processes* in three groups, each with one `[SE-nn]` +requirement; the life-cycle reviews a project passes through, with entrance and success criteria +(Appendix G) and the minimum work products each review presents at a stated maturity +(§5.2.2.2, Table 5-1, Appendix F); the Technology Readiness Levels (Appendix E); the Systems +Engineering Management Plan that records how the processes are tailored (§6, SE-58); the +Technical Authority, waivers and deviations of chapter 2; and in Appendix H the *compliance +matrix* — one row per active requirement with `FC`, `T` or `NA` and a justification — that a +project fills in and the Center reviews. + +A project working in SysML v2 already has the processes, the reviews, the products and the +requirements in its model. What it lacks is the NPR's vocabulary to classify them with, and the +table the NPR asks for generated from the model rather than kept in a spreadsheet beside it. +SysML v2 has no profiles; the vocabulary is a *library* and the compliance matrix is a document +query over the same model. + +## Design + +**The NPR's names and numbers first.** Every concept keeps the NPR's name, numbering and, where +the NPR defines the term, its words (the text is a work of the United States Government). The +library is non-normative: it describes the NPR, it does not replace it, and nothing in it is +stricter than the NPR's text. Following `MOSA` and `OOSEM`, each artefact is a definition +specialising the native SysML type, an abstract base usage subsetting the native base usage, and +a `SemanticMetadata` keyword applying both at once: + +```sysml +#technicalPlanning action plan; // a TechnicalProcess, in technicalProcesses +#lifeCycleReview action pdr { :>> kind = ReviewKind::PDR; } +#verificationAndValidationPlan item vvPlan :> reviewedProducts { + :>> maturity = WorkProductMaturity::baseline; +} +``` + +| NPR 7123.1D | Library element | +| --- | --- | +| §3.2.2–3.2.18, the 17 common technical processes, three groups | `TechnicalProcess :> Action` with `group : TechnicalProcessGroup` (`systemDesign`, `productRealization`, `technicalManagement`), `requirementId`, `section`, `tailoring`; one definition, base usage and keyword per process (`StakeholderExpectationsDefinition` … `DecisionAnalysis`), each doc carrying the NPR's "is used to…" text and its SE id | +| Ch. 2, Technical Authority, tailoring, waivers and deviations | `TechnicalAuthority :> Part` with `TechnicalAuthorityKind`; `@Waiver`, `@Deviation` (the NPR's own distinction: tailoring seeks relief by waiver or deviation, customizing selects how a requirement is implemented and needs neither) | +| §5.1, life cycle and key decision points | `LifeCyclePhase` and `KeyDecisionPoint`, kept thin: NPR 7123.1D illustrates the life cycles and defers them to NPR 7120.5 | +| §5.2.2, life-cycle and technical reviews | `TechnicalReview :> Action` (`entranceCriteria`, `successCriteria`, `reviewedProducts`, `items` for RFAs/RIDs/actions), `LifeCycleReview` (`kind : ReviewKind`, `phase`, `keyDecisionPoint`), `PeerReview`; `ReviewKind` enumerates every Appendix G review by acronym | +| Appendix G, Tables G-1…G-21, entrance and success criteria | `ReviewCriteria::::Entrance` / `Success` requirement definitions (`EntranceCriterion`, `SuccessCriterion`, `number`), one per criterion; a project subsets or redefines them — G.1.2 expects tailoring | +| §5.2.2.2, Table 5-1, Appendix F, work products and maturity | `WorkProduct :> Item` with `maturity : WorkProductMaturity` (`initial`, `final`, `update`; `preliminary`, `baseline`, `approve`; `approach`); one item definition per Table 5-1 product carrying a `@MaturityExpectation { review; maturity; required; requirementId }` per table cell, `required = true` for the §5.2.2.2 minimum products | +| Appendix E, Technology Readiness Levels | `TechnologyReadinessLevel` `trl1`…`trl9` with the definition and success-criteria text; `@TechnologyReadiness { level; assessedAt; basis }` | +| Ch. 6, the SEMP; SE-58 | `SEMP :> Document, WorkProduct`; a project nests one usage per process under it, recording the tailoring | +| Appendix H, Table H-1; Appendix J | `SERequirements` holds every active requirement as `requirement def <'SE-nn'> SEnn :> SERequirement` (doc = statement; `id`, `section`, `rationale`); SE-01…05, SE-49 and SE-50 are deleted and not modelled | +| Appendix H, compliance | `ComplyKind` (`fullyCompliant` "FC", `tailored` "T", `notApplicable` "NA"), `@Compliance { comply; justification }` on a requirement usage, `ComplianceEntries`/`ComplianceRegister` queries, `ComplianceMatrix :> Document` | +| §6.2.1 TPMs, MOEs, leading indicators | the standard `ParametersOfInterestMetadata` (`#moe`, `#mop`), re-exported rather than redefined | + +**Compliance matrix.** `ComplianceMatrix` is a `DocumentQueries::Document` whose one section +tabulates `ComplianceRegister`: a model specialises it and binds `root` to the usage holding its +compliance entries (`calc rows : ComplianceRegister { in :>> root = complianceEntries; }`). The +register walks the descendants of `root`, selects the usages carrying `@Compliance`, and projects +Table H-1's columns — Req ID, NPR Section, Requirement Statement, Rationale, Comply?, +Justification. The statement is the *definition's* documentation, read through the member path +`type.documentation`: a member-path segment naming no declared member reads the element-valued +metaclass feature of that name (`internal/doc/queryexec/memberpath.go`), which is what makes the +documentation of `SE07` reachable from a usage `requirement se07 : SE07`. + +**Checks.** `passes.NPR7123Pass` (constraint tier, source `npr7123`) audits a model against what +the NPR states of the elements it classifies. Every finding is a **warning** — a project between +reviews is a normal state of the work — and every rule applies only to an element of the kind the +NPR speaks about: + +| Code | Rule | NPR 7123.1D | +| --- | --- | --- | +| `npr7123-review-missing-entrance-or-success-criteria` | a `LifeCycleReview` usage nests an `EntranceCriterion` and a `SuccessCriterion` requirement usage; one stating neither, or only one kind, is reported once | §5.2.2.1, SE-34 | +| `npr7123-review-missing-minimum-product` | a review of a kind with minimum products states each under `reviewedProducts`, at or above the required maturity (initial/preliminary < approve/baseline < final/update); an absent product is reported on the review, one below maturity on the product | §5.2.2.2, SE-35…SE-56, SE-66…SE-69; Table 5-1 | +| `npr7123-compliance-missing-justification` | a `@Compliance` marked `tailored` or `notApplicable` states a non-empty `justification` | Table H-1 | +| `npr7123-compliance-matrix-missing-requirement` | the document holding a `ComplianceMatrix` usage has a `@Compliance`-annotated usage of every active SE requirement definition; one finding per matrix, naming the missing ids | Table H-1 | +| `npr7123-semp-missing-process` | a `SEMP` usage nests, at any depth, one usage of each of the 17 process definitions; a SEMP a review lists under `reviewedProducts` is a stand-in and is not audited | §6.2.3, SE-58 | + +Classification follows the typing or the keyword, so `action pdr : LifeCycleReview`, +`#lifeCycleReview action pdr` and a usage of a project's own `action def PDR :> LifeCycleReview` +are all life-cycle reviews. Library documents are not audited, and a model that never touches +`NPR7123` gets no finding; a workspace package that merely reuses the name is not the vocabulary. +A rule the NPR does not state is not added at any severity: the pass does not, for instance, +demand a TRL, a Technical Authority or a review of every kind. + +**A Center implementation layers on top.** NPR 7123.1D §1.2 and Appendix H expect each Center to +implement the NPR with its own procedures. A Center writes a specialising library package rather +than editing this one: its review definitions specialise `LifeCycleReview` and fix the `kind`; +its tailored criteria subset or redefine the `ReviewCriteria` definitions; its templates +specialise the work-product definitions with the Center's names; and its compliance matrix +specialises `ComplianceMatrix`, adding columns for the Center's own requirements. The pass keeps +reading the NPR facts through the specialisations, so a Center's models are audited against the +NPR without a Center-specific pass. + +## How OpenSysML serves the NPR + +What the NPR asks a project to *show* — the SEMP's tailoring of each process (SE-58), the +reviews' criteria and products (§5.2.2), and the compliance matrix (Appendix H) — is generated +from the model: `sysml -render-document` writes the compliance matrix as Markdown, HTML or PDF, +`-validate` reports the gaps as warnings with the NPR section each comes from, and every element +is reachable through the model API, the REPL and the language server. + +## Alternatives considered + +- **One requirement definition per Table 5-1 cell.** Rejected: the maturity a product reaches at a + review is a fact about the product, so it is an annotation on the product definition, and the + minimum products of §5.2.2.2 are the cells marked required, not a parallel list. +- **Entrance and success criteria as plain documentation on the review kind.** Rejected: G.1.2 + says projects tailor them, and a requirement definition per criterion is what a project can + subset, redefine, verify and trace. +- **Statements read through a `documentation` column on the usage.** Rejected: a usage's + documentation is its own; the NPR's statement lives once, on the definition, and the member path + `type.documentation` reads it there without copying it into every entry. +- **Checks as errors, or unconditional.** Rejected, as for MOSA and OOSEM: a project before its + SRR has no baselined requirements yet and is not wrong. + +## Known limitations + +- The life cycles themselves — phases, KDPs, which review closes which phase — are NPR 7120.5's + and NPR 7120.8's; `LifeCyclePhase` and `KeyDecisionPoint` are placeholders a program library + fills in. +- Table H-1 prints SE-59…SE-64 in one row with one rationale; the library gives each its own + statement from §6.2.5–6.2.9 and the part of the shared rationale that speaks to it. +- The pass audits the document that holds the compliance matrix; entries a project keeps in a + second file are not seen by the matrix rule, so keep the matrix beside its entries. +- Center implementations, the program-level SRR/SDR tables of Appendix G applied to programs + rather than projects, and the contracted-project requirements of chapter 4 (SE-24…SE-31) are + vocabulary only: the NPR states no model-checkable fact about them. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 1726b828f4..e75b84a033 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -1954,6 +1954,23 @@ same rule — the OMG function libraries are not implicitly imported either. | An unqualified call the model imports no declaration of fails with the unresolved-reference error the checker reports, naming the qualified declaration an import would make visible, so the diagnostic and the behavior agree instead of contradicting each other | `runtime/eval.go` `invocationTarget`, `unresolvedInvocation` | `runtime/library_functions_test.go:TestLibraryFunctionUnqualifiedNames`, `TestRuntimeRobustnessCalcInvocation/calc_calls_an_unimported_extension_function`, `lsp/library_invocation_test.go:TestPublishDiagnosticsKeepsExtensionFunctionImportGated` | 🧩 Extension (OpenSysML library) | | The function listing covers every function the build implements, each with the package an import must name for its unqualified name to resolve, so a working function is advertised neither as unsupported nor as callable bare | `runtime/builtin_names.go` `Builtin.Package`, `Builtins`, read by `repl/discover.go` `doBuiltins` and `repl/complete.go` | `runtime/library_functions_test.go:TestBuiltinsListEveryFunctionWithItsPackage`, `repl/discover_test.go:TestBuiltinsListsAnExtensionFunctionWithItsImport` | 🧩 Extension (OpenSysML library) | +### NPR7123 Extension Library (`OpenSysML Libraries/NPR7123.sysml`; an OpenSysML extension, non-normative) + +NASA NPR 7123.1D is a process standard, not a modeling language; the library carries its +vocabulary (see `docs/project/npr7123-library.md`) and `passes.NPR7123Pass` audits a model +against the facts the NPR states of that vocabulary, as warnings at the constraint tier. The +rows claim agreement with the NPR's text, not with SysML v2 semantics. + +| Semantic Rule | Implementation | Test Case | Status | +|--------------|----------------|-----------|--------| +| A life-cycle review usage states entrance criteria and success criteria (NPR 7123.1D §5.2.2.1, SE-34): an `EntranceCriterion` and a `SuccessCriterion` requirement usage nested in its body; one stating neither or only one kind is reported once | `passes/npr7123.go` `checkReview` | `passes/npr7123_test.go` `TestNPR7123ReviewMissingCriteria` | 🧩 Extension (OpenSysML library) | +| A life-cycle review of a kind with §5.2.2.2 minimum products (`@MaturityExpectation { required = true }` on the library's work-product definitions, transcribed from Table 5-1) states each product under `reviewedProducts`, at or above the maturity the section asks for (Appendix F order: initial/preliminary < approve/baseline < final/update) | `passes/npr7123.go` `checkReview`, `npr7123MaturityRank` | `TestNPR7123ReviewMissingMinimumProduct` | 🧩 Extension (OpenSysML library) | +| A `@Compliance` marked `tailored` or `notApplicable` states a non-empty `justification` (Table H-1) | `passes/npr7123.go` `checkCompliance` | `TestNPR7123ComplianceMissingJustification` | 🧩 Extension (OpenSysML library) | +| A `ComplianceMatrix` usage's document holds a `@Compliance`-annotated usage of every active `[SE-nn]` definition of `NPR7123::SERequirements` (Table H-1 covers every active requirement); the finding is one per matrix, naming the missing ids | `passes/npr7123.go` `checkMatrix` | `TestNPR7123ComplianceMatrixMissingRequirement` | 🧩 Extension (OpenSysML library) | +| A `SEMP` usage nests, at any depth, one usage of each of the 17 common technical process definitions (§6.2.3, SE-58); a SEMP a review states under `reviewedProducts` is a stand-in and is not audited | `passes/npr7123.go` `checkSEMP`, `reviewedProduct` | `TestNPR7123SEMPMissingProcess` | 🧩 Extension (OpenSysML library) | +| Library documents are not audited, and a model that never uses the library — or a workspace package merely named `NPR7123` — gets no finding | `passes/npr7123.go` `Run`, `newNPR7123Audit` | `TestNPR7123NeedsTheLibrary` | 🧩 Extension (OpenSysML library) | +| A document-query member path whose segment names no declared member reads the element-valued metaclass feature of that name, so `type.documentation` reads the documentation of a usage's definition (what `NPR7123::ComplianceRegister` needs for the Requirement Statement column) | `queryexec/memberpath.go` `memberPathStep` | `queryexec/memberpath_test.go` `TestExecuteMemberPathReadsMetaclassFeatureSegment` | 🧩 Extension (OpenSysML library) | + ### Stochastic Execution (`OpenSysML Libraries/Stochastic.sysml`, `OpenSysML Libraries/RandomFunctions.kerml`; an OpenSysML extension, non-normative) SysML v2 has no notation for a probability on a succession, a random-valued diff --git a/examples/npr7123-demo/README.md b/examples/npr7123-demo/README.md new file mode 100644 index 0000000000..38faf517db --- /dev/null +++ b/examples/npr7123-demo/README.md @@ -0,0 +1,102 @@ +# NPR 7123.1 demo + +[`npr7123-demo.sysml`](npr7123-demo.sysml) works a small fictional project — the *Dust Lidar +Pathfinder*, a lunar-surface dust lidar on a lander — through NASA NPR 7123.1D using the bundled +[`NPR7123`](../../docs/project/npr7123-library.md) library. Each artefact carries the NPR's +keyword, which both types it and adds it to the NPR's base usage, and each NPR fact is stated +where the NPR expects it: + +```sysml +#semp part pathfinderSEMP : SEMP { + :>> maturity = WorkProductMaturity::baseline; + #technicalPlanning action planTechnicalEffort { + :>> tailoring = "Fully applied; the SEMP is this document."; + } +} +#lifeCycleReview action pdr { + :>> kind = ReviewKind::PDR; + requirement pdrEntrance1 : ReviewCriteria::PDR::Entrance1 :> entranceCriteria; + #verificationAndValidationPlan item vvPlanAtPDR :> reviewedProducts { + :>> maturity = WorkProductMaturity::preliminary; + } +} +requirement se07 : SE07 { @Compliance { comply = ComplyKind::fullyCompliant; } } +``` + +| Package | What it holds | +| --- | --- | +| `Technical Planning` | the `#semp` SEMP addressing all 17 common technical processes with its tailoring (SE-58), an earlier SEMP outline, the `#mop` mass and power margins and the `#moe` dust detection rate | +| `Work Products` | the §5.2.2.2 minimum products the project has produced so far — the stakeholder expectations, concept of operations, requirement set, design solution, V&V plan and their companions — each at its current `WorkProductMaturity` | +| `Life-Cycle Reviews` | the `#lifeCycleReview`s SRR, PDR and CDR with tailored Appendix G entrance and success criteria, the minimum products of §5.2.2.2 under `reviewedProducts`, a `#peerReview`, and the lidar transmitter's `@TechnologyReadiness` (TRL 6 at PDR) | +| `Compliance Entries` | one `@Compliance`-annotated usage per active SE requirement — `FC`, `T` or `NA` with its justification — plus the `#technicalAuthority` and the `@Deviation` the tailored entries cite | +| `Compliance Matrix` | a `ComplianceMatrix` document whose table is Table H-1 generated from the entries | + +## From the command line + +Analyse the model: + +```bash +./bin/sysml examples/npr7123-demo/npr7123-demo.sysml -validate +``` + +The model has no errors, and `-validate` also runs the NPR 7123.1D checks, which report the five +gaps the model leaves open on purpose (each is marked with a `doc` comment): + +| Warning | Gap | +| --- | --- | +| `npr7123-semp-missing-process` | `sempOutline` addresses only the four system design processes (SE-58) | +| `npr7123-review-missing-minimum-product` | `vvPlanAtPDR` is `preliminary` where §5.2.2.2 (SE-68) asks for `baseline`; the `cdr` states no design solution (SE-46) | +| `npr7123-review-missing-entrance-or-success-criteria` | the `cdr` states no entrance or success criteria (SE-34) | +| `npr7123-compliance-missing-justification` | `se65` is tailored with no `justification` | +| `npr7123-compliance-matrix-missing-requirement` | no compliance entry covers SE-31 | + +```text +examples/npr7123-demo/npr7123-demo.sysml:75:3: warning[npr7123-semp-missing-process]: This SEMP does not address 13 of the 17 common technical processes: ProductImplementation, ProductIntegration, Pro + #semp part sempOutline : SEMP { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +examples/npr7123-demo/npr7123-demo.sysml:180:4: warning[npr7123-review-missing-minimum-product]: This VerificationAndValidationPlan is stated at maturity preliminary, below the baseline NPR 7123.1D se + #verificationAndValidationPlan item vvPlanAtPDR :> reviewedProducts { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +examples/npr7123-demo/npr7123-demo.sysml:186:3: warning[npr7123-review-missing-minimum-product]: This CDR states no DesignSolution: NPR 7123.1D section 5.2.2.2 (SE-46) has the technical team provide i + #lifeCycleReview action cdr { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +examples/npr7123-demo/npr7123-demo.sysml:186:3: warning[npr7123-review-missing-entrance-or-success-criteria]: This life-cycle review states no entrance criteria and no success criteria: NPR 7123.1D se + #lifeCycleReview action cdr { + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +examples/npr7123-demo/npr7123-demo.sysml:398:4: warning[npr7123-compliance-missing-justification]: This compliance entry is marked tailored with no justification: NPR 7123.1D Table H-1 asks for the ju + requirement se65 : SE65 { + ^~~~~~~~~~~~~~~~~~~~~~~~~ +examples/npr7123-demo/npr7123-demo.sysml:441:3: warning[npr7123-compliance-matrix-missing-requirement]: This compliance matrix has no compliance entry for 1 active NPR 7123.1D requirement(s): SE31. Ta + part complianceMatrix : 'Pathfinder Compliance Matrix'; + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +✓ package 'Dust Lidar Pathfinder' +✓ examples/npr7123-demo/npr7123-demo.sysml: no errors +``` + +Record the missing fact — the processes, a `baseline` maturity, the criteria, a `justification`, +the SE-31 entry — and the warning goes away. + +Render the compliance matrix (Markdown by default; `-doc-form html` or `pdf` for the others): + +```bash +./bin/sysml examples/npr7123-demo/npr7123-demo.sysml \ + -render-document "'Dust Lidar Pathfinder'::'Compliance Matrix'::'Pathfinder Compliance Matrix'" +``` + +The document's one table is Table H-1 over the project's entries, 61 rows for the 62 active +requirements (SE-31 is the deliberate gap); the statement and rationale columns are abridged here: + +```markdown +| Req ID | NPR Section | Requirement Statement | Rationale | Comply? | Justification | +| --- | --- | --- | --- | --- | --- | +| SE-06 | 6.1.5 | The ETA shall approve the SEMP, waiver or deviation authorizations, an… | This requirement ensures that the ETA has reviewed and approved of key… | fullyCompliant | | +| SE-07 | 3.2.2.1 | Program/Project Managers shall identify and implement an ETA-approved… | This requirement ensures that the program/project and the ETA identifi… | fullyCompliant | | +| SE-08 | 3.2.3.1 | Program/Project Managers shall identify and implement an ETA-approved… | This requirement ensures that the program/project and the ETA identifi… | fullyCompliant | | +| SE-09 | 3.2.4.1 | Program/Project Managers shall identify and implement an ETA-approved… | This requirement ensures that the program/project and the ETA identifi… | fullyCompliant | | +| … | | | | | | +| SE-65 | 5.2.1.3 | The technical team shall develop and document an approach to Human Sys… | The HSI approach ensures that system aspects that interface with human… | tailored | | +| SE-69 | 5.2.2.2.g(1) | The technical team shall provide the following minimum products at the… | At ORR the project should have at least preliminary results from the V… | fullyCompliant | | +``` + +The requirement statement is read from the library's requirement definition through the member +path `type.documentation`, so the NPR's text is stated once, in the library. diff --git a/mkdocs.yml b/mkdocs.yml index 5832daf4be..d48e1e3f71 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -207,6 +207,7 @@ exclude_docs: | project/errata-overlay.md project/oosem-library.md project/mosa-library.md + project/npr7123-library.md project/statemachines-library.md project/extension-libraries.md