sourcebound 1.2.1__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 (71) hide show
  1. clean_docs/__init__.py +26 -0
  2. clean_docs/__main__.py +3 -0
  3. clean_docs/accessibility.py +182 -0
  4. clean_docs/adapters/__init__.py +1 -0
  5. clean_docs/adapters/event_capture.py +56 -0
  6. clean_docs/adapters/mdx_dependencies.json +714 -0
  7. clean_docs/adapters/mdx_parser.mjs +29992 -0
  8. clean_docs/applicability.py +330 -0
  9. clean_docs/audit.py +1120 -0
  10. clean_docs/bootstrap.py +507 -0
  11. clean_docs/capabilities.py +200 -0
  12. clean_docs/changed.py +452 -0
  13. clean_docs/claims.py +840 -0
  14. clean_docs/cli.py +1612 -0
  15. clean_docs/context.py +307 -0
  16. clean_docs/corpus.py +377 -0
  17. clean_docs/demo.py +369 -0
  18. clean_docs/doctor.py +184 -0
  19. clean_docs/emit/__init__.py +4 -0
  20. clean_docs/emit/llms_txt.py +102 -0
  21. clean_docs/emit/stepwise.py +168 -0
  22. clean_docs/engine.py +324 -0
  23. clean_docs/errors.py +20 -0
  24. clean_docs/evaluation.py +867 -0
  25. clean_docs/execution.py +138 -0
  26. clean_docs/explain.py +123 -0
  27. clean_docs/extractors/__init__.py +19 -0
  28. clean_docs/extractors/command.py +51 -0
  29. clean_docs/extractors/inventory.py +176 -0
  30. clean_docs/extractors/json_pointer.py +84 -0
  31. clean_docs/extractors/python_literal.py +104 -0
  32. clean_docs/extractors/static.py +111 -0
  33. clean_docs/feedback.py +1390 -0
  34. clean_docs/impact.py +1624 -0
  35. clean_docs/improvements.py +1178 -0
  36. clean_docs/inventory.py +474 -0
  37. clean_docs/isolation.py +157 -0
  38. clean_docs/manifest.py +898 -0
  39. clean_docs/mdx.py +272 -0
  40. clean_docs/migration.py +121 -0
  41. clean_docs/models.py +194 -0
  42. clean_docs/outcomes.py +296 -0
  43. clean_docs/performance.py +123 -0
  44. clean_docs/phrasing.py +448 -0
  45. clean_docs/plugins.py +249 -0
  46. clean_docs/policy.py +536 -0
  47. clean_docs/projections.py +255 -0
  48. clean_docs/regions.py +75 -0
  49. clean_docs/release.py +232 -0
  50. clean_docs/renderers.py +57 -0
  51. clean_docs/residue.py +311 -0
  52. clean_docs/review_contracts.py +862 -0
  53. clean_docs/review_ledger.py +297 -0
  54. clean_docs/review_limits.py +9 -0
  55. clean_docs/sensitivity.py +602 -0
  56. clean_docs/snapshot.py +212 -0
  57. clean_docs/standard.py +281 -0
  58. clean_docs/standards/default.json +309 -0
  59. clean_docs/standards/exemplars.md +87 -0
  60. clean_docs/standards/v0-migrations.json +26 -0
  61. clean_docs/symbols.py +47 -0
  62. clean_docs/templates.py +96 -0
  63. clean_docs/verdict.py +1397 -0
  64. clean_docs/visuals.py +346 -0
  65. clean_docs/write_gate.py +170 -0
  66. sourcebound-1.2.1.dist-info/LICENSE +21 -0
  67. sourcebound-1.2.1.dist-info/METADATA +109 -0
  68. sourcebound-1.2.1.dist-info/RECORD +71 -0
  69. sourcebound-1.2.1.dist-info/WHEEL +5 -0
  70. sourcebound-1.2.1.dist-info/entry_points.txt +2 -0
  71. sourcebound-1.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,297 @@
1
+ """Validate the append-only denominator for review observations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import re
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from clean_docs.errors import ConfigurationError, PolicyError
13
+ from clean_docs.improvements import ImprovementCandidateSet
14
+ from clean_docs.regions import atomic_write
15
+
16
+
17
+ REVIEW_EVENT_LEDGER_SCHEMA = "sourcebound.review-event-ledger.v2"
18
+ _LEGACY_REVIEW_EVENT_LEDGER_SCHEMA = "sourcebound.review-event-ledger.v1"
19
+ _SHA256 = re.compile(r"[0-9a-f]{64}")
20
+ _IDENTIFIER = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class ReviewEvent:
25
+ id: str
26
+ observation_id: str
27
+ disposition: str
28
+ candidate_id: str | None
29
+ successor: str | None
30
+ previous_digest: str | None
31
+ digest: str
32
+
33
+
34
+ def _object(value: Any, where: str) -> dict[str, Any]:
35
+ if not isinstance(value, dict):
36
+ raise ConfigurationError(f"{where} must be an object")
37
+ return value
38
+
39
+
40
+ def _string(value: Any, where: str) -> str:
41
+ if not isinstance(value, str) or not value.strip():
42
+ raise ConfigurationError(f"{where} must be a non-empty string")
43
+ return value
44
+
45
+
46
+ def _identifier(value: Any, where: str) -> str:
47
+ output = _string(value, where)
48
+ if not _IDENTIFIER.fullmatch(output):
49
+ raise ConfigurationError(f"{where} must be kebab-case")
50
+ return output
51
+
52
+
53
+ def _digest(payload: dict[str, Any]) -> str:
54
+ return hashlib.sha256(
55
+ json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
56
+ ).hexdigest()
57
+
58
+
59
+ def _event_digest(event: ReviewEvent) -> str:
60
+ return _digest(
61
+ {
62
+ "id": event.id,
63
+ "observation_id": event.observation_id,
64
+ "disposition": event.disposition,
65
+ "candidate_id": event.candidate_id,
66
+ "successor": event.successor,
67
+ "previous_digest": event.previous_digest,
68
+ }
69
+ )
70
+
71
+
72
+ def _event(value: Any, index: int) -> ReviewEvent:
73
+ where = f"review event ledger.events[{index}]"
74
+ raw = _object(value, where)
75
+ expected = {
76
+ "id",
77
+ "observation_id",
78
+ "disposition",
79
+ "candidate_id",
80
+ "successor",
81
+ "previous_digest",
82
+ "digest",
83
+ }
84
+ if set(raw) != expected:
85
+ raise ConfigurationError(f"{where} must contain exactly: {', '.join(sorted(expected))}")
86
+ disposition = _string(raw["disposition"], f"{where}.disposition")
87
+ if disposition not in {"candidate", "superseded", "merged"}:
88
+ raise ConfigurationError(f"{where}.disposition is invalid")
89
+ candidate_id = raw["candidate_id"]
90
+ successor = raw["successor"]
91
+ if disposition == "candidate":
92
+ candidate_id = _string(candidate_id, f"{where}.candidate_id")
93
+ if not _SHA256.fullmatch(candidate_id):
94
+ raise ConfigurationError(f"{where}.candidate_id must be a SHA-256")
95
+ if successor is not None:
96
+ raise ConfigurationError(f"{where}.successor must be null for a candidate")
97
+ else:
98
+ if candidate_id is not None:
99
+ raise ConfigurationError(f"{where}.candidate_id must be null for {disposition}")
100
+ successor = _identifier(successor, f"{where}.successor")
101
+ previous_digest = raw["previous_digest"]
102
+ if previous_digest is not None:
103
+ previous_digest = _string(previous_digest, f"{where}.previous_digest")
104
+ if not _SHA256.fullmatch(previous_digest):
105
+ raise ConfigurationError(f"{where}.previous_digest must be a SHA-256 or null")
106
+ event = ReviewEvent(
107
+ id=_identifier(raw["id"], f"{where}.id"),
108
+ observation_id=_identifier(raw["observation_id"], f"{where}.observation_id"),
109
+ disposition=disposition,
110
+ candidate_id=candidate_id,
111
+ successor=successor,
112
+ previous_digest=previous_digest,
113
+ digest=_string(raw["digest"], f"{where}.digest"),
114
+ )
115
+ if event.digest != _event_digest(event):
116
+ raise ConfigurationError(f"{where}.digest does not match its content")
117
+ return event
118
+
119
+
120
+ @dataclass(frozen=True)
121
+ class ReviewEventLedger:
122
+ schema: str
123
+ review_id: str
124
+ events: tuple[ReviewEvent, ...]
125
+ head_digest: str
126
+ migration_base_head_digest: str | None
127
+
128
+ def as_dict(self) -> dict[str, object]:
129
+ return {
130
+ "schema": self.schema,
131
+ "review_id": self.review_id,
132
+ "events": [
133
+ {
134
+ "id": event.id,
135
+ "observation_id": event.observation_id,
136
+ "disposition": event.disposition,
137
+ "candidate_id": event.candidate_id,
138
+ "successor": event.successor,
139
+ "previous_digest": event.previous_digest,
140
+ "digest": event.digest,
141
+ }
142
+ for event in self.events
143
+ ],
144
+ "head_digest": self.head_digest,
145
+ "migration_base_head_digest": self.migration_base_head_digest,
146
+ }
147
+
148
+
149
+ def _load_review_event_ledger(path: Path) -> ReviewEventLedger:
150
+ """Load one internally consistent ledger without assigning candidate authority."""
151
+ try:
152
+ raw = json.loads(path.read_text(encoding="utf-8"))
153
+ except (OSError, json.JSONDecodeError) as exc:
154
+ raise ConfigurationError(f"cannot read review event ledger {path}: {exc}") from exc
155
+ root = _object(raw, "review event ledger")
156
+ schema = _string(root.get("schema"), "review event ledger.schema")
157
+ if schema == REVIEW_EVENT_LEDGER_SCHEMA:
158
+ expected_keys = {
159
+ "schema",
160
+ "review_id",
161
+ "events",
162
+ "head_digest",
163
+ "migration_base_head_digest",
164
+ }
165
+ elif schema == _LEGACY_REVIEW_EVENT_LEDGER_SCHEMA:
166
+ expected_keys = {"schema", "review_id", "events", "head_digest"}
167
+ else:
168
+ raise ConfigurationError(
169
+ "review event ledger schema must be "
170
+ f"{REVIEW_EVENT_LEDGER_SCHEMA} or {_LEGACY_REVIEW_EVENT_LEDGER_SCHEMA}"
171
+ )
172
+ if set(root) != expected_keys:
173
+ raise ConfigurationError("review event ledger has an invalid shape")
174
+ review_id = _identifier(root["review_id"], "review event ledger.review_id")
175
+ if not isinstance(root["events"], list) or not root["events"]:
176
+ raise ConfigurationError("review event ledger.events must be a non-empty list")
177
+ events = tuple(_event(item, index) for index, item in enumerate(root["events"]))
178
+ seen_ids: set[str] = set()
179
+ by_observation: dict[str, ReviewEvent] = {}
180
+ previous: str | None = None
181
+ for event in events:
182
+ if event.id in seen_ids:
183
+ raise ConfigurationError(f"duplicate review event id: {event.id}")
184
+ seen_ids.add(event.id)
185
+ if event.previous_digest != previous:
186
+ raise ConfigurationError("review event ledger chain is not append-only")
187
+ previous = event.digest
188
+ if event.observation_id in by_observation:
189
+ raise ConfigurationError(f"duplicate review event observation id: {event.observation_id}")
190
+ by_observation[event.observation_id] = event
191
+ if root["head_digest"] != previous:
192
+ raise ConfigurationError("review event ledger.head_digest does not match the event chain")
193
+ assert previous is not None
194
+ migration_base_head_digest = root.get("migration_base_head_digest")
195
+ if migration_base_head_digest is not None:
196
+ migration_base_head_digest = _string(
197
+ migration_base_head_digest,
198
+ "review event ledger.migration_base_head_digest",
199
+ )
200
+ if not _SHA256.fullmatch(migration_base_head_digest):
201
+ raise ConfigurationError(
202
+ "review event ledger.migration_base_head_digest must be a SHA-256"
203
+ )
204
+ return ReviewEventLedger(
205
+ schema=schema,
206
+ review_id=review_id,
207
+ events=events,
208
+ head_digest=previous,
209
+ migration_base_head_digest=migration_base_head_digest,
210
+ )
211
+
212
+
213
+ def validate_review_event_ledger(
214
+ path: Path,
215
+ candidates: ImprovementCandidateSet,
216
+ *,
217
+ prior_path: Path | None = None,
218
+ ) -> str:
219
+ """Require one current disposition and preserve the base ledger as a prefix."""
220
+ ledger = _load_review_event_ledger(path)
221
+ if ledger.review_id != candidates.review_id:
222
+ raise PolicyError("review event ledger belongs to another review")
223
+ if prior_path is not None:
224
+ prior = _load_review_event_ledger(prior_path)
225
+ if prior.review_id != ledger.review_id:
226
+ raise PolicyError("prior review event ledger belongs to another review")
227
+ if prior.schema == REVIEW_EVENT_LEDGER_SCHEMA and ledger.schema != prior.schema:
228
+ raise PolicyError("review event ledger cannot downgrade from the anchored schema")
229
+ if ledger.schema == REVIEW_EVENT_LEDGER_SCHEMA and prior.schema == _LEGACY_REVIEW_EVENT_LEDGER_SCHEMA:
230
+ if ledger.migration_base_head_digest != prior.head_digest:
231
+ raise PolicyError("review event ledger migration does not bind the legacy head")
232
+ else:
233
+ if ledger.events[: len(prior.events)] != prior.events:
234
+ raise PolicyError("review event ledger rewrites the immutable base history")
235
+ if ledger.migration_base_head_digest != prior.migration_base_head_digest:
236
+ raise PolicyError("review event ledger rewrites its migration anchor")
237
+ events = ledger.events
238
+ by_observation = {event.observation_id: event for event in events}
239
+ candidate_ids = {candidate.observation_id: candidate.id for candidate in candidates.candidates}
240
+ for observation_id, candidate_id in candidate_ids.items():
241
+ ledger_event = by_observation.get(observation_id)
242
+ if ledger_event is None:
243
+ raise PolicyError(f"review event ledger is missing observation: {observation_id}")
244
+ if ledger_event.disposition != "candidate" or ledger_event.candidate_id != candidate_id:
245
+ raise PolicyError(f"review event ledger retargets observation: {observation_id}")
246
+ for event in events:
247
+ if event.disposition == "candidate" and event.observation_id not in candidate_ids:
248
+ raise PolicyError(f"review candidate is silently removed from the review: {event.observation_id}")
249
+ if event.disposition != "candidate":
250
+ successor = by_observation.get(event.successor or "")
251
+ if successor is None or successor.disposition != "candidate":
252
+ raise PolicyError(f"review event {event.id} has no candidate successor")
253
+ return events[-1].digest
254
+
255
+
256
+ def initialize_review_event_ledger(
257
+ candidates: ImprovementCandidateSet,
258
+ ) -> ReviewEventLedger:
259
+ """Start a version 2 ledger from one compiled candidate denominator."""
260
+ events: list[ReviewEvent] = []
261
+ previous_digest: str | None = None
262
+ for candidate in candidates.candidates:
263
+ event = ReviewEvent(
264
+ id=f"event-{candidate.observation_id}",
265
+ observation_id=candidate.observation_id,
266
+ disposition="candidate",
267
+ candidate_id=candidate.id,
268
+ successor=None,
269
+ previous_digest=previous_digest,
270
+ digest="",
271
+ )
272
+ event = ReviewEvent(
273
+ id=event.id,
274
+ observation_id=event.observation_id,
275
+ disposition=event.disposition,
276
+ candidate_id=event.candidate_id,
277
+ successor=event.successor,
278
+ previous_digest=event.previous_digest,
279
+ digest=_event_digest(event),
280
+ )
281
+ events.append(event)
282
+ previous_digest = event.digest
283
+ if not events:
284
+ raise ConfigurationError("review event ledger requires at least one candidate")
285
+ return ReviewEventLedger(
286
+ schema=REVIEW_EVENT_LEDGER_SCHEMA,
287
+ review_id=candidates.review_id,
288
+ events=tuple(events),
289
+ head_digest=events[-1].digest,
290
+ migration_base_head_digest=None,
291
+ )
292
+
293
+
294
+ def write_review_event_ledger(ledger: ReviewEventLedger, output: Path) -> Path:
295
+ """Write one initial review ledger atomically."""
296
+ atomic_write(output, json.dumps(ledger.as_dict(), indent=2, ensure_ascii=False) + "\n")
297
+ return output
@@ -0,0 +1,9 @@
1
+ """Fixed work limits for observe-only review contracts."""
2
+
3
+ MAX_REVIEW_CONTRACTS = 64
4
+ MAX_REVIEW_LOCATORS_PER_CONTRACT = 32
5
+ MAX_REVIEW_LOCATORS = 256
6
+ MAX_REVIEW_UNIQUE_PATHS = 128
7
+ MAX_REVIEW_FILE_BYTES = 1_000_000
8
+ MAX_REVIEW_TOTAL_BYTES = 16_000_000
9
+ MAX_REVIEW_STRUCTURED_NODES = 50_000