verification-ecology-kit 1.0.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.
Files changed (107) hide show
  1. verification_ecology_kit/__init__.py +58 -0
  2. verification_ecology_kit/audit/__init__.py +1 -0
  3. verification_ecology_kit/audit/adversarial_ingress.py +31 -0
  4. verification_ecology_kit/audit/aperture_regression.py +45 -0
  5. verification_ecology_kit/audit/local_info.py +82 -0
  6. verification_ecology_kit/audit/monoculture.py +34 -0
  7. verification_ecology_kit/audit/packet_ecology.py +39 -0
  8. verification_ecology_kit/audit/reports.py +97 -0
  9. verification_ecology_kit/audit/residual_metabolism.py +39 -0
  10. verification_ecology_kit/audit/schema_overclosure.py +33 -0
  11. verification_ecology_kit/audit/security.py +150 -0
  12. verification_ecology_kit/canonicalization.py +140 -0
  13. verification_ecology_kit/cli.py +886 -0
  14. verification_ecology_kit/digest.py +93 -0
  15. verification_ecology_kit/errors.py +37 -0
  16. verification_ecology_kit/ids.py +17 -0
  17. verification_ecology_kit/model/__init__.py +1 -0
  18. verification_ecology_kit/model/aperture.py +76 -0
  19. verification_ecology_kit/model/authority.py +147 -0
  20. verification_ecology_kit/model/boundaries.py +45 -0
  21. verification_ecology_kit/model/certification.py +101 -0
  22. verification_ecology_kit/model/circulation.py +57 -0
  23. verification_ecology_kit/model/conformance.py +289 -0
  24. verification_ecology_kit/model/contracts.py +59 -0
  25. verification_ecology_kit/model/ecology_state.py +38 -0
  26. verification_ecology_kit/model/frontier.py +27 -0
  27. verification_ecology_kit/model/history.py +53 -0
  28. verification_ecology_kit/model/judgments.py +77 -0
  29. verification_ecology_kit/model/ledger.py +239 -0
  30. verification_ecology_kit/model/lifecycle.py +101 -0
  31. verification_ecology_kit/model/maturity.py +28 -0
  32. verification_ecology_kit/model/overclosure.py +97 -0
  33. verification_ecology_kit/model/packets.py +298 -0
  34. verification_ecology_kit/model/reachability.py +118 -0
  35. verification_ecology_kit/model/records.py +145 -0
  36. verification_ecology_kit/model/registries.py +93 -0
  37. verification_ecology_kit/model/residuals.py +98 -0
  38. verification_ecology_kit/operations/__init__.py +1 -0
  39. verification_ecology_kit/operations/base.py +308 -0
  40. verification_ecology_kit/operations/compose.py +18 -0
  41. verification_ecology_kit/operations/contrast.py +18 -0
  42. verification_ecology_kit/operations/fork.py +18 -0
  43. verification_ecology_kit/operations/generalize.py +21 -0
  44. verification_ecology_kit/operations/internalize.py +23 -0
  45. verification_ecology_kit/operations/quarantine.py +18 -0
  46. verification_ecology_kit/operations/redact.py +18 -0
  47. verification_ecology_kit/operations/repair.py +18 -0
  48. verification_ecology_kit/operations/retire.py +18 -0
  49. verification_ecology_kit/operations/specialize.py +18 -0
  50. verification_ecology_kit/ports/__init__.py +1 -0
  51. verification_ecology_kit/ports/checker.py +11 -0
  52. verification_ecology_kit/ports/clock.py +10 -0
  53. verification_ecology_kit/ports/generator.py +12 -0
  54. verification_ecology_kit/ports/policy.py +11 -0
  55. verification_ecology_kit/ports/reporter.py +9 -0
  56. verification_ecology_kit/ports/storage.py +13 -0
  57. verification_ecology_kit/py.typed +1 -0
  58. verification_ecology_kit/references.py +233 -0
  59. verification_ecology_kit/result.py +238 -0
  60. verification_ecology_kit/runtime/__init__.py +1 -0
  61. verification_ecology_kit/runtime/engine.py +73 -0
  62. verification_ecology_kit/runtime/in_memory.py +18 -0
  63. verification_ecology_kit/runtime/json_store.py +83 -0
  64. verification_ecology_kit/runtime/loop.py +72 -0
  65. verification_ecology_kit/runtime/policies.py +37 -0
  66. verification_ecology_kit/schemas/aperture.schema.json +36 -0
  67. verification_ecology_kit/schemas/audit-report.schema.json +42 -0
  68. verification_ecology_kit/schemas/auth-inputs.schema.json +46 -0
  69. verification_ecology_kit/schemas/authority-decision.schema.json +74 -0
  70. verification_ecology_kit/schemas/boundary-record.schema.json +32 -0
  71. verification_ecology_kit/schemas/carrier-registry.schema.json +44 -0
  72. verification_ecology_kit/schemas/certification-profile.schema.json +30 -0
  73. verification_ecology_kit/schemas/certification-record.schema.json +44 -0
  74. verification_ecology_kit/schemas/check-result.schema.json +27 -0
  75. verification_ecology_kit/schemas/checker-registry.schema.json +44 -0
  76. verification_ecology_kit/schemas/conformance-report.schema.json +38 -0
  77. verification_ecology_kit/schemas/continuation-specification.schema.json +30 -0
  78. verification_ecology_kit/schemas/contract-registry.schema.json +12 -0
  79. verification_ecology_kit/schemas/counterexample-channel.schema.json +34 -0
  80. verification_ecology_kit/schemas/digest-record.schema.json +34 -0
  81. verification_ecology_kit/schemas/frontier-profile.schema.json +30 -0
  82. verification_ecology_kit/schemas/judgment-record.schema.json +52 -0
  83. verification_ecology_kit/schemas/ledger-event.schema.json +36 -0
  84. verification_ecology_kit/schemas/lifecycle-status-event.schema.json +34 -0
  85. verification_ecology_kit/schemas/maturity-profile.schema.json +38 -0
  86. verification_ecology_kit/schemas/overclosure-witness.schema.json +30 -0
  87. verification_ecology_kit/schemas/reachability-certificate.schema.json +40 -0
  88. verification_ecology_kit/schemas/reference-edge.schema.json +26 -0
  89. verification_ecology_kit/schemas/residual-ledger.schema.json +12 -0
  90. verification_ecology_kit/schemas/residual-record.schema.json +35 -0
  91. verification_ecology_kit/schemas/runtime-report.schema.json +20 -0
  92. verification_ecology_kit/schemas/schema-catalogue.schema.json +26 -0
  93. verification_ecology_kit/schemas/schema-migration-witness.schema.json +30 -0
  94. verification_ecology_kit/schemas/schema_catalogue.json +36 -0
  95. verification_ecology_kit/schemas/sound-gap-residual.schema.json +15 -0
  96. verification_ecology_kit/schemas/status-event.schema.json +34 -0
  97. verification_ecology_kit/schemas/status-view.schema.json +13 -0
  98. verification_ecology_kit/schemas/use-context.schema.json +46 -0
  99. verification_ecology_kit/schemas/verifier-packet.schema.json +59 -0
  100. verification_ecology_kit/schemas/vet-bundle.schema.json +30 -0
  101. verification_ecology_kit/schemas/vet-object-envelope.schema.json +45 -0
  102. verification_ecology_kit/schemas/vet-object-ref.schema.json +35 -0
  103. verification_ecology_kit-1.0.0.dist-info/METADATA +459 -0
  104. verification_ecology_kit-1.0.0.dist-info/RECORD +107 -0
  105. verification_ecology_kit-1.0.0.dist-info/WHEEL +4 -0
  106. verification_ecology_kit-1.0.0.dist-info/entry_points.txt +2 -0
  107. verification_ecology_kit-1.0.0.dist-info/licenses/LICENSE +16 -0
@@ -0,0 +1,58 @@
1
+ """Public API for verification-ecology-kit."""
2
+
3
+ from verification_ecology_kit.audit.reports import AuditEngine
4
+ from verification_ecology_kit.canonicalization import Canonicalizer
5
+ from verification_ecology_kit.digest import DigestPolicy
6
+ from verification_ecology_kit.model.authority import AuthorityEngine
7
+ from verification_ecology_kit.model.certification import CertificationEngine
8
+ from verification_ecology_kit.model.conformance import ConformanceEngine, VetBundle
9
+ from verification_ecology_kit.model.ecology_state import VerifierEcologyState
10
+ from verification_ecology_kit.model.history import ObservableProcessHistory
11
+ from verification_ecology_kit.model.ledger import ResidualLedger, TraceCertificate
12
+ from verification_ecology_kit.model.maturity import MaturityProfile
13
+ from verification_ecology_kit.model.overclosure import OverclosureWitness
14
+ from verification_ecology_kit.model.packets import (
15
+ BoundaryTesterPacket,
16
+ CounterPacket,
17
+ VerifierPacket,
18
+ )
19
+ from verification_ecology_kit.model.residuals import ResidualRecord
20
+ from verification_ecology_kit.operations.base import PacketOperationEngine
21
+ from verification_ecology_kit.references import (
22
+ DigestRecord,
23
+ ObjectEnvelope,
24
+ ObjectRef,
25
+ ReferenceEdge,
26
+ SchemaMigrationWitness,
27
+ )
28
+ from verification_ecology_kit.runtime.engine import RuntimeEngine
29
+
30
+ __version__ = "1.0.0"
31
+
32
+ __all__ = [
33
+ "AuditEngine",
34
+ "AuthorityEngine",
35
+ "BoundaryTesterPacket",
36
+ "Canonicalizer",
37
+ "CertificationEngine",
38
+ "ConformanceEngine",
39
+ "CounterPacket",
40
+ "DigestPolicy",
41
+ "DigestRecord",
42
+ "MaturityProfile",
43
+ "ObjectEnvelope",
44
+ "ObjectRef",
45
+ "ObservableProcessHistory",
46
+ "OverclosureWitness",
47
+ "PacketOperationEngine",
48
+ "ReferenceEdge",
49
+ "ResidualLedger",
50
+ "ResidualRecord",
51
+ "RuntimeEngine",
52
+ "SchemaMigrationWitness",
53
+ "TraceCertificate",
54
+ "VerifierEcologyState",
55
+ "VerifierPacket",
56
+ "VetBundle",
57
+ "__version__",
58
+ ]
@@ -0,0 +1 @@
1
+ """Audit engines."""
@@ -0,0 +1,31 @@
1
+ """Adversarial packet ingress audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.packets import VerifierPacket
7
+ from verification_ecology_kit.model.records import TrustStatus, Visibility
8
+
9
+
10
+ def audit_adversarial_ingress(packet: VerifierPacket) -> AuditReport:
11
+ findings: list[AuditFinding] = []
12
+ status = packet.circulation_status
13
+ if status is None:
14
+ findings.append(
15
+ AuditFinding("missing_circulation_status", "Packet has no circulation status.")
16
+ )
17
+ elif (
18
+ status.trust_status in {TrustStatus.LOW_TRUST, TrustStatus.ADVERSARIAL}
19
+ and status.visibility != Visibility.QUARANTINED
20
+ ):
21
+ findings.append(
22
+ AuditFinding(
23
+ "low_trust_not_quarantined",
24
+ "Low-trust or adversarial packet must be quarantined before internalization.",
25
+ severity="high",
26
+ evidence_refs=(packet.packet_id,),
27
+ suggested_repair_hooks=("quarantine_packet",),
28
+ )
29
+ )
30
+ decision = "pass" if not findings else "quarantine"
31
+ return AuditReport("adversarial-ingress", decision, findings=findings).finalize()
@@ -0,0 +1,45 @@
1
+ """Aperture regression audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.aperture import Aperture
7
+
8
+
9
+ def audit_aperture_regression(before: Aperture, after: Aperture) -> AuditReport:
10
+ findings: list[AuditFinding] = []
11
+ if before.strict_capacity_leq(after):
12
+ return AuditReport(
13
+ audit_name="aperture-regression",
14
+ decision="pass",
15
+ passed_checks=["strict_capacity_comparison"],
16
+ ).finalize()
17
+ if before.accountable_loss_leq(after):
18
+ findings.append(
19
+ AuditFinding(
20
+ code="accountable_aperture_loss",
21
+ message="Feasible aperture decreased, but the loss is residualized.",
22
+ severity="low",
23
+ suggested_repair_hooks=("review_aperture_debt",),
24
+ )
25
+ )
26
+ return AuditReport(
27
+ audit_name="aperture-regression",
28
+ decision="residualize",
29
+ findings=findings,
30
+ residualized_failures=["aperture_debt"],
31
+ ).finalize()
32
+ findings.append(
33
+ AuditFinding(
34
+ code="silent_aperture_loss",
35
+ message="Feasible aperture decreased without a residual obligation.",
36
+ severity="high",
37
+ suggested_repair_hooks=("create_aperture_debt_residual",),
38
+ )
39
+ )
40
+ return AuditReport(
41
+ audit_name="aperture-regression",
42
+ decision="reject",
43
+ findings=findings,
44
+ support_blocking_failures=["aperture_debt"],
45
+ ).finalize()
@@ -0,0 +1,82 @@
1
+ """Local information leak scanner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
9
+
10
+ LOCAL_PATH_PATTERNS: tuple[re.Pattern[str], ...] = (
11
+ re.compile(r"[A-Za-z]:[\\/](?:Users|Documents and Settings)[\\/][^\\/\s]+", re.IGNORECASE),
12
+ re.compile(r"/(?:Users|home)/[^/\s]+", re.IGNORECASE),
13
+ )
14
+ EMAIL_PATTERN = re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE)
15
+
16
+
17
+ def scan_local_info(path: Path, *, allowlist: tuple[str, ...] = ()) -> AuditReport:
18
+ findings: list[AuditFinding] = []
19
+ for file_path in _iter_text_files(path):
20
+ text = file_path.read_text(encoding="utf-8", errors="ignore")
21
+ for pattern in LOCAL_PATH_PATTERNS:
22
+ for match in pattern.finditer(text):
23
+ value = match.group(0)
24
+ if any(item in value for item in allowlist):
25
+ continue
26
+ findings.append(
27
+ AuditFinding(
28
+ "local_information_leak",
29
+ f"Local path-like value in {file_path.as_posix()}",
30
+ severity="high",
31
+ )
32
+ )
33
+ for match in EMAIL_PATTERN.finditer(text):
34
+ value = match.group(0)
35
+ if any(item in value for item in allowlist):
36
+ continue
37
+ findings.append(
38
+ AuditFinding(
39
+ "private_email",
40
+ f"Email-like value in {file_path.as_posix()}",
41
+ severity="medium",
42
+ )
43
+ )
44
+ return AuditReport(
45
+ "local-info",
46
+ "pass" if not findings else "quarantine",
47
+ findings=findings,
48
+ support_blocking_failures=["local_information_leak"] if findings else [],
49
+ ).finalize()
50
+
51
+
52
+ def _iter_text_files(path: Path) -> list[Path]:
53
+ if path.is_file():
54
+ return [path]
55
+ excluded = {
56
+ ".git",
57
+ ".venv",
58
+ ".pytest_cache",
59
+ ".ruff_cache",
60
+ ".mypy_cache",
61
+ ".hypothesis",
62
+ "__pycache__",
63
+ "dist",
64
+ "build",
65
+ "site",
66
+ }
67
+ files: list[Path] = []
68
+ for file_path in path.rglob("*"):
69
+ if any(part in excluded for part in file_path.parts):
70
+ continue
71
+ if file_path.name == ".coverage":
72
+ continue
73
+ if file_path.is_file() and file_path.suffix.lower() not in {
74
+ ".pyc",
75
+ ".png",
76
+ ".jpg",
77
+ ".jpeg",
78
+ ".gif",
79
+ ".pdf",
80
+ }:
81
+ files.append(file_path)
82
+ return files
@@ -0,0 +1,34 @@
1
+ """Verifier monoculture audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.overclosure import VerifierMonocultureDetector
7
+
8
+
9
+ def audit_monoculture(
10
+ *,
11
+ origin_assumptions: list[tuple[str, ...]],
12
+ question_forms: list[tuple[str, ...]],
13
+ residual_filters: list[tuple[str, ...]],
14
+ counter_packet_routes: list[tuple[str, ...]],
15
+ ) -> AuditReport:
16
+ result = VerifierMonocultureDetector().detect(
17
+ origin_assumptions=origin_assumptions,
18
+ question_forms=question_forms,
19
+ residual_filters=residual_filters,
20
+ counter_packet_routes=counter_packet_routes,
21
+ )
22
+ findings = [
23
+ AuditFinding(
24
+ code=code.value,
25
+ message="Packet population has shared assumptions, filters, or missing counter routes.",
26
+ suggested_repair_hooks=result.suggested_repair_hooks,
27
+ )
28
+ for code in result.failure_codes
29
+ ]
30
+ return AuditReport(
31
+ "monoculture",
32
+ "pass" if result.passed else "residualize",
33
+ findings=findings,
34
+ ).finalize()
@@ -0,0 +1,39 @@
1
+ """Packet ecology audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.packets import VerifierPacket
7
+
8
+
9
+ def audit_packet_ecology(packets: list[VerifierPacket]) -> AuditReport:
10
+ findings: list[AuditFinding] = []
11
+ passed: list[str] = []
12
+ residualized: list[str] = []
13
+ blocking: list[str] = []
14
+ for packet in packets:
15
+ for result in packet.validate():
16
+ if result.passed:
17
+ passed.append(f"{packet.packet_id}:{result.check_name}")
18
+ continue
19
+ codes = [code.value for code in result.failure_codes]
20
+ residualized.extend(codes)
21
+ if result.support_blocking_failures:
22
+ blocking.extend(code.value for code in result.support_blocking_failures)
23
+ findings.append(
24
+ AuditFinding(
25
+ code=",".join(codes) or result.check_name,
26
+ message=f"Packet {packet.packet_id} requires {result.check_name}",
27
+ evidence_refs=(packet.packet_id,),
28
+ suggested_repair_hooks=result.suggested_repair_hooks,
29
+ )
30
+ )
31
+ decision = "pass" if not findings else "residualize"
32
+ return AuditReport(
33
+ audit_name="packet-ecology",
34
+ decision=decision,
35
+ findings=findings,
36
+ passed_checks=passed,
37
+ residualized_failures=residualized,
38
+ support_blocking_failures=blocking,
39
+ ).finalize()
@@ -0,0 +1,97 @@
1
+ """Shared audit reports and aggregate audit engine."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
8
+
9
+ from verification_ecology_kit.canonicalization import Canonicalizer
10
+ from verification_ecology_kit.digest import DigestPolicy
11
+ from verification_ecology_kit.model.aperture import Aperture
12
+ from verification_ecology_kit.model.ecology_state import VerifierEcologyState
13
+ from verification_ecology_kit.model.packets import VerifierPacket
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class AuditFinding:
18
+ code: str
19
+ message: str
20
+ severity: str = "medium"
21
+ evidence_refs: tuple[str, ...] = ()
22
+ suggested_repair_hooks: tuple[str, ...] = ()
23
+
24
+
25
+ @dataclass
26
+ class AuditReport:
27
+ audit_name: str
28
+ decision: str
29
+ findings: list[AuditFinding] = field(default_factory=list)
30
+ passed_checks: list[str] = field(default_factory=list)
31
+ residualized_failures: list[str] = field(default_factory=list)
32
+ support_blocking_failures: list[str] = field(default_factory=list)
33
+ evidence_refs: list[str] = field(default_factory=list)
34
+ provenance: list[str] = field(default_factory=list)
35
+ report_digest: str = ""
36
+
37
+ def finalize(self) -> AuditReport:
38
+ payload = self.to_dict(include_digest=False)
39
+ self.report_digest = (
40
+ DigestPolicy().digest_json(payload, canonicalizer=Canonicalizer()).value
41
+ )
42
+ return self
43
+
44
+ def to_dict(self, *, include_digest: bool = True) -> dict[str, Any]:
45
+ data: dict[str, Any] = {
46
+ "audit_name": self.audit_name,
47
+ "decision": self.decision,
48
+ "findings": [
49
+ {
50
+ "code": finding.code,
51
+ "message": finding.message,
52
+ "severity": finding.severity,
53
+ "evidence_refs": list(finding.evidence_refs),
54
+ "suggested_repair_hooks": list(finding.suggested_repair_hooks),
55
+ }
56
+ for finding in self.findings
57
+ ],
58
+ "passed_checks": self.passed_checks,
59
+ "residualized_failures": self.residualized_failures,
60
+ "support_blocking_failures": self.support_blocking_failures,
61
+ "evidence_refs": self.evidence_refs,
62
+ "provenance": self.provenance,
63
+ }
64
+ if include_digest:
65
+ data["report_digest"] = self.report_digest
66
+ return data
67
+
68
+ def to_json(self) -> str:
69
+ self.finalize()
70
+ return json.dumps(self.to_dict(), indent=2, sort_keys=True)
71
+
72
+ def to_markdown(self) -> str:
73
+ self.finalize()
74
+ lines = [f"# {self.audit_name}", "", f"Decision: `{self.decision}`", ""]
75
+ if not self.findings:
76
+ lines.append("No findings.")
77
+ for finding in self.findings:
78
+ lines.append(f"- `{finding.code}` {finding.severity}: {finding.message}")
79
+ lines.extend(["", f"Report digest: `{self.report_digest}`"])
80
+ return "\n".join(lines)
81
+
82
+
83
+ class AuditEngine:
84
+ def packet_ecology(self, packets: list[VerifierPacket]) -> AuditReport:
85
+ from verification_ecology_kit.audit.packet_ecology import audit_packet_ecology
86
+
87
+ return audit_packet_ecology(packets)
88
+
89
+ def residual_metabolism(self, state: VerifierEcologyState) -> AuditReport:
90
+ from verification_ecology_kit.audit.residual_metabolism import audit_residual_metabolism
91
+
92
+ return audit_residual_metabolism(state)
93
+
94
+ def aperture_regression(self, before: Aperture, after: Aperture) -> AuditReport:
95
+ from verification_ecology_kit.audit.aperture_regression import audit_aperture_regression
96
+
97
+ return audit_aperture_regression(before, after)
@@ -0,0 +1,39 @@
1
+ """Residual metabolism audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.ecology_state import VerifierEcologyState
7
+ from verification_ecology_kit.model.records import LedgerStatus
8
+ from verification_ecology_kit.model.residuals import check_residual_liveness
9
+
10
+
11
+ def audit_residual_metabolism(state: VerifierEcologyState) -> AuditReport:
12
+ findings: list[AuditFinding] = []
13
+ passed: list[str] = []
14
+ residualized: list[str] = []
15
+ for residual in state.residual_ledger.residuals.values():
16
+ if residual.status != LedgerStatus.ACTIVE:
17
+ passed.append(f"{residual.residual_id}:inactive_disposition")
18
+ continue
19
+ result = check_residual_liveness(residual)
20
+ if result.passed:
21
+ passed.append(f"{residual.residual_id}:live")
22
+ else:
23
+ residualized.extend(code.value for code in result.failure_codes)
24
+ findings.append(
25
+ AuditFinding(
26
+ code="liveness_debt",
27
+ message=f"Residual {residual.residual_id} is active without a live route",
28
+ evidence_refs=(residual.residual_id,),
29
+ suggested_repair_hooks=("route_residual", "retire_with_reason", "quarantine"),
30
+ )
31
+ )
32
+ decision = "pass" if not findings else "residualize"
33
+ return AuditReport(
34
+ audit_name="residual-metabolism",
35
+ decision=decision,
36
+ findings=findings,
37
+ passed_checks=passed,
38
+ residualized_failures=residualized,
39
+ ).finalize()
@@ -0,0 +1,33 @@
1
+ """Schema-overclosure audit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
6
+ from verification_ecology_kit.model.overclosure import SchemaOverclosureDetector
7
+
8
+
9
+ def audit_schema_overclosure(
10
+ *,
11
+ schema_rejected_unknown: bool,
12
+ suppressed_residuals: tuple[str, ...] = (),
13
+ dashboard_hid_component_failure: bool = False,
14
+ ) -> AuditReport:
15
+ result, residuals = SchemaOverclosureDetector().detect(
16
+ schema_rejected_unknown=schema_rejected_unknown,
17
+ suppressed_residuals=suppressed_residuals,
18
+ dashboard_hid_component_failure=dashboard_hid_component_failure,
19
+ )
20
+ findings = [
21
+ AuditFinding(
22
+ code=code.value,
23
+ message="Schema behavior may suppress residuals or unknowns.",
24
+ evidence_refs=tuple(residual.residual_id for residual in residuals),
25
+ suggested_repair_hooks=result.suggested_repair_hooks,
26
+ )
27
+ for code in result.failure_codes
28
+ ]
29
+ return AuditReport(
30
+ "schema-overclosure",
31
+ "pass" if result.passed else "residualize",
32
+ findings=findings,
33
+ ).finalize()
@@ -0,0 +1,150 @@
1
+ """Secret and package safety scanners."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import tarfile
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+ from verification_ecology_kit.audit.reports import AuditFinding, AuditReport
11
+
12
+ TOKEN_PREFIXES = ("sk" + "-", "gh" + "p_", "gh" + "o_", "pypi" + "-")
13
+ PRIVATE_KEY_MARKER = "BEGIN " + "PRIVATE KEY"
14
+ AWS_ACCESS_KEY = "AK" + "IA"
15
+
16
+
17
+ def scan_secrets(path: Path) -> AuditReport:
18
+ findings: list[AuditFinding] = []
19
+ token_pattern = re.compile(
20
+ "(" + "|".join(re.escape(prefix) for prefix in TOKEN_PREFIXES) + r")[A-Za-z0-9_\-]{12,}"
21
+ )
22
+ for file_path in _iter_text_files(path):
23
+ text = file_path.read_text(encoding="utf-8", errors="ignore")
24
+ if PRIVATE_KEY_MARKER in text:
25
+ findings.append(
26
+ AuditFinding("private_key", f"Private key marker in {file_path.as_posix()}", "high")
27
+ )
28
+ if AWS_ACCESS_KEY in text:
29
+ findings.append(
30
+ AuditFinding(
31
+ "cloud_credential", f"Cloud key marker in {file_path.as_posix()}", "high"
32
+ )
33
+ )
34
+ if token_pattern.search(text):
35
+ findings.append(
36
+ AuditFinding("token", f"Token-like value in {file_path.as_posix()}", "high")
37
+ )
38
+ if file_path.name.startswith(".env") and file_path.name != ".env.example":
39
+ findings.append(
40
+ AuditFinding(
41
+ "env_file", f"Environment file included: {file_path.as_posix()}", "high"
42
+ )
43
+ )
44
+ return AuditReport(
45
+ "security",
46
+ "pass" if not findings else "quarantine",
47
+ findings=findings,
48
+ support_blocking_failures=["secret_leak"] if findings else [],
49
+ ).finalize()
50
+
51
+
52
+ def verify_package_paths(paths: list[Path]) -> AuditReport:
53
+ findings: list[AuditFinding] = []
54
+ forbidden_suffixes = {".aux", ".bbl", ".bcf", ".blg", ".log", ".out", ".toc", ".pdf"}
55
+ for path in paths:
56
+ if path.suffix.lower() in forbidden_suffixes:
57
+ findings.append(
58
+ AuditFinding(
59
+ "package_content_leak", f"Forbidden build artifact: {path.as_posix()}", "high"
60
+ )
61
+ )
62
+ if ".ipynb_checkpoints" in path.parts:
63
+ findings.append(
64
+ AuditFinding(
65
+ "package_content_leak", f"Notebook checkpoint: {path.as_posix()}", "high"
66
+ )
67
+ )
68
+ return AuditReport(
69
+ "package-contents",
70
+ "pass" if not findings else "quarantine",
71
+ findings=findings,
72
+ support_blocking_failures=["package_content_leak"] if findings else [],
73
+ ).finalize()
74
+
75
+
76
+ def verify_package_archives(dist: Path) -> AuditReport:
77
+ findings: list[AuditFinding] = []
78
+ paths: list[Path] = []
79
+ evidence_refs: list[str] = []
80
+ wheels = 0
81
+ sdists = 0
82
+
83
+ if not dist.exists():
84
+ findings.append(
85
+ AuditFinding("package_artifact_missing", "dist directory is missing", "high")
86
+ )
87
+ else:
88
+ for archive in dist.iterdir():
89
+ if archive.suffix == ".whl":
90
+ wheels += 1
91
+ evidence_refs.append(archive.as_posix())
92
+ with zipfile.ZipFile(archive) as wheel:
93
+ paths.extend(Path(name) for name in wheel.namelist())
94
+ elif archive.suffixes[-2:] == [".tar", ".gz"]:
95
+ sdists += 1
96
+ evidence_refs.append(archive.as_posix())
97
+ with tarfile.open(archive) as sdist:
98
+ paths.extend(Path(member.name) for member in sdist.getmembers())
99
+
100
+ if wheels == 0:
101
+ findings.append(
102
+ AuditFinding("package_artifact_missing", "wheel artifact is missing", "high")
103
+ )
104
+ if sdists == 0:
105
+ findings.append(
106
+ AuditFinding("package_artifact_missing", "sdist artifact is missing", "high")
107
+ )
108
+
109
+ content_report = verify_package_paths(paths)
110
+ findings.extend(content_report.findings)
111
+ return AuditReport(
112
+ "package-contents",
113
+ "pass" if not findings else "quarantine",
114
+ findings=findings,
115
+ support_blocking_failures=["package_content_leak"] if findings else [],
116
+ evidence_refs=evidence_refs,
117
+ ).finalize()
118
+
119
+
120
+ def _iter_text_files(path: Path) -> list[Path]:
121
+ if path.is_file():
122
+ return [path]
123
+ excluded = {
124
+ ".git",
125
+ ".venv",
126
+ ".pytest_cache",
127
+ ".ruff_cache",
128
+ ".mypy_cache",
129
+ ".hypothesis",
130
+ "__pycache__",
131
+ "dist",
132
+ "build",
133
+ "site",
134
+ }
135
+ files: list[Path] = []
136
+ for file_path in path.rglob("*"):
137
+ if any(part in excluded for part in file_path.parts):
138
+ continue
139
+ if file_path.name == ".coverage":
140
+ continue
141
+ if file_path.is_file() and file_path.suffix.lower() not in {
142
+ ".pyc",
143
+ ".png",
144
+ ".jpg",
145
+ ".jpeg",
146
+ ".gif",
147
+ ".pdf",
148
+ }:
149
+ files.append(file_path)
150
+ return files