technical-debt-engine-runtime 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tde_cli/__init__.py +5 -0
- tde_cli/main.py +400 -0
- tde_runtime/__init__.py +6 -0
- tde_runtime/baseline.py +196 -0
- tde_runtime/code_size.py +49 -0
- tde_runtime/complexity.py +92 -0
- tde_runtime/configuration.py +112 -0
- tde_runtime/dependency_health.py +23 -0
- tde_runtime/docker_artifact.py +47 -0
- tde_runtime/evidence_store.py +69 -0
- tde_runtime/execution.py +213 -0
- tde_runtime/maintainability.py +11 -0
- tde_runtime/models.py +70 -0
- tde_runtime/policies/__init__.py +1 -0
- tde_runtime/policies/generation-1.json +18 -0
- tde_runtime/policy.py +202 -0
- tde_runtime/query.py +36 -0
- tde_runtime/registries.py +21 -0
- tde_runtime/release_bundle.py +34 -0
- tde_runtime/release_candidate.py +75 -0
- tde_runtime/release_certification.py +87 -0
- tde_runtime/release_publication.py +114 -0
- tde_runtime/release_qualification.py +115 -0
- tde_runtime/runtime.py +193 -0
- tde_runtime/runtime_qualification.py +63 -0
- tde_runtime/software_assurance.py +183 -0
- tde_runtime/trend.py +73 -0
- tde_runtime/trusted_delivery.py +137 -0
- technical_debt_engine_runtime-0.1.0.dist-info/METADATA +5 -0
- technical_debt_engine_runtime-0.1.0.dist-info/RECORD +33 -0
- technical_debt_engine_runtime-0.1.0.dist-info/WHEEL +5 -0
- technical_debt_engine_runtime-0.1.0.dist-info/entry_points.txt +2 -0
- technical_debt_engine_runtime-0.1.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Canonical, fail-closed operational Software Assurance evidence."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Any, Iterable
|
|
11
|
+
|
|
12
|
+
from .configuration import RuntimeConfiguration
|
|
13
|
+
|
|
14
|
+
ASSURANCE_SCHEMA_VERSION = "1.0.0"
|
|
15
|
+
_USES_KEY = re.compile(r"^\s*(?:-\s+)?uses:\s*(?P<reference>.*)$")
|
|
16
|
+
_ACTION_REFERENCE = re.compile(
|
|
17
|
+
r"^(?P<owner>[A-Za-z0-9][A-Za-z0-9-]*)/"
|
|
18
|
+
r"(?P<repository>[A-Za-z0-9_.-]+)"
|
|
19
|
+
r"(?P<path>(?:/[A-Za-z0-9_.-]+)*)@(?P<revision>[^\s#]+)$"
|
|
20
|
+
)
|
|
21
|
+
_COMMIT_SHA = re.compile(r"^[0-9a-fA-F]{40}$")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _canonical(value: object) -> bytes:
|
|
25
|
+
return (json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + "\n").encode("utf-8")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _digest(path: Path) -> str:
|
|
29
|
+
hasher = sha256()
|
|
30
|
+
with path.open("rb") as stream:
|
|
31
|
+
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
|
32
|
+
hasher.update(block)
|
|
33
|
+
return "sha256:" + hasher.hexdigest()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _git(root: Path, *arguments: str) -> tuple[bool, str]:
|
|
37
|
+
completed = subprocess.run(["git", "-C", str(root), *arguments], text=True, capture_output=True, check=False)
|
|
38
|
+
return completed.returncode == 0, completed.stdout.strip()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_action_reference(reference: str) -> dict[str, Any]:
|
|
42
|
+
"""Normalize a GitHub Action reference and classify its immutability.
|
|
43
|
+
|
|
44
|
+
GitHub accepts ``owner/repository[/path]@revision`` for both step-level
|
|
45
|
+
actions and job-level reusable workflows. Only a complete commit SHA is
|
|
46
|
+
immutable; malformed, local, expression, and mutable references remain
|
|
47
|
+
explicitly untrusted.
|
|
48
|
+
"""
|
|
49
|
+
reference = reference.split("#", 1)[0].strip()
|
|
50
|
+
match = _ACTION_REFERENCE.fullmatch(reference)
|
|
51
|
+
if not match:
|
|
52
|
+
return {"owner": None, "repository": None, "path": None, "commitSha": None, "immutable": False}
|
|
53
|
+
values = match.groupdict()
|
|
54
|
+
revision = values["revision"]
|
|
55
|
+
immutable = bool(_COMMIT_SHA.fullmatch(revision))
|
|
56
|
+
return {
|
|
57
|
+
"owner": values["owner"].lower(),
|
|
58
|
+
"repository": values["repository"].lower(),
|
|
59
|
+
"path": values["path"] or None,
|
|
60
|
+
"commitSha": revision.lower() if immutable else None,
|
|
61
|
+
"immutable": immutable,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SoftwareAssurance:
|
|
66
|
+
"""Evaluate repository and candidate artifacts without delivery or release behavior."""
|
|
67
|
+
|
|
68
|
+
def assure(self, root: str | Path, artifact_directories: Iterable[str | Path] = (), docker_artifact_directory: str | Path | None = None) -> dict[str, Any]:
|
|
69
|
+
root = Path(root).resolve(); limitations: list[str] = []
|
|
70
|
+
repository = self._repository(root, limitations); dependencies = self._dependencies(root, limitations)
|
|
71
|
+
workflows = self._workflows(root, limitations); configuration = self._configuration(root, limitations)
|
|
72
|
+
documentation = self._documentation(root, limitations)
|
|
73
|
+
artifacts = self._artifacts(tuple(Path(item).resolve() for item in artifact_directories), limitations)
|
|
74
|
+
if docker_artifact_directory is not None:
|
|
75
|
+
from .docker_artifact import validate as validate_docker
|
|
76
|
+
candidate = repository["candidateIdentity"].removeprefix("candidate.git.") if repository.get("candidateIdentity") else ""
|
|
77
|
+
docker = validate_docker(docker_artifact_directory, candidate)
|
|
78
|
+
artifacts["docker"] = docker
|
|
79
|
+
artifacts["records"].append({"directory": str(Path(docker_artifact_directory).resolve()), "artifactCount": 1,
|
|
80
|
+
"verified": docker["verified"], "artifacts": [{key: docker[key] for key in ("filename", "digest")} ]})
|
|
81
|
+
artifacts["integrity"] = artifacts["integrity"] and docker["verified"]
|
|
82
|
+
if not docker["verified"]:
|
|
83
|
+
limitations.append("candidate Docker archive or provenance is invalid")
|
|
84
|
+
checks = {"repositoryIntegrity": repository["integrity"], "dependencyIntegrity": dependencies["integrity"],
|
|
85
|
+
"artifactIntegrity": artifacts["integrity"], "workflowIntegrity": workflows["integrity"],
|
|
86
|
+
"configurationIntegrity": configuration["integrity"], "documentationIntegrity": documentation["integrity"],
|
|
87
|
+
"buildProvenanceVerification": artifacts["provenanceVerified"]}
|
|
88
|
+
required = ("repositoryIntegrity", "dependencyIntegrity", "workflowIntegrity", "configurationIntegrity", "documentationIntegrity")
|
|
89
|
+
failed = not all(checks[name] for name in required) or (bool(artifacts["directories"]) and not checks["artifactIntegrity"])
|
|
90
|
+
decision = "FAIL" if failed else "PASS_WITH_WARNINGS" if limitations else "PASS"
|
|
91
|
+
evidence = {"schemaId": "tde.software-assurance", "schemaVersion": ASSURANCE_SCHEMA_VERSION,
|
|
92
|
+
"repository": repository, "dependencies": dependencies, "artifacts": artifacts, "workflows": workflows,
|
|
93
|
+
"configuration": configuration, "documentation": documentation, "checks": checks,
|
|
94
|
+
"limitations": limitations, "decision": decision}
|
|
95
|
+
evidence["assuranceId"] = "assurance.sha256." + sha256(_canonical(evidence)).hexdigest()
|
|
96
|
+
evidence["qualification"] = decision
|
|
97
|
+
return evidence
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _repository(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
101
|
+
valid, candidate = _git(root, "rev-parse", "HEAD"); _, branch = _git(root, "branch", "--show-current")
|
|
102
|
+
branch = branch or os.environ.get("TDE_CANDIDATE_SOURCE_BRANCH", "")
|
|
103
|
+
_, remote = _git(root, "config", "--get", "remote.origin.url"); status_ok, dirty = _git(root, "status", "--porcelain")
|
|
104
|
+
integrity = valid and status_ok and not dirty and bool(branch)
|
|
105
|
+
if not valid: limitations.append("repository identity is unavailable")
|
|
106
|
+
if not branch: limitations.append("candidate is detached from a branch")
|
|
107
|
+
if dirty: limitations.append("working tree is not clean")
|
|
108
|
+
return {"identity": remote or "local", "candidateIdentity": "candidate.git." + candidate if candidate else None,
|
|
109
|
+
"branch": branch or None, "workingTreeClean": not bool(dirty), "integrity": integrity}
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _dependencies(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
113
|
+
project = root / "pyproject.toml"; build_tools = root / "requirements" / "build-tools.txt"; pinned = hashes = False
|
|
114
|
+
if project.is_file():
|
|
115
|
+
match = re.search(r"requires\s*=\s*\[([^]]*)\]", project.read_text(encoding="utf-8"), re.DOTALL)
|
|
116
|
+
pinned = bool(match and re.findall(r'"[^"\n]+==[^"\n]+"', match.group(1)))
|
|
117
|
+
if build_tools.is_file():
|
|
118
|
+
content = build_tools.read_text(encoding="utf-8"); hashes = "--hash=sha256:" in content and "Generated with:" in content
|
|
119
|
+
integrity = project.is_file() and pinned and build_tools.is_file() and hashes
|
|
120
|
+
if not integrity: limitations.append("dependency declarations, exact versions, and hash-locked build provenance are incomplete")
|
|
121
|
+
return {"declaration": str(project.relative_to(root)), "buildTools": str(build_tools.relative_to(root)),
|
|
122
|
+
"exactVersions": pinned, "reproducible": hashes, "provenance": hashes, "integrity": integrity}
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _workflows(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
126
|
+
directory = root / ".github" / "workflows"; files = sorted(directory.glob("*.y*ml")) if directory.is_dir() else []
|
|
127
|
+
contents = [item.read_text(encoding="utf-8") for item in files]
|
|
128
|
+
action_references = [parse_action_reference(match.group("reference"))
|
|
129
|
+
for content in contents for line in content.splitlines()
|
|
130
|
+
if (match := _USES_KEY.match(line))]
|
|
131
|
+
immutable = bool(action_references) and all(reference["immutable"] for reference in action_references)
|
|
132
|
+
least_privilege = any(re.search(r"^permissions:\s*\n\s+contents:\s+read\s*$", content, re.MULTILINE) for content in contents)
|
|
133
|
+
package_build = any(item.name == "package-build.yml" and "tools/package_build.py" in content and "--require-hashes" in content for item, content in zip(files, contents))
|
|
134
|
+
integrity = bool(files) and immutable and least_privilege and package_build
|
|
135
|
+
if not integrity: limitations.append("workflow immutability, least privilege, or reproducible-build coverage is incomplete")
|
|
136
|
+
return {"workflowCount": len(files), "immutableActions": immutable, "actionReferences": action_references,
|
|
137
|
+
"leastPrivilege": least_privilege,
|
|
138
|
+
"buildReproducibility": package_build, "integrity": integrity}
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _configuration(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
142
|
+
schema = root / "schemas" / "configuration.schema.json"; project = root / "pyproject.toml"; config = root / ".tde.yml"
|
|
143
|
+
compatible = False; provenance = None
|
|
144
|
+
try:
|
|
145
|
+
compatible = schema.is_file() and json.loads(schema.read_text(encoding="utf-8"))["properties"]["schemaVersion"]["const"] == "1.0.0"
|
|
146
|
+
if config.is_file(): provenance = RuntimeConfiguration.discover(root).digest()
|
|
147
|
+
except (KeyError, ValueError, json.JSONDecodeError): pass
|
|
148
|
+
integrity = schema.is_file() and project.is_file() and compatible
|
|
149
|
+
if not integrity: limitations.append("configuration schema compatibility or provenance is unavailable")
|
|
150
|
+
return {"schema": str(schema.relative_to(root)), "schemaCompatible": compatible, "configurationDigest": provenance, "integrity": integrity}
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def _documentation(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
154
|
+
required = ("README.md", "ENGINEERING_METHOD.md", "RUNTIME_ARCHITECTURE.md", "INTEGRATION_MODEL.md", "PACKAGING.md")
|
|
155
|
+
missing = [item for item in required if not (root / item).is_file()]
|
|
156
|
+
if missing: limitations.append("canonical documentation is missing: " + ", ".join(missing))
|
|
157
|
+
return {"required": list(required), "missing": missing, "architectureConsistent": not missing,
|
|
158
|
+
"governanceConsistent": not missing, "integrity": not missing}
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _artifacts(directories: tuple[Path, ...], limitations: list[str]) -> dict[str, Any]:
|
|
162
|
+
if not directories:
|
|
163
|
+
limitations.append("no candidate artifact directory was supplied; artifact integrity and reproducibility were not evaluated")
|
|
164
|
+
return {"directories": [], "records": [], "checksumsVerified": False, "identityVerified": False,
|
|
165
|
+
"reproducible": False, "provenanceVerified": False, "integrity": False}
|
|
166
|
+
records: list[dict[str, Any]] = []; valid = True; identities: list[list[tuple[str, str]]] = []
|
|
167
|
+
for directory in directories:
|
|
168
|
+
manifest = directory / "SHA256SUMS"; provenance = directory / "build-provenance.json"
|
|
169
|
+
artifacts = sorted([*directory.glob("*.whl"), *directory.glob("*.tar.gz")]) if directory.is_dir() else []
|
|
170
|
+
try:
|
|
171
|
+
expected = {line.split(maxsplit=1)[1]: "sha256:" + line.split(maxsplit=1)[0] for line in manifest.read_text(encoding="utf-8").splitlines() if line.strip()}
|
|
172
|
+
source = json.loads(provenance.read_text(encoding="utf-8")); recorded = {item["filename"]: item["digest"] for item in source["artifacts"]}
|
|
173
|
+
current = {item.name: _digest(item) for item in artifacts}; directory_valid = len(artifacts) == 2 and current == expected == recorded and bool(source.get("candidateSha"))
|
|
174
|
+
except (OSError, ValueError, KeyError, IndexError, json.JSONDecodeError): directory_valid = False; current = {}
|
|
175
|
+
valid = valid and directory_valid; identities.append(sorted(current.items()))
|
|
176
|
+
records.append({"directory": str(directory), "artifactCount": len(artifacts), "verified": directory_valid,
|
|
177
|
+
"artifacts": [{"filename": name, "digest": digest} for name, digest in sorted(current.items())]})
|
|
178
|
+
reproducible = valid and len(identities) > 1 and all(identity == identities[0] for identity in identities[1:])
|
|
179
|
+
if not valid: limitations.append("candidate artifact checksums, identities, or build provenance are invalid")
|
|
180
|
+
if len(directories) == 1: limitations.append("one candidate artifact directory was supplied; reproducibility requires an independent second candidate")
|
|
181
|
+
elif not reproducible: limitations.append("candidate artifacts are not byte-identical across independent builds")
|
|
182
|
+
return {"directories": [str(item) for item in directories], "records": records, "checksumsVerified": valid,
|
|
183
|
+
"identityVerified": valid, "reproducible": reproducible, "provenanceVerified": valid, "integrity": valid and reproducible}
|
tde_runtime/trend.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Normalized, read-only trends over validated baseline evidence."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from hashlib import sha256
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Mapping
|
|
8
|
+
|
|
9
|
+
from .baseline import BaselineRepository
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TrendEngine:
|
|
13
|
+
"""Aggregates canonical evidence history and deliberately makes no policy decision."""
|
|
14
|
+
|
|
15
|
+
def build(self, current: Mapping[str, Any], location: str | Path, history_depth: int | None = None) -> dict[str, Any]:
|
|
16
|
+
BaselineRepository._validate_evidence(current)
|
|
17
|
+
repository = BaselineRepository(location)
|
|
18
|
+
history = []
|
|
19
|
+
for path in sorted(Path(location).glob("*.json")) if Path(location).is_dir() else []:
|
|
20
|
+
history.append(repository.load(path))
|
|
21
|
+
history.sort(key=lambda item: item["createdAt"])
|
|
22
|
+
if history_depth is not None:
|
|
23
|
+
history = history[-history_depth:]
|
|
24
|
+
evidence = [item["evidence"] for item in history] + [current]
|
|
25
|
+
metric_trends = self._metrics(evidence)
|
|
26
|
+
capability_trends = self._capabilities(evidence)
|
|
27
|
+
finding_trends = self._findings(evidence)
|
|
28
|
+
qualification_history = [item.get("policyEvidence", {}).get("decision", "NOT_APPLICABLE") for item in evidence]
|
|
29
|
+
return {"trendId": "trend." + sha256("|".join(item["integrity"]["contentDigest"] for item in evidence).encode()).hexdigest()[:16],
|
|
30
|
+
"repositoryId": current["repository"]["id"], "history": [{"baselineId": item["baselineId"], "createdAt": item["createdAt"], "evidenceId": item["sourceEvidenceId"]} for item in history],
|
|
31
|
+
"comparisonReferences": [item["baselineId"] for item in history], "repositoryTrend": self._repository(metric_trends),
|
|
32
|
+
"capabilityTrends": capability_trends, "metricTrends": metric_trends, "findingTrends": finding_trends,
|
|
33
|
+
"qualificationTrend": {"history": qualification_history, "direction": self._direction(qualification_history)},
|
|
34
|
+
"window": "latest" if not history else "rolling", "limitations": []}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _metrics(evidence: list[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
38
|
+
series: dict[str, list[float]] = {}
|
|
39
|
+
for snapshot in evidence:
|
|
40
|
+
for metric in snapshot.get("measurements", []):
|
|
41
|
+
if isinstance(metric.get("value"), (int, float)):
|
|
42
|
+
series.setdefault(metric["metricKey"], []).append(metric["value"])
|
|
43
|
+
return [{"metricKey": key, "timeSeries": values, "delta": values[-1] - values[0],
|
|
44
|
+
"movingAverage": sum(values) / len(values), "direction": TrendEngine._numeric_direction(values)} for key, values in sorted(series.items())]
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _capabilities(evidence: list[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
48
|
+
states: dict[str, list[str]] = {}
|
|
49
|
+
for snapshot in evidence:
|
|
50
|
+
for result in snapshot.get("capabilityResults", []):
|
|
51
|
+
states.setdefault(result["capabilityId"], []).append(result.get("status", "UNKNOWN"))
|
|
52
|
+
return [{"capabilityId": key, "history": values, "direction": "STABLE" if len(set(values)) == 1 else "CHANGED"} for key, values in sorted(states.items())]
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _findings(evidence: list[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
56
|
+
seen: dict[str, int] = {}
|
|
57
|
+
for snapshot in evidence:
|
|
58
|
+
for finding in snapshot.get("findings", []):
|
|
59
|
+
seen[finding["findingId"]] = seen.get(finding["findingId"], 0) + 1
|
|
60
|
+
return [{"findingId": key, "occurrences": count, "state": "PERSISTENT" if count > 1 else "INTRODUCED"} for key, count in sorted(seen.items())]
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _repository(metrics: list[Mapping[str, Any]]) -> dict[str, Any]:
|
|
64
|
+
size = next((item for item in metrics if item["metricKey"] == "code_size.code_lines"), None)
|
|
65
|
+
return {"growth": size["delta"] if size else None, "direction": size["direction"] if size else "NOT_APPLICABLE"}
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _numeric_direction(values: list[float]) -> str:
|
|
69
|
+
return "INCREASING" if values[-1] > values[0] else "DECREASING" if values[-1] < values[0] else "STABLE"
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _direction(values: list[str]) -> str:
|
|
73
|
+
return "STABLE" if len(set(values)) <= 1 else "CHANGED"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Canonical, evidence-only Trusted Delivery validation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import Any, Mapping
|
|
11
|
+
|
|
12
|
+
MANIFEST_SCHEMA_ID = "tde.trusted-delivery-manifest"
|
|
13
|
+
MANIFEST_SCHEMA_VERSION = "1.0.0"
|
|
14
|
+
_SHA256 = re.compile(r"^sha256:[0-9a-f]{64}$")
|
|
15
|
+
_GIT_SHA = re.compile(r"^[0-9a-f]{40}$")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _canonical(value: object) -> bytes:
|
|
19
|
+
return (json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + "\n").encode("utf-8")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _git(root: Path, *arguments: str) -> tuple[bool, str]:
|
|
23
|
+
completed = subprocess.run(["git", "-C", str(root), *arguments], text=True, capture_output=True, check=False)
|
|
24
|
+
return completed.returncode == 0, completed.stdout.strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TrustedDelivery:
|
|
28
|
+
"""Bind a candidate to assurance, artifact, manifest, and workflow evidence.
|
|
29
|
+
|
|
30
|
+
This layer deliberately consumes Software Assurance evidence instead of
|
|
31
|
+
re-evaluating assurance rules or making a release/publication decision.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def validate(self, root: str | Path, runtime_evidence: Mapping[str, Any] | None,
|
|
35
|
+
assurance_evidence: Mapping[str, Any] | None = None, manifest: str | Path | None = None) -> dict[str, Any]:
|
|
36
|
+
root = Path(root).resolve(); limitations: list[str] = []
|
|
37
|
+
candidate = self._candidate(root, limitations)
|
|
38
|
+
manifest_evidence = self._manifest(Path(manifest).resolve() if manifest else None, candidate, limitations)
|
|
39
|
+
assurance = self._assurance(assurance_evidence, limitations)
|
|
40
|
+
artifacts = self._artifacts(manifest_evidence, assurance, limitations)
|
|
41
|
+
workflow = self._workflow(root, assurance, limitations)
|
|
42
|
+
runtime_ok = runtime_evidence is not None and runtime_evidence.get("validation", {}).get("status") == "VALID"
|
|
43
|
+
if not runtime_ok:
|
|
44
|
+
limitations.append("validated Runtime evidence is unavailable")
|
|
45
|
+
checks = {
|
|
46
|
+
"candidateIntegrity": candidate["integrity"],
|
|
47
|
+
"manifestIntegrity": manifest_evidence["integrity"],
|
|
48
|
+
"artifactIntegrity": artifacts["integrity"],
|
|
49
|
+
"workflowIntegrity": workflow["integrity"],
|
|
50
|
+
"softwareAssuranceIntegrity": assurance["integrity"],
|
|
51
|
+
"runtimeEvidenceIntegrity": runtime_ok,
|
|
52
|
+
"buildProvenanceValidation": artifacts["provenanceValidated"],
|
|
53
|
+
}
|
|
54
|
+
# An omitted external delivery manifest/artifact is a bounded
|
|
55
|
+
# pre-release limitation. An explicitly supplied invalid input fails
|
|
56
|
+
# closed; candidate, Runtime, and assurance failures always fail.
|
|
57
|
+
required = ("candidateIntegrity", "softwareAssuranceIntegrity", "runtimeEvidenceIntegrity")
|
|
58
|
+
invalid_input = manifest is not None and (not checks["manifestIntegrity"] or not checks["artifactIntegrity"])
|
|
59
|
+
decision = "FAIL" if not all(checks[name] for name in required) or invalid_input else "PASS_WITH_WARNINGS" if limitations else "PASS"
|
|
60
|
+
evidence = {
|
|
61
|
+
"schemaId": "tde.trusted-delivery", "schemaVersion": "1.0.0", "candidate": candidate,
|
|
62
|
+
"manifest": manifest_evidence, "artifacts": artifacts, "workflow": workflow,
|
|
63
|
+
"softwareAssurance": {"assuranceId": assurance.get("assuranceId"), "decision": assurance.get("decision")},
|
|
64
|
+
"runtimeEvidenceId": runtime_evidence.get("integrity", {}).get("contentDigest") if runtime_evidence else None,
|
|
65
|
+
"checks": checks, "limitations": limitations, "decision": decision, "qualification": decision,
|
|
66
|
+
}
|
|
67
|
+
evidence["trustedDeliveryId"] = "trusted-delivery.sha256." + sha256(_canonical(evidence)).hexdigest()
|
|
68
|
+
return evidence
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def _candidate(root: Path, limitations: list[str]) -> dict[str, Any]:
|
|
72
|
+
sha_ok, sha = _git(root, "rev-parse", "HEAD"); _, branch = _git(root, "branch", "--show-current")
|
|
73
|
+
branch = branch or os.environ.get("TDE_CANDIDATE_SOURCE_BRANCH", "")
|
|
74
|
+
_, repository = _git(root, "config", "--get", "remote.origin.url"); status_ok, dirty = _git(root, "status", "--porcelain")
|
|
75
|
+
integrity = sha_ok and bool(_GIT_SHA.fullmatch(sha)) and bool(branch) and status_ok and not dirty
|
|
76
|
+
if not integrity:
|
|
77
|
+
limitations.append("candidate SHA, repository branch, or clean working tree is invalid")
|
|
78
|
+
return {"sha": sha or None, "candidateIdentity": "candidate.git." + sha if sha else None,
|
|
79
|
+
"repository": repository or "local", "branch": branch or None, "workingTreeClean": not bool(dirty),
|
|
80
|
+
"integrity": integrity}
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _manifest(path: Path | None, candidate: Mapping[str, Any], limitations: list[str]) -> dict[str, Any]:
|
|
84
|
+
if path is None:
|
|
85
|
+
limitations.append("no delivery manifest was supplied; manifest and artifact references were not evaluated")
|
|
86
|
+
return {"path": None, "identity": None, "integrity": False, "supplied": False, "artifacts": []}
|
|
87
|
+
try:
|
|
88
|
+
raw = path.read_bytes(); value = json.loads(raw)
|
|
89
|
+
items = value["artifacts"]
|
|
90
|
+
valid = (value.get("schemaId") == MANIFEST_SCHEMA_ID and value.get("schemaVersion") == MANIFEST_SCHEMA_VERSION
|
|
91
|
+
and value.get("candidate", {}).get("sha") == candidate["sha"]
|
|
92
|
+
and value.get("candidate", {}).get("repository") == candidate["repository"]
|
|
93
|
+
and value.get("candidate", {}).get("branch") == candidate["branch"]
|
|
94
|
+
and isinstance(items, list) and bool(items)
|
|
95
|
+
and all(isinstance(item, dict) and isinstance(item.get("filename"), str) and _SHA256.fullmatch(item.get("digest", "")) for item in items))
|
|
96
|
+
except (OSError, ValueError, KeyError, TypeError, json.JSONDecodeError):
|
|
97
|
+
raw = b""; value = {}; items = []; valid = False
|
|
98
|
+
if not valid:
|
|
99
|
+
limitations.append("supplied delivery manifest does not meet the canonical schema, candidate, or checksum requirements")
|
|
100
|
+
return {"path": str(path), "identity": "manifest.sha256." + sha256(raw).hexdigest() if raw else None,
|
|
101
|
+
"schemaId": value.get("schemaId"), "schemaVersion": value.get("schemaVersion"), "supplied": True,
|
|
102
|
+
"candidate": value.get("candidate"), "artifacts": items, "integrity": valid}
|
|
103
|
+
|
|
104
|
+
@staticmethod
|
|
105
|
+
def _assurance(evidence: Mapping[str, Any] | None, limitations: list[str]) -> Mapping[str, Any]:
|
|
106
|
+
valid = (evidence is not None and evidence.get("schemaId") == "tde.software-assurance"
|
|
107
|
+
and isinstance(evidence.get("assuranceId"), str) and evidence.get("decision") != "FAIL")
|
|
108
|
+
if not valid:
|
|
109
|
+
limitations.append("canonical Software Assurance evidence is unavailable or failed")
|
|
110
|
+
return {"integrity": False}
|
|
111
|
+
return {**evidence, "integrity": True}
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _artifacts(manifest: Mapping[str, Any], assurance: Mapping[str, Any], limitations: list[str]) -> dict[str, Any]:
|
|
115
|
+
records = [artifact for directory in assurance.get("artifacts", {}).get("records", []) for artifact in directory.get("artifacts", [])]
|
|
116
|
+
supplied = bool(manifest.get("supplied"))
|
|
117
|
+
if not supplied:
|
|
118
|
+
return {"records": [], "integrity": False, "provenanceValidated": False}
|
|
119
|
+
expected = {(item["filename"], item["digest"]) for item in manifest.get("artifacts", [])}
|
|
120
|
+
actual = {(item.get("filename"), item.get("digest")) for item in records}
|
|
121
|
+
integrity = bool(assurance.get("checks", {}).get("artifactIntegrity")) and expected == actual
|
|
122
|
+
if not integrity:
|
|
123
|
+
limitations.append("manifest artifact references do not match checksum-verified, reproducible assurance artifacts")
|
|
124
|
+
return {"records": sorted(records, key=lambda item: item["filename"]), "integrity": integrity,
|
|
125
|
+
"provenanceValidated": integrity and bool(assurance.get("checks", {}).get("buildProvenanceVerification"))}
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _workflow(root: Path, assurance: Mapping[str, Any], limitations: list[str]) -> dict[str, Any]:
|
|
129
|
+
directory = root / ".github" / "workflows"
|
|
130
|
+
records = [{"path": str(item.relative_to(root)), "digest": "sha256:" + sha256(item.read_bytes()).hexdigest()}
|
|
131
|
+
for item in sorted(directory.glob("*.y*ml"))] if directory.is_dir() else []
|
|
132
|
+
workflow = assurance.get("workflows", {})
|
|
133
|
+
integrity = bool(workflow.get("immutableActions")) and bool(workflow.get("leastPrivilege")) and bool(records)
|
|
134
|
+
if not integrity:
|
|
135
|
+
limitations.append("workflow provenance, immutable actions, or least-privilege evidence is unavailable")
|
|
136
|
+
return {"records": records, "immutableActions": bool(workflow.get("immutableActions")),
|
|
137
|
+
"leastPrivilege": bool(workflow.get("leastPrivilege")), "integrity": integrity}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
tde_cli/__init__.py,sha256=nfLKy9af_L7AkShru2wINR11XlthP2RYas7oDEJ3RmI,108
|
|
2
|
+
tde_cli/main.py,sha256=sExbye_XFA0qpD7h1qFZOLruLomeRXb6bnoBzdgvuTk,25763
|
|
3
|
+
tde_runtime/__init__.py,sha256=a3F_jaulHsYtvNir7bu5h7YZYfv8LHlFbFLpianR-mQ,209
|
|
4
|
+
tde_runtime/baseline.py,sha256=V6Q2NAKYsMlJJrFJ8DVUEuLatNqX7EY1IdaGePObSQo,12220
|
|
5
|
+
tde_runtime/code_size.py,sha256=MZUICW01Hpr2pptZdL3xkvevy1xVvPRogyb8xk1okP4,3920
|
|
6
|
+
tde_runtime/complexity.py,sha256=qkXceiNRhMDfEUW6welqCehkK-x4dW05As6LLFqAFS8,5560
|
|
7
|
+
tde_runtime/configuration.py,sha256=Pk5LhOl1LAnHF4_UFIrE8fhanHRNet7zEPz8FW6n67U,4606
|
|
8
|
+
tde_runtime/dependency_health.py,sha256=Q-YCLfXHHDC6nPuQoU7KUqFSYW8l477Vm6vquDYzz7M,1896
|
|
9
|
+
tde_runtime/docker_artifact.py,sha256=4Xtc8WKAkxXbmgHkMj6p40VivQUpJyVl8ATLHyfZPyo,2270
|
|
10
|
+
tde_runtime/evidence_store.py,sha256=w2lzPZ-KnhXKZlBY5hnpWl58Ld8i20vG_lUXCpq8BQk,4062
|
|
11
|
+
tde_runtime/execution.py,sha256=qvKxS7DfNQlHpOLSHv9HHlVDPKHFjOkq3ofPVqctwpw,17288
|
|
12
|
+
tde_runtime/maintainability.py,sha256=cFlSogrKeoh7reQ1uHx7s_QHga6zPZaptscyl_QjI5s,1282
|
|
13
|
+
tde_runtime/models.py,sha256=wa--Ryn8y81lGSCc6qqcRHys2KJYt4R0Qtczm6O7m8E,1789
|
|
14
|
+
tde_runtime/policy.py,sha256=bRfKWRa4rBxutYItF-SJUp1X1JXZqHJoS303_RQNJb4,12503
|
|
15
|
+
tde_runtime/query.py,sha256=wiFafUy5ah1mHes8p5k5dHe6txsjHSGnFVR9PjIJ2V4,2626
|
|
16
|
+
tde_runtime/registries.py,sha256=weJ9JyRpqjYVkyY8KOFRp9VXyphmNcksiBVzqoKGAKc,903
|
|
17
|
+
tde_runtime/release_bundle.py,sha256=g9XmdtIiUfGoluUo40awtQUdXOpghhwWsSN05vZdbdQ,1744
|
|
18
|
+
tde_runtime/release_candidate.py,sha256=G69PalpAZWetHgGE_dpkMq7QG7j85wVl4ku64uriDBQ,3519
|
|
19
|
+
tde_runtime/release_certification.py,sha256=adyZh6EvXQODUIYxxWn9cyjKEosUOJrcXZD5twP66IE,5865
|
|
20
|
+
tde_runtime/release_publication.py,sha256=LUruMnUpa9LWkysfPeaoMTQJ1IZEJaO1o3B4forzass,8318
|
|
21
|
+
tde_runtime/release_qualification.py,sha256=zStbHQDbzExhUOq7Hur0eAHcoDrobeFZ29CHBDyL59E,7076
|
|
22
|
+
tde_runtime/runtime.py,sha256=ccD0psn76VgKL3gWuiVuUzu9ERrqgzdSl7UbjVwBzX8,12918
|
|
23
|
+
tde_runtime/runtime_qualification.py,sha256=edDuGrcZPVUywq7syL8Tx3z_e5A60bJzSyVt3dVNtBs,4075
|
|
24
|
+
tde_runtime/software_assurance.py,sha256=PuHqwtGwV7jrwZFERbQmrdAsq3qYAq2_JWcK9nnTJn8,12669
|
|
25
|
+
tde_runtime/trend.py,sha256=lHI_HhqQ5w-bZrL7KUuRP7_G9zwSRRO1bUSDPe_7k9w,4258
|
|
26
|
+
tde_runtime/trusted_delivery.py,sha256=R1PHQP6l1tcbK3au-3jbVdrdK4SGIqx8SfCY3Rq9emo,8826
|
|
27
|
+
tde_runtime/policies/__init__.py,sha256=BElFqFO-IljGDlnnJsBmc0yfm_FOh3vWRbrnAc7K1EQ,57
|
|
28
|
+
tde_runtime/policies/generation-1.json,sha256=BuhZYZwT9hjRd-hf4vKENwSwy8DRIv_g3aOSRP2TDvU,1224
|
|
29
|
+
technical_debt_engine_runtime-0.1.0.dist-info/METADATA,sha256=CRx_dlxoJUQZ0KwHkhpdhC0ZlWasPHE2PSaXkpcW3HE,151
|
|
30
|
+
technical_debt_engine_runtime-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
31
|
+
technical_debt_engine_runtime-0.1.0.dist-info/entry_points.txt,sha256=Stpn7-vZYlZ8VYVfoscTtV1DRxktV8flogT0tWFl1GE,50
|
|
32
|
+
technical_debt_engine_runtime-0.1.0.dist-info/top_level.txt,sha256=Iz1bgDYbK6v7h3XujaLDy-Or-T3aRDf2BVmdtXlPOys,20
|
|
33
|
+
technical_debt_engine_runtime-0.1.0.dist-info/RECORD,,
|