sourcecode 5.9.0__py3-none-any.whl → 5.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

sourcecode/__init__.py CHANGED
@@ -4,4 +4,4 @@ ASK Engine is the product. ``ask`` is the canonical CLI command; ``sourcecode``
4
4
  the legacy compatibility alias and the Python/PyPI package name. See
5
5
  docs/PRODUCT_IDENTITY.md (normative)."""
6
6
 
7
- __version__ = "5.9.0"
7
+ __version__ = "5.9.1"
@@ -1,5 +1,5 @@
1
1
  """Generated at build time by hatch_build.py. Do not edit (AUD-592-D04)."""
2
2
 
3
- BUILD_COMMIT = 'b4c3691c4110d863fedcd54a61a08fd2997e400c'
3
+ BUILD_COMMIT = '48f4057a9c265d73e61f76f00b41cd011a2e0031'
4
4
  CLOSURE_PROVENANCE = {'Window': {'commit': '', 'status': 'uncited'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-3` / `443b345`': {'commit': '', 'status': 'uncited'}, '`BUG-4a` / `AUD-588-B11` residual': {'commit': '24b4185', 'status': 'present'}, '`BUG-4b` / `AUD-588-B11` residual': {'commit': '0376350', 'status': 'present'}, '`E-39`, class residual of `AUD-593-N06`': {'commit': '25fc11f', 'status': 'present'}, '`N-5`': {'commit': 'c68083f', 'status': 'present'}, '`N-7`': {'commit': '082e9de', 'status': 'present'}, '`R-1`': {'commit': '424fb1c', 'status': 'present'}, '`R-2`': {'commit': '5158b92', 'status': 'present'}, '`R-3`': {'commit': '88aec04', 'status': 'present'}, '`R-4`': {'commit': '04fd04f', 'status': 'present'}}
5
5
  CLOSURE_COVERAGE = {'closed_rows': 113, 'present': 88, 'absent': 0, 'unresolved': 0, 'uncited': 25, 'unknown': 0}
@@ -16,6 +16,23 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
16
16
 
17
17
  ## Current Synchronization
18
18
 
19
+ **External `5.9.0` audit received, 2026-08-26 - `AUD-608`; corrections shipped in `5.9.1`.** The audited
20
+ revision is the released `5.9.0` commit. The three confirmed corrections are
21
+ closed: `AUD-608-C01` (P1, `cve` now declares its static inventory scope,
22
+ `fe1f624`), `AUD-608-P01` (P1, `pr-risk --since` returns structured base-ref
23
+ errors, `b479465`), and `AUD-608-O01` (P2, `posture --diff --limit` bounds
24
+ `permit_all` plus effective-access evidence maps, `5f8a50b`). `AUD-607-U01`
25
+ is also closed (`19811ac`): the seven new command helps state budget-policy
26
+ non-applicability. `AUD-607-U02` is
27
+ withdrawn after controlled runs showed an external 120-second timeout;
28
+ `AUD-608-C02` (reported UTF-8 mojibake in `chunk-file`) remains a platform
29
+ reproduction follow-up because the released reader is explicitly UTF-8.
30
+
31
+ The new effective-access evidence supersedes the prior `AUD-607-O01`
32
+ non-reproduction: that check did not exercise the payload shape now identified.
33
+ Historical records below remain snapshots. Full evidence is in
34
+ [`AUDIT-2026-08-26-EXTERNAL-5.9.0.md`](AUDIT-2026-08-26-EXTERNAL-5.9.0.md).
35
+
19
36
  **External `5.8.32` audit received, 2026-08-26 — `AUD-607`.** Two independent
20
37
  reports ([`AUDIT-2026-08-26-EXTERNAL-5.8.32.md`](AUDIT-2026-08-26-EXTERNAL-5.8.32.md)):
21
38
  a reverification round (9.7/10) and a comparison round against `5.8.31` (8/10).
@@ -46,13 +46,13 @@ 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.9.0, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
49
+ 5.9.1, `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
 
53
53
  The current external pack audit intake is tracked in
54
54
  [`AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md`](AUDIT-2026-08-26-EXTERNAL-5.8.28-PACK.md);
55
- the two pack fixes are included in release 5.9.0, with release-artifact verification
55
+ the two pack fixes are included in release 5.9.1, with release-artifact verification
56
56
  remaining for detached execution and pack evidence.
57
57
 
58
58
  ---
@@ -206,7 +206,7 @@ pipx install sourcecode # isolated install, no venv needed
206
206
 
207
207
  # Verify
208
208
  ask version
209
- # ask 5.9.0
209
+ # ask 5.9.1
210
210
  ```
211
211
 
212
212
  Requires Python 3.9+.
sourcecode/cli.py CHANGED
@@ -826,6 +826,31 @@ from sourcecode.analysis_budget import ( # noqa: E402
826
826
  )
827
827
 
828
828
 
829
+ def _command_budget_help(command: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
830
+ """Append the command-level budget contract derived from the shared registry."""
831
+ from sourcecode.analysis_budget import budget_registry_name
832
+
833
+ if budget_registry_name(command) is not None:
834
+ policy = (
835
+ "\n\b\nBudget policy: this command is registered for "
836
+ "`ASK_MAX_ANALYSIS_SECONDS`; its result publishes the applicable "
837
+ "analysis-budget and memory contract."
838
+ )
839
+ else:
840
+ policy = (
841
+ "\n\b\nBudget policy: this command does not consume "
842
+ "`ASK_MAX_ANALYSIS_SECONDS` and does not create a command-level "
843
+ "`ASK_MAX_RSS_MB` enforcement scope. A delegated analysis, if any, "
844
+ "publishes its own budget contract."
845
+ )
846
+
847
+ def decorate(command_fn: Callable[..., Any]) -> Callable[..., Any]:
848
+ command_fn.__doc__ = (command_fn.__doc__ or "") + policy
849
+ return command_fn
850
+
851
+ return decorate
852
+
853
+
829
854
  def _build_help_text(scope: "Optional[Any]" = None) -> str:
830
855
  """Build --help text dynamically based on current license state."""
831
856
  try:
@@ -14508,7 +14533,12 @@ def posture_cmd(
14508
14533
  # answered in — a `--diff` carries a bean list per profile set and a
14509
14534
  # `--diff-ref` one per ref, so the bound has to reach every depth or the
14510
14535
  # caller believes they bounded something they did not.
14511
- from sourcecode.output_bounds import POSTURE_BOUNDED_LISTS, bound_lists, resolve_limit
14536
+ from sourcecode.output_bounds import (
14537
+ POSTURE_BOUNDED_LISTS,
14538
+ POSTURE_BOUNDED_MAPS,
14539
+ bound_lists,
14540
+ resolve_limit,
14541
+ )
14512
14542
 
14513
14543
  _posture_limit = resolve_limit(limit, compact)
14514
14544
  # B-8 / AUD-592-B01. A compact answer must retain one actionable unresolved
@@ -14518,6 +14548,7 @@ def posture_cmd(
14518
14548
  _posture_limit = 1 if data.get("summary", {}).get("unresolved", 0) else 0
14519
14549
  bound_lists(
14520
14550
  data, POSTURE_BOUNDED_LISTS, _posture_limit,
14551
+ map_keys=POSTURE_BOUNDED_MAPS,
14521
14552
  # `--compact` is a size contract for agent output. The monotone
14522
14553
  # optimisation may keep a list whole when its disclosure costs more
14523
14554
  # than that individual cut, leaving a large diff above the ceiling.
@@ -16543,6 +16574,7 @@ def _audit_population_for(root: "Path", *, progress: "Any" = None):
16543
16574
  # ── self-audit (what this build can say about itself, and what it cannot) ────
16544
16575
 
16545
16576
  @app.command("self-audit")
16577
+ @_command_budget_help("self-audit")
16546
16578
  def self_audit_cmd(
16547
16579
  fail_on: Optional[str] = typer.Option(
16548
16580
  None, "--fail-on",
@@ -16619,6 +16651,7 @@ def self_audit_cmd(
16619
16651
  # ── drift (what changed in the effective access, and when) ───────────────────
16620
16652
 
16621
16653
  @app.command("drift")
16654
+ @_command_budget_help("drift")
16622
16655
  def drift_cmd(
16623
16656
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
16624
16657
  profile: Optional[str] = typer.Option(
@@ -16759,6 +16792,7 @@ def drift_cmd(
16759
16792
  # ── watch (re-answer only what changed, while you are still editing) ─────────
16760
16793
 
16761
16794
  @app.command("watch")
16795
+ @_command_budget_help("watch")
16762
16796
  def watch_cmd(
16763
16797
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
16764
16798
  once: bool = typer.Option(
@@ -16858,6 +16892,7 @@ def watch_cmd(
16858
16892
  # ── pr-risk (the risk of THIS change, not the state of the repository) ───────
16859
16893
 
16860
16894
  @app.command("pr-risk")
16895
+ @_command_budget_help("pr-risk")
16861
16896
  def pr_risk_cmd(
16862
16897
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
16863
16898
  files: Optional[str] = typer.Option(
@@ -16901,6 +16936,7 @@ def pr_risk_cmd(
16901
16936
  ask pr-risk . --files changed.txt --since origin/main --fail-on block
16902
16937
  """
16903
16938
  from sourcecode.pr_risk import assess, VERDICT_BLOCK, VERDICT_REVIEW, VERDICT_UNKNOWN
16939
+ from sourcecode.context_graph import ContextGraph
16904
16940
  from sourcecode.repository_ir import find_java_files
16905
16941
  from sourcecode.spring_model import SpringSemanticModel
16906
16942
 
@@ -16939,7 +16975,7 @@ def pr_risk_cmd(
16939
16975
  base_routes = None
16940
16976
  if since:
16941
16977
  _prog.start(f"differencing routes against {since}")
16942
- from sourcecode.git_checkout import materialize_ref
16978
+ from sourcecode.git_checkout import RefError, materialize_ref
16943
16979
  from sourcecode.pr_risk import _routes_of
16944
16980
 
16945
16981
  try:
@@ -16947,14 +16983,24 @@ def pr_risk_cmd(
16947
16983
  base_files = find_java_files(base_tree)
16948
16984
  base_cir = ContextGraph.build(base_files, base_tree).cir
16949
16985
  base_routes = _routes_of(base_cir)
16950
- except Exception as _base_exc:
16951
- # A base that could not be read is not a base with no routes:
16952
- # differencing against an empty set would report every existing
16953
- # endpoint as newly added by this diff.
16954
- base_routes = None
16955
- _base_error = f"{type(_base_exc).__name__}: {_base_exc}"
16956
- else:
16957
- _base_error = ""
16986
+ except RefError as exc:
16987
+ _emit_error_json(
16988
+ exc.code,
16989
+ exc.message,
16990
+ ref=since,
16991
+ hint="Pass a commit available in this repository; no ref is fetched or substituted.",
16992
+ )
16993
+ raise typer.Exit(code=1)
16994
+ except Exception as exc:
16995
+ _emit_error_json(
16996
+ EXECUTION_FAILED_CODE,
16997
+ f"Could not build the base route inventory for {since!r}: "
16998
+ f"{type(exc).__name__}: {exc}",
16999
+ ref=since,
17000
+ stage="base_route_inventory",
17001
+ hint="Fix the base tree or retry with a readable local ref; no comparison was produced.",
17002
+ )
17003
+ raise typer.Exit(code=1)
16958
17004
 
16959
17005
  _prog.start("scoring the change")
16960
17006
  result = assess(
@@ -16968,12 +17014,6 @@ def pr_risk_cmd(
16968
17014
  finally:
16969
17015
  _prog.stop()
16970
17016
 
16971
- if since and base_routes is None:
16972
- result.non_coverage.append(
16973
- f"the base ref `{since}` could not be read, so no route was reported "
16974
- f"as newly added: {_base_error}"
16975
- )
16976
-
16977
17017
  data = result.to_dict()
16978
17018
  _emit_command_output(
16979
17019
  _serialize_dict(data, format),
@@ -16998,6 +17038,7 @@ def pr_risk_cmd(
16998
17038
  # ── remediate (finding → recipe → apply → verify, as one loop) ───────────────
16999
17039
 
17000
17040
  @app.command("remediate")
17041
+ @_command_budget_help("remediate")
17001
17042
  def remediate_cmd(
17002
17043
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
17003
17044
  write: bool = typer.Option(
@@ -17101,6 +17142,7 @@ def remediate_cmd(
17101
17142
  # ── intent (natural language → a typed query this CLI already answers) ───────
17102
17143
 
17103
17144
  @app.command("intent")
17145
+ @_command_budget_help("intent")
17104
17146
  def intent_cmd(
17105
17147
  question: str = typer.Argument(..., help="The question, in plain language."),
17106
17148
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
@@ -17179,6 +17221,7 @@ def intent_cmd(
17179
17221
  # ── cve (dependency advisories, gated on reachability) ───────────────────────
17180
17222
 
17181
17223
  @app.command("cve")
17224
+ @_command_budget_help("cve")
17182
17225
  def cve_cmd(
17183
17226
  path: Path = typer.Argument(Path("."), help="Repository path (default: current directory)"),
17184
17227
  feed: Optional[Path] = typer.Option(
@@ -17231,6 +17274,12 @@ def cve_cmd(
17231
17274
  Nothing here answers "not reachable": reflection, SPI loading and container
17232
17275
  wiring reach code no import statement names.
17233
17276
 
17277
+ \b
17278
+ Dependency coverage is the static manifest and supported-lockfile inventory.
17279
+ This command does not execute Maven or Gradle, so a transitive dependency
17280
+ absent from that inventory is not assessed. The JSON result publishes this
17281
+ coverage explicitly; it never treats an unobserved coordinate as safe.
17282
+
17234
17283
  \b
17235
17284
  ask cve . --feed ./osv-maven.json
17236
17285
  ask cve . --feed ./feeds/ --reachable-only --fail-on reachable
@@ -371,6 +371,29 @@ def assess(
371
371
  }
372
372
  non_coverage: list[str] = []
373
373
 
374
+ # DependencyAnalyzer reads manifests and the lockfiles it understands. It
375
+ # deliberately does not execute Maven or Gradle, so a missing record cannot
376
+ # mean a dependency resolver proved the coordinate absent.
377
+ java_records = [
378
+ record for record in dependencies
379
+ if str(getattr(record, "ecosystem", "")).lower() == "java"
380
+ ]
381
+ scopes_observed: dict[str, int] = {}
382
+ for record in java_records:
383
+ scope = str(getattr(record, "scope", None) or "unknown")
384
+ scopes_observed[scope] = scopes_observed.get(scope, 0) + 1
385
+ dependency_inventory = {
386
+ "basis": "static_manifests_and_supported_lockfiles",
387
+ "dependency_resolution": "not_performed",
388
+ "java_records_observed": len(java_records),
389
+ "scopes_observed": dict(sorted(scopes_observed.items())),
390
+ "non_coverage": (
391
+ "Maven and Gradle are not executed. Dependencies absent from the "
392
+ "static manifest or supported lockfile inventory, including "
393
+ "tool-resolved transitive dependencies, are not assessed."
394
+ ),
395
+ }
396
+
374
397
  if not feed.loaded:
375
398
  # A refusal, not a clean scan. The caller turns this into an exit code.
376
399
  return {
@@ -378,6 +401,7 @@ def assess(
378
401
  "schema": SCHEMA_ID,
379
402
  "status": "no_feed",
380
403
  "feed": feed.to_dict(),
404
+ "dependency_inventory": dependency_inventory,
381
405
  "dependencies_examined": 0,
382
406
  "matches": [],
383
407
  "counts": counts,
@@ -490,8 +514,10 @@ def assess(
490
514
  "schema": SCHEMA_ID,
491
515
  "status": "ok",
492
516
  "feed": feed.to_dict(),
517
+ "dependency_inventory": dependency_inventory,
493
518
  "dependencies_examined": examined,
494
519
  "matches": matches,
495
520
  "counts": counts,
496
- "non_coverage": non_coverage + list(feed.limitations),
521
+ "non_coverage": [dependency_inventory["non_coverage"]]
522
+ + non_coverage + list(feed.limitations),
497
523
  }
@@ -72,6 +72,9 @@ POSTURE_BOUNDED_LISTS: "tuple[str, ...]" = (
72
72
  # `endpoint_access.endpoints`; registering only the synthetic `changed`
73
73
  # shape left the largest nested list untouched by `--limit`.
74
74
  "endpoints",
75
+ # AUD-608-O01. Effective access publishes this as one endpoint id per open
76
+ # decision. Omitting it made `posture --diff --limit` leave a full branch.
77
+ "permit_all",
75
78
  # AUD-602-R02. `posture --diff` caps the per-profile bean and security
76
79
  # lists, but the actionable delta is `endpoint_access.changed`: one row per
77
80
  # request whose decision moves between the two profile sets. Omitting it
@@ -79,6 +82,11 @@ POSTURE_BOUNDED_LISTS: "tuple[str, ...]" = (
79
82
  "changed",
80
83
  )
81
84
 
85
+ #: AUD-608-O01. `effective_access.evidence` is an endpoint-id map rather than a
86
+ #: list. It has a separate registry because `evidence` is deliberately a common
87
+ #: name in unrelated payloads and must not become a global truncation rule.
88
+ POSTURE_BOUNDED_MAPS: "tuple[str, ...]" = ("evidence",)
89
+
82
90
  #: The same for `validation`: one row per endpoint and one per gap, both of which
83
91
  #: scale with the repository rather than with the question.
84
92
  VALIDATION_BOUNDED_LISTS: "tuple[str, ...]" = (
@@ -141,6 +149,7 @@ def bound_lists(
141
149
  keys: "Iterable[str]",
142
150
  limit: "int | None",
143
151
  *,
152
+ map_keys: "Iterable[str]" = (),
144
153
  monotone: bool = False,
145
154
  ) -> Any:
146
155
  """Cap every list named in *keys*, anywhere in *payload*, and declare the cut.
@@ -151,8 +160,12 @@ def bound_lists(
151
160
  level and misses one level down is worse than no flag, because the caller
152
161
  believes they bounded it.
153
162
 
154
- Only *list* values are touched. `summary.unresolved` is an int under the same
155
- name, and a count is not a body.
163
+ Named list values are touched. Named maps are touched only when their
164
+ parent is `effective_access`: `evidence` is a deliberately generic key in
165
+ other answer shapes. `summary.unresolved` is an int under the same name,
166
+ and a count is not a body. Maps retain their published insertion order:
167
+ their keys are endpoint identifiers, so sorting again here would make the
168
+ limit a second, inconsistent ranking policy.
156
169
 
157
170
  **A bounded payload is never larger than the unbounded one it replaces.**
158
171
  AUD-597-R03: `posture . --diff dev:prod` estimated 284 464 B and the same
@@ -182,6 +195,7 @@ def bound_lists(
182
195
  if limit is None:
183
196
  return payload
184
197
  bounded = set(keys)
198
+ bounded_maps = set(map_keys)
185
199
  limit = max(0, int(limit))
186
200
  #: Bytes the accepted cuts freed, net of what they had to say about
187
201
  #: themselves. The declaration below is paid for out of this and nothing
@@ -189,16 +203,25 @@ def bound_lists(
189
203
  saved = 0
190
204
  declined: "set[str]" = set()
191
205
 
192
- def walk(node: Any) -> Any:
206
+ def walk(node: Any, parent_key: "str | None" = None) -> Any:
193
207
  nonlocal saved
194
208
  if isinstance(node, dict):
195
209
  for key in list(node):
196
210
  value = node[key]
197
- if key in bounded and isinstance(value, list):
211
+ is_bounded_list = key in bounded and isinstance(value, list)
212
+ is_bounded_map = (
213
+ key in bounded_maps
214
+ and parent_key == "effective_access"
215
+ and isinstance(value, dict)
216
+ )
217
+ if is_bounded_list or is_bounded_map:
198
218
  total = len(value)
199
219
  if total <= limit:
200
220
  continue # nothing cut, and `declare_cap` says nothing
201
- node[key] = value[:limit]
221
+ if isinstance(value, list):
222
+ node[key] = value[:limit]
223
+ else:
224
+ node[key] = dict(list(value.items())[:limit])
202
225
  declare_cap(node, key, total, limit)
203
226
  if not monotone:
204
227
  continue
@@ -206,7 +229,10 @@ def bound_lists(
206
229
  # `[limit:]` weighed as a list is the separators the removed
207
230
  # rows carried with them, which is what the emit actually
208
231
  # stops writing.
209
- freed = payload_bytes(value[limit:])
232
+ removed = value[limit:] if isinstance(value, list) else dict(
233
+ list(value.items())[limit:]
234
+ )
235
+ freed = payload_bytes(removed)
210
236
  spent = payload_bytes({cap_key: node.get(cap_key)})
211
237
  if freed > spent:
212
238
  saved += freed - spent
@@ -215,10 +241,10 @@ def bound_lists(
215
241
  node.pop(cap_key, None)
216
242
  declined.add(key)
217
243
  else:
218
- walk(value)
244
+ walk(value, key)
219
245
  elif isinstance(node, list):
220
246
  for item in node:
221
- walk(item)
247
+ walk(item, parent_key)
222
248
  return node
223
249
 
224
250
  result = walk(payload)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sourcecode
3
- Version: 5.9.0
3
+ Version: 5.9.1
4
4
  Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/HarounDominique/sourcecode
6
6
  Project-URL: Repository, https://github.com/HarounDominique/sourcecode
@@ -59,7 +59,7 @@ Description-Content-Type: text/markdown
59
59
  > question. All analysis is local and deterministic; see [Privacy](#configuration--privacy)
60
60
  > before inspecting a repository whose telemetry policy you need to control.
61
61
 
62
- ![Version](https://img.shields.io/badge/version-5.9.0-blue)
62
+ ![Version](https://img.shields.io/badge/version-5.9.1-blue)
63
63
  ![Python](https://img.shields.io/badge/python-3.9%2B-green)
64
64
 
65
65
  Copyright (c) 2026 Dominique Haroun. All rights reserved. ASK Engine 5.7.0 and
@@ -188,13 +188,13 @@ set `ASK_MAX_RSS_MB=<megabytes>`; if the process exceeds it, ASK refuses the
188
188
  answer with `MEMORY_TOO_LARGE`, including the observed peak and limit. Leave it
189
189
  unset, or set it to `0`, to disable the ceiling explicitly.
190
190
 
191
- **Current performance status.** Release 5.9.0 retains the shared Canonical IR
191
+ **Current performance status.** Release 5.9.1 retains the shared Canonical IR
192
192
  path used by `validation` and now passes that same CIR into labelled `data-exposure`
193
193
  runs; the no-label path remains parse-free. These are implementation properties,
194
194
  not duration promises. A controlled external warm-path audit has opened performance
195
195
  and bounded-output regressions for this release; use `ask cache model <repo>` and
196
196
  `--progress stderr` for supervised runs, and consult the
197
- [latest external audit intake](docs/AUDIT-2026-08-26-EXTERNAL-5.8.31.md) before
197
+ [latest external audit intake](docs/AUDIT-2026-08-26-EXTERNAL-5.9.0.md) before
198
198
  treating a historical timing or bounded flag as a current guarantee.
199
199
 
200
200
  **What a warm actually covers.** `ask cache warm` rebuilds the declared root views and their
@@ -222,7 +222,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
222
222
  # pip / pipx
223
223
  pipx install sourcecode # or: pip install sourcecode
224
224
 
225
- ask version # ask 5.9.0 — and, on a build that has aged,
225
+ ask version # ask 5.9.1 — and, on a build that has aged,
226
226
  # how many releases have probably shipped since
227
227
  ```
228
228
 
@@ -1,6 +1,6 @@
1
- sourcecode/__init__.py,sha256=GBTy1dCabhQynn4AwYQJl5tRJ69byD9x_aAV5seQMsE,308
1
+ sourcecode/__init__.py,sha256=_2HX1Q37i7qsrKVEti8GzYH5VTQN_ceb00JMLUsgPq0,308
2
2
  sourcecode/__main__.py,sha256=qDFVkobwsdZrX0B3stiRqsUNivVpybKO-egzAwCW4xw,165
3
- sourcecode/_build_commit.py,sha256=8-nD4m0EtLUBgon25gM8ZcMeUyffZGEm7sRdXtNNMfo,8740
3
+ sourcecode/_build_commit.py,sha256=fE28IwhQeKoWSX5Qk5TSlP6pkTSAKn0o7s1E1NiL5N0,8740
4
4
  sourcecode/access_drift.py,sha256=1R7_fiAPvKNOnxc9uxkH3JH-k8MiAuyE7FwWUX1r4Fg,8995
5
5
  sourcecode/adaptive_scanner.py,sha256=yJBKjNpkY6bpueYJ2YnRezen3sYZDecEt7WaaNWdqug,9466
6
6
  sourcecode/analysis_budget.py,sha256=ZuNEboPF8A6GK-_V7WNt92ezgriga7_Rbr-vvSN4UEE,4183
@@ -33,7 +33,7 @@ sourcecode/change_plan.py,sha256=drAoDsqFplutCLc6Y0xue7sYkTls0SpYh7aOXvttv3I,129
33
33
  sourcecode/ci_output.py,sha256=X4NRokWWMRF9Ex8OQrEO2g04GzJ00GDP5zYIoqI9YCc,4625
34
34
  sourcecode/cir_graphs.py,sha256=RytqSv1WF8kdlfXj2fPQz16LY4akS2kyThbdWL8beCQ,14835
35
35
  sourcecode/classifier.py,sha256=JBzPwSSrDG-tUHAbcKB678HRbjLpD-ohzbzzO62mgpo,20114
36
- sourcecode/cli.py,sha256=v3tXTToKjIi6iVphkD34fMbFqLtU1J5PdoNYQu8Z6fU,916592
36
+ sourcecode/cli.py,sha256=kdXkygV9OTy5lAHLx730GkUuAzYvYfMZxeqmbd6Q0iI,918522
37
37
  sourcecode/client_calls.py,sha256=YOPnfRZzRyqmdHvXSVeiunW8cuiuCuWXacNIQRgKMCw,22640
38
38
  sourcecode/code_notes_analyzer.py,sha256=EJemNCNc9Dn-1RZYu-aNbK0ELzmsyC4s6FdHi3XyNEI,9392
39
39
  sourcecode/compare.py,sha256=Lnfmm9I3k0H1jP1tV6ZJ-R7znjQPLPLCe5TzV1Jb_PM,15323
@@ -51,7 +51,7 @@ sourcecode/contract_model.py,sha256=nRxJKPMs1VHwFTa8AVXhGmaLjti3Lr2sjHDpWgv1bfE,
51
51
  sourcecode/contract_pipeline.py,sha256=4XE-xhab7ysdExXyUOBl2hAH4MABo7moKnT9V4XcEwQ,30243
52
52
  sourcecode/coverage_parser.py,sha256=X9scuUWxacahYTrTBGea2Ku-sf-WCVcnc9OEB35_3IU,19340
53
53
  sourcecode/cve_feed.py,sha256=t93Ofu0uxB3zyI8W9nUKmH4Ljbt4qZpGIg68IP_aTrY,10749
54
- sourcecode/cve_reachability.py,sha256=omXEuCeW3DmjOf6KEnsXFDF2c_9-HIYmAt6Q09YHiow,19160
54
+ sourcecode/cve_reachability.py,sha256=aEHkG3IaeLxgekK79cz07QTcv9LZHEqEYqyLGjwjzSE,20409
55
55
  sourcecode/data_exposure.py,sha256=73EcuHsN3ESKdSvSj1B2FzD1myGJqzlNJpYO1NpI5WM,19963
56
56
  sourcecode/data_labels.py,sha256=9v0JaICRH7jIZ015T1_DuT5E0MFfeL-Wcq1WOPE0Fe0,10965
57
57
  sourcecode/declarations.py,sha256=Jr7oJDg004sFw0TvJySNVggXFT2jB6hQgbeQK2qreVo,7000
@@ -104,7 +104,7 @@ sourcecode/migration_blast.py,sha256=OPryWkM6-PbvcG3eJFdcWUnlpXBje41nu2BkrZnE2hg
104
104
  sourcecode/non_coverage.py,sha256=Tl04OemPnxnOuWISMF6gAhDYx-P0LP6_l-PbpyvBJb4,30634
105
105
  sourcecode/openapi_surface.py,sha256=BTt0K-woZbkbWTN77IkqeBm_Okag9owR0848fmot8sk,16207
106
106
  sourcecode/openrewrite_recipe.py,sha256=0pS-61falE6rSfkos2MPnmoDamvoeTT-8Kmj7smj_DI,12987
107
- sourcecode/output_bounds.py,sha256=909Wu4qlLqHGa4fgQAsDhSBBi0lL8MQnrXv0gxZM2u8,14051
107
+ sourcecode/output_bounds.py,sha256=BvaRQY5nuytgfNUJHyO7JDLOHuSy0vdjqasISxmxLxI,15516
108
108
  sourcecode/output_budget.py,sha256=AHwxnfToUk9a3MJdcq0WDUiFbyNla9VWxdE0pC7UWfY,15617
109
109
  sourcecode/output_ceiling.py,sha256=rMLDHbXeC6C_uP8iQSVbbCGiJ_n0gMr2YI9zt5nltFA,33201
110
110
  sourcecode/output_encoding.py,sha256=OxiZQtr7uWBDUtblNQviNHsTH1Xo9AZEpsegPlQWKFk,2139
@@ -268,13 +268,13 @@ sourcecode/telemetry/consent.py,sha256=-Wb8BFGXEexOs8M0Eja7F-T6ZtLNPeRoZnHcHvlP7
268
268
  sourcecode/telemetry/events.py,sha256=l3Z2SQUO4_C2D2ItXOOdVNimAmHedUHnUxqYFTxnsAE,2900
269
269
  sourcecode/telemetry/filters.py,sha256=-lqT2oorECuqEIdeHb--7AZ6BCaUx0bya720a-Z5x6w,7335
270
270
  sourcecode/telemetry/transport.py,sha256=4gGHsq0WeY9VywEZXA3vUxykfiYnw9uuqfjAAec7F8o,1681
271
- sourcecode/_docs/DEFECT-LEDGER.md,sha256=UYvIdd7Guh5gebKhfV1q-jseaA4uN-dEX5USa-LHh_Y,1114060
271
+ sourcecode/_docs/DEFECT-LEDGER.md,sha256=EkYSJieDrazfADCZ8tsCsybyW9U0nAzWDQ8rS4KmGTU,1115164
272
272
  sourcecode/_docs/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
273
273
  sourcecode/_docs/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
274
- sourcecode/_docs/USER_GUIDE.md,sha256=Zs2_T4Lw6_lK0JBPqwaTxyVLuOCrGe6PP3cxH_-t4Rs,99674
275
- sourcecode-5.9.0.dist-info/METADATA,sha256=UvX-x8HWbWZA_8Q7A89UA6lemfGIr1gqslK8ch1vYFc,63168
276
- sourcecode-5.9.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
277
- sourcecode-5.9.0.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
278
- sourcecode-5.9.0.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
279
- sourcecode-5.9.0.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
280
- sourcecode-5.9.0.dist-info/RECORD,,
274
+ sourcecode/_docs/USER_GUIDE.md,sha256=VtJs2r3hnqZ93vLpP92tFKgoPZ2VScoiBse1y01a0s4,99674
275
+ sourcecode-5.9.1.dist-info/METADATA,sha256=0K__BP-Li6SKHaPzMyNvNbz3EuqckTJsNp6R7j-xMUo,63167
276
+ sourcecode-5.9.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
277
+ sourcecode-5.9.1.dist-info/entry_points.txt,sha256=-JEAdChrK5We51kZcb7OaDcyil-dHBjBPL-NhuO-QY8,89
278
+ sourcecode-5.9.1.dist-info/licenses/LICENSE,sha256=23-fk61oSWGVmkWyka-FIM0rhAPM2hcYHnsaridL8Uo,1859
279
+ sourcecode-5.9.1.dist-info/licenses/NOTICE,sha256=Br-AA8Y3iJo1WXa5uP8QBpzqI5nDu3iGLbFyvDt3BOs,321
280
+ sourcecode-5.9.1.dist-info/RECORD,,