Skip to content
This repository has been archived by the owner on Dec 12, 2023. It is now read-only.

Convert Implemented Requirements Counts to Dynamic Value Sets #26

Merged
merged 67 commits into from
Dec 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c03e0b7
Test out XSL functions for value-set checks.
aj-stein-gsa Nov 4, 2020
56c8b2a
Remove obsolete comment.
aj-stein-gsa Nov 4, 2020
f74883a
Use fatal error role, no just error.
aj-stein-gsa Nov 11, 2020
e71e0fd
Return nothing if valid, enum for recommendations if invalid.
aj-stein-gsa Nov 11, 2020
33c97d3
Clean up validation function some more.
aj-stein-gsa Nov 11, 2020
e875549
Put back variable and not test condition.
aj-stein-gsa Nov 11, 2020
c367072
Clean up validation function.
aj-stein-gsa Nov 11, 2020
17d9f4a
Update parameters to match metaschema pipeline.
aj-stein-gsa Nov 12, 2020
b5ec63f
Clear intermediate Schematron files too, still no dice.
aj-stein-gsa Nov 12, 2020
6733a18
Make the script actually match docs, thanks @mike-stern!
aj-stein-gsa Nov 12, 2020
cd2bf3c
Revert change to ignore namespacing.
aj-stein-gsa Nov 18, 2020
70e6d14
Add back important script variable bindings after rebase.
aj-stein-gsa Nov 18, 2020
f3b6668
Add global context fn for sensitivity-level.
aj-stein-gsa Nov 18, 2020
921cb91
Clean up function spacing.
aj-stein-gsa Nov 18, 2020
07108bd
Start new approach with global context from XSL functions.
aj-stein-gsa Nov 18, 2020
ab5bf7d
More function cleanup.
aj-stein-gsa Nov 19, 2020
3f53b44
Add empty check util function.
aj-stein-gsa Nov 19, 2020
192a436
Add formatter functions (even if I cannot get them working).
aj-stein-gsa Nov 19, 2020
82bc5ec
Update function-based approach for rules.
aj-stein-gsa Nov 19, 2020
53d6d3c
Test out supressed pending test.
aj-stein-gsa Nov 19, 2020
49cadad
Testing reordering of rules.
aj-stein-gsa Nov 19, 2020
04c5bca
Remove relocated rules.
aj-stein-gsa Nov 19, 2020
1ec2799
Time for some more cleanup.
aj-stein-gsa Nov 19, 2020
e5e1b05
Smaller function name that is more coherent.
aj-stein-gsa Nov 19, 2020
c2ac489
Update to dynamic grouping of items for analysis.
aj-stein-gsa Nov 21, 2020
6fe6ba8
Minor type fixes.
aj-stein-gsa Nov 21, 2020
5e5e2b2
Rename count var more appropriately.
aj-stein-gsa Nov 21, 2020
572cfc5
Reorganize rules a little.
aj-stein-gsa Nov 21, 2020
734f550
No, actually do it this time. And some more reporting info.
aj-stein-gsa Nov 21, 2020
60bc0a0
Include in the reports node the overall count.
aj-stein-gsa Nov 24, 2020
83b7454
Better name XSL functions.
aj-stein-gsa Nov 24, 2020
136a390
More refactoring of lv:analyze function.
aj-stein-gsa Nov 24, 2020
d574b7c
More lv:analyze fn and template touch-ups.
aj-stein-gsa Nov 24, 2020
f055c0a
Add lv:report function to call template.
aj-stein-gsa Nov 24, 2020
5f7f0d4
Finally, a valid report format response, on to tests.
aj-stein-gsa Nov 25, 2020
71be77a
More touch-up, less whitespace escapes for now.
aj-stein-gsa Nov 25, 2020
f6fc823
Remove all-requirements-report, no longer needed.
aj-stein-gsa Nov 25, 2020
8c69ca3
Hoist up required controls declaration.
aj-stein-gsa Nov 25, 2020
716e66c
Push up updated changes for feedback.
aj-stein-gsa Nov 25, 2020
0c60d80
Clean up XSpec context and see if we can push forward.
aj-stein-gsa Nov 25, 2020
e2f1aae
Remove global context comments.
aj-stein-gsa Nov 25, 2020
416348b
Move debugging elements.
aj-stein-gsa Nov 25, 2020
76f60e6
Fix bad assert logic.
aj-stein-gsa Nov 25, 2020
7145eba
Clean up XSpec spacing.
aj-stein-gsa Nov 25, 2020
d85a6ca
Add return type for if-empty-default.
aj-stein-gsa Nov 25, 2020
a77ae40
Improve null checks for empty value-sets.
aj-stein-gsa Nov 25, 2020
00a8138
Rearrange and cleanup tests some more.
aj-stein-gsa Nov 25, 2020
cdec1ba
Some more function cleanups.
aj-stein-gsa Nov 26, 2020
b54945f
Sigh, more touchups.
aj-stein-gsa Nov 26, 2020
43bf265
More silly formatting.
aj-stein-gsa Nov 27, 2020
2a7f8c5
Fix function for proper empty testing.
aj-stein-gsa Nov 27, 2020
fd381e6
Remove tests with params inside x:context.
aj-stein-gsa Nov 27, 2020
a1ddbf3
More touch-ups.
aj-stein-gsa Nov 27, 2020
8b19ae4
Fix up the tests.
aj-stein-gsa Nov 27, 2020
0554bdd
Test fix-ups.
aj-stein-gsa Nov 27, 2020
d9571e8
Clean up tests and start passing again.
aj-stein-gsa Nov 27, 2020
5f63a33
Add back important test.
aj-stein-gsa Nov 27, 2020
a7227d9
Remove unused var for now.
aj-stein-gsa Nov 27, 2020
68542e2
Finally figured out how to test registry context function.
aj-stein-gsa Nov 27, 2020
1b1d1ff
Improve default-if-empty to support many types.
aj-stein-gsa Dec 1, 2020
34be6b1
And update supporting tests.
aj-stein-gsa Dec 1, 2020
1165a02
Fix up tests.
aj-stein-gsa Dec 2, 2020
2a26f03
Fix up if-empty-default tests and other cleanup.
aj-stein-gsa Dec 2, 2020
022faf4
Add empty sequence test.
aj-stein-gsa Dec 2, 2020
4bf7445
Add tests for lv:correct.
aj-stein-gsa Dec 2, 2020
54623b2
add reports section to the html conversion
Dec 2, 2020
5f4eada
remove cross check
Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion resources/validations/bin/validate_with_schematron.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fi

#if version not specified default
SAXON_VERSION=${SAXON_VERSION:-10.2}
SAXON_OPTS="${SAXON_OPTS:-allow-foreign=true}"
SAXON_OPTS="${SAXON_OPTS:-allow-foreign=true diagnose=true}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this permanent? I'm fine if it is, just calling attention

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this in here, I think after reading the core Schematron docs this Saxon flag controls whether we will see diagnostics output at build-time or runtime when we do the processing, as source explains here and an example use here.

We are not making use of it now, but I think it will be useful for the future, so I add it in.

BASE_DIR="${BASE_DIR:-.}"

echo "using saxon version ${SAXON_VERSION}"
Expand Down
10 changes: 10 additions & 0 deletions resources/validations/lib/svrl2html.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
<li>Location: <xsl:value-of select="@location"/></li>
</ul>
</xsl:for-each>
<div>Reports: <xsl:value-of select="count(.//svrl:successful-report)"/></div>
<xsl:for-each select=".//svrl:successful-report">
<ul>
<li><b>Text: <xsl:value-of select="svrl:text"/></b></li>
<li>Test: <xsl:value-of select="@test"/></li>
<li>See: <xsl:value-of select="@see"/></li>
<li>Location: <xsl:value-of select="@location"/></li>
</ul>
</xsl:for-each>

</body>
</html>
</xsl:template>
Expand Down
283 changes: 216 additions & 67 deletions resources/validations/src/ssp.sch
Original file line number Diff line number Diff line change
@@ -1,86 +1,235 @@
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0">
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0"
xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0">

<sch:ns prefix="f" uri="https://fedramp.gov/ns/oscal"/>
<sch:ns prefix="o" uri="http://csrc.nist.gov/ns/oscal/1.0"/>
<sch:ns prefix="oscal" uri="http://csrc.nist.gov/ns/oscal/1.0"/>
<sch:ns prefix="lv" uri="local-validations"/>

<sch:title>FedRAMP System Security Plan Validations</sch:title>

<!--
Use XSL collection to load FedRAMP values, information types, and threats
from a known source in a relative path instead of hard-coding filenames.
All files are XML, but for future-proofing we filter to retrieve only XML
files.
-->

<!--
This workaround is only to allow XSpec to source the proper context for
XPath at the global level. We use XSpec for unit testing, and this is a
known issue with very well-documented work-arounds.

https://gitter.im/usnistgov-OSCAL/FedRAMP-10x-Schematron?at=5fa06e38f2fd4f60fc4ccec7

https://github.com/xspec/xspec/issues/873
https://github.com/xspec/xspec/issues/892
https://github.com/xspec/xspec/issues/1239

See the updated documentation below about the global-context-item pattern.

https://github.com/xspec/xspec/wiki/Writing-Scenarios/ec19017ab00d769b49786cb227e57eaa2e4ee2b2#global-context-item
https://github.com/AirQuick/xspec/tree/14ccd455a0e420c97903c06f0faea86719031044/tutorial/global-context-item

If not, you will definitely see this error like below when running the test suite.

XPDY0002 Finding root of root/key-name the context item is absent
-->
<xsl:param as="document-node(element(o:system-security-plan))" name="global-context-item" select="." />
<xsl:param name="fedramp-registry-href" select="'../../xml?select=*.xml'" />
<xsl:variable name="fedramp-registry" select="collection($fedramp-registry-href)"/>
<xsl:variable name="selected-sensitivty-level" select="$global-context-item/o:system-security-plan/o:system-characteristics/o:security-sensitivity-level"/>

<sch:let name="sensitivity-levels" value="$fedramp-registry/f:fedramp-values/f:value-set[@name='security-sensitivity-level']/f:allowed-values/f:enum/@value"/>
<sch:let name="implementation-statuses" value="$fedramp-registry/f:fedramp-values/f:value-set[@name='control-implementation-status']/f:allowed-values/f:enum/@value"/>

<xsl:variable name="profile-map">
<profile level="low" href="../../../baselines/xml/FedRAMP_LOW-baseline-resolved-profile_catalog.xml"/>
<profile level="moderate" href="../../../baselines/xml/FedRAMP_MODERATE-baseline-resolved-profile_catalog.xml"/>
<profile level="high" href="../../../baselines/xml/FedRAMP_HIGH-baseline-resolved-profile_catalog.xml"/>
</xsl:variable>

<xsl:key name="profile-lookup" match="profile" use="@level"/>
<xsl:variable name="selected-profile-href" select="key('profile-lookup', $selected-sensitivty-level, $profile-map)/@href"/>
<xsl:variable name="selected-profile" select="doc(resolve-uri($selected-profile-href))"/>
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>

<sch:pattern>
<sch:rule context="/">
<sch:assert role="error" id="no-fedramp-registry-values" test="exists($fedramp-registry/f:fedramp-values)">The FedRAMP Registry values are not present, this configuration is invalid.</sch:assert>
</sch:rule>
</sch:pattern>
<xsl:param name="registry-href" select="'../../xml?select=*.xml'"/>

<xsl:function name="lv:if-empty-default">
<xsl:param name="item"/>
<xsl:param name="default"/>
<xsl:choose>
<!-- Atomic types, integers, strings, et cetera. -->
<xsl:when test="$item instance of xs:untypedAtomic or
$item instance of xs:anyURI or
$item instance of xs:string or
$item instance of xs:QName or
$item instance of xs:boolean or
$item instance of xs:base64Binary or
$item instance of xs:hexBinary or
$item instance of xs:integer or
$item instance of xs:decimal or
$item instance of xs:float or
$item instance of xs:double or
$item instance of xs:date or
$item instance of xs:time or
$item instance of xs:dateTime or
$item instance of xs:dayTimeDuration or
$item instance of xs:yearMonthDuration or
$item instance of xs:duration or
$item instance of xs:gMonth or
$item instance of xs:gYear or
$item instance of xs:gYearMonth or
$item instance of xs:gDay or
$item instance of xs:gMonthDay">
<xsl:value-of select="if ($item => string() => normalize-space() = '') then $default else $item"/>
</xsl:when>
<!-- Any node-kind that can be a sequence type -->
<xsl:when test="$item instance of element() or
$item instance of attribute() or
$item instance of text() or
$item instance of node() or
$item instance of document-node() or
$item instance of comment() or
$item instance of processing-instruction()">
<xsl:sequence select="if ($item => normalize-space() => not()) then $default else $item"/>
</xsl:when>
<xsl:otherwise>
<!--
If no suitable type found, return empty sequence, as that can
be falsey and cast to empty string or checked for `not(exist(.))`
later.
-->
<xsl:sequence select="()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="lv:registry" as="item()*">
<xsl:param name="href"/>
<xsl:variable name="collection" select="$href => collection()"/>
<xsl:choose>
<xsl:when test="$collection => exists()">
<xsl:sequence select="$collection"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence>
<fedramp-values xmlns="https://fedramp.gov/ns/oscal"/>
<fedramp-threats xmlns="https://fedramp.gov/ns/oscal"/>
<information-types/>
</xsl:sequence>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="lv:sensitivity-level" as="xs:string">
<xsl:param name="context" as="node()*"/>
<xsl:value-of select="$context//o:security-sensitivity-level"/>
</xsl:function>

<xsl:function name="lv:profile" as="document-node()*">
<xsl:param name="level" />
<xsl:variable name="profile-map">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be gathered from an array of keys (low, moderate, high) and parameterized into the URL following a convention? Is that more trouble than it's worth?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in our pairing for this after formal review, the deal-breaker here is you cannot have <xsl:key/> in any context other than the global context of a XSLT stylesheet (or as we have to deal with it, the Schematron top-level that becomes the global context of the resulting XSLT stylesheet. Wrapping these in a function to make testing work and better encapsulate context/state (I mean the term generally for when people look through this stuff again, so I do not mean <sch:rule context="..."/> specifically) comes at this cost.

I hope to revisit better encapsulation of global parameters again when xspec/xspec#1294 is merged and xspec/xspec#991 is worked on as well. I am trying to work with upstream devs to know if I can contribute here.

<profile level="low" href="../../../baselines/xml/FedRAMP_LOW-baseline-resolved-profile_catalog.xml"/>
<profile level="moderate" href="../../../baselines/xml/FedRAMP_MODERATE-baseline-resolved-profile_catalog.xml"/>
<profile level="high" href="../../../baselines/xml/FedRAMP_HIGH-baseline-resolved-profile_catalog.xml"/>
</xsl:variable>
<xsl:variable name="href" select="$profile-map/profile[@level=$level]/@href"/>
<xsl:sequence select="doc(resolve-uri($href))"/>
</xsl:function>

<xsl:function name="lv:correct">
<xsl:param name="value-set" as="element()*"/>
<xsl:param name="value" as="node()*"/>
<xsl:variable name="values" select="$value-set/f:allowed-values/f:enum/@value"/>
<xsl:choose>
<!-- If allow-other is set, anything is valid. -->
<xsl:when test="$value-set/f:allowed-values/@allow-other='no' and $value = $values"/>
<xsl:otherwise>
<xsl:value-of select="$values" separator=", "/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="lv:analyze">
<xsl:param name="value-set" as="element()*"/>
<xsl:param name="element" as="element()*"/>
<xsl:choose>
<xsl:when test="$value-set/f:allowed-values/f:enum/@value">
<xsl:sequence>
<xsl:call-template name="analysis-template">
<xsl:with-param name="value-set" select="$value-set"/>
<xsl:with-param name="element" select="$element"/>
</xsl:call-template>
</xsl:sequence>
</xsl:when>
<xsl:otherwise>
<xsl:message expand-text="yes">error</xsl:message>
<xsl:sequence>
<xsl:call-template name="analysis-template">
<xsl:with-param name="value-set" select="$value-set"/>
<xsl:with-param name="element" select="$element"/>
<xsl:with-param name="errors">
<error>value-set was malformed</error>
</xsl:with-param>
</xsl:call-template>
</xsl:sequence>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="lv:report" as="xs:string">
<xsl:param name="analysis" as="element()*"/>
<xsl:variable name="results" as="xs:string">
<xsl:call-template name="report-template">
<xsl:with-param name="analysis" select="$analysis"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$results"/>
</xsl:function>

<xsl:template name="analysis-template" as="element()">
<xsl:param name="value-set" as="element()*"/>
<xsl:param name="element" as="element()*"/>
<xsl:param name="errors" as="node()*"/>
<xsl:variable name="ok-values" select="$value-set/f:allowed-values/f:enum/@value"/>
<analysis>
<errors>
<xsl:if test="$errors"><xsl:sequence select="$errors"/></xsl:if>
</errors>
<reports
name="{$value-set/@name}"
formal-name="{$value-set/f:formal-name}"
description="{$value-set/f:description}"
count="{count($element)}">
<xsl:for-each select="$ok-values">
<xsl:variable name="match" select="$element[@value=current()]"/>
<report value="{current()}" count="{count($match)}">
</report>
</xsl:for-each>
</reports>
</analysis>
</xsl:template>

<xsl:template name="report-template" as="xs:string">
<xsl:param name="analysis" as="element()*"/>
<xsl:value-of>
There are <xsl:value-of select="$analysis/reports/@count"/>&#xA0;<xsl:value-of select="$analysis/reports/@formal-name"/>
<xsl:choose>
<xsl:when test="$analysis/reports/report"> items total, with </xsl:when>
<xsl:otherwise> items total. </xsl:otherwise>
</xsl:choose>
<xsl:for-each select="$analysis/reports/report">
<xsl:if test="position() gt 1 and not(position() eq last())">
<xsl:value-of select="current()/@count"/> set as <xsl:value-of select="current()/@value"/>, </xsl:if>
<xsl:if test="position() gt 1 and position() eq last()"
> and <xsl:value-of select="current()/@count"/> set as <xsl:value-of select="current()/@value"/>.</xsl:if>
<xsl:sequence select="."/>
</xsl:for-each>
There are <xsl:value-of select="($analysis/reports/@count - sum($analysis/reports/report/@count))"/> invalid items.
<xsl:if test="count($analysis/errors/error) > 0">
<xsl:message expand-text="yes">hit error block</xsl:message>
<xsl:for-each select="$analysis/errors/error">
Also, <xsl:value-of select="current()/text()"/>, so analysis could be inaccurate or it completely failed.
</xsl:for-each>
</xsl:if>
</xsl:value-of>
</xsl:template>

<sch:pattern>
<sch:rule context="/o:system-security-plan/o:system-characteristics/o:security-sensitivity-level">
<sch:assert id="no-security-sensitivity-level" test="$selected-sensitivty-level">No sensitivty level found from XPath query.</sch:assert>
<sch:assert id="invalid-security-sensitivity-level" test=". = $sensitivity-levels"><sch:value-of select="./name()"/> is an invalid value <sch:value-of select="."/>, not from <sch:value-of select="$sensitivity-levels" /></sch:assert>
</sch:rule>
</sch:pattern>
<sch:pattern>
<sch:rule context="/o:system-security-plan">
<sch:let name="all" value="o:control-implementation/o:implemented-requirement[o:annotation[@name='implementation-status']]"/>
<sch:let name="planned" value="o:control-implementation/o:implemented-requirement[o:annotation[@name='implementation-status' and @value='planned']]"/>
<sch:let name="partial" value="o:control-implementation/o:implemented-requirement[o:annotation[@name='implementation-status' and @value='partial']]"/>
<sch:assert id="invalid-implemented-requirements-count" test="count($all) > 0">There are no control implementations with statuses set.</sch:assert>
<sch:report id="partial-requirements-report" test="true()">There are <sch:value-of select="count($partial)"/> partial<sch:value-of select="if (count($partial)=1) then ' control implementation' else ' control implementations'"/>.</sch:report>
<sch:report id="planned-requirements-report" test="true()">There are <sch:value-of select="count($planned)"/> planned<sch:value-of select="if (count($planned)=1) then ' control implementation' else ' control implementations'"/>.</sch:report>
<sch:report id="all-requirements-report" test="true()">There are <sch:value-of select="count($all)"/> total<sch:value-of select="if (count($all)=1) then ' control implementation' else ' control implementations'"/>.</sch:report>
<sch:let name="registry" value="$registry-href => lv:registry()"/>
<sch:let name="ok-values" value="$registry/f:fedramp-values/f:value-set[@name='security-sensitivity-level']"/>
<sch:let name="sensitivity-level" value="/ => lv:sensitivity-level() => lv:if-empty-default('')"/>
<sch:let name="corrections" value="lv:correct($ok-values, $sensitivity-level)"/>
<sch:assert role="fatal" id="no-registry-values" test="count($registry/f:fedramp-values/f:value-set) > 0"
>The registry values at the path '<sch:value-of select="$registry-href"/>' are not present, this configuration is invalid.</sch:assert>
<sch:assert role="fatal" id="no-security-sensitivity-level" test="$sensitivity-level != ''">No sensitivty level found, no more validation processing can occur.</sch:assert>
<sch:assert role="fatal" id="invalid-security-sensitivity-level" test="empty($ok-values) or not(exists($corrections))"
><sch:value-of select="./name()"/> is an invalid value of '<sch:value-of select="lv:sensitivity-level(/)"/>', not an allowed value of <sch:value-of select="$corrections"/>. No more validation processing can occur.
</sch:assert>
</sch:rule>

<sch:rule context="/o:system-security-plan/o:control-implementation">
<sch:let name="required" value="$selected-profile/*//o:control"/>
<sch:let name="registry" value="$registry-href => lv:registry()"/>
<sch:let name="sensitivity-level" value="/ => lv:sensitivity-level()"/>
<sch:let name="ok-values" value="$registry/f:fedramp-values/f:value-set[@name='control-implementation-status']"/>
<sch:let name="selected-profile" value="$sensitivity-level => lv:profile()"/>
<sch:let name="required-controls" value="$selected-profile/*//o:control"/>
<sch:let name="implemented" value="o:implemented-requirement"/>
<sch:let name="missing" value="$required[not(@id = $implemented/@control-id)]"/>
<sch:report id="each-required-control-report" test="true()">The following <sch:value-of select="count($required)"/><sch:value-of select="if (count($required)=1) then ' control' else ' controls'"/> are required: <sch:value-of select="$required/@id"/></sch:report>
<sch:assert id="incomplete-implementation-requirements" test="true()">This SSP has not implemented <sch:value-of select="count($missing)"/><sch:value-of select="if (count($missing)=1) then ' control' else ' controls'"/>: <sch:value-of select="$missing/@id"/></sch:assert>
<sch:let name="missing" value="$required-controls[not(@id = $implemented/@control-id)]"/>
<sch:report id="each-required-control-report" test="count($required-controls) > 0">The following <sch:value-of select="count($required-controls)"/><sch:value-of select="if (count($required-controls)=1) then ' control' else ' controls'"/> are required: <sch:value-of select="$required-controls/@id"/></sch:report>
<sch:assert id="incomplete-implementation-requirements" test="not(exists($missing))">This SSP has not implemented <sch:value-of select="count($missing)"/><sch:value-of select="if (count($missing)=1) then ' control' else ' controls'"/>: <sch:value-of select="$missing/@id"/></sch:assert>
<sch:let name="results" value="$ok-values => lv:analyze(//o:implemented-requirement/o:annotation[@name='implementation-status'])"/>
<sch:let name="total" value="$results/reports/@count"/>
<sch:report id="control-implemented-requirements-stats" test="count($results/errors/error) = 0"><sch:value-of select="$results => lv:report() => normalize-space()"/></sch:report>
</sch:rule>

<sch:rule context="/o:system-security-plan/o:control-implementation/o:implemented-requirement">
<sch:let name="registry" value="$registry-href => lv:registry()"/>
<sch:let name="status" value="./o:annotation[@name='implementation-status']/@value"/>
<sch:let name="corrections" value="lv:correct($registry/f:fedramp-values/f:value-set[@name='control-implementation-status'], $status)"/>
<sch:assert id="invalid-implementation-status" test="not(exists($corrections))">Invalid status '<sch:value-of select="$status"/>' for <sch:value-of select="./@control-id"/>, must be <sch:value-of select="$corrections"/></sch:assert>
</sch:rule>

</sch:pattern>
</sch:schema>
Loading