sourcecode 2.5.18__py3-none-any.whl → 2.5.19__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.
sourcecode/__init__.py CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "2.5.18"
7
+ __version__ = "2.5.19"
@@ -2475,10 +2475,20 @@ def _scan_guard_clauses(body: str) -> list[dict]:
2475
2475
 
2476
2476
 
2477
2477
  def _extract_guard_facts(symbols: list[SymbolRecord], source: str) -> dict[str, list[dict]]:
2478
- """Ordered `guard_clause` atoms per method/constructor FQN. Reuses the SAME
2479
- `_method_body` extraction as the other body facts; the scanner keeps only the
2480
- structural guard shape (β), on its own occurrence axis."""
2481
- callers = [s for s in symbols if s.symbol_kind in ("method", "constructor") and s.line]
2478
+ """Ordered `guard_clause` atoms per method/constructor/endpoint FQN. Reuses the
2479
+ SAME `_method_body` extraction as the other body facts; the scanner keeps only
2480
+ the structural guard shape (β), on its own occurrence axis.
2481
+
2482
+ Endpoint-kind bodies are included (F2): an annotation-classified handler
2483
+ (`@PostMapping …`, symbol_kind="endpoint") is still a method body, and a guard
2484
+ that throws on a bad request parameter (`if (x == null || x.isBlank()) throw`)
2485
+ is exactly the manual-validation shape the validation surface must see. The
2486
+ scope was previously method/constructor only, which silently dropped every
2487
+ guard living in a handler body — the atom exists, the coverage did not."""
2488
+ callers = [
2489
+ s for s in symbols
2490
+ if s.symbol_kind in ("method", "constructor", "endpoint") and s.line
2491
+ ]
2482
2492
  if not callers:
2483
2493
  return {}
2484
2494
  raw_lines = source.splitlines()
@@ -15,20 +15,23 @@ VAI note (§0.5): the only names this module keys on are **published open
15
15
  standards** (`jakarta.validation`, `javax.validation`, the bean-validation
16
16
  annotation vocabulary). No client / convention name enters a predicate.
17
17
 
18
- ARCHITECTURAL DIVERGENCE — manual_validation (design §2, deferred)
19
- -----------------------------------------------------------------
20
- Design §2 also asks for a `manual_validation` class detected by the **β
21
- GuardClause** shape on handler bodies (a guard that throws on a bad parameter).
22
- That path assumes guard atoms cover handlers. They do NOT: `_extract_guard_facts`
23
- (repository_ir.py) scans only `symbol_kind in {"method", "constructor"}`, and a
24
- mapped handler surfaces as kind `"endpoint"` so `guard_facts` is empty for
25
- every handler body. Design §8 classifies §2 as *Incremental / no new primitive*;
26
- against the current architecture that is false. Rather than smuggle in an IR
27
- change under an "Incremental" increment, `manual_validation` is deferred and the
28
- conflict is documented in the design doc (EPV / INV-2: build the endpoint-scoped
29
- guard capability only when this consumer is ratified). The three classes that ARE
30
- reuse-only (active / available-unused / none) ship here and already resolve the
31
- field defect.
18
+ manual_validation (design §2) — RESOLVED (F2, roadmap Engineering Decision Support)
19
+ -----------------------------------------------------------------------------------
20
+ Design §2 asks for a `manual_validation` class detected by the **β GuardClause**
21
+ shape on handler bodies (a guard that throws/returns on a bad parameter). This was
22
+ previously deferred because `_extract_guard_facts` scanned only
23
+ `symbol_kind in {"method","constructor"}` and a mapped handler surfaces as kind
24
+ `"endpoint"`, so `guard_facts` was empty for every handler body (EPV/INV-2: build
25
+ the endpoint-scoped guard capability only when this consumer is ratified).
26
+
27
+ Roadmap F2 ratifies the consumer. `_extract_guard_facts` now includes endpoint-kind
28
+ bodies (repository_ir.py) the SAME β scan widened to a symbol_kind it silently
29
+ skipped, no new atom type so a handler's entry guards are visible here. This
30
+ module now classifies `manual_validation` from those atoms, scoped to
31
+ null/empty-check guards (β's `other` test_kind lumps range checks with
32
+ non-validation guards like memoization/preconditions, so counting it would inflate
33
+ false positives; range validation is therefore not separately claimed — an honest
34
+ under-report, labelled Likely never Verified).
32
35
  """
33
36
  from __future__ import annotations
34
37
 
@@ -64,6 +67,30 @@ _VALIDATE_MARKERS: frozenset[str] = frozenset({"Valid", "Validated"})
64
67
  # Body-carrying HTTP verbs — the surface where request-body validation applies.
65
68
  _BODY_VERBS: frozenset[str] = frozenset({"POST", "PUT", "PATCH"})
66
69
 
70
+ # β GuardClause test kinds that read as manual input validation (F2). `other`
71
+ # is excluded on purpose: at β it lumps range checks with non-validation guards
72
+ # (memoization, preconditions), so it cannot be claimed as validation.
73
+ _MANUAL_GUARD_TESTS: frozenset[str] = frozenset({"null_check", "empty_check"})
74
+
75
+
76
+ def _manual_guarded_handlers(
77
+ cir: "CanonicalRepositoryIR", handler_symbols: "set[str]"
78
+ ) -> set[str]:
79
+ """Handler symbols carrying ≥1 null/empty β GuardClause that early-exits.
80
+
81
+ Every β atom already early-exits (return/throw — `_scan_guard_clauses` records
82
+ no other shape), so presence of a null/empty atom on a handler body IS the
83
+ manual-validation signal. Scoped to the handlers actually on the body surface
84
+ so a guard in an unrelated method never counts."""
85
+ gf = cir.guard_facts
86
+ out: set[str] = set()
87
+ for h in handler_symbols:
88
+ for atom in gf.get(h, ()) or ():
89
+ if atom.get("test_kind") in _MANUAL_GUARD_TESTS:
90
+ out.add(h)
91
+ break
92
+ return out
93
+
67
94
 
68
95
  def _validation_available(cir: "CanonicalRepositoryIR") -> tuple[bool, list[str]]:
69
96
  """True when a published bean-validation framework is on the classpath,
@@ -133,10 +160,11 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
133
160
 
134
161
  Repo-level class (DESIGN §2):
135
162
  - ``bean_validation_active`` — @Valid present on body handlers.
163
+ - ``manual_validation`` — no @Valid, but body handlers guard
164
+ their input with null/empty β guard clauses that early-exit (Likely).
136
165
  - ``bean_validation_available_unused`` — validation on the classpath and
137
166
  body endpoints exist, but no handler validates → risk (Likely).
138
167
  - ``no_validation_detected`` — none of the above (Unknown).
139
- (``manual_validation`` deferred — see module docstring.)
140
168
 
141
169
  Never raises.
142
170
  """
@@ -145,15 +173,30 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
145
173
 
146
174
  body_eps = [ep for ep in cir.endpoints if str(ep.method).upper() in _BODY_VERBS]
147
175
  body_total = len(body_eps)
176
+ body_handlers = {ep.handler_symbol for ep in body_eps}
177
+ manual_handlers = _manual_guarded_handlers(cir, body_handlers)
148
178
  validated_body = sum(1 for ep in body_eps if ep.handler_symbol in validated_handlers)
179
+ # manual guards only "count" where bean validation is absent — a handler that
180
+ # both @Valid-s and hand-guards is bean-validated, not manual.
181
+ manual_body = sum(
182
+ 1 for ep in body_eps
183
+ if ep.handler_symbol in manual_handlers
184
+ and ep.handler_symbol not in validated_handlers
185
+ )
149
186
  unvalidated_body = body_total - validated_body
150
187
 
151
188
  # per-endpoint findings (only the body surface — where a request body applies)
152
189
  endpoint_findings: list[dict] = []
153
- rollup = {"validated": 0, "available_unused": 0, "unvalidated_unknown": 0}
190
+ rollup = {
191
+ "validated": 0, "manual_guard": 0,
192
+ "available_unused": 0, "unvalidated_unknown": 0,
193
+ }
154
194
  for ep in body_eps:
155
195
  if ep.handler_symbol in validated_handlers:
156
196
  klass, conf = "validated", Confidence.VERIFIED
197
+ elif ep.handler_symbol in manual_handlers:
198
+ # hand-written null/empty guard on the handler body → manual validation
199
+ klass, conf = "manual_guard", Confidence.LIKELY
157
200
  elif available:
158
201
  # @RequestBody-shaped verb, framework available, no @Valid → gap
159
202
  klass, conf = "available_unused", Confidence.LIKELY
@@ -175,11 +218,11 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
175
218
  "the exact @RequestBody parameter count is not captured (no IR atom "
176
219
  "exposes it), so 'available_unused' may include body-less handlers."
177
220
  )
178
- manual_fn = (
179
- "manual_validation (a handler that throws on a bad parameter) is not "
180
- "detected guard-clause atoms do not cover endpoint-kind handlers "
181
- "(architectural defer, see module docstring); such a repo classifies "
182
- "no_validation_detected here."
221
+ manual_range_fn = (
222
+ "manual_validation is detected from null/empty β guard clauses only; "
223
+ "range/other-shaped guards (e.g. `if (n < 0) throw`) are indistinguishable "
224
+ "at β from non-validation guards and are not counted, so a purely "
225
+ "range-guarded handler can under-report."
183
226
  )
184
227
 
185
228
  if validated_body > 0:
@@ -193,11 +236,38 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
193
236
  "validated_body_endpoints": validated_body,
194
237
  "body_endpoints": body_total,
195
238
  "unvalidated_body_endpoints": unvalidated_body,
239
+ "manual_guarded_body_endpoints": manual_body,
196
240
  "validation_available": available,
197
241
  },
198
242
  ),
199
243
  limitations=[common_limitation],
200
- fn_causes=[manual_fn],
244
+ fn_causes=[manual_range_fn],
245
+ )
246
+ elif manual_body > 0:
247
+ pattern = "manual_validation"
248
+ verdict = Verdict(
249
+ claim="Request bodies validated by hand-written guard clauses (no bean validation)",
250
+ confidence=Confidence.LIKELY,
251
+ evidence=Evidence(
252
+ atoms_used=[f"null/empty guard: {h}" for h in sorted(manual_handlers)][:50],
253
+ details={
254
+ "body_endpoints": body_total,
255
+ "manual_guarded_body_endpoints": manual_body,
256
+ "validated_body_endpoints": 0,
257
+ "validation_available": available,
258
+ },
259
+ ),
260
+ limitations=[
261
+ common_limitation,
262
+ "A null/empty guard is a likely input-validation signal, not a "
263
+ "proof — it could defend an internal invariant unrelated to the "
264
+ "request contract.",
265
+ ],
266
+ fp_causes=[
267
+ "A null/empty guard may protect against an internal null rather "
268
+ "than validate the request body.",
269
+ ],
270
+ fn_causes=[manual_range_fn],
201
271
  )
202
272
  elif available and body_total > 0:
203
273
  pattern = "bean_validation_available_unused"
@@ -222,7 +292,7 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
222
292
  "A body endpoint may be intentionally unvalidated (idempotent "
223
293
  "echo, internal-only), or validated manually.",
224
294
  ],
225
- fn_causes=[manual_fn],
295
+ fn_causes=[manual_range_fn],
226
296
  )
227
297
  else:
228
298
  pattern = "no_validation_detected"
@@ -238,7 +308,7 @@ def infer_validation_pattern(cir: "CanonicalRepositoryIR") -> ValidationInferenc
238
308
  "validation import, and no body endpoints, or validation is done "
239
309
  "by a mechanism this projection does not model.",
240
310
  ],
241
- fn_causes=[manual_fn],
311
+ fn_causes=[manual_range_fn],
242
312
  )
243
313
 
244
314
  return ValidationInferenceResult(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 2.5.18
3
+ Version: 2.5.19
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  License-File: LICENSE
6
6
  Keywords: agents,ai,codebase,context,developer-tools,llm
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=wslvpdjMsUV-sx7b3v1cpnzhBqIUNoLGcCwm8IheiSM,309
1
+ sourcecode/__init__.py,sha256=lv_mMr5Y5s1U1_CJa-6pkjbnlCz4lXMFrxs9SHb4qJs,309
2
2
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
3
3
  sourcecode/archetype.py,sha256=d7yoN6Tj4OBj-VgSMPEKg4WF9H8FypnZ5A6AkUh5oIE,34484
4
4
  sourcecode/architecture_analyzer.py,sha256=GFc4ek-s1IHWM7pl-0L32WahZ93AmDgrAMcOuBKA5Dk,61463
@@ -57,7 +57,7 @@ sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
57
57
  sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
58
58
  sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
59
59
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
60
- sourcecode/repository_ir.py,sha256=Q2lMpm5x8ZAIY4MVKWEcLZ-BP26kUoP1dRfdER6gSbo,312316
60
+ sourcecode/repository_ir.py,sha256=2Za5B4ar0iqPkdlHHttmhcHO4ErTXmg8TyOY3tAO2CA,312842
61
61
  sourcecode/ris.py,sha256=Hw8TakTQ6hku-Abf2k8954NwkrH_sP73_8wVH8x5khc,22079
62
62
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
63
63
  sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
@@ -79,7 +79,7 @@ sourcecode/spring_tx_analyzer.py,sha256=7qXwQR9QbpVxbxJC-mawBMXM9xzydtZeq5L18cP_
79
79
  sourcecode/summarizer.py,sha256=sr0-tfecFKCr-fSkPPWbl-t9HC7SY2ZxkjnnXX8DB2A,26621
80
80
  sourcecode/tree_utils.py,sha256=8GAkIfQAsvtEudIeW1l4ooH_oRtrWR8cpJQJsEa_Pfw,2093
81
81
  sourcecode/type_usage_surface.py,sha256=51IrKRQoIoRnlsiDjHnqpJBn2rc6E59aRhgS0HTzAF0,4428
82
- sourcecode/validation_inference.py,sha256=UtmYv-d4TEzT1ihGEipAhtXDrwq0XtVtjMpb3i7NsNc,10815
82
+ sourcecode/validation_inference.py,sha256=cQaEJxCBDRWcD0On6PapoGb4qOTEPPlJ1eD4raFHWp0,14099
83
83
  sourcecode/validation_surface.py,sha256=A0-FVswL__XOcw92wvIwghwLq5AWtIm2kzO3RjEb1b4,27325
84
84
  sourcecode/version_check.py,sha256=CHp6ZxTIfo8kyHPCBgJA1uFC0xQCoXMuuOfrW8QTL8o,4942
85
85
  sourcecode/workspace.py,sha256=X_6NmNnitvT3_38V-JDChydo_sR68s249hLFlrQskU0,8271
@@ -138,8 +138,8 @@ sourcecode/telemetry/consent.py,sha256=LIAO9ohJZF8OuZwM4u1VWtALlYfTCCKq4wV3Vwc7i
138
138
  sourcecode/telemetry/events.py,sha256=LtzYfaX9Ilckj5PTvAcTpDa9mLqDsYPDUiDkRa58piY,2580
139
139
  sourcecode/telemetry/filters.py,sha256=NHa5T-6DaZduQPFuC34jOqHWQgSizM-Ygq8aZ4j19ng,5834
140
140
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
141
- sourcecode-2.5.18.dist-info/METADATA,sha256=Er3B54p5axMRW7yoIsZmmGYvVCH_uILX7C4ge3mMQj0,10852
142
- sourcecode-2.5.18.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
143
- sourcecode-2.5.18.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
144
- sourcecode-2.5.18.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
145
- sourcecode-2.5.18.dist-info/RECORD,,
141
+ sourcecode-2.5.19.dist-info/METADATA,sha256=m52i-fTO7exXgHCc5tUJyv2ne4dBOOAtKThagdx4dSo,10852
142
+ sourcecode-2.5.19.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
143
+ sourcecode-2.5.19.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
144
+ sourcecode-2.5.19.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
145
+ sourcecode-2.5.19.dist-info/RECORD,,