sourcecode 5.8.9__py3-none-any.whl → 5.8.10__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.

Potentially problematic release.


This version of sourcecode might be problematic. Click here for more details.

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__ = "5.8.9"
7
+ __version__ = "5.8.10"
@@ -16,6 +16,21 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
16
16
 
17
17
  ## Current Synchronization
18
18
 
19
+ **As of 2026-08-19, the `5.8.10` release commit:** this is the current status of
20
+ the 5.8.9 re-audit queue. The audit observations below remain historical evidence;
21
+ only the table here declares present state.
22
+
23
+ | Rows | Current status | Evidence |
24
+ |---|---|---|
25
+ | `AUD-588-B01`, `AUD-589-B01`, `C3-122` | **closed** | `20596c8`; failed writes cannot suppress the global output-promise guard, covering both `risk` and `audit-report` emit paths. |
26
+ | `AUD-588-B03` | **closed** | `16a52cc`; `SEC-009`, static `@Profile` evidence and `permit_all_rules` are emitted with regressions. |
27
+ | `AUD-588-B04` | **closed** | `fd34eb1`; `data-exposure` preserves the chain decision for every route. |
28
+ | `AUD-588-B05` | **closed** | `c15f5b6`; changed container-wired components block a false `unaffected` disposition. |
29
+ | `AUD-588-B07` | **closed** | `5cc6ba7`; ASK-09 reads the envelope error contract and covers the wrapped shape. |
30
+ | `AUD-588-B08` | **closed** | `c084555`; `impact` publishes its independent stdout-byte budget. |
31
+ | `AUD-588-B11` (container/units subset) | **closed** | `ca72b61`, `a49cf6a`; plan/compare publish container wiring and impact-chain names security-surface units. |
32
+ | `AUD-588-B02`, `B06`, B11 residuals, `B12` | **open** | Require provider non-coverage verification, remaining contract parity and controlled cost calibration; no closure is inferred from the release. |
33
+
19
34
  **As of 2026-08-19, the `5.8.9` release commit:** the latest audit queue was implemented in
20
35
  committed post-5.8.7 fixes. This table is
21
36
  the authoritative status for round 11; detailed rows retain their original findings and
@@ -60,15 +75,36 @@ to the closed round-11 queue and are not marked closed until reproduced and cove
60
75
  | `AUD-588-B11` | Medium | Seven cross-command contract inconsistencies mix naming, units, container-wired warnings, build discovery, populations and tier claims. | **open — solve through shared fact/units authorities** |
61
76
  | `AUD-588-B12` | Low/Medium | Cost model is stale by up to 1.74x at warm cache and long commands can be silent until timeout. | **open — remeasure 5.8.8 and add default foreground progress** |
62
77
 
78
+ ### Re-audit Intake: 5.8.9
79
+
80
+ The same external auditor re-ran the same `saint-server` snapshot under the same
81
+ read-only protocol against 5.8.9. Score: **80/100** (75 previously), still **53/61
82
+ invocables (87%)**. The CIR remained byte-identical. This reopens false closures and
83
+ adds one newly observable zero-output path; it does not duplicate the rows above.
84
+
85
+ | ID / lineage | Status | Evidence and next action |
86
+ |---|---|---|
87
+ | `AUD-588-B01` / `C3-122` | **reopened, critical** | `risk` exits 0 with empty stdout and can omit a promised `-o` file. Fix early/partial composition and enforce a typed failure or parseable answer through the real entry point. |
88
+ | `AUD-588-B02` + `B06` | **open, critical** | EclipseLink still receives a Hibernate axis and `non_coverage` is null. Make `not_applicable_reason` authoritative for applicability, effort and aggregate; publish provider non-coverage. |
89
+ | `AUD-588-B03` | **partial** | `permitAll` evidence and profiles are present, but no rule or per-finding activation state exists; rename `active_permit_all` to an honest per-entry contract. |
90
+ | `AUD-588-B04` | **partial** | `explain-endpoint` is fixed; `data-exposure` still reports `coverage_unknown` for chain-permitted routes. |
91
+ | `AUD-588-B05` | **regression** | `unaffected_basis` claims no blocker while container-wired changes are present. Demote routes or publish a blocker/caveat; preserve `affected + unknown + unaffected = total`. |
92
+ | `AUD-588-B07` | **partial** | Selftest publishes provenance but reads `error.code` at the wrong envelope level, producing a false `error` instead of `pass`. |
93
+ | `AUD-588-B08` | **partial** | `impact` still trims stdout at 50,000 bytes after the token ceiling is raised without naming that second budget. |
94
+ | `AUD-588-B11` | **open, six residuals** | `plan`/`compare` miss container reach; units, build discovery, populations and risk tier still diverge. |
95
+ | `AUD-588-B12` | **partial** | Timeline completes, but cost anchors are stale and long foreground commands remain silent by default. |
96
+ | `AUD-589-B01` | **new, high** | `audit-report` can exit 0 with empty stdout; its `-o` path diverges and leaves partial evidence when killed. Reuse the working `_partial` and output-promise contracts. |
97
+ | `AUD-589-B02` | **unconfirmed performance suspicion** | `data-exposure --config` was measured 2.76x slower with identical output; require convergent controlled measurements before code changes. |
98
+
63
99
  ### Recommended Features From This Audit
64
100
 
65
101
  | ID | Recommendation | Classification |
66
102
  |---|---|---|
67
- | `AUD-588-F01` | Explicit EclipseLink/JPA-provider migration axis, including namespace and coordinate changes. | **Deferred**; `non_coverage` is the honest current contract. |
68
- | `AUD-588-F02` | Shared answer-contract layer for units, populations, caveats and container-wired reach. | **Covered** by existing authorities and parity tests. |
69
- | `AUD-588-F03` | Versioned cost-model calibration by repository size and cache state. | **Covered** by `perf-baseline-v1` and cache/scale metadata. |
70
- | `AUD-588-F04` | Default progress/heartbeat for long foreground commands without contaminating machine-readable stdout. | **Covered as explicit opt-in** through `--progress`/`ASK_PROGRESS`. |
71
- | `AUD-588-F05` | Controlled audit-coverage expansion from 87% to 90% via safe `auth logout`, reversible `telemetry disable` and dirty-tree `verify-edit` checks. | **Verified** in controlled regression coverage; verification scope. |
103
+ | `AUD-588-F01` | Explicit EclipseLink/JPA-provider migration axis, including namespace and coordinate changes. | **Deferred**; its prerequisite provider `non_coverage` disclosure is still open. |
104
+ | `AUD-588-F02` | Shared answer-contract layer for units, populations, caveats and container-wired reach. | **Partially covered**; six consumers remain disconnected according to the re-audit. |
105
+ | `AUD-588-F03` | Versioned cost-model calibration by repository size and cache state. | **Partially covered**; schema and dimensions exist, but 5.1.0-era anchors are stale. |
106
+ | `AUD-588-F04` | Default progress/heartbeat for long foreground commands without contaminating machine-readable stdout. | **Explicit opt-in by decision**, not default coverage; update cost advice accordingly. |
107
+ | `AUD-588-F05` | Controlled audit-coverage expansion from 87% to 90% via safe `auth logout`, reversible `telemetry disable` and dirty-tree `verify-edit` checks. | **Locally verified only**; external audit remains at 87% with eight blocked invocables. |
72
108
 
73
109
  **Recommended fix order:** `B01`, `B02`, `B04`, `B03`, `B05/B11`, `B08/B09`, `B07`,
74
110
  then `B06/B10/B12`. The dirty-tree `verify-edit` path remains unverified, not confirmed
@@ -136,7 +136,7 @@ pipx install sourcecode # isolated install, no venv needed
136
136
 
137
137
  # Verify
138
138
  ask version
139
- # ask 5.8.9
139
+ # ask 5.8.10
140
140
  ```
141
141
 
142
142
  Requires Python 3.9+.
sourcecode/change_plan.py CHANGED
@@ -28,6 +28,7 @@ from typing import TYPE_CHECKING
28
28
 
29
29
  from sourcecode import non_coverage as _non_coverage
30
30
  from sourcecode.canonical_ir import project_blast_radius
31
+ from sourcecode.container_wiring import detect_classes
31
32
  from sourcecode.repository_ir import _all_callers_from_rg
32
33
  from sourcecode.security_posture import _graph_nodes
33
34
  from sourcecode.path_filters import is_test_path
@@ -139,6 +140,7 @@ def build_change_plan(
139
140
  }
140
141
 
141
142
  seeds = list(blast["matched_fqns"])
143
+ container_wired = [w.to_dict() for w in detect_classes(cir, seeds)]
142
144
  meta = _node_meta(cir)
143
145
  affected = _dependents_closure(cir.reverse_graph or {}, seeds, max_depth, _NODE_CAP)
144
146
 
@@ -221,6 +223,7 @@ def build_change_plan(
221
223
  "count": len(prod),
222
224
  "components": prod[:_LIST_CAP],
223
225
  },
226
+ "container_wired": container_wired,
224
227
  "covering_tests": (
225
228
  {
226
229
  "count": len(tests),
sourcecode/cli.py CHANGED
@@ -2112,12 +2112,14 @@ def _emit_command_output(
2112
2112
  raise typer.Exit(1)
2113
2113
  if output_path is not None:
2114
2114
  _receipt = _safe_write_file(output_path, content)
2115
- _mark_output_delivered()
2116
2115
  if not _receipt.get("written"):
2117
2116
  # B15: the answer was produced and thrown away, on request. Saying
2118
2117
  # "written to nul" would be the one thing worse than the error this
2119
2118
  # used to raise.
2120
2119
  typer.echo(_receipt.get("note") or f"Discarded to {output_path}")
2120
+ # A failed write is never delivery. The guard separately exempts
2121
+ # discard sinks and existing files by path, so no state flag is
2122
+ # needed here.
2121
2123
  elif success_msg:
2122
2124
  # C3-26/C3-18: the payload went to the file, so stdout is free and
2123
2125
  # this confirmation is the run's whole answer — `cache warm` and
@@ -2126,12 +2128,14 @@ def _emit_command_output(
2126
2128
  # stderr battery listed its commands by hand. Measured before the
2127
2129
  # fix on spring-petclinic: stdout 0 bytes, stderr 155.
2128
2130
  typer.echo(success_msg)
2131
+ _mark_output_delivered()
2129
2132
  else:
2130
2133
  # C3-122: a command whose emit seam carries no sentence of its own
2131
2134
  # confirmed a 444-second run with total silence. The receipt is a
2132
2135
  # notice, so a pipeline and a PowerShell capture stay exactly as
2133
2136
  # they were (C3-26) and a person at a terminal is told.
2134
2137
  _notice(f"✓ {_receipt.get('bytes', 0)} bytes written to {output_path}")
2138
+ _mark_output_delivered()
2135
2139
  else:
2136
2140
  try:
2137
2141
  sys.stdout.buffer.write(content.encode("utf-8"))
@@ -2154,7 +2158,8 @@ def _emit_command_output(
2154
2158
  _notice("✓ copied to clipboard")
2155
2159
  # The answer has been handed over. Any `done (Xs)` a command deferred is now
2156
2160
  # a true claim and prints here — never before this line (C3-54).
2157
- _progress_output_emitted()
2161
+ if _OUTPUT_DELIVERED:
2162
+ _progress_output_emitted()
2158
2163
 
2159
2164
 
2160
2165
  def _emit_text_output(
@@ -6983,6 +6988,16 @@ def impact_cmd(
6983
6988
  cir._raw_ir, target, max_depth=depth, list_limit=0 if _to_file else None,
6984
6989
  root=root, covered_types=_covered_types,
6985
6990
  )
6991
+ # Token ceilings and this command's byte budget are independent controls.
6992
+ # Publish both at the point where the impact answer is shaped so raising
6993
+ # ASK_MAX_OUTPUT_TOKENS cannot be mistaken for disabling stdout trimming.
6994
+ result["stdout_byte_budget"] = BUDGET_IMPACT if not _to_file else None
6995
+ result["stdout_byte_budget_basis"] = (
6996
+ "impact stdout rendering is capped independently at 50,000 UTF-8 bytes; "
6997
+ "ASK_MAX_OUTPUT_TOKENS does not disable this byte budget"
6998
+ if not _to_file else
6999
+ "No stdout byte budget applies when --output writes the complete answer."
7000
+ )
6986
7001
  result = _apply_budget(result, BUDGET_IMPACT, label="impact", output_path=output_path)
6987
7002
  if _misread and result.get("resolution") == "not_found":
6988
7003
  result["analysis_warnings"] = [*(result.get("analysis_warnings") or []), _misread]
sourcecode/compare.py CHANGED
@@ -262,6 +262,7 @@ def build_comparison(
262
262
  "resolution": resolution,
263
263
  "resolved": resolved,
264
264
  "cost": cost,
265
+ "container_wired": plan.get("container_wired") if resolved else None,
265
266
  "why": _evidence_trail(cost, blast, resolved, plan),
266
267
  }
267
268
  # C1-13/C2-1: when the walk was capped, some magnitudes are floors. The
@@ -83,6 +83,7 @@ class ExposedRoute:
83
83
  verdict: str
84
84
  access: Optional[str] = None
85
85
  access_decision: Optional[str] = None
86
+ chain_decision: Optional[str] = None
86
87
 
87
88
  def to_dict(self) -> dict:
88
89
  out = {
@@ -100,6 +101,8 @@ class ExposedRoute:
100
101
  if self.access is not None:
101
102
  out["access_under_profile"] = self.access
102
103
  out["access_decision"] = self.access_decision
104
+ if self.chain_decision is not None:
105
+ out["chain_decision"] = self.chain_decision
103
106
  return out
104
107
 
105
108
 
@@ -375,6 +378,10 @@ def _finish_route(
375
378
  verdict=verdict,
376
379
  access=access,
377
380
  access_decision=decision,
381
+ chain_decision=(
382
+ str(bound["chain_decision"])
383
+ if bound.get("chain_decision") is not None else None
384
+ ),
378
385
  )
379
386
 
380
387
 
sourcecode/pr_impact.py CHANGED
@@ -786,6 +786,23 @@ def _run_pr_impact_internal(
786
786
  # `ProductHandlerMapping → blCatalogService → ProductDaoImpl` chain, caught
787
787
  # structurally rather than by recognising a name.
788
788
  disposition_blockers: list[_disposition.Blocker] = []
789
+ changed_wired = [
790
+ item for item in wired
791
+ if set(getattr(item, "kinds", ()) or ())
792
+ & {"container_configuration", "interception_point"}
793
+ ]
794
+ if changed_wired:
795
+ _named = ", ".join(item.symbol for item in changed_wired[:3])
796
+ disposition_blockers.append(_disposition.Blocker(
797
+ reason=_disposition.REASON_CONTAINER_DISPATCH,
798
+ detail=(
799
+ f"{len(changed_wired)} changed container-wired component(s) "
800
+ f"({_named}{', …' if len(changed_wired) > 3 else ''}) are framework "
801
+ "dispatch points. Their route reach is not represented by the call "
802
+ "cone, so routes with no modeled path are unknown rather than "
803
+ "unaffected (NC-010)."
804
+ ),
805
+ ))
789
806
  wired_in_cone = detect_container_wired_classes(
790
807
  cir, sorted(cone_classes - class_fqns_set)
791
808
  )
@@ -56,6 +56,7 @@ RULES: tuple[Rule, ...] = (
56
56
  Rule("SEC-006", "SEC", "Cookie created without the Secure attribute"),
57
57
  Rule("SEC-007", "SEC", "Credential stored in a deployment descriptor"),
58
58
  Rule("SEC-008", "SEC", "SQL built by MyBatis string interpolation"),
59
+ Rule("SEC-009", "SEC", "Anonymous request-chain exception"),
59
60
  Rule("DEAD-001", "DEAD", "Security control present but disabled"),
60
61
  Rule("GATE-001", "GATE", "Custom AOP/security gate can be bypassed by proxy limits"),
61
62
  Rule("GATE-002", "GATE", "Custom gate advice appears to fail open"),
@@ -258,6 +258,28 @@ def _enclosing_type(source: str, offset: int, fallback: str) -> str:
258
258
  def _scan_java(source: str, rel: str) -> "list[SecurityConfigObservation]":
259
259
  out: "list[SecurityConfigObservation]" = []
260
260
 
261
+ # SEC-009 — an anonymous request-chain exception is a finding in its own
262
+ # right. Keep the matcher context requirement narrow so unrelated method
263
+ # names containing `permitAll()` do not become security findings.
264
+ permit = re.search(r"\bpermitAll\s*\(\s*\)", source)
265
+ if permit and re.search(
266
+ r"SecurityFilterChain|authorizeHttpRequests|authorizeRequests|"
267
+ r"requestMatchers|anyRequest",
268
+ source,
269
+ ):
270
+ out.append(SecurityConfigObservation(
271
+ rule="SEC-009",
272
+ file=rel,
273
+ line=_line_of(source, permit.start()),
274
+ symbol=_enclosing_type(source, permit.start(), Path(rel).stem),
275
+ evidence=permit.group(0),
276
+ detail=(
277
+ "The active source contains a request-chain rule that permits "
278
+ "anonymous access. Verify its matcher and profile condition; this "
279
+ "exception does not prove that the whole chain is public."
280
+ ),
281
+ ))
282
+
261
283
  # SEC-004 — a password stored under a fast unsalted digest.
262
284
  if any(tok in source for tok in _ENCODER_CONTEXT):
263
285
  for token in _WEAK_DIGESTS:
sourcecode/selftest.py CHANGED
@@ -144,7 +144,8 @@ def _check_ask09(subject: Subject) -> "dict[str, Any]":
144
144
  "probe_command": probe_command,
145
145
  "observed_payload_excerpt": observed_excerpt,
146
146
  }
147
- body = _payload_of(payload)
147
+ document = payload if isinstance(payload, dict) else {}
148
+ body = _payload_of(document)
148
149
  if code == 0:
149
150
  return _result(
150
151
  NOT_APPLICABLE,
@@ -153,7 +154,10 @@ def _check_ask09(subject: Subject) -> "dict[str, Any]":
153
154
  "needed to exercise it here",
154
155
  **evidence,
155
156
  )
156
- error = body.get("error")
157
+ # The output seam may wrap the command payload and keep the envelope error
158
+ # beside it. Read both shapes; dropping the envelope first turns a real
159
+ # OUTPUT_TOO_LARGE refusal into a false ERROR with code=None.
160
+ error = document.get("error") or body.get("error")
157
161
  error_body = error if isinstance(error, dict) else body
158
162
  if error_body.get("code") != "OUTPUT_TOO_LARGE":
159
163
  return _result(
@@ -163,7 +167,7 @@ def _check_ask09(subject: Subject) -> "dict[str, Any]":
163
167
  **evidence,
164
168
  )
165
169
  hint = str(error_body.get("hint") or "")
166
- basis = body.get("hint_basis") or {}
170
+ basis = document.get("hint_basis") or body.get("hint_basis") or {}
167
171
  declared = list(basis.get("declared_flags") or [])
168
172
  if "No smaller inline variant is available" in hint and declared:
169
173
  return _result(
@@ -1588,6 +1588,10 @@ class ImpactOrchestrator:
1588
1588
  "indirect_caller_symbol_count_unit": "indirect caller list entries",
1589
1589
  "caller_metric_note": CALLER_METRIC_RECONCILIATION,
1590
1590
  "endpoints_total": len(endpoints_affected),
1591
+ "security_surfaces_unit": (
1592
+ "endpoint rows in the impact chain; each row is one affected "
1593
+ "endpoint and carries its security posture verdict"
1594
+ ),
1591
1595
  "findings_in_chain": len(impact_findings),
1592
1596
  "findings_status": findings_status,
1593
1597
  "findings_status_note": (
@@ -508,6 +508,7 @@ class _SEC00XConfigurationWeaknesses:
508
508
  # A credential every reader of the repository already holds.
509
509
  "SEC-007": "high",
510
510
  "SEC-008": "high",
511
+ "SEC-009": "medium",
511
512
  # C3-101 (c), decided in 5.8.7 and recorded here rather than absorbed.
512
513
  # The row left this open on purpose after (a) and (b) shipped: 8 `high`
513
514
  # findings and 0 true positives is a reason to ask whether the severity
@@ -527,6 +528,7 @@ class _SEC00XConfigurationWeaknesses:
527
528
  "SEC-006": "Cookie created without the Secure attribute",
528
529
  "SEC-007": "Credential stored in a deployment descriptor",
529
530
  "SEC-008": "SQL built by MyBatis string interpolation",
531
+ "SEC-009": "Anonymous request-chain exception",
530
532
  "DEAD-001": "Security control present but disabled",
531
533
  }
532
534
  #: A pattern that emits more than one rule id declares them all, so a catalogue
@@ -560,6 +562,11 @@ class _SEC00XConfigurationWeaknesses:
560
562
  "an identifier such as an ORDER BY column, map it through a closed "
561
563
  "allowlist before it reaches the mapper."
562
564
  ),
565
+ "SEC-009": (
566
+ "Confirm the matcher is intentionally public under the applicable "
567
+ "profile, keep it as narrow as possible, and do not treat it as "
568
+ "evidence that unrelated routes are protected or public."
569
+ ),
563
570
  "DEAD-001": (
564
571
  "Decide explicitly: either restore the commented control and verify it in "
565
572
  "the active profile, or delete the dead block with a note explaining the "
@@ -590,6 +597,11 @@ class _SEC00XConfigurationWeaknesses:
590
597
 
591
598
  findings: list[SpringFinding] = []
592
599
  for obs in observations:
600
+ profile_condition, active_by_default = _profile_evidence(root, obs.file)
601
+ evidence = {"matched": obs.evidence, "line": obs.line, "rule": obs.rule}
602
+ if profile_condition is not None:
603
+ evidence["profile_condition"] = profile_condition
604
+ evidence["active_under_default_profiles"] = active_by_default
593
605
  findings.append(SpringFinding(
594
606
  id=SpringFinding.make_id(obs.rule, f"{obs.file}:{obs.line}"),
595
607
  pattern_id=obs.rule,
@@ -602,7 +614,7 @@ class _SEC00XConfigurationWeaknesses:
602
614
  symbol=obs.symbol,
603
615
  source_file=obs.file,
604
616
  first_line=obs.line,
605
- evidence={"matched": obs.evidence, "line": obs.line, "rule": obs.rule},
617
+ evidence=evidence,
606
618
  explanation=obs.detail,
607
619
  fix_hint=self._FIX.get(obs.rule, ""),
608
620
  limitations=[
@@ -618,6 +630,23 @@ def _line_of(source: str, offset: int) -> int:
618
630
  return source.count("\n", 0, max(0, offset)) + 1
619
631
 
620
632
 
633
+ def _profile_evidence(root: Optional[Path], rel: str) -> tuple[Optional[str], Optional[bool]]:
634
+ """Return static @Profile evidence without claiming runtime activation."""
635
+ if root is None:
636
+ return None, None
637
+ try:
638
+ source = (Path(root) / rel).read_text(encoding="utf-8", errors="replace")
639
+ except OSError:
640
+ return None, None
641
+ match = re.search(r"@Profile\s*\((?P<expr>[^)]*)\)", source)
642
+ if match is None:
643
+ return None, True
644
+ expr = match.group("expr").strip()
645
+ names = re.findall(r'"([^"]+)"', expr)
646
+ active_by_default = bool(names) and all(name.startswith("!") for name in names)
647
+ return expr, active_by_default
648
+
649
+
621
650
  _JAVA_METHOD_RE = re.compile(
622
651
  r"(?P<mods>(?:(?:public|protected|private|static|final|synchronized|abstract|native)\s+)*)"
623
652
  r"(?P<ret>[\w.$<>\[\], ?]+)\s+"
@@ -1296,7 +1325,7 @@ def run_security_audit(
1296
1325
  "profiles are static candidates, not proof of activation."
1297
1326
  ),
1298
1327
  }
1299
- _active_permit_all: list[dict[str, Any]] = []
1328
+ _permit_all_rules: list[dict[str, Any]] = []
1300
1329
  if root is not None:
1301
1330
  try:
1302
1331
  from sourcecode.spring_profiles import profile_population
@@ -1316,10 +1345,13 @@ def run_security_audit(
1316
1345
  continue
1317
1346
  live = blank_java_comments(source)
1318
1347
  for match in re.finditer(r"\bpermitAll\s*\(\s*\)", live):
1319
- _active_permit_all.append({
1348
+ profile_condition, active_by_default = _profile_evidence(root, rel)
1349
+ _permit_all_rules.append({
1320
1350
  "file": rel,
1321
1351
  "line": live[:match.start()].count("\n") + 1,
1322
1352
  "evidence": match.group(0),
1353
+ "profile_condition": profile_condition,
1354
+ "active_under_default_profiles": active_by_default,
1323
1355
  })
1324
1356
  except Exception as exc:
1325
1357
  _profile_state["population_error"] = str(exc)
@@ -1398,12 +1430,12 @@ def run_security_audit(
1398
1430
  ),
1399
1431
  "security_model": cir.metadata.get("security_model", "unknown"),
1400
1432
  "security_profiles": _profile_state,
1401
- "active_permit_all": {
1402
- "rules": _active_permit_all,
1403
- "count": len(_active_permit_all),
1433
+ "permit_all_rules": {
1434
+ "rules": _permit_all_rules,
1435
+ "count": len(_permit_all_rules),
1404
1436
  "note": (
1405
- "Rules are live source evidence; their runtime activation still "
1406
- "depends on the selected Spring profile."
1437
+ "Rules are live source evidence. Profile fields are static hints; "
1438
+ "runtime activation still depends on the selected Spring profile."
1407
1439
  ),
1408
1440
  },
1409
1441
  "analysis_time_ms": elapsed_ms,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.8.9
3
+ Version: 5.8.10
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/HarounDominique/sourcecode
6
6
  Project-URL: Repository, https://github.com/HarounDominique/sourcecode
@@ -49,7 +49,7 @@ Description-Content-Type: text/markdown
49
49
 
50
50
  **Context · Impact · Migration · Architecture · Review — everything from one structural model.**
51
51
 
52
- ![Version](https://img.shields.io/badge/version-5.8.9-blue)
52
+ ![Version](https://img.shields.io/badge/version-5.8.10-blue)
53
53
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
54
54
 
55
55
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -139,7 +139,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
139
139
  # pip / pipx
140
140
  pipx install sourcecode # or: pip install sourcecode
141
141
 
142
- ask version # ask 5.8.9 — and, on a build that has aged,
142
+ ask version # ask 5.8.10 — and, on a build that has aged,
143
143
  # how many releases have probably shipped since
144
144
  ```
145
145
 
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=SEXDKNw2zepQYde4aTeTqJMfLe9te9ysvUaPtO8es-8,308
1
+ sourcecode/__init__.py,sha256=N6jOxr62_YHW3o1udlxIduDuvRxhmGvpOs9L3F4lv28,309
2
2
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
3
3
  sourcecode/answer_bounds.py,sha256=_51_DCuYEUmI_xwUFxDMiprSavkNakkLiIBbPjevQ3M,9793
4
4
  sourcecode/archetype.py,sha256=uubguXP5ltQt25ACNVm9kX4KRTe7JCqkFfg4H9zckCo,40971
@@ -22,13 +22,13 @@ sourcecode/caller_metrics.py,sha256=--sFGDnIog_YGu9xZHMcB91F5xZakfaAKvy15xU54hg,
22
22
  sourcecode/caller_reach.py,sha256=M7Ffs5CtlimihfdSy3HtC4WpDP97bRFX15QYOlOfMJw,16626
23
23
  sourcecode/canonical_ir.py,sha256=UmCvjGIz7kyjcKdZFYZuxa9y02EfsmDGZj3kUEKzCvo,36200
24
24
  sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,12767
25
- sourcecode/change_plan.py,sha256=FAbLtexNThXp51STU-F2a_3AQd1LWd3NsKWMnMNJrPo,12333
25
+ sourcecode/change_plan.py,sha256=wJ896V27a3qb-2vcZhrcOSOqFAwCmEyzbhdbdxM2LlQ,12504
26
26
  sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
27
27
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
28
- sourcecode/cli.py,sha256=p5ZvOcCyuV-9YftPfyKAL2whUR2zPwWVAQ9k3-Eel0c,723455
28
+ sourcecode/cli.py,sha256=BV6v8OKXO6mW-lFuc7NX5Z2yPToTZnSRptxr7Qekmic,724322
29
29
  sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
30
30
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
31
- sourcecode/compare.py,sha256=2GdDy0qkDSgmkkpgvoCHitoLj3mt30EeFgJ8PGEXJng,14712
31
+ sourcecode/compare.py,sha256=Lj8epBqrHLWBhiccE2X0ZbaP2H3hNb7qecoN9Jvsj7c,14794
32
32
  sourcecode/confidence_analyzer.py,sha256=5li0NyOdS3Ie-f2ZY1gBmnjtkuRvI50l4OnGaKkXAOU,22358
33
33
  sourcecode/constraint_diff.py,sha256=hWIO5vsxvHjsgQx23K3r_JMy1W6wQ2Ca4DqB9R_iMnY,5831
34
34
  sourcecode/consumer_join.py,sha256=ERwx9SOu0ZE3NgCBYvB0l67iDLnWMdMpVhlCmbA_3Sw,19618
@@ -42,7 +42,7 @@ sourcecode/contract_init.py,sha256=bXRCqWM9tvrDLeT8j7ea6IWf87jB4VJxnlSzGBsRRXo,1
42
42
  sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,3917
43
43
  sourcecode/contract_pipeline.py,sha256=4XE-xhab7ysdExXyUOBl2hAH4MABo7moKnT9V4XcEwQ,30243
44
44
  sourcecode/coverage_parser.py,sha256=X9scuUWxacahYTrTBGea2Ku-sf-WCVcnc9OEB35_3IU,19340
45
- sourcecode/data_exposure.py,sha256=geJCZ_DRgv-sZlyZiRQoCDbOXP9ByZdQ_MvSE3yBYFQ,17735
45
+ sourcecode/data_exposure.py,sha256=PR-kX1UdG4_9A7ZDsKQVrPtos5GNTEMEIkpk9Aveoo0,18018
46
46
  sourcecode/data_labels.py,sha256=c_hmgudTE68cVRdywfbfonr7nUWIL-JH2HOXB8Pplv0,8453
47
47
  sourcecode/declarations.py,sha256=Jr7oJDg004sFw0TvJySNVggXFT2jB6hQgbeQK2qreVo,7000
48
48
  sourcecode/defect_identity.py,sha256=XS2VFDImg9NLaRImybKTeyk-CoDSsXmchldg0dwvM7c,7028
@@ -105,7 +105,7 @@ sourcecode/phased_run.py,sha256=fZxxiCX-CykagFVR_Fp2Tin3H6FDSV8YRpYGtsRAXj8,1835
105
105
  sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
106
106
  sourcecode/posture.py,sha256=m1sYDLtGvVkvDEVqMxuDvAKz4-KPj0z5Q6x3V_5U-FM,83276
107
107
  sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
108
- sourcecode/pr_impact.py,sha256=JRVKV0LiVpE0aTy_Wcy-o0EhWdd1OChxHRIcS0pZHl4,36216
108
+ sourcecode/pr_impact.py,sha256=taHp0X57tLHyKQ5Qvx7UM1HhX4_csw5t0bgLdN7jYm8,37010
109
109
  sourcecode/prepare_context.py,sha256=-FFvr8NgCAHckTp93ZjWEdI3fFURaLkTZiwt3_0eqgg,239532
110
110
  sourcecode/product_info.py,sha256=0zjZEutL7iDdp9aVyIumIrNyNn9VWYFc2lIytSbjBP4,727
111
111
  sourcecode/progress.py,sha256=naKbWFqf-eTE3vtLsi-eHYILz0p3pxCASAzMmvxUIAI,21643
@@ -127,7 +127,7 @@ sourcecode/repository_ir.py,sha256=-tV6QuCu56kUaosz4-SgkPdL46DuOlvlRAYxMOhtvXU,4
127
127
  sourcecode/ris.py,sha256=zlns4esv1tK8VDIYzOzRmw5yM6VLx7oxaPNB_kN3it4,28617
128
128
  sourcecode/risk.py,sha256=hXddUjiI-SS_yQ0gZyhufs4lzKwWySOKY4Lj96gL9_A,89733
129
129
  sourcecode/risk_model.py,sha256=RoC-G15qZP5BLEwwmcr4eJ0APgvIbh1WTqhTVT-5YFE,10895
130
- sourcecode/rule_catalog.py,sha256=pTkgkQZ1u7atB3fkQJ9BPWo8lNiamvEuRN7MtRgle0c,5263
130
+ sourcecode/rule_catalog.py,sha256=QCx6ToFTMCtDt5uuX1uBMLuw3b9Cd92gGRsX34kud4U,5328
131
131
  sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
132
132
  sourcecode/runs.py,sha256=o4oXkB3WFJGScbK_C10OAWzsE18B9Zc55u_IE8_yl4M,6291
133
133
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
@@ -138,9 +138,9 @@ sourcecode/schema_producers.py,sha256=4XzW4wZXnFHOaJT7XenirXGCo0pPZhkdhb7zcDtqFn
138
138
  sourcecode/schema_registry.py,sha256=ny9yqt_Mwt92TwRRviEXA8jfpeld-xB9KUTOaTiyhZs,20996
139
139
  sourcecode/security_chain.py,sha256=1YZVeFLwy65kntMBhMNvz1cUpOoKCmvEqYICgxs66Gw,6823
140
140
  sourcecode/security_config.py,sha256=LUppRDYQK0oKM4qdEOLObaeU3frFTS-paEZo7dMgtY8,5467
141
- sourcecode/security_config_scan.py,sha256=iXDeulgdbAwUFR01p9PgrJED2vd4HE2FCZgqrxX5D0E,32824
141
+ sourcecode/security_config_scan.py,sha256=Wv7QqEx_KbgmhbOGuX7RQfotzkkif43PFU1LohKNWCM,33818
142
142
  sourcecode/security_posture.py,sha256=wj6WwvasIh6r-minwBz3oz1T9Zl8SPsPhn7zxDD-yng,55861
143
- sourcecode/selftest.py,sha256=3z9pMvK4ujMDqrTKskAQbXwfpRu4lJU_yx4mbftFDVg,18772
143
+ sourcecode/selftest.py,sha256=a-6B-0k8tdVapGlXy0oC4FBxqgHgb0m4kb8oxDknnvI,19109
144
144
  sourcecode/semantic_analyzer.py,sha256=xGwQvhU9xeOlZ4N-TPXFZT8HBHx4Rg0nPwu8nDFl00Y,95621
145
145
  sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
146
146
  sourcecode/semantic_integration_engine.py,sha256=ZjyKJKOj-adScWlYBuWx8Ee_P0F4_OImUJVnCYgopuU,20952
@@ -150,11 +150,11 @@ sourcecode/servlet_surface.py,sha256=CeiebkxLXKojUQxIf7HV7tdQAwLM32ggDUwBnVQIjdo
150
150
  sourcecode/source_text.py,sha256=R52gmSljz7L6YVPbixv5W-LOZ5eKUVvSUP3S-QH_IWc,9047
151
151
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
152
152
  sourcecode/spring_findings.py,sha256=cW6NiQAFUM9mDYPitJr6MrQg--YBI41LxrSEMI9aeIQ,25272
153
- sourcecode/spring_impact.py,sha256=NaAZbY8N8gmtf9NHBh5o550yvuiewtmBE7PfAEkCIk8,88321
153
+ sourcecode/spring_impact.py,sha256=yngLmb3ABL9_6b59xSuU6sn2bAJj1MVCGy9dsjgNaic,88539
154
154
  sourcecode/spring_model.py,sha256=7GqgcWnHC96ixusRJmD-9tDV93qTdkMh-8e7CBNK0pc,24126
155
155
  sourcecode/spring_profiles.py,sha256=-kwrCK0O-MRjrCp6SA1d31t--o4Tg87iqPVrTwxoIUs,19605
156
156
  sourcecode/spring_properties.py,sha256=kPTk5qAJxdbHc1hclhHAP0O-hCIzBn8PTh3lzl-I4a4,8400
157
- sourcecode/spring_security_audit.py,sha256=6_zbZaDFZiXe7fMMpAuVjbbbk1oz_wTZI3JfBmHwpqQ,68097
157
+ sourcecode/spring_security_audit.py,sha256=TJHQG_Qen_Fr3rLjDbvfZ08rp7LK-nYVBPIx3eG-qq8,69673
158
158
  sourcecode/spring_semantic.py,sha256=UrmLg_4gmBVmMqvSnUMgSReVEERVGv3X7LYDBEs6k_o,16598
159
159
  sourcecode/spring_tx_analyzer.py,sha256=WBibPgkjQm33kFgERx6zLtp87FpJNE5V_4JI3xZ5zD4,50094
160
160
  sourcecode/sql_taint.py,sha256=As1DbEdjXMBFjCTXU5ojksOzuKkP6LmnuMVnzzmckzc,26998
@@ -240,13 +240,13 @@ sourcecode/telemetry/consent.py,sha256=pQdl-QeLl6Gcibn0eWHSKZrm-HYSsjpVqOnjrgFp8
240
240
  sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB8,2776
241
241
  sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
242
242
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
243
- sourcecode/_docs/DEFECT-LEDGER.md,sha256=cJPUezkF4LbU_8ZSOgeh_m2gUvhoidBCHIrW77AkqIk,732817
243
+ sourcecode/_docs/DEFECT-LEDGER.md,sha256=l9rGV6X-RmLgpWuYBqMcvjjjBx2NHcmdlDH8epSd33A,736756
244
244
  sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
245
245
  sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
246
- sourcecode/_docs/USER_GUIDE.md,sha256=okCMd6CxD3_Nz9qrXA1EGf8D3jHNjK8zAcTClNcm0gE,86739
247
- sourcecode-5.8.9.dist-info/METADATA,sha256=4RMPxZ4FCoQZASGy12Azs46i-7udfu2B5HseWc2OCpM,50807
248
- sourcecode-5.8.9.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
249
- sourcecode-5.8.9.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
250
- sourcecode-5.8.9.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
251
- sourcecode-5.8.9.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
252
- sourcecode-5.8.9.dist-info/RECORD,,
246
+ sourcecode/_docs/USER_GUIDE.md,sha256=PTMJ0zh_RAXmctfwsNA9-xBNudO62y1Io5rSYlI-Oqs,86740
247
+ sourcecode-5.8.10.dist-info/METADATA,sha256=WEFjTsf2cYQHfik7y8eS6Jy3Pp7JfLyMc2HjbVc-DE8,50810
248
+ sourcecode-5.8.10.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
249
+ sourcecode-5.8.10.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
250
+ sourcecode-5.8.10.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
251
+ sourcecode-5.8.10.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
252
+ sourcecode-5.8.10.dist-info/RECORD,,