commitguardian 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.
- commitguard/__init__.py +26 -0
- commitguard/__main__.py +6 -0
- commitguard/api/__init__.py +18 -0
- commitguard/api/app.py +1376 -0
- commitguard/api/governance.py +1085 -0
- commitguard/api/hosting.py +196 -0
- commitguard/api/http.py +252 -0
- commitguard/api/settings.py +169 -0
- commitguard/audit/__init__.py +13 -0
- commitguard/audit/logger.py +34 -0
- commitguard/audit/models.py +222 -0
- commitguard/audit/storage.py +59 -0
- commitguard/ci/__init__.py +7 -0
- commitguard/ci/context.py +60 -0
- commitguard/cli/__init__.py +6 -0
- commitguard/cli/app.py +74 -0
- commitguard/cli/commands/__init__.py +1 -0
- commitguard/cli/commands/benchmark.py +441 -0
- commitguard/cli/commands/check.py +100 -0
- commitguard/cli/commands/ci.py +165 -0
- commitguard/cli/commands/dashboard.py +141 -0
- commitguard/cli/commands/doctor.py +533 -0
- commitguard/cli/commands/github.py +449 -0
- commitguard/cli/commands/hook.py +156 -0
- commitguard/cli/commands/init.py +137 -0
- commitguard/cli/commands/install.py +152 -0
- commitguard/cli/commands/policy.py +36 -0
- commitguard/cli/commands/report.py +39 -0
- commitguard/cli/commands/reproduce.py +123 -0
- commitguard/cli/commands/scan.py +47 -0
- commitguard/cli/common.py +44 -0
- commitguard/cli/output.py +89 -0
- commitguard/cli/render.py +367 -0
- commitguard/config/__init__.py +6 -0
- commitguard/config/defaults.py +53 -0
- commitguard/config/enforcement.py +53 -0
- commitguard/config/loader.py +174 -0
- commitguard/config/schema.py +105 -0
- commitguard/config/sources.py +183 -0
- commitguard/controlplane/__init__.py +24 -0
- commitguard/controlplane/access.py +231 -0
- commitguard/controlplane/commands.py +393 -0
- commitguard/controlplane/errors.py +88 -0
- commitguard/controlplane/identity.py +478 -0
- commitguard/controlplane/members.py +219 -0
- commitguard/controlplane/notifications.py +787 -0
- commitguard/controlplane/pagination.py +146 -0
- commitguard/controlplane/policies.py +1204 -0
- commitguard/controlplane/queries.py +1814 -0
- commitguard/controlplane/results.py +909 -0
- commitguard/controlplane/rules.py +184 -0
- commitguard/controlplane/views.py +799 -0
- commitguard/core/__init__.py +6 -0
- commitguard/core/context.py +31 -0
- commitguard/core/decision.py +58 -0
- commitguard/core/engine.py +82 -0
- commitguard/core/result.py +177 -0
- commitguard/detectors/__init__.py +6 -0
- commitguard/detectors/base.py +58 -0
- commitguard/detectors/bot.py +87 -0
- commitguard/detectors/coauthor.py +86 -0
- commitguard/detectors/identity.py +76 -0
- commitguard/detectors/registry.py +72 -0
- commitguard/detectors/trailer.py +211 -0
- commitguard/exceptions/__init__.py +33 -0
- commitguard/exceptions/base.py +9 -0
- commitguard/exceptions/configuration.py +22 -0
- commitguard/exceptions/detection.py +11 -0
- commitguard/exceptions/git.py +41 -0
- commitguard/exceptions/service.py +25 -0
- commitguard/git/__init__.py +12 -0
- commitguard/git/commands.py +101 -0
- commitguard/git/commit.py +97 -0
- commitguard/git/diff.py +36 -0
- commitguard/git/hooks.py +527 -0
- commitguard/git/push.py +93 -0
- commitguard/git/ranges.py +71 -0
- commitguard/git/repository.py +447 -0
- commitguard/github/__init__.py +34 -0
- commitguard/github/actions.py +163 -0
- commitguard/github/app.py +935 -0
- commitguard/github/auth.py +217 -0
- commitguard/github/check_runs.py +172 -0
- commitguard/github/checks.py +210 -0
- commitguard/github/client.py +844 -0
- commitguard/github/enforcement_status.py +209 -0
- commitguard/github/errors.py +129 -0
- commitguard/github/events.py +563 -0
- commitguard/github/identifiers.py +90 -0
- commitguard/github/installations.py +566 -0
- commitguard/github/markdown.py +19 -0
- commitguard/github/permissions.py +70 -0
- commitguard/github/pull_requests.py +53 -0
- commitguard/github/queue.py +47 -0
- commitguard/github/recovery.py +124 -0
- commitguard/github/repositories.py +305 -0
- commitguard/github/server.py +52 -0
- commitguard/github/settings.py +174 -0
- commitguard/github/storage.py +2315 -0
- commitguard/github/webhooks.py +129 -0
- commitguard/github/worker.py +628 -0
- commitguard/github/workflow.py +286 -0
- commitguard/governance/__init__.py +26 -0
- commitguard/governance/bulk.py +765 -0
- commitguard/governance/cache.py +88 -0
- commitguard/governance/common.py +216 -0
- commitguard/governance/exceptions.py +861 -0
- commitguard/governance/groups.py +448 -0
- commitguard/governance/inventory.py +386 -0
- commitguard/governance/posture.py +1272 -0
- commitguard/governance/resolver.py +632 -0
- commitguard/governance/rollouts.py +760 -0
- commitguard/governance/rules.py +371 -0
- commitguard/governance/schedules.py +663 -0
- commitguard/governance/service.py +120 -0
- commitguard/governance/settings.py +365 -0
- commitguard/governance/simulation.py +618 -0
- commitguard/governance/workflow.py +734 -0
- commitguard/notifications/__init__.py +2 -0
- commitguard/notifications/channels/__init__.py +1 -0
- commitguard/notifications/channels/base.py +22 -0
- commitguard/notifications/channels/email.py +110 -0
- commitguard/notifications/channels/in_app.py +74 -0
- commitguard/notifications/channels/sink.py +58 -0
- commitguard/notifications/channels/webhook.py +233 -0
- commitguard/notifications/deduplication.py +57 -0
- commitguard/notifications/dispatcher.py +201 -0
- commitguard/notifications/models.py +439 -0
- commitguard/notifications/outbox.py +106 -0
- commitguard/notifications/preferences.py +224 -0
- commitguard/notifications/retry.py +282 -0
- commitguard/notifications/service.py +128 -0
- commitguard/notifications/settings.py +167 -0
- commitguard/notifications/templates.py +108 -0
- commitguard/observability/__init__.py +5 -0
- commitguard/observability/logging.py +161 -0
- commitguard/observability/metrics.py +105 -0
- commitguard/policies/__init__.py +6 -0
- commitguard/policies/defaults.py +48 -0
- commitguard/policies/evaluator.py +66 -0
- commitguard/policies/governance.py +498 -0
- commitguard/policies/loader.py +23 -0
- commitguard/policies/mandatory.py +52 -0
- commitguard/policies/model.py +46 -0
- commitguard/provenance/__init__.py +9 -0
- commitguard/provenance/author.py +146 -0
- commitguard/provenance/committer.py +16 -0
- commitguard/provenance/normalization.py +158 -0
- commitguard/provenance/signatures.py +34 -0
- commitguard/provenance/trailers.py +256 -0
- commitguard/research/__init__.py +26 -0
- commitguard/research/compare.py +231 -0
- commitguard/research/datasets.py +1484 -0
- commitguard/research/detection.py +183 -0
- commitguard/research/environment.py +185 -0
- commitguard/research/gitenv.py +108 -0
- commitguard/research/hooks.py +247 -0
- commitguard/research/metrics.py +85 -0
- commitguard/research/performance.py +194 -0
- commitguard/research/platform.py +288 -0
- commitguard/research/report.py +372 -0
- commitguard/research/repository.py +111 -0
- commitguard/research/reproduction.py +297 -0
- commitguard/research/results.py +94 -0
- commitguard/rules/__init__.py +11 -0
- commitguard/rules/data/ai-domains.yaml +51 -0
- commitguard/rules/data/ai-identities.yaml +131 -0
- commitguard/rules/data/bot-identities.yaml +53 -0
- commitguard/rules/data/patterns.yaml +52 -0
- commitguard/rules/loader.py +102 -0
- commitguard/rules/matcher.py +212 -0
- commitguard/rules/models.py +269 -0
- commitguard/security/__init__.py +5 -0
- commitguard/security/hashing.py +30 -0
- commitguard/security/rate_limit.py +33 -0
- commitguard/security/safe_yaml.py +69 -0
- commitguard/security/sanitization.py +85 -0
- commitguard/security/secrets.py +169 -0
- commitguard/security/validation.py +89 -0
- commitguard/services/__init__.py +15 -0
- commitguard/services/analysis.py +119 -0
- commitguard/services/audit.py +95 -0
- commitguard/services/ci.py +383 -0
- commitguard/services/enforcement.py +102 -0
- commitguard/services/hooks.py +254 -0
- commitguard/services/remediation.py +99 -0
- commitguard/services/reports.py +146 -0
- commitguard/services/scan.py +172 -0
- commitguard/utils/__init__.py +1 -0
- commitguard/utils/filesystem.py +72 -0
- commitguard/utils/platform.py +35 -0
- commitguard/utils/subprocess.py +84 -0
- commitguardian-0.1.0.dist-info/METADATA +694 -0
- commitguardian-0.1.0.dist-info/RECORD +197 -0
- commitguardian-0.1.0.dist-info/WHEEL +4 -0
- commitguardian-0.1.0.dist-info/entry_points.txt +2 -0
- commitguardian-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Measurement and evidence: reproducible benchmarks of CommitGuard itself.
|
|
2
|
+
|
|
3
|
+
This package measures the product; it never changes a security decision. Every
|
|
4
|
+
benchmark drives the same code paths production uses - the
|
|
5
|
+
:class:`~commitguard.services.analysis.Analyzer` (detection engine + policy
|
|
6
|
+
evaluator), real ``git`` processes and the installed Git hooks - and records the
|
|
7
|
+
environment it ran in (:mod:`commitguard.research.environment`) so results can
|
|
8
|
+
be compared across versions and machines.
|
|
9
|
+
|
|
10
|
+
Modules::
|
|
11
|
+
|
|
12
|
+
environment benchmark manifest: versions, platform, CPU, RAM, fingerprints
|
|
13
|
+
datasets the labelled commit dataset (clean, violations, variations,
|
|
14
|
+
malformed, adversarial, generated) and its fingerprint
|
|
15
|
+
metrics confusion matrix, precision/recall/F1, latency percentiles
|
|
16
|
+
detection detection accuracy against the labelled dataset
|
|
17
|
+
performance detection latency, message-size scaling, CPU and memory
|
|
18
|
+
hooks Git operations with and without CommitGuard hooks
|
|
19
|
+
repository history-size scaling of range scans on real repositories
|
|
20
|
+
platform a portable self-check of the local enforcement path
|
|
21
|
+
results immutable result files and the run index
|
|
22
|
+
report security and benchmark reports from recorded results
|
|
23
|
+
|
|
24
|
+
Nothing here imports the GitHub App, the API or the dashboard, and nothing
|
|
25
|
+
contacts the network.
|
|
26
|
+
"""
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Comparing a benchmark result with an earlier one.
|
|
2
|
+
|
|
3
|
+
Results are immutable, so comparing versions means comparing two recorded files.
|
|
4
|
+
This module extracts the comparable numbers from each benchmark's result document
|
|
5
|
+
and applies documented thresholds.
|
|
6
|
+
|
|
7
|
+
Two kinds of change are distinguished, because they deserve different responses:
|
|
8
|
+
|
|
9
|
+
* a **correctness** change - a new false negative or false positive, a platform
|
|
10
|
+
check that no longer passes, a seeded violation that was not found - is a
|
|
11
|
+
regression at any size;
|
|
12
|
+
* a **performance** change is a regression only beyond the threshold for that
|
|
13
|
+
measure, and a documented performance regression does not by itself reject a
|
|
14
|
+
release (see docs/maintainers/benchmarking.md).
|
|
15
|
+
|
|
16
|
+
Comparisons are only meaningful between runs of the same benchmark on comparable
|
|
17
|
+
machines; the environment of both runs is reported so that a reader can judge.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any, Literal
|
|
21
|
+
|
|
22
|
+
from pydantic import BaseModel, ConfigDict
|
|
23
|
+
|
|
24
|
+
Verdict = Literal["improved", "unchanged", "regressed", "new", "missing"]
|
|
25
|
+
|
|
26
|
+
#: Relative change (of the worse direction) tolerated before a performance metric
|
|
27
|
+
#: counts as a regression. Correctness metrics use a threshold of 0.
|
|
28
|
+
THRESHOLDS = {
|
|
29
|
+
"latency": 0.20,
|
|
30
|
+
"throughput": 0.20,
|
|
31
|
+
"memory": 0.25,
|
|
32
|
+
"hook_overhead": 0.25,
|
|
33
|
+
"startup": 0.25,
|
|
34
|
+
"correctness": 0.0,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Metric(BaseModel):
|
|
39
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
kind: str
|
|
43
|
+
baseline: float | None
|
|
44
|
+
current: float | None
|
|
45
|
+
lower_is_better: bool
|
|
46
|
+
change_ratio: float | None
|
|
47
|
+
verdict: Verdict
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def correctness(self) -> bool:
|
|
51
|
+
return self.kind == "correctness"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Comparison(BaseModel):
|
|
55
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
56
|
+
|
|
57
|
+
benchmark: str
|
|
58
|
+
baseline_file: str
|
|
59
|
+
current_file: str
|
|
60
|
+
baseline_environment: str
|
|
61
|
+
current_environment: str
|
|
62
|
+
same_environment: bool
|
|
63
|
+
same_dataset: bool
|
|
64
|
+
baseline_dataset: str | None
|
|
65
|
+
current_dataset: str | None
|
|
66
|
+
metrics: tuple[Metric, ...]
|
|
67
|
+
regressions: tuple[str, ...]
|
|
68
|
+
improvements: tuple[str, ...]
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def ok(self) -> bool:
|
|
72
|
+
return not self.regressions
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _environment(document: dict[str, Any]) -> str:
|
|
76
|
+
manifest = document.get("manifest", {})
|
|
77
|
+
return (
|
|
78
|
+
f"{manifest.get('commitguard_version')} on {manifest.get('operating_system')} "
|
|
79
|
+
f"{manifest.get('os_release')} ({manifest.get('cpu_model')}), "
|
|
80
|
+
f"Python {manifest.get('python_version')}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _detection_metrics(result: dict[str, Any]) -> dict[str, tuple[float, str, bool]]:
|
|
85
|
+
decision = result.get("decision", {})
|
|
86
|
+
latency = result.get("latency", {})
|
|
87
|
+
return {
|
|
88
|
+
"false negatives": (decision.get("false_negative", 0), "correctness", True),
|
|
89
|
+
"false positives": (decision.get("false_positive", 0), "correctness", True),
|
|
90
|
+
"exact decisions": (result.get("exact_decision_matches", 0), "correctness", False),
|
|
91
|
+
"latency p50 (ms)": (latency.get("p50_ms", 0.0), "latency", True),
|
|
92
|
+
"latency p95 (ms)": (latency.get("p95_ms", 0.0), "latency", True),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _performance_metrics(result: dict[str, Any]) -> dict[str, tuple[float, str, bool]]:
|
|
97
|
+
metrics: dict[str, tuple[float, str, bool]] = {
|
|
98
|
+
"rule loading (ms)": (result.get("startup_ms", 0.0), "startup", True)
|
|
99
|
+
}
|
|
100
|
+
for batch in result.get("batches", []):
|
|
101
|
+
commits = batch["commits"]
|
|
102
|
+
metrics[f"{commits} commits: p50 (ms)"] = (batch["latency"]["p50_ms"], "latency", True)
|
|
103
|
+
metrics[f"{commits} commits: commits/s"] = (
|
|
104
|
+
batch["commits_per_second"],
|
|
105
|
+
"throughput",
|
|
106
|
+
False,
|
|
107
|
+
)
|
|
108
|
+
for message in result.get("message_sizes", []):
|
|
109
|
+
size = message["message_bytes"]
|
|
110
|
+
metrics[f"{size} byte message: p50 (ms)"] = (message["latency"]["p50_ms"], "latency", True)
|
|
111
|
+
peak = result.get("peak_rss_bytes")
|
|
112
|
+
if peak:
|
|
113
|
+
metrics["peak RSS (MiB)"] = (peak / 1_048_576, "memory", True)
|
|
114
|
+
return metrics
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _hooks_metrics(result: dict[str, Any]) -> dict[str, tuple[float, str, bool]]:
|
|
118
|
+
metrics: dict[str, tuple[float, str, bool]] = {}
|
|
119
|
+
for overhead in result.get("overhead", []):
|
|
120
|
+
metrics[f"{overhead['operation']}: overhead p50 (ms)"] = (
|
|
121
|
+
overhead["overhead_p50_ms"],
|
|
122
|
+
"hook_overhead",
|
|
123
|
+
True,
|
|
124
|
+
)
|
|
125
|
+
for observation in result.get("observations", []):
|
|
126
|
+
metrics[f"observation: {observation['check']}"] = (
|
|
127
|
+
1.0 if observation.get("matches") else 0.0,
|
|
128
|
+
"correctness",
|
|
129
|
+
False,
|
|
130
|
+
)
|
|
131
|
+
return metrics
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _repository_metrics(result: dict[str, Any]) -> dict[str, tuple[float, str, bool]]:
|
|
135
|
+
metrics: dict[str, tuple[float, str, bool]] = {}
|
|
136
|
+
for history in result.get("histories", []):
|
|
137
|
+
commits = history["commits"]
|
|
138
|
+
metrics[f"{commits} commits: commits/s"] = (
|
|
139
|
+
history["commits_per_second"],
|
|
140
|
+
"throughput",
|
|
141
|
+
False,
|
|
142
|
+
)
|
|
143
|
+
metrics[f"{commits} commits: violations found"] = (
|
|
144
|
+
float(history["blocked"]),
|
|
145
|
+
"correctness",
|
|
146
|
+
False,
|
|
147
|
+
)
|
|
148
|
+
return metrics
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _platform_metrics(result: dict[str, Any]) -> dict[str, tuple[float, str, bool]]:
|
|
152
|
+
return {
|
|
153
|
+
"checks passed": (float(result.get("passed", 0)), "correctness", False),
|
|
154
|
+
"checks failed": (float(result.get("failed", 0)), "correctness", True),
|
|
155
|
+
"checks skipped": (float(result.get("skipped", 0)), "correctness", True),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
EXTRACTORS = {
|
|
160
|
+
"detection": _detection_metrics,
|
|
161
|
+
"performance": _performance_metrics,
|
|
162
|
+
"hooks": _hooks_metrics,
|
|
163
|
+
"repository": _repository_metrics,
|
|
164
|
+
"platform": _platform_metrics,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _verdict(
|
|
169
|
+
baseline: float | None, current: float | None, kind: str, lower_is_better: bool
|
|
170
|
+
) -> tuple[float | None, Verdict]:
|
|
171
|
+
if baseline is None:
|
|
172
|
+
return None, "new"
|
|
173
|
+
if current is None:
|
|
174
|
+
return None, "missing"
|
|
175
|
+
if baseline == current:
|
|
176
|
+
return 0.0, "unchanged"
|
|
177
|
+
if baseline == 0:
|
|
178
|
+
# No ratio is meaningful; any movement away from zero is a change.
|
|
179
|
+
worse = current > 0 if lower_is_better else current < 0
|
|
180
|
+
return None, "regressed" if worse else "improved"
|
|
181
|
+
ratio = (current - baseline) / abs(baseline)
|
|
182
|
+
worse_by = ratio if lower_is_better else -ratio
|
|
183
|
+
if worse_by > THRESHOLDS.get(kind, 0.0):
|
|
184
|
+
return ratio, "regressed"
|
|
185
|
+
if worse_by < 0:
|
|
186
|
+
return ratio, "improved"
|
|
187
|
+
return ratio, "unchanged"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def compare(baseline: dict[str, Any], current: dict[str, Any], *, benchmark: str) -> Comparison:
|
|
191
|
+
"""Compare two recorded result documents of the same benchmark."""
|
|
192
|
+
extract = EXTRACTORS.get(benchmark)
|
|
193
|
+
if extract is None:
|
|
194
|
+
raise ValueError(f"no comparison defined for benchmark {benchmark!r}")
|
|
195
|
+
before, after = extract(baseline.get("result", {})), extract(current.get("result", {}))
|
|
196
|
+
metrics: list[Metric] = []
|
|
197
|
+
for name in sorted(set(before) | set(after)):
|
|
198
|
+
old, new = before.get(name), after.get(name)
|
|
199
|
+
kind = (new or old or (0.0, "correctness", True))[1]
|
|
200
|
+
lower_is_better = (new or old or (0.0, "correctness", True))[2]
|
|
201
|
+
old_value = old[0] if old else None
|
|
202
|
+
new_value = new[0] if new else None
|
|
203
|
+
ratio, verdict = _verdict(old_value, new_value, kind, lower_is_better)
|
|
204
|
+
metrics.append(
|
|
205
|
+
Metric(
|
|
206
|
+
name=name,
|
|
207
|
+
kind=kind,
|
|
208
|
+
baseline=old_value,
|
|
209
|
+
current=new_value,
|
|
210
|
+
lower_is_better=lower_is_better,
|
|
211
|
+
change_ratio=ratio,
|
|
212
|
+
verdict=verdict,
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
baseline_environment, current_environment = _environment(baseline), _environment(current)
|
|
216
|
+
return Comparison(
|
|
217
|
+
benchmark=benchmark,
|
|
218
|
+
baseline_file=str(baseline.get("_file", "")),
|
|
219
|
+
current_file=str(current.get("_file", "")),
|
|
220
|
+
baseline_environment=baseline_environment,
|
|
221
|
+
current_environment=current_environment,
|
|
222
|
+
same_environment=baseline.get("manifest", {}).get("cpu_model")
|
|
223
|
+
== current.get("manifest", {}).get("cpu_model"),
|
|
224
|
+
same_dataset=baseline.get("manifest", {}).get("dataset_fingerprint")
|
|
225
|
+
== current.get("manifest", {}).get("dataset_fingerprint"),
|
|
226
|
+
baseline_dataset=baseline.get("manifest", {}).get("dataset_version"),
|
|
227
|
+
current_dataset=current.get("manifest", {}).get("dataset_version"),
|
|
228
|
+
metrics=tuple(metrics),
|
|
229
|
+
regressions=tuple(m.name for m in metrics if m.verdict == "regressed"),
|
|
230
|
+
improvements=tuple(m.name for m in metrics if m.verdict == "improved"),
|
|
231
|
+
)
|