sourcecode 4.14.0__py3-none-any.whl → 4.16.0__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 +1 -1
- sourcecode/cli.py +73 -37
- sourcecode/phased_run.py +88 -2
- sourcecode/progress.py +27 -0
- sourcecode/release_info.py +1 -1
- sourcecode/repository_ir.py +12 -0
- sourcecode/risk.py +136 -3
- sourcecode/rule_pass.py +59 -6
- sourcecode/security_config_scan.py +20 -8
- sourcecode/spring_security_audit.py +16 -6
- sourcecode/spring_tx_analyzer.py +12 -6
- {sourcecode-4.14.0.dist-info → sourcecode-4.16.0.dist-info}/METADATA +51 -11
- {sourcecode-4.14.0.dist-info → sourcecode-4.16.0.dist-info}/RECORD +16 -16
- {sourcecode-4.14.0.dist-info → sourcecode-4.16.0.dist-info}/WHEEL +0 -0
- {sourcecode-4.14.0.dist-info → sourcecode-4.16.0.dist-info}/entry_points.txt +0 -0
- {sourcecode-4.14.0.dist-info → sourcecode-4.16.0.dist-info}/licenses/LICENSE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/cli.py
CHANGED
|
@@ -1261,16 +1261,23 @@ def _analysis_budget(command: str) -> dict[str, Any]:
|
|
|
1261
1261
|
command, ("repo-wide", 600, "run this command in a supervised or nightly job")
|
|
1262
1262
|
)
|
|
1263
1263
|
configured: Optional[float] = None
|
|
1264
|
+
source: Optional[str] = None
|
|
1264
1265
|
raw = os.environ.get("ASK_MAX_ANALYSIS_SECONDS")
|
|
1265
1266
|
if raw:
|
|
1266
1267
|
try:
|
|
1267
1268
|
configured = float(raw)
|
|
1268
1269
|
except ValueError:
|
|
1269
1270
|
configured = None
|
|
1271
|
+
else:
|
|
1272
|
+
# C3-83: a budget is inherited from a shell or a CI runner, so the
|
|
1273
|
+
# person reading `total_defects` is often not the person who set it.
|
|
1274
|
+
# Where it came from travels with it, from here to `_partial`.
|
|
1275
|
+
source = f"ASK_MAX_ANALYSIS_SECONDS={raw} (process environment)"
|
|
1270
1276
|
return {
|
|
1271
1277
|
"class": cls,
|
|
1272
1278
|
"recommended_min_seconds": recommended,
|
|
1273
1279
|
"configured_max_seconds": configured,
|
|
1280
|
+
"configured_source": source,
|
|
1274
1281
|
"fallback": fallback,
|
|
1275
1282
|
}
|
|
1276
1283
|
|
|
@@ -1318,9 +1325,23 @@ def _enforce_analysis_budget(
|
|
|
1318
1325
|
"""
|
|
1319
1326
|
configured = budget.get("configured_max_seconds")
|
|
1320
1327
|
recommended = float(budget.get("recommended_min_seconds") or 0)
|
|
1321
|
-
if configured is None
|
|
1328
|
+
if configured is None:
|
|
1329
|
+
return
|
|
1330
|
+
source = budget.get("configured_source")
|
|
1331
|
+
from sourcecode.phased_run import budget_provenance, budget_warning
|
|
1332
|
+
|
|
1333
|
+
if float(configured) >= recommended:
|
|
1334
|
+
# C3-83: over the floor there is no advice to give, and there is still a
|
|
1335
|
+
# fact the reader of the answer needs — a limit set in an environment is
|
|
1336
|
+
# invisible to whoever reads `total_defects` afterwards.
|
|
1337
|
+
if not source:
|
|
1338
|
+
return
|
|
1339
|
+
try:
|
|
1340
|
+
sys.stderr.write(budget_provenance(command, float(configured), source))
|
|
1341
|
+
sys.stderr.flush()
|
|
1342
|
+
except Exception:
|
|
1343
|
+
pass
|
|
1322
1344
|
return
|
|
1323
|
-
from sourcecode.phased_run import budget_warning
|
|
1324
1345
|
|
|
1325
1346
|
_anchors, _scope = _budget_evidence(command, path)
|
|
1326
1347
|
try:
|
|
@@ -1328,7 +1349,7 @@ def _enforce_analysis_budget(
|
|
|
1328
1349
|
budget_warning(
|
|
1329
1350
|
command, str(budget.get("class") or "repo-wide"),
|
|
1330
1351
|
float(configured), recommended,
|
|
1331
|
-
anchors=_anchors, scope=_scope,
|
|
1352
|
+
anchors=_anchors, scope=_scope, source=source,
|
|
1332
1353
|
)
|
|
1333
1354
|
)
|
|
1334
1355
|
sys.stderr.flush()
|
|
@@ -1960,12 +1981,14 @@ def _output_option(help: "Optional[str]" = None) -> Any:
|
|
|
1960
1981
|
COMMANDS_WITHOUT_OUTPUT: "frozenset[str]" = frozenset()
|
|
1961
1982
|
|
|
1962
1983
|
|
|
1963
|
-
#: The one help string for `--jobs` (F-AQ).
|
|
1984
|
+
#: The one help string for `--jobs` (F-AQ; the scope sentence is C3-84/CL-18).
|
|
1964
1985
|
JOBS_OPTION_HELP = (
|
|
1965
1986
|
"Worker processes used to parse files (default: CPU count − 1). "
|
|
1966
1987
|
"`--jobs 1` parses in this process. Parallelism only warms the "
|
|
1967
1988
|
"content-addressed parse cache — the answer is byte-identical at any value. "
|
|
1968
|
-
"
|
|
1989
|
+
"It does NOT parallelise the rule pass, which is where a repository-wide "
|
|
1990
|
+
"audit spends most of its time, and on a warm cache there is nothing left "
|
|
1991
|
+
"to parse, so raising it buys nothing there. Env: ASK_JOBS."
|
|
1969
1992
|
)
|
|
1970
1993
|
|
|
1971
1994
|
|
|
@@ -1980,6 +2003,18 @@ def _jobs_option() -> Any:
|
|
|
1980
2003
|
repository — so it is recorded in :mod:`sourcecode.parallel` rather than
|
|
1981
2004
|
threaded through every analysis signature. It is published only on commands
|
|
1982
2005
|
that parse a repository; `ASK_JOBS` covers the rest.
|
|
2006
|
+
|
|
2007
|
+
**C3-84 / CL-18.** The fifth evaluation read this help, sampled the process
|
|
2008
|
+
and corrected four rounds of its own diagnosis: parallelism exists and covers
|
|
2009
|
+
the phase that is not the cost. `threads=4` for the first seconds (parsing),
|
|
2010
|
+
then `threads=1` with CPU/wall **0,97** for the remaining ~99 % — the rule
|
|
2011
|
+
pass, applying families over a built IR — and on a warm cache, which is the
|
|
2012
|
+
normal case, there is nothing to parse at all. The old sentence was true
|
|
2013
|
+
about what the flag *does* and silent about what it does not, which is the
|
|
2014
|
+
half that decides whether setting it is worth anything. Saying so costs a
|
|
2015
|
+
line; letting an operator raise it and measure no change costs trust. The
|
|
2016
|
+
capability that makes the rule pass parallel is F-AT, and no help text is a
|
|
2017
|
+
substitute for it.
|
|
1983
2018
|
"""
|
|
1984
2019
|
return typer.Option(None, "--jobs", "-j", min=1, help=JOBS_OPTION_HELP)
|
|
1985
2020
|
|
|
@@ -8181,6 +8216,8 @@ def spring_audit_cmd(
|
|
|
8181
8216
|
phases=_phases,
|
|
8182
8217
|
output_path=output_path,
|
|
8183
8218
|
budget_seconds=_analysis_budget("spring-audit").get("configured_max_seconds"),
|
|
8219
|
+
# C3-83: the budget's origin travels with the budget, into `_partial`.
|
|
8220
|
+
budget_source=_analysis_budget("spring-audit").get("configured_source"),
|
|
8184
8221
|
writer=_safe_write_file,
|
|
8185
8222
|
)
|
|
8186
8223
|
_stopped_early: Optional[str] = None
|
|
@@ -8193,6 +8230,14 @@ def spring_audit_cmd(
|
|
|
8193
8230
|
cir = ContextGraph.build(
|
|
8194
8231
|
file_list, target, progress=_work_sink(_prog)
|
|
8195
8232
|
).cir
|
|
8233
|
+
# C3-85: the stretch between the last `linking` tick and the first rule
|
|
8234
|
+
# tick reports nothing, and what stays on the line is `n/n` — a counter
|
|
8235
|
+
# that finished, reading as a stage still running. Measured on keycloak
|
|
8236
|
+
# (5 486 files, warm): **8,2 s of a 24 s run, 34 %**, all of it here.
|
|
8237
|
+
# It has no population to count — it is one walk that builds the model
|
|
8238
|
+
# every rule then reads — so it is named, which is what C3-56 does for
|
|
8239
|
+
# a stage whose denominator would have to be invented.
|
|
8240
|
+
_prog.update(f"building the semantic model over {len(file_list)} Java files")
|
|
8196
8241
|
_model = SpringSemanticModel.build(cir)
|
|
8197
8242
|
_run.checkpoint("ir")
|
|
8198
8243
|
# The IR is built; the rule passes are the work now, and they are not
|
|
@@ -8316,37 +8361,15 @@ def spring_audit_cmd(
|
|
|
8316
8361
|
|
|
8317
8362
|
data = combined.to_compact_dict() if compact else combined.to_dict()
|
|
8318
8363
|
if _stopped_early:
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
+ "; ".join(
|
|
8329
|
-
f"{'/'.join(entry['rule_ids'])} after "
|
|
8330
|
-
f"{entry['units_done']} of {entry['units_total']} "
|
|
8331
|
-
f"{entry['unit']}"
|
|
8332
|
-
for entry in _rules_partial
|
|
8333
|
-
)
|
|
8334
|
-
+ ". What it found before the cut is reported and is a floor."
|
|
8335
|
-
)
|
|
8336
|
-
if _rules_cut:
|
|
8337
|
-
# C3-71: a phase stopped *inside* is neither completed nor
|
|
8338
|
-
# pending, and calling it either would misreport the run. The
|
|
8339
|
-
# families it did not reach are named instead.
|
|
8340
|
-
data["_partial"]["rules_not_run"] = _rules_cut
|
|
8341
|
-
# A phase that was stopped inside is in neither list, and
|
|
8342
|
-
# putting it in `phases_pending` alone would claim none of it
|
|
8343
|
-
# ran when most of it did.
|
|
8344
|
-
data["_partial"]["phases_stopped_inside"] = _phases_cut
|
|
8345
|
-
data["_partial"]["how_to_read"] += (
|
|
8346
|
-
" The rule pass was stopped between families: "
|
|
8347
|
-
f"{', '.join(_rules_cut)} did not run, so every count here "
|
|
8348
|
-
"is a floor over the families that did."
|
|
8349
|
-
)
|
|
8364
|
+
# C3-87: the block is composed by `PhasedRun.status`, which is the
|
|
8365
|
+
# authority `risk` is now bound to as well — one vocabulary for
|
|
8366
|
+
# "the budget cut this run", not one per command.
|
|
8367
|
+
data["_partial"] = _run.status(
|
|
8368
|
+
_stopped_early,
|
|
8369
|
+
rules_partially_run=_rules_partial,
|
|
8370
|
+
rules_not_run=_rules_cut,
|
|
8371
|
+
phases_stopped_inside=_phases_cut,
|
|
8372
|
+
)
|
|
8350
8373
|
|
|
8351
8374
|
# Non-fatal RIS side-effect — persist summary only (not full findings).
|
|
8352
8375
|
try:
|
|
@@ -8899,6 +8922,8 @@ def risk_cmd(
|
|
|
8899
8922
|
phases=["audit", "compose"],
|
|
8900
8923
|
output_path=output_path,
|
|
8901
8924
|
budget_seconds=_analysis_budget("risk").get("configured_max_seconds"),
|
|
8925
|
+
# C3-83: the budget's origin travels with the budget, into `_partial`.
|
|
8926
|
+
budget_source=_analysis_budget("risk").get("configured_source"),
|
|
8902
8927
|
writer=_safe_write_file,
|
|
8903
8928
|
)
|
|
8904
8929
|
with _expensive_analysis_scope("risk", path, phase):
|
|
@@ -8910,10 +8935,18 @@ def risk_cmd(
|
|
|
8910
8935
|
profiles=_profile_set(profile),
|
|
8911
8936
|
# C3-73: 54 minutes with one line on stderr in the field. The
|
|
8912
8937
|
# factory hands the composer an observer per counted stage.
|
|
8938
|
+
#
|
|
8939
|
+
# C3-87: and the observer carries the deadline. This factory built
|
|
8940
|
+
# its observers with no `stop_when` for four releases, so `risk`
|
|
8941
|
+
# declared `budget_seconds` on the run above and then never asked
|
|
8942
|
+
# whether it was spent — the budget was honoured only between the
|
|
8943
|
+
# two phases. `PhasedRun.exhausted` stays the one authority; what
|
|
8944
|
+
# is new is that the second consumer asks it.
|
|
8913
8945
|
progress=lambda stage, unit="rule families": _rule_pass_progress(
|
|
8914
|
-
_prog, stage, unit
|
|
8946
|
+
_prog, stage, unit, stop_when=_run.exhausted
|
|
8915
8947
|
),
|
|
8916
8948
|
checkpoint=_run.checkpoint,
|
|
8949
|
+
partial_status=lambda **cut: _run.status(_WHY_BUDGET, **cut),
|
|
8917
8950
|
)
|
|
8918
8951
|
finally:
|
|
8919
8952
|
_prog.stop()
|
|
@@ -8930,6 +8963,9 @@ def risk_cmd(
|
|
|
8930
8963
|
success_msg=(
|
|
8931
8964
|
f"risk written to {output_path} ({data['shown']} of "
|
|
8932
8965
|
f"{data['total_defects']} defects composed)"
|
|
8966
|
+
# C3-87: the terminal says it too. A reader who never opens the payload
|
|
8967
|
+
# must not take a truncated ranking for the repository's risk.
|
|
8968
|
+
+ (" — PARTIAL, budget exhausted" if data.get("partial") else "")
|
|
8933
8969
|
),
|
|
8934
8970
|
)
|
|
8935
8971
|
# The complete answer is on disk; the checkpoint that stood in for it is not
|
sourcecode/phased_run.py
CHANGED
|
@@ -88,6 +88,11 @@ class PhasedRun:
|
|
|
88
88
|
phases: "list[str]"
|
|
89
89
|
output_path: Optional[Path] = None
|
|
90
90
|
budget_seconds: Optional[float] = None
|
|
91
|
+
#: Where `budget_seconds` came from, verbatim (C3-83) — e.g.
|
|
92
|
+
#: `ASK_MAX_ANALYSIS_SECONDS=420 (process environment)`. A truncated answer
|
|
93
|
+
#: declares everything about *being* truncated and could not say what decided
|
|
94
|
+
#: it, and the variable is inherited from a shell or a runner.
|
|
95
|
+
budget_source: Optional[str] = None
|
|
91
96
|
#: injected by the CLI so a checkpoint uses the same durable write as the
|
|
92
97
|
#: final answer (fsync + atomic rename), rather than a second, worse one.
|
|
93
98
|
writer: Optional[Callable[[Path, str], None]] = None
|
|
@@ -126,8 +131,29 @@ class PhasedRun:
|
|
|
126
131
|
done = set(self.completed)
|
|
127
132
|
return [p for p in self.phases if p not in done]
|
|
128
133
|
|
|
129
|
-
def status(
|
|
130
|
-
|
|
134
|
+
def status(
|
|
135
|
+
self,
|
|
136
|
+
why: str,
|
|
137
|
+
*,
|
|
138
|
+
rules_partially_run: "Optional[list[dict]]" = None,
|
|
139
|
+
rules_not_run: "Optional[list[str]]" = None,
|
|
140
|
+
phases_stopped_inside: "Optional[list[str]]" = None,
|
|
141
|
+
) -> dict:
|
|
142
|
+
"""The block an incomplete answer carries, and nothing else does.
|
|
143
|
+
|
|
144
|
+
**C3-87.** The three rule-pass keys used to be assembled by the caller, and
|
|
145
|
+
for four releases there was exactly one caller, so "the caller" and "the
|
|
146
|
+
authority" were the same object by accident. When `risk` was bound to the
|
|
147
|
+
same budget it became clear what that costs: a second consumer either
|
|
148
|
+
duplicates thirty lines of `how_to_read` composition or publishes a
|
|
149
|
+
different vocabulary for the same fact. They live here now, so a command
|
|
150
|
+
that stops inside its rule pass describes it the one way — and a *third*
|
|
151
|
+
consumer gets it by construction.
|
|
152
|
+
|
|
153
|
+
Each key is present only when there is something to say. A run stopped
|
|
154
|
+
between phases, with no family cut, produces exactly the block it produced
|
|
155
|
+
before this seam existed.
|
|
156
|
+
"""
|
|
131
157
|
out: dict[str, Any] = {
|
|
132
158
|
"schema_version": SCHEMA_VERSION,
|
|
133
159
|
"partial": True,
|
|
@@ -140,6 +166,40 @@ class PhasedRun:
|
|
|
140
166
|
}
|
|
141
167
|
if self.budget_seconds:
|
|
142
168
|
out["budget_seconds"] = float(self.budget_seconds)
|
|
169
|
+
if self.budget_source:
|
|
170
|
+
out["budget_source"] = self.budget_source
|
|
171
|
+
if rules_partially_run:
|
|
172
|
+
# C3-79: neither run nor not-run. How far it got is what a reader needs
|
|
173
|
+
# in order to decide whether to re-run with more time, and the findings
|
|
174
|
+
# it produced before the cut are in the payload.
|
|
175
|
+
out["rules_partially_run"] = list(rules_partially_run)
|
|
176
|
+
out["how_to_read"] += (
|
|
177
|
+
" One rule family was stopped part-way through its own "
|
|
178
|
+
"population: "
|
|
179
|
+
+ "; ".join(
|
|
180
|
+
f"{'/'.join(entry['rule_ids'])} after "
|
|
181
|
+
f"{entry['units_done']} of {entry['units_total']} "
|
|
182
|
+
f"{entry['unit']}"
|
|
183
|
+
for entry in rules_partially_run
|
|
184
|
+
)
|
|
185
|
+
+ ". What it found before the cut is reported and is a floor."
|
|
186
|
+
)
|
|
187
|
+
if phases_stopped_inside:
|
|
188
|
+
# C3-71: a phase stopped *inside* is neither completed nor pending, and
|
|
189
|
+
# putting it in `phases_pending` alone would claim none of it ran when
|
|
190
|
+
# most of it did. Independent of the rule keys: a phase can be cut in a
|
|
191
|
+
# walk that evaluates no rules at all, which is how `risk` loses its
|
|
192
|
+
# composition join (C3-87).
|
|
193
|
+
out["phases_stopped_inside"] = list(phases_stopped_inside)
|
|
194
|
+
if rules_not_run:
|
|
195
|
+
# The families it did not reach are named, so the answer states a gap
|
|
196
|
+
# rather than an absence of findings.
|
|
197
|
+
out["rules_not_run"] = list(rules_not_run)
|
|
198
|
+
out["how_to_read"] += (
|
|
199
|
+
" The rule pass was stopped between families: "
|
|
200
|
+
f"{', '.join(rules_not_run)} did not run, so every count here "
|
|
201
|
+
"is a floor over the families that did."
|
|
202
|
+
)
|
|
143
203
|
return out
|
|
144
204
|
|
|
145
205
|
# -- checkpoints -------------------------------------------------------
|
|
@@ -210,6 +270,7 @@ def budget_warning(
|
|
|
210
270
|
*,
|
|
211
271
|
anchors: Optional[str] = None,
|
|
212
272
|
scope: Optional[str] = None,
|
|
273
|
+
source: Optional[str] = None,
|
|
213
274
|
) -> str:
|
|
214
275
|
"""What the operator is told when the budget is under the class floor.
|
|
215
276
|
|
|
@@ -238,6 +299,10 @@ def budget_warning(
|
|
|
238
299
|
f"[ask] budget: {command} is a {cls} analysis and the configured budget "
|
|
239
300
|
f"is {configured:g}s."
|
|
240
301
|
]
|
|
302
|
+
if source:
|
|
303
|
+
# C3-83: name the origin, because a variable inherited from a shell or a
|
|
304
|
+
# runner is the one setting nobody in the conversation remembers choosing.
|
|
305
|
+
lines.append(f" Budget read from {source}.")
|
|
241
306
|
if anchors:
|
|
242
307
|
lines.append(f" Measured for this command: {anchors}.")
|
|
243
308
|
else:
|
|
@@ -259,3 +324,24 @@ def budget_warning(
|
|
|
259
324
|
"the ones that did not. It is a floor, not a verdict."
|
|
260
325
|
)
|
|
261
326
|
return "\n".join(lines) + "\n"
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def budget_provenance(command: str, configured: float, source: str) -> str:
|
|
330
|
+
"""One line naming where a budget over the class floor came from (C3-83).
|
|
331
|
+
|
|
332
|
+
Above the floor there is no advice to give and there is still a fact worth
|
|
333
|
+
stating. The field measured what its absence costs: the same build answers
|
|
334
|
+
**395 findings / 98 defects** without a budget and **267 / 11** with
|
|
335
|
+
`ASK_MAX_ANALYSIS_SECONDS=420`, and everything about the truncation is
|
|
336
|
+
declared in the payload — `partial`, `counts_are_floor`, a capped confidence
|
|
337
|
+
— while the *reason* the run was cut lives in an environment variable the
|
|
338
|
+
reader of that payload never saw. So it is named where the run happens, once,
|
|
339
|
+
on stderr, next to the command it is about.
|
|
340
|
+
"""
|
|
341
|
+
return (
|
|
342
|
+
f"[ask] budget: {command} will stop after {configured:g}s. Budget read "
|
|
343
|
+
f"from {source}.\n"
|
|
344
|
+
" A run that hits it publishes a FLOOR: `summary.partial`, "
|
|
345
|
+
"`summary.counts_are_floor` and `_partial` say so. Unset the variable "
|
|
346
|
+
"for a complete audit.\n"
|
|
347
|
+
)
|
sourcecode/progress.py
CHANGED
|
@@ -178,6 +178,10 @@ class Progress:
|
|
|
178
178
|
self._unit = "files"
|
|
179
179
|
self._stage = ""
|
|
180
180
|
self._stage_t0 = time.monotonic()
|
|
181
|
+
#: When the counted state last *advanced* (C3-86). Distinct from
|
|
182
|
+
#: `_stage_t0`, which is when the stage began: a stage can begin, count to
|
|
183
|
+
#: 3 340 of 3 342, and then stand still while an uncounted walk runs.
|
|
184
|
+
self._count_t0 = time.monotonic()
|
|
181
185
|
#: When a heartbeat line was last written, by either emitter (C3-80). One
|
|
182
186
|
#: stamp is what keeps the loop and the in-band beat from double-printing.
|
|
183
187
|
self._last_emit = 0.0
|
|
@@ -227,6 +231,7 @@ class Progress:
|
|
|
227
231
|
self._total = 0
|
|
228
232
|
self._stage = ""
|
|
229
233
|
self._estimate = True
|
|
234
|
+
self._count_t0 = time.monotonic()
|
|
230
235
|
# C3-80: a new phase is the most informative thing this line can say, and
|
|
231
236
|
# the phase that follows it may be the long one.
|
|
232
237
|
self._beat()
|
|
@@ -262,6 +267,11 @@ class Progress:
|
|
|
262
267
|
rule `_eta_seconds` already applies to a stage that has barely started.
|
|
263
268
|
"""
|
|
264
269
|
with self._lock:
|
|
270
|
+
# C3-86: read before anything is mutated — the clock the *staleness* of
|
|
271
|
+
# a line is measured against only restarts when the state it reports
|
|
272
|
+
# actually changed.
|
|
273
|
+
if (done, total, stage) != (self._done, self._total, self._stage):
|
|
274
|
+
self._count_t0 = time.monotonic()
|
|
265
275
|
if stage != self._stage or done < self._done:
|
|
266
276
|
self._stage = stage
|
|
267
277
|
self._stage_t0 = time.monotonic()
|
|
@@ -407,6 +417,19 @@ class Progress:
|
|
|
407
417
|
self._done, self._total, self._unit, self._stage
|
|
408
418
|
)
|
|
409
419
|
eta = self._eta_seconds(done, total)
|
|
420
|
+
# C3-86: how long this count has stood still. The field read
|
|
421
|
+
# `3340/3342 java files eta=0.5s` **fourteen minutes** after the
|
|
422
|
+
# counter last advanced, and both halves of that line are claims the
|
|
423
|
+
# run cannot support: it asserts the stage is 99,9 % done *now*, and
|
|
424
|
+
# the rate behind the ETA is computed over a window in which nothing
|
|
425
|
+
# happened — so the longer the stall lasts, the more imminent the
|
|
426
|
+
# finish looks. A count that has not moved for a whole interval says
|
|
427
|
+
# so and withholds the estimate, which is the rule `_eta_seconds`
|
|
428
|
+
# already applies to a stage too young to extrapolate from.
|
|
429
|
+
stalled = time.monotonic() - self._count_t0
|
|
430
|
+
stale = total > 0 and stalled >= self._interval
|
|
431
|
+
if stale:
|
|
432
|
+
eta = None
|
|
410
433
|
counted = f"{done}/{total} {unit}" if total > 0 else ""
|
|
411
434
|
if self._line_mode:
|
|
412
435
|
line = f"[ask] progress phase={phase}"
|
|
@@ -415,6 +438,8 @@ class Progress:
|
|
|
415
438
|
if counted:
|
|
416
439
|
line += f" {counted}"
|
|
417
440
|
line += f" elapsed={_duration(elapsed)}"
|
|
441
|
+
if stale:
|
|
442
|
+
line += f" unchanged_for={_duration(stalled)}"
|
|
418
443
|
if eta is not None:
|
|
419
444
|
# Named, not implied: the estimate covers the counted stage.
|
|
420
445
|
# Nothing here knows the cost of the stages after it, and a
|
|
@@ -426,6 +451,8 @@ class Progress:
|
|
|
426
451
|
if counted:
|
|
427
452
|
shown += f" {counted}"
|
|
428
453
|
tail = f"{elapsed:.1f}s"
|
|
454
|
+
if stale:
|
|
455
|
+
tail += f", unchanged for {_duration(stalled)}"
|
|
429
456
|
if eta is not None:
|
|
430
457
|
tail += f", eta {_duration(eta)}"
|
|
431
458
|
line = f"\r\033[K{frame} {shown} ({tail})"
|
sourcecode/release_info.py
CHANGED
|
@@ -27,7 +27,7 @@ from typing import Any, Optional
|
|
|
27
27
|
#: The date `__version__` was released, from that version's CHANGELOG entry.
|
|
28
28
|
#: `tests/test_release_info.py` fails the build if the two disagree, so this is
|
|
29
29
|
#: a copy of a fact rather than a second authority for it.
|
|
30
|
-
RELEASE_DATE = "2026-08-
|
|
30
|
+
RELEASE_DATE = "2026-08-10"
|
|
31
31
|
|
|
32
32
|
#: Releases per day, measured over the 30 releases before this one (the same
|
|
33
33
|
#: computation the test re-runs against the CHANGELOG). Stable across window
|
sourcecode/repository_ir.py
CHANGED
|
@@ -5538,6 +5538,14 @@ def build_repo_ir(
|
|
|
5538
5538
|
)
|
|
5539
5539
|
)
|
|
5540
5540
|
|
|
5541
|
+
# C3-85: from here to the end of the build there is no per-file loop, so the
|
|
5542
|
+
# heartbeat kept `linking n/n` on screen — a counter that finished, reading as
|
|
5543
|
+
# a stage still running. Measured on keycloak (5 486 files, warm), the stretch
|
|
5544
|
+
# between the last `linking` tick and the first rule tick was **8,2 s of a 24 s
|
|
5545
|
+
# run**. These stages have no population to count; they are named, which is
|
|
5546
|
+
# what C3-56 does rather than invent a denominator.
|
|
5547
|
+
if progress is not None:
|
|
5548
|
+
progress(0, 0, "assembling the IR")
|
|
5541
5549
|
spring_summary = _build_spring_summary(all_symbols)
|
|
5542
5550
|
|
|
5543
5551
|
# Deduplicate relations
|
|
@@ -5570,6 +5578,8 @@ def build_repo_ir(
|
|
|
5570
5578
|
# when `since` is set). Done here, where `root` is available; placed before the
|
|
5571
5579
|
# XML-security retag so spec routes lacking security get the same treatment.
|
|
5572
5580
|
if route_diffs_arg is None:
|
|
5581
|
+
if progress is not None:
|
|
5582
|
+
progress(0, 0, "recovering spec-declared routes")
|
|
5573
5583
|
_routed_fqns_ir = {
|
|
5574
5584
|
r.get("effective_class")
|
|
5575
5585
|
for r in (ir.get("route_surface") or [])
|
|
@@ -5597,6 +5607,8 @@ def build_repo_ir(
|
|
|
5597
5607
|
r'|xmlns:security="http://www\.springframework\.org/schema/security")',
|
|
5598
5608
|
re.IGNORECASE,
|
|
5599
5609
|
)
|
|
5610
|
+
if progress is not None:
|
|
5611
|
+
progress(0, 0, "scanning XML security configuration")
|
|
5600
5612
|
_xml_sec_detected = False
|
|
5601
5613
|
for _xml_glob in (
|
|
5602
5614
|
"*security*.xml", "*Security*.xml",
|
sourcecode/risk.py
CHANGED
|
@@ -788,6 +788,14 @@ class RiskComposer:
|
|
|
788
788
|
# caller owns — `(stage, unit) -> RulePassProgress | None` — so this module
|
|
789
789
|
# never learns what a spinner is and a run nobody watches pays nothing.
|
|
790
790
|
self._progress = progress
|
|
791
|
+
# C3-87: the observers are kept, not discarded. Each one holds what the
|
|
792
|
+
# operator's budget cut out of the pass it watched, and until this command
|
|
793
|
+
# was bound to the budget there was nothing to keep: `stop_when` was never
|
|
794
|
+
# passed, so `should_stop()` answered False for the life of the run and the
|
|
795
|
+
# deadline could only ever be observed on the boundary between two phases.
|
|
796
|
+
# The field measured what that costs on one machine under one 420 s
|
|
797
|
+
# budget — `spring-audit` 1,94× over, `risk` 6,8× over.
|
|
798
|
+
self._rule_watchers: "list[Any]" = []
|
|
791
799
|
if findings is None:
|
|
792
800
|
findings = list(run_tx_audit(
|
|
793
801
|
self.cir, root=self.root, model=self.model,
|
|
@@ -801,7 +809,21 @@ class RiskComposer:
|
|
|
801
809
|
)
|
|
802
810
|
self.findings = findings
|
|
803
811
|
|
|
812
|
+
# C3-85: everything from here to the composition is repository-wide work
|
|
813
|
+
# with no population of its own, and it reported nothing — so the line held
|
|
814
|
+
# the last rule family's count while it ran. Each walk is named as it
|
|
815
|
+
# starts, which is what the operator needs in order to tell a slow stage
|
|
816
|
+
# from a hung one, and which is also how the next round gets to say *which*
|
|
817
|
+
# of these walks the wall time is in.
|
|
818
|
+
_setup = self._rule_progress("composing risk factors", "walks")
|
|
819
|
+
|
|
820
|
+
def _named(label: str) -> None:
|
|
821
|
+
if _setup is not None:
|
|
822
|
+
_setup.step(label)
|
|
823
|
+
|
|
824
|
+
_named("inferring the security posture")
|
|
804
825
|
posture = infer_security_posture(self.cir, root=self.root)
|
|
826
|
+
_named("resolving the endpoint security surface")
|
|
805
827
|
self.surface = endpoint_security_surface(self.cir, root=self.root, posture=posture)
|
|
806
828
|
access = (
|
|
807
829
|
posture.to_dict().get("endpoints", []) if hasattr(posture, "to_dict") else []
|
|
@@ -815,8 +837,11 @@ class RiskComposer:
|
|
|
815
837
|
# F.4-1. One pass over the α argument-shape atoms and one over the validation
|
|
816
838
|
# surface: both are repository-wide facts, read once and joined by symbol and
|
|
817
839
|
# by route, never re-derived per row.
|
|
840
|
+
_named("reading concatenated-query owners")
|
|
818
841
|
self.concat_owners = _concatenated_query_owners(self.cir)
|
|
842
|
+
_named("reading HTTP-input query sinks")
|
|
819
843
|
self.http_input_query_sinks = _http_input_query_sinks(self.root, self.cir)
|
|
844
|
+
_named("indexing the validation surface")
|
|
820
845
|
self.constraint_index = _constraint_index(
|
|
821
846
|
self.root, self.cir, extract_java_endpoints(self.root)
|
|
822
847
|
)
|
|
@@ -843,6 +868,7 @@ class RiskComposer:
|
|
|
843
868
|
|
|
844
869
|
# One pass answers both: the access axis (C1-26) and how much of the
|
|
845
870
|
# request surface each chain configuration file decides (C1-25).
|
|
871
|
+
_named("resolving the conditional bean graph")
|
|
846
872
|
self.profile_access, self.chain_reach = access_resolution(
|
|
847
873
|
self.root,
|
|
848
874
|
self.profiles if self.profiles is not None else set(),
|
|
@@ -887,13 +913,58 @@ class RiskComposer:
|
|
|
887
913
|
return symbol, "method" if "#" in symbol else "class"
|
|
888
914
|
|
|
889
915
|
def _rule_progress(self, stage: str, unit: str = "rule families") -> "Any":
|
|
890
|
-
"""The observer for one counted stage, or None when nobody is watching.
|
|
916
|
+
"""The observer for one counted stage, or None when nobody is watching.
|
|
917
|
+
|
|
918
|
+
C3-87: "nobody is watching" now includes the budget. The factory returns an
|
|
919
|
+
observer whenever a heartbeat *or* a deadline exists, so a CI run with no
|
|
920
|
+
TTY — precisely where a budget matters — gets the same stop the terminal
|
|
921
|
+
does. The observer is registered here because this is the one place that
|
|
922
|
+
makes them, so `budget_gap()` cannot miss a pass.
|
|
923
|
+
"""
|
|
891
924
|
if self._progress is None:
|
|
892
925
|
return None
|
|
893
926
|
try:
|
|
894
|
-
|
|
927
|
+
watcher = self._progress(stage, unit)
|
|
895
928
|
except Exception:
|
|
896
929
|
return None
|
|
930
|
+
if watcher is not None:
|
|
931
|
+
self._rule_watchers.append((stage, watcher))
|
|
932
|
+
return watcher
|
|
933
|
+
|
|
934
|
+
def budget_gap(self) -> dict:
|
|
935
|
+
"""What the operator's budget cut out of the rule passes, or `{}`.
|
|
936
|
+
|
|
937
|
+
The two audits report through observers this class made, and each records
|
|
938
|
+
its own cut in the vocabulary `spring-audit` already publishes: families
|
|
939
|
+
that never started (`rules_not_run`) and a family stopped part-way through
|
|
940
|
+
one of its stages (`rules_partially_run`, with `stage_stopped_in`). Read
|
|
941
|
+
back here so the composition can publish the gap rather than a total that
|
|
942
|
+
silently shrank.
|
|
943
|
+
|
|
944
|
+
Only the *rule* passes are read. The composition observer counts defects,
|
|
945
|
+
not rule families, and folding its ids into a rule inventory would be the
|
|
946
|
+
two-populations-under-one-name defect this ledger already carries twice.
|
|
947
|
+
"""
|
|
948
|
+
not_run: "list[str]" = []
|
|
949
|
+
partially_run: "list[dict]" = []
|
|
950
|
+
stages: "list[str]" = []
|
|
951
|
+
for stage, watcher in self._rule_watchers:
|
|
952
|
+
if not stage.endswith("rules"):
|
|
953
|
+
continue
|
|
954
|
+
_cut = list(getattr(watcher, "not_run", []) or [])
|
|
955
|
+
_partial = list(getattr(watcher, "partially_run", []) or [])
|
|
956
|
+
if not _cut and not _partial:
|
|
957
|
+
continue
|
|
958
|
+
not_run.extend(_cut)
|
|
959
|
+
partially_run.extend(_partial)
|
|
960
|
+
stages.append(stage)
|
|
961
|
+
if not not_run and not partially_run:
|
|
962
|
+
return {}
|
|
963
|
+
return {
|
|
964
|
+
"rules_not_run": not_run,
|
|
965
|
+
"rules_partially_run": partially_run,
|
|
966
|
+
"stages_stopped_inside": stages,
|
|
967
|
+
}
|
|
897
968
|
|
|
898
969
|
def compose(
|
|
899
970
|
self,
|
|
@@ -1157,6 +1228,7 @@ def _assemble_payload(
|
|
|
1157
1228
|
limit: int,
|
|
1158
1229
|
min_band: str,
|
|
1159
1230
|
profiles: "Optional[set[str]]",
|
|
1231
|
+
budget_cut: "Optional[dict]" = None,
|
|
1160
1232
|
) -> dict:
|
|
1161
1233
|
"""The `risk` answer document for `rows`. One construction, two callers.
|
|
1162
1234
|
|
|
@@ -1199,6 +1271,21 @@ def _assemble_payload(
|
|
|
1199
1271
|
# that sentence, and it belongs where the composition is, not one command away.
|
|
1200
1272
|
"non_coverage": non_coverage.block("security_surface"),
|
|
1201
1273
|
}
|
|
1274
|
+
# C3-87 (and C2-31's rule, applied to the second consumer): the counts above
|
|
1275
|
+
# are arithmetic over what was composed, and on a cut run that is a truncated
|
|
1276
|
+
# population. The mark goes where the numbers are — a reader who never opens
|
|
1277
|
+
# `_partial` must not read `total_defects` as a census. A complete run gains
|
|
1278
|
+
# no fields and stays byte-identical.
|
|
1279
|
+
if budget_cut:
|
|
1280
|
+
payload["partial"] = True
|
|
1281
|
+
payload["counts_are_floor"] = True
|
|
1282
|
+
payload["counts_basis"] = (
|
|
1283
|
+
"This run was cut short by the analysis budget, so every count above "
|
|
1284
|
+
"is a FLOOR over what did run — the absence of a risk composed from a "
|
|
1285
|
+
"rule family that never ran is not evidence there is none, and a "
|
|
1286
|
+
"composition stopped part-way leaves defects unranked rather than "
|
|
1287
|
+
"unfound. `_partial` names what was not measured."
|
|
1288
|
+
)
|
|
1202
1289
|
if len(kept) > limit:
|
|
1203
1290
|
from sourcecode.degradation import cap_effect
|
|
1204
1291
|
|
|
@@ -1238,6 +1325,7 @@ def build_risk(
|
|
|
1238
1325
|
profiles: "Optional[set[str]]" = None,
|
|
1239
1326
|
progress: "Optional[Callable[..., Any]]" = None,
|
|
1240
1327
|
checkpoint: "Optional[Callable[[str, dict], None]]" = None,
|
|
1328
|
+
partial_status: "Optional[Callable[..., dict]]" = None,
|
|
1241
1329
|
) -> dict:
|
|
1242
1330
|
"""Compose this build's own audit with reach, access, write effect and shape.
|
|
1243
1331
|
|
|
@@ -1267,11 +1355,47 @@ def build_risk(
|
|
|
1267
1355
|
# C3-73: the join is the other half of this command's wall time, and it has a
|
|
1268
1356
|
# denominator of its own — one row per defect. Counted with the same seam as
|
|
1269
1357
|
# the rule passes, so there is one place that knows how to report counted work.
|
|
1358
|
+
# C3-87: what the budget cut out of the two rule passes, read back from the
|
|
1359
|
+
# observers this composition made. Empty on a run that was not cut, which is
|
|
1360
|
+
# every run without `ASK_MAX_ANALYSIS_SECONDS` set.
|
|
1361
|
+
_cut: dict = dict(composer.budget_gap())
|
|
1362
|
+
|
|
1270
1363
|
def _assemble(_rows: "list[Any]") -> dict:
|
|
1271
|
-
|
|
1364
|
+
payload = _assemble_payload(
|
|
1272
1365
|
_rows, findings=findings, composer=composer, root=root,
|
|
1273
1366
|
limit=limit, min_band=min_band, profiles=profiles,
|
|
1367
|
+
budget_cut=_cut or None,
|
|
1274
1368
|
)
|
|
1369
|
+
if _cut and partial_status is not None:
|
|
1370
|
+
# A phase stopped *inside* is neither completed nor pending: `audit`
|
|
1371
|
+
# when a rule family was cut, `compose` when the join was.
|
|
1372
|
+
_inside = [
|
|
1373
|
+
name
|
|
1374
|
+
for name, hit in (
|
|
1375
|
+
("audit", bool(_cut.get("rules_not_run"))),
|
|
1376
|
+
("compose", _cut.get("defects_composed") is not None),
|
|
1377
|
+
)
|
|
1378
|
+
if hit
|
|
1379
|
+
]
|
|
1380
|
+
payload["_partial"] = partial_status(
|
|
1381
|
+
rules_partially_run=_cut.get("rules_partially_run") or None,
|
|
1382
|
+
rules_not_run=_cut.get("rules_not_run") or None,
|
|
1383
|
+
phases_stopped_inside=_inside or None,
|
|
1384
|
+
)
|
|
1385
|
+
if _cut.get("defects_composed") is not None:
|
|
1386
|
+
# The join is this command's own population, and a run cut inside
|
|
1387
|
+
# it did rank some defects and not others. Neither `phases_completed`
|
|
1388
|
+
# nor `phases_pending` can say that, so it is said here.
|
|
1389
|
+
payload["_partial"]["composition_stopped_after"] = {
|
|
1390
|
+
"defects_composed": _cut["defects_composed"],
|
|
1391
|
+
"defects_total": _cut["defects_total"],
|
|
1392
|
+
"basis": (
|
|
1393
|
+
"The audit ran; the join that ranks its defects was cut. "
|
|
1394
|
+
"The defects not composed were measured and are counted in "
|
|
1395
|
+
"total_defects — they carry no band and are not in `risks`."
|
|
1396
|
+
),
|
|
1397
|
+
}
|
|
1398
|
+
return payload
|
|
1275
1399
|
|
|
1276
1400
|
if checkpoint is not None:
|
|
1277
1401
|
# The audit and the posture are paid for; the join has not started. A
|
|
@@ -1286,6 +1410,15 @@ def build_risk(
|
|
|
1286
1410
|
rows = []
|
|
1287
1411
|
for _index, defect in enumerate(_defects, start=1):
|
|
1288
1412
|
if _rows_progress is not None:
|
|
1413
|
+
# C3-87: the join is the other half of this command's wall time, so the
|
|
1414
|
+
# deadline is asked here too — on a whole composed row, which is this
|
|
1415
|
+
# loop's unit, and sampled by the same seam the rule passes use.
|
|
1416
|
+
if _rows_progress.tick(
|
|
1417
|
+
_index - 1, len(_defects), label="composing defects", unit="defects"
|
|
1418
|
+
):
|
|
1419
|
+
_cut["defects_composed"] = _index - 1
|
|
1420
|
+
_cut["defects_total"] = len(_defects)
|
|
1421
|
+
break
|
|
1289
1422
|
_rows_progress.rule(_index, len(_defects), str(defect["defect_id"]))
|
|
1290
1423
|
rows.append(composer.compose(
|
|
1291
1424
|
defect_id=str(defect["defect_id"]),
|
sourcecode/rule_pass.py
CHANGED
|
@@ -155,20 +155,37 @@ class RulePassProgress:
|
|
|
155
155
|
from the family's duration to a batch's, and the cost of the check is a
|
|
156
156
|
modulo per unit.
|
|
157
157
|
|
|
158
|
+
**C3-82.** The sampling constant belongs to the *clock* and not to the
|
|
159
|
+
*counter*, and until this the two shared it. The field measured what that
|
|
160
|
+
costs: `1856/3342 java files` at 10,0 s, then **13,6 minutes with no line
|
|
161
|
+
at all**, then `3328/3342` — because a report that lands between two batch
|
|
162
|
+
boundaries is never produced, and a stretch of expensive units (or one
|
|
163
|
+
pathological file) is invisible for as long as it lasts. So the count is
|
|
164
|
+
reported on **every** unit and the clock is still read once per batch: the
|
|
165
|
+
emitter throttles itself on an interval, so a per-unit report costs one
|
|
166
|
+
clock read and produces a line only when one is due, while a per-unit
|
|
167
|
+
budget check would buy no resolution the deadline can use.
|
|
168
|
+
|
|
158
169
|
Never raises: an unreadable clock is not evidence the budget is spent, and
|
|
159
170
|
a reporting fault is not a reason to stop analysing.
|
|
160
171
|
"""
|
|
161
|
-
if done % self.sample_every and done != total:
|
|
162
|
-
return False
|
|
163
172
|
if self.detail_sink is not None:
|
|
164
173
|
try:
|
|
165
174
|
self.detail_sink(done, total, label, unit)
|
|
166
175
|
except Exception:
|
|
167
176
|
pass
|
|
177
|
+
if done % self.sample_every and done != total:
|
|
178
|
+
return False
|
|
168
179
|
return self.should_stop()
|
|
169
180
|
|
|
170
181
|
def record_partial(
|
|
171
|
-
self,
|
|
182
|
+
self,
|
|
183
|
+
rule_ids: "list[str]",
|
|
184
|
+
done: int,
|
|
185
|
+
total: int,
|
|
186
|
+
*,
|
|
187
|
+
unit: str = "files",
|
|
188
|
+
stage: str = "",
|
|
172
189
|
) -> None:
|
|
173
190
|
"""Record a family the budget cut *in flight* (C3-79).
|
|
174
191
|
|
|
@@ -176,12 +193,29 @@ class RulePassProgress:
|
|
|
176
193
|
calling it not-run discards findings that were measured and are published.
|
|
177
194
|
It gets its own state, with the count it reached, because *how far* is the
|
|
178
195
|
thing a reader needs in order to decide whether to re-run with more time.
|
|
196
|
+
|
|
197
|
+
**C3-81.** The count is the *stage's*, not the family's, and until this
|
|
198
|
+
seam said so the entry read as its own contradiction. The field measured
|
|
199
|
+
it: `units_done: 3342, units_total: 3342` in a list meaning *"this family
|
|
200
|
+
did not finish"*, on a family that walks four populations in sequence
|
|
201
|
+
(Java sources, then configuration files, then descriptors, then mappers)
|
|
202
|
+
and was cut at the end of the first. Six `DEAD-001` findings really were
|
|
203
|
+
missing, and nothing in the entry could say where from. So the stage is
|
|
204
|
+
named, and `units_basis` states what the two numbers count — which is the
|
|
205
|
+
difference between a reader concluding *"it finished"* and a reader
|
|
206
|
+
concluding *"three populations after this one never started"*.
|
|
179
207
|
"""
|
|
180
208
|
self.partially_run.append({
|
|
181
209
|
"rule_ids": list(rule_ids),
|
|
182
210
|
"units_done": done,
|
|
183
211
|
"units_total": total,
|
|
184
212
|
"unit": unit,
|
|
213
|
+
"stage_stopped_in": stage or unit,
|
|
214
|
+
"units_basis": (
|
|
215
|
+
"units_done/units_total count the stage named in "
|
|
216
|
+
"stage_stopped_in, not the whole family: a family cut at the end "
|
|
217
|
+
"of one stage did not run the stages after it."
|
|
218
|
+
),
|
|
185
219
|
})
|
|
186
220
|
|
|
187
221
|
def partial_rule_ids(self) -> "list[str]":
|
|
@@ -223,14 +257,33 @@ class FamilyBudget:
|
|
|
223
257
|
label: str
|
|
224
258
|
stopped: bool = False
|
|
225
259
|
|
|
226
|
-
def
|
|
227
|
-
"""
|
|
260
|
+
def stage(self, label: str) -> None:
|
|
261
|
+
"""Name a stretch of this family that has no population to count (C3-85).
|
|
262
|
+
|
|
263
|
+
A family walks several populations in sequence and does uncounted work
|
|
264
|
+
between them — finding the files the next stage will read, for one. That
|
|
265
|
+
work reports nothing, so the line keeps the previous stage's `n/n` on
|
|
266
|
+
screen, which reads as a stage still running rather than one that ended.
|
|
267
|
+
Named, not counted: inventing a denominator for it is the fabrication
|
|
268
|
+
C3-56 refused.
|
|
269
|
+
"""
|
|
270
|
+
self.progress.step(f"{self.label}: {label}")
|
|
271
|
+
|
|
272
|
+
def tick(
|
|
273
|
+
self, done: int, total: int, *, unit: str = "files", stage: str = ""
|
|
274
|
+
) -> bool:
|
|
275
|
+
"""True when this family must stop. Idempotent once it has said so.
|
|
276
|
+
|
|
277
|
+
`done` is what the loop has **finished**, so a stage in flight can never
|
|
278
|
+
report its own population as complete (C3-81); `stage` names the walk the
|
|
279
|
+
count belongs to, because a family may walk several in sequence.
|
|
280
|
+
"""
|
|
228
281
|
if self.stopped:
|
|
229
282
|
return True
|
|
230
283
|
if self.progress.tick(done, total, label=self.label, unit=unit):
|
|
231
284
|
self.stopped = True
|
|
232
285
|
self.progress.record_partial(
|
|
233
|
-
list(self.rule_ids), done, total, unit=unit
|
|
286
|
+
list(self.rule_ids), done, total, unit=unit, stage=stage
|
|
234
287
|
)
|
|
235
288
|
return True
|
|
236
289
|
return False
|
|
@@ -612,8 +612,13 @@ def scan_security_configuration(
|
|
|
612
612
|
|
|
613
613
|
out: "list[SecurityConfigObservation]" = []
|
|
614
614
|
_java_total = len(java_files)
|
|
615
|
-
for _seen, rel in enumerate(java_files
|
|
616
|
-
|
|
615
|
+
for _seen, rel in enumerate(java_files):
|
|
616
|
+
# C3-81: `_seen` is what this walk has *finished*, so the stage cannot
|
|
617
|
+
# report a complete population while it is still being cut, and the stage
|
|
618
|
+
# is named because three more populations follow this one.
|
|
619
|
+
if budget is not None and budget.tick(
|
|
620
|
+
_seen, _java_total, unit="java files", stage="scanning java sources"
|
|
621
|
+
):
|
|
617
622
|
return sorted(
|
|
618
623
|
_shared_across_environments(out),
|
|
619
624
|
key=lambda o: (o.rule, o.file, o.line),
|
|
@@ -633,6 +638,11 @@ def scan_security_configuration(
|
|
|
633
638
|
# surface and Java discovery cannot disagree about what is source.
|
|
634
639
|
from sourcecode.repository_ir import walk_source_tree
|
|
635
640
|
|
|
641
|
+
# C3-85: one walk of the whole source tree, between the Java stage that just
|
|
642
|
+
# ended and the configuration stage that has not started. It reported nothing,
|
|
643
|
+
# so the line held `3342/3342 java files` while it ran.
|
|
644
|
+
if budget is not None:
|
|
645
|
+
budget.stage("finding configuration files, descriptors and mappers")
|
|
636
646
|
_walked_config: "list[str]" = []
|
|
637
647
|
descriptors: "list[str]" = []
|
|
638
648
|
mappers: "list[str]" = []
|
|
@@ -656,9 +666,10 @@ def scan_security_configuration(
|
|
|
656
666
|
if config_files is None:
|
|
657
667
|
config_files = _walked_config
|
|
658
668
|
_cfg = sorted(set(config_files))
|
|
659
|
-
for _seen, rel in enumerate(_cfg
|
|
669
|
+
for _seen, rel in enumerate(_cfg):
|
|
660
670
|
if budget is not None and budget.tick(
|
|
661
|
-
_seen, len(_cfg), unit="configuration files"
|
|
671
|
+
_seen, len(_cfg), unit="configuration files",
|
|
672
|
+
stage="scanning configuration files",
|
|
662
673
|
):
|
|
663
674
|
break
|
|
664
675
|
try:
|
|
@@ -673,9 +684,10 @@ def scan_security_configuration(
|
|
|
673
684
|
# deployment descriptors, and a per-environment overlay tree is covered
|
|
674
685
|
# because the walk finds the file wherever the build put it.
|
|
675
686
|
_desc = sorted(set(descriptors))
|
|
676
|
-
for _seen, rel in enumerate(_desc
|
|
687
|
+
for _seen, rel in enumerate(_desc):
|
|
677
688
|
if budget is not None and budget.tick(
|
|
678
|
-
_seen, len(_desc), unit="deployment descriptors"
|
|
689
|
+
_seen, len(_desc), unit="deployment descriptors",
|
|
690
|
+
stage="scanning deployment descriptors",
|
|
679
691
|
):
|
|
680
692
|
break
|
|
681
693
|
try:
|
|
@@ -686,9 +698,9 @@ def scan_security_configuration(
|
|
|
686
698
|
out.extend(_scan_descriptor(blank_xml_comments(text), rel))
|
|
687
699
|
|
|
688
700
|
_maps = sorted(set(mappers) - set(descriptors))
|
|
689
|
-
for _seen, rel in enumerate(_maps
|
|
701
|
+
for _seen, rel in enumerate(_maps):
|
|
690
702
|
if budget is not None and budget.tick(
|
|
691
|
-
_seen, len(_maps), unit="mapper files"
|
|
703
|
+
_seen, len(_maps), unit="mapper files", stage="scanning mapper files",
|
|
692
704
|
):
|
|
693
705
|
break
|
|
694
706
|
try:
|
|
@@ -110,10 +110,13 @@ class _SEC001UnsecuredEndpoint:
|
|
|
110
110
|
findings: list[SpringFinding] = []
|
|
111
111
|
|
|
112
112
|
_total = len(cir.endpoints)
|
|
113
|
-
for _seen, ep in enumerate(cir.endpoints
|
|
113
|
+
for _seen, ep in enumerate(cir.endpoints):
|
|
114
114
|
# C3-79: the population is the endpoints, so that is what the deadline
|
|
115
115
|
# is observed on. What was measured before the cut is returned.
|
|
116
|
-
|
|
116
|
+
# C3-81: `_seen` counts what is finished, and the walk is named.
|
|
117
|
+
if budget is not None and budget.tick(
|
|
118
|
+
_seen, _total, unit="endpoints", stage="walking endpoints"
|
|
119
|
+
):
|
|
117
120
|
break
|
|
118
121
|
if ep.security is not None and ep.security.policy != "none_detected":
|
|
119
122
|
continue
|
|
@@ -635,12 +638,13 @@ class _GATE00XCustomGateAudit:
|
|
|
635
638
|
sources = _read_java_sources(Path(root), cir)
|
|
636
639
|
findings: list[SpringFinding] = []
|
|
637
640
|
_token_total = len(tokens)
|
|
638
|
-
for _token_seen, token in enumerate(tokens
|
|
641
|
+
for _token_seen, token in enumerate(tokens):
|
|
639
642
|
# C3-79: the gate tokens are this family's population. Each one costs
|
|
640
643
|
# three passes over the admissible sources, so the boundary between two
|
|
641
644
|
# tokens is where a deadline can be honoured without a half-built pass.
|
|
642
645
|
if budget is not None and budget.tick(
|
|
643
|
-
_token_seen, _token_total, unit="gate annotations"
|
|
646
|
+
_token_seen, _token_total, unit="gate annotations",
|
|
647
|
+
stage="resolving gate annotations",
|
|
644
648
|
):
|
|
645
649
|
break
|
|
646
650
|
# C3-61, second measurement. The `token not in source` guard removed
|
|
@@ -992,6 +996,11 @@ class SecurityScanner:
|
|
|
992
996
|
self.rules_run.extend(rule_ids_of(pattern))
|
|
993
997
|
if progress is not None:
|
|
994
998
|
progress.rule(_index, _total, pattern.pattern_id, done=True)
|
|
999
|
+
# C3-85: the tail of the pass. Two walks over every finding the families
|
|
1000
|
+
# produced, after the last family reported and before the next named stage
|
|
1001
|
+
# — inside the field's silence, like the two stages C3-73 named before it.
|
|
1002
|
+
if progress is not None:
|
|
1003
|
+
progress.step("deduplicating and ordering findings")
|
|
995
1004
|
deduped = deduplicate_findings(all_findings)
|
|
996
1005
|
return sorted(deduped, key=lambda f: (SEVERITY_ORDER.get(f.severity, 9), f.symbol))
|
|
997
1006
|
|
|
@@ -1098,8 +1107,9 @@ def run_security_audit(
|
|
|
1098
1107
|
_rules_partial = list(getattr(progress, "partially_run", []) or [])
|
|
1099
1108
|
if _rules_partial:
|
|
1100
1109
|
_cut = ", ".join(
|
|
1101
|
-
f"{'/'.join(entry['rule_ids'])}
|
|
1102
|
-
f"{entry['
|
|
1110
|
+
f"{'/'.join(entry['rule_ids'])} while {entry['stage_stopped_in']}, "
|
|
1111
|
+
f"after {entry['units_done']} of {entry['units_total']} "
|
|
1112
|
+
f"{entry['unit']}"
|
|
1103
1113
|
for entry in _rules_partial
|
|
1104
1114
|
)
|
|
1105
1115
|
_sec_limitations.append(
|
sourcecode/spring_tx_analyzer.py
CHANGED
|
@@ -189,10 +189,12 @@ class _TX001ProxyBypass:
|
|
|
189
189
|
_seen_ids: set[str] = set()
|
|
190
190
|
_declared = tx_index.all_declared
|
|
191
191
|
_total = len(_declared)
|
|
192
|
-
for _seen, boundary in enumerate(_declared
|
|
192
|
+
for _seen, boundary in enumerate(_declared):
|
|
193
193
|
# C3-79: the declared boundaries are this family's population.
|
|
194
|
+
# C3-81: counted as finished, and the walk is named.
|
|
194
195
|
if budget is not None and budget.tick(
|
|
195
|
-
_seen, _total, unit="declared boundaries"
|
|
196
|
+
_seen, _total, unit="declared boundaries",
|
|
197
|
+
stage="walking declared boundaries",
|
|
196
198
|
):
|
|
197
199
|
break
|
|
198
200
|
if boundary.scope != "method":
|
|
@@ -793,9 +795,12 @@ class _TX006SelfInvocation:
|
|
|
793
795
|
_seen_ids: set[str] = set()
|
|
794
796
|
|
|
795
797
|
_total = len(body_facts)
|
|
796
|
-
for _seen, (caller_fqn, atoms) in enumerate(body_facts.items()
|
|
798
|
+
for _seen, (caller_fqn, atoms) in enumerate(body_facts.items()):
|
|
797
799
|
# C3-79: one method body per unit — the population this walk iterates.
|
|
798
|
-
|
|
800
|
+
# C3-81: counted as finished, and the walk is named.
|
|
801
|
+
if budget is not None and budget.tick(
|
|
802
|
+
_seen, _total, unit="method bodies", stage="walking method bodies"
|
|
803
|
+
):
|
|
799
804
|
break
|
|
800
805
|
if not isinstance(atoms, list):
|
|
801
806
|
continue
|
|
@@ -1046,8 +1051,9 @@ def run_tx_audit(
|
|
|
1046
1051
|
_rules_partial = list(getattr(progress, "partially_run", []) or [])
|
|
1047
1052
|
if _rules_partial:
|
|
1048
1053
|
_cut = ", ".join(
|
|
1049
|
-
f"{'/'.join(entry['rule_ids'])}
|
|
1050
|
-
f"{entry['
|
|
1054
|
+
f"{'/'.join(entry['rule_ids'])} while {entry['stage_stopped_in']}, "
|
|
1055
|
+
f"after {entry['units_done']} of {entry['units_total']} "
|
|
1056
|
+
f"{entry['unit']}"
|
|
1051
1057
|
for entry in _rules_partial
|
|
1052
1058
|
)
|
|
1053
1059
|
_tx_limitations.append(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.16.0
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -76,13 +76,31 @@ With the right command class, ASK Engine becomes **constant infrastructure** ins
|
|
|
76
76
|
**Large-repo controls.** Pass `--progress stderr` (or `--progress file:run.log`)
|
|
77
77
|
for line heartbeats in a non-TTY supervised run — with the file being read so
|
|
78
78
|
far and an ETA for the stage being measured. `ASK_PROGRESS` sets the same thing
|
|
79
|
-
in the environment
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
in the environment, and the heartbeat is emitted by the analysis itself, not only
|
|
80
|
+
by a timer thread, so a CPU-bound stretch is never silent. `--jobs N` / `-j`
|
|
81
|
+
(env `ASK_JOBS`) sets the worker processes that parse files — default CPU count
|
|
82
|
+
− 1; parallelism only warms the content-addressed parse cache, so the answer is
|
|
83
|
+
byte-identical at any value. It does **not** parallelise the rule pass, which is
|
|
84
|
+
where a repository-wide audit spends most of its time, and a warm cache leaves it
|
|
85
|
+
nothing to parse — so raising it there buys nothing. Long repo-wide/deep runs also write durable records
|
|
86
|
+
under `~/.sourcecode/runs/<repo-hash>/` by default, not inside the audited
|
|
87
|
+
repository; set `ASK_RUNS_IN_REPO=1` only when repo-local `.ask/runs` records are
|
|
88
|
+
intentional. Use `ask audit-report --from-risk risk.json` to package an existing
|
|
89
|
+
risk run instead of recomputing it.
|
|
90
|
+
|
|
91
|
+
**A budget is honoured, never a veto.** `ASK_MAX_ANALYSIS_SECONDS=<seconds>`
|
|
92
|
+
bounds a repo-wide/deep command. A value below the class floor prints what that
|
|
93
|
+
class typically needs and **runs anyway** — refusing to answer is the opposite of
|
|
94
|
+
this product's doctrine. A run that spends its budget stops on the loop that is
|
|
95
|
+
spending the time, not only between phases: a rule family cut in flight is
|
|
96
|
+
published as `rules_partially_run` with how far it reached, families that never
|
|
97
|
+
started are named in `rules_not_run`, `_partial` names the phases that completed
|
|
98
|
+
and the ones that did not, and the findings measured before the cut are kept.
|
|
99
|
+
The answer marks itself where the numbers are — `summary.partial`,
|
|
100
|
+
`counts_are_floor`, `counts_basis` — and `confidence_level` is capped at `low`,
|
|
101
|
+
because the confidence of the findings that exist says nothing about the family
|
|
102
|
+
that did not run. Measured on BroadleafCommerce (2 766 Java files): a 4.5s budget
|
|
103
|
+
stops at 1 408 files and ends at 5.1s.
|
|
86
104
|
|
|
87
105
|
**What a warm actually covers.** `ask cache warm` runs the compact analysis: it rebuilds the
|
|
88
106
|
shared structural layers (L1/L2 + the Repository Intelligence Snapshot + the shared Canonical
|
|
@@ -108,7 +126,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
108
126
|
# pip / pipx
|
|
109
127
|
pipx install sourcecode # or: pip install sourcecode
|
|
110
128
|
|
|
111
|
-
ask version # ask 4.
|
|
129
|
+
ask version # ask 4.16.0 — and, on a build that has aged,
|
|
112
130
|
# how many releases have probably shipped since
|
|
113
131
|
```
|
|
114
132
|
|
|
@@ -204,6 +222,9 @@ ask verify /path/to/repo # then: only new violations block
|
|
|
204
222
|
> A repository that starts declaring contracts already violates them somewhere; `ask verify`
|
|
205
223
|
> is **baseline-relative by default** (`--fail-on new`) so the gate survives contact with
|
|
206
224
|
> reality instead of being switched off on day one.
|
|
225
|
+
> A pipeline wired before the contracts exist exits 2 (`unverified`, with
|
|
226
|
+
> `unverified_reason` naming which state it is); `--allow-unverified` exits 0 while the
|
|
227
|
+
> verdict stays `unverified` — the declared form of `|| true`, and violations still block.
|
|
207
228
|
> `ask baseline capture|diff|trend` is a different thing: versioned architectural metrics over
|
|
208
229
|
> time, for trend reporting rather than blocking.
|
|
209
230
|
|
|
@@ -324,7 +345,7 @@ from it.
|
|
|
324
345
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
325
346
|
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
326
347
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
327
|
-
| `spring-audit` | core | transactional anomalies + security configuration/exposure + validation gaps | `--ci`, `-f github-comment`; `--table --rule SEC-008 --top-n 20` for human-scale review |
|
|
348
|
+
| `spring-audit` | core | transactional anomalies + security configuration/exposure + validation gaps | `--ci`, `-f github-comment`; `--table --rule SEC-008 --top-n 20` for human-scale review. Every run inventories its own rules — `rules_run`, `rules_not_run`, `rules_partially_run`, `rules_run_count`/`rules_total` — so "all of them" is a fact, not an assumption |
|
|
328
349
|
| `migrate-check` | core | Boot 2→3 readiness + Java LTS/licensing inventory: located blockers, per-dimension score, effort, detected Java 8/11/17/21/25 evidence and explicit licensing-review signals | `--target-jdk 25` sets the target LTS route; `--blast-radius` orders the re-test plan; `--table --rule MIG-001 --band critical --top-n 20` for blocker triage |
|
|
329
350
|
| `impact` / `impact-chain` | core | blast radius of a change, to the endpoints it reaches | target the **interface**, not the `Impl` |
|
|
330
351
|
| `pr-impact` | core | the same, scoped to a PR diff | gating command: `--fail-on`, exit codes |
|
|
@@ -499,6 +520,25 @@ range, and errors: **no source code, paths, secrets, or output**. Turn it off ag
|
|
|
499
520
|
> default you must remember to disable is the wrong default for third-party code. An explicit
|
|
500
521
|
> choice you made before is unchanged.
|
|
501
522
|
|
|
523
|
+
**A question is not a write — `--no-write` / `ASK_READONLY=1`.** One contract over
|
|
524
|
+
every command: nothing is created inside the analysed repository, and a command
|
|
525
|
+
that cannot answer without writing says so instead of writing. A refusal is never
|
|
526
|
+
silent — each one names the path that was not created, on stderr. The scope is the
|
|
527
|
+
tree you pointed at; the shared caches under `~/.sourcecode` and an `--output` path
|
|
528
|
+
elsewhere on the machine are not refused, because refusing them would make the mode
|
|
529
|
+
unusable without making it safer.
|
|
530
|
+
|
|
531
|
+
Most commands read only, and the few that create anything inside the repository do
|
|
532
|
+
it under `.ask/` and only behind an explicit flag (`baseline`, `migrate-check
|
|
533
|
+
--history`, `verify --update-baseline`, `verify-edit --install-hook`, and `.ask/runs`
|
|
534
|
+
when `ASK_RUNS_IN_REPO=1`). `ask --help` prints that list in full, generated from the
|
|
535
|
+
one authority the battery checks — a write seam added without a row there fails the
|
|
536
|
+
suite.
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
ask spring-audit /path/to/client-repo --no-write -o audit.json
|
|
540
|
+
```
|
|
541
|
+
|
|
502
542
|
**Custom security annotations.** Teach `endpoints`, `spring-audit`, and `explain` about
|
|
503
543
|
project-specific authorization annotations via an optional `sourcecode.config.json` at the
|
|
504
544
|
repo root (otherwise they report `policy: "none_detected"`):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
sourcecode/__init__.py,sha256=
|
|
1
|
+
sourcecode/__init__.py,sha256=5rp02UOw1nxBYqNTQnjILrQlHGCnAIXmjbWqy0Ci4T4,309
|
|
2
2
|
sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
|
|
3
3
|
sourcecode/archetype.py,sha256=CZvRLpkHot_D8D3JFQVorr-EHDJyh0BbS7RnSTqBigM,40499
|
|
4
4
|
sourcecode/architectural_baseline.py,sha256=4GiMVBLJVHvRKSWQGf2ZVurI1-R0qk5pmaevgvGSgVA,26325
|
|
@@ -18,7 +18,7 @@ sourcecode/chain_rules.py,sha256=Bi6UHfgd-GxWswmnHRcPz5jdbAuqka3Zkz_P-MTvqhw,127
|
|
|
18
18
|
sourcecode/change_plan.py,sha256=aX1mp2XnlDN-8R2BcTiu8HlkDwQ_4fN1ZLckj0VMXHM,9945
|
|
19
19
|
sourcecode/cir_graphs.py,sha256=9G0HHj1kw2325IDyzo2OpX73BNswEckecf4MZUXB4JM,12078
|
|
20
20
|
sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
|
|
21
|
-
sourcecode/cli.py,sha256=
|
|
21
|
+
sourcecode/cli.py,sha256=TvA4KIhUnm4Pr25B1ISCRSD0OzFdxD9wdP7x9TmKLig,597596
|
|
22
22
|
sourcecode/client_calls.py,sha256=daRTgbXNUOfkzGXJpVb6A737R_Thhka8vw1_YgxCaLg,13548
|
|
23
23
|
sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
|
|
24
24
|
sourcecode/compare.py,sha256=2GdDy0qkDSgmkkpgvoCHitoLj3mt30EeFgJ8PGEXJng,14712
|
|
@@ -81,13 +81,13 @@ sourcecode/parse_cache.py,sha256=fhXnoaOgVWwp_8q0ngJ76UZ09qJ6uxDd0jaX8jdezvo,208
|
|
|
81
81
|
sourcecode/path_admission.py,sha256=OGNSoluhVh8YYOBZBnACUrkmH4Tp_yxjGLifkcsxQpo,5838
|
|
82
82
|
sourcecode/path_filters.py,sha256=LmTYq735orssKTIuxTX1mE1FHAXF7dVEJEse35xE1RA,12371
|
|
83
83
|
sourcecode/perf.py,sha256=vZDEj1wS905peXmREdOniZrWCikElOyPTw-E32zVZAQ,25847
|
|
84
|
-
sourcecode/phased_run.py,sha256
|
|
84
|
+
sourcecode/phased_run.py,sha256=zJGOLAuMAzjr71NQAEkXclXkgzTYpBsF4KP5EV2Itdk,15545
|
|
85
85
|
sourcecode/pipe_contract.py,sha256=PML0Er5d8uDyec4OrdUXuyqHbGPUYndjeaKUjkFz2u4,8369
|
|
86
86
|
sourcecode/posture.py,sha256=cZ3By8LR-1bHfzyVpCDo2HHSAsCvePOmUc37E9GXYDw,76206
|
|
87
87
|
sourcecode/pr_comment_renderer.py,sha256=239PmJdf95av_ZW236C7_tvq_ahECeuF9AZxrheJ4OQ,15573
|
|
88
88
|
sourcecode/pr_impact.py,sha256=KyVBvHCCbgByKA_wqTbO1dSkfi05GEudIAuH4CyDPK4,27395
|
|
89
89
|
sourcecode/prepare_context.py,sha256=ntB2MSScHtYRKRm9SansA0FrklMnXZmE9_yRXqXk0qc,239264
|
|
90
|
-
sourcecode/progress.py,sha256=
|
|
90
|
+
sourcecode/progress.py,sha256=A4hNoCRgmHofilbIXMoR4vlvoF4gupefTUCuYmceI_E,20547
|
|
91
91
|
sourcecode/provenance.py,sha256=gLbsLv0gze7k7ChM4i5M3MwKfFPzs8wzM72rUd2C5sM,7342
|
|
92
92
|
sourcecode/ranking_engine.py,sha256=ZAucq_YX2KkWUuAZf4P0lhtQ_38vEFnUhuGtSZd1S0E,12970
|
|
93
93
|
sourcecode/readiness_timeline.py,sha256=KjghC1MWH7Tzto97TBR3N0F8FkR87w5z7lZ1QsWvAQE,8506
|
|
@@ -95,23 +95,23 @@ sourcecode/readonly.py,sha256=dqGiNVvpfi4Ww8VzwUs6E3SdbqcHL1LFHItIK7IjrM0,5830
|
|
|
95
95
|
sourcecode/reconciliation.py,sha256=GU-1PTcVr8zcbtC7BASfpHcZndP9AdboXPNQiBc0fzo,34251
|
|
96
96
|
sourcecode/redactor.py,sha256=SB4hwIvg8h-hvcqKcDWaZvA-aSyn-at-BIRwa0tUv5E,3227
|
|
97
97
|
sourcecode/reference_facts.py,sha256=Ns495c6eTmq2SrqPkcUrJUJru4_wj_yRWm4YDeJwves,13440
|
|
98
|
-
sourcecode/release_info.py,sha256=
|
|
98
|
+
sourcecode/release_info.py,sha256=4Jfym0xPvJpauhXDWLY2An8yIzae5OlAYZeUr24URsc,5378
|
|
99
99
|
sourcecode/relevance_scorer.py,sha256=0AgEt4KrV73nioMqBgjhGjtY7L2C7L7cSyKtj3IKcrw,9408
|
|
100
100
|
sourcecode/remedies.py,sha256=9eAuP9ndw78YvuUtOi2VvoeNnVCMh1q0uxfnPIkvx7c,7838
|
|
101
101
|
sourcecode/rename_refactor.py,sha256=h6dNFlB9aZ_3q6heeHBkgXQeXaT03nvPSsYH6P8qxFg,12965
|
|
102
102
|
sourcecode/repo_classifier.py,sha256=FG1vaWKdWXsWdl-S8hjVMiTqcwgaRXkDyvK4rPcOGtQ,22681
|
|
103
|
-
sourcecode/repository_ir.py,sha256=
|
|
103
|
+
sourcecode/repository_ir.py,sha256=x0u0m-Dlgtcfr7VsggnKRw_mCobe6B7lZMw3U2H1rd8,368709
|
|
104
104
|
sourcecode/ris.py,sha256=SjjDNMcc9Zr9us9G8ikBVkoPXKK3BpM-dBodSGMh_g4,24783
|
|
105
|
-
sourcecode/risk.py,sha256=
|
|
105
|
+
sourcecode/risk.py,sha256=aSnK369sYcooEm57oaPX3QcCWoElt5YYJrqTtrXUUdo,65486
|
|
106
106
|
sourcecode/rule_catalog.py,sha256=pTkgkQZ1u7atB3fkQJ9BPWo8lNiamvEuRN7MtRgle0c,5263
|
|
107
|
-
sourcecode/rule_pass.py,sha256=
|
|
107
|
+
sourcecode/rule_pass.py,sha256=xK3Tj5wXOzRaSmteKZD4I-QeyrAy7ZZzB8ItjC3r2II,13986
|
|
108
108
|
sourcecode/runs.py,sha256=zIxEPz9DBVVCrX958bZR6bODyfH56A6srAyc8LFMs8I,5303
|
|
109
109
|
sourcecode/runtime_classifier.py,sha256=uTAD6BDCiBLUZEDRfqk718kM4RTT_vAbfkcOI2_Xx58,18432
|
|
110
110
|
sourcecode/sarif.py,sha256=3hQEegUxIZbojFdY59oB-yKsK-rnafHdTNmYsEP2--A,25984
|
|
111
111
|
sourcecode/scanner.py,sha256=z3CV0rcGunu0Y8mpNgp07wI7nxT0pxw1BkXRRtI0Rpo,9609
|
|
112
112
|
sourcecode/schema.py,sha256=aHNXDf8LGyUC8ZDE_VS9kiskC2-Oswhi_WnpdGy6HDw,24897
|
|
113
113
|
sourcecode/security_config.py,sha256=KblMEoRiEjrIE68YsPaUAFebxFp8UM7MS7lAk5CGD8U,3531
|
|
114
|
-
sourcecode/security_config_scan.py,sha256=
|
|
114
|
+
sourcecode/security_config_scan.py,sha256=vpAglQkh7QxUhY_eR6aOOE5d-aU2tsEd-bE8pMoFz8g,30021
|
|
115
115
|
sourcecode/security_posture.py,sha256=JH3v8HKBT4oBKaDCrxNOyrlYSleLlxQw_UgRJcvjX4c,51677
|
|
116
116
|
sourcecode/semantic_analyzer.py,sha256=bpgdC6m0_ftVtRf3rSdwhbhWjnZnGxRXaZVcfe4BbcQ,95414
|
|
117
117
|
sourcecode/semantic_impact_engine.py,sha256=t09IirGC3JjQDy33JZd1_WKzQVKXkoNl3-XEUr5kjis,20563
|
|
@@ -126,9 +126,9 @@ sourcecode/spring_impact.py,sha256=GW77k7KMn13QmzP5520BbVcXSCYlTFeD1bFibhbmzJM,8
|
|
|
126
126
|
sourcecode/spring_model.py,sha256=zOAgFmrRbG4a6KLm1TJl55aWMyPNsz3OS3FSczqPG6A,16594
|
|
127
127
|
sourcecode/spring_profiles.py,sha256=-kwrCK0O-MRjrCp6SA1d31t--o4Tg87iqPVrTwxoIUs,19605
|
|
128
128
|
sourcecode/spring_properties.py,sha256=kPTk5qAJxdbHc1hclhHAP0O-hCIzBn8PTh3lzl-I4a4,8400
|
|
129
|
-
sourcecode/spring_security_audit.py,sha256
|
|
129
|
+
sourcecode/spring_security_audit.py,sha256=-W_cs2vl_GgqWtC2OCdJKL8WuYcZQsessdBO0UTIU-8,55866
|
|
130
130
|
sourcecode/spring_semantic.py,sha256=UrmLg_4gmBVmMqvSnUMgSReVEERVGv3X7LYDBEs6k_o,16598
|
|
131
|
-
sourcecode/spring_tx_analyzer.py,sha256=
|
|
131
|
+
sourcecode/spring_tx_analyzer.py,sha256=4pUTaqqlsKRTBUr6tIwYP9oLamHz_IVKmLq3kFcH7Ww,49412
|
|
132
132
|
sourcecode/summarizer.py,sha256=0aD4x3vgPngqBCEBKGuES1J2Vk5f7mqCm_ZWErwm3js,27025
|
|
133
133
|
sourcecode/target_admission.py,sha256=wFZ4pzlxhiF6Q6s2lEAZEzcCfj1Y6xNvujjt8MdO0Qo,7154
|
|
134
134
|
sourcecode/test_gap_ranking.py,sha256=hl-tTyQUXZGJycG1b6npbLeE_DaVHaOsvPwG9qqC5y8,15711
|
|
@@ -203,8 +203,8 @@ sourcecode/telemetry/consent.py,sha256=pQdl-QeLl6Gcibn0eWHSKZrm-HYSsjpVqOnjrgFp8
|
|
|
203
203
|
sourcecode/telemetry/events.py,sha256=4_yeO58U-Cwc1Qb27VB0_EjhmroY0k91n3_VGxeALB8,2776
|
|
204
204
|
sourcecode/telemetry/filters.py,sha256=RzxauTz8HliO4BllQnXEXc7zTeqdCZi5MgqGEDuW7OQ,6570
|
|
205
205
|
sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
|
|
206
|
-
sourcecode-4.
|
|
207
|
-
sourcecode-4.
|
|
208
|
-
sourcecode-4.
|
|
209
|
-
sourcecode-4.
|
|
210
|
-
sourcecode-4.
|
|
206
|
+
sourcecode-4.16.0.dist-info/METADATA,sha256=lWzS830gWVniMl2-yHA-C6RhBojPBdBuVNDm8fBkgZc,42089
|
|
207
|
+
sourcecode-4.16.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
208
|
+
sourcecode-4.16.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
|
|
209
|
+
sourcecode-4.16.0.dist-info/licenses/LICENSE,sha256=7DdHrU9Z_3e7dSvq4ISijZNjnuHo5NIHNiHDouMQ9JU,10491
|
|
210
|
+
sourcecode-4.16.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|