diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e59b716..3645340 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,9 +27,4 @@ repos: hooks: - id: conventional-pre-commit stages: [commit-msg] - args: [] - - - repo: https://github.com/google/osv-scanner - rev: v2.3.1 - hooks: - - id: osv-scanner \ No newline at end of file + args: [] \ No newline at end of file diff --git a/README.md b/README.md index 235fe26..91ef20a 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,11 @@ A framework for code security that provides abstractions for static analysis too **CodeSecTools** is a collection of scripts and wrappers that abstract external resources (such as SAST tools, datasets, and codebases), providing standardized interfaces to help them interact easily.
- CodeSecTools Overview + Workflow + Workflow
-For step-by-step instructions on installation, configuration, and basic usage, please refer to the [quick start guide](https://oppida.github.io/CodeSecTools/home/quick_start_guide.html). +For step-by-step instructions on installation, configuration, and basic usage, please refer to the [**quick start guide**](https://oppida.github.io/CodeSecTools/home/quick_start_guide.html). For more details on the design and integration of SAST tools and datasets in CodeSecTools, please refer to the [documentation](https://oppida.github.io/CodeSecTools). @@ -47,9 +48,9 @@ For more details on the design and integration of SAST tools and datasets in Cod |SAST Tool|Languages|Maintained|Included in Docker|Continuous Testing|Last Test Date| |:---:|:---:|:---:|:---:|:---:|:---:| -|Coverity|Java|⚠️
(Deprioritized)|❌|❌
(Proprietary)|October 2025| +|Coverity|C/C++, Java|✅|❌|❌
(Proprietary)|February 2026| |Semgrep Community Edition|C/C++, Java|✅|✅|✅|[Latest PR](https://github.com/OPPIDA/CodeSecTools/actions/workflows/ci.yaml)| -|Snyk Code|C/C++, Java|✅|❌|❌
(Rate limited)|November 2025| +|Snyk Code|C/C++, Java|✅|❌|❌
(Rate limited)|February 2026| |Bearer|Java|✅|✅|✅|[Latest PR](https://github.com/OPPIDA/CodeSecTools/actions/workflows/ci.yaml)| |SpotBugs|Java|✅|✅|✅|[Latest PR](https://github.com/OPPIDA/CodeSecTools/actions/workflows/ci.yaml)| |Cppcheck|C/C++|✅|✅|✅|[Latest PR](https://github.com/OPPIDA/CodeSecTools/actions/workflows/ci.yaml)| @@ -97,10 +98,10 @@ Mount necessary directories if you want to include: - a target (`-v ./myproject:/home/codesectools/myproject`) - existing CodeSecTools data (`-v $HOME/.codesectools:/home/codesectools/.codesectools`) -A better way is to use the CLI: +A simpler way is to use the CLI: ```bash -$ cstools -d docker --help +$ cstools docker --help Usage: cstools docker [OPTIONS] diff --git a/codesectools/datasets/core/dataset.py b/codesectools/datasets/core/dataset.py index f04d1de..b1eb003 100644 --- a/codesectools/datasets/core/dataset.py +++ b/codesectools/datasets/core/dataset.py @@ -641,7 +641,7 @@ def validate(self, analysis_results: list[AnalysisResult]) -> GitRepoDatasetData "fp_cwes": fp_cwes, "fn_cwes": fn_cwes, "time": analysis_result.time, - "loc": analysis_result.loc, + "lines_of_codes": analysis_result.lines_of_codes, } validated_repos.append(result) diff --git a/codesectools/sasts/all/cli.py b/codesectools/sasts/all/cli.py index 352191f..b9e62b5 100644 --- a/codesectools/sasts/all/cli.py +++ b/codesectools/sasts/all/cli.py @@ -177,7 +177,7 @@ def list_() -> None: "Dataset", ", ".join( f"[b]{sast.name}[/b]" - for sast in all_sast.sasts + for sast in all_sast.any_sasts if dataset_full_name in sast.list_results(dataset=True) ), ) @@ -187,7 +187,7 @@ def list_() -> None: "Project", ", ".join( f"[b]{sast.name}[/b]" - for sast in all_sast.sasts + for sast in all_sast.any_sasts if project in sast.list_results(project=True) ), ) diff --git a/codesectools/sasts/all/graphics.py b/codesectools/sasts/all/graphics.py index 4ae2409..13ba39e 100644 --- a/codesectools/sasts/all/graphics.py +++ b/codesectools/sasts/all/graphics.py @@ -15,7 +15,7 @@ class Graphics(CoreGraphics): project_name (str): The name of the project being visualized. all_sast (AllSAST): The instance managing all SAST tools. output_dir (Path): The directory containing the aggregated results. - color_mapping (dict): A dictionary mapping SAST tool names to colors. + sast_color (dict): A dictionary mapping SAST tool names to colors. sast_names (list[str]): A list of names of the SAST tools involved in the analysis. plot_functions (list): A list of methods responsible for generating plots. @@ -26,12 +26,12 @@ def __init__(self, project_name: str) -> None: self.project_name = project_name self.all_sast = AllSAST() self.output_dir = self.all_sast.output_dir / project_name - self.color_mapping = {} + self.sast_color = {} cmap = plt.get_cmap("Set2") self.sast_names = [] - for i, sast in enumerate(self.all_sast.sasts): + for i, sast in enumerate(self.all_sast.partial_sasts): if self.project_name in sast.list_results(project=True): - self.color_mapping[sast.name] = cmap(i) + self.sast_color[sast.name] = cmap(i) self.sast_names.append(sast.name) self.plot_functions = [] @@ -49,11 +49,11 @@ def __init__(self, project_name: str) -> None: ) def plot_overview(self) -> Figure: - """Generate an overview plot with stats by files, SAST tools, and categories.""" + """Generate an overview plot with stats by files, SAST tools, and levels.""" fig, (ax1, ax2, ax3) = plt.subplots(1, 3, layout="constrained") by_files = self.result.stats_by_files() by_sasts = self.result.stats_by_sasts() - by_categories = self.result.stats_by_categories() + by_levels = self.result.stats_by_levels() # Plot by files X_files, Y_files = [], [] @@ -64,10 +64,10 @@ def plot_overview(self) -> Figure: X_files.append(shorten_path(k)) Y_files.append(v["count"]) - COLORS_COUNT = {v: 0 for k, v in self.color_mapping.items()} + COLORS_COUNT = {v: 0 for k, v in self.sast_color.items()} - for sast in v["sasts"]: - color = self.color_mapping[sast] + for sast_name in v["sasts"]: + color = self.sast_color[sast_name] COLORS_COUNT[color] += 1 bars = [] @@ -95,41 +95,41 @@ def plot_overview(self) -> Figure: ax2.bar( X_sasts, Y_checkers, - color=[self.color_mapping[s] for s in X_sasts], + color=[self.sast_color[s] for s in X_sasts], ) ax2.set_xticks(X_sasts, X_sasts, rotation=45, ha="right") ax2.set_title("Stats by SAST tools") - # Plot by categories - X_categories = ["HIGH", "MEDIUM", "LOW"] - for category in X_categories: - if not by_categories.get(category): + # Plot by levels + X_levels = ["error", "warning", "note", "none"] + for level in X_levels: + if not by_levels.get(level): continue - sast_counts = by_categories[category]["sast_counts"] + sast_counts = by_levels[level]["sast_counts"] bars = [] current_height = 0 for sast_name, count in sorted(sast_counts.items()): - color = self.color_mapping[sast_name] + color = self.sast_color[sast_name] height = count if height > 0: - bars.append((category, current_height + height, color)) + bars.append((level, current_height + height, color)) current_height += height - for category_name, height, color in bars[::-1]: - ax3.bar(category_name, height, color=color) + for level_name, height, color in bars[::-1]: + ax3.bar(level_name, height, color=color) - ax3.set_xticks(X_categories, X_categories, rotation=45, ha="right") - ax3.set_title("Stats by categories") + ax3.set_xticks(X_levels, X_levels, rotation=45, ha="right") + ax3.set_title("Stats by levels") fig.suptitle( f"Project {self.project_name}, {len(self.result.files)} files analyzed, {len(self.result.defects)} defects raised", fontsize=16, ) - labels = list(self.color_mapping.keys()) + labels = list(self.sast_color.keys()) handles = [ - plt.Rectangle((0, 0), 1, 1, color=self.color_mapping[label]) + plt.Rectangle((0, 0), 1, 1, color=self.sast_color[label]) for label in labels ] plt.legend(handles, labels) @@ -160,7 +160,7 @@ def plot_top_cwes(self) -> Figure: sast_counts, bottom=bottoms, label=sast_name, - color=self.color_mapping.get(sast_name), + color=self.sast_color.get(sast_name), ) bottoms = [b + c for b, c in zip(bottoms, sast_counts, strict=False)] diff --git a/codesectools/sasts/all/parser.py b/codesectools/sasts/all/parser.py index 2c3641d..1120efc 100644 --- a/codesectools/sasts/all/parser.py +++ b/codesectools/sasts/all/parser.py @@ -14,7 +14,13 @@ class AllSASTAnalysisResult: """Represent the aggregated results from multiple SAST analyses on a single project.""" def __init__(self, name: str, analysis_results: dict[str, AnalysisResult]) -> None: - """Initialize an AllSASTAnalysisResult instance.""" + """Initialize an AllSASTAnalysisResult instance. + + Args: + name: The name of the project. + analysis_results: A dictionary of analysis results from various SAST tools. + + """ self.name = name self.source_path = None self.analysis_results = analysis_results @@ -34,22 +40,6 @@ def __init__(self, name: str, analysis_results: dict[str, AnalysisResult]) -> No self.files |= set(analysis_result.files) self.defects += analysis_result.defects - self.category_mapping = {} - for sast_name in self.sast_names: - sast = SASTS_ALL[sast_name]["sast"] - for category_name, color in sast.color_mapping.items(): - if color.lower() == "red": - self.category_mapping[(sast_name, category_name)] = "HIGH" - elif color.lower() == "orange": - self.category_mapping[(sast_name, category_name)] = "MEDIUM" - elif color.lower() == "yellow": - self.category_mapping[(sast_name, category_name)] = "LOW" - - for defect in self.defects: - defect.category = self.category_mapping.get( - (defect.sast, defect.category), "LOW" - ) - def __repr__(self) -> str: """Return a developer-friendly string representation of the aggregated result.""" return f"""{self.__class__.__name__}( @@ -78,9 +68,9 @@ def stats_by_files(self) -> dict: stats = {} for defect in self.defects: if defect.filepath_str not in stats.keys(): - stats[defect.filepath_str] = {"count": 1, "sasts": [defect.sast]} + stats[defect.filepath_str] = {"count": 1, "sasts": [defect.sast_name]} else: - stats[defect.filepath_str]["sasts"].append(defect.sast) + stats[defect.filepath_str]["sasts"].append(defect.sast_name) stats[defect.filepath_str]["count"] += 1 return stats @@ -89,23 +79,23 @@ def stats_by_sasts(self) -> dict: """Calculate statistics on defects, grouped by SAST tool.""" stats = {} for defect in self.defects: - if defect.sast not in stats.keys(): - stats[defect.sast] = {"count": 1} + if defect.sast_name not in stats.keys(): + stats[defect.sast_name] = {"count": 1} else: - stats[defect.sast]["count"] += 1 + stats[defect.sast_name]["count"] += 1 return stats - def stats_by_categories(self) -> dict: - """Calculate statistics on defects, grouped by severity category.""" + def stats_by_levels(self) -> dict: + """Calculate statistics on defects, grouped by severity level.""" stats = {} for defect in self.defects: - if defect.category not in stats.keys(): - stats[defect.category] = {"count": 0, "sast_counts": {}} + if defect.level not in stats.keys(): + stats[defect.level] = {"count": 0, "sast_counts": {}} - stats[defect.category]["count"] += 1 - sast_counts = stats[defect.category]["sast_counts"] - sast_counts[defect.sast] = sast_counts.get(defect.sast, 0) + 1 + stats[defect.level]["count"] += 1 + sast_counts = stats[defect.level]["sast_counts"] + sast_counts[defect.sast_name] = sast_counts.get(defect.sast_name, 0) + 1 return stats def stats_by_cwes(self) -> dict: @@ -119,14 +109,14 @@ def stats_by_cwes(self) -> dict: stats[defect.cwe] = { "count": 1, "files": [defect.filepath_str], - "sast_counts": {defect.sast: 1}, + "sast_counts": {defect.sast_name: 1}, } else: stats[defect.cwe]["count"] += 1 if defect.filepath_str not in stats[defect.cwe]["files"]: stats[defect.cwe]["files"].append(defect.filepath_str) - stats[defect.cwe]["sast_counts"][defect.sast] = ( - stats[defect.cwe]["sast_counts"].get(defect.sast, 0) + 1 + stats[defect.cwe]["sast_counts"][defect.sast_name] = ( + stats[defect.cwe]["sast_counts"].get(defect.sast_name, 0) + 1 ) return stats @@ -144,7 +134,7 @@ def stats_by_scores(self) -> dict: defects_same_cwe = 0 for cwe in defects_cwes: - cwes_sasts = {d.sast for d in defects if d.cwe == cwe} + cwes_sasts = {d.sast_name for d in defects if d.cwe == cwe} if set(self.sast_names) == cwes_sasts: defects_same_cwe += 1 else: @@ -162,7 +152,7 @@ def stats_by_scores(self) -> dict: defects_same_location = 0 defects_same_location_same_cwe = 0 for _, defects_ in defect_locations.items(): - if set(defect.sast for defect in defects_) == set(self.sast_names): + if set(defect.sast_name for defect in defects_) == set(self.sast_names): defects_same_location += 1 defects_by_cwe = {} for defect in defects_: @@ -171,14 +161,14 @@ def stats_by_scores(self) -> dict: defects_by_cwe[defect.cwe].append(defect) for _, defects_ in defects_by_cwe.items(): - if set(defect.sast for defect in defects_) == set( + if set(defect.sast_name for defect in defects_) == set( self.sast_names ): defects_same_location_same_cwe += 1 else: defects_same_location_same_cwe += ( len( - set(defect.sast for defect in defects_) + set(defect.sast_name for defect in defects_) & set(self.sast_names) ) - 1 @@ -224,7 +214,7 @@ def prepare_report_data(self) -> dict: for group in group_successive(defect.lines): start, end = group[0], group[-1] locations.append( - (defect.sast, defect.cwe, defect.message, (start, end)) + (defect.sast_name, defect.cwe, defect.message, (start, end)) ) report["files"][defect_file] = { @@ -239,7 +229,7 @@ def prepare_report_data(self) -> dict: k: v for k, v in sorted( report["files"].items(), - key=lambda item: (sum(v for v in item[1]["score"].values())), + key=lambda item: sum(v for v in item[1]["score"].values()), reverse=True, ) } diff --git a/codesectools/sasts/all/report.py b/codesectools/sasts/all/report.py index 00a1294..3476bc3 100644 --- a/codesectools/sasts/all/report.py +++ b/codesectools/sasts/all/report.py @@ -139,7 +139,7 @@ def generate_single_defect(self, file_data: dict) -> tuple: else "None" ) rows.append( - (start, shortcut, defect.sast, cwe_link, defect.message) + (start, shortcut, defect.sast_name, cwe_link, defect.message) ) else: cwe_link = ( diff --git a/codesectools/sasts/all/sast.py b/codesectools/sasts/all/sast.py index f698839..4eeda09 100644 --- a/codesectools/sasts/all/sast.py +++ b/codesectools/sasts/all/sast.py @@ -19,15 +19,24 @@ class AllSAST: def __init__(self) -> None: """Initialize the AllSAST instance.""" self.output_dir = USER_OUTPUT_DIR / self.name - self.sasts: list[SAST] = [] + self.full_sasts: list[SAST] = [] + self.partial_sasts: list[SAST] = [] + self.any_sasts: list[SAST] = [] for _, sast_data in SASTS_ALL.items(): if sast_data["status"] == "full": - self.sasts.append(sast_data["sast"]()) + self.full_sasts.append(sast_data["sast"]()) + self.partial_sasts.append(sast_data["sast"]()) + self.any_sasts.append(sast_data["sast"]()) + elif sast_data["status"] == "partial": + self.partial_sasts.append(sast_data["sast"]()) + self.any_sasts.append(sast_data["sast"]()) + else: + self.any_sasts.append(sast_data["sast"]()) self.sasts_by_lang = {} self.sasts_by_dataset = {} - for sast in self.sasts: + for sast in self.full_sasts: for lang in sast.supported_languages: if self.sasts_by_lang.get(lang): self.sasts_by_lang[lang].append(sast) @@ -45,7 +54,7 @@ def list_results( ) -> set[str]: """List the names of analysis results common to all enabled SAST tools.""" output_dirs = set() - for sast in self.sasts: + for sast in self.partial_sasts: if not output_dirs: output_dirs = set( sast.list_results(project=project, dataset=dataset, limit=limit) diff --git a/codesectools/sasts/core/graphics.py b/codesectools/sasts/core/graphics.py index 841e0f8..6279410 100644 --- a/codesectools/sasts/core/graphics.py +++ b/codesectools/sasts/core/graphics.py @@ -26,7 +26,7 @@ class Graphics: filetypes (dict[str, str]): A mapping of file extensions to matplotlib backends. sast (SAST): The SAST tool instance associated with the graphics. output_dir (Path): The directory where the analysis results are stored. - color_mapping (dict): A dictionary mapping categories to colors for plots. + level_color_map (dict): A dictionary mapping levels to colors for plots. plot_functions (list): A list of methods responsible for generating plots. """ @@ -45,8 +45,7 @@ def __init__(self, sast: SAST, project_name: str) -> None: """ self.sast = sast self.output_dir = sast.output_dir / project_name - self.color_mapping = sast.color_mapping - self.color_mapping["NONE"] = "BLACK" + self.level_color_map = sast.parser.level_color_map self.plot_functions = [] def export(self, overwrite: bool, format: str) -> None: @@ -104,20 +103,20 @@ def __init__(self, sast: SAST, project_name: str) -> None: self.result = sast.parser.load_from_output_dir(self.output_dir) self.plot_functions.extend([self.plot_overview]) - def checker_to_category(self, checker: str) -> str: - """Map a checker name to its category. + def checker_to_level(self, checker: str) -> str: + """Map a checker name to its level. Args: checker: The name of the checker. Returns: - The category string for the checker, or "NONE" if not found. + The level string for the checker. """ - return self.result.checker_to_category(checker) + return self.result.checker_to_level(checker) def plot_overview(self) -> Figure: - """Generate an overview plot with stats by files, checkers, and categories. + """Generate an overview plot with stats by files, checkers, and levels. Returns: A Matplotlib Figure object containing the plots. @@ -128,7 +127,7 @@ def plot_overview(self) -> Figure: fig, (ax1, ax2, ax3) = plt.subplots(1, 3, layout="constrained") by_files = self.result.stats_by_files() by_checkers = self.result.stats_by_checkers() - by_categories = self.result.stats_by_categories() + by_levels = self.result.stats_by_levels() # Plot by files X_files, Y_files = [], [] @@ -139,11 +138,11 @@ def plot_overview(self) -> Figure: X_files.append(shorten_path(k)) Y_files.append(v["count"]) - COLORS_COUNT = {v: 0 for k, v in self.color_mapping.items()} + COLORS_COUNT = {v: 0 for k, v in self.level_color_map.items()} for checker in v["checkers"]: - category = self.checker_to_category(checker) - color = self.color_mapping[category] + level = self.checker_to_level(checker) + color = self.level_color_map[level] COLORS_COUNT[color] += 1 bars = [] @@ -171,36 +170,36 @@ def plot_overview(self) -> Figure: ax2.bar( X_checkers, Y_checkers, - color=[self.color_mapping[self.checker_to_category(c)] for c in X_checkers], + color=[self.level_color_map[self.checker_to_level(c)] for c in X_checkers], ) ax2.set_xticks(X_checkers, X_checkers, rotation=45, ha="right") ax2.set_title(f"Stats by checkers (limit to {self.limit})") - # Plot by categories - X_categories, Y_categories = [], [] - sorted_categories = sorted( - list(by_categories.items()), - key=lambda e: list(self.sast.color_mapping.keys()).index(e[0]), + # Plot by levels + X_levels, Y_levels = [], [] + sorted_levels = sorted( + list(by_levels.items()), + key=lambda e: list(self.level_color_map.keys()).index(e[0]), ) - for k, v in sorted_categories[: self.limit]: - X_categories.append(k) - Y_categories.append(v["count"]) + for k, v in sorted_levels[: self.limit]: + X_levels.append(k) + Y_levels.append(v["count"]) ax3.bar( - X_categories, - Y_categories, - color=[self.color_mapping[c] for c in X_categories], + X_levels, + Y_levels, + color=[self.level_color_map[c] for c in X_levels], ) - ax3.set_xticks(X_categories, X_categories, rotation=45, ha="right") - ax3.set_title(f"Stats by categories (limit to {self.limit})") + ax3.set_xticks(X_levels, X_levels, rotation=45, ha="right") + ax3.set_title(f"Stats by levels (limit to {self.limit})") fig.suptitle( f"Project {project_name}, {len(self.result.files)} files analyzed, {len(self.result.defects)} defects raised", fontsize=16, ) - labels = list(self.color_mapping.keys()) + labels = list(self.level_color_map.keys()) handles = [ - plt.Rectangle((0, 0), 1, 1, color=self.color_mapping[label]) + plt.Rectangle((0, 0), 1, 1, color=self.level_color_map[label]) for label in labels ] plt.legend(handles, labels) @@ -344,17 +343,17 @@ def __init__(self, sast: SAST, dataset: GitRepoDataset) -> None: ] ) - def checker_to_category(self, checker: str) -> str: - """Map a checker name to its category. + def checker_to_level(self, checker: str) -> str: + """Map a checker name to its level. Args: checker: The name of the checker. Returns: - The category string for the checker, or "NONE" if not found. + The level string for the checker, or "none" if not found. """ - return self.results[0].checker_to_category(checker) + return self.results[0].checker_to_level(checker) def plot_overview(self) -> Figure: """Generate an overview plot classifying defects. @@ -368,13 +367,14 @@ def plot_overview(self) -> Figure: set_names = ["tp_defects", "fp_defects"] X, Y = ["True Positives", "False Positives"], [0, 0] COLORS_COUNT = [ - {v: 0 for k, v in self.color_mapping.items()} for _ in range(len(set_names)) + {v: 0 for k, v in self.level_color_map.items()} + for _ in range(len(set_names)) ] for result in b.validated_repos: for i, name in enumerate(set_names): for defect in result[name]: Y[i] += 1 - color = self.color_mapping[defect.category] + color = self.level_color_map[defect.level] COLORS_COUNT[i][color] += 1 bars = [] @@ -399,14 +399,14 @@ def plot_overview(self) -> Figure: ) ax.set_yscale("log") - ax.set_title("Classification by checkers category") + ax.set_title("Classification by checkers level") fig.suptitle( f"Benchmark against {self.dataset.name} dataset", fontsize=16, ) - labels = list(self.color_mapping.keys()) + labels = list(self.level_color_map.keys()) handles = [ - plt.Rectangle((0, 0), 1, 1, color=self.color_mapping[label]) + plt.Rectangle((0, 0), 1, 1, color=self.level_color_map[label]) for label in labels ] plt.legend(handles, labels) @@ -506,8 +506,8 @@ def plot_defects_per_loc(self) -> Figure: X, Y = [], [] for result in b.validated_repos: defect_number = sum(len(result[name]) for name in set_names) - if result["loc"]: - X.append(result["loc"]) + if result["lines_of_codes"]: + X.append(result["lines_of_codes"]) Y.append(defect_number) ax.set_xscale("log") @@ -541,8 +541,8 @@ def plot_time_per_loc(self) -> Figure: X, Y = [], [] for result in b.validated_repos: - if result["loc"]: - X.append(result["loc"]) + if result["lines_of_codes"]: + X.append(result["lines_of_codes"]) Y.append(result["time"] / 60) ax.set_xscale("log") diff --git a/codesectools/sasts/core/parser.py b/codesectools/sasts/core/parser/__init__.py similarity index 69% rename from codesectools/sasts/core/parser.py rename to codesectools/sasts/core/parser/__init__.py index f78d089..d9d62d9 100644 --- a/codesectools/sasts/core/parser.py +++ b/codesectools/sasts/core/parser/__init__.py @@ -8,7 +8,7 @@ from abc import ABC, abstractmethod from pathlib import Path -from typing import Self +from typing import Literal, Self from codesectools.shared.cwe import CWE @@ -17,52 +17,50 @@ class Defect: """Represent a single defect or finding reported by a SAST tool. Attributes: - sast (str): The name of the SAST tool that reported the defect. filepath (Path): The path to the file where the defect was found. filepath_str (str): The string representation of the file path. filename (str): The name of the file. + sast_name (str): The name of the SAST tool that reported the defect. checker (str): The name of the checker or rule that reported the defect. - category (str): The category of the checker (e.g., security, performance). + level (Literal["none", "note", "warning", "error"]): The severity level of the defect. + level (str): The level of the checker (e.g., security, performance). cwe (CWE): The CWE associated with the defect. message (str): The description of the defect. - location (tuple[int, int] | None): A tuple with the start and end line numbers of the defect. - data (dict): Raw data from the SAST tool for this defect. + lines (list[int] | None): A list of line numbers where the defect is located. """ - sast: str - def __init__( self, + sast_name: str, filepath: Path, checker: str, - category: str, + level: Literal["none", "note", "warning", "error"], cwe: CWE, message: str, lines: list[int] | None, - data: dict, ) -> None: """Initialize a Defect instance. Args: + sast_name: The name of the SAST tool. filepath: The file path of the defect. checker: The name of the rule/checker. - category: The category of the checker. + level: The severity level of the defect. cwe: The CWE associated with the defect. message: The description of the defect. lines: A list of line numbers where the defect is located. - data: Raw data from the SAST tool for this defect. """ self.filepath = filepath self.filepath_str = str(filepath) self.filename = filepath.name + self.sast_name = sast_name self.checker = checker - self.category = category + self.level = level self.cwe = cwe self.message = message self.lines = lines - self.data = data def __repr__(self) -> str: """Return a developer-friendly string representation of the Defect. @@ -74,58 +72,69 @@ def __repr__(self) -> str: return f"""{self.__class__.__name__}( filepath: \t{self.filepath} checker: \t{self.checker} - category: \t{self.category} + level: \t{self.level} cwe: \t{self.cwe} )""" class AnalysisResult(ABC): - """Abstract base class for holding the parsed results of a SAST analysis. + """Abstract base class for a SAST tool's analysis result. + + This class provides a standardized structure for holding analysis data, + including defects, execution time, and language information. It also offers + methods for calculating statistics based on the results. Attributes: - name (str): The name of the analyzed project or dataset. - source_path (Path): The path to the analyzed source code. - lang (str): The primary programming language analyzed. - files (list[str]): A list of files that were analyzed. - defects (list[Defect]): A list of `Defect` objects found. - time (float): The duration of the analysis in seconds. - loc (int): The number of lines of code analyzed. - data (tuple): Raw data from the SAST tool's output. + sast_name (str): The name of the SAST tool that produced the result. + name (str): The name of the project or dataset analyzed. + source_path (Path): The path to the source code that was analyzed. + lang (str): The programming language of the source code. + files (list[str]): A list of file paths that were part of the analysis. + defects (list[Defect]): A list of `Defect` objects found during analysis. + time (float): The total time taken for the analysis in seconds. + lines_of_codes (int): The number of lines of code analyzed. """ + sast_name: str + level_color_map = { + "error": "red", + "warning": "orange", + "note": "yellow", + "none": "gray", + } + def __init__( self, name: str, source_path: Path, lang: str, - files: list[str], defects: list[Defect], time: float, - loc: int, - data: tuple, + lines_of_codes: int, ) -> None: """Initialize an AnalysisResult instance. Args: - name: The name of the analyzed project/dataset. + name: The name of the project or dataset. source_path: The path to the analyzed source code. - lang: The programming language of the code. - files: A list of analyzed files. - defects: A list of `Defect` objects. + lang: The programming language of the source code. + defects: A list of found defects. time: The analysis duration in seconds. - loc: The lines of code analyzed. - data: Raw data from the SAST tool's output. + lines_of_codes: The number of lines of code analyzed. """ self.name = name self.source_path = source_path self.lang = lang - self.files = files self.defects = defects self.time = time - self.loc = loc - self.data = data + self.lines_of_codes = lines_of_codes + + @property + def files(self) -> list[str]: + """Get the list of unique file paths containing defects.""" + return list(set(d.filepath_str for d in self.defects)) def __repr__(self) -> str: """Return a developer-friendly string representation of the AnalysisResult. @@ -176,20 +185,20 @@ def load_from_output_dirs(cls, output_dirs: list[Path]) -> list[Self]: analysis_results.append(cls.load_from_output_dir(output_dir)) return analysis_results - def checker_to_category(self, checker: str) -> str: - """Get the category for a given checker name. + def checker_to_level(self, checker: str) -> str: + """Map a checker name to its severity level. Args: checker: The name of the checker. Returns: - The category string, or "NONE" if not found. + The level string for the checker, or "none" if not found. """ for defect in self.defects: if checker == defect.checker: - return defect.category - return "NONE" + return defect.level + return "none" def stats_by_checkers(self) -> dict: """Calculate statistics on defects, grouped by checker. @@ -209,29 +218,27 @@ def stats_by_checkers(self) -> dict: return stats - def stats_by_categories(self) -> dict: - """Calculate statistics on defects, grouped by category. + def stats_by_levels(self) -> dict: + """Calculate statistics on defects, grouped by level. Returns: - A dictionary where keys are category names and values are dicts + A dictionary where keys are level names and values are dicts containing counts and checker lists. """ stats = {} for defect in self.defects: - if defect.category not in stats.keys(): - stats[defect.category] = { + if defect.level not in stats.keys(): + stats[defect.level] = { "count": 1, "checkers": [defect.checker], "unique": 1, } else: - stats[defect.category]["checkers"].append(defect.checker) - stats[defect.category]["count"] = len( - stats[defect.category]["checkers"] - ) - stats[defect.category]["unique"] = len( - set(stats[defect.category]["checkers"]) + stats[defect.level]["checkers"].append(defect.checker) + stats[defect.level]["count"] = len(stats[defect.level]["checkers"]) + stats[defect.level]["unique"] = len( + set(stats[defect.level]["checkers"]) ) return stats diff --git a/codesectools/sasts/core/parser/format/CoverityJsonOutputV10/__init__.py b/codesectools/sasts/core/parser/format/CoverityJsonOutputV10/__init__.py new file mode 100644 index 0000000..07a2058 --- /dev/null +++ b/codesectools/sasts/core/parser/format/CoverityJsonOutputV10/__init__.py @@ -0,0 +1,179 @@ +"""Coverity JSON Output V10 model.""" + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import BaseModel, Field + + +class Properties(BaseModel): + """Represent additional properties in a Coverity issue.""" + + fb_priority: Annotated[str | None, Field(alias="fb.priority")] = None + extra_properties: dict[str, Any] | None = None + + +class Triage(BaseModel): + """Represent triage information for a Coverity issue.""" + + classification: str | None = None + action: str | None = None + fix_target: Annotated[str | None, Field(alias="fixTarget")] = None + severity: str | None = None + legacy: str | None = None + owner: str | None = None + external_reference: Annotated[str | None, Field(alias="externalReference")] = None + + +class StateOnServer(BaseModel): + """Represent the state of a Coverity issue on the server.""" + + cid: int + present_in_reference_snapshot: Annotated[ + bool, Field(alias="presentInReferenceSnapshot") + ] + first_detected_date_time: Annotated[str, Field(alias="firstDetectedDateTime")] + stream: str + components: list[str] + triage: Triage | None = None + + +class Event(BaseModel): + """Represent an event in a Coverity issue's trace.""" + + cov_l_str_event_description: Annotated[ + str | None, Field(alias="covLStrEventDescription") + ] = None + event_description: Annotated[str, Field(alias="eventDescription")] + event_number: Annotated[int, Field(alias="eventNumber")] + event_tree_position: Annotated[str, Field(alias="eventTreePosition")] + event_set: Annotated[int, Field(alias="eventSet")] + event_tag: Annotated[str, Field(alias="eventTag")] + file_pathname: Annotated[str, Field(alias="filePathname")] + stripped_file_pathname: Annotated[str, Field(alias="strippedFilePathname")] + line_number: Annotated[int, Field(alias="lineNumber")] + column_number: Annotated[int | None, Field(alias="columnNumber")] = None + main: bool + more_information_id: Annotated[ + str | int | None, Field(alias="moreInformationId") + ] = None + remediation: bool = False + events: list[Event] | None = None + + +class CheckerProperties(BaseModel): + """Represent the properties of a Coverity checker.""" + + category: str + category_description: Annotated[str, Field(alias="categoryDescription")] + cwe_category: Annotated[str | int | None, Field(alias="cweCategory")] = None + weakness_id_category: Annotated[str | None, Field(alias="weaknessIdCategory")] = ( + None + ) + issue_kinds: Annotated[list[str], Field(alias="issueKinds")] + event_set_captions: Annotated[list[str], Field(alias="eventSetCaptions")] + impact: str + impact_description: Annotated[str, Field(alias="impactDescription")] + subcategory_local_effect: Annotated[ + str | None, Field(alias="subcategoryLocalEffect") + ] = None + subcategory_short_description: Annotated[ + str, Field(alias="subcategoryShortDescription") + ] + subcategory_long_description: Annotated[ + str, Field(alias="subcategoryLongDescription") + ] + subtype: str | None = None + security_kind: Annotated[bool | None, Field(alias="securityKind")] = None + quality_kind: Annotated[bool | None, Field(alias="qualityKind")] = None + test_kind: Annotated[bool | None, Field(alias="testKind")] = None + origin: str | None = None + + +class DesktopAnalysisSettings(BaseModel): + """Represent the settings used for a Coverity desktop analysis.""" + + analysis_date: Annotated[str, Field(alias="analysisDate")] + cmd_line_args: Annotated[list[str], Field(alias="cmdLineArgs")] + effective_strip_paths: Annotated[list[str], Field(alias="effectiveStripPaths")] + analysis_scope_pathnames: Annotated[ + list[str], Field(alias="analysisScopePathnames") + ] + stripped_analysis_scope_pathnames: Annotated[ + list[str], Field(alias="strippedAnalysisScopePathnames") + ] + intermediate_dir: Annotated[str | None, Field(alias="intermediateDir")] = None + reference_snapshot_details: Annotated[ + dict[str, Any] | None, Field(alias="referenceSnapshotDetails") + ] = None + portable_analysis_settings: Annotated[ + dict[str, Any] | None, Field(alias="portableAnalysisSettings") + ] = None + + +class Error(BaseModel): + """Represent an error or warning encountered during Coverity analysis.""" + + domain: str | None = None + code: str | None = None + message: str | None = None + file: str | None = None + line: int | None = None + + +class Issue(BaseModel): + """Represent a single issue found by Coverity.""" + + merge_key: Annotated[str, Field(alias="mergeKey")] + occurrence_count_for_mk: Annotated[int, Field(alias="occurrenceCountForMK")] + occurrence_number_in_mk: Annotated[int, Field(alias="occurrenceNumberInMK")] + reference_occurrence_count_for_mk: Annotated[ + int | None, Field(alias="referenceOccurrenceCountForMK") + ] = None + checker_name: Annotated[str, Field(alias="checkerName")] + subcategory: str + type: str + code_language: Annotated[str, Field(alias="code-language")] + extra: str | None = None + domain: str + language: str | None = None + main_event_file_pathname: Annotated[str, Field(alias="mainEventFilePathname")] + stripped_main_event_file_pathname: Annotated[ + str, Field(alias="strippedMainEventFilePathname") + ] + main_event_line_number: Annotated[int, Field(alias="mainEventLineNumber")] + main_event_column_number: Annotated[ + int | None, Field(alias="mainEventColumnNumber") + ] = None + properties: Properties | None = None + function_display_name: Annotated[str | None, Field(alias="functionDisplayName")] + function_mangled_name: Annotated[str | None, Field(alias="functionMangledName")] + function_html_display_name: Annotated[ + str | None, Field(alias="functionHtmlDisplayName") + ] + function_simple_name: Annotated[str | None, Field(alias="functionSimpleName")] + function_search_name: Annotated[str | None, Field(alias="functionSearchName")] + local_status: Annotated[str | None, Field(alias="localStatus")] = None + ordered: bool + events: list[Event] + state_on_server: Annotated[StateOnServer | None, Field(alias="stateOnServer")] = ( + None + ) + local_triage: Annotated[Triage | None, Field(alias="localTriage")] = None + checker_properties: Annotated[CheckerProperties, Field(alias="checkerProperties")] + subtype: str | None = None + + +class CoverityJsonOutputV10(BaseModel): + """Represent the root structure of a Coverity JSON V10 output file.""" + + type: str + format_version: Annotated[int, Field(alias="formatVersion")] + suppressed_issue_count: Annotated[int, Field(alias="suppressedIssueCount")] + issues: list[Issue] + desktop_analysis_settings: Annotated[ + DesktopAnalysisSettings | None, Field(alias="desktopAnalysisSettings") + ] = None + error: Error | None = None + warnings: list[Error] = [] diff --git a/codesectools/sasts/core/parser/format/README.md b/codesectools/sasts/core/parser/format/README.md new file mode 100644 index 0000000..1937c4e --- /dev/null +++ b/codesectools/sasts/core/parser/format/README.md @@ -0,0 +1,55 @@ +# SAST Tool Analysis Result Formats: Pydantic Model + +Generate a Pydantic model for a specific format using [datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator). + +## SARIF + +```bash +datamodel-codegen \ + --url https://raw.githubusercontent.com/microsoft/sarif-python-om/refs/heads/main/sarif-schema-2.1.0.json \ + --output SARIF.py \ + --input-file-type jsonschema \ + --output-model-type pydantic_v2.BaseModel \ + --target-pydantic-version 2.11 \ + --use-root-model-type-alias \ + --use-annotated \ + --snake-case-field \ + --use-schema-description \ + --use-standard-collections \ + --use-union-operator \ + --target-python-version 3.12 \ + --enum-field-as-literal all \ + --custom-file-header '"""Static Analysis Results Interchange Format (SARIF) Version 2.1.0 data model."""' + +ruff format SARIF.py +ruff check --unsafe-fixes --fix SARIF.py +ty check SARIF.py +``` + +## Coverity + +```bash +cov-format-errors --dir $PROJECT_DIR --json-output-v10 coverity.json +datamodel-codegen \ + --input coverity.json \ + --output CoverityJsonOutputV10.py \ + --input-file-type json \ + --output-model-type pydantic_v2.BaseModel \ + --target-pydantic-version 2.11 \ + --use-root-model-type-alias \ + --use-annotated \ + --snake-case-field \ + --use-schema-description \ + --use-standard-collections \ + --use-union-operator \ + --target-python-version 3.12 \ + --enum-field-as-literal all \ + --class-name CoverityJsonOutputV10 \ + --custom-file-header '"""Coverity JSON Output V10 model."""' + +# Use LLM to refine the model based on https://documentation.blackduck.com/bundle/coverity-docs/page/desktop-analysis/topics/desktop_analysis_json_output_syntax.html + +ruff format CoverityJsonOutputV10.py +ruff check --unsafe-fixes --fix CoverityJsonOutputV10.py +ty check CoverityJsonOutputV10.py +``` \ No newline at end of file diff --git a/codesectools/sasts/core/parser/format/SARIF/__init__.py b/codesectools/sasts/core/parser/format/SARIF/__init__.py new file mode 100644 index 0000000..b1ee1c8 --- /dev/null +++ b/codesectools/sasts/core/parser/format/SARIF/__init__.py @@ -0,0 +1,3366 @@ +"""Static Analysis Results Interchange Format (SARIF) Version 2.1.0 data model.""" + +from __future__ import annotations + +from typing import Annotated, Literal + +from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field, RootModel + + +class PropertyBag(BaseModel): + """Key/value pairs that provide additional information about the object.""" + + model_config = ConfigDict( + extra="allow", + ) + tags: Annotated[ + list[str] | None, + Field( + description="A set of distinct strings that provide additional information.", + min_length=0, + ), + ] = [] + + +DeprecatedGuid = RootModel[str] + + +class ReportingConfiguration(BaseModel): + """Information about a rule or notification that can be configured at runtime.""" + + model_config = ConfigDict( + extra="forbid", + ) + enabled: Annotated[ + bool | None, + Field( + description="Specifies whether the report may be produced during the scan." + ), + ] = True + level: Annotated[ + Literal["none", "note", "warning", "error"] | None, + Field(description="Specifies the failure level for the report."), + ] = "warning" + rank: Annotated[ + float | None, + Field( + description="Specifies the relative priority of the report. Used for analysis output only.", + ge=-1.0, + le=100.0, + ), + ] = -1.0 + parameters: Annotated[ + PropertyBag | None, + Field(description="Contains configuration information specific to a report."), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the reporting configuration." + ), + ] = None + + +class ToolComponentReference(BaseModel): + """Identifies a particular toolComponent object, either the driver or an extension.""" + + model_config = ConfigDict( + extra="forbid", + ) + name: Annotated[ + str | None, + Field(description="The 'name' property of the referenced toolComponent."), + ] = None + index: Annotated[ + int | None, + Field( + description="An index into the referenced toolComponent in tool.extensions.", + ge=-1, + ), + ] = -1 + guid: Annotated[ + str | None, + Field( + description="The 'guid' property of the referenced toolComponent.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the toolComponentReference." + ), + ] = None + + +class Address(BaseModel): + """A physical or virtual address, or a range of addresses, in an 'addressable region' (memory or a binary file).""" + + model_config = ConfigDict( + extra="forbid", + ) + absolute_address: Annotated[ + int | None, + Field( + alias="absoluteAddress", + description="The address expressed as a byte offset from the start of the addressable region.", + ge=-1, + ), + ] = -1 + relative_address: Annotated[ + int | None, + Field( + alias="relativeAddress", + description="The address expressed as a byte offset from the absolute address of the top-most parent object.", + ), + ] = None + length: Annotated[ + int | None, Field(description="The number of bytes in this range of addresses.") + ] = None + kind: Annotated[ + str | None, + Field( + description="An open-ended string that identifies the address kind. 'data', 'function', 'header','instruction', 'module', 'page', 'section', 'segment', 'stack', 'stackFrame', 'table' are well-known values." + ), + ] = None + name: Annotated[ + str | None, + Field(description="A name that is associated with the address, e.g., '.text'."), + ] = None + fully_qualified_name: Annotated[ + str | None, + Field( + alias="fullyQualifiedName", + description="A human-readable fully qualified name that is associated with the address.", + ), + ] = None + offset_from_parent: Annotated[ + int | None, + Field( + alias="offsetFromParent", + description="The byte offset of this address from the absolute or relative address of the parent object.", + ), + ] = None + index: Annotated[ + int | None, + Field( + description="The index within run.addresses of the cached object for this address.", + ge=-1, + ), + ] = -1 + parent_index: Annotated[ + int | None, + Field( + alias="parentIndex", + description="The index within run.addresses of the parent object.", + ge=-1, + ), + ] = -1 + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the address." + ), + ] = None + + +class LogicalLocation(BaseModel): + """A logical location of a construct that produced a result.""" + + model_config = ConfigDict( + extra="forbid", + ) + name: Annotated[ + str | None, + Field( + description="Identifies the construct in which the result occurred. For example, this property might contain the name of a class or a method." + ), + ] = None + index: Annotated[ + int | None, + Field(description="The index within the logical locations array.", ge=-1), + ] = -1 + fully_qualified_name: Annotated[ + str | None, + Field( + alias="fullyQualifiedName", + description="The human-readable fully qualified name of the logical location.", + ), + ] = None + decorated_name: Annotated[ + str | None, + Field( + alias="decoratedName", + description="The machine-readable name for the logical location, such as a mangled function name provided by a C++ compiler that encodes calling convention, return type and other details along with the function name.", + ), + ] = None + parent_index: Annotated[ + int | None, + Field( + alias="parentIndex", + description="Identifies the index of the immediate parent of the construct in which the result was detected. For example, this property might point to a logical location that represents the namespace that holds a type.", + ge=-1, + ), + ] = -1 + kind: Annotated[ + str | None, + Field( + description="The type of construct this logical location component refers to. Should be one of 'function', 'member', 'module', 'namespace', 'parameter', 'resource', 'returnType', 'type', 'variable', 'object', 'array', 'property', 'value', 'element', 'text', 'attribute', 'comment', 'declaration', 'dtd' or 'processingInstruction', if any of those accurately describe the construct." + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the logical location." + ), + ] = None + + +class Message1(BaseModel): + """Encapsulates a message intended to be read by the end user.""" + + model_config = ConfigDict( + extra="forbid", + ) + text: Annotated[str, Field(description="A plain text message string.")] + markdown: Annotated[str | None, Field(description="A Markdown message string.")] = ( + None + ) + id: Annotated[str | None, Field(description="The identifier for this message.")] = ( + None + ) + arguments: Annotated[ + list[str] | None, + Field( + description="An array of strings to substitute into the message string.", + min_length=0, + ), + ] = [] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the message." + ), + ] = None + + +class Message2(BaseModel): + """Encapsulates a message intended to be read by the end user.""" + + model_config = ConfigDict( + extra="forbid", + ) + text: Annotated[str | None, Field(description="A plain text message string.")] = ( + None + ) + markdown: Annotated[str | None, Field(description="A Markdown message string.")] = ( + None + ) + id: Annotated[str, Field(description="The identifier for this message.")] + arguments: Annotated[ + list[str] | None, + Field( + description="An array of strings to substitute into the message string.", + min_length=0, + ), + ] = [] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the message." + ), + ] = None + + +Message = RootModel[Message1 | Message2] + + +class MultiformatMessageString(BaseModel): + """A message string or message format string rendered in multiple formats.""" + + model_config = ConfigDict( + extra="forbid", + ) + text: Annotated[ + str, Field(description="A plain text message string or format string.") + ] + markdown: Annotated[ + str | None, Field(description="A Markdown message string or format string.") + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the message." + ), + ] = None + + +class Rectangle(BaseModel): + """An area within an image.""" + + model_config = ConfigDict( + extra="forbid", + ) + top: Annotated[ + float | None, + Field( + description="The Y coordinate of the top edge of the rectangle, measured in the image's natural units." + ), + ] = None + left: Annotated[ + float | None, + Field( + description="The X coordinate of the left edge of the rectangle, measured in the image's natural units." + ), + ] = None + bottom: Annotated[ + float | None, + Field( + description="The Y coordinate of the bottom edge of the rectangle, measured in the image's natural units." + ), + ] = None + right: Annotated[ + float | None, + Field( + description="The X coordinate of the right edge of the rectangle, measured in the image's natural units." + ), + ] = None + message: Annotated[ + Message | None, Field(description="A message relevant to the rectangle.") + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the rectangle." + ), + ] = None + + +class ReportingDescriptorReference1(BaseModel): + """Information about how to locate a relevant reporting descriptor.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[str | None, Field(description="The id of the descriptor.")] = None + index: Annotated[ + int, + Field( + description="The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", + ge=-1, + ), + ] + guid: Annotated[ + str | None, + Field( + description="A guid that uniquely identifies the descriptor.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + tool_component: Annotated[ + ToolComponentReference | None, + Field( + alias="toolComponent", + description="A reference used to locate the toolComponent associated with the descriptor.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the reporting descriptor reference." + ), + ] = None + + +class ReportingDescriptorReference2(BaseModel): + """Information about how to locate a relevant reporting descriptor.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[str | None, Field(description="The id of the descriptor.")] = None + index: Annotated[ + int | None, + Field( + description="The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", + ge=-1, + ), + ] = -1 + guid: Annotated[ + str, + Field( + description="A guid that uniquely identifies the descriptor.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] + tool_component: Annotated[ + ToolComponentReference | None, + Field( + alias="toolComponent", + description="A reference used to locate the toolComponent associated with the descriptor.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the reporting descriptor reference." + ), + ] = None + + +class ReportingDescriptorReference3(BaseModel): + """Information about how to locate a relevant reporting descriptor.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[str, Field(description="The id of the descriptor.")] + index: Annotated[ + int | None, + Field( + description="The index into an array of descriptors in toolComponent.ruleDescriptors, toolComponent.notificationDescriptors, or toolComponent.taxonomyDescriptors, depending on context.", + ge=-1, + ), + ] = -1 + guid: Annotated[ + str | None, + Field( + description="A guid that uniquely identifies the descriptor.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + tool_component: Annotated[ + ToolComponentReference | None, + Field( + alias="toolComponent", + description="A reference used to locate the toolComponent associated with the descriptor.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the reporting descriptor reference." + ), + ] = None + + +ReportingDescriptorReference = RootModel[ + ReportingDescriptorReference1 + | ReportingDescriptorReference2 + | ReportingDescriptorReference3 +] + + +class ReportingDescriptorRelationship(BaseModel): + """Information about the relation of one reporting descriptor to another.""" + + model_config = ConfigDict( + extra="forbid", + ) + target: Annotated[ + ReportingDescriptorReference, + Field(description="A reference to the related reporting descriptor."), + ] + kinds: Annotated[ + list[str] | None, + Field( + description="A set of distinct strings that categorize the relationship. Well-known kinds include 'canPrecede', 'canFollow', 'willPrecede', 'willFollow', 'superset', 'subset', 'equal', 'disjoint', 'relevant', and 'incomparable'." + ), + ] = ["relevant"] + description: Annotated[ + Message | None, + Field(description="A description of the reporting descriptor relationship."), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the reporting descriptor reference." + ), + ] = None + + +class RunAutomationDetails(BaseModel): + """Information that describes a run's identity and role within an engineering system process.""" + + model_config = ConfigDict( + extra="forbid", + ) + description: Annotated[ + Message | None, + Field( + description="A description of the identity and role played within the engineering system by this object's containing run object." + ), + ] = None + id: Annotated[ + str | None, + Field( + description="A hierarchical string that uniquely identifies this object's containing run object." + ), + ] = None + guid: Annotated[ + str | None, + Field( + description="A stable, unique identifer for this object's containing run object in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + correlation_guid: Annotated[ + str | None, + Field( + alias="correlationGuid", + description="A stable, unique identifier for the equivalence class of runs to which this object's containing run object belongs in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the run automation details." + ), + ] = None + + +class TranslationMetadata(BaseModel): + """Provides additional metadata related to translation.""" + + model_config = ConfigDict( + extra="forbid", + ) + name: Annotated[ + str, Field(description="The name associated with the translation metadata.") + ] + full_name: Annotated[ + str | None, + Field( + alias="fullName", + description="The full name associated with the translation metadata.", + ), + ] = None + short_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="shortDescription", + description="A brief description of the translation metadata.", + ), + ] = None + full_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="fullDescription", + description="A comprehensive description of the translation metadata.", + ), + ] = None + download_uri: Annotated[ + AnyUrl | None, + Field( + alias="downloadUri", + description="The absolute URI from which the translation metadata can be downloaded.", + ), + ] = None + information_uri: Annotated[ + AnyUrl | None, + Field( + alias="informationUri", + description="The absolute URI from which information related to the translation metadata can be downloaded.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the translation metadata." + ), + ] = None + + +class ArtifactContent(BaseModel): + """Represents the contents of an artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + text: Annotated[ + str | None, Field(description="UTF-8-encoded content from a text artifact.") + ] = None + binary: Annotated[ + str | None, + Field( + description="MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding." + ), + ] = None + rendered: Annotated[ + MultiformatMessageString | None, + Field( + description="An alternate rendered representation of the artifact (e.g., a decompiled representation of a binary region)." + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the artifact content." + ), + ] = None + + +class ArtifactLocation(BaseModel): + """Specifies the location of an artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + uri: Annotated[ + str | None, + Field(description="A string containing a valid relative or absolute URI."), + ] = None + uri_base_id: Annotated[ + str | None, + Field( + alias="uriBaseId", + description='A string which indirectly specifies the absolute URI with respect to which a relative URI in the "uri" property is interpreted.', + ), + ] = None + index: Annotated[ + int | None, + Field( + description="The index within the run artifacts array of the artifact object associated with the artifact location.", + ge=-1, + ), + ] = -1 + description: Annotated[ + Message | None, + Field(description="A short description of the artifact location."), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the artifact location." + ), + ] = None + + +class ConfigurationOverride(BaseModel): + """Information about how a specific rule or notification was reconfigured at runtime.""" + + model_config = ConfigDict( + extra="forbid", + ) + configuration: Annotated[ + ReportingConfiguration, + Field( + description="Specifies how the rule or notification was configured during the scan." + ), + ] + descriptor: Annotated[ + ReportingDescriptorReference, + Field( + description="A reference used to locate the descriptor whose configuration was overridden." + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the configuration override." + ), + ] = None + + +class Edge(BaseModel): + """Represents a directed edge in a graph.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[ + str, + Field( + description="A string that uniquely identifies the edge within its graph." + ), + ] + label: Annotated[ + Message | None, Field(description="A short description of the edge.") + ] = None + source_node_id: Annotated[ + str, + Field( + alias="sourceNodeId", + description="Identifies the source node (the node at which the edge starts).", + ), + ] + target_node_id: Annotated[ + str, + Field( + alias="targetNodeId", + description="Identifies the target node (the node at which the edge ends).", + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the edge." + ), + ] = None + + +class EdgeTraversal(BaseModel): + """Represents the traversal of a single edge during a graph traversal.""" + + model_config = ConfigDict( + extra="forbid", + ) + edge_id: Annotated[ + str, Field(alias="edgeId", description="Identifies the edge being traversed.") + ] + message: Annotated[ + Message | None, + Field(description="A message to display to the user as the edge is traversed."), + ] = None + final_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="finalState", + description="The values of relevant expressions after the edge has been traversed.", + ), + ] = None + step_over_edge_count: Annotated[ + int | None, + Field( + alias="stepOverEdgeCount", + description="The number of edge traversals necessary to return from a nested graph.", + ge=0, + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the edge traversal." + ), + ] = None + + +class ExternalPropertyFileReference1(BaseModel): + """Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.""" + + model_config = ConfigDict( + extra="forbid", + ) + location: Annotated[ + ArtifactLocation, + Field(description="The location of the external property file."), + ] + guid: Annotated[ + str | None, + Field( + description="A stable, unique identifer for the external property file in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + item_count: Annotated[ + int | None, + Field( + alias="itemCount", + description="A non-negative integer specifying the number of items contained in the external property file.", + ge=-1, + ), + ] = -1 + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the external property file." + ), + ] = None + + +class ExternalPropertyFileReference2(BaseModel): + """Contains information that enables a SARIF consumer to locate the external property file that contains the value of an externalized property associated with the run.""" + + model_config = ConfigDict( + extra="forbid", + ) + location: Annotated[ + ArtifactLocation | None, + Field(description="The location of the external property file."), + ] = None + guid: Annotated[ + str, + Field( + description="A stable, unique identifer for the external property file in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] + item_count: Annotated[ + int | None, + Field( + alias="itemCount", + description="A non-negative integer specifying the number of items contained in the external property file.", + ge=-1, + ), + ] = -1 + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the external property file." + ), + ] = None + + +ExternalPropertyFileReference = RootModel[ + ExternalPropertyFileReference1 | ExternalPropertyFileReference2 +] + + +class ExternalPropertyFileReferences(BaseModel): + """References to external property files that should be inlined with the content of a root log file.""" + + model_config = ConfigDict( + extra="forbid", + ) + conversion: Annotated[ + ExternalPropertyFileReference | None, + Field( + description="An external property file containing a run.conversion object to be merged with the root log file." + ), + ] = None + graphs: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing a run.graphs object to be merged with the root log file.", + min_length=0, + ), + ] + externalized_properties: Annotated[ + ExternalPropertyFileReference | None, + Field( + alias="externalizedProperties", + description="An external property file containing a run.properties object to be merged with the root log file.", + ), + ] = None + artifacts: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.artifacts arrays to be merged with the root log file.", + min_length=0, + ), + ] + invocations: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.invocations arrays to be merged with the root log file.", + min_length=0, + ), + ] + logical_locations: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + alias="logicalLocations", + description="An array of external property files containing run.logicalLocations arrays to be merged with the root log file.", + min_length=0, + ), + ] + thread_flow_locations: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + alias="threadFlowLocations", + description="An array of external property files containing run.threadFlowLocations arrays to be merged with the root log file.", + min_length=0, + ), + ] + results: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.results arrays to be merged with the root log file.", + min_length=0, + ), + ] + taxonomies: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.taxonomies arrays to be merged with the root log file.", + min_length=0, + ), + ] + addresses: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.addresses arrays to be merged with the root log file.", + min_length=0, + ), + ] + driver: Annotated[ + ExternalPropertyFileReference | None, + Field( + description="An external property file containing a run.driver object to be merged with the root log file." + ), + ] = None + extensions: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.extensions arrays to be merged with the root log file.", + min_length=0, + ), + ] + policies: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.policies arrays to be merged with the root log file.", + min_length=0, + ), + ] + translations: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + description="An array of external property files containing run.translations arrays to be merged with the root log file.", + min_length=0, + ), + ] + web_requests: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + alias="webRequests", + description="An array of external property files containing run.requests arrays to be merged with the root log file.", + min_length=0, + ), + ] + web_responses: Annotated[ + list[ExternalPropertyFileReference] | None, + Field( + default_factory=list, + alias="webResponses", + description="An array of external property files containing run.responses arrays to be merged with the root log file.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the external property files." + ), + ] = None + + +class GraphTraversal1(BaseModel): + """Represents a path through a graph.""" + + model_config = ConfigDict( + extra="forbid", + ) + run_graph_index: Annotated[ + int, + Field( + alias="runGraphIndex", + description="The index within the run.graphs to be associated with the result.", + ge=-1, + ), + ] + result_graph_index: Annotated[ + int | None, + Field( + alias="resultGraphIndex", + description="The index within the result.graphs to be associated with the result.", + ge=-1, + ), + ] = -1 + description: Annotated[ + Message | None, Field(description="A description of this graph traversal.") + ] = None + initial_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="initialState", + description="Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", + ), + ] = None + immutable_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="immutableState", + description="Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", + ), + ] = None + edge_traversals: Annotated[ + list[EdgeTraversal] | None, + Field( + default_factory=list, + alias="edgeTraversals", + description="The sequences of edges traversed by this graph traversal.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the graph traversal." + ), + ] = None + + +class GraphTraversal2(BaseModel): + """Represents a path through a graph.""" + + model_config = ConfigDict( + extra="forbid", + ) + run_graph_index: Annotated[ + int | None, + Field( + alias="runGraphIndex", + description="The index within the run.graphs to be associated with the result.", + ge=-1, + ), + ] = -1 + result_graph_index: Annotated[ + int, + Field( + alias="resultGraphIndex", + description="The index within the result.graphs to be associated with the result.", + ge=-1, + ), + ] + description: Annotated[ + Message | None, Field(description="A description of this graph traversal.") + ] = None + initial_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="initialState", + description="Values of relevant expressions at the start of the graph traversal that may change during graph traversal.", + ), + ] = None + immutable_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="immutableState", + description="Values of relevant expressions at the start of the graph traversal that remain constant for the graph traversal.", + ), + ] = None + edge_traversals: Annotated[ + list[EdgeTraversal] | None, + Field( + default_factory=list, + alias="edgeTraversals", + description="The sequences of edges traversed by this graph traversal.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the graph traversal." + ), + ] = None + + +GraphTraversal = RootModel[GraphTraversal1 | GraphTraversal2] + + +class LocationRelationship(BaseModel): + """Information about the relation of one location to another.""" + + model_config = ConfigDict( + extra="forbid", + ) + target: Annotated[ + int, Field(description="A reference to the related location.", ge=0) + ] + kinds: Annotated[ + list[str] | None, + Field( + description="A set of distinct strings that categorize the relationship. Well-known kinds include 'includes', 'isIncludedBy' and 'relevant'." + ), + ] = ["relevant"] + description: Annotated[ + Message | None, Field(description="A description of the location relationship.") + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the location relationship." + ), + ] = None + + +class Region(BaseModel): + """A region within an artifact where a result was detected.""" + + model_config = ConfigDict( + extra="forbid", + ) + start_line: Annotated[ + int | None, + Field( + alias="startLine", + description="The line number of the first character in the region.", + ge=1, + ), + ] = None + start_column: Annotated[ + int | None, + Field( + alias="startColumn", + description="The column number of the first character in the region.", + ge=1, + ), + ] = None + end_line: Annotated[ + int | None, + Field( + alias="endLine", + description="The line number of the last character in the region.", + ge=1, + ), + ] = None + end_column: Annotated[ + int | None, + Field( + alias="endColumn", + description="The column number of the character following the end of the region.", + ge=1, + ), + ] = None + char_offset: Annotated[ + int | None, + Field( + alias="charOffset", + description="The zero-based offset from the beginning of the artifact of the first character in the region.", + ge=-1, + ), + ] = -1 + char_length: Annotated[ + int | None, + Field( + alias="charLength", + description="The length of the region in characters.", + ge=0, + ), + ] = None + byte_offset: Annotated[ + int | None, + Field( + alias="byteOffset", + description="The zero-based offset from the beginning of the artifact of the first byte in the region.", + ge=-1, + ), + ] = -1 + byte_length: Annotated[ + int | None, + Field( + alias="byteLength", description="The length of the region in bytes.", ge=0 + ), + ] = None + snippet: Annotated[ + ArtifactContent | None, + Field( + description="The portion of the artifact contents within the specified region." + ), + ] = None + message: Annotated[ + Message | None, Field(description="A message relevant to the region.") + ] = None + source_language: Annotated[ + str | None, + Field( + alias="sourceLanguage", + description="Specifies the source language, if any, of the portion of the artifact specified by the region object.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the region." + ), + ] = None + + +class Replacement(BaseModel): + """The replacement of a single region of an artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + deleted_region: Annotated[ + Region, + Field( + alias="deletedRegion", description="The region of the artifact to delete." + ), + ] + inserted_content: Annotated[ + ArtifactContent | None, + Field( + alias="insertedContent", + description="The content to insert at the location specified by the 'deletedRegion' property.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the replacement." + ), + ] = None + + +class ReportingDescriptor(BaseModel): + """Metadata that describes a specific report produced by the tool, as part of the analysis it provides or its runtime reporting.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[str, Field(description="A stable, opaque identifier for the report.")] + deprecated_ids: Annotated[ + list[str] | None, + Field( + alias="deprecatedIds", + description="An array of stable, opaque identifiers by which this report was known in some previous version of the analysis tool.", + min_length=0, + ), + ] = None + guid: Annotated[ + str | None, + Field( + description="A unique identifer for the reporting descriptor in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + deprecated_guids: Annotated[ + list[DeprecatedGuid] | None, + Field( + alias="deprecatedGuids", + description="An array of unique identifies in the form of a GUID by which this report was known in some previous version of the analysis tool.", + min_length=0, + ), + ] = None + name: Annotated[ + str | None, + Field(description="A report identifier that is understandable to an end user."), + ] = None + deprecated_names: Annotated[ + list[str] | None, + Field( + alias="deprecatedNames", + description="An array of readable identifiers by which this report was known in some previous version of the analysis tool.", + min_length=0, + ), + ] = None + short_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="shortDescription", + description="A concise description of the report. Should be a single sentence that is understandable when visible space is limited to a single line of text.", + ), + ] = None + full_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="fullDescription", + description="A description of the report. Should, as far as possible, provide details sufficient to enable resolution of any problem indicated by the result.", + ), + ] = None + message_strings: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="messageStrings", + description="A set of name/value pairs with arbitrary names. Each value is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + ), + ] = None + default_configuration: Annotated[ + ReportingConfiguration | None, + Field( + alias="defaultConfiguration", + description="Default reporting configuration information.", + ), + ] = None + help_uri: Annotated[ + AnyUrl | None, + Field( + alias="helpUri", + description="A URI where the primary documentation for the report can be found.", + ), + ] = None + help: Annotated[ + MultiformatMessageString | None, + Field( + description="Provides the primary documentation for the report, useful when there is no online documentation." + ), + ] = None + relationships: Annotated[ + list[ReportingDescriptorRelationship] | None, + Field( + default_factory=list, + description="An array of objects that describe relationships between this reporting descriptor and others.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the report." + ), + ] = None + + +class SpecialLocations(BaseModel): + """Defines locations of special significance to SARIF consumers.""" + + model_config = ConfigDict( + extra="forbid", + ) + display_base: Annotated[ + ArtifactLocation | None, + Field( + alias="displayBase", + description="Provides a suggestion to SARIF consumers to display file paths relative to the specified location.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the special locations." + ), + ] = None + + +class ToolComponent(BaseModel): + """A component, such as a plug-in or the driver, of the analysis tool that was run.""" + + model_config = ConfigDict( + extra="forbid", + ) + guid: Annotated[ + str | None, + Field( + description="A unique identifer for the tool component in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + name: Annotated[str, Field(description="The name of the tool component.")] + organization: Annotated[ + str | None, + Field( + description="The organization or company that produced the tool component." + ), + ] = None + product: Annotated[ + str | None, + Field(description="A product suite to which the tool component belongs."), + ] = None + product_suite: Annotated[ + str | None, + Field( + alias="productSuite", + description="A localizable string containing the name of the suite of products to which the tool component belongs.", + ), + ] = None + short_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="shortDescription", + description="A brief description of the tool component.", + ), + ] = None + full_description: Annotated[ + MultiformatMessageString | None, + Field( + alias="fullDescription", + description="A comprehensive description of the tool component.", + ), + ] = None + full_name: Annotated[ + str | None, + Field( + alias="fullName", + description="The name of the tool component along with its version and any other useful identifying information, such as its locale.", + ), + ] = None + version: Annotated[ + str | None, + Field( + description="The tool component version, in whatever format the component natively provides." + ), + ] = None + semantic_version: Annotated[ + str | None, + Field( + alias="semanticVersion", + description="The tool component version in the format specified by Semantic Versioning 2.0.", + ), + ] = None + dotted_quad_file_version: Annotated[ + str | None, + Field( + alias="dottedQuadFileVersion", + description="The binary version of the tool component's primary executable file expressed as four non-negative integers separated by a period (for operating systems that express file versions in this way).", + pattern="[0-9]+(\\.[0-9]+){3}", + ), + ] = None + release_date_utc: Annotated[ + str | None, + Field( + alias="releaseDateUtc", + description="A string specifying the UTC date (and optionally, the time) of the component's release.", + ), + ] = None + download_uri: Annotated[ + AnyUrl | None, + Field( + alias="downloadUri", + description="The absolute URI from which the tool component can be downloaded.", + ), + ] = None + information_uri: Annotated[ + AnyUrl | None, + Field( + alias="informationUri", + description="The absolute URI at which information about this version of the tool component can be found.", + ), + ] = None + global_message_strings: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="globalMessageStrings", + description="A dictionary, each of whose keys is a resource identifier and each of whose values is a multiformatMessageString object, which holds message strings in plain text and (optionally) Markdown format. The strings can include placeholders, which can be used to construct a message in combination with an arbitrary number of additional string arguments.", + ), + ] = None + notifications: Annotated[ + list[ReportingDescriptor] | None, + Field( + default_factory=list, + description="An array of reportingDescriptor objects relevant to the notifications related to the configuration and runtime execution of the tool component.", + min_length=0, + ), + ] + rules: Annotated[ + list[ReportingDescriptor] | None, + Field( + default_factory=list, + description="An array of reportingDescriptor objects relevant to the analysis performed by the tool component.", + min_length=0, + ), + ] + taxa: Annotated[ + list[ReportingDescriptor] | None, + Field( + default_factory=list, + description="An array of reportingDescriptor objects relevant to the definitions of both standalone and tool-defined taxonomies.", + min_length=0, + ), + ] + locations: Annotated[ + list[ArtifactLocation] | None, + Field( + default_factory=list, + description="An array of the artifactLocation objects associated with the tool component.", + min_length=0, + ), + ] + language: Annotated[ + str | None, + Field( + description="The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase language code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + pattern="^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$", + ), + ] = "en-US" + contents: Annotated[ + list[Literal["localizedData", "nonLocalizedData"]] | None, + Field(description="The kinds of data contained in this object."), + ] = ["localizedData", "nonLocalizedData"] + is_comprehensive: Annotated[ + bool | None, + Field( + alias="isComprehensive", + description="Specifies whether this object contains a complete definition of the localizable and/or non-localizable data for this component, as opposed to including only data that is relevant to the results persisted to this log file.", + ), + ] = False + localized_data_semantic_version: Annotated[ + str | None, + Field( + alias="localizedDataSemanticVersion", + description="The semantic version of the localized strings defined in this component; maintained by components that provide translations.", + ), + ] = None + minimum_required_localized_data_semantic_version: Annotated[ + str | None, + Field( + alias="minimumRequiredLocalizedDataSemanticVersion", + description="The minimum value of localizedDataSemanticVersion required in translations consumed by this component; used by components that consume translations.", + ), + ] = None + associated_component: Annotated[ + ToolComponentReference | None, + Field( + alias="associatedComponent", + description="The component which is strongly associated with this component. For a translation, this refers to the component which has been translated. For an extension, this is the driver that provides the extension's plugin model.", + ), + ] = None + translation_metadata: Annotated[ + TranslationMetadata | None, + Field( + alias="translationMetadata", + description="Translation metadata, required for a translation, not populated by other component types.", + ), + ] = None + supported_taxonomies: Annotated[ + list[ToolComponentReference] | None, + Field( + default_factory=list, + alias="supportedTaxonomies", + description="An array of toolComponentReference objects to declare the taxonomies supported by the tool component.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the tool component." + ), + ] = None + + +class VersionControlDetails(BaseModel): + """Specifies the information necessary to retrieve a desired revision from a version control system.""" + + model_config = ConfigDict( + extra="forbid", + ) + repository_uri: Annotated[ + AnyUrl, + Field(alias="repositoryUri", description="The absolute URI of the repository."), + ] + revision_id: Annotated[ + str | None, + Field( + alias="revisionId", + description="A string that uniquely and permanently identifies the revision within the repository.", + ), + ] = None + branch: Annotated[ + str | None, Field(description="The name of a branch containing the revision.") + ] = None + revision_tag: Annotated[ + str | None, + Field( + alias="revisionTag", + description="A tag that has been applied to the revision.", + ), + ] = None + as_of_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="asOfTimeUtc", + description="A Coordinated Universal Time (UTC) date and time that can be used to synchronize an enlistment to the state of the repository at that time.", + ), + ] = None + mapped_to: Annotated[ + ArtifactLocation | None, + Field( + alias="mappedTo", + description="The location in the local file system to which the root of the repository was mapped at the time of the analysis.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the version control details." + ), + ] = None + + +class WebRequest(BaseModel): + """Describes an HTTP request.""" + + model_config = ConfigDict( + extra="forbid", + ) + index: Annotated[ + int | None, + Field( + description="The index within the run.webRequests array of the request object associated with this result.", + ge=-1, + ), + ] = -1 + protocol: Annotated[ + str | None, Field(description="The request protocol. Example: 'http'.") + ] = None + version: Annotated[ + str | None, Field(description="The request version. Example: '1.1'.") + ] = None + target: Annotated[str | None, Field(description="The target of the request.")] = ( + None + ) + method: Annotated[ + str | None, + Field( + description="The HTTP method. Well-known values are 'GET', 'PUT', 'POST', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'." + ), + ] = None + headers: Annotated[ + dict[str, str] | None, Field(description="The request headers.") + ] = None + parameters: Annotated[ + dict[str, str] | None, Field(description="The request parameters.") + ] = None + body: Annotated[ + ArtifactContent | None, Field(description="The body of the request.") + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the request." + ), + ] = None + + +class WebResponse(BaseModel): + """Describes the response to an HTTP request.""" + + model_config = ConfigDict( + extra="forbid", + ) + index: Annotated[ + int | None, + Field( + description="The index within the run.webResponses array of the response object associated with this result.", + ge=-1, + ), + ] = -1 + protocol: Annotated[ + str | None, Field(description="The response protocol. Example: 'http'.") + ] = None + version: Annotated[ + str | None, Field(description="The response version. Example: '1.1'.") + ] = None + status_code: Annotated[ + int | None, + Field( + alias="statusCode", description="The response status code. Example: 451." + ), + ] = None + reason_phrase: Annotated[ + str | None, + Field( + alias="reasonPhrase", + description="The response reason. Example: 'Not found'.", + ), + ] = None + headers: Annotated[ + dict[str, str] | None, Field(description="The response headers.") + ] = None + body: Annotated[ + ArtifactContent | None, Field(description="The body of the response.") + ] = None + no_response_received: Annotated[ + bool | None, + Field( + alias="noResponseReceived", + description="Specifies whether a response was received from the server.", + ), + ] = False + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the response." + ), + ] = None + + +class Artifact(BaseModel): + """A single artifact. In some cases, this artifact might be nested within another artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + description: Annotated[ + Message | None, Field(description="A short description of the artifact.") + ] = None + location: Annotated[ + ArtifactLocation | None, Field(description="The location of the artifact.") + ] = None + parent_index: Annotated[ + int | None, + Field( + alias="parentIndex", + description="Identifies the index of the immediate parent of the artifact, if this artifact is nested.", + ge=-1, + ), + ] = -1 + offset: Annotated[ + int | None, + Field( + description="The offset in bytes of the artifact within its containing artifact.", + ge=0, + ), + ] = None + length: Annotated[ + int | None, Field(description="The length of the artifact in bytes.", ge=-1) + ] = -1 + roles: Annotated[ + list[ + Literal[ + "analysisTarget", + "attachment", + "responseFile", + "resultFile", + "standardStream", + "tracedFile", + "unmodified", + "modified", + "added", + "deleted", + "renamed", + "uncontrolled", + "driver", + "extension", + "translation", + "taxonomy", + "policy", + "referencedOnCommandLine", + "memoryContents", + "directory", + "userSpecifiedConfiguration", + "toolSpecifiedConfiguration", + "debugOutputFile", + ] + ] + | None, + Field( + description="The role or roles played by the artifact in the analysis.", + min_length=0, + ), + ] = [] + mime_type: Annotated[ + str | None, + Field( + alias="mimeType", + description="The MIME type (RFC 2045) of the artifact.", + pattern="[^/]+/.+", + ), + ] = None + contents: Annotated[ + ArtifactContent | None, Field(description="The contents of the artifact.") + ] = None + encoding: Annotated[ + str | None, + Field( + description="Specifies the encoding for an artifact object that refers to a text file." + ), + ] = None + source_language: Annotated[ + str | None, + Field( + alias="sourceLanguage", + description="Specifies the source language for any artifact object that refers to a text file that contains source code.", + ), + ] = None + hashes: Annotated[ + dict[str, str] | None, + Field( + description="A dictionary, each of whose keys is the name of a hash function and each of whose values is the hashed value of the artifact produced by the specified hash function." + ), + ] = None + last_modified_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="lastModifiedTimeUtc", + description='The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified. See "Date/time properties" in the SARIF spec for the required format.', + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the artifact." + ), + ] = None + + +class ArtifactChange(BaseModel): + """A change to a single artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + artifact_location: Annotated[ + ArtifactLocation, + Field( + alias="artifactLocation", + description="The location of the artifact to change.", + ), + ] + replacements: Annotated[ + list[Replacement], + Field( + description="An array of replacement objects, each of which represents the replacement of a single region in a single artifact specified by 'artifactLocation'.", + min_length=1, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the change." + ), + ] = None + + +class Attachment(BaseModel): + """An artifact relevant to a result.""" + + model_config = ConfigDict( + extra="forbid", + ) + description: Annotated[ + Message | None, + Field(description="A message describing the role played by the attachment."), + ] = None + artifact_location: Annotated[ + ArtifactLocation, + Field(alias="artifactLocation", description="The location of the attachment."), + ] + regions: Annotated[ + list[Region] | None, + Field( + default_factory=list, + description="An array of regions of interest within the attachment.", + min_length=0, + ), + ] + rectangles: Annotated[ + list[Rectangle] | None, + Field( + default_factory=list, + description="An array of rectangles specifying areas of interest within the image.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the attachment." + ), + ] = None + + +class Fix(BaseModel): + """A proposed fix for the problem represented by a result object. A fix specifies a set of artifacts to modify. For each artifact, it specifies a set of bytes to remove, and provides a set of new bytes to replace them.""" + + model_config = ConfigDict( + extra="forbid", + ) + description: Annotated[ + Message | None, + Field( + description="A message that describes the proposed fix, enabling viewers to present the proposed change to an end user." + ), + ] = None + artifact_changes: Annotated[ + list[ArtifactChange], + Field( + alias="artifactChanges", + description="One or more artifact changes that comprise a fix for a result.", + min_length=1, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the fix." + ), + ] = None + + +class PhysicalLocation1(BaseModel): + """A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + address: Annotated[Address, Field(description="The address of the location.")] + artifact_location: Annotated[ + ArtifactLocation | None, + Field(alias="artifactLocation", description="The location of the artifact."), + ] = None + region: Annotated[ + Region | None, Field(description="Specifies a portion of the artifact.") + ] = None + context_region: Annotated[ + Region | None, + Field( + alias="contextRegion", + description="Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the physical location." + ), + ] = None + + +class PhysicalLocation2(BaseModel): + """A physical location relevant to a result. Specifies a reference to a programming artifact together with a range of bytes or characters within that artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + address: Annotated[ + Address | None, Field(description="The address of the location.") + ] = None + artifact_location: Annotated[ + ArtifactLocation, + Field(alias="artifactLocation", description="The location of the artifact."), + ] + region: Annotated[ + Region | None, Field(description="Specifies a portion of the artifact.") + ] = None + context_region: Annotated[ + Region | None, + Field( + alias="contextRegion", + description="Specifies a portion of the artifact that encloses the region. Allows a viewer to display additional context around the region.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the physical location." + ), + ] = None + + +PhysicalLocation = RootModel[PhysicalLocation1 | PhysicalLocation2] + + +class ResultProvenance(BaseModel): + """Contains information about how and when a result was detected.""" + + model_config = ConfigDict( + extra="forbid", + ) + first_detection_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="firstDetectionTimeUtc", + description='The Coordinated Universal Time (UTC) date and time at which the result was first detected. See "Date/time properties" in the SARIF spec for the required format.', + ), + ] = None + last_detection_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="lastDetectionTimeUtc", + description='The Coordinated Universal Time (UTC) date and time at which the result was most recently detected. See "Date/time properties" in the SARIF spec for the required format.', + ), + ] = None + first_detection_run_guid: Annotated[ + str | None, + Field( + alias="firstDetectionRunGuid", + description="A GUID-valued string equal to the automationDetails.guid property of the run in which the result was first detected.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + last_detection_run_guid: Annotated[ + str | None, + Field( + alias="lastDetectionRunGuid", + description="A GUID-valued string equal to the automationDetails.guid property of the run in which the result was most recently detected.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + invocation_index: Annotated[ + int | None, + Field( + alias="invocationIndex", + description="The index within the run.invocations array of the invocation object which describes the tool invocation that detected the result.", + ge=-1, + ), + ] = -1 + conversion_sources: Annotated[ + list[PhysicalLocation] | None, + Field( + default_factory=list, + alias="conversionSources", + description="An array of physicalLocation objects which specify the portions of an analysis tool's output that a converter transformed into the result.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the result." + ), + ] = None + + +class Tool(BaseModel): + """The analysis tool that was run.""" + + model_config = ConfigDict( + extra="forbid", + ) + driver: Annotated[ + ToolComponent, Field(description="The analysis tool that was run.") + ] + extensions: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Tool extensions that contributed to or reconfigured the analysis tool that was run.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the tool." + ), + ] = None + + +class Location(BaseModel): + """A location within a programming artifact.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[ + int | None, + Field( + description="Value that distinguishes this location from all other locations within a single result object.", + ge=-1, + ), + ] = -1 + physical_location: Annotated[ + PhysicalLocation | None, + Field( + alias="physicalLocation", description="Identifies the artifact and region." + ), + ] = None + logical_locations: Annotated[ + list[LogicalLocation] | None, + Field( + default_factory=list, + alias="logicalLocations", + description="The logical locations associated with the result.", + min_length=0, + ), + ] + message: Annotated[ + Message | None, Field(description="A message relevant to the location.") + ] = None + annotations: Annotated[ + list[Region] | None, + Field( + default_factory=list, + description="A set of regions relevant to the location.", + min_length=0, + ), + ] + relationships: Annotated[ + list[LocationRelationship] | None, + Field( + default_factory=list, + description="An array of objects that describe relationships between this location and others.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the location." + ), + ] = None + + +class Node(BaseModel): + """Represents a node in a graph.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[ + str, + Field( + description="A string that uniquely identifies the node within its graph." + ), + ] + label: Annotated[ + Message | None, Field(description="A short description of the node.") + ] = None + location: Annotated[ + Location | None, Field(description="A code location associated with the node.") + ] = None + children: Annotated[ + list[Node] | None, + Field(default_factory=list, description="Array of child nodes."), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the node." + ), + ] = None + + +class StackFrame(BaseModel): + """A function call within a stack trace.""" + + model_config = ConfigDict( + extra="forbid", + ) + location: Annotated[ + Location | None, + Field(description="The location to which this stack frame refers."), + ] = None + module: Annotated[ + str | None, + Field( + description="The name of the module that contains the code of this stack frame." + ), + ] = None + thread_id: Annotated[ + int | None, + Field( + alias="threadId", description="The thread identifier of the stack frame." + ), + ] = None + parameters: Annotated[ + list[str] | None, + Field( + description="The parameters of the call that is executing.", min_length=0 + ), + ] = [] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the stack frame." + ), + ] = None + + +class Suppression(BaseModel): + """A suppression that is relevant to a result.""" + + model_config = ConfigDict( + extra="forbid", + ) + guid: Annotated[ + str | None, + Field( + description="A stable, unique identifer for the suprression in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + kind: Annotated[ + Literal["inSource", "external"], + Field( + description="A string that indicates where the suppression is persisted." + ), + ] + state: Annotated[ + Literal["accepted", "underReview", "rejected"] | None, + Field(description="A string that indicates the state of the suppression."), + ] = None + justification: Annotated[ + str | None, + Field( + description="A string representing the justification for the suppression." + ), + ] = None + location: Annotated[ + Location | None, + Field(description="Identifies the location associated with the suppression."), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the suppression." + ), + ] = None + + +class Graph(BaseModel): + """A network of nodes and directed edges that describes some aspect of the structure of the code (for example, a call graph).""" + + model_config = ConfigDict( + extra="forbid", + ) + description: Annotated[ + Message | None, Field(description="A description of the graph.") + ] = None + nodes: Annotated[ + list[Node] | None, + Field( + default_factory=list, + description="An array of node objects representing the nodes of the graph.", + min_length=0, + ), + ] + edges: Annotated[ + list[Edge] | None, + Field( + default_factory=list, + description="An array of edge objects representing the edges of the graph.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the graph." + ), + ] = None + + +class Stack(BaseModel): + """A call stack that is relevant to a result.""" + + model_config = ConfigDict( + extra="forbid", + ) + message: Annotated[ + Message | None, Field(description="A message relevant to this call stack.") + ] = None + frames: Annotated[ + list[StackFrame], + Field( + description="An array of stack frames that represents a sequence of calls, rendered in reverse chronological order, that comprise the call stack.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the stack." + ), + ] = None + + +class ThreadFlowLocation(BaseModel): + """A location visited by an analysis tool while simulating or monitoring the execution of a program.""" + + model_config = ConfigDict( + extra="forbid", + ) + index: Annotated[ + int | None, + Field(description="The index within the run threadFlowLocations array.", ge=-1), + ] = -1 + location: Annotated[Location | None, Field(description="The code location.")] = None + stack: Annotated[ + Stack | None, Field(description="The call stack leading to this location.") + ] = None + kinds: Annotated[ + list[str] | None, + Field( + description="A set of distinct strings that categorize the thread flow location. Well-known kinds include 'acquire', 'release', 'enter', 'exit', 'call', 'return', 'branch', 'implicit', 'false', 'true', 'caution', 'danger', 'unknown', 'unreachable', 'taint', 'function', 'handler', 'lock', 'memory', 'resource', 'scope' and 'value'.", + min_length=0, + ), + ] = [] + taxa: Annotated[ + list[ReportingDescriptorReference] | None, + Field( + default_factory=list, + description="An array of references to rule or taxonomy reporting descriptors that are applicable to the thread flow location.", + min_length=0, + ), + ] + module: Annotated[ + str | None, + Field( + description="The name of the module that contains the code that is executing." + ), + ] = None + state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + description="A dictionary, each of whose keys specifies a variable or expression, the associated value of which represents the variable or expression value. For an annotation of kind 'continuation', for example, this dictionary might hold the current assumed values of a set of global variables." + ), + ] = None + nesting_level: Annotated[ + int | None, + Field( + alias="nestingLevel", + description="An integer representing a containment hierarchy within the thread flow.", + ge=0, + ), + ] = None + execution_order: Annotated[ + int | None, + Field( + alias="executionOrder", + description="An integer representing the temporal order in which execution reached this location.", + ge=-1, + ), + ] = -1 + execution_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="executionTimeUtc", + description="The Coordinated Universal Time (UTC) date and time at which this location was executed.", + ), + ] = None + importance: Annotated[ + Literal["important", "essential", "unimportant"] | None, + Field( + description='Specifies the importance of this location in understanding the code flow in which it occurs. The order from most to least important is "essential", "important", "unimportant". Default: "important".' + ), + ] = "important" + web_request: Annotated[ + WebRequest | None, + Field( + alias="webRequest", + description="A web request associated with this thread flow location.", + ), + ] = None + web_response: Annotated[ + WebResponse | None, + Field( + alias="webResponse", + description="A web response associated with this thread flow location.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the threadflow location." + ), + ] = None + + +class Exception(BaseModel): + """Describes a runtime exception encountered during the execution of an analysis tool.""" + + model_config = ConfigDict( + extra="forbid", + ) + kind: Annotated[ + str | None, + Field( + description="A string that identifies the kind of exception, for example, the fully qualified type name of an object that was thrown, or the symbolic name of a signal." + ), + ] = None + message: Annotated[ + str | None, Field(description="A message that describes the exception.") + ] = None + stack: Annotated[ + Stack | None, + Field(description="The sequence of function calls leading to the exception."), + ] = None + inner_exceptions: Annotated[ + list[Exception] | None, + Field( + default_factory=list, + alias="innerExceptions", + description="An array of exception objects each of which is considered a cause of this exception.", + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the exception." + ), + ] = None + + +class Notification(BaseModel): + """Describes a condition relevant to the tool itself, as opposed to being relevant to a target being analyzed by the tool.""" + + model_config = ConfigDict( + extra="forbid", + ) + locations: Annotated[ + list[Location] | None, + Field( + default_factory=list, + description="The locations relevant to this notification.", + min_length=0, + ), + ] + message: Annotated[ + Message, + Field( + description="A message that describes the condition that was encountered." + ), + ] + level: Annotated[ + Literal["none", "note", "warning", "error"] | None, + Field(description="A value specifying the severity level of the notification."), + ] = "warning" + thread_id: Annotated[ + int | None, + Field( + alias="threadId", + description="The thread identifier of the code that generated the notification.", + ), + ] = None + time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="timeUtc", + description="The Coordinated Universal Time (UTC) date and time at which the analysis tool generated the notification.", + ), + ] = None + exception: Annotated[ + Exception | None, + Field( + description="The runtime exception, if any, relevant to this notification." + ), + ] = None + descriptor: Annotated[ + ReportingDescriptorReference | None, + Field( + description="A reference used to locate the descriptor relevant to this notification." + ), + ] = None + associated_rule: Annotated[ + ReportingDescriptorReference | None, + Field( + alias="associatedRule", + description="A reference used to locate the rule descriptor associated with this notification.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the notification." + ), + ] = None + + +class ThreadFlow(BaseModel): + """Describes a sequence of code locations that specify a path through a single thread of execution such as an operating system or fiber.""" + + model_config = ConfigDict( + extra="forbid", + ) + id: Annotated[ + str | None, + Field( + description="An string that uniquely identifies the threadFlow within the codeFlow in which it occurs." + ), + ] = None + message: Annotated[ + Message | None, Field(description="A message relevant to the thread flow.") + ] = None + initial_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="initialState", + description="Values of relevant expressions at the start of the thread flow that may change during thread flow execution.", + ), + ] = None + immutable_state: Annotated[ + dict[str, MultiformatMessageString] | None, + Field( + alias="immutableState", + description="Values of relevant expressions at the start of the thread flow that remain constant.", + ), + ] = None + locations: Annotated[ + list[ThreadFlowLocation], + Field( + description="A temporally ordered array of 'threadFlowLocation' objects, each of which describes a location visited by the tool while producing the result.", + min_length=1, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the thread flow." + ), + ] = None + + +class CodeFlow(BaseModel): + """A set of threadFlows which together describe a pattern of code execution relevant to detecting a result.""" + + model_config = ConfigDict( + extra="forbid", + ) + message: Annotated[ + Message | None, Field(description="A message relevant to the code flow.") + ] = None + thread_flows: Annotated[ + list[ThreadFlow], + Field( + alias="threadFlows", + description="An array of one or more unique threadFlow objects, each of which describes the progress of a program through a thread of execution.", + min_length=1, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the code flow." + ), + ] = None + + +class Invocation(BaseModel): + """The runtime environment of the analysis tool run.""" + + model_config = ConfigDict( + extra="forbid", + ) + command_line: Annotated[ + str | None, + Field( + alias="commandLine", description="The command line used to invoke the tool." + ), + ] = None + arguments: Annotated[ + list[str] | None, + Field( + description="An array of strings, containing in order the command line arguments passed to the tool from the operating system.", + min_length=0, + ), + ] = None + response_files: Annotated[ + list[ArtifactLocation] | None, + Field( + alias="responseFiles", + description="The locations of any response files specified on the tool's command line.", + min_length=0, + ), + ] = None + start_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="startTimeUtc", + description='The Coordinated Universal Time (UTC) date and time at which the run started. See "Date/time properties" in the SARIF spec for the required format.', + ), + ] = None + end_time_utc: Annotated[ + AwareDatetime | None, + Field( + alias="endTimeUtc", + description='The Coordinated Universal Time (UTC) date and time at which the run ended. See "Date/time properties" in the SARIF spec for the required format.', + ), + ] = None + exit_code: Annotated[ + int | None, Field(alias="exitCode", description="The process exit code.") + ] = None + rule_configuration_overrides: Annotated[ + list[ConfigurationOverride] | None, + Field( + default_factory=list, + alias="ruleConfigurationOverrides", + description="An array of configurationOverride objects that describe rules related runtime overrides.", + min_length=0, + ), + ] + notification_configuration_overrides: Annotated[ + list[ConfigurationOverride] | None, + Field( + default_factory=list, + alias="notificationConfigurationOverrides", + description="An array of configurationOverride objects that describe notifications related runtime overrides.", + min_length=0, + ), + ] + tool_execution_notifications: Annotated[ + list[Notification] | None, + Field( + default_factory=list, + alias="toolExecutionNotifications", + description="A list of runtime conditions detected by the tool during the analysis.", + min_length=0, + ), + ] + tool_configuration_notifications: Annotated[ + list[Notification] | None, + Field( + default_factory=list, + alias="toolConfigurationNotifications", + description="A list of conditions detected by the tool that are relevant to the tool's configuration.", + min_length=0, + ), + ] + exit_code_description: Annotated[ + str | None, + Field( + alias="exitCodeDescription", description="The reason for the process exit." + ), + ] = None + exit_signal_name: Annotated[ + str | None, + Field( + alias="exitSignalName", + description="The name of the signal that caused the process to exit.", + ), + ] = None + exit_signal_number: Annotated[ + int | None, + Field( + alias="exitSignalNumber", + description="The numeric value of the signal that caused the process to exit.", + ), + ] = None + process_start_failure_message: Annotated[ + str | None, + Field( + alias="processStartFailureMessage", + description="The reason given by the operating system that the process failed to start.", + ), + ] = None + execution_successful: Annotated[ + bool, + Field( + alias="executionSuccessful", + description="Specifies whether the tool's execution completed successfully.", + ), + ] + machine: Annotated[ + str | None, Field(description="The machine that hosted the analysis tool run.") + ] = None + account: Annotated[ + str | None, Field(description="The account that ran the analysis tool.") + ] = None + process_id: Annotated[ + int | None, + Field( + alias="processId", description="The process id for the analysis tool run." + ), + ] = None + executable_location: Annotated[ + ArtifactLocation | None, + Field( + alias="executableLocation", + description="An absolute URI specifying the location of the analysis tool's executable.", + ), + ] = None + working_directory: Annotated[ + ArtifactLocation | None, + Field( + alias="workingDirectory", + description="The working directory for the analysis tool run.", + ), + ] = None + environment_variables: Annotated[ + dict[str, str] | None, + Field( + alias="environmentVariables", + description="The environment variables associated with the analysis tool process, expressed as key/value pairs.", + ), + ] = None + stdin: Annotated[ + ArtifactLocation | None, + Field( + description="A file containing the standard input stream to the process that was invoked." + ), + ] = None + stdout: Annotated[ + ArtifactLocation | None, + Field( + description="A file containing the standard output stream from the process that was invoked." + ), + ] = None + stderr: Annotated[ + ArtifactLocation | None, + Field( + description="A file containing the standard error stream from the process that was invoked." + ), + ] = None + stdout_stderr: Annotated[ + ArtifactLocation | None, + Field( + alias="stdoutStderr", + description="A file containing the interleaved standard output and standard error stream from the process that was invoked.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the invocation." + ), + ] = None + + +class Result(BaseModel): + """A result produced by an analysis tool.""" + + model_config = ConfigDict( + extra="forbid", + ) + rule_id: Annotated[ + str | None, + Field( + alias="ruleId", + description="The stable, unique identifier of the rule, if any, to which this notification is relevant. This member can be used to retrieve rule metadata from the rules dictionary, if it exists.", + ), + ] = None + rule_index: Annotated[ + int | None, + Field( + alias="ruleIndex", + description="The index within the tool component rules array of the rule object associated with this result.", + ge=-1, + ), + ] = -1 + rule: Annotated[ + ReportingDescriptorReference | None, + Field( + description="A reference used to locate the rule descriptor relevant to this result." + ), + ] = None + kind: Annotated[ + Literal["notApplicable", "pass", "fail", "review", "open", "informational"] + | None, + Field(description="A value that categorizes results by evaluation state."), + ] = "fail" + level: Annotated[ + Literal["none", "note", "warning", "error"] | None, + Field(description="A value specifying the severity level of the result."), + ] = "warning" + message: Annotated[ + Message, + Field( + description="A message that describes the result. The first sentence of the message only will be displayed when visible space is limited." + ), + ] + analysis_target: Annotated[ + ArtifactLocation | None, + Field( + alias="analysisTarget", + description="Identifies the artifact that the analysis tool was instructed to scan. This need not be the same as the artifact where the result actually occurred.", + ), + ] = None + locations: Annotated[ + list[Location] | None, + Field( + default_factory=list, + description="The set of locations where the result was detected. Specify only one location unless the problem indicated by the result can only be corrected by making a change at every specified location.", + min_length=0, + ), + ] + guid: Annotated[ + str | None, + Field( + description="A stable, unique identifer for the result in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + correlation_guid: Annotated[ + str | None, + Field( + alias="correlationGuid", + description="A stable, unique identifier for the equivalence class of logically identical results to which this result belongs, in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + occurrence_count: Annotated[ + int | None, + Field( + alias="occurrenceCount", + description="A positive integer specifying the number of times this logically unique result was observed in this run.", + ge=1, + ), + ] = None + partial_fingerprints: Annotated[ + dict[str, str] | None, + Field( + alias="partialFingerprints", + description="A set of strings that contribute to the stable, unique identity of the result.", + ), + ] = None + fingerprints: Annotated[ + dict[str, str] | None, + Field( + description="A set of strings each of which individually defines a stable, unique identity for the result." + ), + ] = None + stacks: Annotated[ + list[Stack] | None, + Field( + default_factory=list, + description="An array of 'stack' objects relevant to the result.", + min_length=0, + ), + ] + code_flows: Annotated[ + list[CodeFlow] | None, + Field( + default_factory=list, + alias="codeFlows", + description="An array of 'codeFlow' objects relevant to the result.", + min_length=0, + ), + ] + graphs: Annotated[ + list[Graph] | None, + Field( + default_factory=list, + description="An array of zero or more unique graph objects associated with the result.", + min_length=0, + ), + ] + graph_traversals: Annotated[ + list[GraphTraversal] | None, + Field( + default_factory=list, + alias="graphTraversals", + description="An array of one or more unique 'graphTraversal' objects.", + min_length=0, + ), + ] + related_locations: Annotated[ + list[Location] | None, + Field( + default_factory=list, + alias="relatedLocations", + description="A set of locations relevant to this result.", + min_length=0, + ), + ] + suppressions: Annotated[ + list[Suppression] | None, + Field( + description="A set of suppressions relevant to this result.", min_length=0 + ), + ] = None + baseline_state: Annotated[ + Literal["new", "unchanged", "updated", "absent"] | None, + Field( + alias="baselineState", + description="The state of a result relative to a baseline of a previous run.", + ), + ] = None + rank: Annotated[ + float | None, + Field( + description="A number representing the priority or importance of the result.", + ge=-1.0, + le=100.0, + ), + ] = -1.0 + attachments: Annotated[ + list[Attachment] | None, + Field( + default_factory=list, + description="A set of artifacts relevant to the result.", + min_length=0, + ), + ] + hosted_viewer_uri: Annotated[ + AnyUrl | None, + Field( + alias="hostedViewerUri", + description="An absolute URI at which the result can be viewed.", + ), + ] = None + work_item_uris: Annotated[ + list[AnyUrl] | None, + Field( + alias="workItemUris", + description="The URIs of the work items associated with this result.", + ), + ] = None + provenance: Annotated[ + ResultProvenance | None, + Field(description="Information about how and when the result was detected."), + ] = None + fixes: Annotated[ + list[Fix] | None, + Field( + default_factory=list, + description="An array of 'fix' objects, each of which represents a proposed fix to the problem indicated by the result.", + min_length=0, + ), + ] + taxa: Annotated[ + list[ReportingDescriptorReference] | None, + Field( + default_factory=list, + description="An array of references to taxonomy reporting descriptors that are applicable to the result.", + min_length=0, + ), + ] + web_request: Annotated[ + WebRequest | None, + Field( + alias="webRequest", description="A web request associated with this result." + ), + ] = None + web_response: Annotated[ + WebResponse | None, + Field( + alias="webResponse", + description="A web response associated with this result.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the result." + ), + ] = None + + +class Conversion(BaseModel): + """Describes how a converter transformed the output of a static analysis tool from the analysis tool's native output format into the SARIF format.""" + + model_config = ConfigDict( + extra="forbid", + ) + tool: Annotated[ + Tool, Field(description="A tool object that describes the converter.") + ] + invocation: Annotated[ + Invocation | None, + Field( + description="An invocation object that describes the invocation of the converter." + ), + ] = None + analysis_tool_log_files: Annotated[ + list[ArtifactLocation] | None, + Field( + default_factory=list, + alias="analysisToolLogFiles", + description="The locations of the analysis tool's per-run log files.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the conversion." + ), + ] = None + + +class ExternalProperties(BaseModel): + """The top-level element of an external property file.""" + + model_config = ConfigDict( + extra="forbid", + ) + schema_: Annotated[ + AnyUrl | None, + Field( + alias="schema", + description="The URI of the JSON schema corresponding to the version of the external property file format.", + ), + ] = None + version: Annotated[ + Literal["2.1.0"] | None, + Field( + description="The SARIF format version of this external properties object." + ), + ] = None + guid: Annotated[ + str | None, + Field( + description="A stable, unique identifer for this external properties object, in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + run_guid: Annotated[ + str | None, + Field( + alias="runGuid", + description="A stable, unique identifer for the run associated with this external properties object, in the form of a GUID.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + conversion: Annotated[ + Conversion | None, + Field( + description="A conversion object that will be merged with a separate run." + ), + ] = None + graphs: Annotated[ + list[Graph] | None, + Field( + default_factory=list, + description="An array of graph objects that will be merged with a separate run.", + min_length=0, + ), + ] + externalized_properties: Annotated[ + PropertyBag | None, + Field( + alias="externalizedProperties", + description="Key/value pairs that provide additional information that will be merged with a separate run.", + ), + ] = None + artifacts: Annotated[ + list[Artifact] | None, + Field( + description="An array of artifact objects that will be merged with a separate run.", + min_length=0, + ), + ] = None + invocations: Annotated[ + list[Invocation] | None, + Field( + default_factory=list, + description="Describes the invocation of the analysis tool that will be merged with a separate run.", + min_length=0, + ), + ] + logical_locations: Annotated[ + list[LogicalLocation] | None, + Field( + default_factory=list, + alias="logicalLocations", + description="An array of logical locations such as namespaces, types or functions that will be merged with a separate run.", + min_length=0, + ), + ] + thread_flow_locations: Annotated[ + list[ThreadFlowLocation] | None, + Field( + default_factory=list, + alias="threadFlowLocations", + description="An array of threadFlowLocation objects that will be merged with a separate run.", + min_length=0, + ), + ] + results: Annotated[ + list[Result] | None, + Field( + default_factory=list, + description="An array of result objects that will be merged with a separate run.", + min_length=0, + ), + ] + taxonomies: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Tool taxonomies that will be merged with a separate run.", + min_length=0, + ), + ] + driver: Annotated[ + ToolComponent | None, + Field( + description="The analysis tool object that will be merged with a separate run." + ), + ] = None + extensions: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Tool extensions that will be merged with a separate run.", + min_length=0, + ), + ] + policies: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Tool policies that will be merged with a separate run.", + min_length=0, + ), + ] + translations: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Tool translations that will be merged with a separate run.", + min_length=0, + ), + ] + addresses: Annotated[ + list[Address] | None, + Field( + default_factory=list, + description="Addresses that will be merged with a separate run.", + min_length=0, + ), + ] + web_requests: Annotated[ + list[WebRequest] | None, + Field( + default_factory=list, + alias="webRequests", + description="Requests that will be merged with a separate run.", + min_length=0, + ), + ] + web_responses: Annotated[ + list[WebResponse] | None, + Field( + default_factory=list, + alias="webResponses", + description="Responses that will be merged with a separate run.", + min_length=0, + ), + ] + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the external properties." + ), + ] = None + + +class Run(BaseModel): + """Describes a single run of an analysis tool, and contains the reported output of that run.""" + + model_config = ConfigDict( + extra="forbid", + ) + tool: Annotated[ + Tool, + Field( + description="Information about the tool or tool pipeline that generated the results in this run. A run can only contain results produced by a single tool or tool pipeline. A run can aggregate results from multiple log files, as long as context around the tool run (tool command-line arguments and the like) is identical for all aggregated files." + ), + ] + invocations: Annotated[ + list[Invocation] | None, + Field( + default_factory=list, + description="Describes the invocation of the analysis tool.", + min_length=0, + ), + ] + conversion: Annotated[ + Conversion | None, + Field( + description="A conversion object that describes how a converter transformed an analysis tool's native reporting format into the SARIF format." + ), + ] = None + language: Annotated[ + str | None, + Field( + description="The language of the messages emitted into the log file during this run (expressed as an ISO 639-1 two-letter lowercase culture code) and an optional region (expressed as an ISO 3166-1 two-letter uppercase subculture code associated with a country or region). The casing is recommended but not required (in order for this data to conform to RFC5646).", + pattern="^[a-zA-Z]{2}|^[a-zA-Z]{2}-[a-zA-Z]{2}]?$", + ), + ] = "en-US" + version_control_provenance: Annotated[ + list[VersionControlDetails] | None, + Field( + default_factory=list, + alias="versionControlProvenance", + description="Specifies the revision in version control of the artifacts that were scanned.", + min_length=0, + ), + ] + original_uri_base_ids: Annotated[ + dict[str, ArtifactLocation] | None, + Field( + alias="originalUriBaseIds", + description="The artifact location specified by each uriBaseId symbol on the machine where the tool originally ran.", + ), + ] = None + artifacts: Annotated[ + list[Artifact] | None, + Field( + description="An array of artifact objects relevant to the run.", + min_length=0, + ), + ] = None + logical_locations: Annotated[ + list[LogicalLocation] | None, + Field( + default_factory=list, + alias="logicalLocations", + description="An array of logical locations such as namespaces, types or functions.", + min_length=0, + ), + ] + graphs: Annotated[ + list[Graph] | None, + Field( + default_factory=list, + description="An array of zero or more unique graph objects associated with the run.", + min_length=0, + ), + ] + results: Annotated[ + list[Result] | None, + Field( + description="The set of results contained in an SARIF log. The results array can be omitted when a run is solely exporting rules metadata. It must be present (but may be empty) if a log file represents an actual scan.", + min_length=0, + ), + ] = None + automation_details: Annotated[ + RunAutomationDetails | None, + Field( + alias="automationDetails", + description="Automation details that describe this run.", + ), + ] = None + run_aggregates: Annotated[ + list[RunAutomationDetails] | None, + Field( + default_factory=list, + alias="runAggregates", + description="Automation details that describe the aggregate of runs to which this run belongs.", + min_length=0, + ), + ] + baseline_guid: Annotated[ + str | None, + Field( + alias="baselineGuid", + description="The 'guid' property of a previous SARIF 'run' that comprises the baseline that was used to compute result 'baselineState' properties for the run.", + pattern="^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$", + ), + ] = None + redaction_tokens: Annotated[ + list[str] | None, + Field( + alias="redactionTokens", + description="An array of strings used to replace sensitive information in a redaction-aware property.", + min_length=0, + ), + ] = [] + default_encoding: Annotated[ + str | None, + Field( + alias="defaultEncoding", + description="Specifies the default encoding for any artifact object that refers to a text file.", + ), + ] = None + default_source_language: Annotated[ + str | None, + Field( + alias="defaultSourceLanguage", + description="Specifies the default source language for any artifact object that refers to a text file that contains source code.", + ), + ] = None + newline_sequences: Annotated[ + list[str] | None, + Field( + alias="newlineSequences", + description="An ordered list of character sequences that were treated as line breaks when computing region information for the run.", + min_length=1, + ), + ] = ["\r\n", "\n"] + column_kind: Annotated[ + Literal["utf16CodeUnits", "unicodeCodePoints"] | None, + Field( + alias="columnKind", + description="Specifies the unit in which the tool measures columns.", + ), + ] = None + external_property_file_references: Annotated[ + ExternalPropertyFileReferences | None, + Field( + alias="externalPropertyFileReferences", + description="References to external property files that should be inlined with the content of a root log file.", + ), + ] = None + thread_flow_locations: Annotated[ + list[ThreadFlowLocation] | None, + Field( + default_factory=list, + alias="threadFlowLocations", + description="An array of threadFlowLocation objects cached at run level.", + min_length=0, + ), + ] + taxonomies: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="An array of toolComponent objects relevant to a taxonomy in which results are categorized.", + min_length=0, + ), + ] + addresses: Annotated[ + list[Address] | None, + Field( + default_factory=list, + description="Addresses associated with this run instance, if any.", + min_length=0, + ), + ] + translations: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="The set of available translations of the localized data provided by the tool.", + min_length=0, + ), + ] + policies: Annotated[ + list[ToolComponent] | None, + Field( + default_factory=list, + description="Contains configurations that may potentially override both reportingDescriptor.defaultConfiguration (the tool's default severities) and invocation.configurationOverrides (severities established at run-time from the command line).", + min_length=0, + ), + ] + web_requests: Annotated[ + list[WebRequest] | None, + Field( + default_factory=list, + alias="webRequests", + description="An array of request objects cached at run level.", + min_length=0, + ), + ] + web_responses: Annotated[ + list[WebResponse] | None, + Field( + default_factory=list, + alias="webResponses", + description="An array of response objects cached at run level.", + min_length=0, + ), + ] + special_locations: Annotated[ + SpecialLocations | None, + Field( + alias="specialLocations", + description="A specialLocations object that defines locations of special significance to SARIF consumers.", + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the run." + ), + ] = None + + +class StaticAnalysisResultsFormatSarifVersion210JsonSchema(BaseModel): + """Static Analysis Results Format (SARIF) Version 2.1.0 JSON Schema: a standard format for the output of static analysis tools.""" + + model_config = ConfigDict( + extra="forbid", + ) + field_schema: Annotated[ + AnyUrl | None, + Field( + alias="$schema", + description="The URI of the JSON schema corresponding to the version.", + ), + ] = None + version: Annotated[ + Literal["2.1.0"], + Field(description="The SARIF format version of this log file."), + ] + runs: Annotated[ + list[Run], + Field(description="The set of runs contained in this log file.", min_length=0), + ] + inline_external_properties: Annotated[ + list[ExternalProperties] | None, + Field( + alias="inlineExternalProperties", + description="References to external property files that share data between runs.", + min_length=0, + ), + ] = None + properties: Annotated[ + PropertyBag | None, + Field( + description="Key/value pairs that provide additional information about the log file." + ), + ] = None + + +Node.model_rebuild() +Exception.model_rebuild() diff --git a/codesectools/sasts/core/parser/format/SARIF/parser.py b/codesectools/sasts/core/parser/format/SARIF/parser.py new file mode 100644 index 0000000..90dd79d --- /dev/null +++ b/codesectools/sasts/core/parser/format/SARIF/parser.py @@ -0,0 +1,156 @@ +"""Provide a base parser for SAST tools that output in SARIF format.""" + +import json +from abc import abstractmethod +from pathlib import Path +from typing import Self + +from codesectools.sasts.core.parser import AnalysisResult, Defect +from codesectools.sasts.core.parser.format.SARIF import ( + PropertyBag, + ReportingDescriptor, + Result, +) +from codesectools.sasts.core.parser.format.SARIF import ( + StaticAnalysisResultsFormatSarifVersion210JsonSchema as SARIF, +) +from codesectools.sasts.core.sast import AnalysisInfo +from codesectools.shared.cwe import CWE +from codesectools.utils import MissingFile + + +class SARIFAnalysisResult(AnalysisResult): + """Abstract base class for parsing SARIF formatted analysis results.""" + + def __init__( + self, output_dir: Path, sarif_dict: dict, analysis_info: AnalysisInfo + ) -> None: + """Initialize the SARIFAnalysisResult. + + Args: + output_dir: The directory containing the analysis output. + sarif_dict: The raw SARIF data as a dictionary. + analysis_info: Metadata about the analysis run. + + """ + super().__init__( + name=output_dir.name, + source_path=Path(analysis_info.project_dir), + lang=analysis_info.lang, + defects=[], + time=analysis_info.duration, + lines_of_codes=analysis_info.lines_of_codes, + ) + + self.output_dir = output_dir + self.sarif_dict = self.patch_dict(sarif_dict.copy()) + + self.sarif = SARIF.model_validate(self.sarif_dict) + self.run = self.sarif.runs[0] + self.rules = self.get_rules() + self.raw_rules = self.get_raw_rules() + self.results = self.run.results or [] + + for result in self.results: + filepath, lines = self.get_location(result) + + if rule_id := result.rule_id: + rule = self.rules[rule_id] + + if rule.default_configuration: + level = rule.default_configuration.level or "none" + if result.level: + level = result.level or "none" + else: + level = "none" + + cwe = self.get_cwe(result, rule_id) + else: + continue + + message = result.message.root.text or "" + + self.defects.append( + Defect( + sast_name=self.sast_name, + filepath=filepath, + checker=rule_id, + level=level, + cwe=cwe, + message=message, + lines=lines, + ) + ) + + def patch_dict(self, sarif_dict: dict) -> dict: + """Patch the SARIF dictionary to fix common issues before parsing.""" + return sarif_dict + + def save_patched_dict(self, patched_dict: dict) -> None: + """Save the patched dictionary to a file for debugging.""" + json.dump( + patched_dict, + (self.output_dir / f"{self.sast_name.lower()}_patched.sarif").open("w"), + ) + + def get_rules(self) -> dict[str, ReportingDescriptor]: + """Extract and return all rule descriptors from the SARIF data.""" + rules = {} + if self.run.tool.driver.rules: + for rule in self.run.tool.driver.rules: + if rule: + rules[rule.id] = rule + return rules + + @staticmethod + def get_raw_rules() -> dict: + """Get the raw rule definitions, often from a non-SARIF source.""" + return {} + + def get_rule_properties(self, rule_id: str) -> PropertyBag | None: + """Get the properties for a specific rule ID.""" + if rule := self.rules[rule_id]: + if properties := rule.properties: + return properties + return None + + def get_location(self, result: Result) -> tuple[Path, list[int] | None]: + """Extract the file path and line numbers from a SARIF result.""" + lines = None + if result.locations: + if physical_location := result.locations[0].physical_location: + if root := physical_location.root: + if artifact_location := root.artifact_location: + if uri := artifact_location.uri: + filepath = Path(uri) + + if region := root.region: + lines = [ + line + for line in [region.start_line, region.end_line] + if line + ] + + return filepath, lines + + @abstractmethod + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given result and rule ID.""" + pass + + @classmethod + def load_from_output_dir(cls, output_dir: Path) -> Self: + """Load and parse a SARIF report from an output directory.""" + # Analysis Info + analysis_info = AnalysisInfo.model_validate_json( + (output_dir / "codesectools.json").read_text() + ) + + # Analysis outputs + sarif_report_path = output_dir / f"{cls.sast_name.lower()}.sarif" + if sarif_report_path.is_file(): + sarif_dict = json.load(sarif_report_path.open()) + else: + raise MissingFile([str(sarif_report_path)]) + + return cls(output_dir, sarif_dict, analysis_info) diff --git a/codesectools/sasts/core/sast/__init__.py b/codesectools/sasts/core/sast/__init__.py index 388f83f..28cdd3d 100644 --- a/codesectools/sasts/core/sast/__init__.py +++ b/codesectools/sasts/core/sast/__init__.py @@ -5,7 +5,6 @@ performing benchmarks against datasets. """ -import json import os import random import shutil @@ -15,6 +14,7 @@ from pathlib import Path from typing import Any, Literal, Union +from pydantic import BaseModel from rich import print from codesectools.datasets import DATASETS_ALL @@ -37,6 +37,26 @@ ) +class AnalysisInfo(BaseModel): + """Represents metadata from a SAST analysis. + + Attributes: + project_dir: The absolute path to the project directory that was analyzed. + lang: The programming language of the project. + command_lines: The command lines that were executed for the analysis. + duration: The duration of the analysis in seconds. + lines_of_codes: The number of lines of code in the project for the given language. + + """ + + project_dir: str + lang: str + command_lines: list[str] + logs: str + duration: float + lines_of_codes: int + + class SAST(ABC): """Abstract base class for a SAST tool integration. @@ -56,7 +76,7 @@ class SAST(ABC): output_files (list[tuple[Path, bool]]): Expected output files and whether they are required. parser (type[AnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. + level_color_map (dict): A mapping of result levels to colors for plotting. install_help (str | None): An optional string with installation help. output_dir (Path): (Instance attribute) The base directory for storing analysis results. @@ -78,7 +98,7 @@ class SAST(ABC): environ: dict[str, str] = {} output_files: list[tuple[Path, bool]] parser: AnalysisResult - color_mapping: dict + level_color_map: dict install_help: str | None = None def __init__(self) -> None: @@ -139,26 +159,31 @@ def run_analysis( command_output = "" start = time.time() - for command in self.commands: - rendered_command = render_command(command, render_variables) + rendered_commands = [ + render_command(command, render_variables) for command in self.commands + ] + for rendered_command in rendered_commands: retcode, out = run_command(rendered_command, project_dir, self.environ) command_output += out if retcode not in self.valid_codes: raise NonZeroExit(rendered_command, command_output) end = time.time() - loc = Cloc(project_dir, lang).get_loc() + lines_of_codes = Cloc(project_dir, lang).get_loc() - extra = { - "lang": lang, - "logs": command_output, - "duration": end - start, - "loc": loc, - "project_dir": str(project_dir.resolve()), - } - self.save_results(project_dir, output_dir, extra) + analysis_info = AnalysisInfo( + command_lines=[" ".join(c) for c in rendered_commands], + logs=command_output, + lang=lang, + duration=end - start, + lines_of_codes=lines_of_codes, + project_dir=str(project_dir.resolve()), + ) + self.save_results(project_dir, output_dir, analysis_info) - def save_results(self, project_dir: Path, output_dir: Path, extra: dict) -> None: + def save_results( + self, project_dir: Path, output_dir: Path, analysis_info: AnalysisInfo + ) -> None: """Save the results of a SAST analysis. Copy the tool's output files and save any extra metadata to the result directory. @@ -166,11 +191,13 @@ def save_results(self, project_dir: Path, output_dir: Path, extra: dict) -> None Args: project_dir: The directory where the analysis was run. output_dir: The directory where results should be saved. - extra: A dictionary of extra metadata to save as JSON. + analysis_info: An `AnalysisInfo` object containing metadata about the analysis. """ output_dir.mkdir(exist_ok=True, parents=True) - json.dump(extra, (output_dir / "cstools_output.json").open("w"), indent=4) + (output_dir / "codesectools.json").write_text( + analysis_info.model_dump_json(indent=4) + ) missing_files = [] for path_from_root, required in self.output_files: diff --git a/codesectools/sasts/core/sast/requirements.py b/codesectools/sasts/core/sast/requirements.py index 816aa00..921f130 100644 --- a/codesectools/sasts/core/sast/requirements.py +++ b/codesectools/sasts/core/sast/requirements.py @@ -153,7 +153,7 @@ def check(self, binary: Binary) -> bool: True if the version is sufficient, False otherwise. """ - retcode, output = run_command([binary.name, self.command_flag]) + retcode, output = run_command([binary.name, self.command_flag], silent=True) if m := re.search(self.pattern, output): detected_version = version.parse(m.group(1)) return detected_version >= self.expected @@ -369,10 +369,16 @@ def __init__( self.all = full_reqs + partial_reqs def get_status(self) -> Literal["full"] | Literal["partial"] | Literal["none"]: - """Determine the operational status (full, partial, none) based on fulfilled requirements.""" - # full: can run sast analysis and result parsing - # partial: can run result parsing - # none: nothing + """Determine the operational status based on fulfilled requirements. + + - 'full': All requirements met; can run analysis and parse results. + - 'partial': Only partial requirements met; can only parse existing results. + - 'none': Core requirements not met; the tool is not usable. + + Returns: + The operational status as a string: "full", "partial", or "none". + + """ status = "none" if all(req.is_fulfilled(sast_name=self.name) for req in self.partial): status = "partial" diff --git a/codesectools/sasts/tools/Bearer/parser.py b/codesectools/sasts/tools/Bearer/parser.py index 7d2e946..3ae36d9 100644 --- a/codesectools/sasts/tools/Bearer/parser.py +++ b/codesectools/sasts/tools/Bearer/parser.py @@ -5,94 +5,48 @@ format used by CodeSecTools. """ -import json -from pathlib import Path -from typing import Self - -from codesectools.sasts.core.parser import AnalysisResult, Defect -from codesectools.shared.cwe import CWEs -from codesectools.utils import MissingFile - - -class BearerFinding(Defect): - """Represent a single defect found by Bearer.""" - - sast = "Bearer" - - def __init__(self, defect_data: dict, severity: str) -> None: - """Initialize a BearerFinding instance. - - Args: - defect_data: A dictionary representing a single finding from the JSON output. - severity: The severity level of the finding. - - """ - super().__init__( - filepath=Path(defect_data["filename"]), - checker=defect_data["id"], - category=severity, - cwe=CWEs.from_id(int(defect_data["cwe_ids"][0])), - message=defect_data["description"].split("\n")[2], - lines=[defect_data["line_number"]], - data=defect_data, - ) - - -class BearerAnalysisResult(AnalysisResult): - """Represent the complete result of a Bearer analysis.""" - - def __init__(self, output_dir: Path, result_data: dict, cmdout: dict) -> None: - """Initialize a BearerAnalysisResult instance. - - Args: - output_dir: The directory where the results are stored. - result_data: Parsed data from the main Bearer JSON output. - cmdout: A dictionary with metadata from the command execution. - - """ - super().__init__( - name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=[], - defects=[], - time=cmdout["duration"], - loc=cmdout["loc"], - data=(result_data, cmdout), - ) - - for severity, findings in result_data.items(): - for finding in findings: - self.files.append(Path(finding["filename"]).name) - self.defects.append( - BearerFinding(defect_data=finding, severity=severity) - ) - - @classmethod - def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse Bearer analysis results from a directory. - - Read `bearer_output.json` and `cstools_output.json` to construct a complete - analysis result object. - - Args: - output_dir: The directory containing the Bearer output files. - - Returns: - An instance of `BearerAnalysisResult`. - - Raises: - MissingFile: If a required result file is not found. - - """ - # Cmdout - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis outputs - analysis_output_path = output_dir / "bearer_output.json" - if analysis_output_path.is_file(): - analysis_output = json.load(analysis_output_path.open("r")) - else: - raise MissingFile(["bearer_output.json"]) - - return cls(output_dir, analysis_output, cmdout) +from itertools import chain + +import yaml + +from codesectools.sasts.core.parser.format.SARIF import Result +from codesectools.sasts.core.parser.format.SARIF.parser import SARIFAnalysisResult +from codesectools.shared.cwe import CWE, CWEs +from codesectools.utils import USER_CACHE_DIR + +BEARER_RULES_DIR = USER_CACHE_DIR / "bearer-rules" / "rules" + + +class BearerAnalysisResult(SARIFAnalysisResult): + """Represent the complete result of a Bearer analysis from a SARIF file.""" + + sast_name = "Bearer" + + @staticmethod + # @Cache(BEARER_RULES_DIR / ".cstools_cache").memoize(expire=None) + def get_raw_rules() -> dict: + """Load and return all Bearer rules from the cached YAML files.""" + raw_rules = {} + if BEARER_RULES_DIR.is_dir(): + rule_paths = chain( + BEARER_RULES_DIR.rglob("*.yml"), BEARER_RULES_DIR.rglob("*.yaml") + ) + for rule_path in rule_paths: + try: + data = yaml.safe_load(rule_path.open("r")) + rule_id = data["metadata"]["id"] + raw_rules[rule_id] = data + + for aux in data.get("auxiliary", []): + raw_rules[aux["id"]] = data + except (TypeError, KeyError, yaml.composer.ComposerError): # ty:ignore[possibly-missing-attribute] + pass + return raw_rules + + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given rule ID.""" + raw_rule = self.raw_rules[rule_id] + if cwe_ids := raw_rule["metadata"].get("cwe_id"): + cwe_id = int(cwe_ids[0]) + return CWEs.from_id(cwe_id) + return CWEs.NOCWE diff --git a/codesectools/sasts/tools/Bearer/sast.py b/codesectools/sasts/tools/Bearer/sast.py index fedcbc8..921bb96 100644 --- a/codesectools/sasts/tools/Bearer/sast.py +++ b/codesectools/sasts/tools/Bearer/sast.py @@ -13,8 +13,10 @@ GitRepo, SASTRequirements, ) -from codesectools.sasts.tools.Bearer.parser import BearerAnalysisResult -from codesectools.utils import USER_CACHE_DIR +from codesectools.sasts.tools.Bearer.parser import ( + BEARER_RULES_DIR, + BearerAnalysisResult, +) class BearerSAST(BuildlessSAST): @@ -31,7 +33,7 @@ class BearerSAST(BuildlessSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[BearerAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. + level_color_map (dict): A mapping of result levels to colors for plotting. """ @@ -57,23 +59,16 @@ class BearerSAST(BuildlessSAST): ".", "--force", "--disable-default-rules", - f"--external-rule-dir={str(USER_CACHE_DIR / 'bearer-rules' / 'rules' / '{lang}')}", + f"--external-rule-dir={str(BEARER_RULES_DIR / '{lang}')}", "--scanner=sast", - "--format=json", - "--output=bearer_output.json", + "--format=sarif", + "--output=bearer.sarif", "--disable-version-check", "--exit-code=0", ] ] valid_codes = [0] output_files = [ - (Path("bearer_output.json"), True), + (Path("bearer.sarif"), True), ] parser = BearerAnalysisResult - color_mapping = { - "critical": "red", - "high": "red", - "medium": "orange", - "low": "yellow", - "warning": "yellow", - } diff --git a/codesectools/sasts/tools/Coverity/parser.py b/codesectools/sasts/tools/Coverity/parser.py index e3b9f66..9339cd5 100644 --- a/codesectools/sasts/tools/Coverity/parser.py +++ b/codesectools/sasts/tools/Coverity/parser.py @@ -1,274 +1,105 @@ -"""Provides classes for parsing Coverity analysis results. - -This module defines `CoverityDefect` and `CoverityAnalysisResult` to process -the XML and YAML output from a Coverity scan, converting it into the standardized -format used by CodeSecTools. -""" +"""Provide a base parser for SAST tools that output in SARIF format.""" import json -import re -from functools import lru_cache from pathlib import Path from typing import Self from codesectools.sasts.core.parser import AnalysisResult, Defect +from codesectools.sasts.core.parser.format.CoverityJsonOutputV10 import ( + CoverityJsonOutputV10, +) +from codesectools.sasts.core.sast import AnalysisInfo from codesectools.shared.cwe import CWEs -from codesectools.utils import USER_CONFIG_DIR, MissingFile - -USER_COVERITY_DIR = USER_CONFIG_DIR / "Coverity" - - -class CoverityConfig: - """Handle the loading and parsing of Coverity configuration files.""" - - def __init__(self) -> None: - """Initialize the CoverityConfig instance.""" - self._type_to_cwe = None - self._languages = None - self._color_mapping = None - - @staticmethod - @lru_cache(maxsize=None) - def _load_issue_types_file() -> dict | None: - """Load and parse the issueTypes.json file.""" - types_file = USER_COVERITY_DIR / "issueTypes.json" - if types_file.is_file(): - return json.load(types_file.open()) - return None - - @staticmethod - @lru_cache(maxsize=None) - def _load_config_file() -> dict | None: - """Load and parse the config.json file.""" - config_file = USER_COVERITY_DIR / "config.json" - if config_file.is_file(): - return json.load(config_file.open()) - return None - - @property - def type_to_cwe(self) -> dict: - """Get a mapping from Coverity issue types to CWE IDs.""" - if self._type_to_cwe is None: - types_data = self._load_issue_types_file() - if types_data and "issue_type" in types_data: - self._type_to_cwe = { - type_info["type"]: type_info["cim_checker_properties"][ - "cweCategory" - ] - for type_info in types_data["issue_type"] - } - else: - self._type_to_cwe = {} - return self._type_to_cwe - - @property - def languages(self) -> dict: - """Get the language configuration for Coverity.""" - if self._languages is None: - config_data = self._load_config_file() - if config_data and "languages" in config_data: - self._languages = config_data["languages"] - else: - self._languages = {} - return self._languages - - @property - def color_mapping(self) -> dict: - """Get the color mapping for Coverity issue categories.""" - if self._color_mapping is None: - config_data = self._load_config_file() - if config_data and "color_mapping" in config_data: - self._color_mapping = config_data["color_mapping"] - else: - self._color_mapping = {} - return self._color_mapping - - -class CoverityDefect(Defect): - """Represents a single defect found by Coverity. - - Parses defect data to extract file, checker, category, and CWE information. - It also determines the checker category based on predefined sets. - - Attributes: - lang (str): The programming language of the file with the defect. - function (str): The function in which the defect was found. - - """ - - sast = "Coverity" - - def __init__(self, defect_data: dict) -> None: - """Initialize a CoverityDefect instance from raw defect data. - - Args: - defect_data: A dictionary representing a single defect, parsed - from Coverity's XML output. - - """ - super().__init__( - filepath=Path(defect_data["file"]), - checker=defect_data["checker"], - category="", - cwe=CWEs.from_id(CoverityConfig().type_to_cwe.get(defect_data["type"], -1)), - message="", # TODO - lines=[defect_data["line"]], - data=defect_data, - ) - - self.lang = defect_data["lang"].lower() - - if self.checker.startswith("SIGMA"): - self.category = "SIGMA" - elif self.checker.startswith("FB"): - self.category = "SPOTBUGS" - else: - if self.lang in CoverityConfig().languages.keys(): - for set_name, checker_set in ( - CoverityConfig().languages[self.lang]["checker_sets"].items() - ): - if self.checker in checker_set: - self.category = set_name - break - - # Extra - self.function = defect_data["function"] +from codesectools.utils import MissingFile class CoverityAnalysisResult(AnalysisResult): - """Represents the complete result of a Coverity analysis. - - Parses various output files from a Coverity run to populate analysis - metadata, including metrics, configuration, file lists, and defects. - - Attributes: - metrics (dict): A dictionary of metrics from the analysis. - config (dict): The Coverity configuration used for the scan. - analysis_cmd (str): The command used to run the analysis. - code_lines_by_lang (dict): A dictionary mapping languages to their - line counts. + """Represent the complete result of a Coverity analysis.""" - """ + sast_name = "Coverity" + impact_level_mapping = { + "High": "error", + "Medium": "warning", + "Low": "note", + "Audit": "none", + } def __init__( - self, - output_dir: Path, - result_data: dict, - config_data: str, - captured_list: str, - defects: list[Defect], - cmdout: dict, + self, output_dir: Path, coverity_dict: dict, analysis_info: AnalysisInfo ) -> None: """Initialize a CoverityAnalysisResult instance. Args: - output_dir: The directory where the results are stored. - result_data: Parsed data from ANALYSIS.metrics.xml. - config_data: Parsed data from coverity.yaml. - captured_list: A string containing the list of captured source files. - defects: A list of `CoverityDefect` objects. - cmdout: A dictionary with metadata from the command execution. + output_dir: The directory containing the analysis output. + coverity_dict: The parsed Coverity JSON dictionary. + analysis_info: The analysis metadata. """ super().__init__( name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=[], - defects=defects, - time=0, - loc=0, - data=(result_data, config_data, captured_list, cmdout), + source_path=Path(analysis_info.project_dir), + lang=analysis_info.lang, + defects=[], + time=analysis_info.duration, + lines_of_codes=analysis_info.lines_of_codes, ) - self.metrics = {} - for metric in result_data["coverity"]["metrics"]["metric"]: - self.metrics[metric["name"]] = metric["value"] + self.output_dir = output_dir + self.cov_json = CoverityJsonOutputV10.model_validate(coverity_dict) + self.issues = self.cov_json.issues - self.time = int(self.metrics["time"]) + for issue in self.issues: + if issue.language: + if issue.language.lower() != self.lang: + continue + else: + continue - self.files = list(map(lambda line: str(Path(line)), captured_list.splitlines())) + checker = issue.checker_name + filepath = Path(issue.main_event_file_pathname) + lines = [issue.main_event_line_number] + event_descriptions = [] - file_count = 0 - for lang, pattern in CoverityConfig().languages.items(): - include = pattern["include"] - exclude = pattern["exclude"] - files = [ - file - for file in self.files - if re.search(include, file) and not re.search(exclude, file) - ] - if len(files) > file_count: - file_count = len(files) - self.lang = lang + for event in issue.events: + event_descriptions.append( + f"L{event.line_number}: {event.event_description}" + ) - # Extra - self.config = config_data - self.analysis_cmd = self.metrics["args"] - self.code_lines_by_lang = {} - for key in self.metrics.keys(): - if r := re.search(r"(.*)-code-lines", key): - self.code_lines_by_lang[r.group(1)] = int(self.metrics[key]) - self.loc = self.code_lines_by_lang[self.lang] + if cwe_str := issue.checker_properties.cwe_category: + cwe = CWEs.from_id(int(cwe_str)) + else: + cwe = CWEs.NOCWE + message = "\n".join( + [issue.checker_properties.subcategory_long_description] + + event_descriptions + ) + level = self.impact_level_mapping[issue.checker_properties.impact] + + self.defects.append( + Defect( + sast_name=self.sast_name, + filepath=filepath, + checker=checker, + level=level, # ty:ignore[invalid-argument-type] + cwe=cwe, + message=message, + lines=lines, + ) + ) @classmethod def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse Coverity analysis results from a directory. - - Reads ANALYSIS.metrics.xml, coverity.yaml, captured file lists, and - error XML files to construct a complete analysis result object. - - Args: - output_dir: The directory containing the Coverity output files. - - Returns: - An instance of `CoverityAnalysisResult`. - - Raises: - MissingFile: If a required result file is not found. - - """ - import xmltodict - import yaml - - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis metrics - filepath = output_dir / "ANALYSIS.metrics.xml" - if filepath.is_file(): - analysis_data = xmltodict.parse(filepath.open("rb")) - else: - raise MissingFile(["ANALYSIS.metrics.xml"]) + """Load and parse a SARIF report from an output directory.""" + # Analysis Info + analysis_info = AnalysisInfo.model_validate_json( + (output_dir / "codesectools.json").read_text() + ) - # Config - filepath = output_dir / "coverity.yaml" - if filepath.is_file(): - config_data = yaml.load(filepath.open("r"), Loader=yaml.Loader) + # Analysis outputs + coveirty_report_path = output_dir / "coverity.json" + if coveirty_report_path.is_file(): + coverity_dict = json.load(coveirty_report_path.open()) else: - config_data = "" - - # Captured source file list - captured_list = "" - for file in output_dir.glob("capture-files-src-list*"): - captured_list += open(file, "r").read() - - # Defects - defects = [] - for file in output_dir.glob("*errors.xml"): - f = open(file, "r") - try: - errors = xmltodict.parse(f"{f.read()}".encode())["root"][ - "error" - ] - except TypeError: - pass + raise MissingFile([str(coveirty_report_path)]) - if isinstance(errors, list): - for error in errors: - defects.append(CoverityDefect(error)) - else: - defects.append(CoverityDefect(errors)) - - return cls( - output_dir, analysis_data, config_data, captured_list, defects, cmdout - ) + return cls(output_dir, coverity_dict, analysis_info) diff --git a/codesectools/sasts/tools/Coverity/sast.py b/codesectools/sasts/tools/Coverity/sast.py index 6215276..0f80a6c 100644 --- a/codesectools/sasts/tools/Coverity/sast.py +++ b/codesectools/sasts/tools/Coverity/sast.py @@ -8,10 +8,9 @@ from codesectools.sasts.core.sast import BuildlessSAST from codesectools.sasts.core.sast.properties import SASTProperties -from codesectools.sasts.core.sast.requirements import Binary, Config, SASTRequirements +from codesectools.sasts.core.sast.requirements import Binary, SASTRequirements from codesectools.sasts.tools.Coverity.parser import ( CoverityAnalysisResult, - CoverityConfig, ) @@ -29,12 +28,11 @@ class CoveritySAST(BuildlessSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[CoverityAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. """ name = "Coverity" - supported_languages = CoverityConfig().languages.keys() + supported_languages = ["c", "java"] supported_dataset_names = ["BenchmarkJava", "CVEfixes"] properties = SASTProperties(free=False, offline=True) requirements = SASTRequirements( @@ -47,33 +45,18 @@ class CoveritySAST(BuildlessSAST): "cov-analyze", url="https://documentation.blackduck.com/bundle/coverity-docs/page/deploy-install-guide/topics/installing_coverity_analysis_components.html", ), + Binary( + "cov-format-errors", + url="https://documentation.blackduck.com/bundle/coverity-docs/page/deploy-install-guide/topics/installing_coverity_analysis_components.html", + ), ], - partial_reqs=[ - Config("issueTypes.json", doc=True, sast_name=name), - Config("config.json", doc=True, sast_name=name), - ], + partial_reqs=[], ) commands = [ - [ - "coverity", - "capture", - "--disable-build-command-inference", - "--language", - "{lang}", - ], - [ - "cov-analyze", - "--dir", - "idir", - "--all-security", - "--enable-callgraph-metrics", - ], + ["coverity", "capture", "--disable-build-command-inference"], + ["cov-analyze", "--dir", "idir", "--all-security", "--disable-spotbugs"], + ["cov-format-errors", "--dir", "idir", "--json-output-v10", "coverity.json"], ] valid_codes = [0] - output_files = [ - (Path("coverity.yaml"), False), - (Path("idir", "coverity-cli", "capture-files-src-list*"), True), - (Path("idir", "output", "*.xml"), False), - ] + output_files = [(Path("coverity.json"), True)] parser = CoverityAnalysisResult - color_mapping = CoverityConfig().color_mapping diff --git a/codesectools/sasts/tools/Cppcheck/parser.py b/codesectools/sasts/tools/Cppcheck/parser.py index 017db8f..ae2e235 100644 --- a/codesectools/sasts/tools/Cppcheck/parser.py +++ b/codesectools/sasts/tools/Cppcheck/parser.py @@ -7,119 +7,28 @@ from __future__ import annotations -import json -from pathlib import Path -from typing import TYPE_CHECKING, Self +import re +from typing import TYPE_CHECKING -from codesectools.sasts.core.parser import AnalysisResult, Defect +from codesectools.sasts.core.parser.format.SARIF.parser import SARIFAnalysisResult from codesectools.shared.cwe import CWE, CWEs -from codesectools.utils import MissingFile if TYPE_CHECKING: - from lxml.etree import ElementTree + from codesectools.sasts.core.parser.format.SARIF import Result -class CppcheckError(Defect): - """Represent a single error reported by Cppcheck.""" - - sast = "Cppcheck" - - def __init__( - self, - filepath: Path, - checker: str, - category: str, - cwe: CWE, - message: str, - lines: list[int] | None, - data: dict, - ) -> None: - """Initialize a CppcheckError instance. - - Args: - filepath: The file path of the defect. - checker: The name of the rule/checker. - category: The category of the checker. - cwe: The CWE associated with the defect. - message: The description of the defect. - lines: A list of line numbers where the defect is located. - data: Raw data from the SAST tool for this defect. - - """ - super().__init__(filepath, checker, category, cwe, message, lines, data) - - -class CppcheckAnalysisResult(AnalysisResult): +class CppcheckAnalysisResult(SARIFAnalysisResult): """Represent the complete result of a Cppcheck analysis.""" - def __init__(self, output_dir: Path, xml_tree: ElementTree, cmdout: dict) -> None: - """Initialize a CppcheckAnalysisResult instance. - - Args: - output_dir: The directory where the results are stored. - xml_tree: Parsed data from the main Cppcheck XML output. - cmdout: A dictionary with metadata from the command execution. - - """ - super().__init__( - name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=[], - defects=[], - time=cmdout["duration"], - loc=cmdout["loc"], - data=(xml_tree, cmdout), - ) - - errors = xml_tree.xpath("/results/errors/error") - for error in errors: - category = error.get("severity") - if category in ["error", "warning", "style"]: - self.defects.append( - CppcheckError( - filepath=Path(error.xpath("location")[0].get("file")), - checker=error.get("id"), - category=category, - cwe=CWEs.from_id(int(error.get("cwe", -1))), - message=error.get("msg"), - lines=[ - int(location.get("line")) - for location in error.xpath("location") - ], - data=error.attrib, - ) - ) - - self.files = list(set(d.filepath_str for d in self.defects)) - - @classmethod - def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse Cppcheck analysis results from a directory. - - Read `cppcheck_output.xml` and `cstools_output.json` to construct a complete - analysis result object. - - Args: - output_dir: The directory containing the Cppcheck output files. - - Returns: - An instance of `CppcheckAnalysisResult`. - - Raises: - MissingFile: If a required result file is not found. - - """ - from lxml import etree - - # Cmdout - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis outputs - analysis_output_path = output_dir / "cppcheck_output.xml" - if analysis_output_path.is_file(): - analysis_output = etree.parse(analysis_output_path) - else: - raise MissingFile(["cppcheck_output.xml"]) - - return cls(output_dir, analysis_output, cmdout) + sast_name = "Cppcheck" + rule_categories = ["error", "warning", "style"] + + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given rule ID.""" + if rule_properties := self.get_rule_properties(rule_id): + if tags := rule_properties.tags: + for tag in tags: + if m := re.search(r"cwe-(\d+)", tag.lower()): + cwe_id = int(m.group(1)) + return CWEs.from_id(cwe_id) + return CWEs.NOCWE diff --git a/codesectools/sasts/tools/Cppcheck/sast.py b/codesectools/sasts/tools/Cppcheck/sast.py index 4b28b79..0f95331 100644 --- a/codesectools/sasts/tools/Cppcheck/sast.py +++ b/codesectools/sasts/tools/Cppcheck/sast.py @@ -31,7 +31,6 @@ class CppcheckSAST(PrebuiltBuildlessSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[CppcheckAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. """ @@ -54,22 +53,17 @@ class CppcheckSAST(PrebuiltBuildlessSAST): "cppcheck", (".", "--project={artifacts}"), "--enable=all", - "--xml", - "--output-file=cppcheck_output.xml", + "--output-format=sarif", + "--output-file=cppcheck.sarif", "--cppcheck-build-dir={tempdir}", f"-j{CPU_COUNT}", ] ] valid_codes = [0] output_files = [ - (Path("cppcheck_output.xml"), True), + (Path("cppcheck.sarif"), True), ] parser = CppcheckAnalysisResult - color_mapping = { - "error": "red", - "warning": "orange", - "style": "yellow", - } # PrebuiltSAST artifact_name = "Compilation database" diff --git a/codesectools/sasts/tools/SemgrepCE/parser.py b/codesectools/sasts/tools/SemgrepCE/parser.py index a92c3f0..458a9f2 100644 --- a/codesectools/sasts/tools/SemgrepCE/parser.py +++ b/codesectools/sasts/tools/SemgrepCE/parser.py @@ -5,113 +5,82 @@ format used by CodeSecTools. """ -import json +import os import re -from pathlib import Path -from typing import Self - -from codesectools.sasts.core.parser import AnalysisResult, Defect -from codesectools.shared.cwe import CWEs -from codesectools.utils import MissingFile - - -class SemgrepCEFinding(Defect): - """Represents a single finding reported by Semgrep Community Edition. - - Parses defect data from the Semgrep JSON output to extract file, checker, - category, CWE, and line information. - """ - - sast = "SemgrepCE" - - def __init__(self, defect_data: dict) -> None: - """Initialize a SemgrepCEFinding instance from raw defect data. - - Args: - defect_data: A dictionary representing a single finding, parsed - from Semgrep Community Edition's JSON output. - - """ - super().__init__( - filepath=Path(defect_data["path"]), - checker=defect_data["check_id"].split(".")[-1], - category=defect_data["extra"]["metadata"].get("impact", "NONE"), - cwe=CWEs.from_string(defect_data["extra"]["metadata"].get("cwe", [""])[0]), - message=defect_data["extra"]["message"], - lines=list( - range(defect_data["start"]["line"], defect_data["end"]["line"] + 1) - ), - data=defect_data, - ) - - -class SemgrepCEAnalysisResult(AnalysisResult): - """Represents the complete result of a Semgrep Community Edition analysis. - - Parses the main JSON output and command output logs to populate analysis - metadata, including timings, file lists, defects, and code coverage. - - Attributes: - checker_category (dict): A mapping from checker names to their categories. - coverage (float): The parsing coverage reported by Semgrep. - - """ - - def __init__(self, output_dir: Path, result_data: dict, cmdout: dict) -> None: - """Initialize a SemgrepCEAnalysisResult instance. - - Args: - output_dir: The directory where the results are stored. - result_data: Parsed data from the main Semgrep Community Edition JSON output. - cmdout: A dictionary with metadata from the command execution. - - """ - super().__init__( - name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=result_data["paths"]["scanned"], - defects=[], - time=result_data["time"]["profiling_times"]["total_time"], - loc=0, - data=(result_data, cmdout), - ) - - self.checker_category = {} - for defect_data in result_data["results"]: - defect = SemgrepCEFinding(defect_data) - self.defects.append(defect) - self.checker_category[defect.checker] = defect.category - - if match := re.search(r"Parsed lines:[^\d]*([\d\.]+)%", cmdout["logs"]): - self.coverage = float(match.groups()[0]) / 100 - self.loc = int(self.coverage * cmdout["loc"]) - - @classmethod - def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse Semgrep Community Edition analysis results from a directory. - - Read `semgrepce_output.json` and `cstools_output.json` to construct a complete - analysis result object. - - Args: - output_dir: The directory containing the Semgrep Community Edition output files. - - Returns: - An instance of `SemgrepCEAnalysisResult`. - - Raises: - MissingFile: If a required result file is not found. - - """ - # Cmdout - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis outputs - analysis_output_path = output_dir / "semgrepce_output.json" - if analysis_output_path.is_file(): - analysis_output = json.load(analysis_output_path.open("r")) - else: - raise MissingFile(["semgrepce_output.json"]) - - return cls(output_dir, analysis_output, cmdout) +from itertools import chain +from typing import Any + +import yaml + +from codesectools.sasts.core.parser.format.SARIF import Result +from codesectools.sasts.core.parser.format.SARIF.parser import SARIFAnalysisResult +from codesectools.shared.cwe import CWE, CWEs +from codesectools.utils import USER_CACHE_DIR + +SEMGREP_RULES_DIR = USER_CACHE_DIR / "semgrep-rules" + + +class SemgrepCEAnalysisResult(SARIFAnalysisResult): + """Represent the complete result of a SemgrepCE analysis from a SARIF file.""" + + sast_name = "SemgrepCE" + rule_categories = [ + "best-practice", + "correctness", + "maintainability", + "performance", + "portability", + "security", + ] + + # Rule id is using full path: + # home.michel..codesectools.cache.semgrep-rules.java.android.best-practice.manifest-usesCleartextTraffic-true + # Removing the path to rules to keep only the real rule id: + # java.android.best-practice.manifest-usesCleartextTraffic-true + def patch_dict(self, sarif_dict: dict) -> dict: + """Patch the SARIF dictionary to shorten rule IDs.""" + rule_path_pattern = str(SEMGREP_RULES_DIR).replace(os.sep, ".")[1:] + "." + + def recursive_patch(data: Any) -> None: # noqa: ANN401 + if isinstance(data, dict): + for key, value in data.items(): + if isinstance(value, str) and rule_path_pattern in value: + data[key] = value.replace(rule_path_pattern, "") + else: + recursive_patch(value) + elif isinstance(data, list): + for item in data: + recursive_patch(item) + + recursive_patch(sarif_dict) + self.save_patched_dict(sarif_dict) + return sarif_dict + + @staticmethod + # @Cache(SEMGREP_RULES_DIR / ".cstools_cache").memoize(expire=None) + def get_raw_rules() -> dict: + """Load and return all Semgrep rules from the cached YAML files.""" + raw_rules = {} + if SEMGREP_RULES_DIR.is_dir(): + rule_paths = chain( + SEMGREP_RULES_DIR.rglob("*.yml"), SEMGREP_RULES_DIR.rglob("*.yaml") + ) + for rule_path in rule_paths: + try: + data = yaml.safe_load(rule_path.open("r")) + for rule in data.get("rules"): + rule_id = rule["id"] + raw_rules[rule_id] = rule + except (TypeError, KeyError, yaml.composer.ComposerError): # ty:ignore[possibly-missing-attribute] + pass + return raw_rules + + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given rule ID.""" + if rule_properties := self.get_rule_properties(rule_id): + if tags := rule_properties.tags: + for tag in tags: + if m := re.search(r"cwe-(\d+)", tag.lower()): + cwe_id = int(m.group(1)) + return CWEs.from_id(cwe_id) + return CWEs.NOCWE diff --git a/codesectools/sasts/tools/SemgrepCE/sast.py b/codesectools/sasts/tools/SemgrepCE/sast.py index c4a8600..4ceefde 100644 --- a/codesectools/sasts/tools/SemgrepCE/sast.py +++ b/codesectools/sasts/tools/SemgrepCE/sast.py @@ -13,8 +13,10 @@ GitRepo, SASTRequirements, ) -from codesectools.sasts.tools.SemgrepCE.parser import SemgrepCEAnalysisResult -from codesectools.utils import USER_CACHE_DIR +from codesectools.sasts.tools.SemgrepCE.parser import ( + SEMGREP_RULES_DIR, + SemgrepCEAnalysisResult, +) class SemgrepCESAST(BuildlessSAST): @@ -31,7 +33,6 @@ class SemgrepCESAST(BuildlessSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[SemgrepCEAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. """ @@ -56,18 +57,14 @@ class SemgrepCESAST(BuildlessSAST): [ "semgrep", "scan", - f"--config={str(USER_CACHE_DIR / 'semgrep-rules' / '{lang}')}", + f"--config={str(SEMGREP_RULES_DIR / '{lang}')}", "--metrics=off", - "--json-output=semgrepce_output.json", + "--sarif", + "--sarif-output=semgrepce.sarif", ] ] valid_codes = [0, 1] # https://semgrep.dev/docs/cli-reference#exit-codes output_files = [ - (Path("semgrepce_output.json"), True), + (Path("semgrepce.sarif"), True), ] parser = SemgrepCEAnalysisResult - color_mapping = { - "HIGH": "RED", - "MEDIUM": "ORANGE", - "LOW": "YELLOW", - } diff --git a/codesectools/sasts/tools/SnykCode/parser.py b/codesectools/sasts/tools/SnykCode/parser.py index 5871dde..2e7ece8 100644 --- a/codesectools/sasts/tools/SnykCode/parser.py +++ b/codesectools/sasts/tools/SnykCode/parser.py @@ -5,151 +5,25 @@ format used by CodeSecTools. """ -import json -from pathlib import Path -from typing import Self +import re -from codesectools.sasts.core.parser import AnalysisResult, Defect +from codesectools.sasts.core.parser.format.SARIF import Result +from codesectools.sasts.core.parser.format.SARIF.parser import SARIFAnalysisResult from codesectools.shared.cwe import CWE, CWEs -class SnykCodeIssue(Defect): - """Represents a single issue reported by Snyk Code. +class SnykCodeAnalysisResult(SARIFAnalysisResult): + """Represent the complete result of a Snyk Code analysis from a SARIF file.""" - Parses defect data from the Snyk Code JSON output to extract file, checker, - category, and CWE information. - """ + sast_name = "SnykCode" + rule_categories = [] - sast = "SnykCode" - - def __init__( - self, - filepath: Path, - checker: str, - category: str, - cwe: CWE, - message: str, - lines: list[int] | None, - data: dict, - ) -> None: - """Initialize a SnykCodeIssue instance. - - Args: - filepath: The file path of the defect. - checker: The name of the rule/checker. - category: The category of the checker. - cwe: The CWE associated with the defect. - message: The description of the defect. - lines: A list of line numbers where the defect is located, or None. - data: Raw data from the SAST tool for this defect. - - """ - super().__init__(filepath, checker, category, cwe, message, lines, data) - - -class SnykCodeAnalysisResult(AnalysisResult): - """Represents the complete result of a Snyk Code analysis. - - Parses the main JSON output and command output logs to populate analysis - metadata, including timings, file lists, and defects. - """ - - normalize_lang_names = {"java": ["java"], "cpp": ["c", "cpp"]} - - def __init__(self, output_dir: Path, result_data: dict, cmdout: dict) -> None: - """Initialize a SnykCodeAnalysisResult instance. - - Args: - output_dir: The directory where the results are stored. - result_data: Parsed data from the main Snyk Code JSON output. - cmdout: A dictionary with metadata from the command execution. - - """ - super().__init__( - name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=[], - defects=[], - time=cmdout["duration"], - loc=cmdout["loc"], - data=(result_data, cmdout), - ) - - if not result_data: - return - - for run in result_data["runs"]: - for result in run["results"]: - rule_index = result["ruleIndex"] - lang, *_, checker = result["ruleId"].split("/") - if self.lang not in self.normalize_lang_names.get(lang, []): - continue - - start = ( - result["locations"][0]["physicalLocation"] - .get("region", {}) - .get("startLine", None) - ) - end = ( - result["locations"][0]["physicalLocation"] - .get("region", {}) - .get("endLine", None) - ) - if start and end: - lines = list(range(start, end + 1)) - elif start: - lines = [start] - elif end: - lines = [end] - else: - lines = None - - defect = SnykCodeIssue( - filepath=Path( - result["locations"][0]["physicalLocation"]["artifactLocation"][ - "uri" - ] - ), - checker=checker, - category=run["tool"]["driver"]["rules"][rule_index][ - "defaultConfiguration" - ]["level"], - cwe=CWEs.from_string( - run["tool"]["driver"]["rules"][rule_index]["properties"]["cwe"][ - 0 - ] - ), - message=result["message"]["text"], - lines=lines, - data=result, - ) - self.defects.append(defect) - - self.files = list(set(d.filepath_str for d in self.defects)) - - @classmethod - def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse Snyk Code analysis results from a directory. - - Read `snyk_results.json` and `cstools_output.json` to construct a complete - analysis result object. - - Args: - output_dir: The directory containing the Snyk Code output files. - - Returns: - An instance of `SnykCodeAnalysisResult`. - - """ - # Cmdout - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis outputs - analysis_output_path = output_dir / "snyk_results.json" - if analysis_output_path.is_file(): - analysis_output = json.load(analysis_output_path.open("r")) - else: - analysis_output = {} - - return cls(output_dir, analysis_output, cmdout) + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given rule ID.""" + if rule_properties := self.get_rule_properties(rule_id): + if extra := rule_properties.__pydantic_extra__: + if cwe := extra.get("cwe"): + if m := re.search(r"cwe-(\d+)", cwe[0].lower()): + cwe_id = int(m.group(1)) + return CWEs.from_id(cwe_id) + return CWEs.NOCWE diff --git a/codesectools/sasts/tools/SnykCode/sast.py b/codesectools/sasts/tools/SnykCode/sast.py index 3e4ca86..ae8b0f0 100644 --- a/codesectools/sasts/tools/SnykCode/sast.py +++ b/codesectools/sasts/tools/SnykCode/sast.py @@ -27,7 +27,6 @@ class SnykCodeSAST(BuildlessSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[SnykCodeAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. """ @@ -45,21 +44,15 @@ class SnykCodeSAST(BuildlessSAST): ], partial_reqs=[], ) - commands = [["snyk", "code", "test", "--json-file-output=snyk_results.json"]] + commands = [["snyk", "code", "test", "--sarif-file-output=snykcode.sarif"]] valid_codes = [ 0, 1, ] # https://docs.snyk.io/developer-tools/snyk-cli/commands/code-test#exit-codes output_files = [ - (Path("snyk_results.json"), False), + (Path("snykcode.sarif"), False), ] parser = SnykCodeAnalysisResult - color_mapping = { - "error": "red", - "warning": "orange", - "note": "yellow", - "info": "yellow", - } def __init__(self) -> None: """Initialize the SnykCodeSAST instance. diff --git a/codesectools/sasts/tools/SpotBugs/parser.py b/codesectools/sasts/tools/SpotBugs/parser.py index 4c4fe22..9d53d30 100644 --- a/codesectools/sasts/tools/SpotBugs/parser.py +++ b/codesectools/sasts/tools/SpotBugs/parser.py @@ -5,161 +5,72 @@ format used by CodeSecTools. """ -import json +from __future__ import annotations + from pathlib import Path -from typing import Self +from typing import TYPE_CHECKING, Any -from codesectools.sasts.core.parser import AnalysisResult, Defect +from codesectools.sasts.core.parser.format.SARIF.parser import SARIFAnalysisResult from codesectools.shared.cwe import CWE, CWEs -from codesectools.utils import MissingFile - - -class SpotBugsIssue(Defect): - """Represent a single issue reported by SpotBugs.""" - - sast = "SpotBugs" - - def __init__( - self, - filepath: Path, - checker: str, - category: str, - cwe: CWE, - message: str, - lines: list[int] | None, - data: dict, - ) -> None: - """Initialize a SpotBugsIssue instance. - - Args: - filepath: The file path of the defect. - checker: The name of the rule/checker. - category: The category of the checker. - cwe: The CWE associated with the defect. - message: The description of the defect. - lines: A list of line numbers where the defect is located, or None. - data: Raw data from the SAST tool for this defect. - """ - super().__init__(filepath, checker, category, cwe, message, lines, data) +if TYPE_CHECKING: + from codesectools.sasts.core.parser.format.SARIF import Result -class SpotBugsAnalysisResult(AnalysisResult): +class SpotBugsAnalysisResult(SARIFAnalysisResult): """Represent the complete result of a SpotBugs analysis.""" - def __init__(self, output_dir: Path, result_data: dict, cmdout: dict) -> None: - """Initialize a SpotBugsAnalysisResult instance. - - Args: - output_dir: The directory where the results are stored. - result_data: Parsed data from the main SpotBugs JSON output. - cmdout: A dictionary with metadata from the command execution. - - """ - super().__init__( - name=output_dir.name, - source_path=Path(cmdout["project_dir"]), - lang=cmdout["lang"], - files=[], - defects=[], - time=cmdout["duration"], - loc=cmdout["loc"], - data=(result_data, cmdout), - ) - - if not result_data: - return + sast_name = "SpotBugs" + rule_categories = ["SECURITY", "CORRECTNESS", "MT_CORRECTNESS"] + # uri is Java class name which misses base project path: + # org/mypackage/... + # We want: + # /home/user/mypackage/src/main/java/org/mypackage/... + def patch_dict(self, sarif_dict: dict) -> dict: + """Patch the SARIF dictionary to resolve relative Java class paths.""" partial_parents = {} - for run in result_data["runs"]: - for result in run["results"]: - rule_index = result["ruleIndex"] - checker = result["ruleId"] - - # Note: All file paths are relative to the application's class path, not the project source directory. - partial_filepath = Path( - result["locations"][0]["physicalLocation"]["artifactLocation"][ - "uri" - ] - ) - if partial_filepath.parent not in partial_parents: - if next(self.source_path.rglob(str(partial_filepath)), None): - filepath = next( - self.source_path.rglob(str(partial_filepath)) - ).relative_to(self.source_path) - partial_parents[partial_filepath.parent] = filepath.parent - else: - filepath = ( - partial_parents[partial_filepath.parent] / partial_filepath.name - ) - - start = ( - result["locations"][0]["physicalLocation"] - .get("region", {}) - .get("startLine", None) - ) - end = ( - result["locations"][0]["physicalLocation"] - .get("region", {}) - .get("endLine", None) - ) - if start and end: - lines = list(range(start, end + 1)) - elif start: - lines = [start] - elif end: - lines = [end] - else: - lines = None - - defect = SpotBugsIssue( - filepath=filepath, - checker=checker, - category=run["tool"]["driver"]["rules"][rule_index]["properties"][ - "tags" - ][0], - cwe=CWEs.from_id( - int( - run["tool"]["driver"]["rules"][rule_index].get( - "relationships", [{"target": {"id": -1}}] - )[0]["target"]["id"] - ) - ), - message=result["message"]["text"], - lines=lines, - data=result, - ) - if defect.category in ["SECURITY", "CORRECTNESS", "MT_CORRECTNESS"]: - self.defects.append(defect) - - self.files = list(set(d.filepath_str for d in self.defects)) - - @classmethod - def load_from_output_dir(cls, output_dir: Path) -> Self: - """Load and parse SpotBugs analysis results from a directory. - - Read `spotbugs_output.json` and `cstools_output.json` to construct a complete - analysis result object. - - Args: - output_dir: The directory containing the SpotBugs output files. - - Returns: - An instance of `SpotBugsAnalysisResult`. - - Raises: - MissingFile: If a required result file is not found. - - """ - # Cmdout - cmdout = json.load((output_dir / "cstools_output.json").open()) - - # Analysis outputs - analysis_output_path = output_dir / "spotbugs_output.json" - if analysis_output_path.is_file(): - analysis_output = json.load(analysis_output_path.open("r")) - else: - raise MissingFile(["spotbugs_output.json"]) - - return cls(output_dir, analysis_output, cmdout) + def recursive_patch(data: Any) -> None: # noqa: ANN401 + if isinstance(data, dict): + for key, value in data.items(): + if key == "uri": + partial_filepath = Path(value) + if partial_filepath.parent not in partial_parents: + if next( + self.source_path.rglob(str(partial_filepath)), None + ): + filepath = next( + self.source_path.rglob(str(partial_filepath)) + ).relative_to(self.source_path) + partial_parents[partial_filepath.parent] = ( + filepath.parent + ) + else: + filepath = ( + partial_parents[partial_filepath.parent] + / partial_filepath.name + ) + + data[key] = str(filepath) + else: + recursive_patch(value) + + elif isinstance(data, list): + for item in data: + recursive_patch(item) + + recursive_patch(sarif_dict) + self.save_patched_dict(sarif_dict) + return sarif_dict + + def get_cwe(self, result: Result, rule_id: str) -> CWE: + """Get the CWE for a given rule ID.""" + rule = self.rules[rule_id] + if relationships := rule.relationships: + for relationship in relationships: + if target := relationship.target: + if root := target.root: + if id := root.id: + return CWEs.from_id(int(id)) + return CWEs.NOCWE diff --git a/codesectools/sasts/tools/SpotBugs/sast.py b/codesectools/sasts/tools/SpotBugs/sast.py index dd0ceda..ff06e6e 100644 --- a/codesectools/sasts/tools/SpotBugs/sast.py +++ b/codesectools/sasts/tools/SpotBugs/sast.py @@ -36,7 +36,6 @@ class SpotBugsSAST(PrebuiltSAST): output_files (list[tuple[Path, bool]]): A list of expected output files and whether they are required. parser (type[SpotBugsAnalysisResult]): The parser class for the tool's results. - color_mapping (dict): A mapping of result categories to colors for plotting. """ @@ -64,21 +63,15 @@ class SpotBugsSAST(PrebuiltSAST): "-textui", "-nested:true", "-progress", - "-sarif=spotbugs_output.json", + "-sarif=spotbugs.sarif", "{artifacts}", ] ] valid_codes = [0] output_files = [ - (Path("spotbugs_output.json"), True), + (Path("spotbugs.sarif"), True), ] parser = SpotBugsAnalysisResult - # Based on: spotbugs/spotbugs/etc/bugrank.txt - color_mapping = { - "SECURITY": "red", - "CORRECTNESS": "orange", - "MT_CORRECTNESS": "yellow", - } # PrebuiltSAST artifact_name = "Java Bytecode" diff --git a/codesectools/utils.py b/codesectools/utils.py index acae7f2..f3aa49c 100644 --- a/codesectools/utils.py +++ b/codesectools/utils.py @@ -78,10 +78,20 @@ def render_command(command: list, mapping: dict[str, str]) -> list[str]: if isinstance(arg, tuple): default_arg, optional_arg = arg - if pattern := get_pattern(optional_arg, mapping): - _command[i] = optional_arg.replace(pattern, mapping[pattern]) - elif pattern := get_pattern(default_arg, mapping): - _command[i] = default_arg.replace(pattern, mapping[pattern]) + optional_pattern = get_pattern(optional_arg, mapping) + default_pattern = get_pattern(default_arg, mapping) + + if optional_pattern and mapping.get(optional_pattern): + _command[i] = optional_arg.replace( + optional_pattern, mapping[optional_pattern] + ) + else: + if default_pattern and mapping.get(default_pattern): + _command[i] = default_arg.replace( + default_pattern, mapping[default_pattern] + ) + else: + _command[i] = default_arg else: if pattern := get_pattern(arg, mapping): value = mapping[pattern] @@ -104,7 +114,10 @@ def render_command(command: list, mapping: dict[str, str]) -> list[str]: def run_command( - command: Sequence[str], cwd: Path | None = None, env: dict[str, str] | None = None + command: Sequence[str], + cwd: Path | None = None, + env: dict[str, str] | None = None, + silent: bool = False, ) -> tuple[int | None, str]: """Execute a command in a subprocess and capture its output. @@ -112,6 +125,7 @@ def run_command( command: The command to execute, as a list of strings. cwd: The working directory for the command. env: Optional dictionary of environment variables to set for the command. + silent: If True, do not print command output in debug mode. Returns: A tuple containing the command's return code and its combined @@ -137,7 +151,7 @@ def run_command( if process.stdout: for line in process.stdout: stdout += line - if DEBUG(): + if DEBUG() and not silent: click.echo(line, nl=False) process.wait() diff --git a/docs/assets/overview.excalidraw b/docs/assets/overview.excalidraw deleted file mode 100644 index 831ef11..0000000 --- a/docs/assets/overview.excalidraw +++ /dev/null @@ -1,2449 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "id": "nBrrWzfiIRCS4-j29ZouL", - "type": "rectangle", - "x": 517.7905912853422, - "y": 685.4951902117049, - "width": 750.4000244140625, - "height": 132.00001525878898, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#b2f2bb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b1y", - "roundness": { - "type": 3 - }, - "seed": 1446773798, - "version": 836, - "versionNonce": 567540326, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "iiEaipiEi97LH8SaiB_KL", - "type": "rectangle", - "x": 482.40008544921875, - "y": 210.59998321533203, - "width": 808.7996826171875, - "height": 623.199821472168, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b1z", - "roundness": { - "type": 3 - }, - "seed": 1125673254, - "version": 1423, - "versionNonce": 290178810, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "m7KCF3uhYxT9FzXXWXF7L" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "m7KCF3uhYxT9FzXXWXF7L", - "type": "text", - "x": 764.9579620361328, - "y": 215.59998321533203, - "width": 243.68392944335938, - "height": 45, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#3e9c36", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b20", - "roundness": null, - "seed": 392655354, - "version": 1220, - "versionNonce": 1332985254, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "CodeSecTools", - "fontSize": 36, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": "iiEaipiEi97LH8SaiB_KL", - "originalText": "CodeSecTools", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "Rv6iSb2qCupGgOzwOVA8J", - "type": "rectangle", - "x": 712.9902677990141, - "y": 441.4951062883649, - "width": 346.39996337890614, - "height": 224.00006103515622, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b21", - "roundness": { - "type": 3 - }, - "seed": 383605990, - "version": 1724, - "versionNonce": 1626134458, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "w-Cc81-N7vAvSkZRegGu8" - }, - { - "id": "oT2VVu6qyEZwdf7RrFhqQ", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "w-Cc81-N7vAvSkZRegGu8", - "type": "text", - "x": 801.3203154064358, - "y": 446.4951062883649, - "width": 169.7398681640625, - "height": 25, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#ffffff", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b22", - "roundness": null, - "seed": 1378668582, - "version": 1584, - "versionNonce": 314120422, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Data abstraction", - "fontSize": 20, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": "Rv6iSb2qCupGgOzwOVA8J", - "originalText": "Data abstraction", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "Yp-Ops1lm44S4mFwzv_v2", - "type": "rectangle", - "x": 719.2928134448903, - "y": 269.6718345779226, - "width": 362.3999938964843, - "height": 141.5999298095703, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b22G", - "roundness": { - "type": 3 - }, - "seed": 485793748, - "version": 1598, - "versionNonce": 1114887916, - "isDeleted": false, - "boundElements": [], - "updated": 1764004560433, - "link": null, - "locked": false - }, - { - "id": "13dkZHvSDLQKbfPK5JpGL", - "type": "rectangle", - "x": 708.9904509044827, - "y": 281.09506661551325, - "width": 362.3999938964843, - "height": 141.5999298095703, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b23", - "roundness": { - "type": 3 - }, - "seed": 1483036838, - "version": 1500, - "versionNonce": 1068640724, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "fsuA80u19oDfd_WXy5fbY" - } - ], - "updated": 1764004513531, - "link": null, - "locked": false - }, - { - "id": "fsuA80u19oDfd_WXy5fbY", - "type": "text", - "x": 815.0244856334866, - "y": 286.09506661551325, - "width": 150.33192443847656, - "height": 35, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b24", - "roundness": null, - "seed": 1552723450, - "version": 1388, - "versionNonce": 1022742356, - "isDeleted": false, - "boundElements": [], - "updated": 1764004513531, - "link": null, - "locked": false, - "text": "SAST Tool", - "fontSize": 28, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": "13dkZHvSDLQKbfPK5JpGL", - "originalText": "SAST Tool", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "ljpVgLZj0RDw1SkVJgIQY", - "type": "rectangle", - "x": 1107.2928287036793, - "y": 226.27181779325466, - "width": 172.79998779296875, - "height": 151.99999999999997, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b24G", - "roundness": { - "type": 3 - }, - "seed": 892714580, - "version": 820, - "versionNonce": 1133845228, - "isDeleted": false, - "boundElements": [], - "updated": 1764004596046, - "link": null, - "locked": false - }, - { - "id": "ehh88Kle4w-5mpVadmmlV", - "type": "rectangle", - "x": 1096.9903898693265, - "y": 234.2951551164899, - "width": 172.79998779296875, - "height": 151.99999999999997, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b25", - "roundness": { - "type": 3 - }, - "seed": 1673776934, - "version": 745, - "versionNonce": 2102505786, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "jjGwFPfnU92IxQ2pnFlVm" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "jjGwFPfnU92IxQ2pnFlVm", - "type": "text", - "x": 1133.1024046398343, - "y": 239.2951551164899, - "width": 100.57595825195312, - "height": 35, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b26", - "roundness": null, - "seed": 369818214, - "version": 734, - "versionNonce": 2127075174, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Project", - "fontSize": 28, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": "ehh88Kle4w-5mpVadmmlV", - "originalText": "Project", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "Y1koFTBntLWjV_oBg9Et0", - "type": "rectangle", - "x": 1105.6928531177418, - "y": 400.07185136259056, - "width": 172.79998779296875, - "height": 261.59994506835926, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b26G", - "roundness": { - "type": 3 - }, - "seed": 1150040660, - "version": 1164, - "versionNonce": 891305940, - "isDeleted": false, - "boundElements": [], - "updated": 1764004639763, - "link": null, - "locked": false - }, - { - "id": "bm_HYs349MNhCJXSJAr-s", - "type": "rectangle", - "x": 1096.9906340099515, - "y": 409.49509102957575, - "width": 172.79998779296875, - "height": 261.59994506835926, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b27", - "roundness": { - "type": 3 - }, - "seed": 812551398, - "version": 1028, - "versionNonce": 557545978, - "isDeleted": false, - "boundElements": [ - { - "id": "YcJlmoG9sQIynU92KxxM8", - "type": "text" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "YcJlmoG9sQIynU92KxxM8", - "type": "text", - "x": 1125.7386503673733, - "y": 631.095036097935, - "width": 115.303955078125, - "height": 35, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b28", - "roundness": null, - "seed": 35127334, - "version": 996, - "versionNonce": 1036827302, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Dataset", - "fontSize": 28, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "bottom", - "containerId": "bm_HYs349MNhCJXSJAr-s", - "originalText": "Dataset", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "-EqWrsOTDO-ya_IIS3Y7F", - "type": "rectangle", - "x": 501.79040817987345, - "y": 391.49512154715393, - "width": 183.20004272460932, - "height": 275.2000885009765, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b29", - "roundness": { - "type": 3 - }, - "seed": 1915272870, - "version": 1608, - "versionNonce": 1584278202, - "isDeleted": false, - "boundElements": [ - { - "type": "text", - "id": "sGxUalepl9DFIch94stpK" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "sGxUalepl9DFIch94stpK", - "type": "text", - "x": 537.3704786754789, - "y": 396.49512154715393, - "width": 112.03990173339844, - "height": 50, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#ffffff", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2A", - "roundness": null, - "seed": 1316994234, - "version": 1565, - "versionNonce": 878107813, - "isDeleted": false, - "boundElements": [], - "updated": 1760632010944, - "link": null, - "locked": false, - "text": "SAST Tool\nabstraction", - "fontSize": 20, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": "-EqWrsOTDO-ya_IIS3Y7F", - "originalText": "SAST Tool abstraction", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "u2Xd62JX7I6Fe4sSSLRtv", - "type": "rectangle", - "x": 908.1905851818265, - "y": 330.2951551164898, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2B", - "roundness": { - "type": 3 - }, - "seed": 2057788198, - "version": 1391, - "versionNonce": 1804387194, - "isDeleted": false, - "boundElements": [ - { - "id": "8NyFg1uK-idXM6x3llKnW", - "type": "text" - }, - { - "id": "YzS0nBIbkKe5kZRq5Dd6G", - "type": "arrow" - }, - { - "id": "EHLo9jiSojJJcP3VE-zLv", - "type": "arrow" - }, - { - "id": "6KPYzD-qzKYj2qhXFFQrw", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "8NyFg1uK-idXM6x3llKnW", - "type": "text", - "x": 938.9585905529202, - "y": 355.0951581682476, - "width": 64.86395263671875, - "height": 20, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2C", - "roundness": null, - "seed": 547161702, - "version": 1346, - "versionNonce": 1932445990, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Analyzer", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "u2Xd62JX7I6Fe4sSSLRtv", - "originalText": "Analyzer", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "MjKEQ-zh6QaziH-sClPuW", - "type": "rectangle", - "x": 532.1904936290922, - "y": 459.89528329031793, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2D", - "roundness": { - "type": 3 - }, - "seed": 1691670310, - "version": 1529, - "versionNonce": 853165114, - "isDeleted": false, - "boundElements": [ - { - "id": "k8DsKjhpR3TlJxsCuhAhD", - "type": "text" - }, - { - "id": "T-ligD1pELhBjckeQyCsa", - "type": "arrow" - }, - { - "id": "PerXf-uA7sHI_OsSpcdZ1", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "k8DsKjhpR3TlJxsCuhAhD", - "type": "text", - "x": 570.2864943004789, - "y": 484.69528634207575, - "width": 50.20796203613281, - "height": 20, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2E", - "roundness": null, - "seed": 1606091366, - "version": 1501, - "versionNonce": 839300198, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Parser", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "MjKEQ-zh6QaziH-sClPuW", - "originalText": "Parser", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "9A_gsReld74bwT77CUkTI", - "type": "rectangle", - "x": 737.7903471447172, - "y": 484.6951947893415, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2F", - "roundness": { - "type": 3 - }, - "seed": 962154982, - "version": 1469, - "versionNonce": 1580838138, - "isDeleted": false, - "boundElements": [ - { - "id": "OMa1beETojr56DZebaF2-", - "type": "text" - }, - { - "id": "E9ckiLPEa6txaw0qv6hpe", - "type": "arrow" - }, - { - "id": "OVkQYwMi9Yne7aYQZeJh9", - "type": "arrow" - }, - { - "id": "Sp1qM7gCBukASzmmujQjj", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "OMa1beETojr56DZebaF2-", - "type": "text", - "x": 743.0543776012602, - "y": 499.4951978410993, - "width": 115.87190246582031, - "height": 40, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2G", - "roundness": null, - "seed": 190981414, - "version": 1468, - "versionNonce": 2101295014, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Validation\n(Dataset only)", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "9A_gsReld74bwT77CUkTI", - "originalText": "Validation\n(Dataset only)", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "3yJZox55c-ILEPHij6IdS", - "type": "rectangle", - "x": 737.790591285342, - "y": 330.2950788225445, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2H", - "roundness": { - "type": 3 - }, - "seed": 1952573242, - "version": 1551, - "versionNonce": 1663958458, - "isDeleted": false, - "boundElements": [ - { - "id": "Z6zR4iJ_QVP8mTw5rLdAX", - "type": "text" - }, - { - "id": "6KPYzD-qzKYj2qhXFFQrw", - "type": "arrow" - }, - { - "id": "PerXf-uA7sHI_OsSpcdZ1", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "Z6zR4iJ_QVP8mTw5rLdAX", - "type": "text", - "x": 757.8706236339748, - "y": 355.0950818743023, - "width": 86.23989868164062, - "height": 20, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2I", - "roundness": null, - "seed": 1960112122, - "version": 1550, - "versionNonce": 880219397, - "isDeleted": false, - "boundElements": [], - "updated": 1760632005725, - "link": null, - "locked": false, - "text": "Tool result", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "3yJZox55c-ILEPHij6IdS", - "originalText": "Tool result", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "8qAhylsGA8gx_F27etVCZ", - "type": "rectangle", - "x": 530.5904264904202, - "y": 564.6953321184429, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "#a5d8ff", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2J", - "roundness": { - "type": 3 - }, - "seed": 1800783354, - "version": 1639, - "versionNonce": 249181818, - "isDeleted": false, - "boundElements": [ - { - "id": "zLS1LG-rKg8ejuxE-Zcnt", - "type": "text" - }, - { - "id": "T-ligD1pELhBjckeQyCsa", - "type": "arrow" - }, - { - "id": "E9ckiLPEa6txaw0qv6hpe", - "type": "arrow" - }, - { - "id": "oUk_xUVqX-FGKv11BFvqr", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "zLS1LG-rKg8ejuxE-Zcnt", - "type": "text", - "x": 545.9904509044827, - "y": 579.4953351702007, - "width": 95.59991455078125, - "height": 40, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2K", - "roundness": null, - "seed": 123664058, - "version": 1625, - "versionNonce": 396867028, - "isDeleted": false, - "boundElements": [], - "updated": 1764004650574, - "link": null, - "locked": false, - "text": "Abstracted\nSAST result", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "8qAhylsGA8gx_F27etVCZ", - "originalText": "Abstracted\nSAST result", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "T-ligD1pELhBjckeQyCsa", - "type": "arrow", - "x": 593.9685555506644, - "y": 531.2952466692243, - "width": 0.22533526835968587, - "height": 31.80010986328125, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "#ffffff", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2L", - "roundness": { - "type": 2 - }, - "seed": 1706259450, - "version": 2067, - "versionNonce": 302077754, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0.22533526835968587, - 31.80010986328125 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "MjKEQ-zh6QaziH-sClPuW", - "focus": 0.026610349052956665, - "gap": 1.7999572753907387 - }, - "endBinding": { - "elementId": "8qAhylsGA8gx_F27etVCZ", - "focus": 0.010424716250178358, - "gap": 1.5999755859373863 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "R8QIQIVyFH3hha72qaYHc", - "type": "rectangle", - "x": 736.9905424572172, - "y": 578.2950483049665, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2M", - "roundness": { - "type": 3 - }, - "seed": 196800058, - "version": 1805, - "versionNonce": 181293414, - "isDeleted": false, - "boundElements": [ - { - "id": "9i6Uy6Dt1Obg5zWgTqqa2", - "type": "text" - }, - { - "id": "oUk_xUVqX-FGKv11BFvqr", - "type": "arrow" - }, - { - "id": "Sp1qM7gCBukASzmmujQjj", - "type": "arrow" - }, - { - "id": "fmpXvjyxrH-CkrnbrCiEC", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "9i6Uy6Dt1Obg5zWgTqqa2", - "type": "text", - "x": 760.7105665661039, - "y": 603.0950513567243, - "width": 78.95991516113281, - "height": 20, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2N", - "roundness": null, - "seed": 1769281274, - "version": 1803, - "versionNonce": 218521594, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Statistics", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "R8QIQIVyFH3hha72qaYHc", - "originalText": "Statistics", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "NC67N2GLMVkKJQjD72vI1", - "type": "rectangle", - "x": 1118.5904570079983, - "y": 433.4952283586773, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2O", - "roundness": { - "type": 3 - }, - "seed": 84311654, - "version": 1391, - "versionNonce": 1963668646, - "isDeleted": false, - "boundElements": [ - { - "id": "fH3YsezqxCqKvOyV6EPSd", - "type": "text" - }, - { - "id": "EHLo9jiSojJJcP3VE-zLv", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "fH3YsezqxCqKvOyV6EPSd", - "type": "text", - "x": 1145.1344709850491, - "y": 458.2952314104351, - "width": 73.31193542480469, - "height": 20, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2P", - "roundness": null, - "seed": 833995174, - "version": 1356, - "versionNonce": 468471994, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Codebase", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "NC67N2GLMVkKJQjD72vI1", - "originalText": "Codebase", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "03m1x_zTn7wNWGKGdjIQU", - "type": "rectangle", - "x": 1122.5907011486233, - "y": 537.4952588762554, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2Q", - "roundness": { - "type": 3 - }, - "seed": 887632122, - "version": 1393, - "versionNonce": 608808934, - "isDeleted": false, - "boundElements": [ - { - "id": "XwT6g0Sj9aNR6KJInYA5X", - "type": "text" - }, - { - "id": "oT2VVu6qyEZwdf7RrFhqQ", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "XwT6g0Sj9aNR6KJInYA5X", - "type": "text", - "x": 1149.222704932803, - "y": 552.2952619280132, - "width": 73.13595581054688, - "height": 40, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2R", - "roundness": null, - "seed": 1768686010, - "version": 1381, - "versionNonce": 1649813882, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Expected\nresult", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "03m1x_zTn7wNWGKGdjIQU", - "originalText": "Expected result", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "KJU51q1RLxUG_O4iJKISF", - "type": "rectangle", - "x": 1122.5904875255765, - "y": 294.29526192801325, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2S", - "roundness": { - "type": 3 - }, - "seed": 880105274, - "version": 1360, - "versionNonce": 882295590, - "isDeleted": false, - "boundElements": [ - { - "id": "3FVT_3ZGnu86hkNBwfsif", - "type": "text" - }, - { - "id": "YzS0nBIbkKe5kZRq5Dd6G", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "3FVT_3ZGnu86hkNBwfsif", - "type": "text", - "x": 1149.1345015026272, - "y": 319.09526497977106, - "width": 73.31193542480469, - "height": 20, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2T", - "roundness": null, - "seed": 1090150394, - "version": 1329, - "versionNonce": 394717754, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Codebase", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "KJU51q1RLxUG_O4iJKISF", - "originalText": "Codebase", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "HaejGcDLMBI6jQK737D0b", - "type": "image", - "x": 520.1906462169828, - "y": 219.69514901297418, - "width": 159.20007324218753, - "height": 159.20007324218753, - "angle": 0, - "strokeColor": "transparent", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2U", - "roundness": null, - "seed": 1097464954, - "version": 889, - "versionNonce": 945778278, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "status": "saved", - "fileId": "406391fa8caaf383e1c546938fa1b5e310bbdcc4", - "scale": [ - 1, - 1 - ], - "crop": null - }, - { - "id": "YzS0nBIbkKe5kZRq5Dd6G", - "type": "arrow", - "x": 1121.5904875255765, - "y": 330.96348158966805, - "width": 86.0134780101655, - "height": 15.760264655196124, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2V", - "roundness": { - "type": 2 - }, - "seed": 26848550, - "version": 115, - "versionNonce": 308384506, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -86.0134780101655, - 15.760264655196124 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "KJU51q1RLxUG_O4iJKISF", - "focus": 0.2195356509077588, - "gap": 1 - }, - "endBinding": { - "elementId": "u2Xd62JX7I6Fe4sSSLRtv", - "focus": -0.14545380339260208, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "EHLo9jiSojJJcP3VE-zLv", - "type": "arrow", - "x": 1117.5904570079983, - "y": 454.10335249942, - "width": 81.97051703933494, - "height": 62.3692948439201, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2W", - "roundness": { - "type": 2 - }, - "seed": 1374848506, - "version": 103, - "versionNonce": 1819324838, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -81.97051703933494, - -62.3692948439201 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "NC67N2GLMVkKJQjD72vI1", - "focus": -0.4149903302592666, - "gap": 1 - }, - "endBinding": { - "elementId": "u2Xd62JX7I6Fe4sSSLRtv", - "focus": -0.26921691700849265, - "gap": 2.5422803297761982 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "6KPYzD-qzKYj2qhXFFQrw", - "type": "arrow", - "x": 907.1905851818263, - "y": 364.63246124223207, - "width": 40.600067138671534, - "height": 0.9574317087786426, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2X", - "roundness": { - "type": 2 - }, - "seed": 1614187110, - "version": 369, - "versionNonce": 1432953786, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -40.600067138671534, - 0.9574317087786426 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "u2Xd62JX7I6Fe4sSSLRtv", - "focus": 0.060889590311880314, - "gap": 1 - }, - "endBinding": { - "elementId": "3yJZox55c-ILEPHij6IdS", - "focus": 0.04027054135262338, - "gap": 2.3999633789064774 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "PerXf-uA7sHI_OsSpcdZ1", - "type": "arrow", - "x": 742.7009101078232, - "y": 394.10196310337756, - "width": 85.51047751388717, - "height": 70.8947225982759, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2Y", - "roundness": { - "type": 2 - }, - "seed": 1981847930, - "version": 268, - "versionNonce": 1811385574, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -85.51047751388717, - 70.8947225982759 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "3yJZox55c-ILEPHij6IdS", - "focus": 0.2215623013879044, - "gap": 1.3987811721053487 - }, - "endBinding": { - "elementId": "MjKEQ-zh6QaziH-sClPuW", - "focus": 0.24700546159965295, - "gap": 1.7977867335244673 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "E9ckiLPEa6txaw0qv6hpe", - "type": "arrow", - "x": 658.5239352278987, - "y": 583.1303071224876, - "width": 77.61485230641233, - "height": 62.33685854491421, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2Z", - "roundness": { - "type": 2 - }, - "seed": 1102262950, - "version": 294, - "versionNonce": 448830586, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 77.61485230641233, - -62.33685854491421 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "8qAhylsGA8gx_F27etVCZ", - "focus": 0.41639007281356694, - "gap": 1.533545358572269 - }, - "endBinding": { - "elementId": "9A_gsReld74bwT77CUkTI", - "focus": 0.6557238662083935, - "gap": 1.6515596104061387 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "c1bZ9KK0GjMU1wUx8nVuQ", - "type": "rectangle", - "x": 896.1905241466702, - "y": 484.6951337541851, - "width": 139.19995117187497, - "height": 70, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "#a5d8ff", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2a", - "roundness": { - "type": 3 - }, - "seed": 196606842, - "version": 1672, - "versionNonce": 1686175782, - "isDeleted": false, - "boundElements": [ - { - "id": "HZP6NpkRPZAZUo1LeKskZ", - "type": "text" - }, - { - "id": "OVkQYwMi9Yne7aYQZeJh9", - "type": "arrow" - }, - { - "id": "oT2VVu6qyEZwdf7RrFhqQ", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "HZP6NpkRPZAZUo1LeKskZ", - "type": "text", - "x": 904.9985533214749, - "y": 499.6951337541851, - "width": 121.58389282226562, - "height": 40, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2b", - "roundness": null, - "seed": 417233978, - "version": 1688, - "versionNonce": 1613430074, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Abstracted\nexpected result", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "c1bZ9KK0GjMU1wUx8nVuQ", - "originalText": "Abstracted expected result", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "oT2VVu6qyEZwdf7RrFhqQ", - "type": "arrow", - "x": 1123.3905058361233, - "y": 573.8952527727398, - "width": 87.20001220703125, - "height": 51.999969482421875, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2c", - "roundness": { - "type": 2 - }, - "seed": 2026262054, - "version": 78, - "versionNonce": 1241123686, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -87.20001220703125, - -51.999969482421875 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "03m1x_zTn7wNWGKGdjIQU", - "focus": -0.5223483956070474, - "gap": 1 - }, - "endBinding": { - "elementId": "c1bZ9KK0GjMU1wUx8nVuQ", - "focus": -0.5199883094821327, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "OVkQYwMi9Yne7aYQZeJh9", - "type": "arrow", - "x": 895.1905241466702, - "y": 522.7244149900345, - "width": 29.40042114257801, - "height": 0.294671009747276, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2d", - "roundness": { - "type": 2 - }, - "seed": 2035852774, - "version": 129, - "versionNonce": 1930118650, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - -29.40042114257801, - 0.294671009747276 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "c1bZ9KK0GjMU1wUx8nVuQ", - "focus": -0.06896533580513858, - "gap": 1 - }, - "endBinding": { - "elementId": "9A_gsReld74bwT77CUkTI", - "focus": 0.11494251865583527, - "gap": 1.59979248046875 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "oUk_xUVqX-FGKv11BFvqr", - "type": "arrow", - "x": 661.1904020763577, - "y": 606.3612265369113, - "width": 76.40017700195335, - "height": 1.16131835695694, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2e", - "roundness": { - "type": 2 - }, - "seed": 688833018, - "version": 428, - "versionNonce": 179285670, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 76.40017700195335, - 1.16131835695694 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "8qAhylsGA8gx_F27etVCZ", - "focus": 0.045511851663706304, - "gap": 4.20001220703125 - }, - "endBinding": { - "elementId": "R8QIQIVyFH3hha72qaYHc", - "focus": 0.12921899336494835, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "Sp1qM7gCBukASzmmujQjj", - "type": "arrow", - "x": 799.5538660032678, - "y": 555.6952558244978, - "width": 0.32673372030785686, - "height": 22.39968872070301, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2f", - "roundness": { - "type": 2 - }, - "seed": 973305466, - "version": 418, - "versionNonce": 2086958778, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 0.32673372030785686, - 22.39968872070301 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "9A_gsReld74bwT77CUkTI", - "focus": 0.011674266873427656, - "gap": 1.4000549316407387 - }, - "endBinding": { - "elementId": "R8QIQIVyFH3hha72qaYHc", - "focus": 0.003148850208911594, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "o2jgH_j0wCa3l8haJniOs", - "type": "rectangle", - "x": 738.5904875255766, - "y": 729.8953366960799, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "#a5d8ff", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2g", - "roundness": { - "type": 3 - }, - "seed": 1877281318, - "version": 1757, - "versionNonce": 1129245158, - "isDeleted": false, - "boundElements": [ - { - "id": "s-OJz5HWRJV02UpnexhOv", - "type": "text" - }, - { - "id": "95QysWJKPpna8D18T4L6g", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "s-OJz5HWRJV02UpnexhOv", - "type": "text", - "x": 757.9985075451078, - "y": 744.6953397478377, - "width": 87.58392333984375, - "height": 40, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2h", - "roundness": null, - "seed": 541906278, - "version": 1789, - "versionNonce": 215584634, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Abstracted\nresource", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "o2jgH_j0wCa3l8haJniOs", - "originalText": "Abstracted\nresource", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "lyEJ-uP2TFmd3SQZjiBzJ", - "type": "rectangle", - "x": 555.3907194591703, - "y": 728.2952390398299, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2i", - "roundness": { - "type": 3 - }, - "seed": 1612265638, - "version": 1797, - "versionNonce": 1927593254, - "isDeleted": false, - "boundElements": [ - { - "id": "un1gOcEbQ3ysnvyiZbMzk", - "type": "text" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "un1gOcEbQ3ysnvyiZbMzk", - "type": "text", - "x": 562.718752906436, - "y": 743.0952420915877, - "width": 111.743896484375, - "height": 40, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2j", - "roundness": null, - "seed": 116863974, - "version": 1848, - "versionNonce": 682463290, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Internal\nimplementation", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "lyEJ-uP2TFmd3SQZjiBzJ", - "originalText": "Internal implementation", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "uCmhsxaOu6k_hStgo2i6U", - "type": "rectangle", - "x": 998.5906706310453, - "y": 729.8953061785018, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2k", - "roundness": { - "type": 3 - }, - "seed": 1861469990, - "version": 1891, - "versionNonce": 786311270, - "isDeleted": false, - "boundElements": [ - { - "id": "H_Kq-7Bqgi9UilrhYjQC8", - "type": "text" - }, - { - "id": "w-AQv0iuPCxZ2tnZ3vRlm", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "H_Kq-7Bqgi9UilrhYjQC8", - "type": "text", - "x": 1028.77467636835, - "y": 744.6953092302596, - "width": 66.03195190429688, - "height": 40, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2l", - "roundness": null, - "seed": 679211622, - "version": 1907, - "versionNonce": 1405319418, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "External\nresource", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "uCmhsxaOu6k_hStgo2i6U", - "originalText": "External resource", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "w-AQv0iuPCxZ2tnZ3vRlm", - "type": "arrow", - "x": 1125.9906340099515, - "y": 763.7100948600631, - "width": 105.3997497558596, - "height": 2.5852204737119564, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "#e9ecef", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2m", - "roundness": { - "type": 2 - }, - "seed": 777827750, - "version": 1246, - "versionNonce": 1460153254, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 105.3997497558596, - 2.5852204737119564 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "uCmhsxaOu6k_hStgo2i6U", - "focus": -0.0704232587437968, - "gap": 1 - }, - "endBinding": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "95QysWJKPpna8D18T4L6g", - "type": "arrow", - "x": 865.9904509044828, - "y": 765.933588148216, - "width": 101.39987182617222, - "height": 0.0195557729570055, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "#a5d8ff", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2n", - "roundness": { - "type": 2 - }, - "seed": 432214246, - "version": 1548, - "versionNonce": 496912826, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 101.39987182617222, - -0.0195557729570055 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "o2jgH_j0wCa3l8haJniOs", - "focus": 0.02153409123301185, - "gap": 1 - }, - "endBinding": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - }, - { - "id": "lXtl4i0-bXZ2vOos72023", - "type": "text", - "x": 853.1605864025297, - "y": 692.4952665056502, - "width": 66.0599365234375, - "height": 25, - "angle": 0, - "strokeColor": "#1e1e1e", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2o", - "roundness": null, - "seed": 1437896550, - "version": 326, - "versionNonce": 1234159334, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Legend", - "fontSize": 20, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Legend", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "A_XDsEEN-eO7TAAfIwMBO", - "type": "rectangle", - "x": 902.5904875255766, - "y": 575.8951459612164, - "width": 126.39996337890625, - "height": 69.6000061035156, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "#b2f2bb", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2p", - "roundness": { - "type": 3 - }, - "seed": 480958822, - "version": 1577, - "versionNonce": 761332346, - "isDeleted": false, - "boundElements": [ - { - "id": "FhafgNLejvOlos-DBOPLR", - "type": "text" - }, - { - "id": "fmpXvjyxrH-CkrnbrCiEC", - "type": "arrow" - } - ], - "updated": 1760631995840, - "link": null, - "locked": false - }, - { - "id": "FhafgNLejvOlos-DBOPLR", - "type": "text", - "x": 932.8064985729399, - "y": 600.6951490129742, - "width": 65.96794128417969, - "height": 20, - "angle": 0, - "strokeColor": "#2f9e44", - "backgroundColor": "transparent", - "fillStyle": "solid", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2q", - "roundness": null, - "seed": 706133158, - "version": 1556, - "versionNonce": 386005542, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "text": "Graphics", - "fontSize": 16, - "fontFamily": 5, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "A_XDsEEN-eO7TAAfIwMBO", - "originalText": "Graphics", - "autoResize": true, - "lineHeight": 1.25 - }, - { - "id": "fmpXvjyxrH-CkrnbrCiEC", - "type": "arrow", - "x": 862.5904570079985, - "y": 612.2952466692242, - "width": 39.20001220703125, - "height": 0.800048828125, - "angle": 0, - "strokeColor": "#1971c2", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 2, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "frameId": null, - "index": "b2r", - "roundness": { - "type": 2 - }, - "seed": 524433658, - "version": 38, - "versionNonce": 1305387834, - "isDeleted": false, - "boundElements": [], - "updated": 1760631995840, - "link": null, - "locked": false, - "points": [ - [ - 0, - 0 - ], - [ - 39.20001220703125, - 0.800048828125 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "R8QIQIVyFH3hha72qaYHc", - "focus": -0.057449628868013615, - "gap": 1 - }, - "endBinding": { - "elementId": "A_XDsEEN-eO7TAAfIwMBO", - "focus": -0.10269772475101732, - "gap": 1 - }, - "startArrowhead": null, - "endArrowhead": "arrow", - "elbowed": false - } - ], - "appState": { - "gridSize": 20, - "gridStep": 5, - "gridModeEnabled": false, - "viewBackgroundColor": "#ffffff", - "lockedMultiSelections": {} - }, - "files": { - "406391fa8caaf383e1c546938fa1b5e310bbdcc4": { - "mimeType": "image/svg+xml", - "id": "406391fa8caaf383e1c546938fa1b5e310bbdcc4", - "dataURL": "data:image/svg+xml;base64,PHN2ZyB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iODAiIHZpZXdCb3g9IjAgLTk2MCAxNjAwIDE2MDAiIHdpZHRoPSI4MCIgZmlsbD0iIzQ4NzUyYyIgdmVyc2lvbj0iMS4xIiBpZD0ic3ZnMSIgc29kaXBvZGk6ZG9jbmFtZT0ibG9nby5zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIGlua3NjYXBlOnZlcnNpb249IjEuNC4yIChmNDMyN2Y0LCAyMDI1LTA1LTEzKSIgaW5rc2NhcGU6ZXhwb3J0LWZpbGVuYW1lPSJsb2dvLnBuZyIgaW5rc2NhcGU6ZXhwb3J0LXhkcGk9Ijk2IiBpbmtzY2FwZTpleHBvcnQteWRwaT0iOTYiPjxkZWZzIGlkPSJkZWZzMSIvPjxzb2RpcG9kaTpuYW1lZHZpZXcgaWQ9Im5hbWVkdmlldzEiIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIgYm9yZGVyY29sb3I9IiMwMDAwMDAiIGJvcmRlcm9wYWNpdHk9IjAiIGlua3NjYXBlOnNob3dwYWdlc2hhZG93PSIyIiBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIiBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSJ0cnVlIiBpbmtzY2FwZTpkZXNrY29sb3I9IiNkMWQxZDEiIGlua3NjYXBlOnpvb209IjUuNzY3MzM5NyIgaW5rc2NhcGU6Y3g9IjI0LjI3NDYyNCIgaW5rc2NhcGU6Y3k9IjU5LjIxMjc0MiIgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxOTIwIiBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIiBpbmtzY2FwZTp3aW5kb3cteD0iLTkiIGlua3NjYXBlOndpbmRvdy15PSItOSIgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnMSIgaW5rc2NhcGU6ZXhwb3J0LWJnY29sb3I9IiNmZmZmZmZmZiIvPjxjaXJjbGUgc3R5bGU9ImZpbGw6I2U2ZTZlNjtzdHJva2Utd2lkdGg6NTkuNzUxOSIgaWQ9InBhdGgyIiBjeD0iODAwIiBjeT0iLTE2MCIgcj0iODAwIi8+PGcgc3R5bGU9ImZpbGw6IzNlOWMzNjtmaWxsLW9wYWNpdHk6MSIgaWQ9Imc0IiB0cmFuc2Zvcm09Im1hdHJpeCgwLjkyNzcyNDkyLDAsMCwwLjkyNzcyNDkyLDM1NC42OTIwNCwyODUuMzA3OTYpIj48cGF0aCBkPSJNIDQ4MCwtODAgUSAzNDAsLTExNSAyNTAsLTI0Mi41IDE2MCwtMzcwIDE2MCwtNTIyIHYgLTIzOCBsIDMyMCwtMTIwIDMyMCwxMjAgdiAyMzggcSAwLDE1MiAtOTAsMjc5LjUgUSA2MjAsLTExNSA0ODAsLTgwIFogbSAwLC02MiBRIDU4NiwtMTc3IDY1NS41LC0yNzAuNSA3MjUsLTM2NCA3MzcsLTQ4MCBIIDQ4MCB2IC0zMzUgbCAtMjYwLDk3IHYgMTk2IHEgMCwxMiAwLjUsMjAuNSAwLjUsOC41IDIuNSwyMS41IGggMjU3IHoiIGlkPSJwYXRoMS0xIiBzdHlsZT0iZmlsbDojM2U5YzM2O2ZpbGwtb3BhY2l0eToxIi8+PC9nPjxnIHN0eWxlPSJmaWxsOiMzYTNhM2E7ZmlsbC1vcGFjaXR5OjEiIGlkPSJnMyIgdHJhbnNmb3JtPSJtYXRyaXgoMiwwLDAsMiwtMzAxOC42MTI0LDM5Ny45OTY0MykiPjxwYXRoIGQ9Im0gMTc0OS4zMDYyLC0zOC45OTgyMTUgLTI0MCwtMjQwLjAwMDAwNSAyNDIsLTI0MiA0Myw0MyAtMTk5LDE5OSAxOTcsMTk3LjAwMDAwMiB6IG0gMzE4LDIgLTQzLC00My4wMDAwMDMgMTk5LC0xOTkuMDAwMDAyIC0xOTcsLTE5NyA0MywtNDMgMjQwLDI0MCB6IiBpZD0icGF0aDEiIHN0eWxlPSJmaWxsOiMzYTNhM2E7ZmlsbC1vcGFjaXR5OjEiLz48L2c+PC9zdmc+", - "created": 1756930410680, - "lastRetrieved": 1760631996273 - } - } -} \ No newline at end of file diff --git a/docs/assets/overview.svg b/docs/assets/overview.svg deleted file mode 100644 index 766d964..0000000 --- a/docs/assets/overview.svg +++ /dev/null @@ -1,4 +0,0 @@ - - -CodeSecToolsData abstractionSAST ToolProjectDatasetSAST ToolabstractionAnalyzerParserValidation(Dataset only)Tool resultAbstractedSAST resultStatisticsCodebaseExpectedresultCodebaseAbstractedexpected resultAbstractedresourceInternalimplementationExternalresourceLegendGraphics \ No newline at end of file diff --git a/docs/assets/workflow.excalidraw b/docs/assets/workflow.excalidraw new file mode 100644 index 0000000..e8a9a9c --- /dev/null +++ b/docs/assets/workflow.excalidraw @@ -0,0 +1,1732 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "1WOw0ruK_4ow9UpBTbOCH", + "type": "rectangle", + "x": 740.4000244140625, + "y": 172.19997406005857, + "width": 605.6000366210936, + "height": 399.2000427246094, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3r", + "roundness": { + "type": 3 + }, + "seed": 1380637624, + "version": 879, + "versionNonce": 2082261283, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "nJwG9rIAOjSBHo-8Kvwn_" + } + ], + "updated": 1770051681264, + "link": null, + "locked": false + }, + { + "id": "nJwG9rIAOjSBHo-8Kvwn_", + "type": "text", + "x": 979.5300827026367, + "y": 177.19997406005857, + "width": 127.33992004394531, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3r4", + "roundness": null, + "seed": 1310219704, + "version": 677, + "versionNonce": 2009730701, + "isDeleted": false, + "boundElements": [], + "updated": 1770051681264, + "link": null, + "locked": false, + "text": "CodeSecTools", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "1WOw0ruK_4ow9UpBTbOCH", + "originalText": "CodeSecTools", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "rrAVnM6wJfyO_K3yT1qYt", + "type": "arrow", + "x": 919.4000244140625, + "y": 373.20001220703125, + "width": 97.5999755859375, + "height": 1.600006103515625, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4H", + "roundness": null, + "seed": 1865976008, + "version": 242, + "versionNonce": 1565629027, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 97.5999755859375, + 1.600006103515625 + ] + ], + "startBinding": { + "elementId": "dcMvFl9-jYTSN7OKHo1zk", + "mode": "inside", + "fixedPoint": [ + 0.9851854112413194, + 0.5068505158854681 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.006172743571883711, + 0.5931374162309373 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "K4k1y-4rYKVzgMnL6tx12", + "type": "arrow", + "x": 920.2000122070312, + "y": 484.3999938964844, + "width": 102.4000244140625, + "height": 80.79998779296875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4I", + "roundness": null, + "seed": 1355795400, + "version": 235, + "versionNonce": 1053094211, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685300, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 102.4000244140625, + -80.79998779296875 + ] + ], + "startBinding": { + "elementId": "2rrktBRftxGKG5SWNKpk6", + "mode": "inside", + "fixedPoint": [ + 0.9925927056206597, + 0.5342477457363579 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.04938289047536295, + 0.9460784167062785 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "lxSPet11KFTLTcwCOY2Nz", + "type": "arrow", + "x": 484.36166615463213, + "y": 410.51581228472793, + "width": 81.23837046646162, + "height": 81.08425485394395, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4LG", + "roundness": null, + "seed": 1453316808, + "version": 230, + "versionNonce": 1348451784, + "isDeleted": false, + "boundElements": [], + "updated": 1770049174596, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 81.23837046646162, + 81.08425485394395 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.8146520798858764, + 0.9653344396982297 + ] + }, + "endBinding": { + "elementId": "n9L2RTRKZYvHSmSDiSStq", + "mode": "inside", + "fixedPoint": [ + 0.009756227818931021, + 0.6105786124627609 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "B_4OBDuRAeWhtde-0m8sU", + "type": "arrow", + "x": 484.11648967152416, + "y": 333.6315699389768, + "width": 80.68349812144459, + "height": 66.8315516284299, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4LV", + "roundness": null, + "seed": 1703575496, + "version": 259, + "versionNonce": 710640056, + "isDeleted": false, + "boundElements": [], + "updated": 1770049171292, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 80.68349812144459, + -66.8315516284299 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.8129588719868639, + 0.04124478505474318 + ] + }, + "endBinding": { + "elementId": "0kZdpb30DwuhVWczqWxNN", + "mode": "inside", + "fixedPoint": [ + 0.004877881305973704, + 0.5432699396764403 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "muRO6gu_oT3NGR8LL10PV", + "type": "arrow", + "x": 724.5999908447266, + "y": 266.1572433540849, + "width": 91.60002136230469, + "height": 0.9572311470536761, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4Y", + "roundness": null, + "seed": 140997832, + "version": 169, + "versionNonce": 1551157027, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 91.60002136230469, + -0.9572311470536761 + ] + ], + "startBinding": { + "elementId": "0kZdpb30DwuhVWczqWxNN", + "mode": "inside", + "fixedPoint": [ + 0.979268143816692, + 0.5355442802755439 + ] + }, + "endBinding": { + "elementId": "bXzZn0RfDeKIo_ZZ9vHOA", + "mode": "inside", + "fixedPoint": [ + 0.02962974265769676, + 0.5342474844557642 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "TO6JDEUsMDWonPnK9L6gC", + "type": "arrow", + "x": 727.2000122070312, + "y": 373.20001220703125, + "width": 88.20001220703125, + "height": 1.600006103515625, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4Z", + "roundness": null, + "seed": 1645462728, + "version": 157, + "versionNonce": 1771460099, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 88.20001220703125, + 1.600006103515625 + ] + ], + "startBinding": { + "elementId": "UAf-Zc1R3X18DN0yp8ThH", + "mode": "inside", + "fixedPoint": [ + 0.9951219326112328, + 0.5048085352371604 + ] + }, + "endBinding": { + "elementId": "dcMvFl9-jYTSN7OKHo1zk", + "mode": "inside", + "fixedPoint": [ + 0.022222448278356483, + 0.5342478763766547 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "PXV7WEIYbkhaSxyWvJo2h", + "type": "arrow", + "x": 728, + "y": 482.8000183105469, + "width": 85.79998779296875, + "height": 0.79998779296875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4a", + "roundness": null, + "seed": 992697784, + "version": 169, + "versionNonce": 876335331, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685300, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 85.79998779296875, + 0.79998779296875 + ] + ], + "startBinding": { + "elementId": "n9L2RTRKZYvHSmSDiSStq", + "mode": "inside", + "fixedPoint": [ + 0.9999999069586033, + 0.5048088103354805 + ] + }, + "endBinding": { + "elementId": "2rrktBRftxGKG5SWNKpk6", + "mode": "inside", + "fixedPoint": [ + 0.007407294379340278, + 0.5205493267713582 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "UAf-Zc1R3X18DN0yp8ThH", + "type": "rectangle", + "x": 564.0000152587891, + "y": 331.19993591308594, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4b", + "roundness": null, + "seed": 1319958968, + "version": 291, + "versionNonce": 1197012936, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Ler9FfGduELynvKU7J5tl" + }, + { + "id": "Pej9NH0sbkW-bmZ8tq4CN", + "type": "arrow" + }, + { + "id": "TO6JDEUsMDWonPnK9L6gC", + "type": "arrow" + } + ], + "updated": 1770048619895, + "link": null, + "locked": false + }, + { + "id": "Pej9NH0sbkW-bmZ8tq4CN", + "type": "arrow", + "x": 511.20002156107876, + "y": 372.62106161038275, + "width": 54.399954024858744, + "height": 0.5789505966484967, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4bV", + "roundness": null, + "seed": 282682568, + "version": 181, + "versionNonce": 1442859464, + "isDeleted": false, + "boundElements": [], + "updated": 1770049167965, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 54.399954024858744, + 0.5789505966484967 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.999999853843008, + 0.5098685861624841 + ] + }, + "endBinding": { + "elementId": "UAf-Zc1R3X18DN0yp8ThH", + "mode": "inside", + "fixedPoint": [ + 0.009755855653344131, + 0.5048085352371604 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "Ler9FfGduELynvKU7J5tl", + "type": "text", + "x": 598.3700485229492, + "y": 360.29994201660156, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4c", + "roundness": null, + "seed": 876334024, + "version": 273, + "versionNonce": 816400568, + "isDeleted": false, + "boundElements": [], + "updated": 1770048619895, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UAf-Zc1R3X18DN0yp8ThH", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "n9L2RTRKZYvHSmSDiSStq", + "type": "rectangle", + "x": 564.0000152587891, + "y": 440.79991912841797, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4d", + "roundness": null, + "seed": 823674808, + "version": 338, + "versionNonce": 2146877128, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "sD7QI9NrBkB5q_DogfQB9" + }, + { + "id": "lxSPet11KFTLTcwCOY2Nz", + "type": "arrow" + }, + { + "id": "PXV7WEIYbkhaSxyWvJo2h", + "type": "arrow" + } + ], + "updated": 1770048619895, + "link": null, + "locked": false + }, + { + "id": "sD7QI9NrBkB5q_DogfQB9", + "type": "text", + "x": 598.3700485229492, + "y": 469.8999252319336, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4e", + "roundness": null, + "seed": 1730580664, + "version": 320, + "versionNonce": 787444152, + "isDeleted": false, + "boundElements": [], + "updated": 1770048619895, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "n9L2RTRKZYvHSmSDiSStq", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0kZdpb30DwuhVWczqWxNN", + "type": "rectangle", + "x": 564.0000152587891, + "y": 221.5999526977539, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4f", + "roundness": null, + "seed": 579144136, + "version": 334, + "versionNonce": 653894584, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "w8wF3Ew_yHchj_y6LKxNi" + }, + { + "id": "B_4OBDuRAeWhtde-0m8sU", + "type": "arrow" + }, + { + "id": "muRO6gu_oT3NGR8LL10PV", + "type": "arrow" + } + ], + "updated": 1770049055599, + "link": null, + "locked": false + }, + { + "id": "w8wF3Ew_yHchj_y6LKxNi", + "type": "text", + "x": 598.3700485229492, + "y": 250.69995880126953, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4g", + "roundness": null, + "seed": 2144374984, + "version": 310, + "versionNonce": 881359544, + "isDeleted": false, + "boundElements": [], + "updated": 1770048619895, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0kZdpb30DwuhVWczqWxNN", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "bXzZn0RfDeKIo_ZZ9vHOA", + "type": "rectangle", + "x": 813, + "y": 233.9999542236328, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4h", + "roundness": null, + "seed": 143356344, + "version": 578, + "versionNonce": 1832304589, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "trG_aSB1_qOnsdMxFWG17" + }, + { + "id": "fOWe71rlar6m5Kqkgkrma", + "type": "arrow" + }, + { + "id": "muRO6gu_oT3NGR8LL10PV", + "type": "arrow" + } + ], + "updated": 1770051685299, + "link": null, + "locked": false + }, + { + "id": "trG_aSB1_qOnsdMxFWG17", + "type": "text", + "x": 838.0800323486328, + "y": 250.69995880126953, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4i", + "roundness": null, + "seed": 2057599672, + "version": 568, + "versionNonce": 490072771, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bXzZn0RfDeKIo_ZZ9vHOA", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "dcMvFl9-jYTSN7OKHo1zk", + "type": "rectangle", + "x": 813, + "y": 343.59993743896484, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4j", + "roundness": null, + "seed": 406854856, + "version": 605, + "versionNonce": 1770346221, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "R49xUx-Q8Xi7AjZA-x7x5" + }, + { + "id": "rrAVnM6wJfyO_K3yT1qYt", + "type": "arrow" + }, + { + "id": "TO6JDEUsMDWonPnK9L6gC", + "type": "arrow" + } + ], + "updated": 1770051685299, + "link": null, + "locked": false + }, + { + "id": "R49xUx-Q8Xi7AjZA-x7x5", + "type": "text", + "x": 838.0800323486328, + "y": 360.29994201660156, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4k", + "roundness": null, + "seed": 1387348936, + "version": 594, + "versionNonce": 1895996835, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dcMvFl9-jYTSN7OKHo1zk", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "2rrktBRftxGKG5SWNKpk6", + "type": "rectangle", + "x": 813, + "y": 453.1999206542969, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4l", + "roundness": null, + "seed": 1097044936, + "version": 599, + "versionNonce": 205111821, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "jGETh8zSNN5_lYAlPltrY" + }, + { + "id": "K4k1y-4rYKVzgMnL6tx12", + "type": "arrow" + }, + { + "id": "PXV7WEIYbkhaSxyWvJo2h", + "type": "arrow" + } + ], + "updated": 1770051685299, + "link": null, + "locked": false + }, + { + "id": "jGETh8zSNN5_lYAlPltrY", + "type": "text", + "x": 838.0800323486328, + "y": 469.8999252319336, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4m", + "roundness": null, + "seed": 374201032, + "version": 589, + "versionNonce": 49659011, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685300, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2rrktBRftxGKG5SWNKpk6", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yRd5B5qRIYBGw5M1RVEb8", + "type": "arrow", + "x": 1145, + "y": 369.20001220703125, + "width": 54.4000244140625, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4r", + "roundness": null, + "seed": 2117963448, + "version": 182, + "versionNonce": 2063292856, + "isDeleted": false, + "boundElements": [], + "updated": 1770049619040, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 54.4000244140625, + 0 + ] + ], + "startBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.9938267854779697, + 0.5245098955857598 + ] + }, + "endBinding": { + "elementId": "J_ISCBLVR5LOB2VG1Diu_", + "mode": "inside", + "fixedPoint": [ + 0.015431858929709278, + 0.5245098955857598 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "fOWe71rlar6m5Kqkgkrma", + "type": "arrow", + "x": 921, + "y": 262.8000183105469, + "width": 101.60003662109375, + "height": 71.199951171875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4sV", + "roundness": null, + "seed": 1963846088, + "version": 310, + "versionNonce": 913289091, + "isDeleted": false, + "boundElements": [], + "updated": 1770051685299, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 101.60003662109375, + 71.199951171875 + ] + ], + "startBinding": { + "elementId": "bXzZn0RfDeKIo_ZZ9vHOA", + "mode": "inside", + "fixedPoint": [ + 1, + 0.49315170499957794 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.04938289047536295, + 0.09313685524608722 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "oym1FSPYzGbJi-oudw4l8", + "type": "rectangle", + "x": 1016.2000122070312, + "y": 326.4000015258789, + "width": 129.60003662109375, + "height": 81.60000610351562, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4y", + "roundness": { + "type": 3 + }, + "seed": 1228400072, + "version": 188, + "versionNonce": 2001560008, + "isDeleted": false, + "boundElements": [ + { + "id": "fOWe71rlar6m5Kqkgkrma", + "type": "arrow" + }, + { + "id": "rrAVnM6wJfyO_K3yT1qYt", + "type": "arrow" + }, + { + "id": "K4k1y-4rYKVzgMnL6tx12", + "type": "arrow" + }, + { + "type": "text", + "id": "PX7mz1B5W4A9VvNBTl382" + }, + { + "id": "yRd5B5qRIYBGw5M1RVEb8", + "type": "arrow" + } + ], + "updated": 1770049619039, + "link": null, + "locked": false + }, + { + "id": "PX7mz1B5W4A9VvNBTl382", + "type": "text", + "x": 1027.0300903320312, + "y": 342.2000045776367, + "width": 107.93988037109375, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4z", + "roundness": null, + "seed": 607559864, + "version": 152, + "versionNonce": 1706566088, + "isDeleted": false, + "boundElements": [], + "updated": 1770049200027, + "link": null, + "locked": false, + "text": "Aggregated\nresults", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oym1FSPYzGbJi-oudw4l8", + "originalText": "Aggregated\nresults", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "fTYfDZndWAse-4VQW-PAO", + "type": "rectangle", + "x": 366.4000549316406, + "y": 330.2000045776367, + "width": 144.79998779296875, + "height": 83.19998168945312, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b50", + "roundness": { + "type": 3 + }, + "seed": 556361400, + "version": 214, + "versionNonce": 768666568, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "zZgf_bf9Sib4SS7ZUoyJr" + }, + { + "id": "B_4OBDuRAeWhtde-0m8sU", + "type": "arrow" + }, + { + "id": "Pej9NH0sbkW-bmZ8tq4CN", + "type": "arrow" + }, + { + "id": "lxSPet11KFTLTcwCOY2Nz", + "type": "arrow" + } + ], + "updated": 1770049177837, + "link": null, + "locked": false + }, + { + "id": "zZgf_bf9Sib4SS7ZUoyJr", + "type": "text", + "x": 383.9300994873047, + "y": 359.2999954223633, + "width": 109.73989868164062, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b51", + "roundness": null, + "seed": 1645277384, + "version": 190, + "versionNonce": 81433784, + "isDeleted": false, + "boundElements": [], + "updated": 1770049177837, + "link": null, + "locked": false, + "text": "Source code", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fTYfDZndWAse-4VQW-PAO", + "originalText": "Source code", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "XQMhpfiDErmPPrP58kIeR", + "type": "rectangle", + "x": 718.6000366210938, + "y": 202.99999237060547, + "width": 40.79998779296875, + "height": 337.6000061035156, + "angle": 4.665933729156906, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b52", + "roundness": { + "type": 3 + }, + "seed": 627975096, + "version": 185, + "versionNonce": 1620466120, + "isDeleted": true, + "boundElements": [], + "updated": 1770049272120, + "link": null, + "locked": false + }, + { + "id": "KnBBV3gOcCv0w_JkVhEp7", + "type": "text", + "x": 736.3900375366211, + "y": 359.2999954223633, + "width": 5.2199859619140625, + "height": 25, + "angle": 4.665933729156906, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b53", + "roundness": null, + "seed": 1557318840, + "version": 3, + "versionNonce": 717215432, + "isDeleted": true, + "boundElements": [], + "updated": 1770049266318, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "XQMhpfiDErmPPrP58kIeR", + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "AjhpjKNHcjNstE-w5JRUR", + "type": "rectangle", + "x": 571.2000122070312, + "y": 352.60001373291016, + "width": 333.60003662109375, + "height": 38.39996337890625, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b54", + "roundness": { + "type": 3 + }, + "seed": 255320264, + "version": 309, + "versionNonce": 1756476616, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "buEoQfq-MFykf-i2ZXMPZ" + } + ], + "updated": 1770049313117, + "link": null, + "locked": false + }, + { + "id": "buEoQfq-MFykf-i2ZXMPZ", + "type": "text", + "x": 688.6100921630859, + "y": 359.2999954223633, + "width": 98.77987670898438, + "height": 25, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b55", + "roundness": null, + "seed": 1028564424, + "version": 288, + "versionNonce": 968535496, + "isDeleted": false, + "boundElements": [], + "updated": 1770049313117, + "link": null, + "locked": false, + "text": "Integration", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AjhpjKNHcjNstE-w5JRUR", + "originalText": "Integration", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "bLiA2D01gq3Z2cAzWpznu", + "type": "arrow", + "x": 1323.6001892089844, + "y": 366.53299404853783, + "width": 59.199859619140625, + "height": 1.0670120549777948, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b58", + "roundness": null, + "seed": 539099064, + "version": 104, + "versionNonce": 871280824, + "isDeleted": false, + "boundElements": [], + "updated": 1770049646898, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 59.199859619140625, + 1.0670120549777948 + ] + ], + "startBinding": { + "elementId": "J_ISCBLVR5LOB2VG1Diu_", + "mode": "inside", + "fixedPoint": [ + 0.9737661930321042, + 0.49182585197049206 + ] + }, + "endBinding": { + "elementId": "opw3caUApWxtIMKsYNcjd", + "mode": "inside", + "fixedPoint": [ + 0.008367913003200838, + 0.49784957721669204 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "J_ISCBLVR5LOB2VG1Diu_", + "type": "rectangle", + "x": 1197.4000549316406, + "y": 326.4000015258789, + "width": 129.60003662109375, + "height": 81.60000610351562, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b59", + "roundness": { + "type": 3 + }, + "seed": 1414008504, + "version": 268, + "versionNonce": 767619000, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "fKu2YRE8Qt_Jscad04g0Z" + }, + { + "id": "yRd5B5qRIYBGw5M1RVEb8", + "type": "arrow" + }, + { + "id": "bLiA2D01gq3Z2cAzWpznu", + "type": "arrow" + } + ], + "updated": 1770049639027, + "link": null, + "locked": false + }, + { + "id": "fKu2YRE8Qt_Jscad04g0Z", + "type": "text", + "x": 1211.510124206543, + "y": 342.2000045776367, + "width": 101.37989807128906, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5A", + "roundness": null, + "seed": 1053128632, + "version": 252, + "versionNonce": 1053886408, + "isDeleted": false, + "boundElements": [], + "updated": 1770049639027, + "link": null, + "locked": false, + "text": "Cross-\nVerification", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "J_ISCBLVR5LOB2VG1Diu_", + "originalText": "Cross-Verification", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "opw3caUApWxtIMKsYNcjd", + "type": "rectangle", + "x": 1381.2001037597656, + "y": 321.2999954223633, + "width": 191.20001220703116, + "height": 92.99999999999997, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5B", + "roundness": { + "type": 3 + }, + "seed": 1539448264, + "version": 363, + "versionNonce": 943787976, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "DyKZQIke44c5gOlxrI8jK" + }, + { + "id": "bLiA2D01gq3Z2cAzWpznu", + "type": "arrow" + } + ], + "updated": 1770049646898, + "link": null, + "locked": false + }, + { + "id": "DyKZQIke44c5gOlxrI8jK", + "type": "text", + "x": 1399.9101867675781, + "y": 342.7999954223633, + "width": 153.77984619140625, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5C", + "roundness": null, + "seed": 944448712, + "version": 374, + "versionNonce": 1967643064, + "isDeleted": false, + "boundElements": [], + "updated": 1770049646898, + "link": null, + "locked": false, + "text": "Increased\nconfidence result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "opw3caUApWxtIMKsYNcjd", + "originalText": "Increased confidence result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0TOFIwQSc8GiS4pLS0WcM", + "type": "rectangle", + "x": 768.9999694824219, + "y": 352.60001373291016, + "width": 333.60003662109375, + "height": 38.39996337890625, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5D", + "roundness": { + "type": 3 + }, + "seed": 1450273539, + "version": 473, + "versionNonce": 739247139, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Sn8-maQGyEuwNRVU08O3z" + } + ], + "updated": 1770051710487, + "link": null, + "locked": false + }, + { + "id": "Sn8-maQGyEuwNRVU08O3z", + "type": "text", + "x": 906.9200210571289, + "y": 359.2999954223633, + "width": 57.75993347167969, + "height": 25, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5E", + "roundness": null, + "seed": 464720547, + "version": 458, + "versionNonce": 1610351501, + "isDeleted": false, + "boundElements": [], + "updated": 1770051710487, + "link": null, + "locked": false, + "text": "Parser", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0TOFIwQSc8GiS4pLS0WcM", + "originalText": "Parser", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/assets/workflow.svg b/docs/assets/workflow.svg new file mode 100644 index 0000000..4e48838 --- /dev/null +++ b/docs/assets/workflow.svg @@ -0,0 +1,4 @@ + + +CodeSecToolsSAST ToolSAST ToolSAST ToolResultResultResultAggregatedresultsSource codeIntegrationCross-VerificationIncreasedconfidence resultParser \ No newline at end of file diff --git a/docs/assets/workflow_example.excalidraw b/docs/assets/workflow_example.excalidraw new file mode 100644 index 0000000..9160c1f --- /dev/null +++ b/docs/assets/workflow_example.excalidraw @@ -0,0 +1,4970 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "H5vXVSWVkSU7FGxjWLU3x", + "type": "rectangle", + "x": 709.7999572753906, + "y": 650.4000396728516, + "width": 654.4000854492186, + "height": 399.2000427246094, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3p", + "roundness": { + "type": 3 + }, + "seed": 2012012232, + "version": 1064, + "versionNonce": 146249912, + "isDeleted": false, + "boundElements": [ + { + "id": "od7XvY1LuafGqrZR-T32u", + "type": "text" + } + ], + "updated": 1770051483035, + "link": null, + "locked": false + }, + { + "id": "od7XvY1LuafGqrZR-T32u", + "type": "text", + "x": 973.3300399780273, + "y": 655.4000396728516, + "width": 127.33992004394531, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3q", + "roundness": null, + "seed": 1668662728, + "version": 862, + "versionNonce": 280215992, + "isDeleted": false, + "boundElements": [], + "updated": 1770051483036, + "link": null, + "locked": false, + "text": "CodeSecTools", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "H5vXVSWVkSU7FGxjWLU3x", + "originalText": "CodeSecTools", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "1WOw0ruK_4ow9UpBTbOCH", + "type": "rectangle", + "x": 740.4000244140625, + "y": 172.19997406005857, + "width": 605.6000366210936, + "height": 399.2000427246094, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3r", + "roundness": { + "type": 3 + }, + "seed": 1380637624, + "version": 853, + "versionNonce": 1068873656, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "nJwG9rIAOjSBHo-8Kvwn_", + "type": "text", + "x": 979.5300827026367, + "y": 177.19997406005857, + "width": 127.33992004394531, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b3r4", + "roundness": null, + "seed": 1310219704, + "version": 651, + "versionNonce": 158121928, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "CodeSecTools", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "1WOw0ruK_4ow9UpBTbOCH", + "originalText": "CodeSecTools", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "rrAVnM6wJfyO_K3yT1qYt", + "type": "arrow", + "x": 963.4000244140625, + "y": 373.20001220703125, + "width": 53.5999755859375, + "height": 1.600006103515625, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4H", + "roundness": null, + "seed": 1865976008, + "version": 201, + "versionNonce": 546701496, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 53.5999755859375, + 1.600006103515625 + ] + ], + "startBinding": { + "elementId": "dcMvFl9-jYTSN7OKHo1zk", + "mode": "inside", + "fixedPoint": [ + 0.9851854112413194, + 0.5068505158854681 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.006172743571883711, + 0.5931374162309373 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "K4k1y-4rYKVzgMnL6tx12", + "type": "arrow", + "x": 964.2000122070312, + "y": 484.3999938964844, + "width": 58.4000244140625, + "height": 80.79998779296875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4I", + "roundness": null, + "seed": 1355795400, + "version": 194, + "versionNonce": 1548846792, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 58.4000244140625, + -80.79998779296875 + ] + ], + "startBinding": { + "elementId": "2rrktBRftxGKG5SWNKpk6", + "mode": "inside", + "fixedPoint": [ + 0.9925927056206597, + 0.5342477457363579 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.04938289047536295, + 0.9460784167062785 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "lxSPet11KFTLTcwCOY2Nz", + "type": "arrow", + "x": 484.36166615463213, + "y": 410.51581228472793, + "width": 81.23837046646162, + "height": 81.08425485394395, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4LG", + "roundness": null, + "seed": 1453316808, + "version": 233, + "versionNonce": 1887775160, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 81.23837046646162, + 81.08425485394395 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.8146520798858764, + 0.9653344396982297 + ] + }, + "endBinding": { + "elementId": "n9L2RTRKZYvHSmSDiSStq", + "mode": "inside", + "fixedPoint": [ + 0.009756227818931021, + 0.6105786124627609 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "B_4OBDuRAeWhtde-0m8sU", + "type": "arrow", + "x": 484.11648967152416, + "y": 333.6315699389768, + "width": 80.68349812144459, + "height": 66.8315516284299, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4LV", + "roundness": null, + "seed": 1703575496, + "version": 264, + "versionNonce": 621112776, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 80.68349812144459, + -66.8315516284299 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.8129588719868639, + 0.04124478505474318 + ] + }, + "endBinding": { + "elementId": "0kZdpb30DwuhVWczqWxNN", + "mode": "inside", + "fixedPoint": [ + 0.004877881305973704, + 0.5432699396764403 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "muRO6gu_oT3NGR8LL10PV", + "type": "arrow", + "x": 724.5999908447266, + "y": 266.1572433540849, + "width": 135.6000213623047, + "height": 0.9572311470536761, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4Y", + "roundness": null, + "seed": 140997832, + "version": 128, + "versionNonce": 715600568, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 135.6000213623047, + -0.9572311470536761 + ] + ], + "startBinding": { + "elementId": "0kZdpb30DwuhVWczqWxNN", + "mode": "inside", + "fixedPoint": [ + 0.979268143816692, + 0.5355442802755439 + ] + }, + "endBinding": { + "elementId": "bXzZn0RfDeKIo_ZZ9vHOA", + "mode": "inside", + "fixedPoint": [ + 0.02962974265769676, + 0.5342474844557642 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "TO6JDEUsMDWonPnK9L6gC", + "type": "arrow", + "x": 727.2000122070312, + "y": 373.20001220703125, + "width": 132.20001220703125, + "height": 1.600006103515625, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4Z", + "roundness": null, + "seed": 1645462728, + "version": 116, + "versionNonce": 480539848, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 132.20001220703125, + 1.600006103515625 + ] + ], + "startBinding": { + "elementId": "UAf-Zc1R3X18DN0yp8ThH", + "mode": "inside", + "fixedPoint": [ + 0.9951219326112328, + 0.5048085352371604 + ] + }, + "endBinding": { + "elementId": "dcMvFl9-jYTSN7OKHo1zk", + "mode": "inside", + "fixedPoint": [ + 0.022222448278356483, + 0.5342478763766547 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "PXV7WEIYbkhaSxyWvJo2h", + "type": "arrow", + "x": 728, + "y": 482.8000183105469, + "width": 129.79998779296875, + "height": 0.79998779296875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4a", + "roundness": null, + "seed": 992697784, + "version": 128, + "versionNonce": 1168120760, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 129.79998779296875, + 0.79998779296875 + ] + ], + "startBinding": { + "elementId": "n9L2RTRKZYvHSmSDiSStq", + "mode": "inside", + "fixedPoint": [ + 0.9999999069586033, + 0.5048088103354805 + ] + }, + "endBinding": { + "elementId": "2rrktBRftxGKG5SWNKpk6", + "mode": "inside", + "fixedPoint": [ + 0.007407294379340278, + 0.5205493267713582 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "UAf-Zc1R3X18DN0yp8ThH", + "type": "rectangle", + "x": 564.0000152587891, + "y": 331.19993591308594, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4b", + "roundness": null, + "seed": 1319958968, + "version": 296, + "versionNonce": 2026544072, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "Pej9NH0sbkW-bmZ8tq4CN", + "type": "arrow", + "x": 511.20002156107876, + "y": 372.62106161038275, + "width": 54.399954024858744, + "height": 0.5789505966484967, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4bV", + "roundness": null, + "seed": 282682568, + "version": 186, + "versionNonce": 1133765816, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 54.399954024858744, + 0.5789505966484967 + ] + ], + "startBinding": { + "elementId": "fTYfDZndWAse-4VQW-PAO", + "mode": "inside", + "fixedPoint": [ + 0.999999853843008, + 0.5098685861624841 + ] + }, + "endBinding": { + "elementId": "UAf-Zc1R3X18DN0yp8ThH", + "mode": "inside", + "fixedPoint": [ + 0.009755855653344131, + 0.5048085352371604 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "Ler9FfGduELynvKU7J5tl", + "type": "text", + "x": 598.3700485229492, + "y": 360.29994201660156, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4c", + "roundness": null, + "seed": 876334024, + "version": 277, + "versionNonce": 1536418504, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "UAf-Zc1R3X18DN0yp8ThH", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "n9L2RTRKZYvHSmSDiSStq", + "type": "rectangle", + "x": 564.0000152587891, + "y": 440.79991912841797, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4d", + "roundness": null, + "seed": 823674808, + "version": 341, + "versionNonce": 1109822904, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "sD7QI9NrBkB5q_DogfQB9", + "type": "text", + "x": 598.3700485229492, + "y": 469.8999252319336, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4e", + "roundness": null, + "seed": 1730580664, + "version": 323, + "versionNonce": 1134315976, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "n9L2RTRKZYvHSmSDiSStq", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "0kZdpb30DwuhVWczqWxNN", + "type": "rectangle", + "x": 564.0000152587891, + "y": 221.5999526977539, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4f", + "roundness": null, + "seed": 579144136, + "version": 339, + "versionNonce": 175904440, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "w8wF3Ew_yHchj_y6LKxNi", + "type": "text", + "x": 598.3700485229492, + "y": 250.69995880126953, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4g", + "roundness": null, + "seed": 2144374984, + "version": 314, + "versionNonce": 2080362696, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0kZdpb30DwuhVWczqWxNN", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "bXzZn0RfDeKIo_ZZ9vHOA", + "type": "rectangle", + "x": 857, + "y": 233.9999542236328, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4h", + "roundness": null, + "seed": 143356344, + "version": 537, + "versionNonce": 557350840, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "trG_aSB1_qOnsdMxFWG17", + "type": "text", + "x": 882.0800323486328, + "y": 250.69995880126953, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4i", + "roundness": null, + "seed": 2057599672, + "version": 527, + "versionNonce": 207255496, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bXzZn0RfDeKIo_ZZ9vHOA", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "dcMvFl9-jYTSN7OKHo1zk", + "type": "rectangle", + "x": 857, + "y": 343.59993743896484, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4j", + "roundness": null, + "seed": 406854856, + "version": 564, + "versionNonce": 1451215032, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "R49xUx-Q8Xi7AjZA-x7x5", + "type": "text", + "x": 882.0800323486328, + "y": 360.29994201660156, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4k", + "roundness": null, + "seed": 1387348936, + "version": 553, + "versionNonce": 684211912, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "dcMvFl9-jYTSN7OKHo1zk", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "2rrktBRftxGKG5SWNKpk6", + "type": "rectangle", + "x": 857, + "y": 453.1999206542969, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4l", + "roundness": null, + "seed": 1097044936, + "version": 558, + "versionNonce": 1442181560, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "jGETh8zSNN5_lYAlPltrY", + "type": "text", + "x": 882.0800323486328, + "y": 469.8999252319336, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4m", + "roundness": null, + "seed": 374201032, + "version": 548, + "versionNonce": 568665544, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2rrktBRftxGKG5SWNKpk6", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yRd5B5qRIYBGw5M1RVEb8", + "type": "arrow", + "x": 1145, + "y": 369.20001220703125, + "width": 54.4000244140625, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4r", + "roundness": null, + "seed": 2117963448, + "version": 185, + "versionNonce": 918485688, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 54.4000244140625, + 0 + ] + ], + "startBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.9938267854779697, + 0.5245098955857598 + ] + }, + "endBinding": { + "elementId": "J_ISCBLVR5LOB2VG1Diu_", + "mode": "inside", + "fixedPoint": [ + 0.015431858929709278, + 0.5245098955857598 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "fOWe71rlar6m5Kqkgkrma", + "type": "arrow", + "x": 965, + "y": 262.8000183105469, + "width": 57.60003662109375, + "height": 71.199951171875, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4sV", + "roundness": null, + "seed": 1963846088, + "version": 269, + "versionNonce": 1422732488, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 57.60003662109375, + 71.199951171875 + ] + ], + "startBinding": { + "elementId": "bXzZn0RfDeKIo_ZZ9vHOA", + "mode": "inside", + "fixedPoint": [ + 1, + 0.49315170499957794 + ] + }, + "endBinding": { + "elementId": "oym1FSPYzGbJi-oudw4l8", + "mode": "inside", + "fixedPoint": [ + 0.04938289047536295, + 0.09313685524608722 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "oym1FSPYzGbJi-oudw4l8", + "type": "rectangle", + "x": 1016.2000122070312, + "y": 326.4000015258789, + "width": 129.60003662109375, + "height": 81.60000610351562, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4y", + "roundness": { + "type": 3 + }, + "seed": 1228400072, + "version": 191, + "versionNonce": 192865208, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "PX7mz1B5W4A9VvNBTl382", + "type": "text", + "x": 1027.0300903320312, + "y": 342.2000045776367, + "width": 107.93988037109375, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b4z", + "roundness": null, + "seed": 607559864, + "version": 155, + "versionNonce": 454841288, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Aggregated\nresults", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oym1FSPYzGbJi-oudw4l8", + "originalText": "Aggregated\nresults", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "fTYfDZndWAse-4VQW-PAO", + "type": "rectangle", + "x": 366.4000549316406, + "y": 330.2000045776367, + "width": 144.79998779296875, + "height": 83.19998168945312, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b50", + "roundness": { + "type": 3 + }, + "seed": 556361400, + "version": 221, + "versionNonce": 1918479544, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "zZgf_bf9Sib4SS7ZUoyJr", + "type": "text", + "x": 383.9300994873047, + "y": 359.2999954223633, + "width": 109.73989868164062, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b51", + "roundness": null, + "seed": 1645277384, + "version": 195, + "versionNonce": 988715720, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Source code", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "fTYfDZndWAse-4VQW-PAO", + "originalText": "Source code", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "XQMhpfiDErmPPrP58kIeR", + "type": "rectangle", + "x": 718.6000366210938, + "y": 202.99999237060547, + "width": 40.79998779296875, + "height": 337.6000061035156, + "angle": 4.665933729156906, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b52", + "roundness": { + "type": 3 + }, + "seed": 627975096, + "version": 185, + "versionNonce": 1620466120, + "isDeleted": true, + "boundElements": [], + "updated": 1770049272120, + "link": null, + "locked": false + }, + { + "id": "KnBBV3gOcCv0w_JkVhEp7", + "type": "text", + "x": 736.3900375366211, + "y": 359.2999954223633, + "width": 5.2199859619140625, + "height": 25, + "angle": 4.665933729156906, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b53", + "roundness": null, + "seed": 1557318840, + "version": 3, + "versionNonce": 717215432, + "isDeleted": true, + "boundElements": [], + "updated": 1770049266318, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "XQMhpfiDErmPPrP58kIeR", + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "AjhpjKNHcjNstE-w5JRUR", + "type": "rectangle", + "x": 571.2000122070312, + "y": 352.60001373291016, + "width": 333.60003662109375, + "height": 38.39996337890625, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b54", + "roundness": { + "type": 3 + }, + "seed": 255320264, + "version": 312, + "versionNonce": 619926968, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "buEoQfq-MFykf-i2ZXMPZ", + "type": "text", + "x": 688.6100921630859, + "y": 359.2999954223633, + "width": 98.77987670898438, + "height": 25, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b55", + "roundness": null, + "seed": 1028564424, + "version": 291, + "versionNonce": 1457521096, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "text": "Integration", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "AjhpjKNHcjNstE-w5JRUR", + "originalText": "Integration", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "bLiA2D01gq3Z2cAzWpznu", + "type": "arrow", + "x": 1323.6001892089844, + "y": 366.53299404853783, + "width": 59.199859619140625, + "height": 1.0670120549777948, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b58", + "roundness": null, + "seed": 539099064, + "version": 107, + "versionNonce": 141549240, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 59.199859619140625, + 1.0670120549777948 + ] + ], + "startBinding": { + "elementId": "J_ISCBLVR5LOB2VG1Diu_", + "mode": "inside", + "fixedPoint": [ + 0.9737661930321042, + 0.49182585197049206 + ] + }, + "endBinding": { + "elementId": "opw3caUApWxtIMKsYNcjd", + "mode": "inside", + "fixedPoint": [ + 0.008367913003200838, + 0.49784957721669204 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false, + "moveMidPointsWithElement": false + }, + { + "id": "J_ISCBLVR5LOB2VG1Diu_", + "type": "rectangle", + "x": 1197.4000549316406, + "y": 326.4000015258789, + "width": 129.60003662109375, + "height": 81.60000610351562, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b59", + "roundness": { + "type": 3 + }, + "seed": 1414008504, + "version": 271, + "versionNonce": 1577879752, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603029, + "link": null, + "locked": false + }, + { + "id": "fKu2YRE8Qt_Jscad04g0Z", + "type": "text", + "x": 1211.510124206543, + "y": 342.2000045776367, + "width": 101.37989807128906, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5A", + "roundness": null, + "seed": 1053128632, + "version": 255, + "versionNonce": 1030965176, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603030, + "link": null, + "locked": false, + "text": "Cross-\nVerification", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "J_ISCBLVR5LOB2VG1Diu_", + "originalText": "Cross-Verification", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "opw3caUApWxtIMKsYNcjd", + "type": "rectangle", + "x": 1381.2001037597656, + "y": 321.2999954223633, + "width": 191.20001220703116, + "height": 92.99999999999997, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5B", + "roundness": { + "type": 3 + }, + "seed": 1539448264, + "version": 366, + "versionNonce": 2081435592, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603030, + "link": null, + "locked": false + }, + { + "id": "DyKZQIke44c5gOlxrI8jK", + "type": "text", + "x": 1399.9101867675781, + "y": 342.7999954223633, + "width": 153.77984619140625, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5C", + "roundness": null, + "seed": 944448712, + "version": 377, + "versionNonce": 807506104, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603030, + "link": null, + "locked": false, + "text": "Increased\nconfidence result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "opw3caUApWxtIMKsYNcjd", + "originalText": "Increased confidence result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "2p5NKLFGf3K8L8TjiuIM7", + "type": "rectangle", + "x": 618.7999572753906, + "y": 618.0000152587891, + "width": 144.79998779296875, + "height": 83.19998168945312, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5D", + "roundness": { + "type": 3 + }, + "seed": 700529080, + "version": 271, + "versionNonce": 119679688, + "isDeleted": true, + "boundElements": [], + "updated": 1770049883426, + "link": null, + "locked": false + }, + { + "id": "YrAFolq1EZi7yXL8X1_36", + "type": "text", + "x": 636.3300018310547, + "y": 647.1000061035156, + "width": 109.73989868164062, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5E", + "roundness": null, + "seed": 31539896, + "version": 247, + "versionNonce": 327206328, + "isDeleted": true, + "boundElements": [], + "updated": 1770049883426, + "link": null, + "locked": false, + "text": "Source code", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2p5NKLFGf3K8L8TjiuIM7", + "originalText": "Source code", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "P7Zx8GHhMQmAEAfk9Pu0t", + "type": "rectangle", + "x": 355.5999450683594, + "y": 717.2000274658203, + "width": 144.79998779296875, + "height": 83.19998168945312, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5F", + "roundness": { + "type": 3 + }, + "seed": 1669754312, + "version": 220, + "versionNonce": 853864888, + "isDeleted": true, + "boundElements": [], + "updated": 1770049943918, + "link": null, + "locked": false + }, + { + "id": "SN41ZTJRo2egaId41HjkB", + "type": "text", + "x": 373.12998962402344, + "y": 746.3000183105469, + "width": 109.73989868164062, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5G", + "roundness": null, + "seed": 1661301960, + "version": 196, + "versionNonce": 258352584, + "isDeleted": true, + "boundElements": [], + "updated": 1770049943918, + "link": null, + "locked": false, + "text": "Source code", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "P7Zx8GHhMQmAEAfk9Pu0t", + "originalText": "Source code", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "QDywX7-4lPIKPgAcA8vot", + "type": "rectangle", + "x": 425.9999542236328, + "y": 782.5000610351562, + "width": 144.79998779296875, + "height": 135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5H", + "roundness": null, + "seed": 796397512, + "version": 329, + "versionNonce": 1075818680, + "isDeleted": false, + "boundElements": [ + { + "id": "8dd2W2SZrSfFbtdtGAPX6", + "type": "text" + } + ], + "updated": 1770051529861, + "link": null, + "locked": false + }, + { + "id": "8dd2W2SZrSfFbtdtGAPX6", + "type": "text", + "x": 476.38397216796875, + "y": 800.0000610351562, + "width": 44.031951904296875, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5I", + "roundness": null, + "seed": 1148376776, + "version": 336, + "versionNonce": 1317322184, + "isDeleted": false, + "boundElements": [], + "updated": 1770051529861, + "link": null, + "locked": false, + "text": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "QDywX7-4lPIKPgAcA8vot", + "originalText": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "E8mxzQ2ayaglM5MugVu6a", + "type": "rectangle", + "x": 589.3598937988281, + "y": 692.4000549316406, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5IG", + "roundness": null, + "seed": 229329080, + "version": 439, + "versionNonce": 393926344, + "isDeleted": false, + "boundElements": [ + { + "id": "vTP8ywR1lxLCSt1k5CInP", + "type": "text" + } + ], + "updated": 1770051529861, + "link": null, + "locked": false + }, + { + "id": "vTP8ywR1lxLCSt1k5CInP", + "type": "text", + "x": 623.7299270629883, + "y": 721.5000610351562, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5IV", + "roundness": null, + "seed": 464613816, + "version": 420, + "versionNonce": 2042217672, + "isDeleted": false, + "boundElements": [], + "updated": 1770051529861, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "E8mxzQ2ayaglM5MugVu6a", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "MBZl0lM6kl3iXsLKGHipa", + "type": "rectangle", + "x": 589.3598937988281, + "y": 808.4000549316406, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5Il", + "roundness": null, + "seed": 1625667016, + "version": 447, + "versionNonce": 1558659512, + "isDeleted": false, + "boundElements": [ + { + "id": "pRw0ZUJW3eWQti44MBPSr", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "pRw0ZUJW3eWQti44MBPSr", + "type": "text", + "x": 623.7299270629883, + "y": 837.5000610351562, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5J", + "roundness": null, + "seed": 87801032, + "version": 429, + "versionNonce": 895162552, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "MBZl0lM6kl3iXsLKGHipa", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "znjQOsS_xEXlLwAL9tqlf", + "type": "rectangle", + "x": 589.3598937988281, + "y": 924.4000549316406, + "width": 164, + "height": 83.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5JV", + "roundness": null, + "seed": 905704632, + "version": 425, + "versionNonce": 1650209720, + "isDeleted": false, + "boundElements": [ + { + "id": "zsThweFeYsqoABLP1UjTn", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "zsThweFeYsqoABLP1UjTn", + "type": "text", + "x": 623.7299270629883, + "y": 953.5000610351562, + "width": 95.25993347167969, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "8XJ9OpA000aWM-NTJhB9N" + ], + "frameId": null, + "index": "b5K", + "roundness": null, + "seed": 1292672440, + "version": 407, + "versionNonce": 1929792184, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "SAST Tool", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "znjQOsS_xEXlLwAL9tqlf", + "originalText": "SAST Tool", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yv_CX6BMFIdpB3Yv78kQe", + "type": "rectangle", + "x": 817.9999084472656, + "y": 657.599983215332, + "width": 108, + "height": 58.400009155273445, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5P", + "roundness": null, + "seed": 743761080, + "version": 561, + "versionNonce": 639037880, + "isDeleted": true, + "boundElements": [], + "updated": 1770050013909, + "link": null, + "locked": false + }, + { + "id": "D9J72mKi_OZ94T0fBZJvw", + "type": "text", + "x": 843.0799407958984, + "y": 674.2999877929688, + "width": 57.839935302734375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5Q", + "roundness": null, + "seed": 1791462840, + "version": 551, + "versionNonce": 542385608, + "isDeleted": true, + "boundElements": [], + "updated": 1770050013909, + "link": null, + "locked": false, + "text": "Result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "yv_CX6BMFIdpB3Yv78kQe", + "originalText": "Result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "l8CX1XvHlR5ilHSdqVQc-", + "type": "rectangle", + "x": 896.39990234375, + "y": 634.5, + "width": 144.79998779296875, + "height": 135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5R", + "roundness": null, + "seed": 1553225144, + "version": 251, + "versionNonce": 367332296, + "isDeleted": true, + "boundElements": [], + "updated": 1770050024968, + "link": null, + "locked": false + }, + { + "id": "r71ycvy0iQnjYLQM84svO", + "type": "text", + "x": 941.2799224853516, + "y": 639.5, + "width": 55.039947509765625, + "height": 125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5S", + "roundness": null, + "seed": 602988216, + "version": 256, + "versionNonce": 1872262328, + "isDeleted": true, + "boundElements": [], + "updated": 1770050024968, + "link": null, + "locked": false, + "text": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "l8CX1XvHlR5ilHSdqVQc-", + "originalText": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Y8_VkDJejGLkngqe8h3By", + "type": "rectangle", + "x": 833.1998901367188, + "y": 632.1000366210938, + "width": 144.79998779296875, + "height": 135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5T", + "roundness": null, + "seed": 661306824, + "version": 296, + "versionNonce": 993009336, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "LQ0gCuEwuH9slSPU59SS2", + "type": "text", + "x": 878.0799102783203, + "y": 637.1000366210938, + "width": 55.039947509765625, + "height": 125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5U", + "roundness": null, + "seed": 1172292808, + "version": 302, + "versionNonce": 1718634696, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false, + "text": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Y8_VkDJejGLkngqe8h3By", + "originalText": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "K_LWsceP-LaBGEnNbOldE", + "type": "rectangle", + "x": 860.39990234375, + "y": 637.2000122070312, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5V", + "roundness": { + "type": 3 + }, + "seed": 1580569272, + "version": 178, + "versionNonce": 774604728, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "Wjgs1dZfA6jzZx7B4V2SA", + "type": "rectangle", + "x": 860.39990234375, + "y": 686.8000183105469, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5W", + "roundness": { + "type": 3 + }, + "seed": 1736739272, + "version": 232, + "versionNonce": 1107029960, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "7VpqMnyt8C84zUaeHhKjr", + "type": "rectangle", + "x": 833.1998901367188, + "y": 800.1000061035156, + "width": 144.79998779296875, + "height": 135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5X", + "roundness": null, + "seed": 66342600, + "version": 324, + "versionNonce": 1607585976, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "pgvq11oKnD0uN6GmQ9BK2", + "type": "text", + "x": 878.0799102783203, + "y": 805.1000061035156, + "width": 55.039947509765625, + "height": 125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5Y", + "roundness": null, + "seed": 1561277896, + "version": 330, + "versionNonce": 461027016, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false, + "text": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7VpqMnyt8C84zUaeHhKjr", + "originalText": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "brZF_xmJTyZUpqPF1R1CW", + "type": "rectangle", + "x": 860.39990234375, + "y": 805.1999816894531, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5Z", + "roundness": { + "type": 3 + }, + "seed": 1077230792, + "version": 206, + "versionNonce": 2009528760, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "hSBVStY7LBVVNgOMZ1wVA", + "type": "rectangle", + "x": 860.39990234375, + "y": 904.7999877929688, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5a", + "roundness": { + "type": 3 + }, + "seed": 2130863048, + "version": 312, + "versionNonce": 126393800, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "pLKFSgEvop--qRYtNCANa", + "type": "rectangle", + "x": 833.1998901367188, + "y": 972.9000244140625, + "width": 144.79998779296875, + "height": 135, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5b", + "roundness": null, + "seed": 33395144, + "version": 356, + "versionNonce": 154075832, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "Mv_nRtQ9fAz5hIVQw28Ao", + "type": "text", + "x": 878.0799102783203, + "y": 977.9000244140625, + "width": 55.039947509765625, + "height": 125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5c", + "roundness": null, + "seed": 1861753032, + "version": 362, + "versionNonce": 658613448, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false, + "text": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "pLKFSgEvop--qRYtNCANa", + "originalText": "Line 1\nLine 2\nLine 3\nLine 4\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "qYtoFStKObfg8C8lahXg9", + "type": "rectangle", + "x": 860.39990234375, + "y": 978, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5d", + "roundness": { + "type": 3 + }, + "seed": 151633864, + "version": 238, + "versionNonce": 581823416, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "sY62OXs8ykdt1B0SZ8H8o", + "type": "rectangle", + "x": 860.39990234375, + "y": 1052.6000061035156, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5e", + "roundness": { + "type": 3 + }, + "seed": 1615759048, + "version": 317, + "versionNonce": 406965192, + "isDeleted": true, + "boundElements": [], + "updated": 1770050420832, + "link": null, + "locked": false + }, + { + "id": "H-V8El1PpswpyhYufXDWe", + "type": "rectangle", + "x": 1184.5598266601564, + "y": 812.8501052856445, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5l", + "roundness": { + "type": 3 + }, + "seed": 2012909256, + "version": 377, + "versionNonce": 1356413640, + "isDeleted": true, + "boundElements": [], + "updated": 1770050506181, + "link": null, + "locked": false + }, + { + "id": "XHYCs1TBMsNmuskCLfxdU", + "type": "text", + "x": 1202.2398345947267, + "y": 817.8501052856445, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5m", + "roundness": null, + "seed": 1984787912, + "version": 211, + "versionNonce": 813872568, + "isDeleted": true, + "boundElements": [], + "updated": 1770050506181, + "link": null, + "locked": false, + "text": "Line 1", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "H-V8El1PpswpyhYufXDWe", + "originalText": "Line 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "e15Slm5EsyruQn0jc57Ca", + "type": "rectangle", + "x": 1114.7999267578125, + "y": 858.0000915527344, + "width": 90.39996337890625, + "height": 24.000000000000007, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 50, + "groupIds": [], + "frameId": null, + "index": "b5n", + "roundness": { + "type": 3 + }, + "seed": 225497784, + "version": 235, + "versionNonce": 387728072, + "isDeleted": true, + "boundElements": [], + "updated": 1770050232955, + "link": null, + "locked": false + }, + { + "id": "R7fko87-pK56FbrB__1tq", + "type": "rectangle", + "x": 1113.839892578125, + "y": 869.3000793457031, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5o", + "roundness": { + "type": 3 + }, + "seed": 1032939704, + "version": 265, + "versionNonce": 718201784, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false + }, + { + "id": "vtcrbU_hEeM5BQWTui0GW", + "type": "text", + "x": 1131.5199005126954, + "y": 874.3000793457031, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5p", + "roundness": null, + "seed": 701731256, + "version": 101, + "versionNonce": 1489785800, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false, + "text": "Line 3", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "R7fko87-pK56FbrB__1tq", + "originalText": "Line 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "PK4GG8sc6kP6ipbQBMn1_", + "type": "rectangle", + "x": 1196.7198730468751, + "y": 869.3000793457031, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5q", + "roundness": { + "type": 3 + }, + "seed": 1624197816, + "version": 288, + "versionNonce": 665431224, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false + }, + { + "id": "xMi3K-hW27UrCrc19FqUI", + "type": "text", + "x": 1214.3998809814454, + "y": 874.3000793457031, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5r", + "roundness": null, + "seed": 788870072, + "version": 126, + "versionNonce": 1117690568, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false, + "text": "Line 4", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PK4GG8sc6kP6ipbQBMn1_", + "originalText": "Line 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Gev7VqsVL-JoBlJr-7s2a", + "type": "rectangle", + "x": 1279.599853515625, + "y": 870.10009765625, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5s", + "roundness": { + "type": 3 + }, + "seed": 1294024392, + "version": 287, + "versionNonce": 809719224, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false + }, + { + "id": "q6b5dqQKKWQ7mdmWhVsRz", + "type": "text", + "x": 1297.2798614501953, + "y": 875.10009765625, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5t", + "roundness": null, + "seed": 1394484680, + "version": 125, + "versionNonce": 1754268104, + "isDeleted": true, + "boundElements": [], + "updated": 1770050294685, + "link": null, + "locked": false, + "text": "Line 4", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Gev7VqsVL-JoBlJr-7s2a", + "originalText": "Line 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "YGBANDCEk3D-pkUAJoUAr", + "type": "rectangle", + "x": 977.5199218749999, + "y": 883.7000732421875, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5u", + "roundness": { + "type": 3 + }, + "seed": 1794116808, + "version": 338, + "versionNonce": 1921529784, + "isDeleted": true, + "boundElements": [], + "updated": 1770050507538, + "link": null, + "locked": false + }, + { + "id": "JG7E35nyyteVuyODN6haE", + "type": "text", + "x": 995.1999298095702, + "y": 888.7000732421875, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5v", + "roundness": null, + "seed": 702755784, + "version": 174, + "versionNonce": 50479048, + "isDeleted": true, + "boundElements": [], + "updated": 1770050507538, + "link": null, + "locked": false, + "text": "Line 3", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "YGBANDCEk3D-pkUAJoUAr", + "originalText": "Line 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "GNHQzSnm4EE9DZuevsjNU", + "type": "rectangle", + "x": 1081.1998901367188, + "y": 883.7000732421875, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5w", + "roundness": { + "type": 3 + }, + "seed": 232356552, + "version": 381, + "versionNonce": 1427669176, + "isDeleted": true, + "boundElements": [], + "updated": 1770050507538, + "link": null, + "locked": false + }, + { + "id": "Eq5sw5wKZr9oBNTWQqWVx", + "type": "text", + "x": 1098.879898071289, + "y": 888.7000732421875, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5x", + "roundness": null, + "seed": 1232003528, + "version": 217, + "versionNonce": 121046728, + "isDeleted": true, + "boundElements": [], + "updated": 1770050507538, + "link": null, + "locked": false, + "text": "Line 4", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "GNHQzSnm4EE9DZuevsjNU", + "originalText": "Line 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "GSU3TwWEW5p6m_XmgO-QD", + "type": "rectangle", + "x": 1184.8798583984376, + "y": 883.7000732421875, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5y", + "roundness": { + "type": 3 + }, + "seed": 1539270856, + "version": 409, + "versionNonce": 376873416, + "isDeleted": true, + "boundElements": [], + "updated": 1770050506181, + "link": null, + "locked": false + }, + { + "id": "CvxcuyD0gwLX05oL5wheG", + "type": "text", + "x": 1202.559866333008, + "y": 888.7000732421875, + "width": 55.039947509765625, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b5z", + "roundness": null, + "seed": 6754248, + "version": 245, + "versionNonce": 722681528, + "isDeleted": true, + "boundElements": [], + "updated": 1770050506181, + "link": null, + "locked": false, + "text": "Line 5", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "GSU3TwWEW5p6m_XmgO-QD", + "originalText": "Line 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Ghhd-dpxG2M74K4_SW6fD", + "type": "rectangle", + "x": 771.9198455810547, + "y": 704.0000610351562, + "width": 108, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b5zG", + "roundness": null, + "seed": 1625182136, + "version": 671, + "versionNonce": 1086105016, + "isDeleted": false, + "boundElements": [ + { + "id": "aNTlZLfuPZ4raC1wxnRWT", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "aNTlZLfuPZ4raC1wxnRWT", + "type": "text", + "x": 803.9038696289062, + "y": 714.0000610351562, + "width": 44.031951904296875, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b5zV", + "roundness": null, + "seed": 1089181880, + "version": 762, + "versionNonce": 554499256, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1\nLine 3", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Ghhd-dpxG2M74K4_SW6fD", + "originalText": "Line 1\nLine 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "zHHbDnKQ_Oel1rcpvhmyD", + "type": "rectangle", + "x": 771.9198455810547, + "y": 820.0000610351562, + "width": 108, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b5zl", + "roundness": null, + "seed": 158542024, + "version": 687, + "versionNonce": 1395169208, + "isDeleted": false, + "boundElements": [ + { + "id": "euQnQrvEdRMMKInK2reU1", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "euQnQrvEdRMMKInK2reU1", + "type": "text", + "x": 803.9038696289062, + "y": 830.0000610351562, + "width": 44.031951904296875, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b60", + "roundness": null, + "seed": 1101189064, + "version": 780, + "versionNonce": 2123312824, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1\nLine 4", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "zHHbDnKQ_Oel1rcpvhmyD", + "originalText": "Line 1\nLine 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "SgIkwTUHe8arDbaiXA7Zg", + "type": "rectangle", + "x": 771.9198455810547, + "y": 936.0000610351562, + "width": 108, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b60V", + "roundness": null, + "seed": 717613752, + "version": 673, + "versionNonce": 750782904, + "isDeleted": false, + "boundElements": [ + { + "id": "0zcBFQyW8btpg3OEi5M3_", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "0zcBFQyW8btpg3OEi5M3_", + "type": "text", + "x": 803.9038696289062, + "y": 946.0000610351562, + "width": 44.031951904296875, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kpm99TPfZlT3fLmIocvM9" + ], + "frameId": null, + "index": "b61", + "roundness": null, + "seed": 1093129144, + "version": 768, + "versionNonce": 44884152, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1\nLine 5", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "SgIkwTUHe8arDbaiXA7Zg", + "originalText": "Line 1\nLine 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "5CUQGZhzQuGqEAi7ZKCWK", + "type": "rectangle", + "x": 898.4797973632812, + "y": 748.8000640869141, + "width": 232.8001708984374, + "height": 202.39999389648438, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b618", + "roundness": { + "type": 3 + }, + "seed": 898151864, + "version": 740, + "versionNonce": 413334456, + "isDeleted": false, + "boundElements": [ + { + "id": "jMw8YLrbPJp2TLEFwB2A1", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "jMw8YLrbPJp2TLEFwB2A1", + "type": "text", + "x": 927.5799865722656, + "y": 753.8000640869141, + "width": 174.59979248046875, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61G", + "roundness": null, + "seed": 525942456, + "version": 834, + "versionNonce": 1552290488, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Aggregated results", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "5CUQGZhzQuGqEAi7ZKCWK", + "originalText": "Aggregated results", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "baL5kYnVfbZw7ZqzYYNMU", + "type": "rectangle", + "x": 918.0798034667969, + "y": 797.2500381469727, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61O", + "roundness": { + "type": 3 + }, + "seed": 1874418872, + "version": 528, + "versionNonce": 435252664, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "eJ8X9a8_1dIkB6tA1yG__" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "eJ8X9a8_1dIkB6tA1yG__", + "type": "text", + "x": 941.2638092041016, + "y": 804.7500381469727, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61V", + "roundness": null, + "seed": 2056406728, + "version": 544, + "versionNonce": 1933231288, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "baL5kYnVfbZw7ZqzYYNMU", + "originalText": "Line 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "ybVWyIt88mGv4zbVZpnoF", + "type": "rectangle", + "x": 918.2399719238281, + "y": 844.6750450134277, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61d", + "roundness": { + "type": 3 + }, + "seed": 548132024, + "version": 562, + "versionNonce": 1808490424, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "rogzOKl3KQ3Fm2xcbfbR9" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "rogzOKl3KQ3Fm2xcbfbR9", + "type": "text", + "x": 941.4239776611328, + "y": 852.1750450134277, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61l", + "roundness": null, + "seed": 381713848, + "version": 578, + "versionNonce": 1709897400, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ybVWyIt88mGv4zbVZpnoF", + "originalText": "Line 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "aYIsva6PkGwGgj6pajky-", + "type": "rectangle", + "x": 917.4398620605468, + "y": 892.1000518798828, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b61t", + "roundness": { + "type": 3 + }, + "seed": 169018568, + "version": 594, + "versionNonce": 22073784, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "DEjXti3grBhu2xDgPxVE_" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "DEjXti3grBhu2xDgPxVE_", + "type": "text", + "x": 940.6238677978515, + "y": 899.6000518798828, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "BvVf9wzfj7aamdJVQbeS5", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b62", + "roundness": null, + "seed": 1508322248, + "version": 611, + "versionNonce": 196520120, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 4", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "aYIsva6PkGwGgj6pajky-", + "originalText": "Line 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "T19M1e2xjzkdY-KcziQaU", + "type": "rectangle", + "x": 1021.7597717285157, + "y": 797.2500381469727, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b62G", + "roundness": { + "type": 3 + }, + "seed": 1411110584, + "version": 573, + "versionNonce": 94487480, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "pITxDFaQ7w6QQBFrzVhIr" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "pITxDFaQ7w6QQBFrzVhIr", + "type": "text", + "x": 1044.9437774658204, + "y": 804.7500381469727, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b62V", + "roundness": null, + "seed": 551567288, + "version": 589, + "versionNonce": 1941822136, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "T19M1e2xjzkdY-KcziQaU", + "originalText": "Line 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "1bAZleXpQgVMLaz-_KKsV", + "type": "rectangle", + "x": 1021.9199401855469, + "y": 844.6750450134277, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b62l", + "roundness": { + "type": 3 + }, + "seed": 374867640, + "version": 603, + "versionNonce": 1541289400, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "bQx2y_p0xkwhSwwYfUxlj" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "bQx2y_p0xkwhSwwYfUxlj", + "type": "text", + "x": 1045.1039459228516, + "y": 852.1750450134277, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b63", + "roundness": null, + "seed": 739667896, + "version": 623, + "versionNonce": 1696631992, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 3", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "1bAZleXpQgVMLaz-_KKsV", + "originalText": "Line 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "mofIgbU2HmkVVVVNyB-eg", + "type": "rectangle", + "x": 1021.1198303222657, + "y": 892.1000518798828, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b63V", + "roundness": { + "type": 3 + }, + "seed": 178156232, + "version": 641, + "versionNonce": 522594232, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "1lKqNCliErvZGAY-SQ1KG" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "1lKqNCliErvZGAY-SQ1KG", + "type": "text", + "x": 1044.3038360595704, + "y": 899.6000518798828, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "XuYO1gNmeuXMC_rM4_zWN", + "eJ_exMOglkIy9D3T8ALY_", + "vU5iG6Z3fQ8rFY6VOFjHe" + ], + "frameId": null, + "index": "b64", + "roundness": null, + "seed": 1389681096, + "version": 661, + "versionNonce": 1593753272, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 5", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "mofIgbU2HmkVVVVNyB-eg", + "originalText": "Line 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "mhCXyY6UU3ZeEAaZWklhz", + "type": "rectangle", + "x": 1149.8399200439453, + "y": 741.6000518798828, + "width": 204.0000610351562, + "height": 216.80001831054682, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6F8", + "roundness": { + "type": 3 + }, + "seed": 1845150136, + "version": 457, + "versionNonce": 580491704, + "isDeleted": false, + "boundElements": [ + { + "id": "6-iaifeQXIrdFTZ2wuebZ", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "6-iaifeQXIrdFTZ2wuebZ", + "type": "text", + "x": 1171.7700271606445, + "y": 746.6000518798828, + "width": 160.1398468017578, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6FG", + "roundness": null, + "seed": 363449016, + "version": 516, + "versionNonce": 410546360, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Cross-Verification", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "mhCXyY6UU3ZeEAaZWklhz", + "originalText": "Cross-Verification", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "-oqsFVrYCTJMgAi3p4F83", + "type": "rectangle", + "x": 1172.6399688720703, + "y": 779.300048828125, + "width": 158.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6FV", + "roundness": { + "type": 3 + }, + "seed": 283728056, + "version": 752, + "versionNonce": 1901628344, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "DoxMiT5zJ-40D2eO84x6r" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "DoxMiT5zJ-40D2eO84x6r", + "type": "text", + "x": 1193.5680236816406, + "y": 786.800048828125, + "width": 116.54385375976562, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6Fd", + "roundness": null, + "seed": 749568440, + "version": 671, + "versionNonce": 11379384, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1 (Score: 3)", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "-oqsFVrYCTJMgAi3p4F83", + "originalText": "Line 1 (Score: 3)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "04I0VTVFyi7bRAHXjk-JM", + "type": "rectangle", + "x": 1172.6399688720703, + "y": 822.7667032877604, + "width": 158.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6Fl", + "roundness": { + "type": 3 + }, + "seed": 261548744, + "version": 739, + "versionNonce": 1317207480, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "IUf9baYG_kj2AYI2fePl9" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "IUf9baYG_kj2AYI2fePl9", + "type": "text", + "x": 1193.5680236816406, + "y": 830.2667032877604, + "width": 116.54385375976562, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6G", + "roundness": null, + "seed": 301879752, + "version": 662, + "versionNonce": 1052660920, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 3 (Score: 1)", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "04I0VTVFyi7bRAHXjk-JM", + "originalText": "Line 3 (Score: 1)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "sH1nBZNLyVxwvAdMwRKQe", + "type": "rectangle", + "x": 1172.6399688720703, + "y": 866.2333577473958, + "width": 158.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6GG", + "roundness": { + "type": 3 + }, + "seed": 1013281464, + "version": 759, + "versionNonce": 892923832, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "6_SujfvB5q93oigcRyOYZ" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "6_SujfvB5q93oigcRyOYZ", + "type": "text", + "x": 1193.5680236816406, + "y": 873.7333577473958, + "width": 116.54385375976562, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6GV", + "roundness": null, + "seed": 93739960, + "version": 682, + "versionNonce": 835356344, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 4 (Score: 1)", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "sH1nBZNLyVxwvAdMwRKQe", + "originalText": "Line 4 (Score: 1)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "s3MKhniSHuFp7SO6ifXGU", + "type": "rectangle", + "x": 1172.6399688720703, + "y": 909.7000122070311, + "width": 158.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6H", + "roundness": { + "type": 3 + }, + "seed": 671585992, + "version": 739, + "versionNonce": 1486512568, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "kzJLqym497hHkqdovZYYk" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "kzJLqym497hHkqdovZYYk", + "type": "text", + "x": 1193.5680236816406, + "y": 917.2000122070311, + "width": 116.54385375976562, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "RcXYBWN_1zMs66vOQR4pM", + "4Chrzduk-YF-ZSR4kbQCv" + ], + "frameId": null, + "index": "b6I", + "roundness": null, + "seed": 796491208, + "version": 664, + "versionNonce": 1374921723, + "isDeleted": false, + "boundElements": [], + "updated": 1770220380473, + "link": null, + "locked": false, + "text": "Line 5 (Score: 1)", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "s3MKhniSHuFp7SO6ifXGU", + "originalText": "Line 5 (Score: 1)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "oiE6Oyt_6jErfo8Ufa3b2", + "type": "rectangle", + "x": 807.6000061035156, + "y": 355.60008239746094, + "width": 333.60003662109375, + "height": 38.39996337890625, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6S", + "roundness": { + "type": 3 + }, + "seed": 151422408, + "version": 395, + "versionNonce": 151451336, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603030, + "link": null, + "locked": false + }, + { + "id": "MFphq0P3ln7wkYRziA3mz", + "type": "text", + "x": 925.0100860595703, + "y": 362.30006408691406, + "width": 98.77987670898438, + "height": 25, + "angle": 1.5707970534038997, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6T", + "roundness": null, + "seed": 281185480, + "version": 374, + "versionNonce": 673102264, + "isDeleted": true, + "boundElements": [], + "updated": 1770051603030, + "link": null, + "locked": false, + "text": "Integration", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "oiE6Oyt_6jErfo8Ufa3b2", + "originalText": "Integration", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "21s-X5h04IHlVC61iGT6m", + "type": "rectangle", + "x": 1372.3999328613281, + "y": 725.5000457763672, + "width": 283.2000122070313, + "height": 249.00003051757812, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6U", + "roundness": { + "type": 3 + }, + "seed": 1998357448, + "version": 480, + "versionNonce": 600265656, + "isDeleted": false, + "boundElements": [ + { + "id": "Nj2IWDAzf73EcC794jUBF", + "type": "text" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "Nj2IWDAzf73EcC794jUBF", + "type": "text", + "x": 1391.220069885254, + "y": 730.5000457763672, + "width": 245.5597381591797, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#69db7c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6V", + "roundness": null, + "seed": 124308168, + "version": 483, + "versionNonce": 1263889080, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Increased confidence result", + "fontSize": 20, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "21s-X5h04IHlVC61iGT6m", + "originalText": "Increased confidence result", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "MrOBlrtnCdxBHZed6IqBH", + "type": "rectangle", + "x": 1382.7999267578125, + "y": 787.2000503540039, + "width": 109.60003662109375, + "height": 145.60002136230472, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "bEtjyWk38JKNF8phnZl42", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6W", + "roundness": { + "type": 3 + }, + "seed": 890425288, + "version": 201, + "versionNonce": 970966456, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "-4EatyWFGJML8aSRV3CBP" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "-4EatyWFGJML8aSRV3CBP", + "type": "text", + "x": 1398.6479721069336, + "y": 792.2000503540039, + "width": 77.90394592285156, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "bEtjyWk38JKNF8phnZl42", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6X", + "roundness": null, + "seed": 1715363512, + "version": 156, + "versionNonce": 1209277624, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "High\nprobability", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "MrOBlrtnCdxBHZed6IqBH", + "originalText": "High probability", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "TAYxq-sPmAZOOKJSG5jzX", + "type": "rectangle", + "x": 1392.3999633789062, + "y": 855.5000610351562, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "-F7zmFXrHEU3f4dbFwav_", + "j3xYGi4JuH1XmCxGYS1k8", + "101rlvDG2Y9egGRXY5ZbE", + "bEtjyWk38JKNF8phnZl42", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6Y", + "roundness": { + "type": 3 + }, + "seed": 275551176, + "version": 609, + "versionNonce": 1979955128, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "hB_Oca69W-o7HpYg2e2L5" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "hB_Oca69W-o7HpYg2e2L5", + "type": "text", + "x": 1415.583969116211, + "y": 863.0000610351562, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "-F7zmFXrHEU3f4dbFwav_", + "j3xYGi4JuH1XmCxGYS1k8", + "101rlvDG2Y9egGRXY5ZbE", + "bEtjyWk38JKNF8phnZl42", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6Z", + "roundness": null, + "seed": 473357000, + "version": 599, + "versionNonce": 642666168, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 1", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "TAYxq-sPmAZOOKJSG5jzX", + "originalText": "Line 1", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "9S3rAAazQOsd8q3YiFnu5", + "type": "rectangle", + "x": 1504.3998718261719, + "y": 769.6000900268555, + "width": 142.40008544921875, + "height": 180.79994201660148, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#eaddd7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6a", + "roundness": { + "type": 3 + }, + "seed": 1166796744, + "version": 306, + "versionNonce": 1800612280, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "UX6sHLrZD6XGqekAErjWA" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "UX6sHLrZD6XGqekAErjWA", + "type": "text", + "x": 1519.0479583740234, + "y": 774.6000900268555, + "width": 113.10391235351562, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6b", + "roundness": null, + "seed": 441167560, + "version": 238, + "versionNonce": 1562640568, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Low probability", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "top", + "containerId": "9S3rAAazQOsd8q3YiFnu5", + "originalText": "Low probability", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "BcddRERi-01Ht5tlT7mt4", + "type": "rectangle", + "x": 1530.3999328613281, + "y": 804.9000549316406, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kWnQRxJ-H5O686d-SDi32", + "gZJx3u3E82pgGLjSKYBQV", + "eWJ3Gal--2gKbzw2Lm2lY", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6c", + "roundness": { + "type": 3 + }, + "seed": 655539128, + "version": 623, + "versionNonce": 1693326264, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "sT0FeNkF-rj-H0C9aWCFT" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "sT0FeNkF-rj-H0C9aWCFT", + "type": "text", + "x": 1553.5839385986328, + "y": 812.4000549316406, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "kWnQRxJ-H5O686d-SDi32", + "gZJx3u3E82pgGLjSKYBQV", + "eWJ3Gal--2gKbzw2Lm2lY", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6d", + "roundness": null, + "seed": 498238648, + "version": 615, + "versionNonce": 464859832, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 3", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BcddRERi-01Ht5tlT7mt4", + "originalText": "Line 3", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "3S9scYkpLVfBheLsFbgNX", + "type": "rectangle", + "x": 1530.3999328613281, + "y": 848.9000396728516, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "OyA75mo0LxEqOwNzcbw7h", + "9DTj2VlRBDb0IFPhdrTkz", + "FQlE_T7YvaPLRtwUZ2Yf7", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6e", + "roundness": { + "type": 3 + }, + "seed": 879732408, + "version": 600, + "versionNonce": 1710525880, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "D1RNLoErhCLYdDH17INN2" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "D1RNLoErhCLYdDH17INN2", + "type": "text", + "x": 1553.5839385986328, + "y": 856.4000396728516, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "OyA75mo0LxEqOwNzcbw7h", + "9DTj2VlRBDb0IFPhdrTkz", + "FQlE_T7YvaPLRtwUZ2Yf7", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6f", + "roundness": null, + "seed": 1715081144, + "version": 594, + "versionNonce": 1226958008, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 4", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "3S9scYkpLVfBheLsFbgNX", + "originalText": "Line 4", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Hd38eKs8WTxlrAfkKbKed", + "type": "rectangle", + "x": 1530.3999328613281, + "y": 892.9000244140625, + "width": 90.39996337890625, + "height": 35, + "angle": 0, + "strokeColor": "#f08c00", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "6nj3VxzKFLM9hwmdfDma-", + "9NJGZm3716NPKKNlHSbiI", + "cxbi2uXj3733-_pTI_NDm", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6g", + "roundness": { + "type": 3 + }, + "seed": 159630280, + "version": 616, + "versionNonce": 696680376, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "A6_hVf5Y56ngvZX0PyQZR" + } + ], + "updated": 1770051472022, + "link": null, + "locked": false + }, + { + "id": "A6_hVf5Y56ngvZX0PyQZR", + "type": "text", + "x": 1553.5839385986328, + "y": 900.4000244140625, + "width": 44.031951904296875, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffec99", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "6nj3VxzKFLM9hwmdfDma-", + "9NJGZm3716NPKKNlHSbiI", + "cxbi2uXj3733-_pTI_NDm", + "dp9yzo8f1msIPaT_RN5RS", + "Ve4W9N-pB8DuL6BoeUA-s", + "zWKUlRSiw807mwyQ_8D5O" + ], + "frameId": null, + "index": "b6h", + "roundness": null, + "seed": 1256721096, + "version": 608, + "versionNonce": 76158648, + "isDeleted": false, + "boundElements": [], + "updated": 1770051472022, + "link": null, + "locked": false, + "text": "Line 5", + "fontSize": 16, + "fontFamily": 6, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Hd38eKs8WTxlrAfkKbKed", + "originalText": "Line 5", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "8MrOebP7iBy57HWShF8dA", + "type": "arrow", + "x": 446.3999328613281, + "y": 950.800048828125, + "width": 1131.2000427246094, + "height": 139.19998168945312, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6i", + "roundness": { + "type": 2 + }, + "seed": 1829286840, + "version": 136, + "versionNonce": 2033882312, + "isDeleted": true, + "boundElements": [], + "updated": 1770051507063, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1131.2000427246094, + 139.19998168945312 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "wsNfAUtCHQTRC_u8Mippw", + "type": "arrow", + "x": 525.5902281341228, + "y": 771.5000610351562, + "width": 66.409710830721, + "height": 35.899993896484375, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6j", + "roundness": { + "type": 2 + }, + "seed": 611566776, + "version": 74, + "versionNonce": 1132466120, + "isDeleted": true, + "boundElements": [], + "updated": 1770051529861, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 66.409710830721, + -35.899993896484375 + ] + ], + "startBinding": { + "elementId": "QDywX7-4lPIKPgAcA8vot", + "mode": "orbit", + "fixedPoint": [ + 0.20302072427401271, + 0.20302072427401208 + ] + }, + "endBinding": { + "elementId": "E8mxzQ2ayaglM5MugVu6a", + "mode": "inside", + "fixedPoint": [ + 0.016097836378144055, + 0.5192308397688 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "RWwErAQAx7IQlFP8SYy0a", + "type": "arrow", + "x": 504.79994201660156, + "y": 1072.4000854492188, + "width": 1082.4000091552734, + "height": 0.00006103515625, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ff8787", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "b6k", + "roundness": { + "type": 2 + }, + "seed": 952472760, + "version": 169, + "versionNonce": 236904632, + "isDeleted": false, + "boundElements": [], + "updated": 1770051594884, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 1082.4000091552734, + -0.00006103515625 + ] + ], + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/assets/workflow_example.svg b/docs/assets/workflow_example.svg new file mode 100644 index 0000000..edc52d0 --- /dev/null +++ b/docs/assets/workflow_example.svg @@ -0,0 +1,4 @@ + + +CodeSecToolsLine 1Line 2Line 3Line 4Line 5SAST ToolSAST ToolSAST ToolLine 1Line 3Line 1Line 4Line 1Line 5Aggregated resultsLine 1Line 1Line 4Line 1Line 3Line 5Cross-VerificationLine 1 (Score: 3)Line 3 (Score: 1)Line 4 (Score: 1)Line 5 (Score: 1)Increased confidence resultHighprobabilityLine 1Low probabilityLine 3Line 4Line 5 \ No newline at end of file diff --git a/docs/dataset/index.md b/docs/dataset/index.md index 32bc8d6..4478663 100644 --- a/docs/dataset/index.md +++ b/docs/dataset/index.md @@ -36,6 +36,7 @@ classDiagram +validate(analysis_results) GitRepoDatasetData } class File { + +filepath: Path +filename: str +content: bytes +cwes: list~CWE~ diff --git a/docs/gen_pages.py b/docs/gen_pages.py index d6b29c2..67b1366 100644 --- a/docs/gen_pages.py +++ b/docs/gen_pages.py @@ -15,7 +15,7 @@ import yaml from jinja2 import Environment, FileSystemLoader -from mkdocs_gen_files import open # ty: ignore[unresolved-import] +from mkdocs_gen_files import open DOCS_DIR = Path("docs") TEMPLATE_DIR = DOCS_DIR / "templates" diff --git a/docs/sast/index.md b/docs/sast/index.md index 1a14d15..2083970 100644 --- a/docs/sast/index.md +++ b/docs/sast/index.md @@ -32,7 +32,7 @@ classDiagram +output_files: list~tuple~Path, bool~~ +parser: type~AnalysisResult~ +run_analysis(lang, project_dir, output_dir) void - +save_results(project_dir, output_dir, extra) void + +save_results(project_dir, output_dir, analysis_info) void +analyze_files(dataset, overwrite, testing) void +analyze_repos(dataset, overwrite, testing) void +list_results(project, dataset, limit) list~str~ @@ -42,11 +42,11 @@ classDiagram } class PrebuiltSAST { + +artifact_name: str + +artifact_type: str } class PrebuiltBuildlessSAST { - +artifact_name: str - +artifact_type: str } SAST <|-- BuildlessSAST @@ -61,24 +61,24 @@ classDiagram +files: list~str~ +defects: list~Defect~ +time: float - +loc: int + +lines_of_codes: int +load_from_output_dir(output_dir)* Self +load_from_output_dirs(output_dirs) list~Self~ +stats_by_checkers() dict - +stats_by_categories() dict + +stats_by_levels() dict +stats_by_files() dict +stats_by_cwes() dict } class Defect { - +sast: str + +sast_name: str +filepath: Path +filename: str +checker: str - +category: str + +level: str +cwe: CWE +message: str - +location: tuple~int, int~ + +lines: list~int~ } AnalysisResult --> Defect : contains diff --git a/pyproject.toml b/pyproject.toml index ba9c7cc..eb5fc40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "CodeSecTools" -version = "0.14.1" +version = "0.15.0" description = "A framework for code security that provides abstractions for static analysis tools and datasets to support their integration, testing, and evaluation." readme = "README.md" license = "AGPL-3.0-only" @@ -16,6 +16,7 @@ dependencies = [ "matplotlib>=3.10.3", "numpy>=2.3.1", "packaging>=25.0", + "pydantic>=2.12.5", "python-on-whales>=0.79.0", "pyyaml>=6.0.2", "requests>=2.32.4", diff --git a/requirements.txt b/requirements.txt index 4731f0a..e26bda6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,17 @@ # This file was autogenerated by uv via the following command: # uv export --frozen --output-file=requirements.txt -e . +annotated-doc==0.0.4 \ + --hash=sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320 \ + --hash=sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4 + # via typer annotated-types==0.7.0 \ --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 # via pydantic -certifi==2025.11.12 \ - --hash=sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b \ - --hash=sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316 +certifi==2026.2.25 \ + --hash=sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa \ + --hash=sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7 # via requests charset-normalizer==3.4.4 \ --hash=sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394 \ @@ -173,13 +177,13 @@ gitdb==4.0.12 \ --hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \ --hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf # via gitpython -gitpython==3.1.45 \ - --hash=sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c \ - --hash=sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77 +gitpython==3.1.46 \ + --hash=sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f \ + --hash=sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058 # via codesectools -humanize==4.14.0 \ - --hash=sha256:2fa092705ea640d605c435b1ca82b2866a1b601cdf96f076d70b79a855eba90d \ - --hash=sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff +humanize==4.15.0 \ + --hash=sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10 \ + --hash=sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769 # via codesectools idna==3.11 \ --hash=sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea \ @@ -373,138 +377,141 @@ mdurl==0.1.2 \ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba # via markdown-it-py -numpy==2.3.5 \ - --hash=sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b \ - --hash=sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae \ - --hash=sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3 \ - --hash=sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0 \ - --hash=sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b \ - --hash=sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28 \ - --hash=sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e \ - --hash=sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017 \ - --hash=sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41 \ - --hash=sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e \ - --hash=sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63 \ - --hash=sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9 \ - --hash=sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8 \ - --hash=sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139 \ - --hash=sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4 \ - --hash=sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952 \ - --hash=sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd \ - --hash=sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b \ - --hash=sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce \ - --hash=sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f \ - --hash=sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5 \ - --hash=sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42 \ - --hash=sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248 \ - --hash=sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3 \ - --hash=sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b \ - --hash=sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e \ - --hash=sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0 \ - --hash=sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa \ - --hash=sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a \ - --hash=sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d \ - --hash=sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c \ - --hash=sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52 \ - --hash=sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5 \ - --hash=sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d \ - --hash=sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1 \ - --hash=sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7 \ - --hash=sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188 \ - --hash=sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2 \ - --hash=sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903 \ - --hash=sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c \ - --hash=sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234 \ - --hash=sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82 \ - --hash=sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39 \ - --hash=sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf \ - --hash=sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20 \ - --hash=sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946 \ - --hash=sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0 \ - --hash=sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9 \ - --hash=sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff \ - --hash=sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad \ - --hash=sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227 \ - --hash=sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e \ - --hash=sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf \ - --hash=sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769 \ - --hash=sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013 \ - --hash=sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520 +numpy==2.4.2 \ + --hash=sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82 \ + --hash=sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75 \ + --hash=sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257 \ + --hash=sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71 \ + --hash=sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a \ + --hash=sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181 \ + --hash=sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85 \ + --hash=sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef \ + --hash=sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a \ + --hash=sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c \ + --hash=sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e \ + --hash=sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f \ + --hash=sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1 \ + --hash=sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b \ + --hash=sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657 \ + --hash=sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262 \ + --hash=sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a \ + --hash=sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b \ + --hash=sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae \ + --hash=sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554 \ + --hash=sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548 \ + --hash=sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05 \ + --hash=sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1 \ + --hash=sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622 \ + --hash=sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a \ + --hash=sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27 \ + --hash=sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba \ + --hash=sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443 \ + --hash=sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98 \ + --hash=sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110 \ + --hash=sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308 \ + --hash=sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f \ + --hash=sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5 \ + --hash=sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460 \ + --hash=sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef \ + --hash=sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab \ + --hash=sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909 \ + --hash=sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e \ + --hash=sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325 \ + --hash=sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979 \ + --hash=sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7 \ + --hash=sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7 \ + --hash=sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74 \ + --hash=sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499 \ + --hash=sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000 \ + --hash=sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a \ + --hash=sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913 \ + --hash=sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8 \ + --hash=sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d \ + --hash=sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f \ + --hash=sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb \ + --hash=sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa \ + --hash=sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236 \ + --hash=sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1 # via # codesectools # contourpy # matplotlib -packaging==25.0 \ - --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ - --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f +packaging==26.0 \ + --hash=sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4 \ + --hash=sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529 # via # codesectools # matplotlib -pillow==11.3.0 \ - --hash=sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2 \ - --hash=sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214 \ - --hash=sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59 \ - --hash=sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50 \ - --hash=sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632 \ - --hash=sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a \ - --hash=sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 \ - --hash=sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced \ - --hash=sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12 \ - --hash=sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 \ - --hash=sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6 \ - --hash=sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580 \ - --hash=sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd \ - --hash=sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8 \ - --hash=sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673 \ - --hash=sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788 \ - --hash=sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542 \ - --hash=sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e \ - --hash=sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8 \ - --hash=sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523 \ - --hash=sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809 \ - --hash=sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477 \ - --hash=sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027 \ - --hash=sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b \ - --hash=sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e \ - --hash=sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b \ - --hash=sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae \ - --hash=sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d \ - --hash=sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6 \ - --hash=sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024 \ - --hash=sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d \ - --hash=sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f \ - --hash=sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874 \ - --hash=sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa \ - --hash=sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149 \ - --hash=sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd \ - --hash=sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c \ - --hash=sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31 \ - --hash=sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e \ - --hash=sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db \ - --hash=sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69 \ - --hash=sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77 \ - --hash=sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7 \ - --hash=sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a \ - --hash=sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b \ - --hash=sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635 \ - --hash=sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3 \ - --hash=sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d \ - --hash=sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe \ - --hash=sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805 \ - --hash=sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36 \ - --hash=sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12 \ - --hash=sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d \ - --hash=sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c \ - --hash=sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6 \ - --hash=sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1 \ - --hash=sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653 \ - --hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c \ - --hash=sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4 +pillow==12.1.1 \ + --hash=sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9 \ + --hash=sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da \ + --hash=sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f \ + --hash=sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642 \ + --hash=sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850 \ + --hash=sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9 \ + --hash=sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8 \ + --hash=sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6 \ + --hash=sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd \ + --hash=sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c \ + --hash=sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35 \ + --hash=sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1 \ + --hash=sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af \ + --hash=sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60 \ + --hash=sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986 \ + --hash=sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13 \ + --hash=sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717 \ + --hash=sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b \ + --hash=sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15 \ + --hash=sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a \ + --hash=sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb \ + --hash=sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e \ + --hash=sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a \ + --hash=sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f \ + --hash=sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a \ + --hash=sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce \ + --hash=sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc \ + --hash=sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586 \ + --hash=sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f \ + --hash=sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8 \ + --hash=sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60 \ + --hash=sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0 \ + --hash=sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334 \ + --hash=sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524 \ + --hash=sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf \ + --hash=sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2 \ + --hash=sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7 \ + --hash=sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4 \ + --hash=sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b \ + --hash=sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397 \ + --hash=sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c \ + --hash=sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e \ + --hash=sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029 \ + --hash=sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3 \ + --hash=sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052 \ + --hash=sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984 \ + --hash=sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293 \ + --hash=sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523 \ + --hash=sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f \ + --hash=sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f \ + --hash=sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79 \ + --hash=sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8 \ + --hash=sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3 \ + --hash=sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e \ + --hash=sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36 \ + --hash=sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f \ + --hash=sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f \ + --hash=sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6 \ + --hash=sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20 \ + --hash=sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202 \ + --hash=sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0 \ + --hash=sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289 # via matplotlib pydantic==2.12.5 \ --hash=sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49 \ --hash=sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d - # via python-on-whales + # via + # codesectools + # python-on-whales pydantic-core==2.41.5 \ --hash=sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90 \ --hash=sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740 \ @@ -572,17 +579,17 @@ pygments==2.19.2 \ --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b # via rich -pyparsing==3.2.5 \ - --hash=sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6 \ - --hash=sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e +pyparsing==3.3.2 \ + --hash=sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d \ + --hash=sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc # via matplotlib python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 # via matplotlib -python-on-whales==0.79.0 \ - --hash=sha256:4a39ab107a4e740e4302c853c0efc5ced4b4048f55acbb959a2faf53b7003c27 \ - --hash=sha256:919bba304cc04db4b75cdd7fb14c0a8fea6ebeafacf7989f6956641cb58210e0 +python-on-whales==0.80.0 \ + --hash=sha256:0f18dda93a621dc34c5c26d0dbdc20003dca07a02114c4ce60910c4afb12c1ac \ + --hash=sha256:3a7207899e5189695e54859a4c49461306c67fe8545a7eb53f884e342550c37d # via codesectools pyyaml==6.0.3 \ --hash=sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c \ @@ -629,9 +636,9 @@ requests==2.32.5 \ --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf # via codesectools -rich==14.2.0 \ - --hash=sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4 \ - --hash=sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd +rich==14.3.3 \ + --hash=sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d \ + --hash=sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b # via typer shellingham==1.5.4 \ --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ @@ -645,13 +652,13 @@ smmap==5.0.2 \ --hash=sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5 \ --hash=sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e # via gitdb -tqdm==4.67.1 \ - --hash=sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2 \ - --hash=sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2 +tqdm==4.67.3 \ + --hash=sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb \ + --hash=sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf # via codesectools -typer==0.20.0 \ - --hash=sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37 \ - --hash=sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a +typer==0.24.1 \ + --hash=sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e \ + --hash=sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45 # via codesectools typing-extensions==4.15.0 \ --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ @@ -660,7 +667,6 @@ typing-extensions==4.15.0 \ # pydantic # pydantic-core # python-on-whales - # typer # typing-inspection typing-inspection==0.4.2 \ --hash=sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7 \ @@ -672,7 +678,7 @@ urllib3==2.6.3 \ # via # codesectools # requests -xmltodict==1.0.2 \ - --hash=sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649 \ - --hash=sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d +xmltodict==1.0.4 \ + --hash=sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61 \ + --hash=sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a # via codesectools diff --git a/tests/test_all_sasts.py b/tests/test_all_sasts.py index a816188..0db0155 100644 --- a/tests/test_all_sasts.py +++ b/tests/test_all_sasts.py @@ -43,12 +43,10 @@ def test_included() -> None: def test_analyze(monkeypatch: pytest.MonkeyPatch) -> None: """Test the 'allsast analyze' command.""" logging.info("Testing All SAST analyze command on Java code") # Support Java only - git.Repo.clone_from("https://github.com/ScaleSec/vulnado.git", "/tmp/vulnado") - monkeypatch.chdir("/tmp/vulnado") + git.Repo.clone_from("https://github.com/appsecco/dvja.git", "/tmp/dvja") + monkeypatch.chdir("/tmp/dvja") - retcode, stdout = run_command( - "./mvnw clean compile".split(" "), cwd=Path("/tmp/vulnado") - ) + retcode, stdout = run_command("mvn clean compile".split(" "), cwd=Path("/tmp/dvja")) assert retcode == 0 result = runner.invoke( @@ -62,21 +60,21 @@ def test_list() -> None: logging.info("Testing All SAST list command on Java code") result = runner.invoke(build_cli(), ["list"]) assert result.exit_code == 0 - assert "vulnado" in result.output + assert "dvja" in result.output def test_plot() -> None: """Test the 'allsast plot' command.""" logging.info("Testing All SAST plot command on Java code") - result = runner.invoke(build_cli(), ["plot", "vulnado"]) + result = runner.invoke(build_cli(), ["plot", "dvja"]) assert result.exit_code == 0 - assert (all_sast.output_dir / "vulnado" / "_figures").is_dir() + assert (all_sast.output_dir / "dvja" / "_figures").is_dir() def test_report() -> None: """Test the 'allsast report' command.""" logging.info("Testing All SAST report command on Java code") - result = runner.invoke(build_cli(), ["report", "vulnado"]) + result = runner.invoke(build_cli(), ["report", "dvja"]) assert result.exit_code == 0 - assert (all_sast.output_dir / "vulnado" / "report").is_dir() - assert list((all_sast.output_dir / "vulnado" / "report").glob("*.html")) + assert (all_sast.output_dir / "dvja" / "report").is_dir() + assert list((all_sast.output_dir / "dvja" / "report").glob("*.html")) diff --git a/uv.lock b/uv.lock index 4431326..fcae0e5 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 3 requires-python = ">=3.12" +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -13,25 +22,25 @@ wheels = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] name = "backrefs" -version = "6.1" +version = "6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, ] [[package]] @@ -86,11 +95,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -230,7 +239,7 @@ wheels = [ [[package]] name = "codesectools" -version = "0.14.1" +version = "0.15.0" source = { editable = "." } dependencies = [ { name = "gitpython" }, @@ -239,6 +248,7 @@ dependencies = [ { name = "matplotlib" }, { name = "numpy" }, { name = "packaging" }, + { name = "pydantic" }, { name = "python-on-whales" }, { name = "pyyaml" }, { name = "requests" }, @@ -295,6 +305,7 @@ requires-dist = [ { name = "numpy", specifier = ">=2.3.1" }, { name = "packaging", specifier = ">=25.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.3.0" }, + { name = "pydantic", specifier = ">=2.12.5" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.4.1" }, { name = "pytest-order", marker = "extra == 'test'", specifier = ">=1.3.0" }, { name = "python-on-whales", specifier = ">=0.79.0" }, @@ -391,24 +402,24 @@ wheels = [ [[package]] name = "cssselect" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/0a/c3ea9573b1dc2e151abfe88c7fe0c26d1892fe6ed02d0cdb30f0d57029d5/cssselect-1.3.0.tar.gz", hash = "sha256:57f8a99424cfab289a1b6a816a43075a4b00948c86b4dcf3ef4ee7e15f7ab0c7", size = 42870, upload-time = "2025-03-10T09:30:29.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/2e/cdfd8b01c37cbf4f9482eefd455853a3cf9c995029a46acd31dfaa9c1dd6/cssselect-1.4.0.tar.gz", hash = "sha256:fdaf0a1425e17dfe8c5cf66191d211b357cf7872ae8afc4c6762ddd8ac47fc92", size = 40589, upload-time = "2026-01-29T07:00:26.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/58/257350f7db99b4ae12b614a36256d9cc870d71d9e451e79c2dc3b23d7c3c/cssselect-1.3.0-py3-none-any.whl", hash = "sha256:56d1bf3e198080cc1667e137bc51de9cadfca259f03c2d4e09037b3e01e30f0d", size = 18786, upload-time = "2025-03-10T09:30:28.048Z" }, + { url = "https://files.pythonhosted.org/packages/20/0c/7bb51e3acfafd16c48875bf3db03607674df16f5b6ef8d056586af7e2b8b/cssselect-1.4.0-py3-none-any.whl", hash = "sha256:c0ec5c0191c8ee39fcc8afc1540331d8b55b0183478c50e9c8a79d44dbceb1d8", size = 18540, upload-time = "2026-01-29T07:00:24.994Z" }, ] [[package]] name = "cssselect2" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tinycss2" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/20/92eaa6b0aec7189fa4b75c890640e076e9e793095721db69c5c81142c2e1/cssselect2-0.9.0.tar.gz", hash = "sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb", size = 35595, upload-time = "2026-02-12T17:16:39.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" }, + { url = "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563", size = 15453, upload-time = "2026-02-12T17:16:38.317Z" }, ] [[package]] @@ -449,11 +460,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.1" +version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] @@ -523,44 +534,40 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.45" +version = "3.1.46" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, ] [[package]] -name = "griffe" -version = "1.15.0" +name = "griffelib" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, ] [[package]] name = "humanize" -version = "4.14.0" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/43/50033d25ad96a7f3845f40999b4778f753c3901a11808a584fed7c00d9f5/humanize-4.14.0.tar.gz", hash = "sha256:2fa092705ea640d605c435b1ca82b2866a1b601cdf96f076d70b79a855eba90d", size = 82939, upload-time = "2025-10-15T13:04:51.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/5b/9512c5fb6c8218332b530f13500c6ff5f3ce3342f35e0dd7be9ac3856fd3/humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff", size = 132092, upload-time = "2025-10-15T13:04:49.404Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, ] [[package]] name = "identify" -version = "2.6.15" +version = "2.6.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, ] [[package]] @@ -760,11 +767,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -940,16 +947,16 @@ wheels = [ [[package]] name = "mkdocs-autorefs" -version = "1.4.3" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, ] [[package]] @@ -1005,15 +1012,15 @@ wheels = [ [[package]] name = "mkdocs-include-markdown-plugin" -version = "7.2.0" +version = "7.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mkdocs" }, { name = "wcmatch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/10/b0b75ac42f4613556a808eee2dad3efe7a7d5079349aa5b9229d863e829f/mkdocs_include_markdown_plugin-7.2.0.tar.gz", hash = "sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3", size = 25509, upload-time = "2025-09-28T21:50:50.41Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/03/cd5e4383e677a3192127c4da67cb6046a8b1ae32ef6201f4faffd4b0c7a5/mkdocs_include_markdown_plugin-7.2.1.tar.gz", hash = "sha256:5d94db87b06cd303619dbaebba5f7f43a3ded7fd7709451d26f08c176376ffec", size = 25395, upload-time = "2026-01-25T15:02:27.861Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/f9/783338d1d7fd548c7635728b67a0f8f96d9e6c265aa61c51356c03597767/mkdocs_include_markdown_plugin-7.2.0-py3-none-any.whl", hash = "sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b", size = 29548, upload-time = "2025-09-28T21:50:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0f/73a1d330183e79b21ee1b1a5dd4102fad1bd70231cf3b0620a7391b3c813/mkdocs_include_markdown_plugin-7.2.1-py3-none-any.whl", hash = "sha256:30da634c568ea5d5f9e5881d51f80ac30d8c5f891cec160344ad7a0fdaea6286", size = 29512, upload-time = "2026-01-25T15:02:26.333Z" }, ] [[package]] @@ -1030,7 +1037,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.7.0" +version = "9.7.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -1045,9 +1052,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/b4/f900fcb8e6f510241e334ca401eddcb61ed880fb6572f7f32e4228472ca1/mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22", size = 4097748, upload-time = "2026-02-24T12:06:22.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/16ad0193079bb8a15aa1d2620813a9cd15b18de150a4ea1b2c607fb4c74d/mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d", size = 9305078, upload-time = "2026-02-24T12:06:19.155Z" }, ] [package.optional-dependencies] @@ -1096,7 +1103,7 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "1.0.0" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -1106,9 +1113,9 @@ dependencies = [ { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" }, + { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, ] [package.optional-dependencies] @@ -1118,97 +1125,95 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "2.0.1" +version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe" }, + { name = "griffelib" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] name = "numpy" -version = "2.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, ] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1222,86 +1227,89 @@ wheels = [ [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, ] [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1331,11 +1339,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -1435,24 +1443,24 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "10.19.1" +version = "10.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/2d/9f30cee56d4d6d222430d401e85b0a6a1ae229819362f5786943d1a8c03b/pymdown_extensions-10.19.1.tar.gz", hash = "sha256:4969c691009a389fb1f9712dd8e7bd70dcc418d15a0faf70acb5117d022f7de8", size = 847839, upload-time = "2025-12-14T17:25:24.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/35/b763e8fbcd51968329b9adc52d188fc97859f85f2ee15fe9f379987d99c5/pymdown_extensions-10.19.1-py3-none-any.whl", hash = "sha256:e8698a66055b1dc0dca2a7f2c9d0ea6f5faa7834a9c432e3535ab96c0c4e509b", size = 266693, upload-time = "2025-12-14T17:25:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, ] [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -1495,17 +1503,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + [[package]] name = "python-on-whales" -version = "0.79.0" +version = "0.80.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/f5/3a283762a9528dea6c666d28ae4c47cc14ad4ce9994ee976852dd0cb166f/python_on_whales-0.79.0.tar.gz", hash = "sha256:919bba304cc04db4b75cdd7fb14c0a8fea6ebeafacf7989f6956641cb58210e0", size = 114675, upload-time = "2025-10-24T09:07:02.81Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/8e/0f76b8ccd44d7acaeb2ef15109b039a00cee5df9935249c1aa704e749f4a/python_on_whales-0.80.0.tar.gz", hash = "sha256:0f18dda93a621dc34c5c26d0dbdc20003dca07a02114c4ce60910c4afb12c1ac", size = 114918, upload-time = "2026-01-10T18:00:56.207Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/f5/b8cf36274244c12fa1535a62fafdc72c89e8fe965ef1477a9279c09aa2f6/python_on_whales-0.79.0-py3-none-any.whl", hash = "sha256:4a39ab107a4e740e4302c853c0efc5ced4b4048f55acbb959a2faf53b7003c27", size = 118821, upload-time = "2025-10-24T09:07:01.668Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d9/7dc70cfd792b420ccbdb249be4a43e949b5c151120b7abfdcacd93e37b2b/python_on_whales-0.80.0-py3-none-any.whl", hash = "sha256:3a7207899e5189695e54859a4c49461306c67fe8545a7eb53f884e342550c37d", size = 119194, upload-time = "2026-01-10T18:00:54.776Z" }, ] [[package]] @@ -1583,50 +1604,49 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] [[package]] name = "ruff" -version = "0.14.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" }, - { url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" }, - { url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" }, - { url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" }, - { url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" }, - { url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" }, - { url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" }, - { url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" }, - { url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" }, - { url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" }, - { url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" }, - { url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" }, - { url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" }, - { url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" }, - { url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" }, - { url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" }, +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] name = "setuptools" -version = "80.9.0" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[package]] @@ -1658,11 +1678,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8" +version = "2.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, ] [[package]] @@ -1691,14 +1711,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] @@ -1712,42 +1732,41 @@ wheels = [ [[package]] name = "ty" -version = "0.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/e5/15b6aceefcd64b53997fe2002b6fa055f0b1afd23ff6fc3f55f3da944530/ty-0.0.2.tar.gz", hash = "sha256:e02dc50b65dc58d6cb8e8b0d563833f81bf03ed8a7d0b15c6396d486489a7e1d", size = 4762024, upload-time = "2025-12-16T20:13:41.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/86/65d4826677d966cf226662767a4a597ebb4b02c432f413673c8d5d3d1ce8/ty-0.0.2-py3-none-linux_armv6l.whl", hash = "sha256:0954a0e0b6f7e06229dd1da3a9989ee9b881a26047139a88eb7c134c585ad22e", size = 9771409, upload-time = "2025-12-16T20:13:28.964Z" }, - { url = "https://files.pythonhosted.org/packages/d4/bc/6ab06b7c109cec608c24ea182cc8b4714e746a132f70149b759817092665/ty-0.0.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d6044b491d66933547033cecc87cb7eb599ba026a3ef347285add6b21107a648", size = 9580025, upload-time = "2025-12-16T20:13:34.507Z" }, - { url = "https://files.pythonhosted.org/packages/54/de/d826804e304b2430f17bb27ae15bcf02380e7f67f38b5033047e3d2523e6/ty-0.0.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbca7f08e671a35229f6f400d73da92e2dc0a440fba53a74fe8233079a504358", size = 9098660, upload-time = "2025-12-16T20:13:01.278Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8e/5cd87944ceee02bb0826f19ced54e30c6bb971e985a22768f6be6b1a042f/ty-0.0.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3abd61153dac0b93b284d305e6f96085013a25c3a7ab44e988d24f0a5fcce729", size = 9567693, upload-time = "2025-12-16T20:13:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b1/062aab2c62c5ae01c05d27b97ba022d9ff66f14a3cb9030c5ad1dca797ec/ty-0.0.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21a9f28caafb5742e7d594104e2fe2ebd64590da31aed4745ae8bc5be67a7b85", size = 9556471, upload-time = "2025-12-16T20:13:07.771Z" }, - { url = "https://files.pythonhosted.org/packages/0e/07/856f6647a9dd6e36560d182d35d3b5fb21eae98a8bfb516cd879d0e509f3/ty-0.0.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3ec63fd23ab48e0f838fb54a47ec362a972ee80979169a7edfa6f5c5034849d", size = 9971914, upload-time = "2025-12-16T20:13:18.852Z" }, - { url = "https://files.pythonhosted.org/packages/2e/82/c2e3957dbf33a23f793a9239cfd8bd04b6defd999bd0f6e74d6a5afb9f42/ty-0.0.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e5e2e0293a259c9a53f668c9c13153cc2f1403cb0fe2b886ca054be4ac76517c", size = 10840905, upload-time = "2025-12-16T20:13:37.098Z" }, - { url = "https://files.pythonhosted.org/packages/3b/17/49bd74e3d577e6c88b8074581b7382f532a9d40552cc7c48ceaa83f1d950/ty-0.0.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2511ac02a83d0dc45d4570c7e21ec0c919be7a7263bad9914800d0cde47817", size = 10570251, upload-time = "2025-12-16T20:13:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9b/26741834069722033a1a0963fcbb63ea45925c6697357e64e361753c6166/ty-0.0.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c482bfbfb8ad18b2e62427d02a0c934ac510c414188a3cf00e16b8acc35482f0", size = 10369078, upload-time = "2025-12-16T20:13:20.851Z" }, - { url = "https://files.pythonhosted.org/packages/94/fc/1d34ec891900d9337169ff9f8252fcaa633ae5c4d36b67effd849ed4f9ac/ty-0.0.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb514711eed3f56d7a130d4885f4b5d8e490fdcd2adac098e5cf175573a0dda3", size = 10121064, upload-time = "2025-12-16T20:13:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/e5/02/e640325956172355ef8deb9b08d991f229230bf9d07f1dbda8c6665a3a43/ty-0.0.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2c37fa26c39e9fbed7c73645ba721968ab44f28b2bfe2f79a4e15965a1c426f", size = 9553817, upload-time = "2025-12-16T20:13:27.057Z" }, - { url = "https://files.pythonhosted.org/packages/35/13/c93d579ece84895da9b0aae5d34d84100bbff63ad9f60c906a533a087175/ty-0.0.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:13b264833ac5f3b214693fca38e380e78ee7327e09beaa5ff2e47d75fcab9692", size = 9577512, upload-time = "2025-12-16T20:13:16.956Z" }, - { url = "https://files.pythonhosted.org/packages/85/53/93ab1570adc799cd9120ea187d5b4c00d821e86eca069943b179fe0d3e83/ty-0.0.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:08658d6dbbf8bdef80c0a77eda56a22ab6737002ba129301b7bbd36bcb7acd75", size = 9692726, upload-time = "2025-12-16T20:13:31.169Z" }, - { url = "https://files.pythonhosted.org/packages/9a/07/5fff5335858a14196776207d231c32e23e48a5c912a7d52c80e7a3fa6f8f/ty-0.0.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4a21b5b012061cb13d47edfff6be70052694308dba633b4c819b70f840e6c158", size = 10213996, upload-time = "2025-12-16T20:13:14.606Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d3/896b1439ab765c57a8d732f73c105ec41142c417a582600638385c2bee85/ty-0.0.2-py3-none-win32.whl", hash = "sha256:d773fdad5d2b30f26313204e6b191cdd2f41ab440a6c241fdb444f8c6593c288", size = 9204906, upload-time = "2025-12-16T20:13:25.099Z" }, - { url = "https://files.pythonhosted.org/packages/5d/0a/f30981e7d637f78e3d08e77d63b818752d23db1bc4b66f9e82e2cb3d34f8/ty-0.0.2-py3-none-win_amd64.whl", hash = "sha256:d1c9ac78a8aa60d0ce89acdccf56c3cc0fcb2de07f1ecf313754d83518e8e8c5", size = 10066640, upload-time = "2025-12-16T20:13:04.045Z" }, - { url = "https://files.pythonhosted.org/packages/5a/c4/97958503cf62bfb7908d2a77b03b91a20499a7ff405f5a098c4989589f34/ty-0.0.2-py3-none-win_arm64.whl", hash = "sha256:fbdef644ade0cd4420c4ec14b604b7894cefe77bfd8659686ac2f6aba9d1a306", size = 9572022, upload-time = "2025-12-16T20:13:39.189Z" }, +version = "0.0.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, + { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, + { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, + { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, + { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, + { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, ] [[package]] name = "typer" -version = "0.20.0" +version = "0.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "click" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, ] [[package]] @@ -1764,7 +1783,7 @@ wheels = [ [[package]] name = "types-lxml" -version = "2025.11.25" +version = "2026.2.16" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -1772,9 +1791,9 @@ dependencies = [ { name = "types-html5lib" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/30/63ad18e633f61d848444fe4f1b95fcfcf80283bc5578f308501ef7210437/types_lxml-2025.11.25.tar.gz", hash = "sha256:30e7ff9a2d8d7c2ea7967c5d71c8169dd1806b58503ed4972af1ce602919b0e4", size = 156623, upload-time = "2025-11-25T20:41:24.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/ad/c70ac8cbdc28eb58a17301c69b4925af54b614e47f9b2ebc9de5cc10f786/types_lxml-2026.2.16.tar.gz", hash = "sha256:b3a1340cc06db98d541c785732f6f68bea438daff4e2b7809ef748d545d01406", size = 161204, upload-time = "2026-02-17T02:34:50.855Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/83/ab792ad6d91cb25eca5547f1f9e593994310ad5bc19c2baf9e85cd4d506c/types_lxml-2025.11.25-py3-none-any.whl", hash = "sha256:c9e56aad336076f245e38d7f9b6bd6cdd9ffadda64ce650b9d3aa3a9ae4a8890", size = 96781, upload-time = "2025-11-25T20:41:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5c/03ec9befbf4bb5309bfd576c6a5ac1c75633f78f6b64cf1f594e97cd3d23/types_lxml-2026.2.16-py3-none-any.whl", hash = "sha256:5dd81ffa54830e5f361988737c5f1d6a0ae48b2742790637ec560df790ea0401", size = 97040, upload-time = "2026-02-17T02:34:49.286Z" }, ] [[package]] @@ -1818,16 +1837,17 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.35.4" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/28/e6f1a6f655d620846bd9df527390ecc26b3805a0c5989048c210e22c5ca9/virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c", size = 6028799, upload-time = "2025-10-29T06:57:40.511Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b", size = 6005095, upload-time = "2025-10-29T06:57:37.598Z" }, + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] [[package]] @@ -1877,9 +1897,9 @@ wheels = [ [[package]] name = "xmltodict" -version = "1.0.2" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/aa/917ceeed4dbb80d2f04dbd0c784b7ee7bba8ae5a54837ef0e5e062cd3cfb/xmltodict-1.0.2.tar.gz", hash = "sha256:54306780b7c2175a3967cad1db92f218207e5bc1aba697d887807c0fb68b7649", size = 25725, upload-time = "2025-09-17T21:59:26.459Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/20/69a0e6058bc5ea74892d089d64dfc3a62ba78917ec5e2cfa70f7c92ba3a5/xmltodict-1.0.2-py3-none-any.whl", hash = "sha256:62d0fddb0dcbc9f642745d8bbf4d81fd17d6dfaec5a15b5c1876300aad92af0d", size = 13893, upload-time = "2025-09-17T21:59:24.859Z" }, + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, ]