sourcecode 5.8.17__py3-none-any.whl → 5.8.18__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.17"
7
+ __version__ = "5.8.18"
@@ -16,7 +16,30 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
16
16
 
17
17
  ## Current Synchronization
18
18
 
19
- **Latest attached audits — release `5.8.16`, 2026-08-21, two independent rounds.**
19
+ **Latest attached audits — release `5.8.18`, 2026-08-21, two independent rounds, and the
20
+ first pass since 5.8.2 that opens with a regression.** Round A (`saint-server`/MSAS,
21
+ 3 342 Java files, 17 commands, 29 invocations, same HEAD as the previous round bit for
22
+ bit) scores **8.77/10** — up from 8.21 — with **zero false statements about the
23
+ repository for the second consecutive run**, 6 of 6 reported defects closed and verified
24
+ from outside, and **five new contract defects, four of them the same class as the fix that
25
+ preceded them**. Round B (a 12-repository bank, ~150 invocations, the §7 measurement
26
+ protocol applied: idle host, interleaved, ×3, median, cache state declared per row) closes
27
+ **8 of the 10 new rows and 2 of the 9 inherited ones**, records **zero content regressions
28
+ and no performance row above the ×1.25 threshold**, and finds **two regressions, one of
29
+ them a P0**: `compare` is dead in every valid invocation with an unhandled `NameError`,
30
+ and `project_summary` publishes an Apache licence header as the description of the project
31
+ on 3 of 8 repositories.
32
+
33
+ **The two rounds agree on the shape of what is left, and it has moved again.** The
34
+ previous pass ended with contracts that were *absent*; this one ends with contracts that
35
+ *contradict each other across authorities*: two homonymous fields declaring the same unit
36
+ and the same population and differing 16×, one budget published three times with three
37
+ values, a help page denying a scope the fix beside it granted, and two ledger rows marked
38
+ `closed` that do not reproduce as closed in the field. **A row marked closed that is not
39
+ closed is worse than an open one — nobody looks at it again** — so those are carried at P1
40
+ below rather than left in their original sections.
41
+
42
+ **Previous attached audits — release `5.8.16`, 2026-08-21, two independent rounds.**
20
43
  Round A (`saint-server`, 3 342 Java files, 17 commands, 25 invocations) scores **8.21/10**
21
44
  with **zero false statements about the repository** and two contract defects. Round B (a
22
45
  16-repository bank, 49 to 24 073 Java files, **240 invocations, 0 crashes, 0 tracebacks**)
@@ -28,10 +51,11 @@ previously open defects by correction with **zero regressions among the inherite
28
51
 
29
52
  Both rounds agree on where the product now fails, and it is not the Java/Spring analysis:
30
53
  it is the contract an agent consumes programmatically — the write guard, repository
31
- identity, list units, flag scope, and published budgets. The seventh-pass section below is
32
- the only current queue; older rounds remain for traceability.
54
+ identity, list units, flag scope, and published budgets. Its queue is closed in `5.8.17`;
55
+ **the eighth-pass section below is the only current queue**, and older rounds remain for
56
+ traceability.
33
57
 
34
- **Mechanisms confirmed in source during intake** (not taken on the reports' word):
58
+ **Mechanisms confirmed in source during the seventh-pass intake** (not taken on the reports' word):
35
59
  `verify --init` writes with no `readonly.guard` (`cli.py:10706-10708`) while five sibling
36
60
  writers guard; `spring-audit` derives `repo_id` from the CIR content hash
37
61
  (`spring_security_audit.py:1416`, `spring_tx_analyzer.py:1081`) while every other surface
@@ -52,10 +76,13 @@ itself rather than merely under-declaring.
52
76
  **Release status:** entries marked “pending `5.8.14`” in their historical wording are
53
77
  shipped in `5.8.14`; the correction battery fixes listed above are included in `5.8.15`.
54
78
 
55
- **Current release:** `5.8.16` includes two post-audit corrections: compact cold-start
56
- identity is preserved for `no_ris`, and manifest-cache keys include Python requirements
57
- files. The testing-repository hygiene exercise changed no external repository; it
58
- completed 108/108 read-only workflow runs with valid JSON and exit code 0.
79
+ **Current release:** `5.8.18` closes the whole eighth-pass queue, one commit per row, and
80
+ includes the targeted release battery. `5.8.17` before it carried the seventh-pass queue;
81
+ `5.8.16` before that carried two post-audit
82
+ corrections: compact cold-start identity is preserved for `no_ris`, and manifest-cache keys
83
+ include Python requirements files. The testing-repository hygiene exercise changed no
84
+ external repository; it completed 108/108 read-only workflow runs with valid JSON and exit
85
+ code 0.
59
86
 
60
87
  The battery also closed the following reproducible contract defects in `5.8.15`:
61
88
  `migrate-apply` is registered across help, progress, path-admission, output-purity,
@@ -73,6 +100,107 @@ with neither parseable stdout nor its requested artifact) or `BUG-6`
73
100
  queue position. `BUG-1` and `BUG-5` gain witnesses on public OSS repositories, recorded
74
101
  under their own rows rather than as new ones.
75
102
 
103
+ ### Eighth Audit Pass: `5.8.17` findings, closed in `5.8.18` / MSAS + 12-repository bank / 2026-08-21
104
+
105
+ The current queue. Ordered as the correction order: **regressions first, then the rows a
106
+ previous pass marked closed that the field does not reproduce as closed, then new contract
107
+ defects, then residuals.** Canonical IDs are `AUD-592-*` (bank round, reporter IDs `A-*`,
108
+ `B-*`, `N-*`, `D-*`) and `AUD-593-*` (MSAS round, reporter IDs `N-0*`). Rows held elsewhere
109
+ in this ledger are listed after the table as mappings, not as new defects.
110
+
111
+ | ID | Severity | Current status | Required direction |
112
+ |---|---|---|---|
113
+ | `AUD-592-R01` / `A-1` | **P0 — regression** | **open, confirmed in source, and it is two defects from one error.** `compare` raises `NameError: name '_compare_scan_trunc' is not defined` on **5 of 5 valid invocations** — Rich traceback on stderr, zero bytes on stdout, no error envelope, exit 1 — ending a 240-invocation streak with no traceback. The assignment introduced by `AUD-591-A06` landed **inside `plan_cmd`** (`cli.py:8874`, beside `plan`'s own `find_java_files` at `:8873`) while the read is in `compare_cmd` (`cli.py:8972`); the two functions are `8818-8901` and `8903-9002`. Verified by reading the source at HEAD, not from the report. Second effect: `plan` computes its truncation state and **discards it** — nothing in the payload carries it, so `plan` answers `resolution: not_found` over a walk that stopped at its cap, offering candidates from a different repository. Third effect: `compare`'s other `AUD-591-A06` fix, `data.setdefault("scope", ...)` at `cli.py:8975`, is **after the line that raises** and has never executed, so the cross-repository candidate leak is still undeclared. | Read `_last_scan_truncation()` at `compare`'s own call site, immediately after `cli.py:8965`; rename the `plan_cmd` variable to `_plan_scan_trunc` and **bind it to the payload** — an unused walk-state variable is the defect, not the fix. Regression: an AST assertion that no `_*_scan_trunc` name is read in a `FunctionDef` that does not assign it, plus one execution test per command that owns a walk. A unit test that imported `compare_cmd` would not have caught this; one that *calls* it would. |
114
+ | `AUD-592-R02` / `N-4`, reopens `AUD-513-N04` | **P1 — regression** | **open, confirmed in source, on 3 of 8 repositories (37,5 %), in `--compact` and `--agent` alike.** `project_summary` publishes an Apache-2.0 licence header as the description of the project (struts), a 30-character horizontal rule (halo) and a setext underline (shopizer) — and `summary_basis` asserts `"README descriptive prose"` over all three. In 5.8.16 this field was `null` with `"no descriptive section found in README"`, the literal acceptance text of `AUD-513-N04`; the field went from honest to false. Mechanism, four separate holes, all read at HEAD: HTML comments are detected **only on their opening line** (`summarizer.py:335-337`) with no `in_html_comment` state — three lines above, `in_code_block` does exactly that for fences (`:326-330`) — so `<!---` on line 1 flushes and lines 2-15 of the licence become the first paragraph; headings are ATX-only (`:331`), so setext titles underlined with `===` are content; the short-fragment floor is `len(paragraph) < 30` **strictly** (`:359`), so a rule of exactly 30 dashes survives; `_LICENSE_MARKETING_RE` (`:266-281`) matches product-tier and marketing phrasing and **carries no Apache/MIT/GPL/BSD boilerplate pattern at all**; and `summary_basis` is assigned unconditionally to whatever paragraph survives the filters (`:382-385`) — a restatement of the code path, not a property that was checked. | Add `in_html_comment` symmetric to `in_code_block`; recognise setext underlines as headings, not content; extend the licence regex with the four standard boilerplates; and make `summary_basis` describe the path actually taken — where nothing is verifiably descriptive, `null` plus the 5.8.14 text. **The mechanical cause of the reopening is that no README fixtures exist**: add the three witnesses from the bank as fixtures (HTML licence comment, 30-character rule, setext `===`) in the same commit as the fix. |
115
+ | `AUD-593-N01` / `N-01` | **P1** | **open, and it falsifies the closure assertion of `AUD-590-B02`.** For one symbol, one tree, one HEAD, consecutive invocations, both at `depth=4`: `impact` publishes `direct_caller_count: 34` (210 reference sites) and `impact-chain` publishes `554` (3 103), both under the literal unit `"distinct caller classes"`. The product's own comparability rule (`CALLER_METRIC_RECONCILIATION`) is *"two figures are comparable only when both match"* — they match, and differ 16×. **The authority is what is wrong, not only the number**: `FAN_IN_FIGURES` (`caller_metrics.py:80-110`) declares both rows `unit=distinct_classes, population=references_excluding_imports`, and the populations are not the same. Measured mechanism: `impact-chain` expands the seed set with every member of the interfaces the target implements (CH-001b, `spring_impact.py:1110-1135`) and then takes depth 1 **from the expanded seeds** (`caller_reach.py:309-336`), so its "direct" includes callers that name a sibling implementation; `impact` admits only references that name the target and keeps interface-mediated callers on a separate axis (`repository_ir.py:9263`, `:9596-9606`). Corroborating symptom: `chain_classes_total`, declared *"unbounded above by any direct-caller count"*, equals `direct_caller_count` exactly (554), and `indirect_callers` is `[]` — that emptiness is the hub guard capping the walk to depth 1 (`caller_reach.py:319-321`) and **is** declared, so it is not a confident zero. The published closure assertion `len(impact.direct_callers) == chain.metadata.direct_caller_count` measures 30 (list, capped from 34) against 554: it passes on a fixture where the two coincide by accident. | Decide which figure the `direct_caller_*` namespace owns, and fix the **catalogue** either way: either `impact-chain` measures depth-1 fan-in on the unexpanded seed (≈34), or the figure is renamed out of that namespace and its `population` becomes its own tag — an interface-expanded seed set is not `references_excluding_imports`. Regression is parametric over the catalogue, not over `CommonService`: for every pair of `FAN_IN_FIGURES` rows sharing `(unit, population)`, the emitted values must be equal, run over ≥3 symbols of different fan-in **including one that trips the hub guard** — the condition under which the divergence appears, and the reason two previous corrections of this class (`callers_total`, `P0-2`) closed a name and left the divergence. `chain_classes_total ≥ direct_caller_count` with strict inequality wherever uncapped transitive reach exists. |
116
+ | `AUD-592-B01` / `B-8` | **P1 — closed row that does not reproduce as closed** | **open; the ledger row overstates its own fix.** `B-8` is recorded as *"compact posture retains one unresolved sample when the total is non-zero"* (`52593b6`). The code retains a sample only when the total is **exactly one**: `_posture_limit = 1 if summary["unresolved"] == 1 else 0` (`cli.py:12653-12654`). Field measurement on `mall`: `summary.unresolved: 3`, `unresolved: []`, `unresolved_cap: {total: 3, shown: 0, omitted: 3, limit: 0}` — and it is the only cap in that payload with `limit: 0` while `undecided_cap` and `evidence_cap` carry 200. `--limit 10` returns all three, so the analysis is intact and only the compact contract is wrong. | Either make the code do what the row says (retain ≥1 sample whenever `summary.unresolved > 0`) or correct the row to the narrower promise and reopen the defect it leaves. Do not leave `closed` standing over `shown: 0`. Add an assertion that no `cap_effect` receives `limit=0` by default on a path where `--limit 0` is published as *"no cap"* (`AUD-513-N08`), so the published semantics and the internal default cannot invert each other. |
117
+ | `AUD-592-A02` / `A-1` second half, `§12.5` | **P2** | **open — scan truncation is declared by 1 of 4 commands that own a walk.** Over a truncated scan (cap observed at **8 000 files**), `impact` publishes `scan_truncated`; `plan` and `impact-chain` answer `not_found` with `scan_truncated: null`, and `endpoints` exits 1 with no payload — every one of them an exclusion derived from an incomplete population. `compare` cannot be measured (`AUD-592-R01`). Second, narrower defect in the same row: the root `--help` describes the cap as *"the scan stopped at 25 000 directory entries"* — a different limit in a different unit from the 8 000-file cap the field observes. | Publish the truncation beside every walk that can end in `not_found`, `candidates` or a census — the seventh pass already recorded that `extract_java_endpoints` does its own uncapped `rglob`, so this must be read at each call site, never centrally. Reconcile the two caps in the help text or declare both with their units. |
118
+ | `AUD-593-N02` / `N-02` | **P2** | **open — the `--compact` budget is published three times, in two shipped authorities, with three values.** `ask --help` generates *"~7K tokens (30 KB, chars/4)"* from `output_budget`, the module that enforces the limit — this half is the `AUD-590-B03` fix and it is correct. The packaged `docs/USER_GUIDE.md` is still hand-maintained and disagrees with it twice: `:111` *"A ~10K-token subset"* and `:243` / `:1366` *"~2,500–4,000 tokens"*; `--agent` carries a fourth figure at `:254` / `:1367` (*4,500–5,500*). Measured payload: 22 390 B ≈ 5 597 est. tokens — inside the help ceiling, **+40 % over the guide's table**. `grep '7K tokens' *.py` finds no literal: the help is generated and the guide is not, and that asymmetry is the defect. | Generate every token figure in the packaged docs from `output_budget.token_estimate`, or delete them and point at `--help`. Extend the existing release assertion (today help ↔ payload ↔ section registry) to cover `docs/*.md`: a token band in a shipped `.md` that does not come from that module fails the battery. Packaged documentation is a published authority and belongs inside the same assertion as the help text. |
119
+ | `AUD-593-N03` / `N-03` | **P2** | **open — `--help` denies the scope that `AUD-590-B04` had just granted.** With `ASK_MAX_ANALYSIS_SECONDS=90` the root analysis prints *"ask is a repo-wide analysis and the configured budget is 90s. Budget read from ASK_MAX_ANALYSIS_SECONDS=90 (process environment)"* — the fix works. The help page beside it still reads *"deadline only for spring-audit, risk, audit-report"* and *"Other commands are not time-bounded by this variable"* (`cli.py:621-622`). The root analysis is the most expensive command in the product and the one an agent most needs to bound; a reader of the help will not set the variable there. | Generate that line from the same registry `_build_analysis_classes` populates (the root is keyed there as `B14`), never by hand. Assertion: the set of commands the help names equals the set the preamble emitter covers. |
120
+ | `AUD-593-N05` / `N-05` | **P3** | **open — a figure emitted outside the catalogue that forbids it.** `via_interface_resolution[].caller_count` publishes 37 where every other surface of the same fact says 23 (`interface_mediated_caller_count`, the list length, and the `explanation` prose), and it carries no `_unit` while the rest of the payload does. Both are legitimate and neither is declared: `caller_count` is `len(_iface_callers)` — raw caller symbols of the interface, before the `not in all_affected` filter and before normalisation to classes (`repository_ir.py:9248-9251`); the 23 is `caller_classes(_iface_mediated_callers, ...)` (`:9603`, `:10173`). It is also **not in `FAN_IN_FIGURES`**, which states that a figure absent from it may not be emitted — so `tests/test_fan_in_authority.py` did not catch a fan-in figure added in this release. | Give it a unit and a `FAN_IN_FIGURES` row, or rename it to `interface_caller_symbols`. Make the authority test sweep the **emitted** keys against the catalogue rather than the catalogue against itself; a catalogue that only validates its own rows cannot see an unregistered emission. |
121
+ | `AUD-593-N06` / `N-06` | **P3** | **open, latent — a silent `[:30]` where every sibling list declares its cap.** `out["interface_mediated_callers"] = _iface_mediated_classes[:30]` (`repository_ir.py:10172`) with no `*_cap` block, in a payload where `direct_callers_cap`, `indirect_callers_cap` and `security_surface_affected_omitted` all carry `total`/`shown`/`omitted`/`direction`/`how_to_read`. On MSAS the list is 23, so nothing is visible; the first repository with more than 30 interface-mediated callers loses the excess without a word. | Route it through the same `_cap_effect(...)` used 27 lines above (`repository_ir.py:10145`). |
122
+ | `AUD-592-D01` / `D-1` | **P3 — residual of `AUD-591-A08`** | **open, confirmed in source.** The write inventory in the root help says `migrate-check` writes *"only with `--history-dir`"* (`cli.py:296`). `--history-dir` only relocates the destination; the flag that writes is `--snapshot`, as `migrate-check --help` states correctly and as the code comment at `cli.py:12980-12986` states explicitly. Verified in the field: `migrate-check --history-dir <path>` exits 0 with zero writes and no guard message, while `--snapshot` fires the guard correctly. `AUD-591-A08`'s assertion checks that each cited flag **exists** in that command's parser — `--history-dir` does — not that it is the flag that triggers the write. | Key the inventory on `(command, triggering flag, path)` taken from the guard site itself, as `AUD-591-A08`'s own acceptance text required. An auditor tests what the inventory lists: this is the same mechanism by which `AUD-591-A01` went unfound. |
123
+ | `AUD-592-D02` / `D-2` | **P3 — residual of `AUD-591-A02`** | **open.** `--min-band` filters and validates (`cli.py:11294-11308` in `risk`, `:11563-11568` in `enrich`) but publishes no `_filter` block, while its sibling `--band` publishes one through `_apply_selection`. A consumer cannot tell a filtered answer from a small one. | Route `--min-band` through `_apply_selection` like every other selection flag, or publish the same `_filter` shape from wherever it is applied. |
124
+ | `AUD-592-D03` / `D-3` | **P3 — residual of `AUD-591-A02`** | **open.** `enrich --rule NOPE-999` exits 0 with an empty result; `spring-audit --rule NOPE-999` exits 1 against the rule catalogue. One flag name, two contracts. Measured with a synthetic SARIF 2.1.0 (2 results): `--rule`, `--band` and an invalid `--band` all behave correctly in `enrich`; only unknown-rule validation is missing. | Validate against the same catalogue in both commands. `enrich` is experimental-tier, which sets the severity, not the contract. |
125
+ | `AUD-592-D04` / `build_commit` | **P3 — process** | **open, fourth consecutive report.** `ask version` publishes `build_commit: unrecorded`, so no measurement in these ~150 invocations is attributable to a commit and *"fixed in 5.8.17"* is not falsifiable from outside. | Stamp the commit at build time. It is the cheapest row in this table and it is the precondition for every other row's verification. |
126
+
127
+ **Mapped, not new — inherited rows this pass re-witnesses.** `B-6` (partial answers exit 0)
128
+ keeps its row and gains its best measurement yet: overrun ×5.4 → ×4.2 → **×1.39** (median
129
+ 20.90 s of 70.74/20.90/19.70 against a 15 s budget), with a `_partial` block that is
130
+ exemplary (`phases_completed`, `phases_pending`, `why_stopped`, and *"every count here is a
131
+ FLOOR"*) — and `exit 0` on all three runs, so a pipeline reading `$?` passes with two of
132
+ three phases unexecuted. **`AUD-591-A09` is the template**: `exit_code` / `gate_exit_code` /
133
+ `gate_exit_code_basis`, or an explicit `--allow-partial`. `B-3` (`--agent` versus
134
+ `--compact`) gains a witness at 3 342 files: 35 357 B ≈ 8 839 est. tokens, **+61 % over the
135
+ only published band**, 16 blocks present in `--compact` and absent from `--agent`
136
+ (`transactional_boundaries`, `mybatis`, `spring_profiles`, `deployment`, `env_map`,
137
+ `stacks` among them), and `sibling_view` still names no block; on the bank round the two
138
+ views are no longer a subset relation at all (9 blocks agent-only, 15 compact-only) and the
139
+ ×2.25 time overhead is gone (×1.02). Substantively, `security_surface` is absent from
140
+ **both** channels on a repository with 368 security findings and 725 routes open by rule —
141
+ the predicate is disclosed under `AUD-590-B03`, not closed. `N-5` (an advisory saying
142
+ *"8 372 Java files"* with no population label while the progress line says
143
+ `[population=production_java_sources]`) remains the unclosed half of `AUD-588-B11`; the
144
+ label exists (`cli.py:10278-10280`) and reaches the progress lines and the payload but not
145
+ the advisory emitted from `phased_run.py:367`. `D-09` (cost anchors stamped *"on 5.1.0"* in
146
+ a 5.8.17 build — now counted: **17 anchors on 5.1.0, one each on 4.18.0, 5.0.0 and 3.2.2**,
147
+ in a `cache model` output presented as operational guidance) belongs to `BUG-5`. `N-6`
148
+ (cold `spring-audit` on `openmrs-core`) belongs to `BUG-1` and **improved**: 8.86 s → 8.06 s.
149
+
150
+ **Closed by correction and re-verified from outside in this pass — do not re-derive.**
151
+ `AUD-591-A01` (8 of 8 write paths refuse, verified by filesystem footprint rather than
152
+ `git status`, `ASK_RUNS_IN_REPO=1` included), `AUD-591-A02`, `AUD-591-A03` (and the
153
+ proposed highest-priority probe is retired: `rename-class --from O` exits 1, as the seventh
154
+ pass had already established), `AUD-591-A05`, `AUD-591-A06` (50.30 s → 0.43 s, and the
155
+ sweep it asked for holds: **6 of 6 error paths ≤0.54 s**), `AUD-591-A08` (modulo
156
+ `AUD-592-D01`), `AUD-591-A09`, `AUD-591-A10`, `AUD-590-B01` (`repo_id` cardinality 1 with
157
+ `repo_id_basis`; the content hash lives on as `content_id`), `AUD-590-B02` (the *lists* —
158
+ the counters above it are `AUD-593-N01`), `AUD-590-B03` (in `--help`; the packaged guide is
159
+ `AUD-593-N02`), `AUD-590-B04` (both halves: a `partial-analysis-v1` stub with `partial:
160
+ true` where 5.8.16 left zero bytes, and an 807 B preamble byte-compatible with
161
+ `spring-audit`'s 813 B), `AUD-590-R01` (`route_census` — and it **refuted the auditor's own
162
+ double-counting hypothesis with the product's data**: `mappings 3763` against
163
+ `on_rows 1120`, with `distinct_routes ≥ mappings`), `AUD-591-Q01`, `B-7` (closed by
164
+ deletion, which was the right answer: `total_ms` is gone, replaced by
165
+ `wall_ms`/`measured_ms`/`unaccounted_ms`/`unaccounted_pct` with arithmetic exact to the
166
+ decimal and a `unit` explaining why the remainder is published rather than distributed).
167
+
168
+ **Verified and not defects — do not spend effort.** `posture --profile` accepting an
169
+ undeclared profile is **by design and correctly declared**: it is a hypothesis input, not a
170
+ catalogue name, and the payload publishes `profiles_requested` beside `profiles_declared`
171
+ (the bank round withdrew this finding itself after reading the payload). `pr-impact` on a
172
+ clean tree exits 1 with a typed `INVALID_INPUT` naming the empty diff. `pr-impact` over
173
+ three classes refuses with `OUTPUT_TOO_LARGE` and names `--output` — a ceiling as contract,
174
+ not a truncation. `verify` publishes `exit_code: 2` matching the process (`AUD-591-A09`).
175
+ `data-exposure` still answers `answered: false` rather than inferring labels. Cache
176
+ freshness reporting `STALE` at delta 0 commits is `Uncommitted: True` over the auditor's own
177
+ untracked files. `repo_id` absent from `endpoints`, `posture` and `--compact` is coverage,
178
+ not a defect — the criterion was cardinality 1 among emitters, and the help no longer
179
+ promises it.
180
+
181
+ **Process findings of this pass, and they are the same finding twice.**
182
+
183
+ 1. **The class survives the fix, four times out of five.** `AUD-590-B02` (unit absent from
184
+ the lists) returned as `AUD-593-N01` (unit present and false in the counters) and
185
+ `AUD-593-N05` (a new field shipped without one); `AUD-590-B03` (two figures in one help
186
+ page) returned as `AUD-593-N02` (three figures once the packaged guide is counted) and
187
+ as the `--agent` band in `B-3`; `AUD-590-B04` (a variable invisible to the root) returned
188
+ as `AUD-593-N03` (a help page denying the scope the fix granted). The seventh pass named
189
+ this pattern its highest-value finding and wrote the rule — *every acceptance criterion
190
+ carrying a sweep clause is closed with a parametric test over the catalogue* — and the
191
+ two classes closed **as sweeps** in that pass (write guards 8/8, numeric bounds 7/7, and
192
+ now error-path cost 6/6) did not recur, while three of three closed by instance did.
193
+ `FAN_IN_FIGURES`, `COMMANDS_THAT_WRITE` and the serializer's section registry are already
194
+ catalogues: the test goes over the catalogue, never over the reported symbol.
195
+ 2. **Two rows in this ledger were marked `closed` while the field reproduces them open**
196
+ (`AUD-592-B01`, and `AUD-588-B11`'s advisory half). Both were closed against a surface
197
+ narrower than the row's own wording. A closure whose assertion cannot fail on the tree
198
+ that produced the defect is not a closure: `AUD-593-N01`'s assertion passes today only
199
+ because the fixture makes both figures coincide.
200
+ 3. **Packaged documentation is a published authority.** `AUD-593-N02` exists because the
201
+ release assertion covers help ↔ payload and stops at the package boundary. Two stale
202
+ figures in a shipped `.md` are enough to contradict the correct one in the help.
203
+
76
204
  ### Seventh Audit Pass: `5.8.16` / 16-repository bank + `saint-server` / 2026-08-21
77
205
 
78
206
  Ordered as the correction queue, regressions and permissive-direction failures first. Each
@@ -184,7 +312,7 @@ partial, `F03` open, `F04` explicit opt-in and `F05` locally verified only.
184
312
  | `N-6` | Medium | **not reproduced after cache fixes** | Isolated cold `openmrs-core`: 5.18 s wall / 4.76 s internal, all four layers cold. Retain the field report as an environment-specific reproduction request. |
185
313
  | `B-3` | Medium, core/supported | **closed by `773268d`** | Help no longer claims maximum signal; `sibling_view` points to compact. |
186
314
  | `B-6` | Medium, core | **closed by `17c6e06`** | Partial answers publish `overrun_bound` and its wall-clock basis. |
187
- | `B-8` | Low | **closed by `52593b6`** | Compact posture retains one unresolved sample when the total is non-zero. |
315
+ | `B-8` | Low | **reopened 5.8.17 — see `AUD-592-B01`** (was: closed by `52593b6`) | The fix retains a sample only when the total is exactly 1, not whenever it is non-zero, so `total: 3, shown: 0` still reproduces. |
188
316
  | `N-5` | Low, core | **closed by `c68083f`** | Progress and payload name the Java population. |
189
317
  | `N-7` | Low, core | **closed by `082e9de`** | Bounded fuzzy candidates and matching message hints handle close typos. |
190
318
 
@@ -207,7 +335,7 @@ automation remains deferred; provider non-coverage is the current product behavi
207
335
  | `AUD-513-N01` (`N-1`) | **closed in 5.8.14** | `rename-class` rewrote three Petclinic files under both `ASK_READONLY=1` and `--no-write`, then exited 0. The audit restored the tree; this is a write-policy breach, not an artifact-directory exception. `readonly.guard_mutation()` now refuses before any planned source write or physical rename; help names the command as mutating and regressions cover both readonly controls plus `--dry-run`. |
208
336
  | `AUD-513-N02` / `B-4` / `N-5` | **N02/B-4 implemented in 5.8.14; N-5 open** | `posture.summary.unconditional` counted 1,697 on a non-Spring Struts tree and 1,573 on Mall, and `security` was `0/0/0` with no population. The posture projection now uses the canonical Spring IoC bean population, publishes its population/unit and separates unconditional security beans. Regressions cover non-Spring, mixed-bean and conditional/unconditional security fixtures. Progress/advice still use incompatible “Java files” scopes (`N-5`), so the `AUD-588-F02` family remains open. |
209
337
  | `AUD-513-B01` / `B-3` / `B-5` | **B01/B-5 implemented in 5.8.14; B-3 closed as revalidated** | A MyBatis XML paired with a `*Mapper.java` interface now takes precedence over an absent `@Mapper` annotation, preserving XML-backed/`@MapperScan` interfaces in `mapper_interfaces` rather than misclassifying them as DTO mappers. `imports_found` now contains only actual Java imports; code, XML and build matches are emitted as `evidence`, including in compact output. `B-3` was stale against the current source: both views consume `JAVA_SPRING_SECTIONS`; a real MyBatis Mapper/XML regression proves the section is retained by `--agent`. |
210
- | `AUD-513-N03/N04/N07/N08/N09`, `B-6/B-7/B-8`, `D-09` | **N03/N04/N07/N08/N09 implemented in 5.8.14; remainder open** | `impact` now always publishes the `candidates` field its not-found message names, including `[]` when no close symbol exists. Budget advice no longer calls partial snapshot/RIS evidence a warm cache: it identifies that presence and says context/parse are unknown before analysis, whose payload then reports all four actual layers. README operational sections (`clone`, install, build, getting started, troubleshooting and related headings) are never promoted into a project summary; with no descriptive prose compact output publishes `project_summary: null` and `summary_basis`. `security_posture.limitations` is now cache-state invariant; dynamic execution advice travels in `security_posture.operational_hints`. Every numeric output bound rejects negative values at parse time with a structured `INVALID_INPUT` envelope (`flag`, `value`, `expected`); `--limit 0` still means no cap wherever that behavior was published. Budgets overrun, timing coverage is inconsistent, compact hides unresolved identities and cost anchors remain stale. |
338
+ | `AUD-513-N03/N04/N07/N08/N09`, `B-6/B-7/B-8`, `D-09` | **N03/N04/N07/N08/N09 implemented in 5.8.14; remainder open** | `impact` now always publishes the `candidates` field its not-found message names, including `[]` when no close symbol exists. Budget advice no longer calls partial snapshot/RIS evidence a warm cache: it identifies that presence and says context/parse are unknown before analysis, whose payload then reports all four actual layers. README operational sections (`clone`, install, build, getting started, troubleshooting and related headings) are never promoted into a project summary; with no descriptive prose compact output publishes `project_summary: null` and `summary_basis` — **`N04` regressed in 5.8.17 and is `AUD-592-R02`: licence headers and layout rules are promoted again, under a basis that asserts they are prose.** `security_posture.limitations` is now cache-state invariant; dynamic execution advice travels in `security_posture.operational_hints`. Every numeric output bound rejects negative values at parse time with a structured `INVALID_INPUT` envelope (`flag`, `value`, `expected`); `--limit 0` still means no cap wherever that behavior was published. Budgets overrun, timing coverage is inconsistent, compact hides unresolved identities and cost anchors remain stale. |
211
339
  | `AUD-588-B11` (container/units subset) | **partially implemented in 5.8.14** | `d745d31` discovers nested Maven modules; `273ae8e` suppresses unsupported `pr-impact.unaffected_basis`. `e53507d` separates direct impact matches from analysis seeds and implementation classes; `8386744` carries the established container-wiring fact into plan checklists and compare cost dimensions. Source population units and risk-tier wording remain open. |
212
340
  | `AUD-511-R02` / `AUD-589-B02` / `AUD-513-N06` | **partially corrected; `data-exposure` open, P1** | Nine of ten measured commands recovered, many to their best historical timings. `data-exposure --config` did not: 42.7 s versus 16.2 s in 5.8.8 and a 28.9 s model, with byte-equivalent output. Measure it with the idle-host, isolated-cache, interleaved-control protocol before changing code, then compare its reuse path with `validation`. |
213
341
  | `AUD-588-B02`, `B06`, B11 residuals, `B12` | **B02/B06 closed; B11/B12 open, P2** | `3869c87` excludes explicit EclipseLink from Hibernate applicability and effort; `037223e` publishes provider non-coverage. The re-audit verifies both and the payload reduction; `cf57457` makes aggregate wording name only applicable dimensions. Complete B11 consumer parity and calibrate B12 only from provenance-bearing controlled samples. |
@@ -407,6 +535,8 @@ retained only as evidence of what the auditor observed before the fix.
407
535
  | ASK-17 | **The parse store's default budget cannot hold a multi-repository workspace warm, and the cold cost of the largest repository is 7,6 minutes.** Measured on the 8-repo corpus (43 986 `.java`): `cache status` reports 56 730 entries at **511,12 MB against a 512 MB budget** — i.e. permanently sweeping by LRU — so `tutorials` (24 073 `.java`, the largest contributor) is the first candidate for eviction. Cold `--compact` on it: **457,6 s**, independently reproduced at 449 s, against 2,0 s on the second pass. | 5.8.4 corpus re-audit | Medium — warm plus `--compact` is still the answer (2,0 s), but a workspace this size cannot keep every repository warm at the default budget, and nothing tells the caller which repository is cold before it pays for it | **closed 5.8.5 — the disclosure shipped and the owed measurement taken, under our own control.** ⚠ **The +66 % does not reproduce, and the mechanism the row suspected is worth ~3 %, not 66 %.** Protocol: `tutorials` (24 073 `.java`), root `--compact`, both cache bases isolated **and emptied between runs** (`SOURCECODE_CONTEXT_CACHE_DIR` *and* `SOURCECODE_CACHE_DIR` — the first attempt isolated only the first, and the second pass of each version answered from the L2 view its own first pass had written: 2,4 s with an empty parse store, which is how a measurement of a cold path becomes a measurement of a warm one), `ASK_PARSE_CACHE_MAX_MB` fixed at 4 096 MB so no sweep can confound the comparison, 2 passes per version. **5.7.2 (`e803c8b`): 112,0 s / 112,6 s. This tree: 113,8 s / 115,2 s — +2,3 %**, with per-version dispersion of 1,005× and 1,012× and a payload 2,2 % larger. Then the audit's own condition, isolated as the only variable — the store pre-filled to 489 MB against the **default** 512 MB budget, so every 32 MB written sweeps: **117,0 s, +2,7 %.** So the LRU sweep is not where 457,6 s comes from, and neither is the analysis path: the auditor was right to hold the regression, and the held figure is now retracted **with a number** rather than on suspicion. ⚠ Scope, stated because the difference is unexplained rather than explained away: 457,6 s on Windows 11 / pipx is **not reproduced here** (113 s on 20 cores), and that gap is not assertable in either direction from this measurement — what is assertable is that 5.7.2 → this tree did not get slower and that a saturated store costs ~3 %. ✅ **What the measurement does confirm is the row's own claim, with our number: one repository of 24 073 `.java` leaves 46 840 entries and 470 MB in the store — 92 % of the 512 MB default budget** — so a workspace with a second repository of any size is permanently sweeping by construction, exactly as reported. Sizing rule, measured rather than guessed: ~20 KB of store per Java file, so ~500 MB per 24 000-file repository. Collateral confirmation of `AS-18`: after the saturated run the store rests at 669 MB — 157 MB over the budget and **under** the 736 MB effective ceiling it publishes at 7 writers — so the ceiling holds under the condition that produced the complaint. **The disclosure half:** `cache status` now publishes **coverage per repository**, which is the half the row itself recommended and the half that changes a decision: *«tutorials: 3 100 of 24 073 files cached (13 %)»* replaces a blind guess about whether to raise the budget, and an LRU eviction becomes visible **before** somebody pays 457,6 s to discover it. Both halves of the attribution were already held and thrown away — the walk knows the repository and it computes the store key for every file — so the run records the pairs (`parse_cache.record_repository_index`, from **both** readers of the store: `build_repo_ir` and the route-surface extractor, so the figure does not depend on which command was typed) and `store_stats` intersects them with the keys it collects **in the entry walk it already performs**: coverage costs an intersection, never a second scan of the store and never a scan of the repository. Three rules keep it honest. The index is **merged, never replaced**, because a `--changed-only` or `--since` run would otherwise shrink a 24 000-file population to the twelve files it read and publish *«12 of 12 cached (100 %)»* about a repository that is cold. The number travels with its **basis** — a file deleted since its last analysis still counts as recorded and reads as uncached, which errs toward *colder than it is* and says so. And the index is **neither an entry nor evictable**: the entry walk, the byte accounting and the LRU sweep all glob `*.json`, so its bytes are published under their own name (`repository_index_bytes`) rather than folded into a total that means entries — an index swept away with the entries it describes cannot report the eviction, which is the one moment it exists for. It lives inside the generation root, so retiring a generation retires its indexes with it: the keys are only readable by the build that wrote them. Regression `tests/test_parse_store_repository_coverage_ask17.py`, 11 assertions, including the one the row is about — entries deleted underneath a recorded repository make coverage **fall** while the population holds. ⚠ **Still owed, and unchanged:** the controlled cold measurement (5.7.2 against this tree, `ASK_PARSE_CACHE_MAX_MB` fixed, store emptied between runs, on a >20 000-file repository). Until it exists neither the +66 % nor its absence is assertable, and the row stays open on that half alone. **History:** **open** — ⚠ **not filed as a regression, deliberately**: the same cold figure was 274,8 s in 5.7.2 (+66 %), and the auditor refuses to call it one because the conditions are not comparable — in 5.7.2 the store had been retired by a version change, here it was mid-LRU-sweep. Seven false positives of exactly this class have been retracted over seven cycles; this is the eighth candidate and it is being held. What is owed is a measurement **we** control: cold `--compact` on a >20 000-file repository, 5.7.2 against 5.8.4, with `ASK_PARSE_CACHE_MAX_MB` fixed and the store emptied between runs — until that exists, neither the +66 % nor its absence is assertable. Recommended beside it, and cheap because both halves already exist: `cache status` should publish **coverage per repository** — how many entries belong to each analysed repository and what fraction of its files are covered — so *tutorials: 3 100 of 24 073 files cached (13 %)* replaces a blind decision about whether to raise the budget. Entries are content-addressed and the walk knows the repository, so this is a projection of facts we hold, not new analysis. **Refutation reproduced independently in cycle 8, under the reporter's own protocol**: both stores isolated and emptied, `ASK_PARSE_CACHE_MAX_MB=4096`, 2 passes per version — 5.7.2 at 112,0 / 112,6 s against 5.8.5 at 113,8 / 115,2 s (~3 %), and an A/B of the budget itself (512 MB sweeping by LRU against 4 096 MB that cannot sweep) at 9,1–10,1 s against 9,0–9,7 s: **the LRU sweep costs nothing measurable**. The +66 % is retired as the reporter's eighth false positive, with `E-37` named as its confounder. |
408
536
  | C3-128 | **`timeline` is the only gate-shaped command above 120 s, and it has not come back to its record.** Steady state, 4 runs, audited corpus: `timeline --since HEAD~5` costs **198 469 ms**, +27,3 % over its 5.7.2 record of 155 950 ms. The two other commands over 120 s are there structurally — `delta` (234 s) and `contract-diff` (136 s) analyse two whole trees — while `timeline` analyses **five** and costs less than `delta` does with two, so its cost is not explained by the number of trees it walks. | 5.8.2 → 5.8.4 re-audits | Low-Medium — an investigation command rather than a gate, and the last performance figure of the round sitting outside its own best | **closed 5.8.5 by measurement — the clock is attributed, and there is nothing left unaccounted to tune against.** The row's own instruction was ASK-16's: instrument before tuning. `timeline` published a per-sample total over what is really four costs — materialising a tree, measuring each watched metric, releasing the tree, and the remainder — so *«198 469 ms»* named a command rather than a phase. `perf.PhaseTimings` (the ASK-16 authority, always on) now splits it, with the phase names taken from `--watch` so the split cannot drift from the population, and the tree materialisation kept as its own phase because git's work must not be attributed to an analysis. **Measured, BroadleafCommerce (2 985 `.java`), `--since HEAD~5 --watch posture`, 5 samples: wall 38 921 ms — `measure:posture` 32 273 (82,9 %), `materialise_tree` 5 376 (13,8 %, ~1 075 ms per tree), `release_tree` 1 272 (3,3 %), unaccounted 0,45 ms (0,0 %).** So the answer to the row's premise — *«it analyses five trees and costs less than `delta` does with two»* — is that five sixths of the cost **is** the analysis, re-run per tree by construction, and the git work is a sixth of it: `timeline` is N × one analysis and there is no timeline-specific overhead to remove. Any future gain belongs to the metric being sampled (`C3-84`, `C3-127`), which is where it would also help every other command, and the payload now says so per run instead of per audit. Regression `tests/test_timeline_timings_c3_128.py`, 9 assertions, including that the series itself is byte-identical across two runs once the clock readings are removed — instrumentation that moved an answer would be a worse defect than the row. **Original note:** **open** — ⚠ the cache-reuse half of `B7` must **not** be reopened on this evidence: the v3 assertion (`max(sample) < 8 × posture_warm`) passes at 6,8 in both 5.8.2 and 5.8.4, against 14,8 / 13,4 / 12,4 / 8,5 in the four versions that genuinely failed it. What has not returned is the absolute cost. Attribution comes before tuning and is now cheap: `metadata.timings` (`ASK-16`) exists, so the per-phase split across the five trees can be published before anything is changed. |
409
537
  | B20 | **Thirteen declared renames with no cut-off date, and two distinct `1.0` identifiers meanwhile.** The registry publishes `pending_renames` for the 13 non-conforming `schema_version` values with their canonical name — exactly the policy `ASK-11` exists to enforce: the rename is an incompatible change, declared before it is made, never applied in silence. The consequence is published by the registry itself as `ambiguous_identifiers: 1` — `spring-audit` emits `1.0` for `core-analysis-v1` and `impact-chain` emits `1.0` for `impact-chain-v1`, so a consumer dispatching on the emitted version cannot tell them apart. | 5.8.4 re-audit (residual of `B19` / `B6`) | Low — declared debt rather than a defect, and the audit says so in as many words | **closed 5.8.5 — the window is declared where every other incompatible change is, and the reported ambiguity was understated by five shapes.** `BC-002` in `sourcecode.breaking_changes`: **announced 5.8.5, takes effect 6.0.0**, printed by `ask schema breaking-changes-v1`, carried in the CHANGELOG's `## Upgrading` section above the release history, and projected into `ask schema schemas-v1` as `pending_renames_window` — **one fact, two surfaces**, with a structural assertion that the registry source holds no second copy of the date. The registry's policy is generalised rather than loosened: a declared change is now `kind: exit_code` (must move an exit code) **or** `kind: contract` (must move a **published value** *and* name the version it takes effect in), because a rename breaks a consumer with every exit code still 0 and a registry that only knew about exit codes had nowhere to put it. A contract change publishes no `exit_code_before`/`after` at all — inviting a reader to check a field that cannot move is how a disclosure becomes noise. The 13 affected shapes are **read from `schema_registry.canonical_migrations()` at call time**, never copied: a shape that starts conforming leaves the declaration by itself (asserted by swapping the registry for a conforming one and watching the list empty). ⚠ **Correction to the reported cause, measured**: the audit named *two* shapes spelling their version `1.0`; there are **seven** — `core-analysis-v1`, `impact-chain-v1`, `pr-impact-v1`, `migration-blast-v1`, `spring-impact-v1`, `event-topology-v1`, `test-gap-ranking-v1` — so `ambiguous_identifiers: 1` was counting one *identifier* over seven shapes, not two. `ask schema 1.0` already resolved to all seven with each canonical name; the count is now asserted from the registry so it cannot be quoted from prose again. The rename itself is deliberately **not** made early: that is the incompatible change this policy exists to prevent. Regression `tests/test_schema_rename_window_b20.py`, 11 assertions, plus the ASK-11 battery generalised to both kinds. **Original note:** **open** — the missing half is a date, not a decision. Announce the cut-off window in `breaking-changes-v1` with the target version, the way every other incompatible change is announced, so a consumer can pin `core-analysis-v1` today and know when the bare `1.0` stops being emitted. Until then `ask schema 1.0` resolving to all seven shapes, each offering its canonical name, is the correct behaviour and must not be *fixed* by renaming an emitted value early — that is the incompatible change this policy exists to prevent. ⚠ **Round 10 ran on the 5.8.5 build and still reports the renames as *«deuda bien declarada, pero sin fecha»*, asking for precisely what `BC-002` already ships.** The row stays closed — the window exists, is announced 5.8.5 / effective 6.0.0, and is printed by `ask schema breaking-changes-v1`, carried in the CHANGELOG's `## Upgrading` and projected into `schemas-v1` as `pending_renames_window`. What it leaves behind is a **discoverability check, not a defect**: the reporter quoted `pending_renames` and `counts` out of the registry payload and did not see the window beside them, so verify that `pending_renames_window` travels in the same payload those two keys do — and if it does not, that is where it belongs. A declaration a ten-round auditor cannot find is not yet declared to a consumer. |
538
+ | E-38 | **A class-level route prefix carried by a meta-annotation is dropped, and the route is published without it.** `_build_route_surface` reads the class prefix only when the class symbol carries `@RequestMapping` or `@Path` **literally** (`repository_ir.py:5882-5887`); a repository that declares its own composed annotation gets `prefixes = [""]`, and the published `path` is the method suffix alone. **Measured on shenyu (5.8.17): 35 of 365 published routes — 9,6 %, over 21 controllers — carry `path: "/"`.** `AiProxyApiKeyController` is annotated `@RestApi("/selector/{selectorId}/ai-proxy-apikey")`, a Shenyu annotation meta-annotated with `@RestController` + `@RequestMapping`; its five handlers are published at `/`, `/`, `/batchDelete`, `/{id}`, `/page`. The served URLs do not exist as published. This is a **confident falsehood**, not a gap: no `path_resolution: "unresolved"`, no `path_expression`, no warning, and `route_census.distinct_routes` (316 of 365) reads the collisions as if two controllers genuinely shared a path. It also propagates to every consumer keyed on route text — `explain-endpoint`, `data-exposure --path-prefix`, `validation --path-prefix`, `pr-impact` route matching — where a correct query returns nothing. **The machinery already exists on the other axis**: `E-12`/`5.7.1` closed exactly this class for *beans* by resolving the spelling against the graph's `imports` edges, and `BeanGraph.build` already builds a meta-annotation map in its first pass. The route axis never asked. Vendor-agnostic by construction: the rule is "an annotation this repository declares that itself carries `@RequestMapping`/`@Path`", never a proprietary name. | found 2026-08-21 during the `AUD-590-R01` census triage, on the corrected 5.8.17 build; **not part of the seventh-pass queue and deliberately not fixed in it** — it moves route counts on any repository using composed annotations and needs its own measured pass with the golden set re-baselined | **High** — it is the rule this repository enforces most loudly, inverted on the route axis: a published route that is not served, with no affordance saying so. Blast radius is every repository with a framework-level or in-house composed controller annotation; shenyu is the witness, dubbo/sa-token/halo are unmeasured | **open** — direction: resolve the class-level prefix through the meta-annotation map `BeanGraph.build` already computes, in the order `E-12` established (explicit single-type import decides; wildcard leaves it admissible; a repository-declared annotation in the owner's package is the owner's own; **nothing resolved stays unresolved rather than acquiring a verdict from silence**). Where it cannot be resolved, publish `path_resolution: "unresolved"` with the annotation as `path_expression` — the contract the method-level path already honours — never a bare `/`. Regression on a fixture declaring its own composed annotation, plus a shenyu assertion that no published route is `/` |
539
+ | B29 | **A cache-freshness test asserts a cold cache without ensuring one, so it fails on every run but the first after a purge.** `tests/test_cli.py::test_compact` asserts `_meta.timing_ms.served_from_cache is False` and does nothing to make that true: the first `--compact` run over its fixture populates the cache the second one then hits. Verified symmetrically at `d9071d4` (clean) and at every commit of the seventh-pass queue — purge, run, pass; run again, fail — so it is **state-dependent, not a regression**, and it has been reported as "1 pre-existing failure" for the whole pass. It is still a red line in a suite whose value is that red means something. | found 2026-08-21 while establishing the baseline for the seventh-pass queue | **Low** as a defect, **medium** as an eroder — a permanently-red test trains a reader to skip the summary line, which is how a real regression gets shipped | **open** — direction: the test must create the state it asserts (purge this fixture's cache dir in the fixture, or assert the *transition* cold→warm across two runs rather than the absolute). Do not "fix" it by deleting the assertion: `served_from_cache` is the field `is_stale`/cache-freshness rows are keyed to, and a cold-start claim is worth holding |
410
540
  | E-37 | **Two environment variables move three cache stores, and no surface said which moves which.** `SOURCECODE_CACHE_DIR` relocates the per-repository snapshot store (core snapshots, the rendered L2 view, the RIS); `SOURCECODE_CONTEXT_CACHE_DIR` relocates the shared ones (the Canonical IR and the per-file parse store). Their defaults are siblings under `~/.sourcecode`, so the split is not derivable from the paths, and `cache status` published a path per store with no variable beside it while `docs/CACHE.md` named both variables in one sentence after the table. | found here 2026-08-18 while taking the controlled A/B `ASK-17` owed, not reported by the field | Medium — it does not make an answer wrong, it makes a **measurement** wrong, silently: an operator who redirects or empties *the cache* moves one store and is served answers out of the other | **closed 5.8.5** — measured first: with only `SOURCECODE_CONTEXT_CACHE_DIR` redirected, the second cold pass of each version answered from the L2 view its own first pass had written — **2,4 s against 113 s, with an empty parse store** — and the run reported `cache_source: L2_view` without anything naming the store it came out of. Now every store in `cache status` publishes `base_env`, from one authority (`cache._STORE_BASE_ENV`), the text answer prints *«<path> ($VAR moves it)»* beside each one, `stores.base_env_note` states the consequence rather than the layout, and `docs/CACHE.md` carries a **column** instead of a sentence covering both. Regression `tests/test_cache_store_base_env_e37.py`, 8 assertions, and the ones that matter are **behavioural**: setting the variable a store names moves *that* store and leaves the others where they were — a published mapping nobody exercises is the class of claim this repository refuses everywhere else. The CLI is forbidden a second copy of the mapping (structural assertion). **Confirmed from the outside in cycle 8, by the reporter falling into it first**: isolating only `SOURCECODE_CONTEXT_CACHE_DIR`, their second *cold* pass answered from the L2 view their first pass had written — **2,4 s against 113 s with an empty parse store** — and their write-up now quotes the shipped `base_env` mapping back as the fix (*"dos variables mueven tres almacenes y ninguna superficie decía cuál"*). A defect found while measuring, that was itself corrupting the measurement. |
411
541
  | B21 | **Six of the thirteen shapes being renamed name no command that emits them.** `schemas-v1` publishes `emitted_by` per shape; for `migration-blast-v1`, `spring-impact-v1`, `event-topology-v1`, `test-gap-ranking-v1`, `canonical-ir-v1` and `hibernate-strategy-v2` it is `[]`, beside seven shapes where it is populated. Read the way a consumer reads a list, an empty `emitted_by` says *nothing emits this shape* — which tells the one consumer that **is** affected by `BC-002` that it is not. | found here while declaring `BC-002` (5.8.5), not reported by the field | Low — a disclosure gap in a declaration whose whole purpose is to let a consumer decide whether it is affected | **closed 5.8.6 — derived, not typed out.** `schema_producers` reads the two authorities the CLI already holds — the registered command tree and the import graph of the module that defines each callback — and answers with the evidence it derived on: `command_import` (the command's own body imports the producing module), `module_import` (one hop, through an intermediate narrow enough to attribute), `substrate` (reached from more commands than a producer would be) or `unresolved` (nothing found, said in a sentence). Five of the six now name a command — `migration-blast-v1` → `migrate-check`, `spring-impact-v1` and `event-topology-v1` → `impact-chain`, `test-gap-ranking-v1` → `impact`, `hibernate-strategy-v2` → `migrate-check`/`migrate-recipe` — and the sixth, `canonical-ir-v1`, is reached from **28 modules**: naming every command would be less true than naming none, so it publishes `substrate` with the count and the instruction to match on `subject`. A declared producer still wins, because some producers are not one registered command (`ask (root)`, `baseline capture`). Regression `tests/test_schema_producers_derived_b21.py`, 33 assertions, parametrised by the registry so a fourteenth unnamed shape fails the battery. **History:** open, and named rather than shipped as a fact: every `BC-002` row with an empty producer list carries `emitted_by_basis` — *«not declared in the registry: this shape's producing command is not named yet, so match on `subject`»* — so the gap is visible instead of being read as a negative claim. What remains is to fill the six, and the honest way is derivation rather than six more hand-written strings: the producer is discoverable from the command that constructs each payload, which is the same authority every other population in this repository comes from. Until then the assertion in `tests/test_schema_rename_window_b20.py` holds the weaker invariant a reader can rely on: a row either names its emitters or says why it cannot. |
412
542
  | C3-129 | **`timeline`'s absolute cost, now fully attributed and still 27 % above its own record.** `C3-128` closed by instrumenting rather than tuning, which was the right order and left this behind: the clock is split, nothing is unaccounted, and the number has not moved. Round 10 on 5.8.5 measures `timeline --since HEAD~5` at **~198 000 ms against the 5.7.2 record of 155 950 ms (+27 %)** — the only gate-shaped command over 120 s that is not there structurally (`delta` 191 s and `contract-diff` 115 s walk two whole trees, `compare` 46 s walks N candidates; `timeline` walks **five** trees and costs more than `delta` does with two). | 5.8.2 → 5.8.5 re-audits (residual of `C3-128`, which closed the attribution half in 5.8.5) | Low-Medium — an investigation command rather than a CI gate, and the last performance figure of the round sitting outside its own best | **closed 5.8.6 — by not paying twice for what does not change.** A metric at a commit is a pure function of that tree, and the tree at a sha never changes: the only cache in this product whose key can be exact rather than heuristic (analyzer fingerprint + metric + commit sha). `timeline_cache.SampleCache` stores measured values under the per-repository core store, and when every watched metric for a sample is a hit the tree is **never materialised**, which is the 13,8 % the split attributes to git. Measured on BroadleafCommerce over the same range: **26 845 ms → 12 ms**, samples and transitions byte-identical. What makes it admissible rather than merely fast, all asserted: a failed sample is never stored (one bad run cannot become permanent), the fingerprint is in the key (corrected analysis is never served from a previous release), the hit is published (`sample_cache`, `from_cache` per sample, and `cost` separates the cached population from the measured one instead of reporting a median over both), and `--no-cache` / `ASK_TIMELINE_NO_SAMPLE_CACHE=1` measure everything again so the command stays measurable without emptying a store (E-37). `cache_model` carries the layer, so `cache model` and `docs/CACHE.md` state it from one authority. Regression `tests/test_timeline_sample_cache_c3_129.py`, 11 assertions. **History:** open — and it opened with the answer already published, which is why it is cheap to attack and expensive to leave: `timeline`'s own `timings` say **82,9 % `measure:posture`, 13,8 % `materialise_tree`, 3,3 % `release_tree`, 0,0 % unattributed**. So this is not a `timeline` defect at all: it is `posture` paid five times over five materialised trees, and it makes the same substrate question `C3-127` asks — with the difference that here the five payers are one command, so the cache is intra-invocation and needs no cross-command contract. ⚠ **Do not reopen `B7`**: cross-tree cache reuse is healthy, the v3 assertion passes at 6,8×. Acceptance: `timeline --since HEAD~5` back inside +10 % of 155 950 ms on the audited corpus with `timings` still summing to 100 %, and `measure:posture` falling as a share rather than the total falling for an unnamed reason. |
@@ -46,7 +46,7 @@ CLI commands — impact, endpoints, spring-audit, explain, … each a pro
46
46
  The key idea: the extraction is **content-addressed**. Commands reuse the parse cache and,
47
47
  where their analysed scope matches, the shared Canonical IR; `ask cache model` names what a
48
48
  warm buys for each command rather than implying that every projection costs the same. In
49
- 5.8.17, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
49
+ 5.8.18, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
50
50
  model across all declared label seeds. (The extraction and consumption contract is fixed in
51
51
  the architecture ADRs 0001–0004 under `docs/architecture/`.)
52
52
 
@@ -108,7 +108,7 @@ human. They project the same semantic model into agent-ready context:
108
108
  | `ask prepare-context <task>` | Task-shaped context bundle (`onboard`, `review-pr`, `fix-bug`, `refactor`, `generate-tests`) sized for an LLM prompt. |
109
109
  | `ask repo-ir` | The low-level symbol IR itself — the raw semantic representation, for tools that want the graph, not a report. |
110
110
  | `ask --agent` | Agent-optimized output envelope for any command. |
111
- | `ask --compact` | A ~10K-token subset (status, stacks, entry points, key dependencies) for cheap orientation. |
111
+ | `ask --compact` | A bounded subset (status, stacks, entry points, key dependencies) for cheap orientation. See `ask --help` for the current output budget. |
112
112
  | `ask mcp` | Runs ASK as an MCP server so an agent (Claude Desktop / Cursor) calls it as a tool. |
113
113
 
114
114
  Rule of thumb: **developer-facing** commands (`impact`, `spring-audit`, `endpoints`,
@@ -142,7 +142,7 @@ pipx install sourcecode # isolated install, no venv needed
142
142
 
143
143
  # Verify
144
144
  ask version
145
- # ask 5.8.17
145
+ # ask 5.8.18
146
146
  ```
147
147
 
148
148
  Requires Python 3.9+.
@@ -240,7 +240,7 @@ ask . --compact --git-context # includes commit hotspots
240
240
  ask . --compact --copy # copy to clipboard
241
241
  ```
242
242
 
243
- Output (~2,500–4,000 tokens): detected stacks, entry points, dependencies, transactional boundaries (Spring), env vars, confidence level, analysis gaps.
243
+ Output: detected stacks, entry points, dependencies, transactional boundaries (Spring), env vars, confidence level, analysis gaps. The current output budget is published by `ask --help`.
244
244
 
245
245
  ### `ask --agent`
246
246
 
@@ -251,7 +251,7 @@ ask /repo --agent --output context.json
251
251
  cat context.json | claude -p "Explain the architecture"
252
252
  ```
253
253
 
254
- Output (~4,500–5,500 tokens): project identity, entry points, file relevance ranking, architecture classification, confidence.
254
+ Output: project identity, entry points, file relevance ranking, architecture classification, confidence. The current output budget is published by `ask --help`.
255
255
 
256
256
  ### `ask onboard` *(supported)*
257
257
 
@@ -1363,8 +1363,8 @@ reason to warm before the first prompt (`ask cache warm <path>`).
1363
1363
 
1364
1364
  | Mode | When to use | Token size |
1365
1365
  |------|-------------|-----------|
1366
- | `--compact` | AI session start, high-level overview | 2,500–4,000 |
1367
- | `--agent` | AI agent system prompt injection | 4,500–5,500 |
1366
+ | `--compact` | AI session start, high-level overview | See `ask --help` |
1367
+ | `--agent` | AI agent system prompt injection | See `ask --help` |
1368
1368
  | `onboard` | Task-structured agent/developer onboarding | ~2,600 |
1369
1369
  | `fix-bug` (trimmed) | Bug triage with token budget | ~4,600 |
1370
1370
  | `--full` | Raise the display caps (boundaries and DTO mappers in full, `file_relevance` to 40) | bounded, larger |
@@ -50,6 +50,11 @@ POP_REFERENCES_WITH_IMPORTS = "references_including_imports"
50
50
  POP_REFERENCES_NO_IMPORTS = "references_excluding_imports"
51
51
  POP_ALL_EDGES = "all_incoming_edges"
52
52
  POP_TRANSITIVE_CHAIN = "transitive_chain"
53
+ #: AUD-593-N01. `impact-chain` walks from a seed set expanded with the members of
54
+ #: every interface the target implements (CH-001b), so its depth-1 callers include
55
+ #: classes that name a sibling implementation and never name the target. That is a
56
+ #: different population, not a different way of counting the same one.
57
+ POP_EXPANDED_SEED_REFERENCES = "references_to_target_or_its_declared_interfaces"
53
58
 
54
59
 
55
60
  def caller_classes(symbols: Iterable[str], *, exclude: Iterable[str] = ()) -> set[str]:
@@ -103,13 +108,38 @@ FAN_IN_FIGURES: tuple[dict[str, str], ...] = (
103
108
  "unit": UNIT_CLASSES,
104
109
  "population": POP_TRANSITIVE_CHAIN,
105
110
  "means": "every class in the call chain, direct AND transitive — a reach "
106
- "figure, not fan-in; unbounded above by any direct-caller count",
111
+ "figure, not fan-in. It is >= the chain's own depth-1 figure, "
112
+ "and equal to it when the hub guard caps the walk at depth 1 "
113
+ "(`depth_capped_by_guard: true`), which is declared in the same "
114
+ "metadata block",
115
+ },
116
+ {
117
+ "key": "impact-chain.metadata.expanded_seed_caller_count",
118
+ "unit": UNIT_CLASSES,
119
+ "population": POP_EXPANDED_SEED_REFERENCES,
120
+ "means": "distinct classes calling the target OR any member of an "
121
+ "interface it declares, at depth 1 of the chain walk; callers "
122
+ "that inject the interface are the reason the chain expands its "
123
+ "seed, and they name a type the target implements, not the "
124
+ "target — so this is a wider population than `impact` admits",
107
125
  },
108
126
  {
109
127
  "key": "impact-chain.metadata.direct_caller_count",
110
128
  "unit": UNIT_CLASSES,
111
- "population": POP_REFERENCES_NO_IMPORTS,
112
- "means": "distinct classes calling the target at depth 1",
129
+ "population": POP_EXPANDED_SEED_REFERENCES,
130
+ "means": "the same interface-expanded figure as "
131
+ "`expanded_seed_caller_count`, under the name it shipped with. "
132
+ "It reads as a peer of "
133
+ "`impact.stats.direct_caller_count` and is not one: measured 554 "
134
+ "against 34 for one symbol at one HEAD (AUD-593-N01). Read the "
135
+ "expanded name, and read `impact` for fan-in on the target alone",
136
+ },
137
+ {
138
+ "key": "impact-chain.via_interface_resolution[].caller_count",
139
+ "unit": UNIT_SYMBOLS,
140
+ "population": POP_EXPANDED_SEED_REFERENCES,
141
+ "means": "raw caller symbols for one interface bridge before callers are "
142
+ "normalised to distinct classes",
113
143
  },
114
144
  {
115
145
  "key": "modernize.in_degree",
@@ -142,6 +172,18 @@ CALLER_METRIC_RECONCILIATION: str = _render_reconciliation()
142
172
 
143
173
  #: Kept for the payload key that was `callers_total` before it was named for what
144
174
  #: it counts. Consumers reading the old key get the same number and this note.
175
+ #: AUD-593-N01. The key stays; what it claimed about itself does not.
176
+ DIRECT_CALLER_COUNT_DEPRECATION: str = (
177
+ "metadata.direct_caller_count counts classes that call the target OR any "
178
+ "member of an interface the target declares — the chain walks from a seed "
179
+ "set expanded with those interfaces (CH-001b), which is why a caller that "
180
+ "injects the interface is found at all. It is therefore NOT the peer of "
181
+ "impact.stats.direct_caller_count, which admits only references naming the "
182
+ "target: the two were declared the same population and measured 554 against "
183
+ "34 for one symbol at one commit. It is superseded by "
184
+ "metadata.expanded_seed_caller_count, which says which population it is."
185
+ )
186
+
145
187
  CALLERS_TOTAL_DEPRECATION: str = (
146
188
  "metadata.callers_total is the size of the whole call chain (direct + "
147
189
  "transitive), not a fan-in count; it is superseded by "
sourcecode/cli.py CHANGED
@@ -293,7 +293,7 @@ EXPERIMENTAL_BASIS: "dict[str, str]" = {
293
293
  #: both directions at once.
294
294
  COMMANDS_THAT_WRITE: "tuple[tuple[str, str], ...]" = (
295
295
  ("baseline capture", "`<repo>/.ask/baselines/` (or `--dir`) — one JSON per capture"),
296
- ("migrate-check", "`<repo>/.ask/readiness-history/` — only with `--history-dir`"),
296
+ ("migrate-check", "`<repo>/.ask/readiness-history/` — only with `--snapshot` (optionally relocated by `--history-dir`)"),
297
297
  ("verify", "`<repo>/.ask/contracts.yml` — only with `--init`, and not with `--dry-run`"),
298
298
  ("verify", "`<repo>/.ask/contracts-baseline.json` — only with `--capture-baseline`"),
299
299
  ("verify-edit", "the repository's git hooks directory — only with `--install-hook`"),
@@ -585,6 +585,17 @@ def _compact_budget() -> str:
585
585
  return compact_budget_phrase()
586
586
 
587
587
 
588
+ # Keep the help claim tied to the commands that emit the budget preamble.
589
+ _BUDGETED_ANALYSIS_COMMANDS = (
590
+ "ask (root)", "spring-audit", "verify --init", "verify", "risk",
591
+ "audit-report", "migrate-check", "migrate-apply",
592
+ )
593
+
594
+
595
+ def _budgeted_analysis_commands_phrase() -> str:
596
+ return ", ".join(_BUDGETED_ANALYSIS_COMMANDS)
597
+
598
+
588
599
  def _build_help_text(scope: "Optional[Any]" = None) -> str:
589
600
  """Build --help text dynamically based on current license state."""
590
601
  try:
@@ -618,8 +629,8 @@ scope matches; `ask cache model` says exactly what a warm buys per command.
618
629
  [bold]Performance / CI:[/bold]
619
630
  validation . --compact [dim]# reuses the shared CIR on a warm matching scope[/dim]
620
631
  data-exposure . [dim]# one semantic model is shared across declared label seeds[/dim]
621
- ASK_MAX_ANALYSIS_SECONDS=N [dim]# deadline only for spring-audit, risk, audit-report[/dim]
622
- [dim]Other commands are not time-bounded by this variable; use `--progress` or `--detach` where offered.[/dim]
632
+ ASK_MAX_ANALYSIS_SECONDS=N [dim]# deadline for {_budgeted_analysis_commands_phrase()}[/dim]
633
+ [dim]Other commands do not consume this budget; use `--progress` or `--detach` where offered.[/dim]
623
634
 
624
635
  [bold]Change and risk:[/bold]
625
636
  impact-chain <Class> . [dim]# blast radius; TX/SEC findings opt-in[/dim]
@@ -1648,7 +1659,6 @@ _COMMAND_FALLBACK = {
1648
1659
  "migrate-check": "run inventory in CI and deep migration checks nightly",
1649
1660
  }
1650
1661
 
1651
-
1652
1662
  def _build_analysis_classes() -> "dict[str, tuple[str, int, str]]":
1653
1663
  """Every command's analysis class, derived from the cost model (C4-19).
1654
1664
 
@@ -8871,10 +8881,19 @@ def plan_cmd(
8871
8881
  # Test-inclusive build: covering-test discovery needs the test→production call
8872
8882
  # edges, which only exist when test sources are in the graph (D3).
8873
8883
  _files = find_java_files(target_dir, include_tests=True)
8874
- _compare_scan_trunc = _last_scan_truncation()
8884
+ # AUD-591-A06 / AUD-592-R01: read beside the walk that produced this list,
8885
+ # and bound to the answer built from it. `plan` answers `resolution:
8886
+ # not_found` and offers candidates; both are exclusions drawn from this
8887
+ # population, so a walk that stopped at its cap has to say so here.
8888
+ _plan_scan_trunc = _last_scan_truncation()
8875
8889
  _cir = ContextGraph.build(_files, target_dir, progress=_work_sink(_prog)).cir
8876
8890
  _prog.update("planning change")
8877
8891
  data = build_change_plan(_cir, target, max_depth=depth, root=target_dir)
8892
+ if _plan_scan_trunc:
8893
+ data["scan_truncated"] = _plan_scan_trunc
8894
+ # The tree these components and candidates came from: `--path` defaults to
8895
+ # `.`, so a run from the wrong directory answers about another repository.
8896
+ data.setdefault("scope", {"path": str(target_dir)})
8878
8897
  _prog.finish()
8879
8898
 
8880
8899
  output = _serialize_dict(data, format)
@@ -8964,6 +8983,10 @@ def compare_cmd(
8964
8983
  # Test-inclusive build: the cost vector counts covering tests (tests at risk),
8965
8984
  # which need test→production call edges — same opt-in the `plan` command uses.
8966
8985
  _files = find_java_files(target_dir, include_tests=True)
8986
+ # AUD-592-R01: read here, at this command's own call site. The assignment
8987
+ # this line restores had landed in `plan_cmd`, so every `compare` raised
8988
+ # NameError and the `scope` fix below it never ran.
8989
+ _compare_scan_trunc = _last_scan_truncation()
8967
8990
  _cir = ContextGraph.build(_files, target_dir, progress=_work_sink(_prog)).cir
8968
8991
  _prog.update("comparing candidates")
8969
8992
  data = build_comparison(_cir, list(targets), max_depth=depth, root=target_dir)
@@ -11439,10 +11462,8 @@ def enrich_cmd(
11439
11462
  help=(
11440
11463
  "Report only enriched findings from this external rule id. Applies "
11441
11464
  "to every format, not only --table; the payload carries a `_filter` "
11442
- "block with `total_before_filter`. Not validated against a "
11443
- "catalogue: the ids come from the scanner's SARIF, not from this "
11444
- "build, so a selection that matches nothing reports `matched: 0` "
11445
- "against the population it searched."
11465
+ "block with `total_before_filter`. The id must be present in the "
11466
+ "scanner's SARIF findings."
11446
11467
  ),
11447
11468
  ),
11448
11469
  band_filter: Optional[str] = typer.Option(
@@ -11610,6 +11631,16 @@ def enrich_cmd(
11610
11631
  finally:
11611
11632
  _prog.stop()
11612
11633
 
11634
+ if rule_filter:
11635
+ _available_rule_ids = {
11636
+ str((row.get("external") or {}).get("rule_id"))
11637
+ for row in (data.get("findings") or [])
11638
+ if isinstance(row.get("external"), dict)
11639
+ and row["external"].get("rule_id")
11640
+ }
11641
+ if rule_filter not in _available_rule_ids:
11642
+ _reject_selection_value("--rule", rule_filter, sorted(_available_rule_ids))
11643
+
11613
11644
  if table:
11614
11645
  output = _render_enrich_table(
11615
11646
  data, rule_filter=rule_filter, band_filter=band_filter, top_n=top_n
@@ -11632,6 +11663,14 @@ def enrich_cmd(
11632
11663
  )
11633
11664
  if _sel is not None:
11634
11665
  data["_filter"] = _sel
11666
+ if min_band != "low":
11667
+ data["_filter"] = {
11668
+ **(data.get("_filter") or {}),
11669
+ "min_band": min_band,
11670
+ "total_before_filter": data.get("defects", 0),
11671
+ "matched": data.get("min_band_total", 0),
11672
+ "shown": len(data.get("findings") or []),
11673
+ }
11635
11674
  output = _serialize_dict(data, format)
11636
11675
  _emit_command_output(
11637
11676
  output,
@@ -12646,12 +12685,11 @@ def posture_cmd(
12646
12685
  from sourcecode.output_bounds import POSTURE_BOUNDED_LISTS, bound_lists, resolve_limit
12647
12686
 
12648
12687
  _posture_limit = resolve_limit(limit, compact)
12649
- # Compact posture keeps the verdict counts and drops the potentially large
12650
- # bodies. The cap is the actionable signal; retaining a representative row
12651
- # would make compact output depend on repository ordering and dilute the
12652
- # bounded contract.
12688
+ # B-8 / AUD-592-B01. A compact answer must retain one actionable unresolved
12689
+ # bean whenever the analysis found any, not only when the total is exactly 1.
12690
+ # An explicit --limit still has precedence, including --limit 0.
12653
12691
  if compact and limit is None:
12654
- _posture_limit = 1 if data.get("summary", {}).get("unresolved", 0) == 1 else 0
12692
+ _posture_limit = 1 if data.get("summary", {}).get("unresolved", 0) else 0
12655
12693
  bound_lists(data, POSTURE_BOUNDED_LISTS, _posture_limit)
12656
12694
 
12657
12695
  _emit_command_output(
@@ -13313,6 +13351,7 @@ def impact_chain_cmd(
13313
13351
  )
13314
13352
 
13315
13353
  file_list = find_java_files(target)
13354
+ _chain_scan_trunc = _last_scan_truncation()
13316
13355
  if not file_list:
13317
13356
  data: dict = {
13318
13357
  "schema_version": "1.0",
@@ -13326,6 +13365,8 @@ def impact_chain_cmd(
13326
13365
  "confidence": "low",
13327
13366
  "metadata": {},
13328
13367
  }
13368
+ if _chain_scan_trunc:
13369
+ data["scan_truncated"] = _chain_scan_trunc
13329
13370
  output = _serialize_dict(data, format)
13330
13371
  _emit_command_output(output, output_path, copy,
13331
13372
  success_msg=f"Impact chain written to {output_path}")
@@ -13347,6 +13388,8 @@ def impact_chain_cmd(
13347
13388
  from sourcecode.spring_event_topology import run_event_topology
13348
13389
  evt_result = run_event_topology(cir, symbol, model=_model)
13349
13390
  data = evt_result.to_dict()
13391
+ if _chain_scan_trunc:
13392
+ data["scan_truncated"] = _chain_scan_trunc
13350
13393
  _prog.finish()
13351
13394
  output = _serialize_dict(data, format)
13352
13395
  _emit_command_output(
@@ -13366,6 +13409,8 @@ def impact_chain_cmd(
13366
13409
  )
13367
13410
 
13368
13411
  data = result.to_dict()
13412
+ if _chain_scan_trunc:
13413
+ data["scan_truncated"] = _chain_scan_trunc
13369
13414
  # Same rule as `impact`: a file is a request for the whole answer (C2-2). An
13370
13415
  # explicit `--limit` still wins — the user asking for a cap on a file has said
13371
13416
  # so, and a default is not a request.
@@ -3,6 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
+ import subprocess
7
+ from pathlib import Path
6
8
 
7
9
  PRODUCT = "ASK Engine"
8
10
  TAGLINE = "Actionable Software Knowledge Engine"
@@ -11,6 +13,26 @@ PUBLISHER = "Dominique Haroun"
11
13
  LICENSE_ID = "LicenseRef-ASK-Proprietary"
12
14
 
13
15
 
16
+ def _build_commit() -> str:
17
+ """Read injected build provenance, or the package checkout's own HEAD."""
18
+ injected = os.environ.get("ASK_BUILD_COMMIT")
19
+ if injected:
20
+ return injected
21
+ package_root = Path(__file__).resolve().parents[2]
22
+ try:
23
+ result = subprocess.run(
24
+ ["git", "-C", str(package_root), "rev-parse", "HEAD"],
25
+ capture_output=True,
26
+ text=True,
27
+ check=True,
28
+ timeout=2,
29
+ )
30
+ except (OSError, subprocess.SubprocessError):
31
+ return "unrecorded"
32
+ commit = result.stdout.strip()
33
+ return commit or "unrecorded"
34
+
35
+
14
36
  def build_provenance() -> dict[str, str]:
15
37
  """Return build facts without discovering state from the user's repository."""
16
38
  return {
@@ -18,5 +40,5 @@ def build_provenance() -> dict[str, str]:
18
40
  "publisher": PUBLISHER,
19
41
  "license": LICENSE_ID,
20
42
  "distribution": os.environ.get("ASK_DISTRIBUTION", "official"),
21
- "build_commit": os.environ.get("ASK_BUILD_COMMIT", "unrecorded"),
43
+ "build_commit": _build_commit(),
22
44
  }
@@ -5870,6 +5870,18 @@ def _build_route_surface(
5870
5870
  # Phase 1: build per-class metadata (prefixes list) and own endpoint list.
5871
5871
  # "prefixes" is a list to support array @RequestMapping({"/v1/foo", "/v1/bar"}).
5872
5872
  class_info: dict[str, dict] = {} # simple_name → {fqn, prefixes, own_endpoints}
5873
+ # E-38: a project-local annotation can compose a class-level route prefix
5874
+ # (for example @ApiController carrying @RequestMapping("/api")).
5875
+ _meta_route_prefixes: dict[str, tuple[list[str], str]] = {}
5876
+ for sym in symbols:
5877
+ if sym.symbol_kind != "annotation":
5878
+ continue
5879
+ if "@RequestMapping" in sym.annotations:
5880
+ _meta_args = sym.annotation_values.get("@RequestMapping", "")
5881
+ _meta_route_prefixes[sym.symbol.split(".")[-1]] = (
5882
+ _parse_route_paths(_meta_args, constants),
5883
+ _route_path_expression(_meta_args, constants),
5884
+ )
5873
5885
  # P1 FIX: class symbol lookup for security annotation inheritance
5874
5886
  class_sym_by_simple: dict[str, "SymbolRecord"] = {}
5875
5887
  for sym in symbols:
@@ -5888,6 +5900,12 @@ def _build_route_surface(
5888
5900
  args = sym.annotation_values.get("@Path", "")
5889
5901
  prefixes = _parse_route_paths(args, constants) if args else [""]
5890
5902
  _prefix_expr = _route_path_expression(args, constants)
5903
+ else:
5904
+ for annotation in sym.annotations:
5905
+ _composed = _meta_route_prefixes.get(annotation.lstrip("@"))
5906
+ if _composed is not None:
5907
+ prefixes, _prefix_expr = _composed
5908
+ break
5891
5909
  class_info[simple] = {
5892
5910
  "prefix_expression": _prefix_expr,
5893
5911
  "fqn": sym.symbol,
@@ -9248,6 +9266,10 @@ def compute_blast_radius(
9248
9266
  _iface_bridging.append({
9249
9267
  "interface": _iface_fqn,
9250
9268
  "caller_count": len(_iface_callers),
9269
+ "caller_count_unit": "caller_symbols",
9270
+ "caller_count_population": (
9271
+ "references_to_target_or_its_declared_interfaces"
9272
+ ),
9251
9273
  })
9252
9274
  for c in _iface_callers:
9253
9275
  if c not in all_affected:
@@ -10171,6 +10193,14 @@ def compute_blast_radius(
10171
10193
  # symbol from `medium` to `critical`.
10172
10194
  out["interface_mediated_callers"] = _iface_mediated_classes[:30]
10173
10195
  out["interface_mediated_caller_count"] = len(_iface_mediated_classes)
10196
+ if len(_iface_mediated_classes) > 30:
10197
+ out["interface_mediated_callers_cap"] = _cap_effect(
10198
+ "display_list",
10199
+ limit=30,
10200
+ total=len(_iface_mediated_classes),
10201
+ shown=30,
10202
+ omitted=len(_iface_mediated_classes) - 30,
10203
+ )
10174
10204
  out["via_interface_note"] = (
10175
10205
  "Target is a concrete class injected via interface(s) in a "
10176
10206
  f"{_di_label} DI framework. `interface_mediated_callers` reach it "
sourcecode/sarif.py CHANGED
@@ -590,6 +590,8 @@ def enrich_sarif(
590
590
  "kind each row is."
591
591
  ),
592
592
  "shown": len(shown),
593
+ "min_band": min_band,
594
+ "min_band_total": len(kept),
593
595
  "by_band": by_band,
594
596
  **({"profile_set": sorted(composer.profiles)} if composer.profiles is not None else {}),
595
597
  "findings": [
@@ -28,6 +28,7 @@ from typing import TYPE_CHECKING, Optional
28
28
  from sourcecode.caller_metrics import (
29
29
  CALLER_METRIC_RECONCILIATION,
30
30
  CALLERS_TOTAL_DEPRECATION,
31
+ DIRECT_CALLER_COUNT_DEPRECATION,
31
32
  caller_classes,
32
33
  )
33
34
  from sourcecode.caller_reach import CALLER_CAP, SKIP_EDGE_TYPES
@@ -1628,10 +1629,29 @@ class ImpactOrchestrator:
1628
1629
  caller_classes(direct_callers, exclude=seed_fqns)
1629
1630
  | caller_classes(indirect_callers, exclude=seed_fqns)
1630
1631
  ),
1632
+ # AUD-593-N01: this walk starts from a seed set expanded with
1633
+ # the members of every interface the target declares (CH-001b),
1634
+ # so "direct" here admits classes that name a sibling
1635
+ # implementation and never name the target. Measured 554 against
1636
+ # `impact`'s 34 for one symbol at one HEAD — under a name that
1637
+ # reads as the peer of `impact.stats.direct_caller_count` and a
1638
+ # population tag that claimed to be the same. The figure is
1639
+ # right; its name and its declared population were not.
1640
+ "expanded_seed_caller_count": len(
1641
+ caller_classes(direct_callers, exclude=seed_fqns)
1642
+ ),
1643
+ "expanded_seed_caller_count_unit": "distinct caller classes",
1644
+ "expanded_seed_caller_count_population": (
1645
+ "classes calling the target OR any member of an interface it "
1646
+ "declares. `ask impact` admits only references naming the "
1647
+ "target, so its direct_caller_count is a subset of this and "
1648
+ "the two are not comparable"
1649
+ ),
1631
1650
  "direct_caller_count": len(
1632
1651
  caller_classes(direct_callers, exclude=seed_fqns)
1633
1652
  ),
1634
1653
  "direct_caller_count_unit": "distinct caller classes",
1654
+ "direct_caller_count_note": DIRECT_CALLER_COUNT_DEPRECATION,
1635
1655
  "direct_caller_symbol_count": len(direct_callers),
1636
1656
  "direct_caller_symbol_count_unit": "direct caller list entries",
1637
1657
  "indirect_caller_symbol_count": len(indirect_callers),
sourcecode/summarizer.py CHANGED
@@ -276,7 +276,23 @@ class ProjectSummarizer:
276
276
  r"|\bno need to deal with\b"
277
277
  r"|\bwith minimum effort\b"
278
278
  r"|\bsign up\b.*\bevaluation\b"
279
- r"|\bcontact us\b.*\bmore information\b",
279
+ r"|\bcontact us\b.*\bmore information\b"
280
+ # AUD-592-R02: the four standard boilerplates. This regex matched product
281
+ # tiers and marketing and carried no Apache/MIT/GPL/BSD pattern, so an
282
+ # Apache-2.0 header reached `project_summary` as the project description.
283
+ r"|\bapache licen[sc]e\b"
284
+ r"|\blicensed under the apache\b"
285
+ r"|\bversion 2\.0, january 2004\b"
286
+ r"|\bwww\.apache\.org/licenses\b"
287
+ r"|\bmit licen[sc]e\b"
288
+ r"|\bgnu (?:general|lesser) public licen[sc]e\b"
289
+ r"|\bbsd \d-clause\b"
290
+ r"|\bredistribution and use in source and binary forms\b"
291
+ r"|\bterms and conditions for use, reproduction\b"
292
+ r"|\bpermission is hereby granted, free of charge\b"
293
+ r"|\bwithout warranties or conditions of any kind\b"
294
+ r"|\bcopyright \(c\)\s*\d{4}"
295
+ r"|\bunless required by applicable law\b",
280
296
  __import__("re").IGNORECASE,
281
297
  )
282
298
 
@@ -310,10 +326,18 @@ class ProjectSummarizer:
310
326
  _BADGE_RE = _re.compile(r"^\[?!\[") # [![badge](...)] or ![img](...)
311
327
  _LINK_ONLY_RE = _re.compile(r"^\[.*?\]\(.*?\)$") # pure link line
312
328
 
329
+ # AUD-592-R02: a horizontal rule is a separator in every width. The
330
+ # usefulness floor is a length test, so a rule of exactly 30 dashes was
331
+ # long enough to be published as the description of the project.
332
+ _RULE_RE = _re.compile(r"^(?:-{3,}|={3,}|\*{3,}|_{3,})$")
333
+ # Setext underlines: the line above them is a heading, not content.
334
+ _SETEXT_RE = _re.compile(r"^(?:=+|-+)$")
335
+
313
336
  paragraphs: list[tuple[str, str]] = []
314
337
  current_lines: list[str] = []
315
338
  current_heading = ""
316
339
  in_code_block = False
340
+ in_html_comment = False
317
341
 
318
342
  def _flush() -> None:
319
343
  nonlocal current_lines
@@ -323,6 +347,14 @@ class ProjectSummarizer:
323
347
 
324
348
  for raw_line in content.splitlines():
325
349
  line = raw_line.strip()
350
+ # An HTML comment spans lines. Detecting only its opening line —
351
+ # three lines below where `in_code_block` does exactly this for
352
+ # fences — let a `<!--` licence header on line 1 flush, and lines
353
+ # 2-15 of the licence become the README's first paragraph.
354
+ if in_html_comment:
355
+ if "-->" in line:
356
+ in_html_comment = False
357
+ continue
326
358
  if line.startswith("```"):
327
359
  in_code_block = not in_code_block
328
360
  continue
@@ -332,7 +364,22 @@ class ProjectSummarizer:
332
364
  _flush()
333
365
  current_heading = line.lstrip("#").strip()
334
366
  continue
335
- if not line or line.startswith(("<!--", ">")):
367
+ if line.startswith("<!--"):
368
+ _flush()
369
+ if "-->" not in line:
370
+ in_html_comment = True
371
+ continue
372
+ # A setext underline retitles the line above it. Without this, a
373
+ # README whose title is underlined with `===` has no heading at all
374
+ # and its title is content.
375
+ if current_lines and _SETEXT_RE.match(line):
376
+ current_heading = current_lines.pop().strip()
377
+ _flush()
378
+ continue
379
+ if _RULE_RE.match(line):
380
+ _flush()
381
+ continue
382
+ if not line or line.startswith(">"):
336
383
  _flush()
337
384
  continue
338
385
  # Treat HTML-markup-only lines (<img .../>, <p align=...>, <a><img></a>)
@@ -379,9 +426,16 @@ class ProjectSummarizer:
379
426
  cleaned = self._clean_inline_markup(paragraph)
380
427
  if len(cleaned) < 30:
381
428
  continue
429
+ # AUD-592-R02: say which paragraph this is and what was checked,
430
+ # not that the result is descriptive. The unconditional
431
+ # "README descriptive prose" asserted a property of the text over
432
+ # whatever survived the filters — including an Apache header.
382
433
  self.summary_basis = (
383
- f"README descriptive section: {heading}" if heading
384
- else "README descriptive prose"
434
+ f"README section '{heading}', first paragraph passing the "
435
+ "licence, marketing, operations, startup and tool-output filters"
436
+ if heading else
437
+ "README lead paragraph, passing the licence, marketing, "
438
+ "operations, startup and tool-output filters (no heading above it)"
385
439
  )
386
440
  return cleaned
387
441
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.8.17
3
+ Version: 5.8.18
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
@@ -55,7 +55,7 @@ Description-Content-Type: text/markdown
55
55
  > question. All analysis is local and deterministic; see [Privacy](#configuration--privacy)
56
56
  > before inspecting a repository whose telemetry policy you need to control.
57
57
 
58
- ![Version](https://img.shields.io/badge/version-5.8.17-blue)
58
+ ![Version](https://img.shields.io/badge/version-5.8.18-blue)
59
59
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
60
60
 
61
61
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -125,7 +125,7 @@ stops at 1 408 files and ends at 5.1s. Commands outside that phase-runner set
125
125
  must not be gated on this variable; their budget applicability remains visible in
126
126
  the [defect ledger](docs/DEFECT-LEDGER.md).
127
127
 
128
- **Current performance status.** Release 5.8.17 retains the shared Canonical IR
128
+ **Current performance status.** Release 5.8.18 retains the shared Canonical IR
129
129
  path used by `validation` and now passes that same CIR into labelled `data-exposure`
130
130
  runs; the no-label path remains parse-free. The one-model-per-declared-label path is
131
131
  also retained. These are implementation properties, not duration promises:
@@ -159,7 +159,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
159
159
  # pip / pipx
160
160
  pipx install sourcecode # or: pip install sourcecode
161
161
 
162
- ask version # ask 5.8.17 — and, on a build that has aged,
162
+ ask version # ask 5.8.18 — and, on a build that has aged,
163
163
  # how many releases have probably shipped since
164
164
  ```
165
165
 
@@ -1,4 +1,4 @@
1
- sourcecode/__init__.py,sha256=bczi04jMzMOQ5s5PXRmFAX_OMdjbE3ZRYlBagr1hDU4,309
1
+ sourcecode/__init__.py,sha256=YsPRdjH3wWeYUXkz4KK3Vv0rjKRxj2E6IDH0VNm4MdU,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=V_s5grgpbanrXb8qGEQb73W5Cg5iKW9FQHWKgg5KM8Y,41201
@@ -18,7 +18,7 @@ sourcecode/bundled_docs.py,sha256=hvQeUNDo9SEp_Mtf6o9bexKUmJ8YW76rXKn9spRfQQs,25
18
18
  sourcecode/cache.py,sha256=fozQb7ohE8TwwOhEcxA0lwsmvIuZNW7jxAeiget50z4,43471
19
19
  sourcecode/cache_model.py,sha256=_q6CJV4PksQRxNkCIwQ8hEj8yePiU5460rDoaoGAtgo,62032
20
20
  sourcecode/call_surface.py,sha256=fiqYfHooxN1fX9oQoysq1LS3LoZcobhyUNjAGEZKpwk,4148
21
- sourcecode/caller_metrics.py,sha256=--sFGDnIog_YGu9xZHMcB91F5xZakfaAKvy15xU54hg,7904
21
+ sourcecode/caller_metrics.py,sha256=6eXgmqToa6_a-bb3jYw9NAn4hvgbYHdlUErSpiSbO2g,10464
22
22
  sourcecode/caller_reach.py,sha256=M7Ffs5CtlimihfdSy3HtC4WpDP97bRFX15QYOlOfMJw,16626
23
23
  sourcecode/canonical_ir.py,sha256=rBgl7bbj529qvzal_c9yMyGXcwyMeoJCFcOU3vtgBJY,36797
24
24
  sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,12767
@@ -26,7 +26,7 @@ sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,129
26
26
  sourcecode/ci_output.py,sha256=un1GjW0iw9hQA9g-7xpOseBxctQIrAic57WKxTbIGNA,2457
27
27
  sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
28
28
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
29
- sourcecode/cli.py,sha256=QfgH33kjrAN0l7mq4OOKhAA1YXYYjOe82i5OLG3xroE,773533
29
+ sourcecode/cli.py,sha256=X1rwFmlTE8jwh27681UZZTTeuUUcg6jmhKDv9yQKV-8,775596
30
30
  sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
31
31
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
32
32
  sourcecode/compare.py,sha256=FeDsbf7VACLcc6muA9mx_3iHmlDyq7QFqGPYGme9jtY,15141
@@ -110,7 +110,7 @@ sourcecode/posture.py,sha256=6Rg4s-OUBYhXFlsVIbU_8H_bDB15zPjkhSNGzq3kUfs,85077
110
110
  sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
111
111
  sourcecode/pr_impact.py,sha256=taHp0X57tLHyKQ5Qvx7UM1HhX4_csw5t0bgLdN7jYm8,37010
112
112
  sourcecode/prepare_context.py,sha256=Dc31QYmhgsvLvPav4py4E0RK_QBlj5K8oJ5TFn9ATi8,240881
113
- sourcecode/product_info.py,sha256=0zjZEutL7iDdp9aVyIumIrNyNn9VWYFc2lIytSbjBP4,727
113
+ sourcecode/product_info.py,sha256=SQPELTh7hI3nMOsxImJesI4_XUFwCDgb9jTUqwv0GJA,1360
114
114
  sourcecode/progress.py,sha256=naKbWFqf-eTE3vtLsi-eHYILz0p3pxCASAzMmvxUIAI,21643
115
115
  sourcecode/provenance.py,sha256=gLbsLv0gze7k7ChM4i5M3MwKfFPzs8wzM72rUd2C5sM,7342
116
116
  sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
@@ -126,7 +126,7 @@ sourcecode/remediation_diff.py,sha256=fGYqPmQF7bO54L8ivztX5PgmW4fVu54gNcG6OEQ192
126
126
  sourcecode/remedies.py,sha256=V2_cD4pLq8bKRkzF-3RGFoiu1xWYZS384WDVp1MrlZw,10225
127
127
  sourcecode/rename_refactor.py,sha256=MOTsNqukDpDhAXRPx5skWzdAD-junM4H9tks8deKDVI,13447
128
128
  sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
129
- sourcecode/repository_ir.py,sha256=zQNyWwMQeOdgOH5UB154JdETkKSBqfhq7sOF1JlpNdg,501923
129
+ sourcecode/repository_ir.py,sha256=qLby9Kj4HqJuy6Rb7uNzo-ms7WXJVYLs27m6cED4SNc,503332
130
130
  sourcecode/ris.py,sha256=zlns4esv1tK8VDIYzOzRmw5yM6VLx7oxaPNB_kN3it4,28617
131
131
  sourcecode/risk.py,sha256=4Hu84_l8HnakAOIJcJJWAXg8IU_KZIGRvPXvEbJr0zo,90363
132
132
  sourcecode/risk_model.py,sha256=RoC-G15qZP5BLEwwmcr4eJ0APgvIbh1WTqhTVT-5YFE,10895
@@ -134,7 +134,7 @@ sourcecode/rule_catalog.py,sha256=QCx6ToFTMCtDt5uuX1uBMLuw3b9Cd92gGRsX34kud4U,53
134
134
  sourcecode/rule_pass.py,sha256=DTNxD-nAecMJhIzI1LCd9FyRumInQSZjitacNh4Zoc8,14671
135
135
  sourcecode/runs.py,sha256=o4oXkB3WFJGScbK_C10OAWzsE18B9Zc55u_IE8_yl4M,6291
136
136
  sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
137
- sourcecode/sarif.py,sha256=3hQEegUxIZbojFdY59oB-yKsK-rnafHdTNmYsEP2--A,25984
137
+ sourcecode/sarif.py,sha256=AfsaOTwPE5KCxEEF14-W-n5Jn6Npfu_MhjFWtysMdUU,26051
138
138
  sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
139
139
  sourcecode/schema.py,sha256=61Ux_qRITs_orcrZM20ctV2k1ZfzauqqMgJlZJYAEbE,25087
140
140
  sourcecode/schema_producers.py,sha256=4XzW4wZXnFHOaJT7XenirXGCo0pPZhkdhb7zcDtqFnM,10390
@@ -153,7 +153,7 @@ sourcecode/servlet_surface.py,sha256=CeiebkxLXKojUQxIf7HV7tdQAwLM32ggDUwBnVQIjdo
153
153
  sourcecode/source_text.py,sha256=R52gmSljz7L6YVPbixv5W-LOZ5eKUVvSUP3S-QH_IWc,9047
154
154
  sourcecode/spring_event_topology.py,sha256=5_ON_21Le5zbG-1GRc5GLIi5HJfy_QjcXLVPC5WeUGQ,18055
155
155
  sourcecode/spring_findings.py,sha256=6Obf69EevvRL1sHZaoeIMrui5HH8oFyS_OSUO0W1tCQ,26242
156
- sourcecode/spring_impact.py,sha256=ORAWTLGoOKU0qJkfseeI2hV53jMKjvrXKVIg7CZo6pY,91096
156
+ sourcecode/spring_impact.py,sha256=dTmsAwsbUyxD9LAmTz5RQodeqWcW64rhF0IM-4SuZXM,92418
157
157
  sourcecode/spring_model.py,sha256=-wjfNP6XCcPgdkZr7jpj2CHRqWf8qB542ns-hs0nSEY,24713
158
158
  sourcecode/spring_profiles.py,sha256=-kwrCK0O-MRjrCp6SA1d31t--o4Tg87iqPVrTwxoIUs,19605
159
159
  sourcecode/spring_properties.py,sha256=kPTk5qAJxdbHc1hclhHAP0O-hCIzBn8PTh3lzl-I4a4,8400
@@ -161,7 +161,7 @@ sourcecode/spring_security_audit.py,sha256=Zs26iJRlLPS9pOYoNraM7I1fZteoTaJbSwCrz
161
161
  sourcecode/spring_semantic.py,sha256=zrUYWF7Gnz6hpP_3hRveHmGEntT95WpgXt6u7PzLSGg,16835
162
162
  sourcecode/spring_tx_analyzer.py,sha256=zpGea2RXWiyEdT-eg1jGrywhqRFuN5VG9MAF6rIKweI,50466
163
163
  sourcecode/sql_taint.py,sha256=As1DbEdjXMBFjCTXU5ojksOzuKkP6LmnuMVnzzmckzc,26998
164
- sourcecode/summarizer.py,sha256=8PoGDKjjmGNJ4fZnqsHAV_jo_99Q3_IQy6zx_47jW50,30165
164
+ sourcecode/summarizer.py,sha256=t8m77JJhPSIZtpvGptHygeL-WMSaWLq9DcWFCXKY-SI,33061
165
165
  sourcecode/target_admission.py,sha256=wFZ4pzlxhiF6Q6s2lEAZEzcCfj1Y6xNvujjt8MdO0Qo,7154
166
166
  sourcecode/test_gap_ranking.py,sha256=hl-tTyQUXZGJycG1b6npbLeE_DaVHaOsvPwG9qqC5y8,15711
167
167
  sourcecode/test_sources.py,sha256=KM88qbMfLDmUV5X2nA83LjYz8A2ipj4A_2u2FhZx8gc,9440
@@ -243,13 +243,13 @@ sourcecode/telemetry/consent.py,sha256=-Wb8BFGXEexOs8M0Eja7F-T6ZtLNPeRoZnHcHvlP7
243
243
  sourcecode/telemetry/events.py,sha256=l3Z2SQUO4_C2D2ItXOOdVNimAmHedUHnUxqYFTxnsAE,2900
244
244
  sourcecode/telemetry/filters.py,sha256=-lqT2oorECuqEIdeHb--7AZ6BCaUx0bya720a-Z5x6w,7335
245
245
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
246
- sourcecode/_docs/DEFECT-LEDGER.md,sha256=DPfdl22qA8QXCNpsUfTWSJnOhaDZ7y7brn2TZaY4LaQ,781688
246
+ sourcecode/_docs/DEFECT-LEDGER.md,sha256=253tOgQyij_eor4Kgto143F9ATSPfaqSoXomUCiZOEo,809733
247
247
  sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
248
248
  sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
249
- sourcecode/_docs/USER_GUIDE.md,sha256=gRQMS79BEfQNa2uFrKZp4WVkbhQ3bW73wZ4WdzAjoYg,90158
250
- sourcecode-5.8.17.dist-info/METADATA,sha256=vGKXjN6u3zK87j5QC4mPyz0--nGdJ1u9XPb5PV8GoNA,53019
251
- sourcecode-5.8.17.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
252
- sourcecode-5.8.17.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
253
- sourcecode-5.8.17.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
254
- sourcecode-5.8.17.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
255
- sourcecode-5.8.17.dist-info/RECORD,,
249
+ sourcecode/_docs/USER_GUIDE.md,sha256=_t_OWeMLWJknKqNJ7fYowf0QKSUKwM_Pqeb92b0Ofvs,90273
250
+ sourcecode-5.8.18.dist-info/METADATA,sha256=uXRN8oHMzTmLhr51wLtnp2evzHU_n9KhYfW5b8pZlko,53019
251
+ sourcecode-5.8.18.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
252
+ sourcecode-5.8.18.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
253
+ sourcecode-5.8.18.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
254
+ sourcecode-5.8.18.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
255
+ sourcecode-5.8.18.dist-info/RECORD,,