sourcecode 5.9.0__py3-none-any.whl → 5.9.2__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 +1 -1
- sourcecode/_build_commit.py +1 -1
- sourcecode/_docs/DEFECT-LEDGER.md +17 -0
- sourcecode/_docs/USER_GUIDE.md +59 -9
- sourcecode/audit_report.py +20 -0
- sourcecode/audit_verify.py +76 -0
- sourcecode/baseline_attestation.py +77 -0
- sourcecode/cache_model.py +5 -0
- sourcecode/cli.py +405 -25
- sourcecode/cve_reachability.py +27 -1
- sourcecode/format_contract.py +5 -1
- sourcecode/license.py +42 -0
- sourcecode/output_bounds.py +34 -8
- sourcecode/regulatory_pack.py +84 -0
- sourcecode/release_info.py +1 -1
- sourcecode/remediation_report.py +78 -0
- sourcecode/sarif_emit.py +54 -0
- sourcecode/schema_registry.py +12 -0
- sourcecode/vex.py +83 -0
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/METADATA +26 -10
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/RECORD +25 -20
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/WHEEL +0 -0
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/entry_points.txt +0 -0
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/licenses/LICENSE +0 -0
- {sourcecode-5.9.0.dist-info → sourcecode-5.9.2.dist-info}/licenses/NOTICE +0 -0
sourcecode/__init__.py
CHANGED
sourcecode/_build_commit.py
CHANGED
|
@@ -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 = '
|
|
3
|
+
BUILD_COMMIT = 'cbecede0ebc0e7fe99d4c064d5fc052b1b37799e'
|
|
4
4
|
CLOSURE_PROVENANCE = {'Window': {'commit': '', 'status': 'uncited'}, '`AUD-588-F05`': {'commit': '470e4a4', 'status': 'present'}, '`AUD-590-B01` / `B-01`, `F-01`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B02` / `B-02`, `F-02`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B03` / `B-03`, `F-03`': {'commit': '', 'status': 'uncited'}, '`AUD-590-B04` / `B-04`, `F-04`': {'commit': '', 'status': 'uncited'}, '`AUD-590-R01` / `R-01`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A01` / `A-1`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A02` / `A-2`, `A-4`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A03` / `A-3`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A05` / `A-5`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A06` / `A-6`, `A-7`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A08` / `A-8`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A09` / `A-9`': {'commit': '', 'status': 'uncited'}, '`AUD-591-A10` / `A-10`': {'commit': '', 'status': 'uncited'}, '`AUD-592-A02` / `A-1` second half, `§12.5`': {'commit': '63f3d61', 'status': 'present'}, '`AUD-592-B01` / `B-8`': {'commit': '8329614', 'status': 'present'}, '`AUD-592-D01` / `D-1`': {'commit': '0f8e03f', 'status': 'present'}, '`AUD-592-D02` / `D-2`': {'commit': '1d3db4d', 'status': 'present'}, '`AUD-592-D03` / `D-3`': {'commit': 'f28fe40', 'status': 'present'}, '`AUD-592-R01` / `A-1`': {'commit': '216025f', 'status': 'present'}, '`AUD-592-R02` / `N-4`, reopens `AUD-513-N04`': {'commit': 'ec3050d', 'status': 'present'}, '`AUD-593-N01` / `N-01`': {'commit': 'dd6a706', 'status': 'present'}, '`AUD-593-N02` / `N-02`': {'commit': 'f539787', 'status': 'present'}, '`AUD-593-N03` / `N-03`': {'commit': '1dd0b1c', 'status': 'present'}, '`AUD-593-N05` / `N-05`': {'commit': 'aa89388', 'status': 'present'}, '`AUD-593-N06` / `N-06`': {'commit': 'aa584a6', 'status': 'present'}, '`AUD-594-N04` / `D-5`, disclosure half of `B-3`': {'commit': 'a0060b7', 'status': 'present'}, '`AUD-594-X01` / `A-1`, residual of `AUD-593-N03`': {'commit': 'dd222b9', 'status': 'present'}, '`AUD-594-X02` / `A-1` second half': {'commit': '1193937', 'status': 'present'}, '`AUD-594-X03` / `X-03`, residual of `AUD-593-N01`': {'commit': 'e89e8f0', 'status': 'present'}, '`AUD-594-X04`': {'commit': '05b8cc9', 'status': 'present'}, '`AUD-595-A02` / `N-5`, advisory half of `AUD-588-B11`': {'commit': '3e15227', 'status': 'present'}, '`AUD-595-A03` / `B-6` exit-code half': {'commit': 'ab7285f', 'status': 'present'}, '`AUD-595-A05`': {'commit': 'e0b3d10', 'status': 'present'}, '`AUD-595-A07`, narrow half of `AUD-592-A02`': {'commit': 'cc15177', 'status': 'present'}, '`AUD-595-B01` / §B': {'commit': 'b77a1ad', 'status': 'present'}, '`AUD-595-Q01`': {'commit': '91a6566', 'status': 'present'}, '`AUD-596-A01` / `ASK-DET-001`': {'commit': 'fa508b4', 'status': 'present'}, '`AUD-596-A02` / `ASK-SELF-001`': {'commit': '4dfadc8', 'status': 'present'}, '`AUD-596-A03` / `ASK-C1-001`': {'commit': 'bffa3a6', 'status': 'present'}, '`AUD-596-A04` / `ASK-UX-003` + `ASK-UX-004`': {'commit': 'f0aac3c', 'status': 'present'}, '`AUD-596-B01` / `B-01`, harder witness for `BUG-6`': {'commit': '567d09d', 'status': 'present'}, '`AUD-596-B02` / `B-02`, class residual of `R2`': {'commit': '', 'status': 'uncited'}, '`AUD-596-B03` / `B-03`': {'commit': 'a8cb814', 'status': 'present'}, '`AUD-596-B06` / `B-06`': {'commit': '8107927', 'status': 'present'}, '`AUD-596-B07` / `B-07`, class residual of `AUD-592-R02`': {'commit': 'e060485', 'status': 'present'}, '`AUD-596-B09` / `B-09`': {'commit': '5b8146c', 'status': 'present'}, '`AUD-596-B10` / `B-10`': {'commit': '5cb994d', 'status': 'present'}, '`AUD-596-B16` / `B-16`': {'commit': '1507f69', 'status': 'present'}, '`AUD-596-D01` / `B-13` + `B-14`': {'commit': '0b1bdf0', 'status': 'present'}, '`AUD-596-D02` / `B-08`, residual of `AUD-594-N04`': {'commit': 'e7f73c3', 'status': 'present'}, '`AUD-596-D03` / `ASK-AGT-001`': {'commit': '8c46967', 'status': 'present'}, '`AUD-596-D04` / `B-18`': {'commit': '040f027', 'status': 'present'}, '`AUD-596-D05` / `B-19`': {'commit': 'fd2bf68', 'status': 'present'}, '`AUD-596-D06` / `B-20`': {'commit': '784cd83', 'status': 'present'}, '`AUD-596-D07` / `B-17`, half refuted': {'commit': 'c3ff822', 'status': 'present'}, '`AUD-596-D08` / `B-22`, class residual of `AUD-591-A06`': {'commit': 'b396c6a', 'status': 'present'}, '`AUD-596-D09` / `B-23`, surviving half of `B26`': {'commit': 'fd3d63f', 'status': 'present'}, '`AUD-596-D10` / `B-29`': {'commit': '', 'status': 'uncited'}, '`AUD-596-D11` / `ASK-UX-005`': {'commit': 'f4b4f77', 'status': 'present'}, '`AUD-596-D12` — six hints that send the reader back to the error': {'commit': '498df44', 'status': 'present'}, '`AUD-596-D13` / `B-28`': {'commit': '1014c72', 'status': 'present'}, '`AUD-596-D14` / `B-15`, narrow half of `B20`': {'commit': '10f2827', 'status': 'present'}, '`AUD-596-R01` / `B-05`': {'commit': '', 'status': 'uncited'}, '`AUD-596-X01` / `ASK-GATE-001`, gating half of `B24`': {'commit': 'e59c655', 'status': 'present'}, '`AUD-596-X02` / `ASK-UX-001` + `ASK-UX-002` + `B-21`': {'commit': 'f6ebf50', 'status': 'present'}, '`AUD-597-A01` / `ASK-UX-001` + `N-01`': {'commit': 'f8cf061', 'status': 'present'}, '`AUD-597-A02` / `ASK-UX-008` + `ASK-DOC-002`': {'commit': '9d47a4c', 'status': 'present'}, '`AUD-597-A03` / `ASK-DET-001`': {'commit': '4c7622a', 'status': 'present'}, '`AUD-597-A04` / `ASK-LEDGER-001`': {'commit': '46fb870', 'status': 'present'}, '`AUD-597-D01` / `B-04`': {'commit': '1094e6a', 'status': 'present'}, '`AUD-597-D02` / `ASK-DOC-001`': {'commit': 'ecb1bb7', 'status': 'present'}, '`AUD-597-D03` / `N-06`': {'commit': '9784333', 'status': 'present'}, '`AUD-597-D04` / `N-05`': {'commit': 'e51c087', 'status': 'present'}, '`AUD-597-D05` / `N-04`, fourth appearance of `C4-27`': {'commit': '9cccc15', 'status': 'present'}, '`AUD-597-D06` / `ASK-CLI-001`': {'commit': '178aa6d', 'status': 'present'}, '`AUD-597-D07` / `N-03`': {'commit': '9245e35', 'status': 'present'}, '`AUD-597-R01` / `ASK-PERF-001`': {'commit': '6910054', 'status': 'present'}, '`AUD-597-R02` / `ASK-ENC-001`': {'commit': 'ce6dac1', 'status': 'present'}, '`AUD-597-R03` / `ASK-UX-002`': {'commit': 'c57e50b', 'status': 'present'}, '`AUD-597-X01` / `ASK-BUILD-001` + `ASK-META-001` / `B-12`': {'commit': '354338b', 'status': 'present'}, '`AUD-598-B01` / `ASK-META-001`': {'commit': 'b2f7c65', 'status': 'present'}, '`AUD-598-B02` / `ASK-UX-002`': {'commit': '56a4bf7', 'status': 'present'}, '`AUD-598-B03` / `B-11`': {'commit': 'c63cb24', 'status': 'present'}, '`AUD-598-B04` / `N-02`': {'commit': '187937f', 'status': 'present'}, '`AUD-598-R01` / `ASK-PERF-002`': {'commit': '', 'status': 'uncited'}, '`AUD-598-X01` / `N-01` + `B-13` + `B-14` + `B-29` + `N-06`': {'commit': '6f2f5a4', 'status': 'present'}, '`AUD-600-B01` / `P-01`': {'commit': '366ffed', 'status': 'present'}, '`AUD-600-B02` / `P-02` + `ASK-PACK-002`': {'commit': '2a814c3', 'status': 'present'}, '`AUD-600-B03` / `ASK-PACK-001`': {'commit': '452f2a0', 'status': 'present'}, '`AUD-600-D01`': {'commit': '1224e1b', 'status': 'present'}, '`AUD-600-D02`': {'commit': 'eab8172', 'status': 'present'}, '`AUD-600-R01` / `R-01`': {'commit': '88add64', 'status': 'present'}, '`AUD-601-B01` / `N-02`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-B02` / `ASK-PACK-004`': {'commit': '', 'status': 'uncited'}, '`AUD-601-D01` / `N-01`, `ASK-UX-001`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-D02` / `B-13`, `B-14`, 5th round': {'commit': '', 'status': 'uncited'}, '`AUD-601-M01` / `B-26`, `B-27` retracted': {'commit': '', 'status': 'uncited'}, '`AUD-601-R01` / `R-02`': {'commit': '', 'status': 'uncited'}, '`B-3`': {'commit': '773268d', 'status': 'present'}, '`B-6`': {'commit': '17c6e06', 'status': 'present'}, '`B-8`': {'commit': '8329614', 'status': 'present'}, '`BUG-3` / `443b345`': {'commit': '', 'status': 'uncited'}, '`BUG-4a` / `AUD-588-B11` residual': {'commit': '24b4185', 'status': 'present'}, '`BUG-4b` / `AUD-588-B11` residual': {'commit': '0376350', 'status': 'present'}, '`E-39`, class residual of `AUD-593-N06`': {'commit': '25fc11f', 'status': 'present'}, '`N-5`': {'commit': 'c68083f', 'status': 'present'}, '`N-7`': {'commit': '082e9de', 'status': 'present'}, '`R-1`': {'commit': '424fb1c', 'status': 'present'}, '`R-2`': {'commit': '5158b92', 'status': 'present'}, '`R-3`': {'commit': '88aec04', 'status': 'present'}, '`R-4`': {'commit': '04fd04f', 'status': 'present'}}
|
|
5
5
|
CLOSURE_COVERAGE = {'closed_rows': 113, 'present': 88, 'absent': 0, 'unresolved': 0, 'uncited': 25, 'unknown': 0}
|
|
@@ -16,6 +16,23 @@ The facts these rows are keyed to are published: `ask schema facts-v1` prints th
|
|
|
16
16
|
|
|
17
17
|
## Current Synchronization
|
|
18
18
|
|
|
19
|
+
**External `5.9.0` audit received, 2026-08-26 - `AUD-608`; corrections shipped in `5.9.1`.** The audited
|
|
20
|
+
revision is the released `5.9.0` commit. The three confirmed corrections are
|
|
21
|
+
closed: `AUD-608-C01` (P1, `cve` now declares its static inventory scope,
|
|
22
|
+
`fe1f624`), `AUD-608-P01` (P1, `pr-risk --since` returns structured base-ref
|
|
23
|
+
errors, `b479465`), and `AUD-608-O01` (P2, `posture --diff --limit` bounds
|
|
24
|
+
`permit_all` plus effective-access evidence maps, `5f8a50b`). `AUD-607-U01`
|
|
25
|
+
is also closed (`19811ac`): the seven new command helps state budget-policy
|
|
26
|
+
non-applicability. `AUD-607-U02` is
|
|
27
|
+
withdrawn after controlled runs showed an external 120-second timeout;
|
|
28
|
+
`AUD-608-C02` (reported UTF-8 mojibake in `chunk-file`) remains a platform
|
|
29
|
+
reproduction follow-up because the released reader is explicitly UTF-8.
|
|
30
|
+
|
|
31
|
+
The new effective-access evidence supersedes the prior `AUD-607-O01`
|
|
32
|
+
non-reproduction: that check did not exercise the payload shape now identified.
|
|
33
|
+
Historical records below remain snapshots. Full evidence is in
|
|
34
|
+
[`AUDIT-2026-08-26-EXTERNAL-5.9.0.md`](AUDIT-2026-08-26-EXTERNAL-5.9.0.md).
|
|
35
|
+
|
|
19
36
|
**External `5.8.32` audit received, 2026-08-26 — `AUD-607`.** Two independent
|
|
20
37
|
reports ([`AUDIT-2026-08-26-EXTERNAL-5.8.32.md`](AUDIT-2026-08-26-EXTERNAL-5.8.32.md)):
|
|
21
38
|
a reverification round (9.7/10) and a comparison round against `5.8.31` (8/10).
|
sourcecode/_docs/USER_GUIDE.md
CHANGED
|
@@ -46,13 +46,13 @@ CLI commands — impact, endpoints, spring-audit, explain, … each a pro
|
|
|
46
46
|
The key idea: the extraction is **content-addressed**. Commands reuse the parse cache and,
|
|
47
47
|
where their analysed scope matches, the shared Canonical IR; `ask cache model` names what a
|
|
48
48
|
warm buys for each command rather than implying that every projection costs the same. In
|
|
49
|
-
5.9.
|
|
49
|
+
5.9.2, `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.
|
|
55
|
+
the two pack fixes are included in release 5.9.2, with release-artifact verification
|
|
56
56
|
remaining for detached execution and pack evidence.
|
|
57
57
|
|
|
58
58
|
---
|
|
@@ -171,6 +171,7 @@ human. They project the same semantic model into agent-ready context:
|
|
|
171
171
|
|---------|---------|
|
|
172
172
|
| `ask prepare-context <task>` | Task-shaped context bundle (`onboard`, `review-pr`, `fix-bug`, `refactor`, `generate-tests`) sized for an LLM prompt. |
|
|
173
173
|
| `ask repo-ir` | The low-level symbol IR itself — the raw semantic representation, for tools that want the graph, not a report. |
|
|
174
|
+
| `ask repo-ir --budget-tokens N` | Stateless context budget for the inline IR. Composes with `--since`; returns the full view when it fits, otherwise the summary and explicit `non_coverage` for omitted graph fields. |
|
|
174
175
|
| `ask --agent` | Agent-optimized output envelope for any command. |
|
|
175
176
|
| `ask --compact` | A bounded subset (status, stacks, entry points, key dependencies) for cheap orientation. See `ask --help` for the current output budget. |
|
|
176
177
|
| `ask mcp` | Runs ASK as an MCP server so an agent (Claude Desktop / Cursor) calls it as a tool. |
|
|
@@ -179,6 +180,20 @@ Rule of thumb: **developer-facing** commands (`impact`, `spring-audit`, `endpoin
|
|
|
179
180
|
`validation`) answer a question you read; **agent-facing** commands (`prepare-context`,
|
|
180
181
|
`repo-ir`, `--agent`, `--compact`) assemble context another model will consume.
|
|
181
182
|
|
|
183
|
+
`repo-ir --budget-tokens N` is intentionally stateless: `--since <ref>` narrows the
|
|
184
|
+
semantic change set and `--budget-tokens N` bounds the serialized inline answer. If
|
|
185
|
+
the graph does not fit, ASK returns the existing summary view and publishes
|
|
186
|
+
`token_budget.coverage: non_coverage` with the omitted fields. A budget that cannot
|
|
187
|
+
fit even that summary is refused rather than silently truncating JSON. The budget
|
|
188
|
+
applies to stdout; use `--output FILE` for an unbounded artifact.
|
|
189
|
+
|
|
190
|
+
For a rerunnable context-cost comparison, use
|
|
191
|
+
`python3 scripts/context_cost_harness.py` with `--with-ask` and `--without-ask`
|
|
192
|
+
command templates. Templates are executed without a shell and may use `{repo}`
|
|
193
|
+
and `{since}`. The report records both outputs, but only computes a token delta
|
|
194
|
+
when both commands exit successfully and `--comparable` is explicit; otherwise
|
|
195
|
+
the result is `not_comparable` and the process exits non-zero.
|
|
196
|
+
|
|
182
197
|
**Honest state of `prepare-context` (measured in the field, 3 300-file repo):**
|
|
183
198
|
|
|
184
199
|
| Task | State |
|
|
@@ -206,7 +221,7 @@ pipx install sourcecode # isolated install, no venv needed
|
|
|
206
221
|
|
|
207
222
|
# Verify
|
|
208
223
|
ask version
|
|
209
|
-
# ask 5.9.
|
|
224
|
+
# ask 5.9.2
|
|
210
225
|
```
|
|
211
226
|
|
|
212
227
|
Requires Python 3.9+.
|
|
@@ -251,7 +266,7 @@ estimate scaled by file count would be wrong in the direction that costs you a s
|
|
|
251
266
|
and an anchor printed without its release goes on recommending a nightly job for a command
|
|
252
267
|
that has come to finish in seconds (C3-97).
|
|
253
268
|
|
|
254
|
-
|
|
269
|
+
50 commands and seven command groups exist. Four of them carry most of the measured
|
|
255
270
|
value in field use, and they are the ones to learn first:
|
|
256
271
|
|
|
257
272
|
| Start with | Because |
|
|
@@ -605,6 +620,25 @@ ask audit-report . --profile prod --format markdown
|
|
|
605
620
|
ask audit-report . --sign-key audit.key -o audit-report.json
|
|
606
621
|
```
|
|
607
622
|
|
|
623
|
+
Add a technical ENS evidence mapping to the signed bundle. The mapping is not
|
|
624
|
+
a certification: controls that ASK cannot observe are explicitly marked
|
|
625
|
+
`missing_evidence`. Owners, expiry dates and exceptions can be supplied in a
|
|
626
|
+
JSON metadata file.
|
|
627
|
+
|
|
628
|
+
```bash
|
|
629
|
+
ask audit-report . --pack ens --sign-key audit.key --pack-metadata controls.json -o ens-report.json
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### `ask audit-verify` *(supported)*
|
|
633
|
+
|
|
634
|
+
Verify a signed `audit-report` locally without re-running the analysis. The
|
|
635
|
+
command recomputes the canonical JSON payload and HMAC-SHA256, returning
|
|
636
|
+
`valid: true` with exit code 0 or `valid: false` with exit code 1.
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
ask audit-verify audit-report.json --sign-key audit.key
|
|
640
|
+
```
|
|
641
|
+
|
|
608
642
|
`--sign-key` signs the canonical JSON payload with HMAC-SHA256 using a local key file and
|
|
609
643
|
publishes `payload_sha256`, `key_id` and `signature`. This is integrity evidence for the
|
|
610
644
|
bundle, not a public PKI or compliance certification.
|
|
@@ -1114,6 +1148,7 @@ ask baseline capture . --ref v1.4.0 # freeze the measured fingerprint
|
|
|
1114
1148
|
ask baseline capture . --auto # the same, safe to run on every push
|
|
1115
1149
|
ask trend . # read the series (same as `baseline trend`)
|
|
1116
1150
|
ask baseline diff . # the tree against the newest captured point
|
|
1151
|
+
ask baseline attest . # emit a bounded evidence attestation
|
|
1117
1152
|
```
|
|
1118
1153
|
|
|
1119
1154
|
Both readers take **either** a repository path or a directory of baselines: `ask trend .`
|
|
@@ -1140,6 +1175,19 @@ passes `--dir` and writes them elsewhere.
|
|
|
1140
1175
|
`trend` reports movement — totals over time and per-hotspot fan-in tracks. It labels nothing
|
|
1141
1176
|
"improving" or "degrading"; that reading is yours.
|
|
1142
1177
|
|
|
1178
|
+
`baseline attest` is the read-only packaging seam for an auditor or CI artifact. It reads
|
|
1179
|
+
the stored architecture or findings series (`--kind findings`) and publishes its capture
|
|
1180
|
+
boundaries, latest commit, comparability and explicit limits as `baseline-attestation-v1`.
|
|
1181
|
+
It is evidence continuity, not a legal or regulatory compliance verdict, and it returns
|
|
1182
|
+
`status: no_history` instead of manufacturing a clean zero when no capture exists.
|
|
1183
|
+
|
|
1184
|
+
For a multi-repository buyer, `ask auth workspace <repo>...` previews the aggregate Java
|
|
1185
|
+
population that a workspace-sized entitlement would see. The preview is advisory only: it
|
|
1186
|
+
does not change the current per-repository gate or charge the workspace.
|
|
1187
|
+
For a named-buyer pilot, add `--enforce`: crossing the aggregate threshold then requires
|
|
1188
|
+
the existing Pro entitlement and exits with the standard license-required contract; below
|
|
1189
|
+
the threshold it passes. This flag is opt-in and does not change per-repository gates.
|
|
1190
|
+
|
|
1143
1191
|
---
|
|
1144
1192
|
|
|
1145
1193
|
## The gate commands
|
|
@@ -1297,15 +1345,15 @@ explaining. The rest do one thing; `ask <command> --help` is the whole story.
|
|
|
1297
1345
|
|
|
1298
1346
|
| Command | Tier | What it does |
|
|
1299
1347
|
|---|---|---|
|
|
1300
|
-
| `ask migrate-check` | core | Spring Boot 2→3 readiness plus Java LTS/licensing inventory: blockers, effort range, target JDK evidence and explicit licensing-review signals. Full reference: [migrate-check.md](migrate-check.md) |
|
|
1348
|
+
| `ask migrate-check` | core | Spring Boot 2→3 readiness plus Java LTS/licensing inventory: blockers, effort range, target JDK evidence and explicit licensing-review signals. Supports `--format sarif` for located ASK-owned findings. Full reference: [migrate-check.md](migrate-check.md) |
|
|
1301
1349
|
| `ask drift` | supported | What changed in the effective access — since the last capture (`--capture` records; the default compares) or between two profile sets (`--against`). One arithmetic for both axes. Separates `newly_unmatched` from `newly_matched`, and a `removed` route is NOT reported as having become safe. |
|
|
1302
1350
|
| `ask watch` | supported | Warm the index once, then answer what each save affected — one JSON line per tick. `--once` is the pre-commit shape; `--ticks`/`--for` bound the loop. A file added since the warm is reported `unresolved`, never `unreachable`, and a failed analysis is an `error` tick rather than an empty change list. |
|
|
1303
1351
|
| `ask pr-risk` | supported | One verdict for one diff — does this change make things worse? Joins `pr-impact` reach, endpoint access, the shared risk model and CODEOWNERS. `--since` differences route inventories so `new_endpoints` is a fact rather than a guess. An unresolved changed file caps the verdict at `UNKNOWN`, which fails every gate. |
|
|
1304
1352
|
| `ask remediate` | supported | One loop from finding to verified change: audit → recipe → apply in a dedicated worktree → `verify-edit`. Dry by default; `--write` applies. Publishes `mechanisable` against `manual_remainder` with the reason each family cannot be rewritten, and reports `unverified` — never `applied` — when the result could not be checked. |
|
|
1305
|
-
| `ask cve` | supported | Cross the dependency inventory against a **local** OSV advisory feed (`--feed`; no network is taken), then report whether a route can reach the vulnerable package: `reachable`, `imported_not_exposed`, `not_observed` or `unknown`. `not_observed` is not a statement of safety, and nothing here answers "not reachable". |
|
|
1353
|
+
| `ask cve` | supported | Cross the dependency inventory against a **local** OSV advisory feed (`--feed`; no network is taken), then report whether a route can reach the vulnerable package: `reachable`, `imported_not_exposed`, `not_observed` or `unknown`. `--vex` imports exact local decisions as `accepted_by_vex` without changing reachability. `not_observed` is not a statement of safety, and nothing here answers "not reachable". |
|
|
1306
1354
|
| `ask explain <Class>` | supported | Human-readable architectural summary for one class (text by design, not JSON) |
|
|
1307
1355
|
| `ask export` | supported | Tool-agnostic views for downstream tooling: `--c4`, `--module-graph`, `--by-directory`, `--integrations`. `--c4 --render mermaid\|svg` draws the same model instead of emitting it — presentation over the existing payload, with the node cap written on the diagram. |
|
|
1308
|
-
| `ask repo-ir` | supported | Deterministic symbol-level IR for the whole repository |
|
|
1356
|
+
| `ask repo-ir` | supported | Deterministic symbol-level IR for the whole repository; `--since REF --budget-tokens N` bounds the inline agent context without silently truncating JSON |
|
|
1309
1357
|
| `ask cold-start` | supported | Repository Intelligence Snapshot bootstrap context, returned from the persisted RIS |
|
|
1310
1358
|
| `ask baseline … --kind findings` | supported | The defect population as a persisted series, in the same store as the architectural baseline: `capture` records it, `diff` reports new/resolved/persisting against the last capture, and `trend` says how long each still-open defect has been open. `first_seen` is the earliest STORED capture — not the day it was introduced. |
|
|
1311
1359
|
| `ask prepare-context <task>` | supported | Task-shaped context: `onboard`, `delta`, `fix-bug`, `generate-tests`. See [Typical workflows](#typical-workflows) |
|
|
@@ -1314,9 +1362,11 @@ explaining. The rest do one thing; `ask <command> --help` is the whole story.
|
|
|
1314
1362
|
| `ask rename-class` | supported | Word-boundary Java rename across the repository |
|
|
1315
1363
|
| `ask schema <name>` | supported | Print a published JSON Schema for this tool's output |
|
|
1316
1364
|
| `ask self-audit` | supported | Check the SHIPPED build's own coherence — cache model, format contract, schema registry and output affordances. Declares explicitly that the semantic engine does not apply to this product's own source (ASK analyses Java; this build is Python), rather than reporting zeros. `ask selftest` is the different question: the defect ledger run against a real repository. |
|
|
1365
|
+
| `ask auth workspace <repo>...` | supported | Aggregate workspace entitlement preview. `--enforce` opts into the named-buyer pilot that reuses the existing Pro gate when the organization-sized threshold is crossed; advisory mode remains the default and the CLI does not bill. |
|
|
1366
|
+
| `ask remediation-report` | supported | Redacted public projection of the shipped remediation ledger: unique row counts, status/severity distribution, build provenance and closure coverage; omits private narrative and paths. It is a trust artifact, not an SLA or security verdict. |
|
|
1317
1367
|
| `ask archetype` | experimental | Evidence-based architectural archetype across four dimensions |
|
|
1318
1368
|
| `ask retrieve` | parked | Typed knowledge queries over the model — kept working, no longer developed. Symbol-oriented queries accept `--target/-t`; `--module/-m`, `--subsystem/-s` and `--endpoint/-e` remain compatible domain aliases. |
|
|
1319
|
-
| `ask baseline capture\|diff\|trend` | supported | Versioned architectural metrics over time; see [`ask trend`](#ask-trend-dir--how-the-architecture-moved) |
|
|
1369
|
+
| `ask baseline capture\|diff\|trend\|attest` | supported | Versioned architectural metrics over time plus a bounded read-only evidence attestation; see [`ask trend`](#ask-trend-dir--how-the-architecture-moved) |
|
|
1320
1370
|
| `ask cache status\|warm\|model\|clear\|freshness` | supported | Cache inspection; `ask cache model` states what a warm buys each command |
|
|
1321
1371
|
| `ask config` · `ask version` · `ask activate` · `ask auth` · `ask telemetry` · `ask mcp` | supported | Configuration, version, licence, authentication, telemetry (on by default), MCP integration |
|
|
1322
1372
|
|
|
@@ -1770,7 +1820,7 @@ Allowed formats per command (the first is the default):
|
|
|
1770
1820
|
| `impact-chain` | `json`, `yaml` | `json` |
|
|
1771
1821
|
| `spring-audit` | `json`, `yaml`, `github-comment`, `github-actions` | `json` |
|
|
1772
1822
|
| `prepare-context` | `json`, `github-comment` | `json` |
|
|
1773
|
-
| `migrate-check` | `json`, `text`, `github-actions` | `json` |
|
|
1823
|
+
| `migrate-check` | `json`, `text`, `github-actions`, `sarif` | `json` |
|
|
1774
1824
|
| `explain` | `text`, `json` | `text` |
|
|
1775
1825
|
| `pr-impact` | `json`, `text`, `github-actions` | `json` |
|
|
1776
1826
|
| `verify` | `json`, `yaml`, `github-actions` | `json` |
|
sourcecode/audit_report.py
CHANGED
|
@@ -78,6 +78,7 @@ def build_audit_report(
|
|
|
78
78
|
partial_status: "Optional[Callable[..., dict]]" = None,
|
|
79
79
|
ci: bool = False,
|
|
80
80
|
allow_partial: bool = False,
|
|
81
|
+
regulatory_pack: Optional[dict[str, Any]] = None,
|
|
81
82
|
) -> dict[str, Any]:
|
|
82
83
|
"""Build the buyer-readable audit bundle from existing command payloads.
|
|
83
84
|
|
|
@@ -151,6 +152,14 @@ def build_audit_report(
|
|
|
151
152
|
"non_coverage": (risk.get("non_coverage") or {}).get("items", []),
|
|
152
153
|
},
|
|
153
154
|
}
|
|
155
|
+
if regulatory_pack is not None:
|
|
156
|
+
if regulatory_pack.get("framework") == "ENS":
|
|
157
|
+
from sourcecode.regulatory_pack import build_ens_pack
|
|
158
|
+
unsigned["regulatory_pack"] = build_ens_pack(
|
|
159
|
+
unsigned, regulatory_pack.get("metadata")
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
unsigned["regulatory_pack"] = regulatory_pack
|
|
154
163
|
unsigned["evidence_bounds"] = _evidence_bounds(
|
|
155
164
|
root=root, profiles=profiles, risk=risk, posture=posture
|
|
156
165
|
)
|
|
@@ -307,6 +316,17 @@ def render_markdown(report: dict[str, Any]) -> str:
|
|
|
307
316
|
"",
|
|
308
317
|
str((report.get("evidence_bounds") or {}).get("claim_rule", "")),
|
|
309
318
|
])
|
|
319
|
+
regulatory = report.get("regulatory_pack")
|
|
320
|
+
if isinstance(regulatory, dict):
|
|
321
|
+
lines.extend(["", "## Regulatory Evidence Mapping", "",
|
|
322
|
+
str(regulatory.get("statement", "")), "",
|
|
323
|
+
"| Control | Status | Owner | Expiry |", "|---|---|---|---|"])
|
|
324
|
+
for control in regulatory.get("controls") or []:
|
|
325
|
+
lines.append("| " + " | ".join([
|
|
326
|
+
str(control.get("control_id", "")), str(control.get("status", "")),
|
|
327
|
+
str(control.get("owner", "not declared")),
|
|
328
|
+
str(control.get("expiry", "not declared")),
|
|
329
|
+
]) + " |")
|
|
310
330
|
signature = report.get("signature")
|
|
311
331
|
if isinstance(signature, dict):
|
|
312
332
|
lines.extend([
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Offline verification for ASK's signed audit-report bundles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import hmac
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from sourcecode.audit_report import AUDIT_REPORT_SCHEMA, _canonical_json
|
|
12
|
+
|
|
13
|
+
VERIFICATION_SCHEMA = "audit-verification-v1"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def verify_audit_report(report: dict[str, Any], key: bytes) -> dict[str, Any]:
|
|
17
|
+
"""Return a deterministic verification verdict without inspecting a repo."""
|
|
18
|
+
signature = report.get("signature")
|
|
19
|
+
if not isinstance(signature, dict):
|
|
20
|
+
return {
|
|
21
|
+
"schema_version": VERIFICATION_SCHEMA,
|
|
22
|
+
"valid": False,
|
|
23
|
+
"reason": "missing_signature",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
payload = dict(report)
|
|
27
|
+
payload.pop("signature", None)
|
|
28
|
+
canonical = _canonical_json(payload)
|
|
29
|
+
expected_hash = hashlib.sha256(canonical).hexdigest()
|
|
30
|
+
expected_signature = hmac.new(key, canonical, hashlib.sha256).hexdigest()
|
|
31
|
+
hash_matches = hmac.compare_digest(str(signature.get("payload_sha256", "")), expected_hash)
|
|
32
|
+
signature_matches = hmac.compare_digest(
|
|
33
|
+
str(signature.get("signature", "")), expected_signature
|
|
34
|
+
)
|
|
35
|
+
algorithm_matches = signature.get("algorithm") == "hmac-sha256"
|
|
36
|
+
schema_matches = payload.get("schema_version") == AUDIT_REPORT_SCHEMA
|
|
37
|
+
valid = schema_matches and algorithm_matches and hash_matches and signature_matches
|
|
38
|
+
|
|
39
|
+
result: dict[str, Any] = {
|
|
40
|
+
"schema_version": VERIFICATION_SCHEMA,
|
|
41
|
+
"valid": valid,
|
|
42
|
+
"algorithm": signature.get("algorithm"),
|
|
43
|
+
"payload_sha256": expected_hash,
|
|
44
|
+
"key_id": hashlib.sha256(key).hexdigest()[:12],
|
|
45
|
+
"checks": {
|
|
46
|
+
"schema": schema_matches,
|
|
47
|
+
"algorithm": algorithm_matches,
|
|
48
|
+
"payload_sha256": hash_matches,
|
|
49
|
+
"signature": signature_matches,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
if isinstance(payload.get("repository"), dict):
|
|
53
|
+
result["repository"] = payload["repository"]
|
|
54
|
+
if not valid:
|
|
55
|
+
result["reason"] = "signature_mismatch"
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_audit_report(path: Path) -> dict[str, Any]:
|
|
60
|
+
"""Load and validate the outer JSON shape before cryptographic checks."""
|
|
61
|
+
if path.is_dir():
|
|
62
|
+
raise ValueError(f"report path is a directory: {path}")
|
|
63
|
+
try:
|
|
64
|
+
value = json.loads(path.read_text(encoding="utf-8"))
|
|
65
|
+
except OSError as exc:
|
|
66
|
+
raise ValueError(f"could not read report: {exc}") from exc
|
|
67
|
+
except json.JSONDecodeError as exc:
|
|
68
|
+
raise ValueError(f"report is not valid JSON: {exc}") from exc
|
|
69
|
+
if not isinstance(value, dict):
|
|
70
|
+
raise ValueError("report must be a JSON object")
|
|
71
|
+
if value.get("schema_version") != AUDIT_REPORT_SCHEMA:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"unsupported report schema: {value.get('schema_version')!r}; "
|
|
74
|
+
f"expected {AUDIT_REPORT_SCHEMA!r}"
|
|
75
|
+
)
|
|
76
|
+
return value
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""baseline_attestation.py - a bounded, read-only view of persisted history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
SCHEMA_VERSION = "baseline-attestation-v1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_attestation(
|
|
12
|
+
trend_payload: dict[str, Any], *, kind: str, history_dir: str
|
|
13
|
+
) -> dict[str, Any]:
|
|
14
|
+
"""Project an existing trend into an evidence-bounded statement."""
|
|
15
|
+
status = trend_payload.get("status", "ok")
|
|
16
|
+
if status == "no_history":
|
|
17
|
+
return {
|
|
18
|
+
"schema_version": SCHEMA_VERSION,
|
|
19
|
+
"status": "no_history",
|
|
20
|
+
"kind": kind,
|
|
21
|
+
"history_dir": history_dir,
|
|
22
|
+
"claims": [],
|
|
23
|
+
"non_coverage": [
|
|
24
|
+
"No persisted captures exist; no change, duration, or trend can be asserted."
|
|
25
|
+
],
|
|
26
|
+
"source_trend": trend_payload,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
points = trend_payload.get("points") or []
|
|
30
|
+
first = points[0] if points else {}
|
|
31
|
+
last = points[-1] if points else {}
|
|
32
|
+
claims = [{
|
|
33
|
+
"claim": "persisted_history",
|
|
34
|
+
"value": True,
|
|
35
|
+
"basis": f"{trend_payload.get('schema', kind + '-trend-v1')} points",
|
|
36
|
+
}]
|
|
37
|
+
non_coverage = [
|
|
38
|
+
"This is evidence continuity, not a security or regulatory compliance verdict.",
|
|
39
|
+
]
|
|
40
|
+
if kind == "architecture":
|
|
41
|
+
comparability = trend_payload.get("comparability") or {}
|
|
42
|
+
claims.append({
|
|
43
|
+
"claim": "series_comparable",
|
|
44
|
+
"value": comparability.get("status") == "comparable",
|
|
45
|
+
"basis": "point-to-point comparability boundaries",
|
|
46
|
+
})
|
|
47
|
+
non_coverage.append("A metric movement is not labelled improving or degrading.")
|
|
48
|
+
if comparability.get("status") != "comparable":
|
|
49
|
+
non_coverage.append(
|
|
50
|
+
"At least one transition crosses a tool or schema boundary; attribute metric movement with care."
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
non_coverage.append(
|
|
54
|
+
"first_seen is the earliest stored capture, not the introduction date."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
"schema_version": SCHEMA_VERSION,
|
|
59
|
+
"status": "ok",
|
|
60
|
+
"kind": kind,
|
|
61
|
+
"history_dir": history_dir,
|
|
62
|
+
"captures": trend_payload.get(
|
|
63
|
+
"baseline_count", trend_payload.get("captures", len(points))
|
|
64
|
+
),
|
|
65
|
+
"series_starts_at": {
|
|
66
|
+
"captured_at": first.get("captured_at"),
|
|
67
|
+
"commit": first.get("commit"),
|
|
68
|
+
},
|
|
69
|
+
"latest_capture": {
|
|
70
|
+
"captured_at": last.get("captured_at"),
|
|
71
|
+
"commit": last.get("commit"),
|
|
72
|
+
"tool_version": last.get("tool_version"),
|
|
73
|
+
},
|
|
74
|
+
"claims": claims,
|
|
75
|
+
"non_coverage": non_coverage,
|
|
76
|
+
"source_trend": trend_payload,
|
|
77
|
+
}
|
sourcecode/cache_model.py
CHANGED
|
@@ -561,6 +561,10 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
561
561
|
"Reads this build's own registries and manifest; it never "
|
|
562
562
|
"analyses a repository, so there is nothing to cache.",
|
|
563
563
|
analysis_class="core"),
|
|
564
|
+
CommandCache("remediation-report", (), "none", False,
|
|
565
|
+
"Reads the bundled remediation ledger and build provenance; it "
|
|
566
|
+
"analyses no repository and uses no cache layer.",
|
|
567
|
+
analysis_class="none"),
|
|
564
568
|
CommandCache("drift", ("parse", "cir"), "shared", False,
|
|
565
569
|
"Resolves the posture through the shared CIR; --against resolves "
|
|
566
570
|
"a second profile set over the same parse.",
|
|
@@ -595,6 +599,7 @@ COMMANDS: tuple[CommandCache, ...] = (
|
|
|
595
599
|
"repository", analysis_class="repo-wide"),
|
|
596
600
|
CommandCache("selftest", ("cir", "parse"), "shared", False, "Runs the ledger's acceptance criteria by invoking other commands against this repository, so it inherits their layers — a warm makes those runs warm. It holds no cache of its own and stores nothing.", analysis_class="ir"),
|
|
597
601
|
CommandCache("regress", (), "none", False, "Diffs two payload files this CLI already wrote; analyses no source, so no cache layer applies. What it reads about caching is the `cache_layers` field inside those payloads — to refuse a comparison taken under different cache states, not to use one.", analysis_class="none"),
|
|
602
|
+
CommandCache("audit-verify", (), "none", False, "Verifies one local report and key; it does not inspect a repository or use ASK analysis caches.", analysis_class="none"),
|
|
598
603
|
CommandCache("trend", (), "none", False, "Reads stored baseline artifacts from disk; analyses no source, so no cache layer applies. Same command as `baseline trend`.", analysis_class="none"),
|
|
599
604
|
CommandCache("baseline", ("parse",), "shared", False,
|
|
600
605
|
"`capture`/`diff`/`trend` over architectural metrics. The field figure is `capture`, "
|