API and report reference

Public Python API

from cwl_baseline import BaselineOptions, baseline, baseline_plugin

# previous and current are TranspilerContext objects supplied by the host.
report = baseline(previous, current, review_bump=None)

# Resolve previous through current.resolver and write a report.
baseline_plugin.execute(
    current,
    BaselineOptions(previous="release.cwl", output="baseline.json", check=True),
)

baseline(previous, current, *, review_bump=None) returns BaselineReport. baseline_plugin.execute(context, options) returns None and writes JSON. Both compare all Processes in the contexts, using each context's metadata.software_version.

Plugin options

Option Type / default Meaning
previous Nonempty string, required Passed unchanged to context.resolver.resolve()
output Path, baseline.json Destination; parent directory must exist
check Boolean, false Fail on unresolved review or insufficient version
review_bump patch, minor, major, or None Global classification of review findings

Unknown options are rejected. The runtime CLI uses --review-bump for review_bump and --check / --no-check for check.

Report fields

Field Meaning
schema_version Report format version, currently 1.0
previous_version, current_version Compared SemVer metadata
minimum_bump Maximum static floor: none, patch, minor, or major
minimum_version Previous version incremented once by that floor
suggested_version Version after review classification, or null while unresolved
review_required Whether any review remains unresolved
review_bump Supplied global classification, or null
declared_version_sufficient Review is resolved and current version meets the suggestion
findings Detailed changes contributing to the decision

Each finding contains rule, path, category, minimum_bump, review_required, message, before, after, before_present, and after_present. Categories are interface, environment, behavior, and metadata. Paths use JSON-Pointer-style escaping in the normalized model. Presence flags distinguish missing values from null. Original finding review flags remain true after global classification for audit.

The suggestion is a minimum release, not an instruction to downgrade an already higher version. See policy for aggregation, prereleases, and the meaning of unresolved review.

Errors

  • PluginFailureError: domain failures, including invalid version metadata, ambiguous/duplicate identities, unresolved review or insufficient version under check.
  • PluginExecutionError: unexpected resolver errors or report write failures.
  • Resolver PluginError exceptions propagate unchanged.
  • Invalid options raise Pydantic validation errors.

Check failures occur after the report is written. Failures during resolution or comparison may prevent a report from being produced.

Implementation reference

Compare all declared Processes, taking the maximum bump exactly once.

Source code in src/cwl_baseline/compare.py
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
def baseline(
    previous: TranspilerContext,
    current: TranspilerContext,
    *,
    review_bump: Literal["patch", "minor", "major"] | None = None,
) -> BaselineReport:
    """Compare all declared Processes, taking the maximum bump exactly once."""
    old_version = parse_version(previous.metadata.software_version, "Previous")
    new_version = parse_version(current.metadata.software_version, "Current")
    if old_version.prerelease is not None:
        raise PluginFailureError(
            "The comparison baseline must be a released version, not a prerelease."
        )
    old, new = document(previous), document(current)
    if not old:
        raise PluginFailureError("The previous document contains no Processes.")
    comparator = Comparator()
    for name in sorted(old.keys() | new.keys()):
        path = path_join("/processes", name)
        if name not in new:
            comparator.add(
                "process.removed",
                path,
                Bump.MAJOR,
                "Public Process removed.",
                old[name],
                MISSING,
            )
        elif name not in old:
            comparator.add(
                "process.added",
                path,
                Bump.MINOR,
                "Public Process added.",
                MISSING,
                new[name],
            )
        else:
            comparator.process(old[name], new[name], path)
    findings = comparator.findings
    minimum = max((Bump[f.minimum_bump.upper()] for f in findings), default=Bump.NONE)
    reviews = any(f.review_required for f in findings)
    effective = (
        max(minimum, Bump[review_bump.upper()]) if reviews and review_bump else minimum
    )
    unresolved = reviews and review_bump is None
    minimum_version = increment(old_version, minimum)
    suggestion = None if unresolved else increment(old_version, effective)
    return BaselineReport(
        previous_version=str(old_version),
        current_version=str(new_version),
        minimum_bump=minimum.label,
        minimum_version=str(minimum_version),
        suggested_version=str(suggestion) if suggestion is not None else None,
        review_required=unresolved,
        review_bump=review_bump,
        declared_version_sufficient=not unresolved
        and new_version >= (suggestion or minimum_version),
        findings=findings,
    )

Bases: BaseModel

Source code in src/cwl_baseline/plugin.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class BaselineOptions(BaseModel):
    model_config = ConfigDict(extra="forbid")

    previous: str = Field(
        min_length=1,
        description="Previous release location accepted by the context resolver",
    )
    output: Path = Field(
        default=Path("baseline.json"), description="JSON report destination"
    )
    check: bool = Field(
        default=False,
        description="Fail if review is unresolved or the declared version is insufficient",
    )
    review_bump: Literal["patch", "minor", "major"] | None = Field(
        default=None,
        description="Explicit classification of all changes requiring behavioral review",
    )

Bases: BaseModel

Source code in src/cwl_baseline/models.py
61
62
63
64
65
66
67
68
69
70
71
class BaselineReport(BaseModel):
    schema_version: str = "1.0"
    previous_version: str
    current_version: str
    minimum_bump: BumpName
    minimum_version: str
    suggested_version: str | None
    review_required: bool
    review_bump: Literal["patch", "minor", "major"] | None = None
    declared_version_sufficient: bool
    findings: list[Finding] = Field(default_factory=list)

Bases: BaseModel

Source code in src/cwl_baseline/models.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class Finding(BaseModel):
    model_config = ConfigDict(frozen=True)

    rule: str
    path: str
    category: FindingCategory
    minimum_bump: BumpName
    review_required: bool = False
    message: str
    before: Any = None
    after: Any = None
    # Presence flags distinguish an absent field from an explicit JSON null.
    before_present: bool = True
    after_present: bool = True