sourcecode 5.9.26__py3-none-any.whl → 5.9.28__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.26"
7
+ __version__ = "5.9.28"
@@ -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 = '2f283c4d305e2709dc154a4399650c2042b28d68'
3
+ BUILD_COMMIT = 'adbbf9ca7652e2ad2dfdb2db911c12e1793dded5'
4
4
  CLOSURE_PROVENANCE = {'AUD-611-B05': {'commit': '', 'status': 'uncited'}, 'AUD-611-B06': {'commit': '', 'status': 'uncited'}, 'AUD-611-B07': {'commit': '', 'status': 'uncited'}, 'AUD-611-B08': {'commit': '', 'status': 'uncited'}, 'AUD-611-B09': {'commit': '', 'status': 'uncited'}, 'AUD-611-B10': {'commit': '', 'status': 'uncited'}, 'AUD-611-B11': {'commit': '', 'status': 'uncited'}, 'AUD-612-07': {'commit': '', 'status': 'uncited'}, 'AUD-612-09': {'commit': '', 'status': 'uncited'}, 'AUD-612-14': {'commit': '', 'status': 'uncited'}, 'AUD-612-B03': {'commit': '', 'status': 'uncited'}, 'AUD-612-B07': {'commit': '', 'status': 'uncited'}, 'AUD-612-B13': {'commit': '', 'status': 'uncited'}, 'AUD-612-E': {'commit': '', 'status': 'uncited'}, 'AUD-612-G': {'commit': '', 'status': 'uncited'}, 'AUD-612-I': {'commit': '', 'status': 'uncited'}, 'AUD-612-K': {'commit': '', 'status': 'uncited'}, 'AUD-612-M': {'commit': '', 'status': 'uncited'}, 'AUD-612-R': {'commit': '', 'status': 'uncited'}, 'AUD-613-ROOT': {'commit': '', 'status': 'uncited'}, 'AUD-614-07': {'commit': '', 'status': 'uncited'}, 'AUD-614-E': {'commit': '', 'status': 'uncited'}, 'AUD-614-F': {'commit': '', 'status': 'uncited'}, 'AUD-614-G': {'commit': '', 'status': 'uncited'}, 'AUD-614-I': {'commit': '', 'status': 'uncited'}, 'AUD-614-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-614-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-614-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-614-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-615-07': {'commit': '', 'status': 'uncited'}, 'AUD-615-COLD-COMPACT': {'commit': '', 'status': 'uncited'}, 'AUD-615-DRIFT': {'commit': '', 'status': 'uncited'}, 'AUD-615-E': {'commit': '', 'status': 'uncited'}, 'AUD-615-F': {'commit': '', 'status': 'uncited'}, 'AUD-615-INTENT-TIE': {'commit': '', 'status': 'uncited'}, 'AUD-615-JUNIT4': {'commit': '', 'status': 'uncited'}, 'AUD-615-RISK-L0': {'commit': '', 'status': 'uncited'}, 'AUD-615-SELFTEST-TIMEOUT': {'commit': '', 'status': 'uncited'}, 'AUD-616-E-PROV': {'commit': '', 'status': 'uncited'}, 'AUD-616-F, -RISK-L0, -SELFTEST-TIMEOUT, -INTENT-TIE, -DRIFT, -COLD-COMPACT, -E, -07': {'commit': '', 'status': 'uncited'}, 'AUD-616-INTENT-TIE-COV': {'commit': '', 'status': 'uncited'}, 'AUD-616-JUNIT4-SELF': {'commit': '', 'status': 'uncited'}, 'AUD-616-KOTLIN': {'commit': '', 'status': 'uncited'}, 'AUD-616-O': {'commit': '', 'status': 'uncited'}, 'AUD-616-P': {'commit': '', 'status': 'uncited'}, 'Window': {'commit': '', 'status': 'uncited'}, '`ASK-FIELD-01`': {'commit': '829f9fa', 'status': 'present'}, '`ASK-FIELD-02`': {'commit': '6a659fa', 'status': 'present'}, '`ASK-FIELD-03`': {'commit': '55b0d51', 'status': 'present'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`AUD-619-WATCH`': {'commit': '', 'status': 'uncited'}, '`AUD-620-COMMAND-SURFACE`': {'commit': '', 'status': 'uncited'}, '`AUD-620-REVIEW-PR`': {'commit': '', 'status': 'uncited'}, '`AUD-621-REVIEW-PR`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-15`': {'commit': '', 'status': 'uncited'}, '`BUG-21`': {'commit': '', 'status': 'uncited'}, '`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'}, '`REG-01`': {'commit': '', 'status': 'uncited'}, '`REG-04`': {'commit': '', 'status': 'uncited'}}
5
5
  CLOSURE_COVERAGE = {'closed_rows': 169, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 78, 'unknown': 0}
@@ -14,6 +14,180 @@ Status values: `open` · `in-progress` · `closed <version>` · `by-design` (wit
14
14
  The facts these rows are keyed to are published: `ask schema facts-v1` prints the registry
15
15
  (authority, definition, consumers, parity test) that the release battery enforces.
16
16
 
17
+ ## Current Synchronization — Release `5.9.28`, nine rows closed on the 5.9.27 queue (2026-09-02)
18
+
19
+ **`R-01`'s architectural question is answered in source, and the answer was
20
+ "exclusive use".** `parallel.default_jobs()` returned `cpu_count() - 1` for
21
+ each process independently, so `k` concurrent `ask` invocations started
22
+ `k × (N-1)` parsing workers on `N` cores — and no field in any payload said the
23
+ run had been sharing the machine. That is what four rounds of "same command,
24
+ sometimes fast, sometimes hung, no per-process variable distinguishing the two"
25
+ looks like from outside. The cache was *not* the same kind of defect: writes
26
+ are unique-temp + `os.replace` with retry and every read is guarded, so a
27
+ concurrent writer degrades a peer to a cache miss; the one operation that hurt
28
+ a peer was eviction, which now defers while a peer is live.
29
+
30
+ Every row marked **fixed** below is closed in release `5.9.28`; the two
31
+ open rows say what closure still needs. This batch does not claim to close
32
+ the erratic-failure row — one hang was
33
+ observed with nothing else running, and that is unexplained. It removes the
34
+ mechanism the 5.9.27 round evidenced, and gives the next round the variable it
35
+ did not have: `_meta.concurrency` on every answer.
36
+
37
+ **Measured here, for the two latency rows:** `ask --help` is 0.41–0.44 s on
38
+ this machine over three consecutive runs (the audit measured 14.4–14.7 s under
39
+ confirmed contention). That is not a refutation of the field figure — it is a
40
+ different machine — but it does put the row behind `R-01` rather than beside
41
+ it, and the next round can separate them with the disclosure block above.
42
+
43
+ | ID | Severity | Status | Evidence and required closure |
44
+ |---|---:|---|---|
45
+ | `R-01` (contention) | Critical maximum | **mechanism removed, row open pending field re-measure** | `concurrency.py` keeps a best-effort census of live invocations (one lease per process, swept by pid liveness on POSIX and lease age everywhere — never `os.kill(pid, 0)` on Windows, where CPython maps a non-console signal onto `TerminateProcess` and the POSIX idiom would kill the peer it was counting). `parallel.default_jobs()` divides the worker budget by that census; `cache._gc` defers eviction while a peer is live; `_meta.concurrency` publishes peers at start, whether the budget was shared, and that a shared run is not an isolated-machine measurement. `ASK_NO_CONCURRENCY_CENSUS=1` restores the previous behaviour. Closure needs a field round measuring the same commands with the block attached. |
46
+ | `BUG-22` / `REG-02` (enforcement half) | High | **fixed** | The ceiling was checked at phase boundaries only, so a phase could allocate without bound and be refused once it finished — the 2 029.92 MB peak under a 1 536 MB ceiling that was genuinely in force. `resource_budget` now arms a ceiling, samples it inside the parse loop (throttled to one footprint sample per 0.5 s; a per-file call costs one clock read) and raises a typed `CeilingExceeded` that the CLI renders as the same `MEMORY_TOO_LARGE` refusal the boundary check emits. Deliberately not a sampler thread: a thread can see the crossing but cannot unwind the main thread anywhere safe. `_meta.memory.enforcement` publishes when the ceiling is checked. |
47
+ | `BUG-10` | High | **fixed** | The zero-endpoint answer had four named detections and everything else fell through to "no recognized routing construct was found", which reads as *no HTTP surface*. `unmodeled_routing_evidence` now publishes files declaring an annotation whose argument is a path-like string literal that produced no endpoint — a rule that names no framework, per this repository's vendor-agnostic convention. Measured: `struts` reports 4 files under `@Action`/`@Namespace` where the answer used to say nothing was found; `spring-petclinic` reports none. |
48
+ | `NEW-02` / `BUG-05` | High | **fixed** | Overloaded handlers all inherited the final overload's line because the symbol index is keyed `Class#method` with no parameter types. The extractor has no signature to disambiguate with, so the row's other branch applies: `overload_count`, `anchor_ambiguity` and `anchor_candidate_lines` are published per entry, with `anchor_ambiguity_note` once beside the list, and an unambiguous anchor carries none of it. |
49
+ | `BUG-13` | High | **fixed** | `selftest .` over nested repositories warned and then ran the ledger against the workspace — the shape behind `selftest` falling from 9/12 to 1/12 rows inside one 120 s deadline. The implicit `.` now refuses with `INVALID_USAGE` before any row runs, naming the count, with `--allow-nested` for a caller who means the workspace. |
50
+ | `BUG-16` | High | **fixed** | The context cache's caps (30-day TTL, 256 entries) were real and lived inside `_gc`, so ~1.7 GB of growth had no readable contract. `cache.eviction_contract()` and `context_cache.eviction_contract()` are now the one authority each `_gc` reads, and `cache status` publishes them per store in JSON and text. The parse store already published its budget; its block projects those same figures rather than measuring them again. |
51
+ | `ALT-08` (arithmetic half) | Low | **fixed** | `_truncation_summary.final_size_bytes` was measured before the summary and `_budget_note` were attached, so the one part of the payload the accounting omitted was the accounting. It is now measured over the assembled object and republished to a fixed point, with `final_size_basis` written before the fixed point is sought and `content_size_bytes` keeping the trimmed-content figure under a name that says which it is. |
52
+ | `ALT-25` (`impact-chain` audit clock) | Low | **fixed** | `metadata.audit_time_ms` was always ~0.0 on the default path and the 5.9.25 round read it as a security phase skipped by `ALT-03`. It is not: `include_findings` is opt-in, so the clock measured the elapsed time of *not* running an audit. It is `null` when `findings_status` is `not_computed`, and `query_time_basis` states the rule. |
53
+ | `retrieve security-surface --target` | Low | **fixed** | Four rounds tried it and got "No such option: --target". The intent is repository-wide by construction, and the refusal now says so and names `security-chain --target`, derived from `intent_router.ANCHOR_KINDS` rather than a second list. |
54
+ | `ask --help` latency | Low | **open, not reproduced here** | 0.41–0.44 s over three runs on the engineering host against 14.4–14.7 s in the field under confirmed contention. Re-measure with `_meta.concurrency` attached before treating it as its own row. |
55
+ | `ALT-07` (`remediate` hang), `ALT-10` (`onboard`) | Medium | **open, unchanged** | Both share the erratic signature `R-01` now instruments. No separate mechanism found in source this round; neither reproduces on the engineering host. |
56
+ | `BUG-02`, `BUG-12` | Critical maximum | **open, unchanged** | Both need the field's own large repository to reproduce (`rc=127` with no output; multi-gigabyte RSS while building a small answer). `BUG-22`'s sampler bounds the second one's damage without closing it. |
57
+
58
+ ## Current Synchronization — Alternative External Audit `5.9.27`, shared-machine contention confirmed (2026-09-02)
59
+
60
+ **First score uptick in four rounds (3.8 → 4.6/10), with a large
61
+ qualification found mid-session: the audit machine was shared with a second,
62
+ independent `ask` test harness.** The native-Windows auditor found
63
+ `entry-trace.jsonl` (this ledger's own diagnostic, added last round) growing
64
+ with invocations they never launched, against five OSS reference
65
+ repositories under the exact same `timeout N ask ...` harness pattern they
66
+ use, and confirmed a live foreign `ask.exe` process via `Get-CimInstance
67
+ Win32_Process` under their own Windows user. Two of their own `spring-audit`
68
+ hangs overlapped a 525-second window of 34 foreign invocations. Full
69
+ evidence:
70
+ [`AUDIT-2026-09-02-ALTERNATIVE-5.9.27.md`](AUDIT-2026-09-02-ALTERNATIVE-5.9.27.md).
71
+
72
+ **`R-01` (new, highest priority):** if ASK's shared worker pool and cache do
73
+ not isolate concurrent `ask` processes on one machine, contention could
74
+ produce exactly the pattern reported since 5.9.23 — the same command
75
+ sometimes hanging, sometimes returning empty near-instantly, no per-process
76
+ variable distinguishing the two. This is a real, evidenced contributing
77
+ factor, not a proven sole cause (one hang occurred after 20 seconds of no
78
+ foreign activity at all). The open question for engineering: is ASK's worker
79
+ pool designed to tolerate concurrent `ask` invocations on one machine, or
80
+ does it assume exclusive use? If the latter, that is an architectural
81
+ constraint, not a series of unrelated command bugs, and changes where the
82
+ fix belongs.
83
+
84
+ **Entry-point trace progress, confirmed:** the auditor's own invocations —
85
+ including ones that returned empty or hung — now appear in `entry-trace.jsonl`
86
+ (the fix from last round's `R-01`). This confirms `main_entry` is reached on
87
+ the failing runs. `empty_answer_diagnostics.record()` still never fired on
88
+ any observed failure this session, which narrows the search specifically to
89
+ somewhere between `main_entry` and the output-promise guard — a materially
90
+ smaller surface than "somewhere in the process," three rounds ago.
91
+
92
+ | ID | Severity | Status | Evidence and required closure |
93
+ |---|---:|---|---|
94
+ | `R-01` (contention) | Critical maximum | **open, new evidenced hypothesis** | Confirmed shared-machine contention during this audit session (foreign `ask` process, matching harness pattern, overlapping timing with 2 of the auditor's own hangs). Needs an internal answer, not more external symptom-chasing: does the worker pool serialize or isolate concurrent invocations, or assume exclusive use? If it assumes exclusive use, `ALT-01`/`ALT-03`/`NEW-01` may be one architectural defect, not several. |
95
+ | `R-01` (entry trace) | — | **confirmed working, narrows the search** | The auditor's own failing invocations now appear in `entry-trace.jsonl`. `main_entry` is reached; the gap is between there and `_fail_on_missing_required_answer`/`_fail_on_unkept_output_promise`, which still never fire. |
96
+ | `ALT-09` | Critical | **fixed, reconfirmed a third session** | Piped `n` to `cache clear` aborts correctly. |
97
+ | `BUG-15` / `BUG-21` | Low | **fixed, reconfirmed** | No markup leak; `--jobs` range validated. |
98
+ | `R-05` from last round (`selftest` itemization) | High | **fixed, reconfirmed** | `"10 ledger row(s) could not run: ASK-16, B6, E-37, ..."` replaces the prior generic slug. |
99
+ | `onboard` | Medium | **open, still the most erratic single measurement** | 904ms to a full 200s external kill, same invocation, same tree, within one session. Cross-reference against `entry-trace.jsonl` timing before assuming a cold/warm explanation. |
100
+ | `ask --help` latency | Low | **open, persisted a second round** | 14.4-14.7s this session (was 3-4s last round, flagged then as "worth its own row if it persists"). It has persisted. Not verified in isolation from `R-01`'s contention this session. |
101
+
102
+ The remaining rows (`BUG-01`, `BUG-03`, `BUG-06`, `BUG-08` through `BUG-23`,
103
+ `REG-02`, `REG-03`, `NEW-02`) were not relaunched this round for time budget;
104
+ no regression signal against them. `watch`'s ~55s warm-before-first-tick and
105
+ `retrieve security-surface --target` remain unchanged, correctly understood
106
+ as real cost and a known gap respectively, not new defects.
107
+
108
+ **Corroborating data point, unverified in detail:** a separate exchange the
109
+ same session referenced a distinct "External" lineage audit of 5.9.27 scoring
110
+ **8.1/10** on the five OSS reference repositories found in the trace above —
111
+ consistent with the shared-machine hypothesis (two lineages, two repo
112
+ populations, one machine) but not independently confirmed here; the
113
+ transcript fragment available was too corrupted to verify its table in
114
+ detail and is not published as a standalone audit document.
115
+
116
+ ## Current Synchronization — External Audit `5.9.26`, self-contradictory session (2026-09-02)
117
+
118
+ > **Provenance correction, added during the 5.9.27 round.** The
119
+ > native-Windows auditor for 5.9.27 states plainly that the "initial
120
+ > 129-invocation pass ... 0/23 ... 7.4/10" described below does not
121
+ > correspond to anything they ran. The 5.9.27 round independently confirmed
122
+ > the audit machine was shared with a second, concurrent `ask` test harness
123
+ > exercising five OSS reference repositories (`thingsboard`, `mall`,
124
+ > `struts`, `spring-petclinic`, `jhipster-sample-app`) — a repo set and
125
+ > pattern that matches the "External" multi-repository lineage this ledger
126
+ > has tracked separately since 5.9.22 (`AUDIT-2026-09-02-EXTERNAL-5.9.26.md`
127
+ > below, and the 6.3→3.8 scores it reflects). The likely explanation is that
128
+ > the two-pass narrative below mixed observations from two independent,
129
+ > concurrent sessions on one shared machine rather than being one
130
+ > evaluator's self-revision. Left as written below rather than rewritten,
131
+ > with this correction attached, per this ledger's own discipline of
132
+ > recording when it got something wrong rather than quietly fixing the
133
+ > record. See
134
+ > [`AUDIT-2026-09-02-ALTERNATIVE-5.9.27.md`](AUDIT-2026-09-02-ALTERNATIVE-5.9.27.md)
135
+ > for the evidence (`entry-trace.jsonl` cross-contamination, a live foreign
136
+ > process confirmed via `Get-CimInstance Win32_Process`).
137
+
138
+ **The 5.9.26 fix batch did not close the release gate, and new evidence
139
+ narrows the failure to before the guard rather than inside it.** This audit
140
+ round is internally contradictory and both halves are published rather than
141
+ resolved: an initial 129-invocation pass replicating the exact 5.9.25
142
+ methodology found zero empty answers (0/23) and scored 7.4/10; a second,
143
+ larger pass in the same session found zero *successful* answers across 35
144
+ further invocations of the same command family and revised the verdict to
145
+ **3.8/10**, the lowest of four rounds. The auditor's own final position is the
146
+ second number. Full evidence:
147
+ [`AUDIT-2026-09-02-EXTERNAL-5.9.26.md`](AUDIT-2026-09-02-EXTERNAL-5.9.26.md).
148
+
149
+ **New finding, most actionable in four rounds (`R-01`):** across 32 empty
150
+ `exit 0` answers this session, stdout **and** stderr were both empty and no
151
+ `empty_answer_diagnostics` sidecar file exists anywhere on the audited
152
+ machine — the guard that is supposed to write one on every trip did not fire.
153
+ This does not mean the guard added in 5.9.26 is miscalibrated; it means the
154
+ failing runs are not reaching `main_entry`'s wrapper at all, which rules out
155
+ "a caught exception inside command logic" as the mechanism and points instead
156
+ at something that terminates the process before Python's own `try`/`except`
157
+ around `app(prog_name="ask")` ever regains control — an external kill, a
158
+ native crash, or an `os._exit()`-shaped exit somewhere in a dependency. A
159
+ one-line "reached the guard" debug log at the top of `main_entry` would
160
+ confirm this directly without needing native Windows to interpret.
161
+
162
+ Other updates this round: `BUG-17` reconfirmed with a more precise repro
163
+ (no path, a real path, and a nonexistent path are all byte-identical, not
164
+ merely "an extra argument is now accepted"); `ALT-10`/`onboard` got worse (a
165
+ cold run hit the external 200s kill entirely, worse than 5.9.25's 164.9s
166
+ worst case); `ALT-11`/`watch` reconfirmed working, with a correction — the
167
+ 3,342-file audited repository's index warm takes 51.7s before the first
168
+ NDJSON line, real and not a defect, but long enough to still read as hung
169
+ without foreknowledge; and `ask --help` costs 3-4s even warm, which `REG-01`
170
+ never claimed to fix (`REG-01` was specifically the 82-120s hang in a nested
171
+ workspace, now resolved) but is worth its own row if it persists.
172
+
173
+ | ID | Severity | Status | Evidence and required closure |
174
+ |---|---:|---|---|
175
+ | `NEW-01`/`ALT-01`/`ALT-03` | Critical maximum | **open, contradictory session, worst round measured** | 0 of 35 invocations answered in the second pass of this session (vs. 0 of 23 *empty* in the first pass of the same session) — do not average the two into a rate; neither is regime evidence. First round where a single batch of identical `spring-audit . --compact` calls produced both failure shapes (a 200s hang, then a near-instant empty answer) with nothing changed between them. `R-01` above is the actionable lead. |
176
+ | `R-01` | Critical maximum | **instrumented, not yet closed** | The `empty_answer_diagnostics` sidecar added for `ALT-01`/`ALT-03` did not fire in 32 observed empty answers this session — no file, no error envelope. `record_entry_reached` now appends one line to `entry-trace.jsonl` as the very first statement of `main_entry`, argv-gated to `REQUIRED_ANSWER_COMMANDS` so `--help` and other latency-sensitive commands are not charged for it. The next native occurrence answers directly whether the failing run reached this product's own entry point at all, rather than requiring inference from the absence of a failure record. This does not close the row — it is the instrumentation the row asked for, not a fix for whatever is upstream if the trace turns out empty too. |
177
+ | `ALT-10` | Medium | **open, worse** | A cold `onboard .` hit the external 200s kill entirely this session (5.9.25's worst case answered at 164.9s); a second, untouched run answered in 83.5s — still ~120x the 681-780ms historical baseline. Shares `R-01`'s erratic shape, not a simple cold/warm split. |
178
+ | `BUG-17` | Low | **fixed** | `remediation-report` still accepts any `path` value (5.9.24's deliberate CLI-consistency choice, kept — a caller that always passes a repository path needs no special case for the one command with none to say about it), but the payload now carries `path_argument: {given, used: false, note}` whenever one is given, in both JSON and Markdown, whether or not the path exists. `rows.pop("path_argument")` still equals the no-path payload exactly — nothing else changed. |
179
+ | `ALT-09` | Critical | **fixed, reconfirmed** | A piped `n` answer to `ask cache clear .` aborts with `rc=1`, `"Aborted."` on stderr, cache untouched. |
180
+ | `ALT-04` | High | **fixed, reconfirmed** | `impact-chain` gave the same `content_id` across 3 repeated runs again this session. |
181
+ | `BUG-01` | Critical maximum | **fixed, reconfirmed with an independent fixture** | The auditor built their own unbalanced-paren file (not the shipped test fixture) and got the expected `reason: "unbalanced_parens"` gap. The documented residual class (a syntax error that disturbs neither braces nor parens) is unchanged and expected. |
182
+ | `BUG-15` | Low | **fixed, reconfirmed** | 0 Rich-markup occurrences across 3 repeated non-TTY `--help` runs. |
183
+ | `BUG-21` | Low | **fixed, reconfirmed** | `--jobs 0` and `--jobs 999` both rejected with the stated `1<=x<=128` range. |
184
+ | `ALT-11` | Low | **fixed, reconfirmed with a measurement correction** | `watch` emits its `status: "watching"` line at 51.7s on the 3,342-file audited repository — real index-warm cost, not a defect, but long enough that a short observation window (the auditor's own first attempt, 8-20s) reads it as still broken. No further action required by this row; `ALT-10`/onboard shares the same warm-up path and is where the latency itself belongs. |
185
+
186
+ The remaining rows below this section (`BUG-03`, `BUG-09` through `BUG-23`,
187
+ `REG-02`, `REG-03`, `REG-04`, `NEW-02`) were reconfirmed unchanged this round
188
+ or not relaunched for time budget; none regressed. Full detail in the audit
189
+ document.
190
+
17
191
  ## Current Synchronization — Alternative External Audit `5.9.25`, and External Audit `5.9.25` (2026-09-02)
18
192
 
19
193
  **Release `5.9.26` closes eight rows from this round with verified evidence**
@@ -75,7 +249,7 @@ and [`AUDIT-2026-09-02-EXTERNAL-5.9.25.md`](AUDIT-2026-09-02-EXTERNAL-5.9.25.md)
75
249
  | `BUG-01` | Critical maximum | **fixed, mechanism narrowed, not eliminated** | Reproduced directly: a missing `;` or a missing `)` on a method signature both extract a clean class+method symbol pair with balanced braces, so the E-10 brace-only check (`unbalanced_braces`) never fired and the file passed as clean. `repository_ir.py` now also masks and checks paren balance (`unbalanced_parens`), publishing the same `parse_structure` gap for that shape. This is a heuristic extractor without a real Java grammar, so a syntax error that disturbs neither braces nor parens (for example a missing operand with balanced punctuation on both sides) remains undetectable by construction — the row stays open at lower severity for that residual class, not closed. |
76
250
  | `BUG-02` | Critical maximum | **open, P0** | Large `risk` ended `rc=127` with no output or structured failure. Add fail-fast resource/process handling and durable partial checkpoints. |
77
251
  | `BUG-12` | Critical maximum | **open, P0** | Deep `--agent` output construction reaches multi-gigabyte RSS before a small answer. Bound projection while building. |
78
- | `BUG-03` | High | **fixed** | `non_coverage.stack_fit`'s marginal-detection disclosure (`fit: "partial"`, below 10% Spring-bean density) has existed since AUD-596-D13 but was unreachable from `risk` and `posture`: neither call site passed `spring_footprint`, so `java_files` stayed 0 and the branch that reads it could never fire regardless of the real ratio. Both now compute it the same way `spring-audit`'s own call already did (`sum(bean.spring for bean in graph.beans.values())` over `len(cir.files)`); verified end to end on a 1-bean/16-file fixture. |
252
+ | `BUG-03` | High | **fixed** | `non_coverage.stack_fit`'s marginal-detection disclosure (`fit: "partial"`, below 10% Spring-bean density) has existed since AUD-596-D13 but was unreachable from `risk` and `posture`: neither call site passed `spring_footprint`, so `java_files` stayed 0 and the branch that reads it could never fire regardless of the real ratio. Both now compute it the same way `spring-audit`'s own call already did (`sum(bean.spring for bean in graph.beans.values())` over `len(cir.files)`); verified end to end on a 1-bean/16-file fixture. The 5.9.26 re-audit's own index table still lists this row `abierto` for both 5.9.25 and 5.9.26 without naming which command was tested or a repro that contradicts the fixture above; the fix stands on the passing regression tests until a command-specific repro says otherwise. |
79
253
  | `BUG-04` | High | **open, P1** | `selftest` publishes `complete=true` with five error rows. Derive bounds from actual results. |
80
254
  | `BUG-05` | High | **open, P1** | Overloaded endpoint handlers can receive the wrong declaration line. Resolve by mapping annotation or publish ambiguity. |
81
255
  | `BUG-07` | Closed 5.9.23, verification gap | Compact JVM inventory is bounded, but the audit notes no selftest row protects the correction. Add release-battery coverage before treating it as durable closure. |
@@ -279,7 +279,7 @@ pipx install sourcecode # isolated install, no venv needed
279
279
 
280
280
  # Verify
281
281
  ask version
282
- # ask 5.9.26
282
+ # ask 5.9.28
283
283
  ```
284
284
 
285
285
  Requires Python 3.9+.
sourcecode/cache.py CHANGED
@@ -151,6 +151,49 @@ _DEFAULT_KEEP_COMMITS: int = 5
151
151
  _DEFAULT_MAX_CORES: int = 20
152
152
  _DEFAULT_MAX_SIZE_MB: int = 50
153
153
 
154
+
155
+ def eviction_contract() -> "dict[str, Any]":
156
+ """The three caps this store evicts under, read once (BUG-16).
157
+
158
+ `_gc` used to read the environment inline, so the only way to learn what
159
+ bounded the cache was to read the sweep. A consumer asking `cache status`
160
+ what will be evicted, and when, now gets the same numbers the sweep uses.
161
+ """
162
+ def _int(name: str, default: int) -> int:
163
+ try:
164
+ return int(os.environ.get(name, default))
165
+ except (TypeError, ValueError):
166
+ return default
167
+
168
+ keep = _int("SOURCECODE_CACHE_KEEP_COMMITS", _DEFAULT_KEEP_COMMITS)
169
+ max_cores = _int("SOURCECODE_CACHE_MAX_CORES", _DEFAULT_MAX_CORES)
170
+ max_size_mb = _int("SOURCECODE_CACHE_MAX_SIZE_MB", _DEFAULT_MAX_SIZE_MB)
171
+ clauses = []
172
+ if keep > 0:
173
+ clauses.append(f"the last {keep} distinct commits are kept")
174
+ if max_cores > 0:
175
+ clauses.append(f"at most {max_cores} cores, least recently used evicted first")
176
+ if max_size_mb > 0:
177
+ clauses.append(f"the store is trimmed toward {max_size_mb} MB")
178
+ return {
179
+ "keep_commits": keep,
180
+ "max_cores": max_cores,
181
+ "max_size_mb": max_size_mb,
182
+ "keep_commits_env": "SOURCECODE_CACHE_KEEP_COMMITS",
183
+ "max_cores_env": "SOURCECODE_CACHE_MAX_CORES",
184
+ "max_size_env": "SOURCECODE_CACHE_MAX_SIZE_MB",
185
+ "policy": (
186
+ "; ".join(clauses) + ", swept after each write"
187
+ if clauses else
188
+ "no eviction — every cap was set to 0 deliberately"
189
+ ),
190
+ "deferred_while_shared": (
191
+ "A sweep is skipped while another `ask` invocation is live on this "
192
+ "machine (R-01): eviction would delete the working set a concurrent "
193
+ "run is reading."
194
+ ),
195
+ }
196
+
154
197
  #: Windows hardening for _atomic_write: os.replace can raise PermissionError
155
198
  #: (WinError 5/32) when an antivirus scanner, search indexer, or concurrent
156
199
  #: reader transiently holds the destination open. Retry briefly to ride it out.
@@ -501,27 +544,45 @@ def _store_breakdown(repo_root: Path, core_bytes: int) -> "dict[str, Any]":
501
544
  "bytes": core_bytes,
502
545
  "scope": "this repository",
503
546
  "holds": "core snapshots, rendered views and their CAS blobs",
547
+ "eviction": eviction_contract(),
504
548
  },
505
549
  }
506
550
  try:
507
551
  from sourcecode.context_cache import ContextCache # noqa: PLC0415
508
552
 
509
553
  ctx = ContextCache.for_repo(repo_root).stats()
554
+ from sourcecode.context_cache import ( # noqa: PLC0415
555
+ eviction_contract as _ctx_eviction,
556
+ )
557
+
510
558
  out["shared_cir"] = {
511
559
  "cache_dir": ctx["cache_dir"],
512
560
  "entries": ctx["contexts"],
513
561
  "bytes": ctx["bytes_stored"],
514
562
  "scope": "this repository",
515
563
  "holds": "the shared Canonical IR that explain/impact/posture reuse",
564
+ # BUG-16: measured at ~1.7 GB in two rounds with nothing published
565
+ # about what bounds it. The caps were real and lived inside `_gc`.
566
+ "eviction": _ctx_eviction(),
516
567
  }
517
568
  except Exception:
518
569
  out["shared_cir"] = {"available": False}
519
570
  try:
520
571
  from sourcecode import parse_cache as _pc # noqa: PLC0415
521
572
 
573
+ _parse_stats = _pc.store_stats()
522
574
  out["parse"] = {
523
- **_pc.store_stats(),
575
+ **_parse_stats,
524
576
  "holds": "per-file parses, content-addressed across every repository",
577
+ # BUG-16: this store already published its budget under its own
578
+ # names; the block below is that same answer projected under the
579
+ # name every store now answers to, never a second measurement.
580
+ "eviction": {
581
+ "policy": _parse_stats.get("policy"),
582
+ "budget_mb": _parse_stats.get("budget_mb"),
583
+ "budget_env": _parse_stats.get("budget_env"),
584
+ "sweep_interval_mb": _parse_stats.get("sweep_interval_mb"),
585
+ },
525
586
  }
526
587
  except Exception:
527
588
  out["parse"] = {"available": False}
@@ -1080,9 +1141,25 @@ def _gc(cache_d: Path) -> None:
1080
1141
  oldest core+snapshot files until under budget.
1081
1142
  Views and CAS blobs are swept after each pass.
1082
1143
  """
1083
- keep = int(os.environ.get("SOURCECODE_CACHE_KEEP_COMMITS", _DEFAULT_KEEP_COMMITS))
1084
- max_cores = int(os.environ.get("SOURCECODE_CACHE_MAX_CORES", _DEFAULT_MAX_CORES))
1085
- max_size_bytes = int(os.environ.get("SOURCECODE_CACHE_MAX_SIZE_MB", _DEFAULT_MAX_SIZE_MB)) * 1024 * 1024
1144
+ # R-01 (5.9.27): eviction is the one cache operation a concurrent peer can
1145
+ # be hurt by. Writes are unique-temp + `os.replace` and every read is
1146
+ # guarded, so a peer degrades to a cache miss at worst — but `_gc` deletes
1147
+ # the working set a peer may be halfway through reading, turning its warm
1148
+ # run into a cold one for reasons nothing in its payload could name. A
1149
+ # deferred sweep costs disk until the machine is quiet; it is bounded by
1150
+ # the same caps on the next single-tenant run.
1151
+ try:
1152
+ from sourcecode import concurrency as _concurrency
1153
+
1154
+ if _concurrency.peers() > 0:
1155
+ return
1156
+ except Exception:
1157
+ pass
1158
+
1159
+ _contract = eviction_contract()
1160
+ keep = int(_contract["keep_commits"])
1161
+ max_cores = int(_contract["max_cores"])
1162
+ max_size_bytes = int(_contract["max_size_mb"]) * 1024 * 1024
1086
1163
 
1087
1164
  try:
1088
1165
  all_snapshots = list(cache_d.glob("snapshot-*.json.gz"))