forked from ChelseaKR/ceqa-preflight
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodels.py
More file actions
200 lines (142 loc) · 6.33 KB
/
Copy pathmodels.py
File metadata and controls
200 lines (142 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
"""Typed contracts shared by loaders, inspectors, rules, and reporters."""
from __future__ import annotations
from datetime import datetime
from enum import StrEnum
from pathlib import PurePosixPath, PureWindowsPath
from typing import Any
from urllib.parse import urlparse
from pydantic import BaseModel, ConfigDict, Field, field_validator
class StrictModel(BaseModel):
"""Base model that rejects unknown fields and trims input strings."""
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
class FilingType(StrEnum):
"""Filing types supported by the initial product scope."""
NOD = "NOD"
NOE = "NOE"
class FindingStatus(StrEnum):
"""User-facing outcome levels for a check."""
# This is a user-facing validation status, not a credential.
PASS = "pass" # nosec B105
WARNING = "warning"
FAILURE = "failure"
MANUAL = "manual"
class SkipReason(StrEnum):
"""Why an applicable rule did not run during a check."""
EXPERIMENTAL_NOT_INCLUDED = "experimental_not_included"
WITHDRAWN = "withdrawn"
NOT_SELECTED = "not_selected"
EXCLUDED_BY_REQUEST = "excluded_by_request"
class Confidence(StrEnum):
"""Confidence in extracted or inferred evidence."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class SourceKind(StrEnum):
"""What kind of authority a citation carries, so a reader never mistakes one for another."""
OFFICIAL = "official" # State of California guidance the rule is grounded in
TECHNICAL_REFERENCE = "technical_reference" # a non-CEQA technical reference (e.g. OWASP)
PROJECT_ADVISORY = "project_advisory" # this project's own documented reasoning
class SourceCitation(StrictModel):
"""A current, traceable source for a rule or report finding."""
title: str = Field(min_length=1)
url: str = Field(min_length=1)
kind: SourceKind = SourceKind.OFFICIAL
section: str | None = None
effective_date: str | None = None
accessed_date: str | None = None
@field_validator("url")
@classmethod
def require_http_url(cls, value: str) -> str:
parsed = urlparse(value)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError("source URL must be an absolute HTTP(S) URL")
return value
class Evidence(StrictModel):
"""Structured evidence supporting a finding without embedding source files."""
details: dict[str, Any] = Field(default_factory=dict)
class Finding(StrictModel):
"""A deterministic result from one versioned rule."""
rule_id: str = Field(min_length=1)
rule_version: str = Field(min_length=1)
status: FindingStatus
title: str = Field(min_length=1)
message: str = Field(min_length=1)
document: str | None = None
page: int | None = Field(default=None, ge=1)
field: str | None = None
evidence: Evidence = Field(default_factory=Evidence)
remediation: str = Field(min_length=1)
source: SourceCitation | None = None
confidence: Confidence = Confidence.HIGH
class SkippedCheck(StrictModel):
"""A rule that applied to this filing type but did not run, and why.
A report that lists only what ran cannot be read as a statement about the whole
package: a reader has no way to tell an all-clear from an all-clear with checks
removed. Every skip is recorded here so a clean result always carries its own scope.
"""
rule_id: str = Field(min_length=1)
rule_version: str = Field(min_length=1)
title: str = Field(min_length=1)
reason: SkipReason
detail: str = Field(min_length=1)
source: SourceCitation | None = None
def _normalize_relative_path(value: str) -> str:
"""Return a safe, POSIX-style path that cannot escape a package root."""
if not value or "\x00" in value:
raise ValueError("document path must be a non-empty, non-null string")
windows_path = PureWindowsPath(value)
candidate = value.replace("\\", "/")
posix_path = PurePosixPath(candidate)
if windows_path.is_absolute() or windows_path.drive or posix_path.is_absolute():
raise ValueError("document path must be relative to the package root")
if any(part in {"", ".", ".."} for part in posix_path.parts):
raise ValueError("document path cannot contain empty, current, or parent segments")
return posix_path.as_posix()
class DocumentEntry(StrictModel):
"""A document expected in the package."""
path: str = Field(min_length=1)
category: str | None = None
primary: bool = False
@field_validator("path")
@classmethod
def normalize_relative_path(cls, value: str) -> str:
return _normalize_relative_path(value)
class ProjectMetadata(StrictModel):
"""Manifest data intentionally supplied for conservative consistency checks."""
title: str = Field(min_length=1)
description: str | None = None
sch_number: str | None = None
lead_agency: str | None = None
county: str | None = None
city_or_community: str | None = None
class Contact(StrictModel):
"""A project or agency contact represented in a manifest."""
name: str = Field(min_length=1)
authority: str | None = None
role: str = Field(min_length=1)
class PackageManifest(StrictModel):
"""The user-supplied, versioned description of an intended filing package."""
schema_version: str = Field(default="1.0")
filing_type: FilingType
project: ProjectMetadata
contacts: list[Contact] = Field(default_factory=list)
documents: list[DocumentEntry] = Field(min_length=1)
@field_validator("schema_version")
@classmethod
def require_supported_schema_major(cls, value: str) -> str:
major, separator, _ = value.partition(".")
if not separator or major != "1":
raise ValueError("unsupported manifest schema major; expected 1.x")
return value
class InspectionReport(StrictModel):
"""Versioned, JSON-serializable output for an inspection run."""
report_schema_version: str = Field(default="1.1")
tool_version: str = Field(min_length=1)
ruleset_version: str = Field(min_length=1)
generated_at: datetime
input_fingerprint: str = Field(min_length=1)
filing_type: FilingType
findings: list[Finding] = Field(default_factory=list)
manual_review: list[Finding] = Field(default_factory=list)
not_run: list[SkippedCheck] = Field(default_factory=list)
disclaimer: str = Field(min_length=1)