sourcecode 5.9.22__py3-none-any.whl → 5.9.23__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.9.22"
7
+ __version__ = "5.9.23"
@@ -1,5 +1,5 @@
1
1
  """Generated at build time by hatch_build.py. Do not edit (AUD-592-D04)."""
2
2
 
3
- BUILD_COMMIT = '64c8b3e429eadd7a12d34eb1e5eefa0c08e430e4'
3
+ BUILD_COMMIT = '75c2f78dd8e2c7945e8a034f4f3c4fc8f9c40fc8'
4
4
  CLOSURE_PROVENANCE = {'AUD-611-B05': {'commit': '', 'status': 'uncited'}, 'AUD-611-B06': {'commit': '', 'status': 'uncited'}, 'AUD-611-B07': {'commit': '', 'status': 'uncited'}, 'AUD-611-B08': {'commit': '', 'status': 'uncited'}, 'AUD-611-B09': {'commit': '', 'status': 'uncited'}, 'AUD-611-B10': {'commit': '', 'status': 'uncited'}, 'AUD-611-B11': {'commit': '', 'status': 'uncited'}, 'AUD-612-07': {'commit': '', 'status': 'uncited'}, 'AUD-612-09': {'commit': '', 'status': 'uncited'}, 'AUD-612-14': {'commit': '', 'status': 'uncited'}, 'AUD-612-B03': {'commit': '', 'status': 'uncited'}, 'AUD-612-B07': {'commit': '', 'status': 'uncited'}, 'AUD-612-B13': {'commit': '', 'status': 'uncited'}, 'AUD-612-E': {'commit': '', 'status': 'uncited'}, 'AUD-612-G': {'commit': '', 'status': 'uncited'}, 'AUD-612-I': {'commit': '', 'status': 'uncited'}, 'AUD-612-K': {'commit': '', 'status': 'uncited'}, 'AUD-612-M': {'commit': '', 'status': 'uncited'}, 'AUD-612-R': {'commit': '', 'status': 'uncited'}, 'AUD-613-ROOT': {'commit': '', 'status': 'uncited'}, 'AUD-614-07': {'commit': '', 'status': 'uncited'}, 'AUD-614-E': {'commit': '', 'status': 'uncited'}, 'AUD-614-F': {'commit': '', 'status': 'uncited'}, 'AUD-614-G': {'commit': '', 'status': 'uncited'}, 'AUD-614-I': {'commit': '', 'status': 'uncited'}, 'AUD-614-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-614-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-614-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-614-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-615-07': {'commit': '', 'status': 'uncited'}, 'AUD-615-COLD-COMPACT': {'commit': '', 'status': 'uncited'}, 'AUD-615-DRIFT': {'commit': '', 'status': 'uncited'}, 'AUD-615-E': {'commit': '', 'status': 'uncited'}, 'AUD-615-F': {'commit': '', 'status': 'uncited'}, 'AUD-615-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-615-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-615-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-615-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-616-E-PROV': {'commit': '', 'status': 'uncited'}, 'AUD-616-F, -RISK-L0, -SELFTEST-TIMEOUT, -INTENT-TIE, -DRIFT, -COLD-COMPACT, -E, -07': {'commit': '', 'status': 'uncited'}, 'AUD-616-INTENT-TIE-COV': {'commit': '', 'status': 'uncited'}, 'AUD-616-JUNIT4-SELF': {'commit': '', 'status': 'uncited'}, 'AUD-616-KOTLIN': {'commit': '', 'status': 'uncited'}, 'AUD-616-O': {'commit': '', 'status': 'uncited'}, 'AUD-616-P': {'commit': '', 'status': 'uncited'}, 'Window': {'commit': '', 'status': 'uncited'}, '`ASK-FIELD-01`': {'commit': '829f9fa', 'status': 'present'}, '`ASK-FIELD-02`': {'commit': '6a659fa', 'status': 'present'}, '`ASK-FIELD-03`': {'commit': '55b0d51', 'status': 'present'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`AUD-619-WATCH`': {'commit': '', 'status': 'uncited'}, '`AUD-620-COMMAND-SURFACE`': {'commit': '', 'status': 'uncited'}, '`AUD-620-REVIEW-PR`': {'commit': '', 'status': 'uncited'}, '`AUD-621-REVIEW-PR`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-3` / `443b345`': {'commit': '', 'status': 'uncited'}, '`BUG-4a` / `AUD-588-B11` residual': {'commit': '24b4185', 'status': 'present'}, '`BUG-4b` / `AUD-588-B11` residual': {'commit': '0376350', 'status': 'present'}, '`E-39`, class residual of `AUD-593-N06`': {'commit': '25fc11f', 'status': 'present'}, '`N-5`': {'commit': 'c68083f', 'status': 'present'}, '`N-7`': {'commit': '082e9de', 'status': 'present'}, '`R-1`': {'commit': '424fb1c', 'status': 'present'}, '`R-2`': {'commit': '5158b92', 'status': 'present'}, '`R-3`': {'commit': '88aec04', 'status': 'present'}, '`R-4`': {'commit': '04fd04f', 'status': 'present'}}
5
5
  CLOSURE_COVERAGE = {'closed_rows': 165, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 74, 'unknown': 0}
@@ -14,6 +14,32 @@ Status values: `open` · `in-progress` · `closed <version>` · `by-design` (wit
14
14
  The facts these rows are keyed to are published: `ask schema facts-v1` prints the registry
15
15
  (authority, definition, consumers, parity test) that the release battery enforces.
16
16
 
17
+ ## Current Synchronization — External Audit `5.9.22` (2026-09-01)
18
+
19
+ The external audit confirms strong precision, determinism and uncertainty
20
+ disclosure, but opens a P0/P1 cluster around silent success, incomplete
21
+ population disclosure, large-run memory and compact-output contracts. Full
22
+ evidence: [`AUDIT-2026-09-01-EXTERNAL-5.9.22.md`](AUDIT-2026-09-01-EXTERNAL-5.9.22.md).
23
+
24
+ | ID | Severity | Status | Evidence and required closure |
25
+ |---|---:|---|---|
26
+ | `BUG-01` | Critical maximum | **open, P0** | Full Markdown `audit-report` reproduced with `rc=0` and empty stdout. Add a global non-empty-answer invariant and renderer coverage. |
27
+ | `BUG-02` | Critical maximum | **open, P0** | Large `risk` ended `rc=127` with no output or structured failure. Add fail-fast resource/process handling and durable partial checkpoints. |
28
+ | `BUG-12` | Critical maximum | **open, P0** | Deep `--agent` output construction reaches multi-gigabyte RSS before a small answer. Bound projection while building. |
29
+ | `BUG-03` | High | **open, P1** | `content_id` changes between cold and warm because cache state is hashed into identity. Separate execution metadata from answer identity. |
30
+ | `BUG-04` | High | **open, P1** | `selftest` publishes `complete=true` with five error rows. Derive bounds from actual results. |
31
+ | `BUG-05` | High | **open, P1** | Overloaded endpoint handlers can receive the wrong declaration line. Resolve by mapping annotation or publish ambiguity. |
32
+ | `BUG-07` | High | **open, P1** | `--compact` remains oversized on `migrate-check` and `spring-audit`. Apply bounded projections with omission metadata. |
33
+ | `BUG-10` | High | **open, P1** | Unsupported routing styles can produce empty coverage gaps. Publish detected-but-unmodeled routing gaps. |
34
+ | `BUG-11` | High | **open, P1** | Default `pack assessment` can fail on a small Spring repository. Use a bounded default or explicit full mode. |
35
+ | `BUG-13` | High | **open, P1** | Unscoped `selftest` starts over nested repositories after warning. Refuse and require an explicit path. |
36
+ | `BUG-16` | High | **open, P1** | Context-cache growth has no published budget or eviction contract; about 1.5 GB was measured. |
37
+ | `BUG-22` | High | **open, P1** | RSS ceilings are checked post-hoc. Add periodic sampling and orderly abort. |
38
+
39
+ The remaining `BUG-04b`, `BUG-06`, `BUG-08`, `BUG-09`, `BUG-15`, `BUG-19`,
40
+ `BUG-20`, `BUG-21` and `BUG-23` findings are open P2/coverage follow-ups in
41
+ the audit document. None is closed by documentation alone.
42
+
17
43
  ## Current Synchronization — External Audit `AUD-621`
18
44
 
19
45
  **Release `5.9.19` remediation.** The stderr-decoding fix for `AUD-621-PACK-STDERR` remains in code, and the distinct `pack gate --since` crash found by the post-remediation re-audit is corrected with a Python 3.9–3.11-compatible tar extraction path. `AUD-621-R` has a second Windows barrier at process-pool construction, complementing the sequential fallback. The broader findings remain open until their required native Windows evidence is recorded; `AUD-621-CONTEXT` and `AUD-621-WIN-PROC` are unchanged.
@@ -382,6 +382,9 @@ Output: detected stacks, entry points, dependencies, transactional boundaries (S
382
382
  ### `ask --agent`
383
383
 
384
384
  More structured version of `--compact`. Designed for AI agent system prompt injection.
385
+ It is bounded to 40 KB on stdout and admits repositories up to 10,000 Java files;
386
+ larger repositories return `AGENT_SCOPE_TOO_LARGE` before expensive analysis. Use
387
+ `--compact` or narrow the path in that case.
385
388
 
386
389
  ```bash
387
390
  ask /repo --agent --output context.json
@@ -528,7 +531,7 @@ disabled changes runtime behaviour nobody measured; a diff is how a person makes
528
531
  call with the evidence in front of them.
529
532
 
530
533
  **TX patterns (TX-001..TX-006):** proxy bypass, nested transactions, readOnly propagation, NOT_SUPPORTED in active TX, exception swallowing, self-invocation of a `@Transactional` sibling.
531
- **SEC patterns (SEC-001..SEC-009):** unsecured endpoints, CVE-2025-41248 `@PreAuthorize` inheritance bypass, `@Transactional` on controllers, passwords stored under a fast unsalted digest, CSRF disabled under session-bearing authentication, cookies created without the Secure attribute, credentials stored in a deployment descriptor, SQL built by MyBatis string interpolation, and anonymous request-chain exceptions. **DEAD-001** reports a security control that is present only in comments and therefore disabled.
534
+ **SEC patterns (SEC-001..SEC-010):** unsecured endpoints, CVE-2025-41248 `@PreAuthorize` inheritance bypass, `@Transactional` on controllers, passwords stored under a fast unsalted digest, CSRF disabled under session-bearing authentication, cookies created without the Secure attribute, credentials stored in a deployment descriptor, SQL built by MyBatis string interpolation, anonymous request-chain exceptions, and HTTP input reaching a concatenated known query sink through bounded positional in-repository calls. **DEAD-001** reports a security control that is present only in comments and therefore disabled.
532
535
  **GATE patterns (GATE-001..GATE-004):** custom AOP/security-gate risks over the same custom-gate authority published in `security_posture`: proxy bypass (`private`/`final`/self-invocation), advice that appears to fail open, disabled or tautological annotation attributes, and fragile `args[0]`/reflection binding.
533
536
 
534
537
  Each finding includes `severity`, `confidence`, `symbol`, `source_file`, `evidence`, `explanation`, and `fix_hint`. JAVA/SPRING ONLY.
@@ -1812,10 +1815,10 @@ stops at 1 408 files inside the `SEC-004` group and ends at 5.1s.
1812
1815
  **Memory budget.** Repository-scoped JSON answers include `_meta.memory` with
1813
1816
  `unit: "MB"`, the observed `peak_rss_mb`, the `peak_rss_basis` it was sampled
1814
1817
  from, `configured_max_rss_mb`, and `ceiling_enforceable`. To bound long analyses
1815
- on a CI or supervised runner, set `ASK_MAX_RSS_MB=<megabytes>`. Exceeding the
1816
- ceiling produces exit code `1` and a structured `MEMORY_TOO_LARGE` error with the
1817
- peak and configured limit; no partial answer is published. Unset the variable, or
1818
- set it to `0`, to disable this ceiling.
1818
+ on a CI or supervised runner, set `ASK_MAX_RSS_MB=<megabytes>`. `--agent` uses
1819
+ 1,536 MB when the variable is unset. Exceeding the ceiling produces exit code `1`
1820
+ and a structured `MEMORY_TOO_LARGE` error with the peak and configured limit; no
1821
+ partial answer is published. Set the variable to `0` to disable this ceiling.
1819
1822
 
1820
1823
  Where the platform exposes no footprint sample, `peak_rss_mb` is `null` and
1821
1824
  `peak_rss_basis` says why — never `0`, which would read as a measured floor. A
@@ -99,6 +99,16 @@ def _gaps(payload: "dict[str, Any]") -> "list[dict[str, Any]]":
99
99
  found.append({"field": "non_coverage", "what": _summarise(entry)})
100
100
  elif non_coverage:
101
101
  found.append({"field": "non_coverage", "what": _summarise(non_coverage)})
102
+ populations = [payload.get("population")]
103
+ metadata = payload.get("metadata")
104
+ if isinstance(metadata, dict):
105
+ populations.append(metadata.get("population"))
106
+ for population in populations:
107
+ if not isinstance(population, dict):
108
+ continue
109
+ for failure in _as_list(population.get("parse_failures")):
110
+ if failure:
111
+ found.append({"field": "population.parse_failures", "what": _summarise(failure)})
102
112
  for prefix, block in _blocks(payload):
103
113
  for field in _GAP_FIELDS:
104
114
  value = block.get(field)
@@ -594,6 +594,7 @@ def ir_dict_to_canonical(
594
594
  "edge_count": len(call_graph),
595
595
  "subsystems": ir.get("subsystems") or [],
596
596
  "analysis_gaps": ir.get("analysis_gaps") or [],
597
+ "population": ir.get("population") or {},
597
598
  "spring_events": ir.get("spring_events") or {},
598
599
  "score_basis": (ir.get("impact") or {}).get("score_basis", "none"),
599
600
  "reverse_graph_size": len(ir.get("reverse_graph") or {}),
sourcecode/cli.py CHANGED
@@ -513,6 +513,28 @@ _START_HERE: "tuple[tuple[str, str], ...]" = (
513
513
  )
514
514
 
515
515
 
516
+ def _start_here_entries(root: "Optional[Path]") -> "tuple[tuple[str, str], ...]":
517
+ """Return pasteable first steps that the measured repository can accept."""
518
+ if root is None:
519
+ return _START_HERE
520
+ try:
521
+ from sourcecode.spring_profiles import profile_population
522
+
523
+ profiles = profile_population(root).names
524
+ except Exception:
525
+ # Help must remain available if a repository cannot be read.
526
+ return _START_HERE
527
+ if len(profiles) < 2:
528
+ # `posture --diff` rejects undeclared profile names. Do not publish an
529
+ # example that cannot run on this repository.
530
+ return tuple(entry for entry in _START_HERE if not entry[0].startswith("posture "))
531
+ diff = f"posture . --diff {profiles[0]}:{profiles[1]}"
532
+ return tuple(
533
+ (diff, answers) if invocation.startswith("posture ") else (invocation, answers)
534
+ for invocation, answers in _START_HERE
535
+ )
536
+
537
+
516
538
  #: Resolved bounding flags, by invocation. Deliberately **not** an `lru_cache`:
517
539
  #: only a resolution that found the command is stored. AUD-601-D01 — the front
518
540
  #: page is composed while this module is still being imported, so the first
@@ -637,6 +659,8 @@ def _start_here_block(scope: "Optional[Any]" = None) -> str:
637
659
  BUCKET_ORDER, BUCKET_TITLE, BUDGETED, DETACH, RUNS_NOW, UNKNOWN, plan,
638
660
  )
639
661
 
662
+ root = getattr(scope, "root", None) if scope is not None else None
663
+ entries = _start_here_entries(root)
640
664
  if scope is None or not scope.measured:
641
665
  # AUD-601-D01. The ceiling is a property of the **command**, not of the
642
666
  # repository, so the bounding flag must not depend on measuring one.
@@ -647,13 +671,13 @@ def _start_here_block(scope: "Optional[Any]" = None) -> str:
647
671
  # the generator covers them, and this rendering never called it.
648
672
  static = [
649
673
  (_bounded_invocation(invocation), answers)
650
- for invocation, answers in _START_HERE
674
+ for invocation, answers in entries
651
675
  ]
652
676
  width = max([29] + [len(invocation) + 1 for invocation, _ in static])
653
677
  lines = ["[bold]Start here — Java/Spring analysis:[/bold]"]
654
678
  for invocation, answers in static:
655
679
  lines.append(f" {invocation:<{width}}[dim]# {answers}[/dim]")
656
- if any(bounded != raw for (bounded, _), (raw, _) in zip(static, _START_HERE)):
680
+ if any(bounded != raw for (bounded, _), (raw, _) in zip(static, entries)):
657
681
  lines.append(
658
682
  " [dim]Bounded for a first look: each line above carries the "
659
683
  "flag its command declares against the output ceiling. Drop it, "
@@ -661,7 +685,7 @@ def _start_here_block(scope: "Optional[Any]" = None) -> str:
661
685
  )
662
686
  return "\n".join(lines)
663
687
 
664
- answers = dict(_START_HERE)
688
+ answers = dict(entries)
665
689
  # AUD-596-X02: this block sold three commands under "Runs now" and two of
666
690
  # them exited 1 here, both with `OUTPUT_TOO_LARGE` — `posture . --diff
667
691
  # dev:prod` at ~71K estimated tokens over a 50K ceiling, and `endpoints .`
@@ -672,12 +696,12 @@ def _start_here_block(scope: "Optional[Any]" = None) -> str:
672
696
  # it from an error.
673
697
  bounded = {
674
698
  invocation: _bounded_invocation(invocation)
675
- for invocation, _ in _START_HERE
699
+ for invocation, _ in entries
676
700
  }
677
701
  for invocation, bounded_form in bounded.items():
678
702
  if bounded_form != invocation:
679
703
  answers[bounded_form] = answers[invocation]
680
- grouped = plan([bounded[invocation] for invocation, _ in _START_HERE], scope)
704
+ grouped = plan([bounded[invocation] for invocation, _ in entries], scope)
681
705
 
682
706
  # AUD-597-A01. The plan orders by *time* and the ceiling refuses by *size*,
683
707
  # so a command that is seconds away from an answer it cannot print is not a
@@ -729,7 +753,7 @@ def _start_here_block(scope: "Optional[Any]" = None) -> str:
729
753
  " [dim]…each of these answers into a file and survives the shell:"
730
754
  " add --output <file> --detach[/dim]"
731
755
  )
732
- if any(bounded[invocation] != invocation for invocation, _ in _START_HERE):
756
+ if any(bounded[invocation] != invocation for invocation, _ in entries):
733
757
  lines.append(
734
758
  " [dim]Bounded for a first look: each line above carries the flag "
735
759
  "its command declares against the output ceiling. Drop it, or add "
@@ -888,12 +912,12 @@ scope matches; `ask cache model` says exactly what a warm buys per command.
888
912
  data-exposure . [dim]# one semantic model is shared across declared label seeds[/dim]
889
913
  repo-ir . --since main --budget-tokens 12000 [dim]# bounded agent context[/dim]
890
914
  migrate-check . --format sarif -o migration.sarif [dim]# located migration findings[/dim]
891
- [dim]The repository is a positional argument everywhere; --path, --repo and --dir
892
- are accepted as aliases for it on any command that does not already declare them.[/dim]
915
+ [dim]Repository paths are normally positional. Commands that publish --path, --repo
916
+ or --dir accept that documented spelling; use each command's --help for its grammar.[/dim]
893
917
 
894
918
  ASK_MAX_ANALYSIS_SECONDS=N [dim]# deadline for {_budget_tiers_phrase()}[/dim]
895
919
  [dim]Other commands do not consume this budget; use `--progress` or `--detach` where offered.[/dim]
896
- ASK_MAX_RSS_MB=N [dim]# peak resident-memory ceiling for costly analyses; 0 disables[/dim]
920
+ ASK_MAX_RSS_MB=N [dim]# peak resident-memory ceiling; --agent defaults to 1536 MB, 0 disables[/dim]
897
921
  [dim]Repository JSON answers publish `_meta.memory.peak_rss_mb` with its basis; exceeding the ceiling
898
922
  returns MEMORY_TOO_LARGE, and a platform that exposes no sample returns MEMORY_CEILING_UNAVAILABLE
899
923
  rather than passing a ceiling nothing measured.[/dim]
@@ -1796,7 +1820,12 @@ def _apply_budget(
1796
1820
 
1797
1821
  to_file = output_path is not None
1798
1822
  return _trim(
1799
- data, budget_bytes, label=label, skip=to_file, warn_stderr=not to_file
1823
+ data,
1824
+ budget_bytes,
1825
+ label=label,
1826
+ skip=to_file,
1827
+ warn_stderr=not to_file,
1828
+ strict=(label in {"agent", "compact"}),
1800
1829
  )
1801
1830
 
1802
1831
 
@@ -3140,6 +3169,18 @@ def _emit_command_output(
3140
3169
  not about the encoding. A command that already serialized its own YAML passes
3141
3170
  the default and is untouched: the conversion only fires on a JSON object.
3142
3171
  """
3172
+ # A completed analysis with no payload is never a successful answer. The
3173
+ # process-level guard also catches this for the production entry point, but
3174
+ # rejecting it here protects every direct Typer invocation and prevents an
3175
+ # empty file from being published as an artefact.
3176
+ if not content.strip():
3177
+ _emit_error_json(
3178
+ EXECUTION_FAILED_CODE,
3179
+ "The command finished without publishing an answer.",
3180
+ command=_ANSWER_REQUIRED_BY or "unknown",
3181
+ expected="a non-empty parseable answer or report",
3182
+ )
3183
+ raise typer.Exit(code=1)
3143
3184
  if stamp_envelope:
3144
3185
  content = _stamp_envelope(content)
3145
3186
  if fmt == "yaml":
@@ -4906,6 +4947,24 @@ DOCS_DEPTH_CHOICES = ["module", "symbols", "full"]
4906
4947
  _FREE_TIER_NODE_CAP: int = 50 # graph/semantic node cap — applies only to large repos on free tier
4907
4948
  _JAVA_MIN_SCAN_DEPTH: int = 12 # Maven src/main/java/<pkg>/<module>/File depth floor
4908
4949
  _LARGE_REPO_ADVISORY_FILES: int = 3000 # cold-scan advisory threshold (files); honest expectation-setting, not a speed claim
4950
+ _AGENT_JAVA_FILE_CAP: int = 10_000
4951
+
4952
+
4953
+ def _agent_scope_error(root: Path) -> "Optional[dict[str, Any]]":
4954
+ """Refuse an agent run that cannot stay inside its memory contract."""
4955
+ try:
4956
+ java_files = sum(1 for _ in root.rglob("*.java"))
4957
+ except OSError:
4958
+ return None
4959
+ if java_files <= _AGENT_JAVA_FILE_CAP:
4960
+ return None
4961
+ return {
4962
+ "code": "AGENT_SCOPE_TOO_LARGE",
4963
+ "message": f"--agent is limited to {_AGENT_JAVA_FILE_CAP:,} Java files by default; this repository has {java_files:,}.",
4964
+ "hint": "Use --compact for the bounded summary, or narrow the repository path.",
4965
+ "java_files": java_files,
4966
+ "max_java_files": _AGENT_JAVA_FILE_CAP,
4967
+ }
4909
4968
 
4910
4969
  #: What one Java file costs in serialized `repo-ir` output, measured across five
4911
4970
  #: repositories (C3-18): spring-petclinic 30 files/318 KB (10.8 KB/file),
@@ -5769,7 +5828,14 @@ def main(
5769
5828
  code_notes = True
5770
5829
  no_tree = True # agents never need the raw file tree
5771
5830
  architecture = True # agents need full architectural signal (M4)
5772
- graph_modules = True # IC-003: import graph needed for architecture confidence
5831
+ # Module graphs are quadratic on large import populations and are not
5832
+ # required by agent_view. Keep them opt-in via --graph-modules.
5833
+ _agent_error = _agent_scope_error(target)
5834
+ if _agent_error is not None:
5835
+ _emit_error_json(
5836
+ _agent_error.pop("code"), _agent_error.pop("message"), **_agent_error
5837
+ )
5838
+ raise typer.Exit(code=2)
5773
5839
 
5774
5840
  # --compact implicitly enables lightweight analysis passes so that
5775
5841
  # dependency_summary, env_summary and code_notes_summary are never null.
@@ -6351,6 +6417,7 @@ def main(
6351
6417
  """Close a phase and stop the run if the operator's budget is spent."""
6352
6418
  _root_run.completed.append(phase)
6353
6419
  if not _root_run.exhausted():
6420
+ _root_memory_seam()
6354
6421
  return
6355
6422
  _progress.stop()
6356
6423
  _block = _root_run.status(_WHY_BUDGET)
@@ -6362,6 +6429,26 @@ def main(
6362
6429
  _emit_command_output(_serialize_dict(_block, format), output, copy)
6363
6430
  raise typer.Exit(code=_exit)
6364
6431
 
6432
+ def _root_memory_seam() -> None:
6433
+ """Enforce the agent's protective RSS ceiling at every root boundary."""
6434
+ if not agent:
6435
+ return
6436
+ from sourcecode import resource_budget
6437
+
6438
+ memory_error = resource_budget.exceeded(
6439
+ "ask --agent",
6440
+ default_ceiling_mb=resource_budget.DEFAULT_AGENT_RSS_CEILING_MB,
6441
+ )
6442
+ if memory_error is None:
6443
+ return
6444
+ _progress.stop()
6445
+ _emit_error_json(
6446
+ memory_error.pop("code"),
6447
+ memory_error.pop("message"),
6448
+ **memory_error,
6449
+ )
6450
+ raise typer.Exit(code=1)
6451
+
6365
6452
  _perf_discovery = perf.start()
6366
6453
  scanner = AdaptiveScanner(target, topology=_topology, base_depth=effective_depth,
6367
6454
  extra_excludes=_extra_excludes)
@@ -7099,7 +7186,7 @@ def main(
7099
7186
  _root_timing.setdefault("ris_ms", 0.0)
7100
7187
  _root_timing.setdefault("snapshot_ms", 0.0)
7101
7188
  # 4. Serialize
7102
- _progress.update("serializing output")
7189
+ _progress.update("building bounded agent context" if agent else "serializing output")
7103
7190
  _perf_serialize = perf.start()
7104
7191
  if _is_contract_mode and not agent:
7105
7192
  from sourcecode.serializer import contract_view as _contract_view
@@ -7156,6 +7243,7 @@ def main(
7156
7243
  content = _serialize_dict(data, format)
7157
7244
 
7158
7245
  perf.stop("serialize", _perf_serialize)
7246
+ _root_seam("serialize")
7159
7247
  perf.flush_recorder()
7160
7248
 
7161
7249
  # 5. Write output (CLI-04)
@@ -2673,6 +2673,20 @@ class MigrationReport:
2673
2673
  for _col in ("evidence", "target_lts_blockers"):
2674
2674
  _cap_into(_l, _col, top_n)
2675
2675
  compact["java_lts_inventory"] = _l
2676
+ inventory = full.get("jvm_inventory")
2677
+ if isinstance(inventory, dict) and isinstance(inventory.get("frameworks"), dict):
2678
+ _inventory = dict(inventory)
2679
+ _frameworks: dict[str, dict] = {}
2680
+ for _name, _framework in inventory["frameworks"].items():
2681
+ if not isinstance(_framework, dict):
2682
+ _frameworks[_name] = _framework
2683
+ continue
2684
+ _bounded = dict(_framework)
2685
+ for _collection in ("declared", "observed"):
2686
+ _cap_into(_bounded, _collection, top_n)
2687
+ _frameworks[_name] = _bounded
2688
+ _inventory["frameworks"] = _frameworks
2689
+ compact["jvm_inventory"] = _inventory
2676
2690
  return compact
2677
2691
 
2678
2692
  def to_text(self, min_severity: str = "low") -> str:
@@ -487,6 +487,7 @@ _STACK_INDEPENDENT_AXES: "tuple[str, ...]" = (
487
487
  def stack_fit(
488
488
  spring_detected: bool,
489
489
  security_frameworks: "tuple[str, ...] | list[str]" = (),
490
+ spring_footprint: "Optional[dict[str, int]]" = None,
490
491
  ) -> dict:
491
492
  """Whether this product's Spring-shaped axes model the repository. (CL-19)
492
493
 
@@ -553,6 +554,22 @@ def stack_fit(
553
554
  "Spring Security result must not be read as public access."
554
555
  ),
555
556
  }
557
+ footprint = spring_footprint or {}
558
+ beans = int(footprint.get("spring_beans") or 0)
559
+ java_files = int(footprint.get("java_files") or 0)
560
+ if spring_detected and java_files and beans / java_files < 0.1:
561
+ return {
562
+ "schema_version": STACK_FIT_SCHEMA,
563
+ "spring_detected": True,
564
+ "fit": "partial",
565
+ "spring_footprint": {"spring_beans": beans, "java_files": java_files},
566
+ "statement": (
567
+ "Spring is present but marginal in this scope, so Spring-shaped axes "
568
+ "model only the detected Spring population. Do not read their absence "
569
+ "of findings as coverage of the remaining repository."
570
+ ),
571
+ "how_to_read": "Use Spring results for the detected Spring population only.",
572
+ }
556
573
  if spring_detected:
557
574
  return {
558
575
  "schema_version": STACK_FIT_SCHEMA,
@@ -74,6 +74,11 @@ _TRIM_SCHEDULE: list[tuple[str, str | None, int]] = [
74
74
  ]
75
75
 
76
76
 
77
+ # The root CLI adds cache, timing and envelope records after view trimming.
78
+ # This is the measured upper bound needed to keep its published ceilings true.
79
+ _STRICT_POSTPROCESSING_RESERVE = 12_288
80
+
81
+
77
82
  def _serialized_size(data: Any) -> int:
78
83
  """Byte size of JSON-serialized data (UTF-8)."""
79
84
  return len(json.dumps(data, ensure_ascii=False).encode("utf-8"))
@@ -86,6 +91,7 @@ def trim_to_budget(
86
91
  label: str = "",
87
92
  skip: bool = False,
88
93
  warn_stderr: bool = False,
94
+ strict: bool = False,
89
95
  ) -> dict:
90
96
  """Progressively trim *data* to fit within *budget_bytes*.
91
97
 
@@ -101,17 +107,26 @@ def trim_to_budget(
101
107
 
102
108
  Returns data unchanged if already within budget or ``skip=True``.
103
109
  """
104
- if skip or _serialized_size(data) <= budget_bytes:
110
+ # The strict caller needs space for metadata added after this return.
111
+ target_budget = (
112
+ max(0, budget_bytes - _STRICT_POSTPROCESSING_RESERVE)
113
+ if strict else budget_bytes
114
+ )
115
+ if skip or _serialized_size(data) <= target_budget:
105
116
  return data
106
117
 
107
118
  result: dict = dict(data)
108
119
  original_size = _serialized_size(result)
120
+ # Truncation metadata is added after content is reduced. Agent output
121
+ # publishes a hard byte ceiling, so reserve room for that metadata first.
122
+ # The CLI appends envelope, timing and cache metadata after this function.
123
+ # Reserve their worst observed footprint for every published hard ceiling.
109
124
  trimmed_sections: list[str] = []
110
125
  # Track original counts for total_omitted_items calculation.
111
126
  _original_counts: dict[str, int] = {}
112
127
 
113
128
  for top_key, inner_key, max_items in _TRIM_SCHEDULE:
114
- if _serialized_size(result) <= budget_bytes:
129
+ if _serialized_size(result) <= target_budget:
115
130
  break
116
131
 
117
132
  if top_key not in result or result[top_key] is None:
@@ -163,7 +178,7 @@ def trim_to_budget(
163
178
  # again — so when the schedule is exhausted and the payload is still over,
164
179
  # the remaining cut is chosen by *measuring* what is actually large.
165
180
  _MEASURED_FLOOR = 5 # never cut a list below this: a sample of 0 is not a sample
166
- while _serialized_size(result) > budget_bytes:
181
+ while _serialized_size(result) > target_budget:
167
182
  _candidates = [
168
183
  (_serialized_size(v), k, len(v))
169
184
  for k, v in result.items()
@@ -337,4 +352,3 @@ def compact_budget_phrase() -> str:
337
352
  context window needs.
338
353
  """
339
354
  return _budget_phrase(BUDGET_COMPACT)
340
-
@@ -5151,6 +5151,7 @@ def _compute_analysis_gaps(
5151
5151
  relations: list[RelationEdge],
5152
5152
  unparsed_type_files: Optional[list[str]] = None,
5153
5153
  unbalanced_files: Optional[list[str]] = None,
5154
+ parse_failures: Optional[list[dict]] = None,
5154
5155
  ) -> list[dict]:
5155
5156
  """Compute structural analysis gaps — real system failures, not cosmetic issues."""
5156
5157
  gaps: list[dict] = []
@@ -5199,6 +5200,20 @@ def _compute_analysis_gaps(
5199
5200
  "files": list(rf.symbols),
5200
5201
  })
5201
5202
 
5203
+ if parse_failures:
5204
+ failed_files = [row["path"] for row in parse_failures if row.get("status") == "failed"]
5205
+ recovered_files = [row["path"] for row in parse_failures if row.get("status") == "recovered"]
5206
+ gaps.append({
5207
+ "area": "parse_failures",
5208
+ "reason": (
5209
+ f"{len(failed_files)} Java file(s) could not be parsed and "
5210
+ f"{len(recovered_files)} were only partially recovered. Their "
5211
+ "absence or recovered symbols must not be read as complete coverage."
5212
+ ),
5213
+ "impact": "high" if failed_files else "medium",
5214
+ "files": [row["path"] for row in parse_failures],
5215
+ })
5216
+
5202
5217
  controllers = spring_summary.get("controllers", [])
5203
5218
  if controllers and not route_surface:
5204
5219
  gaps.append({
@@ -5251,6 +5266,7 @@ def _assemble(
5251
5266
  custom_security: "tuple[CustomSecuritySpec, ...]" = (),
5252
5267
  unparsed_type_files: Optional[list[str]] = None,
5253
5268
  unbalanced_files: Optional[list[str]] = None,
5269
+ parse_failures: Optional[list[dict]] = None,
5254
5270
  constants: "Optional[dict[str, str]]" = None,
5255
5271
  root: "Optional[Path]" = None,
5256
5272
  ) -> dict:
@@ -5548,6 +5564,7 @@ def _assemble(
5548
5564
  sorted_syms, spring_summary, _route_surface, sorted_rels,
5549
5565
  unparsed_type_files=unparsed_type_files,
5550
5566
  unbalanced_files=unbalanced_files,
5567
+ parse_failures=parse_failures,
5551
5568
  )
5552
5569
 
5553
5570
  # Detect filter-based security model for the assembled IR.
@@ -6717,6 +6734,9 @@ def build_repo_ir(
6717
6734
  # for the rare file that already looks unbalanced, exactly as the
6718
6735
  # zero-symbol probe below does.
6719
6736
  _unbalanced_files: list[str] = []
6737
+ # BUG-01: a tolerant extractor may recover useful symbols, but it must never
6738
+ # make a malformed or non-text Java file disappear from the population.
6739
+ _parse_failures: dict[str, dict[str, str]] = {}
6720
6740
  _total_files = len(file_paths)
6721
6741
 
6722
6742
  # F-AQ. Before the loop below runs, hand the same files to a pool of worker
@@ -6746,9 +6766,19 @@ def build_repo_ir(
6746
6766
  progress(_file_index, _total_files, "parsing")
6747
6767
  abs_path = root / rel_path
6748
6768
  try:
6749
- source = abs_path.read_text(encoding="utf-8", errors="replace")
6769
+ raw_source = abs_path.read_bytes()
6750
6770
  except OSError:
6771
+ _parse_failures[rel_path] = {
6772
+ "path": rel_path, "status": "failed", "reason": "unreadable_file",
6773
+ }
6751
6774
  continue
6775
+ try:
6776
+ source = raw_source.decode("utf-8")
6777
+ except UnicodeDecodeError:
6778
+ source = raw_source.decode("utf-8", errors="replace")
6779
+ _parse_failures[rel_path] = {
6780
+ "path": rel_path, "status": "failed", "reason": "invalid_utf8",
6781
+ }
6752
6782
  _meta_files_read += 1
6753
6783
  _meta_lines_read += source.count("\n") + (1 if source and not source.endswith("\n") else 0)
6754
6784
  _meta_chars_read += len(source)
@@ -6787,12 +6817,22 @@ def build_repo_ir(
6787
6817
  from sourcecode.reconciliation import declares_java_type
6788
6818
  if declares_java_type(_mask_java(source)):
6789
6819
  _unparsed_type_files.append(rel_path)
6820
+ _parse_failures.setdefault(rel_path, {
6821
+ "path": rel_path,
6822
+ "status": "failed",
6823
+ "reason": "type_declaration_produced_no_symbols",
6824
+ })
6790
6825
  # E-10: a brace that never closes is not a style question — it means the
6791
6826
  # symbols recovered from this file stop somewhere the reader cannot see.
6792
6827
  elif symbols and source.count("{") != source.count("}"):
6793
6828
  _masked_src = _mask_java(source)
6794
6829
  if _masked_src.count("{") != _masked_src.count("}"):
6795
6830
  _unbalanced_files.append(rel_path)
6831
+ _parse_failures.setdefault(rel_path, {
6832
+ "path": rel_path,
6833
+ "status": "recovered",
6834
+ "reason": "unbalanced_braces",
6835
+ })
6796
6836
  _per_file.append((rel_path, package, raw_imports, symbols))
6797
6837
  if "@" in source:
6798
6838
  _parsed_refs = _extract_annotation_type_refs(source, package)
@@ -7014,6 +7054,7 @@ def build_repo_ir(
7014
7054
  custom_security=_custom_sec_tuple,
7015
7055
  unparsed_type_files=_unparsed_type_files,
7016
7056
  unbalanced_files=_unbalanced_files,
7057
+ parse_failures=[_parse_failures[path] for path in sorted(_parse_failures)],
7017
7058
  # Cross-file constant folding for annotation paths: per-file folding only
7018
7059
  # sees constants declared in the same file (see _collect_repo_constants).
7019
7060
  constants=_repo_constants,
@@ -7117,6 +7158,10 @@ def build_repo_ir(
7117
7158
  "methods": sum(_kind_counts.get(k, 0) for k in _METHOD_KINDS),
7118
7159
  "symbols_excluded": symbols_excluded_disclosure(_meta_symbols_excluded),
7119
7160
  }
7161
+ ir["population"] = {
7162
+ "java_files": len(file_paths),
7163
+ "parse_failures": [_parse_failures[path] for path in sorted(_parse_failures)],
7164
+ }
7120
7165
  if emit_body_facts:
7121
7166
  # Additive, in-memory only: not a cir_hash input, not serialized by the
7122
7167
  # repo-ir command (which builds without this flag). Sorted for determinism.
@@ -12,6 +12,7 @@ except ImportError: # pragma: no cover - exercised on Windows
12
12
  _resource = None
13
13
 
14
14
  ENV_RSS_CEILING = "ASK_MAX_RSS_MB"
15
+ DEFAULT_AGENT_RSS_CEILING_MB = 1_536.0
15
16
  ERROR_CODE = "MEMORY_TOO_LARGE"
16
17
  UNAVAILABLE_CODE = "MEMORY_CEILING_UNAVAILABLE"
17
18
 
@@ -121,11 +122,11 @@ def peak_rss_mb() -> Optional[float]:
121
122
  return peak_rss_sample()[0]
122
123
 
123
124
 
124
- def configured_ceiling_mb() -> Optional[float]:
125
- """Read the positive RSS ceiling; zero or unset means no ceiling."""
125
+ def configured_ceiling_mb(*, default: Optional[float] = None) -> Optional[float]:
126
+ """Read the positive RSS ceiling; an explicit zero disables any default."""
126
127
  raw = os.environ.get(ENV_RSS_CEILING)
127
128
  if raw is None or not raw.strip():
128
- return None
129
+ return default
129
130
  try:
130
131
  value = float(raw.strip())
131
132
  except ValueError:
@@ -173,7 +174,11 @@ def _unavailable(command: str, ceiling: float, basis: str) -> dict[str, Any]:
173
174
  }
174
175
 
175
176
 
176
- def exceeded(command: str) -> Optional[dict[str, Any]]:
177
+ def exceeded(
178
+ command: str,
179
+ *,
180
+ default_ceiling_mb: Optional[float] = None,
181
+ ) -> Optional[dict[str, Any]]:
177
182
  """Build the refusal payload when the ceiling was exceeded — or unusable.
178
183
 
179
184
  Three outcomes, and the middle one is `AUD-601-R01`'s: no ceiling asked for
@@ -182,7 +187,7 @@ def exceeded(command: str) -> Optional[dict[str, Any]]:
182
187
  A gate that was requested and cannot be run must say so; passing it in
183
188
  silence is the failure mode the row was filed for.
184
189
  """
185
- ceiling = configured_ceiling_mb()
190
+ ceiling = configured_ceiling_mb(default=default_ceiling_mb)
186
191
  if ceiling is None:
187
192
  return None
188
193
  peak, basis = peak_rss_sample()
@@ -57,6 +57,7 @@ RULES: tuple[Rule, ...] = (
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
59
  Rule("SEC-009", "SEC", "Anonymous request-chain exception"),
60
+ Rule("SEC-010", "SEC", "HTTP input reaches a concatenated SQL query sink"),
60
61
  Rule("DEAD-001", "DEAD", "Security control present but disabled"),
61
62
  Rule("GATE-001", "GATE", "Custom AOP/security gate can be bypassed by proxy limits"),
62
63
  Rule("GATE-002", "GATE", "Custom gate advice appears to fail open"),
@@ -143,11 +143,11 @@ def _non_coverage_block() -> dict:
143
143
  return block("security_surface")
144
144
 
145
145
 
146
- def _stack_fit_block(spring_detected: bool) -> dict:
146
+ def _stack_fit_block(spring_detected: bool, spring_footprint: Optional[dict] = None) -> dict:
147
147
  """Whether this report's Spring-shaped axes model the repository (CL-19)."""
148
148
  from sourcecode.non_coverage import stack_fit
149
149
 
150
- return stack_fit(spring_detected)
150
+ return stack_fit(spring_detected, spring_footprint=spring_footprint)
151
151
 
152
152
 
153
153
  @dataclass
@@ -168,6 +168,7 @@ class SpringAuditResult:
168
168
  git_head: str = ""
169
169
  generated_at: str = ""
170
170
  spring_detected: bool = False
171
+ spring_footprint: dict = field(default_factory=dict)
171
172
  scope: str = "all" # "all" | "tx" | "security"
172
173
  findings: list[SpringFinding] = field(default_factory=list)
173
174
  limitations: list[str] = field(default_factory=list)
@@ -469,7 +470,7 @@ class SpringAuditResult:
469
470
  # consumer reading a missing key on every repository the case was
470
471
  # written for, unable to tell "this build does not publish it" from
471
472
  # "your stack is modelled".
472
- "stack_fit": _stack_fit_block(self.spring_detected),
473
+ "stack_fit": _stack_fit_block(self.spring_detected, self.spring_footprint),
473
474
  }
474
475
 
475
476
  def to_compact_dict(self, top_n: int = COMPACT_TOP_N) -> dict:
@@ -480,6 +480,55 @@ def _controller_source_file(cir: "CanonicalRepositoryIR", controller_fqn: str) -
480
480
  return ""
481
481
 
482
482
 
483
+ class _SEC010HttpInputConcatenatedQuery:
484
+ """Bounded HTTP-input flow into a known concatenated SQL sink."""
485
+
486
+ pattern_id = "SEC-010"
487
+ severity = "high"
488
+
489
+ def analyze(
490
+ self,
491
+ cir: "CanonicalRepositoryIR",
492
+ tx_index: Optional[TransactionBoundaryIndex],
493
+ root: Optional[Path],
494
+ *,
495
+ model: Optional[SpringSemanticModel] = None,
496
+ ) -> list[SpringFinding]:
497
+ if root is None:
498
+ return []
499
+ # Reuse risk's bounded propagation rather than inventing a second taint
500
+ # analysis. It follows annotated HTTP parameters through positional
501
+ # in-repository calls to the published query-sink vocabulary only.
502
+ from sourcecode.risk import _http_input_query_sinks
503
+
504
+ sinks = _http_input_query_sinks(root, cir)
505
+ findings: list[SpringFinding] = []
506
+ for symbol, trace in sorted(sinks.items()):
507
+ owner = symbol.split("#", 1)[0]
508
+ findings.append(SpringFinding(
509
+ id=SpringFinding.make_id(self.pattern_id, symbol),
510
+ pattern_id=self.pattern_id,
511
+ category="security",
512
+ severity=self.severity,
513
+ confidence="medium",
514
+ title="HTTP input reaches a concatenated SQL query sink",
515
+ symbol=symbol,
516
+ source_file=_controller_source_file(cir, owner),
517
+ evidence={"flow": trace, "scope": "bounded_http_input_to_known_query_sink"},
518
+ explanation=(
519
+ "An annotated HTTP input reaches a query argument constructed by "
520
+ "concatenation. The observed flow is limited to positional calls "
521
+ "inside this repository and the documented query-sink vocabulary."
522
+ ),
523
+ fix_hint="Use a parameterized query and bind the request value instead of concatenating SQL.",
524
+ limitations=[
525
+ "This is not general taint analysis; see the published query-sink vocabulary.",
526
+ ],
527
+ related_symbols=[owner],
528
+ ))
529
+ return findings
530
+
531
+
483
532
  # ---------------------------------------------------------------------------
484
533
  # Default pattern registry
485
534
  # ---------------------------------------------------------------------------
@@ -1130,6 +1179,7 @@ _DEFAULT_SECURITY_PATTERNS: list[SecurityPattern] = [
1130
1179
  _SEC002PreAuthorizeGenericInheritance(), # type: ignore[list-item]
1131
1180
  _SEC003TransactionalOnController(), # type: ignore[list-item]
1132
1181
  _SEC00XConfigurationWeaknesses(), # type: ignore[list-item]
1182
+ _SEC010HttpInputConcatenatedQuery(), # type: ignore[list-item]
1133
1183
  _GATE00XCustomGateAudit(), # type: ignore[list-item]
1134
1184
  ]
1135
1185
 
@@ -1321,6 +1371,12 @@ def run_security_audit(
1321
1371
  _spring_detected = _detect_spring(
1322
1372
  cir, model=model, tx_total=tx_index.stats()["total"]
1323
1373
  )
1374
+ from sourcecode.spring_model import BeanGraph
1375
+ _beans = (model.bean_graph if model is not None else BeanGraph.build(cir)).beans
1376
+ _spring_footprint = {
1377
+ "spring_beans": sum(1 for bean in _beans.values() if bean.spring),
1378
+ "java_files": len(cir.files),
1379
+ }
1324
1380
 
1325
1381
  _profile_state: dict[str, Any] = {
1326
1382
  "selected": [],
@@ -1421,6 +1477,7 @@ def run_security_audit(
1421
1477
  repo_id=_cache_repo_id(Path(root)) if root is not None else "",
1422
1478
  content_id=getattr(cir, "cir_hash", "")[:16],
1423
1479
  spring_detected=_spring_detected,
1480
+ spring_footprint=_spring_footprint,
1424
1481
  scope="security",
1425
1482
  findings=findings,
1426
1483
  limitations=_sec_limitations,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.9.22
3
+ Version: 5.9.23
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
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
59
59
  > question. All analysis is local and deterministic; see [Privacy](#configuration--privacy)
60
60
  > before inspecting a repository whose telemetry policy you need to control.
61
61
 
62
- ![Version](https://img.shields.io/badge/version-5.9.22-blue)
62
+ ![Version](https://img.shields.io/badge/version-5.9.23-blue)
63
63
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
64
64
 
65
65
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -191,10 +191,10 @@ must not be gated on this variable; their budget applicability remains visible i
191
191
  the [defect ledger](docs/DEFECT-LEDGER.md).
192
192
 
193
193
  **Memory is measurable and bounded.** Repository-scoped JSON answers publish
194
- `_meta.memory.peak_rss_mb` and the configured ceiling. For a supervised deep run,
195
- set `ASK_MAX_RSS_MB=<megabytes>`; if the process exceeds it, ASK refuses the
196
- answer with `MEMORY_TOO_LARGE`, including the observed peak and limit. Leave it
197
- unset, or set it to `0`, to disable the ceiling explicitly.
194
+ `_meta.memory.peak_rss_mb` and the configured ceiling. `--agent` applies a
195
+ 1,536 MB ceiling by default; set `ASK_MAX_RSS_MB=<megabytes>` to override it or
196
+ `ASK_MAX_RSS_MB=0` to disable it explicitly. If the process exceeds the ceiling,
197
+ ASK refuses the answer with `MEMORY_TOO_LARGE`, including the observed peak and limit.
198
198
 
199
199
  **Current performance status.** Release 5.9.22 retains the shared Canonical IR
200
200
  path used by `validation` and now passes that same CIR into labelled `data-exposure`
@@ -212,6 +212,14 @@ exit 75 (`--allow-partial` accepts one deliberately). The long-run progress
212
212
  message names the phase it is in and whether that phase has changed, so a slow run
213
213
  is distinguishable from a blocked one.
214
214
 
215
+ **External audit boundary (2026-09-01).** The latest Windows audit rates the
216
+ large-repository experience at **6.6/10** and reports open P0/P1 issues: empty
217
+ successful Markdown `audit-report`, silent `risk` termination, multi-gigabyte
218
+ deep-agent RSS, incomplete parse/coverage disclosure and oversized compact
219
+ surfaces. These findings are tracked in the [external audit](docs/AUDIT-2026-09-01-EXTERNAL-5.9.22.md)
220
+ and [defect ledger](docs/DEFECT-LEDGER.md); they are not closed by the existing
221
+ engineering-host measurements.
222
+
215
223
  The current external audit still records two open availability limits, and they
216
224
  are held open deliberately: `audit-report --format markdown` under contention, and
217
225
  the root compact flow under strict timeouts on very large monorepos. Neither
@@ -1,10 +1,10 @@
1
- sourcecode/__init__.py,sha256=rlhdFHgHxU-_wnswNtAc3jkUHnsGhX1taqMHDQJ5P6c,309
1
+ sourcecode/__init__.py,sha256=bD90W3elmTu9mgmtBKJcMr19al2pGKjdSzwGGF7ucJM,309
2
2
  sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
3
- sourcecode/_build_commit.py,sha256=h33fClasWi1xoqBmi7TKbd8bGOaY-_NiL3paWe84zFg,11638
3
+ sourcecode/_build_commit.py,sha256=HCCDrmPAO_ebM9AhlL3dNAbyYlwq8cgPLNTGTObEGHY,11638
4
4
  sourcecode/access_drift.py,sha256=uYD8ipiEAaTnkz0IhG6ujU_KI3LghdY-6ycurs-BZnc,9355
5
5
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
6
6
  sourcecode/analysis_budget.py,sha256=ZuNEboPF8A6GK-_V7WNt92ezgriga7_Rbr-vvSN4UEE,4183
7
- sourcecode/answer_bounds.py,sha256=GKYAQix01_a760YhvWMa-cXu1yT-y7vFgpofkjEwviU,10424
7
+ sourcecode/answer_bounds.py,sha256=Y7yDvgvb9oSRHFx3SUZVDeXMG1ritxxC8gkB7rycpA8,10889
8
8
  sourcecode/archetype.py,sha256=V_s5grgpbanrXb8qGEQb73W5Cg5iKW9FQHWKgg5KM8Y,41201
9
9
  sourcecode/architectural_baseline.py,sha256=9ds-ZYw4dEJUKWZSdq_ufHq7d2AXnMbEHteSQlT7iu8,26468
10
10
  sourcecode/architectural_delta.py,sha256=JhZVmA0gYUryt4x4ieVuxPbcvoBygHgADZaYJ_o4gdk,15142
@@ -29,13 +29,13 @@ sourcecode/cache_observation.py,sha256=5E2BeqiYE5wBCbvDglkc5A9fTNhXkT_OhcDuuCqRu
29
29
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
30
30
  sourcecode/caller_metrics.py,sha256=zGc3sHRRwEJgAGmyfqEiH69mD9w5rOqMUhzby8Vmm_w,12599
31
31
  sourcecode/caller_reach.py,sha256=M7Ffs5CtlimihfdSy3HtC4WpDP97bRFX15QYOlOfMJw,16626
32
- sourcecode/canonical_ir.py,sha256=KcbR6Vnmo8UbwSbIn6RDAGdNtfPoxz1c_0IVCaoIuss,38779
32
+ sourcecode/canonical_ir.py,sha256=H28TJOG75IMHEkY_YkbJ9LBz2YzgTcNipp2LnYbTYOc,38829
33
33
  sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,12767
34
34
  sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,12934
35
35
  sourcecode/ci_output.py,sha256=X4NRokWWMRF9Ex8OQrEO2g04GzJ00GDP5zYIoqI9YCc,4625
36
36
  sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
37
37
  sourcecode/classifier.py,sha256=ft1RGPIChvO84q2QMrEH93x1J15AXNWMh26lnxvoRao,26476
38
- sourcecode/cli.py,sha256=2d-S0OQQnoF8bHll_MfIuFUSyAVtRl_Nopb1WEBd8Vo,956694
38
+ sourcecode/cli.py,sha256=KvL0q2lG5AaYMVfb2B23OEqKK3M2uH1eaQpkIJxqSH0,960162
39
39
  sourcecode/click_runtime.py,sha256=K2cUdzFhbd6uEUpucWptXE89_z-ZNZGsqgd-34usUXk,1890
40
40
  sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
41
41
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
@@ -102,14 +102,14 @@ sourcecode/license.py,sha256=eIEuIVQXywl_S9LOIACOEpJ4M5RyaPvWrqg_jSNOrXA,31088
102
102
  sourcecode/manifest_cache.py,sha256=qoBn7hPeaMjLHi3higfxsjo7oS7noommRqDnpKP0Ya8,4124
103
103
  sourcecode/mcp_nudge.py,sha256=beNjfyb7z09T1rnS4t6Q4qfbLzZQNaP6ddCA4XpmV0s,3256
104
104
  sourcecode/metrics_analyzer.py,sha256=gLoRWKygF18jLgwsqmGXSWopw-f5iO1VbM7Jjdif6ag,22809
105
- sourcecode/migrate_check.py,sha256=tUfTpsMUv06JaKpHAbW_TX3ejO_L4iugYCb__lidq4U,171476
105
+ sourcecode/migrate_check.py,sha256=OvJ51zcnxYTnPoCAPk63ua4En8qdGpvbU8HHDeQT4H4,172215
106
106
  sourcecode/migration_apply.py,sha256=36E9IUq7RbpGVt6RZ8PlSZKgDoEMHorZrXw6UVWPnpw,8692
107
107
  sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg,9359
108
- sourcecode/non_coverage.py,sha256=Tl04OemPnxnOuWISMF6gAhDYx-P0LP6_l-PbpyvBJb4,30634
108
+ sourcecode/non_coverage.py,sha256=0voxV7GdEZBaDHKDaw9RjHeb2qH-8ZdAUAOiu71vd8I,31508
109
109
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
110
110
  sourcecode/openrewrite_recipe.py,sha256=0pS-61falE6rSfkos2MPnmoDamvoeTT-8Kmj7smj_DI,12987
111
111
  sourcecode/output_bounds.py,sha256=BvaRQY5nuytgfNUJHyO7JDLOHuSy0vdjqasISxmxLxI,15516
112
- sourcecode/output_budget.py,sha256=AHwxnfToUk9a3MJdcq0WDUiFbyNla9VWxdE0pC7UWfY,15617
112
+ sourcecode/output_budget.py,sha256=aiIsX7sefUB1CHiBTt-SskCcdxcbczYC8LdD6ppvSPA,16352
113
113
  sourcecode/output_ceiling.py,sha256=dY007vP4V3T9AI09EIz_W36A3FWARQiege_BZ72uMs0,34757
114
114
  sourcecode/output_encoding.py,sha256=OxiZQtr7uWBDUtblNQviNHsTH1Xo9AZEpsegPlQWKFk,2139
115
115
  sourcecode/output_sink.py,sha256=Eya-PnXxqc3RGulJoLhuC9LUF73SZD0aO5O2B0xAMXU,8762
@@ -148,12 +148,12 @@ sourcecode/remediation_report.py,sha256=z28x-u618feU2d7ol_FW7IfhUOq6iETCfdpl7zJC
148
148
  sourcecode/remedies.py,sha256=V2_cD4pLq8bKRkzF-3RGFoiu1xWYZS384WDVp1MrlZw,10225
149
149
  sourcecode/rename_refactor.py,sha256=oASZIf7TxcOBy3RQ0FuOe-4HlNW_JOu9RW9JqCxGNBE,17225
150
150
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
151
- sourcecode/repository_ir.py,sha256=GLYSuPuFqwWdIUtZsUNFctl5DgUaOsDKEK_rwnS-F9g,526567
152
- sourcecode/resource_budget.py,sha256=Bo-XMvVK9XyHY_S06fVirZrhXgwmnr3XI38-Li55NWY,7767
151
+ sourcecode/repository_ir.py,sha256=O6d1IWp0-RlJyx4OHBGcflvNA0CgsTS7td7UlI9t7S8,528687
152
+ sourcecode/resource_budget.py,sha256=wqe6WuXlEfxAgBGLj0QX47HmV8WldaYWQjLe-TMR6hk,7938
153
153
  sourcecode/ris.py,sha256=HsLVHZfseEHNqMyxrj8SvrzUK7jE_85lsWjHtsEw9ik,29196
154
154
  sourcecode/risk.py,sha256=rG7fqDIjyhtvB0Qje0d2ZEUl5kemMBwbuOJG8ZJxqrQ,102552
155
155
  sourcecode/risk_model.py,sha256=RoC-G15qZP5BLEwwmcr4eJ0APgvIbh1WTqhTVT-5YFE,10895
156
- sourcecode/rule_catalog.py,sha256=QCx6ToFTMCtDt5uuX1uBMLuw3b9Cd92gGRsX34kud4U,5328
156
+ sourcecode/rule_catalog.py,sha256=7lwyNe_sA7M9KtoKXVf9qQYBlo7VEPQ3IK1KiGSqTts,5408
157
157
  sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
158
158
  sourcecode/runs.py,sha256=o4oXkB3WFJGScbK_C10OAWzsE18B9Zc55u_IE8_yl4M,6291
159
159
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
@@ -178,12 +178,12 @@ sourcecode/serializer.py,sha256=zcwsbkC84XKhoDEMv5YR4pP_CnH9iSB1_GlUUvfF8es,1598
178
178
  sourcecode/servlet_surface.py,sha256=CeiebkxLXKojUQxIf7HV7tdQAwLM32ggDUwBnVQIjdo,13499
179
179
  sourcecode/source_text.py,sha256=R52gmSljz7L6YVPbixv5W-LOZ5eKUVvSUP3S-QH_IWc,9047
180
180
  sourcecode/spring_event_topology.py,sha256=gm_1J7Dj9JQ1tXnkOzIfjPNFxPLmSN56Vcj8RisqB5I,18093
181
- sourcecode/spring_findings.py,sha256=U5gMDdSdhpJLstt3ghG4y5CCS9nzIU9NuRMHpNu7FpY,27766
181
+ sourcecode/spring_findings.py,sha256=pvH7Qg7MHv_6m1QyxoVBppCceK_U4P_MKbU6YdQivUM,27922
182
182
  sourcecode/spring_impact.py,sha256=7gcP4MJcRl8SF5UjYnZXsvtwvIollUtYXlUn08q-JMM,98677
183
183
  sourcecode/spring_model.py,sha256=MBUKR1tes5UVGxtYiv4J2WrydgOp_zReek7P7hO50hA,28962
184
184
  sourcecode/spring_profiles.py,sha256=jDNBVhEZQAZXrpqt85FkP2URsMw4YWhQo3W3SLHuwpM,19617
185
185
  sourcecode/spring_properties.py,sha256=kPTk5qAJxdbHc1hclhHAP0O-hCIzBn8PTh3lzl-I4a4,8400
186
- sourcecode/spring_security_audit.py,sha256=mJxrB-uKvMh2aF3Q8cFVOC8yOYb2JW8P-yK-paMTQBQ,71218
186
+ sourcecode/spring_security_audit.py,sha256=uEAuHGcPYbmhHeL4k5sDqBvUs5RaN6j-zM6oJqv1yrk,73734
187
187
  sourcecode/spring_semantic.py,sha256=zrUYWF7Gnz6hpP_3hRveHmGEntT95WpgXt6u7PzLSGg,16835
188
188
  sourcecode/spring_tx_analyzer.py,sha256=cFK9M3madb7dKbJ_d97cO-1fGMUPDOwB8KoJ-oocMeY,50951
189
189
  sourcecode/sql_taint.py,sha256=As1DbEdjXMBFjCTXU5ojksOzuKkP6LmnuMVnzzmckzc,26998
@@ -275,13 +275,13 @@ sourcecode/telemetry/consent.py,sha256=-Wb8BFGXEexOs8M0Eja7F-T6ZtLNPeRoZnHcHvlP7
275
275
  sourcecode/telemetry/events.py,sha256=l3Z2SQUO4_C2D2ItXOOdVNimAmHedUHnUxqYFTxnsAE,2900
276
276
  sourcecode/telemetry/filters.py,sha256=-lqT2oorECuqEIdeHb--7AZ6BCaUx0bya720a-Z5x6w,7335
277
277
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
278
- sourcecode/_docs/DEFECT-LEDGER.md,sha256=27Td56IIsaXPz9udDpLMiMDP4RMeaQTh5-FUqucpee4,1167577
278
+ sourcecode/_docs/DEFECT-LEDGER.md,sha256=OpDD8MD9feF0s8sLstG9UtLjXFijaakvnEO1zQqsliI,1170077
279
279
  sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
280
280
  sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
281
- sourcecode/_docs/USER_GUIDE.md,sha256=A__FLktxJ8S-VMUAQ5OxPRfUkKQ7yKy6FQbWxiZvc6c,108275
282
- sourcecode-5.9.22.dist-info/METADATA,sha256=Plv_fedlIoI6bKsHB5Q5zFlOlC_nK-KN7gLa34KVsb8,66673
283
- sourcecode-5.9.22.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
284
- sourcecode-5.9.22.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
285
- sourcecode-5.9.22.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
286
- sourcecode-5.9.22.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
287
- sourcecode-5.9.22.dist-info/RECORD,,
281
+ sourcecode/_docs/USER_GUIDE.md,sha256=5gVJnyzQHCNyJ3DgIU6VOJZCrjs9l7CQCrd9NLKcm2Y,108622
282
+ sourcecode-5.9.23.dist-info/METADATA,sha256=U5g82Fb-Tg9MxLP6x66-jEXfh5_EZCu6TkNLKwLEgfo,67221
283
+ sourcecode-5.9.23.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
284
+ sourcecode-5.9.23.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
285
+ sourcecode-5.9.23.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
286
+ sourcecode-5.9.23.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
287
+ sourcecode-5.9.23.dist-info/RECORD,,