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
165 lines (117 loc) · 5.01 KB
/
Copy pathmodels.py
File metadata and controls
165 lines (117 loc) · 5.01 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
"""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 Confidence(StrEnum):
"""Confidence in extracted or inferred evidence."""
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
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)
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
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.0")
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)
disclaimer: str = Field(min_length=1)