Skip to content

trestle.core.oscal_backward_compatibility

trestle.core.oscal_backward_compatibility ¤

Pre-parse OSCAL backward-compatibility checker.

Inspects the raw JSON dict of an OSCAL document before Pydantic parsing and reports constructs that are incompatible with OSCAL 1.2.x. This allows trestle to emit clear, actionable error messages instead of cryptic Pydantic validation errors when older documents are loaded.

Attributes¤

logger = logging.getLogger(__name__) module-attribute ¤

Classes¤

CompatIssue ¤

Bases: NamedTuple


              flowchart TD
              trestle.core.oscal_backward_compatibility.CompatIssue[CompatIssue]

              

              click trestle.core.oscal_backward_compatibility.CompatIssue href "" "trestle.core.oscal_backward_compatibility.CompatIssue"
            

A single backward-compatibility issue found in a raw OSCAL document.

Source code in trestle/core/oscal_backward_compatibility.py
43
44
45
46
47
48
49
50
51
class CompatIssue(NamedTuple):
    """A single backward-compatibility issue found in a raw OSCAL document."""

    field_path: str
    message: str
    severity: str  # 'ERROR' or 'WARNING'

    def __str__(self) -> str:
        return f'COMPAT {self.severity}: {self.field_path} — {self.message}'
Attributes¤
field_path instance-attribute ¤
message instance-attribute ¤
severity instance-attribute ¤
Methods:¤
__str__() ¤
Source code in trestle/core/oscal_backward_compatibility.py
50
51
def __str__(self) -> str:
    return f'COMPAT {self.severity}: {self.field_path} — {self.message}'

Functions:¤

check_file(path) ¤

Load a JSON file and run the backward-compatibility checks on it.

Parameters:

Name Type Description Default
path Path

Path to a JSON OSCAL document.

required

Returns:

Type Description
List[CompatIssue]

A list of CompatIssue. Empty list means no issues detected.

List[CompatIssue]

Returns an empty list if the file cannot be read or is not valid JSON —

List[CompatIssue]

the subsequent model load will fail and report the error.

Source code in trestle/core/oscal_backward_compatibility.py
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
def check_file(path: pathlib.Path) -> List[CompatIssue]:
    """Load a JSON file and run the backward-compatibility checks on it.

    Args:
        path: Path to a JSON OSCAL document.

    Returns:
        A list of CompatIssue.  Empty list means no issues detected.
        Returns an empty list if the file cannot be read or is not valid JSON —
        the subsequent model load will fail and report the error.
    """
    try:
        raw = json.loads(path.read_text(encoding='utf-8'))
    except Exception as e:
        logger.debug(f'Skipping compatibility checks for {path}: file could not be read or parsed as JSON: {e}')
        return []
    return check_raw_oscal(raw)

check_raw_oscal(raw) ¤

Inspect a raw OSCAL JSON dict and return all backward-compatibility issues.

Parameters:

Name Type Description Default
raw Dict[str, Any]

The top-level parsed JSON dict of an OSCAL document.

required

Returns:

Type Description
List[CompatIssue]

A list of CompatIssue. Empty list means no issues detected.

Source code in trestle/core/oscal_backward_compatibility.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def check_raw_oscal(raw: Dict[str, Any]) -> List[CompatIssue]:
    """Inspect a raw OSCAL JSON dict and return all backward-compatibility issues.

    Args:
        raw: The top-level parsed JSON dict of an OSCAL document.

    Returns:
        A list of CompatIssue.  Empty list means no issues detected.
    """
    issues: List[CompatIssue] = []

    # The top-level key is the document type, e.g. "profile", "catalog"
    if not raw or not isinstance(raw, dict) or len(raw) != 1:
        return issues
    doc_type = next(iter(raw))
    doc = raw[doc_type]

    # --- metadata datetime fields ---
    # Changed in OSCAL 1.2.0: these metadata timestamps require timezone-aware
    # values.
    metadata = doc.get('metadata', {})
    _check_aware_datetime(metadata, 'last-modified', f'{doc_type}.metadata.last-modified', issues)
    _check_aware_datetime(metadata, 'published', f'{doc_type}.metadata.published', issues)
    for i, rev in enumerate(metadata.get('revisions', [])):
        _check_aware_datetime(rev, 'last-modified', f'{doc_type}.metadata.revisions[{i}].last-modified', issues)
        _check_aware_datetime(rev, 'published', f'{doc_type}.metadata.revisions[{i}].published', issues)

    # --- profile-specific checks ---
    # These checks target profile constructs whose shape changed in OSCAL 1.2.0.
    if doc_type == 'profile':
        _check_profile(doc, issues)

    # --- oscal-version ---
    # Only warn about an out-of-range version when structural errors are already
    # present; a version mismatch on an otherwise-clean document is harmless.
    if any(i.severity == 'ERROR' for i in issues):
        _check_oscal_version(doc, issues)

    return issues

handler: python