sourcecode 5.9.10__py3-none-any.whl → 5.9.11__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.
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.10"
7
+ __version__ = "5.9.11"
@@ -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 = '3d7178f2f1b249e6821bcd7fdd80046405476724'
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'}, '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'}, '`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
- CLOSURE_COVERAGE = {'closed_rows': 154, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 63, 'unknown': 0}
3
+ BUILD_COMMIT = '443139a89c4a318cca61eba18eb95a291255d75d'
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'}, '`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
+ CLOSURE_COVERAGE = {'closed_rows': 161, 'present': 91, 'absent': 0, 'unresolved': 0, 'uncited': 70, 'unknown': 0}
@@ -150,6 +150,83 @@ Spring detection controls, baseline safety and the prior `/dev/null` fix matrix.
150
150
  intermittent endpoint-security retrieval issue had 0/18 failures in this pass and stays
151
151
  under observation because its historical rate is about 2% over 51 runs.
152
152
 
153
+ **External audit intake `AUD-616`, re-audit of release 5.9.10 — 2026-08-28.** The scope was
154
+ again restricted to `testing/`, with direct re-verification of all twelve `AUD-615` items, a
155
+ robustness sweep over six previously unaudited repositories and five parallel forks, each
156
+ with its own scratch directory — the methodology control `AUD-615` required is now applied.
157
+ The report publishes three headline numbers (8.5 provisional, 8.0 corrected, 8.1 on a
158
+ per-axis rubric); **the governing score is the conservative 8.0/10**, and as before the rows
159
+ below record evidence, not a headline. The installed artefact self-declares `"stale": true`;
160
+ the auditor continued against it deliberately, so every timing is a property of that build.
161
+ No target-repository writes, commits or Java-file changes were reported.
162
+
163
+ The governing item of this round is the `tutorials` trend: five consecutive releases, each
164
+ slower than the last, with no measurement out of order. It is escalated to critical and
165
+ reclassified from an availability observation to a sustained performance regression.
166
+
167
+ **Verdict on the round (5.9.11).** Seven of the nine rows are closed, two are partially
168
+ closed and one — `WIN-PROC` — is carried forward as not actionable from a Darwin checkout.
169
+ The two critical rows are the two partial ones, and both for the same reason, stated plainly
170
+ because it changes what a future round should measure: **neither timing symptom reproduces
171
+ in this checkout.** Root `--compact` on `tutorials` completes in 54.9 s, exit 0, 15 KB, and
172
+ `audit-report --format markdown` on `thingsboard` ran 2/2 clean at 39.3 s and 26.8 s. The
173
+ 4m00→7m00 trend and the four-round non-completion are properties of the auditor's build and
174
+ platform, and re-measurement there is the outstanding work on both rows.
175
+
176
+ What was done instead of accepting or dismissing them was to attribute the cost and remove
177
+ the silence. The cost is now published per phase against file count, and two mechanisms
178
+ behind it are closed with a measured **×1.69** on the governing subject — detection 30.3 s →
179
+ 9.2 s, total 54.9 s → 32.5 s, payload byte-identical. The silence is closed at both ends: the
180
+ root analysis stops at `ASK_MAX_ANALYSIS_SECONDS` and returns a structured partial envelope
181
+ with a gate verdict instead of zero bytes at exit 124, and the delayed liveness warning names
182
+ the phase, its counts and whether it has changed — the one signal that separates a blocked
183
+ run from a slow one, and the one four consecutive rounds did not have.
184
+
185
+ Three findings of this round belong to the checkout rather than to the report, and are
186
+ recorded so they are not re-derived. `AUD-616-P`'s race did not reproduce serialized, and the
187
+ double miss reproduced here first was **our own concurrency** — the same contamination
188
+ `AUD-615` had to correct for; a deterministic mechanism with the identical symptom was found
189
+ underneath it and fixed. `AUD-616-JUNIT4-SELF`'s filed remedy would not have closed the row —
190
+ JUnit4 has three `main()`-bearing entry points, so "no entry point" was the wrong condition.
191
+ And the root analysis joining the bounded tier exposed three batteries comparing the two
192
+ command-name vocabularies as raw strings, or walking only to command leaves; each now goes
193
+ through the authority that already reconciles them.
194
+
195
+ | ID | Severity | Status | Evidence and required closure |
196
+ |---|---:|---|---|
197
+ | AUD-616-TUTORIALS | Critical / sustained regression | partially closed 5.9.11 — perf attributed and bounded; the reported trend does not reproduce here | **Not reproducible as filed on this platform.** Root `--compact` on `tutorials` (24,073 Java files) completes in 54.9 s, exit 0, 15 KB, four serialized cold runs (54.9 / 52.7 / 53.5 / 57.9, max/min 1.10). The report's 4m00→7m00 with exit 124 is a property of the auditor's build and platform, not of this checkout. What was verified and fixed instead is the cost and the silence. **Cost, attributed per phase against file count** (`SOURCECODE_PERF`): discovery 3.5 s, detection 30.3 s, analysis 13.4 s, serialize 6.0 s. Two mechanisms found by cProfile and closed: `manifest_cache._key()` ran fourteen `rglob` walks per call and was called by both `load()` and `save()` for each of 246 modules (1.5 M `posix.scandir` calls, 26 s) — now one `os.walk`, memoised per root, digest unchanged and asserted against the fourteen calls it replaces; and `TypeClassifier` walked the file tree seven times per `enrich` × 246 modules — now once, threaded. **Measured: detection 30.3 → 9.2 s, total 54.9 → 32.5 s (×1.69), payload byte-identical, max/min 1.003.** **Silence, closed:** the root analysis is a `PhasedRun` over the four `perf` seams, so `ASK_MAX_ANALYSIS_SECONDS` bounds it and a spent budget emits the `partial-answer-v1` envelope — phases completed and pending, elapsed, budget and its provenance — instead of zero bytes. It declares `--ci`/`--allow-partial` and exits 75 on a cut gated run. **Still open:** a published repository-size envelope (the scale contract), and re-measurement on the auditor's platform. |
198
+ | AUD-616-L | Critical / regression | partially closed 5.9.11 — stage-level progress shipped; the unbounded run does not reproduce | **Not reproducible as filed here.** `audit-report --format markdown` on `thingsboard` ran 2/2 clean: exit 0, 3,662 bytes both times, 39.3 s and 26.8 s — consistent with the audit's own isolated fork (3m1.4s, 1m49.7s, 3,701 bytes) and nowhere near a timeout. The **bounded structured failure** the row asks for is already shipped and verified: the command is a `PhasedRun`, and under `ASK_MAX_ANALYSIS_SECONDS=1` it returns a report declaring `Status: partial` and `Total defects: 0 (floor — this run was cut short)`. **The heartbeat half was a real defect and is fixed.** The delayed warning read `run has exceeded 5.0m; still working` and repeated that sentence unchanged for the life of the run — identical at 60 s and 420 s, which is why four rounds could not separate a blocked run from a slow one: a blocked process is also still alive. It now names the phase, the stage and its counts, how long the run has been in that phase, and whether the phase changed since the previous warning. Writing it surfaced a second defect: the first version read `_stage_t0`, which only restarts on a *counted* stage change, and published `parsing manifests for 21.1s` about a phase entered at 3 s — the run's elapsed time under a label claiming to be the phase's. A phase clock now exists separately from the counted-stage clock `_eta_seconds` uses. **Still open:** reproduction under deliberate contention on the auditor's platform. |
199
+ | AUD-616-KOTLIN | Medium / confident falsehood | closed 5.9.11 | Reproduced exactly, then closed at both ends. `JvmExtDetector` also admits on source population now, with the sibling `.java` count as the bar rather than an absolute number, so a Kotlin repository on the Groovy DSL — or with no root build file, which is `ktor-samples` — surfaces a Kotlin stack, while a Java monolith carrying three `.kt` tests does not. Admission by source publishes `detection_method: heuristic`, no `manifests` and no `package_manager`: a build file was not read, so nothing only a build file attests is claimed. The real authority for `confidence` was `TypeClassifier._enrich_stack`, which recomputes what the detector published from a score summing *declarations* and gives the source population one point whether there are two files or twenty thousand; both ends are corrected there, once, for every stack with a source extension — zero source files caps at `low` with a `no_source_evidence` signal, and owning the largest source population against a competing manifest-backed claim floors at `medium`. `ktor-samples` now reports `kotlin` primary at `medium`, `java` at `low`, and headlines "Kotlin library / package". Controls re-measured unchanged: `spring-security`, `shenyu`, `thingsboard` keep `java` primary at `high`. |
200
+ | AUD-616-O | Medium | closed 5.9.11 | `--output FILE` moves ahead of the format substitutions, and each substitution now states in its own text that it re-runs the command and that its cost on this repository is unmeasured. The module's ordering rule is unchanged and was not the defect; the reading of "the same answer" was. A bounding flag and `--output FILE` keep the rendering that has already run to completion here — that run is why the refusal has a size to report at all — while a different `--format` is a different composition this module has measured nothing about. The bypass stays last, the file destination is untouched, and the population is still the intersection of `FORMAT_REGISTRY` with the command's declared options, so nothing branches on a command name. |
201
+ | AUD-616-P | Low-medium / investigation | closed 5.9.11 — race not reproduced, a deterministic mechanism with the same symptom fixed | **The race does not reproduce serialized.** On `tutorials`, store cleared first, four sequential runs: 82.1 s cold and `false`, then 1.58 / 1.48 / 1.47 s, all `true`. A double miss was reproduced here first and then attributed to two `ask` processes analysing the repository at once — runs against one repository share `~/.sourcecode/cache/<repo_id>/` whatever scratch directory they are given, so per-fork scratch isolation does not separate them. **A deterministic mechanism with the same symptom did reproduce and is fixed.** The GC's size pass sorted candidates oldest-first and could walk to the end of that list, and the end of it is the entry the calling run has just written: a core larger than the whole store budget was written and deleted inside one process, on every run, for ever. It is reachable at this scale rather than hypothetically — `tutorials` stores a 9.4 MB core against a 50 MB default. The newest core is no longer a candidate; everything older still is. When the newest entry alone exceeds the budget the store stays over budget, and `cache.status()` publishes `size_budget_mb`, `largest_core_bytes` and an `over_budget_reason` naming the variable to raise. |
202
+ | AUD-616-JUNIT4-SELF | Low-medium / classification | closed 5.9.11 | **One correction to the remedy as filed:** it proposed classifying single-module JVM repositories "with no entry point", and JUnit4 has three — `TestRunner`, `JUnitCore` and `Version` each carry a `main()` — so that condition would have left the row open. The multi-module requirement was what was doing the wrong work: module count describes a build layout, not whether a repository is a library, and it was in the branch only because the row that introduced it (JobRunr) happened to be multi-module. By the time control reaches that point every stronger reading has been ruled out by the branches above, so what is left is a JVM repository with sources and no application-defining framework — the same inference the multi-module branch already makes. Measured over thirteen JVM repositories before and after: three change and all three are libraries (`junit4`, `mybatis-3`, `Mybatis-PageHelper`, each `unknown` → `library`); the other ten are byte-identical. A repository with no detected stack still publishes no type, which is the `shopizer` case confirmed correct by design. |
203
+ | AUD-616-WIN-PROC | Medium | open, carried forward — not actionable on this platform | Unchanged from `AUD-614-WIN-PROC` and `AUD-615-WIN-PROC`. The subject is native Windows process-tree teardown and the auditor's own sample was declared inconclusive (contaminated by unrelated sessions). Nothing in this round can be reproduced or refuted from a Darwin checkout, and shipping a Job Objects path that no test here can exercise would be a change made blind. It needs a native Windows/Git Bash run, and the narrowed reading stands: late cleanup and non-response to the parent's termination signal, not a permanent leak. |
204
+ | AUD-616-INTENT-TIE-COV | Low / coverage | closed 5.9.11 | Behaviour was already correct and nothing held it there. Both sides are covered now, with the tying questions derived from the shipped vocabulary rather than invented: `central classes deployment shape` ties two repository-wide intents at 2.8284, and `what endpoints break cross module impact` ties two target-required ones at 5.1962. Each refuses, names both candidates and the reason, offers no command, and reports no intent — so no consumer reads the rest of the payload as describing a losing candidate. A first test asserts the fixtures still tie, so a vocabulary change that destroys the tie fails the test depending on it instead of passing vacuously; a second asserts both candidates really are of one requirement class, without which the battery would silently become another copy of the rows it complements. |
205
+ | AUD-616-E-PROV | Low / ledger hygiene | closed 5.9.11 | **The command is `ask impact-chain <EventClass> <repo> --type events`** — the event-topology query, a third sibling of `impact` and `impact-chain` and the one the re-audit could not identify. `EventTopologyResult` declares `risk_level: str = "low"` as its dataclass default and the `not_found` branch returned the result without overriding it, so a class absent from the CIR was published as a resolved low-risk answer while its two siblings said `unknown`. The value was corrected in 5.9.9; what nothing covered is the **parity** — three commands, three code paths, one question, and no test that fails when one drifts again. A battery now runs all three through the CLI on a repository, the way an external evaluator would, and asserts they agree. Re-verified: all three publish `resolution: not_found` and `risk_level: unknown`. |
206
+ | AUD-616-F, -RISK-L0, -SELFTEST-TIMEOUT, -INTENT-TIE, -DRIFT, -COLD-COMPACT, -E, -07 | — | closed 5.9.9 (reverified in 5.9.10) | 6/6 correct exits with stdout to `/dev/null`; `risk --limit 0` in 2.8 s with `analysis_skipped: true`; `selftest --timeout` exit 75 with a 7.2 KB structured diagnostic; intent behaviour coherent with the reclassification; `drift` default profile normalized; `cold-start --compact` size stable across repeated runs. No regression in any of them. |
207
+
208
+ **Size-datum note.** The report records `cold-start --compact` as 14,959-14,960 bytes in one
209
+ block and 88,265-88,266 bytes in another. Different repositories; no reconciliation is
210
+ claimed, and the one-byte delta is the redirection newline, not a payload change.
211
+
212
+ **Robustness sweep.** Five of six new repositories behaved correctly and open no row:
213
+ `spock` (674 Java + 607 Groovy, languages not confused), `Chronicle-Queue` (CLI tool,
214
+ `spring_detected: false`), `javaee7-samples` (classic Java EE), `camel-examples` (474
215
+ dependencies, no crash) and `shopizer-v1.1.5` (no root `pom.xml`, `project_type: null` with
216
+ an explicit explanation — correct by design, the type genuinely cannot be inferred). The
217
+ sixth, `ktor-samples`, is the first Kotlin subject in the series and opened `AUD-616-KOTLIN`.
218
+
219
+ **Also stable.** `pack gate --since develop` measured 208.6 s, consistent with the 234 s
220
+ recovery in 5.9.9: the four-round upward trend on that command remains broken. The full
221
+ earlier closure set was re-exercised without a single regression.
222
+
223
+ **Commercially**, the band stays `$25-40/developer/month` with the standing no-large-scale
224
+ CI-gating condition. The `TUTORIALS` trend strengthens that condition rather than relaxing
225
+ it: a degradation visible across five consecutive releases is precisely the evidence an
226
+ annual reliability commitment cannot be sold against. Full triage is in
227
+ [`AUDIT-2026-08-28-EXTERNAL-5.9.10.md`](AUDIT-2026-08-28-EXTERNAL-5.9.10.md).
228
+
229
+
153
230
  **Commercial boundary.** The corrected assessment is `$25-40/developer/month`, not the
154
231
  initial `$45-58` headline. Do not market enterprise-scale support or production CI
155
232
  gating for repositories above roughly 3,000 files until the four critical/high
@@ -46,23 +46,40 @@ 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.10, `validation` enters through that shared CIR and `data-exposure` reuses one semantic
49
+ 5.9.11, `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.10, with release-artifact verification
55
+ the two pack fixes are included in release 5.9.11, with release-artifact verification
56
56
  remaining for detached execution and pack evidence.
57
57
 
58
- ### Reliability contracts in 5.9.10
58
+ ### Reliability contracts in 5.9.11
59
+
60
+ **A deadline the root analysis honours.** `ASK_MAX_ANALYSIS_SECONDS=<seconds>` now
61
+ stops `ask <repo>` at a measured phase boundary. A run that spends its budget
62
+ returns a structured `partial-answer-v1` payload naming the phases that completed,
63
+ the phases that did not, the elapsed time and the budget that decided it — not an
64
+ empty stdout. Counts in a partial answer are floors over the completed phases
65
+ only. Add `--ci` to make a cut run fail with exit 75, and `--allow-partial` to
66
+ accept one on purpose; both are published back in the payload as `exit_code`
67
+ beside `gate_exit_code`, so a pipeline can tell a chosen zero from a defaulted one.
68
+
69
+ **Progress that distinguishes slow from blocked.** On a non-interactive run, the
70
+ delayed liveness warning names the phase, the stage inside it and its counts, how
71
+ long the run has been in that phase, and whether the phase has changed since the
72
+ previous warning. Two warnings naming different phases are progress; two naming
73
+ the same phase are a stretch that is not reporting.
59
74
 
60
75
  **Known availability limits.** Do not place `ask audit-report . --format markdown`
61
76
  or the root compact command behind a strict timeout on repositories above roughly
62
77
  3,000 files. Markdown audit-report delivery is still contention-sensitive; for a
63
78
  supervised or CI workflow, save `risk` first and use `audit-report --from-risk`,
64
- prefer JSON/YAML, and retain progress output. The current closure status is in the
65
- [development roadmap](DEVELOPMENT-ROADMAP.md#aud-615-re-audit-release-599).
79
+ prefer JSON/YAML, and retain progress output. Both limits are held open
80
+ deliberately: neither reproduces on the engineering platform, so neither is closed
81
+ on measurements taken there. The current closure status is in the
82
+ [development roadmap](DEVELOPMENT-ROADMAP.md#external-audit-intake--aud-616-re-audit-release-5910).
66
83
 
67
84
  Invalid analysis inputs fail before expensive work and use the structured
68
85
  `INVALID_INPUT` envelope: a missing `--consumer` repository, a malformed CVE
@@ -243,7 +260,7 @@ pipx install sourcecode # isolated install, no venv needed
243
260
 
244
261
  # Verify
245
262
  ask version
246
- # ask 5.9.10
263
+ # ask 5.9.11
247
264
  ```
248
265
 
249
266
  Requires Python 3.9+.
@@ -111,6 +111,17 @@ def _gaps(payload: "dict[str, Any]") -> "list[dict[str, Any]]":
111
111
  "field": "_partial",
112
112
  "what": "the run stopped before every phase completed",
113
113
  })
114
+ elif payload.get("partial") is True:
115
+ # AUD-616-TUTORIALS. A cut run can carry its declaration in two places: a
116
+ # `_partial` block attached to an answer that was still produced, and — for
117
+ # a command that has no partial answer to attach it to — the whole payload
118
+ # being the `partial-answer-v1` envelope. Only the first was recognised, so
119
+ # a bounded deadline failure published `complete: true` beside
120
+ # `"partial": true` in the same object.
121
+ found.append({
122
+ "field": "partial",
123
+ "what": "the run stopped before every phase completed",
124
+ })
114
125
  return found
115
126
 
116
127
 
sourcecode/cache.py CHANGED
@@ -402,6 +402,7 @@ def status(repo_root: Path) -> dict[str, Any]:
402
402
  total_bytes = sum(f.stat().st_size for f in all_files if f.exists())
403
403
  return {
404
404
  "cache_dir": str(cache_d),
405
+ **_size_budget_fields(cores, snapshots, cas_blobs),
405
406
  "cached_cores": len(cores),
406
407
  "snapshots": len(snapshots),
407
408
  "views": len(views),
@@ -419,6 +420,51 @@ def status(repo_root: Path) -> dict[str, Any]:
419
420
  }
420
421
 
421
422
 
423
+ def _size_budget_fields(
424
+ cores: "list[Path]", snapshots: "list[Path]", cas_blobs: "list[Path]"
425
+ ) -> "dict[str, Any]":
426
+ """What the store's size budget is, and whether one entry cannot fit under it.
427
+
428
+ AUD-616-P. The GC no longer evicts the entry a run has just written, so a
429
+ repository whose core is larger than `SOURCECODE_CACHE_MAX_SIZE_MB` keeps its
430
+ cache and the store sits over budget. That is the honest outcome and it must
431
+ not be silent: the alternative the code used to take was to delete the entry
432
+ and leave a second identical run reporting `served_from_cache: false` with no
433
+ explanation anywhere.
434
+ """
435
+ try:
436
+ budget_mb = int(
437
+ os.environ.get("SOURCECODE_CACHE_MAX_SIZE_MB", _DEFAULT_MAX_SIZE_MB)
438
+ )
439
+ except ValueError:
440
+ budget_mb = _DEFAULT_MAX_SIZE_MB
441
+ counted = [p for p in (*cores, *snapshots, *cas_blobs) if p.exists()]
442
+ counted_bytes = sum(p.stat().st_size for p in counted)
443
+ largest = max((p.stat().st_size for p in cores if p.exists()), default=0)
444
+ budget_bytes = budget_mb * 1024 * 1024
445
+ reason: "Optional[str]" = None
446
+ if budget_bytes > 0 and counted_bytes > budget_bytes:
447
+ if largest > budget_bytes:
448
+ reason = (
449
+ f"one cached analysis is {round(largest / (1024 * 1024), 2)} MB, "
450
+ f"larger than the whole {budget_mb} MB store budget. It is kept "
451
+ f"rather than evicted, so this repository is still served from "
452
+ f"cache; raise SOURCECODE_CACHE_MAX_SIZE_MB to bring the store "
453
+ f"back under budget."
454
+ )
455
+ else:
456
+ reason = (
457
+ f"the store holds {round(counted_bytes / (1024 * 1024), 2)} MB "
458
+ f"against a {budget_mb} MB budget; older entries are evicted on "
459
+ f"the next write."
460
+ )
461
+ return {
462
+ "size_budget_mb": budget_mb,
463
+ "largest_core_bytes": largest,
464
+ "over_budget_reason": reason,
465
+ }
466
+
467
+
422
468
  #: E-37. Which environment variable relocates which store. Two exist, they cover
423
469
  #: different stores, and the difference is invisible in a payload that prints a
424
470
  #: path and no variable — so an operator who moves or empties "the cache" moves
@@ -1058,9 +1104,31 @@ def _gc(cache_d: Path) -> None:
1058
1104
  if total > max_size_bytes:
1059
1105
  # Sort oldest-first; evict core+snapshot files until under budget
1060
1106
  size_candidates.sort(key=lambda p: p.stat().st_mtime)
1107
+ # AUD-616-P. The loop below used to be able to reach the end of that
1108
+ # list, and the end of that list is the entry the run that called
1109
+ # this GC has just written. A single core larger than the whole
1110
+ # budget was therefore stored and deleted again inside one process,
1111
+ # on every run, for ever: the repository could never be served from
1112
+ # cache, `served_from_cache` stayed `false` on a second identical
1113
+ # run, and nothing anywhere said why. `tutorials` stores a 9.4 MB
1114
+ # core against a 50 MB default, so six distinct tree states of one
1115
+ # large repository are enough to reach it.
1116
+ #
1117
+ # Eviction exists to bound a store, not to undo the work of the
1118
+ # process performing it. The newest core is never a candidate; when
1119
+ # it alone exceeds the budget the store stays over budget, which is
1120
+ # a fact `status()` publishes rather than a state the GC destroys
1121
+ # answers to hide.
1122
+ _newest_core = max(
1123
+ (p for p in size_candidates if p.name.startswith("core-")),
1124
+ key=lambda p: p.stat().st_mtime,
1125
+ default=None,
1126
+ )
1061
1127
  for evict in size_candidates:
1062
1128
  if evict.name.startswith("view-"):
1063
1129
  continue
1130
+ if _newest_core is not None and evict == _newest_core:
1131
+ continue
1064
1132
  total -= evict.stat().st_size if evict.exists() else 0
1065
1133
  _safe_unlink(evict)
1066
1134
  surviving = [p for p in surviving if p != evict]
sourcecode/classifier.py CHANGED
@@ -145,21 +145,47 @@ class TypeClassifier:
145
145
  stacks: Sequence[StackDetection],
146
146
  entry_points: Sequence[EntryPoint],
147
147
  ) -> tuple[list[StackDetection], str | None]:
148
- enriched = [self._enrich_stack(file_tree, stack, entry_points) for stack in stacks]
149
- project_type = self._classify_project_type(file_tree, enriched, entry_points)
148
+ # AUD-616-TUTORIALS. One walk of the tree, threaded through every helper
149
+ # below. `flatten_file_tree` rebuilds the whole path list on each call and
150
+ # this class called it seven times per `enrich` — once for the primary-stack
151
+ # authority, once per stack for the extension count, and once each inside
152
+ # `_classify_project_type`, `_is_multi_module`,
153
+ # `_localized_adapter_frameworks` and `_api_is_center_of_gravity`. On a
154
+ # monorepo `enrich` itself runs once per detected module (247 times on
155
+ # `tutorials`), so the walks multiply: the profile attributes 22.9 s to
156
+ # `_count_extension_hits` alone and 21.5 s to `flatten_file_tree` across
157
+ # 9.6 M recursive calls, out of a 55 s run. The tree is built once and is
158
+ # not mutated here, so walking it once is not an optimisation with a
159
+ # trade-off — it is the same answer with the repetition removed.
160
+ flat_paths = flatten_file_tree(file_tree)
161
+
162
+ # AUD-616-KOTLIN. The source population of every stack, measured once, so
163
+ # `_enrich_stack` can compare a stack against its siblings instead of only
164
+ # against itself. A per-stack score cannot tell "no source evidence" from
165
+ # "the most source evidence in the tree", and those are the two ends of the
166
+ # confidence scale.
167
+ source_hits = {
168
+ stack.stack: self._count_extension_hits(flat_paths, stack.stack)
169
+ for stack in stacks
170
+ }
171
+ enriched = [
172
+ self._enrich_stack(flat_paths, stack, entry_points, source_hits, stacks)
173
+ for stack in stacks
174
+ ]
175
+ project_type = self._classify_project_type(flat_paths, enriched, entry_points)
150
176
  # Delegate to the single primary-stack authority. enrich only ever sees the
151
177
  # ROOT stacks; the authority runs again at model finalization over the full
152
178
  # root+workspace set (that call is the one consumers read).
153
- final_stacks = select_primary_stack(
154
- enriched, project_type, flatten_file_tree(file_tree)
155
- )
179
+ final_stacks = select_primary_stack(enriched, project_type, flat_paths)
156
180
  return final_stacks, project_type
157
181
 
158
182
  def _enrich_stack(
159
183
  self,
160
- file_tree: dict[str, Any],
184
+ flat_paths: "Sequence[str]",
161
185
  stack: StackDetection,
162
186
  entry_points: Sequence[EntryPoint],
187
+ source_hits: "dict[str, int] | None" = None,
188
+ all_stacks: "Sequence[StackDetection] | None" = None,
163
189
  ) -> StackDetection:
164
190
  signals = list(stack.signals)
165
191
  score = 0
@@ -179,7 +205,13 @@ class TypeClassifier:
179
205
  score += 2
180
206
  signals.extend(f"entry:{entry.path}" for entry in matching_entry_points)
181
207
 
182
- extension_hits = self._count_extension_hits(file_tree, stack.stack)
208
+ if source_hits is None:
209
+ source_hits = {}
210
+ if all_stacks is None:
211
+ all_stacks = [stack]
212
+ extension_hits = source_hits.get(stack.stack)
213
+ if extension_hits is None:
214
+ extension_hits = self._count_extension_hits(flat_paths, stack.stack)
183
215
  if extension_hits >= 2:
184
216
  score += 1
185
217
  signals.append(f"extensions:{extension_hits}")
@@ -189,15 +221,52 @@ class TypeClassifier:
189
221
  signals.append("method:heuristic")
190
222
 
191
223
  confidence = self._score_to_confidence(score)
224
+
225
+ # AUD-616-KOTLIN. The score above is a sum of *declarations* — manifests,
226
+ # a package manager, framework coordinates — and source files contribute a
227
+ # single point to it whether there are two of them or twenty thousand. On
228
+ # `ktor-samples` that arithmetic published `java` at `high` on the strength
229
+ # of one appengine `pom.xml` in a subdirectory, with zero `.java` files in
230
+ # the tree, and left the 151 `.kt` files at `low`. Both ends are corrected
231
+ # here, once, for every stack that has a source extension at all — the rule
232
+ # is the product's own: never a confident falsehood, and never call the
233
+ # best evidence in the repository the weakest claim available.
234
+ known_exts = _STACK_EXTENSIONS.get(stack.stack)
235
+ if known_exts:
236
+ if extension_hits == 0:
237
+ # A manifest attests that something is built here. It does not
238
+ # attest the language, and nothing else in the tree does either.
239
+ confidence = "low"
240
+ signals.append(
241
+ f"no_source_evidence:0 {'/'.join(known_exts)} files in tree"
242
+ )
243
+ elif (
244
+ confidence == "low"
245
+ and extension_hits >= max(source_hits.values(), default=0)
246
+ and any(
247
+ other.stack != stack.stack and other.manifests
248
+ for other in all_stacks
249
+ )
250
+ ):
251
+ # This stack owns the largest source population in the tree and a
252
+ # *different* stack is making a manifest-backed claim over the same
253
+ # repository. Leaving the measured language at the bottom of the
254
+ # scale while the declared one sits above it is what let `java`
255
+ # outrank 151 `.kt` files. The floor is deliberately conditioned on
256
+ # a competing claim: a lone heuristic stack with two source files
257
+ # and nothing to contradict it is honestly `low`, and stays there.
258
+ confidence = "medium"
259
+ signals.append(f"largest_source_population:{extension_hits}")
192
260
  return replace(stack, confidence=confidence, signals=self._unique(signals))
193
261
 
194
262
  def _classify_project_type(
195
263
  self,
196
- file_tree: dict[str, Any],
264
+ flat_paths: "Sequence[str]",
197
265
  stacks: Sequence[StackDetection],
198
266
  entry_points: Sequence[EntryPoint],
199
267
  ) -> str | None:
200
- flat_paths = set(flatten_file_tree(file_tree))
268
+ all_paths = flat_paths
269
+ flat_paths = set(all_paths)
201
270
  stack_names = {stack.stack for stack in stacks}
202
271
  framework_names = {framework.name for stack in stacks for framework in stack.frameworks}
203
272
 
@@ -213,7 +282,7 @@ class TypeClassifier:
213
282
  # unaffected — its dominant module *does* use the framework. Drop such
214
283
  # localized frameworks from the set that drives the app-type decision below.
215
284
  _app_frameworks = framework_names & (_WEB_FRAMEWORKS | _API_FRAMEWORKS)
216
- _localized = self._localized_adapter_frameworks(file_tree, stacks, _app_frameworks)
285
+ _localized = self._localized_adapter_frameworks(all_paths, stacks, _app_frameworks)
217
286
  framework_names = framework_names - _localized
218
287
 
219
288
  if len(stack_names) >= 2 and self._is_fullstack(stacks):
@@ -240,7 +309,7 @@ class TypeClassifier:
240
309
 
241
310
  if framework_names & _API_FRAMEWORKS:
242
311
  if not (stack_names & _JVM_STACKS) or self._api_is_center_of_gravity(
243
- file_tree, stacks, entry_points
312
+ all_paths, stacks, entry_points
244
313
  ):
245
314
  return "api"
246
315
  # A JVM API framework is present but is NOT the repo's center of gravity.
@@ -249,7 +318,7 @@ class TypeClassifier:
249
318
  # locatable minority module). Do not headline the whole system as an API —
250
319
  # the framework stays in stacks[].frameworks as a secondary capability and
251
320
  # the primary type falls back to the structural center of gravity.
252
- if self._is_multi_module(file_tree):
321
+ if self._is_multi_module(all_paths):
253
322
  return "library"
254
323
 
255
324
  # All app-defining frameworks were localized to optional adapter submodules
@@ -277,7 +346,7 @@ class TypeClassifier:
277
346
  # structured codebase (e.g. JobRunr: core + per-framework adapter modules).
278
347
  # This is checked BEFORE the weak `bin/`-directory CLI heuristic so a build
279
348
  # output / wrapper `bin/` dir does not mislabel a library as a CLI.
280
- if stack_names & _JVM_STACKS and self._is_multi_module(file_tree):
349
+ if stack_names & _JVM_STACKS and self._is_multi_module(all_paths):
281
350
  return "library"
282
351
 
283
352
  # A JUnit 4-only repository is a test suite, not an unknown application.
@@ -295,14 +364,42 @@ class TypeClassifier:
295
364
  if any(path.startswith("bin/") for path in flat_paths):
296
365
  return "cli"
297
366
 
367
+ # AUD-616-JUNIT4-SELF. The JUnit4 *framework* repository — the one that
368
+ # provides `junit:junit` — returned `unknown`. `AUD-615-JUNIT4` closed the
369
+ # consumer case (a repo that only *uses* JUnit is a test suite) and does
370
+ # not reach this one: the framework has production sources under
371
+ # `src/main/java`, so the `test` branch above rejects it, and it is a
372
+ # single module, so the `library` branch above rejects it too.
373
+ #
374
+ # One correction to the remedy as filed. The report proposed requiring
375
+ # "no entry point", and JUnit4 has three — `TestRunner`, `JUnitCore` and
376
+ # `Version` all carry a `main()`. Requiring their absence would have left
377
+ # the row open. The multi-module requirement is what was doing the wrong
378
+ # work: module count is a fact about a build layout, not about whether a
379
+ # repository is a library, and it was only ever there because the row that
380
+ # introduced the branch (JobRunr) happened to be multi-module.
381
+ #
382
+ # By the time control reaches here every stronger reading has been ruled
383
+ # out by the branches above — a web framework, a server-side template
384
+ # engine, an API framework at the centre of gravity, a CLI framework, an
385
+ # explicit `cli` entry point, a JUnit4-only test tree, a `bin/` directory.
386
+ # What is left is a JVM repository with sources and no application-defining
387
+ # framework, which is the same inference the multi-module branch already
388
+ # makes. `unknown` there is not caution, it is a vacuous answer about a
389
+ # clearly structured codebase.
390
+ if stack_names & _JVM_STACKS and any(
391
+ path.endswith(_JVM_CODE_EXTS) for path in flat_paths
392
+ ):
393
+ return "library"
394
+
298
395
  return "unknown" if stacks else None
299
396
 
300
- def _is_multi_module(self, file_tree: dict[str, Any]) -> bool:
397
+ def _is_multi_module(self, flat_paths: "Sequence[str]") -> bool:
301
398
  """True when the repo has >1 source module (distinct `*/src/...` roots)."""
302
399
  _CODE_EXTS = (".java", ".kt", ".kts", ".scala", ".groovy")
303
400
  modules = {
304
401
  self._module_of(p)
305
- for p in flatten_file_tree(file_tree)
402
+ for p in flat_paths
306
403
  if p.endswith(_CODE_EXTS)
307
404
  }
308
405
  modules.discard("")
@@ -327,7 +424,7 @@ class TypeClassifier:
327
424
 
328
425
  def _localized_adapter_frameworks(
329
426
  self,
330
- file_tree: dict[str, Any],
427
+ flat_paths: "Sequence[str]",
331
428
  stacks: Sequence[StackDetection],
332
429
  candidate_frameworks: set[str],
333
430
  ) -> set[str]:
@@ -345,7 +442,7 @@ class TypeClassifier:
345
442
 
346
443
  _CODE_EXTS = (".java", ".kt", ".kts", ".scala", ".groovy")
347
444
  module_file_counts: dict[str, int] = {}
348
- for p in flatten_file_tree(file_tree):
445
+ for p in flat_paths:
349
446
  if not p.endswith(_CODE_EXTS):
350
447
  continue
351
448
  mod = self._module_of(p)
@@ -383,7 +480,7 @@ class TypeClassifier:
383
480
 
384
481
  def _api_is_center_of_gravity(
385
482
  self,
386
- file_tree: dict[str, Any],
483
+ flat_paths: "Sequence[str]",
387
484
  stacks: Sequence[StackDetection],
388
485
  entry_points: Sequence[EntryPoint],
389
486
  ) -> bool:
@@ -394,7 +491,7 @@ class TypeClassifier:
394
491
  ``_API_PRIMARY_SHARE`` of the JVM source — otherwise it is a minor capability
395
492
  of an engine/platform/library, not the primary type."""
396
493
  counts: dict[str, int] = {}
397
- for p in flatten_file_tree(file_tree):
494
+ for p in flat_paths:
398
495
  if p.endswith(_JVM_CODE_EXTS):
399
496
  mod = self._module_of(p)
400
497
  counts[mod] = counts.get(mod, 0) + 1
@@ -452,9 +549,11 @@ class TypeClassifier:
452
549
  has_api = True
453
550
  return has_web and has_api
454
551
 
455
- def _count_extension_hits(self, file_tree: dict[str, Any], stack: str) -> int:
552
+ def _count_extension_hits(self, flat_paths: "Sequence[str]", stack: str) -> int:
456
553
  extensions = _STACK_EXTENSIONS.get(stack, ())
457
- return sum(1 for path in flatten_file_tree(file_tree) if path.endswith(extensions))
554
+ if not extensions:
555
+ return 0
556
+ return sum(1 for path in flat_paths if path.endswith(extensions))
458
557
 
459
558
  def _score_to_confidence(self, score: int) -> ConfidenceLevel:
460
559
  if score >= 6: