check: the eval rows re-measured on the subjects left, and checked
Browse filesREADME's nexa check rows rested on eval-check-2026-09.json of
2026-09-14, and two of its five subjects went with the TypeScript and
JavaScript corpora in 8d155aa. Re-measured over the three left, five
runs each: 10 findings in 3 of 3 files, the same on every run, and 3
advisory reported apart; PASS, WARN and REJECT 5 each; 1 of 3 files
with one verdict on every run; 1 rule cited beyond the checkers in 15
reviews, the pinned one, on the plain script.
Reading the first run found the eval miscounting. file_finding_count
kept advisory hits, though its comment said live findings only, so it
read 13 where check prints 10 and fieldtest counts 10. It counts live
findings now, and advisory ones in a field of their own that the report
prints the way check does. The committed run is the second, made after
that and the citation fix in the previous commit (ARCHITECTURE 3.74).
figures.py reads both eval runs. check.* comes from the check rows, and
--check fails on a row for a file the repository no longer has, and on
files reviewed unevenly. retrieval.* comes from retrieval-2026-09.json,
the rows of nexa eval --retrieval --json, which now writes a head with
k, the embedder and the corpus hash it ranked. --check fails on a rule
the run never asked, and on a corpus that moved under it, which a rule
id cannot show when only the wording changed. The README rows are
marked.
The plain-script sample is re-captured from subjects/etl/load_prices.py,
since upload_handler.py is not in the repository. nexa check --no-model
is under a second rather than 0.26 s: 0.42 to 0.52 s on the subjects
and 0.78 s on area_core.py. The nexa ask stability and escalation rows
now carry their date and say that half that run was TypeScript and
JavaScript; re-running them needs the 30B. subjects/README.md loses its
stale "nineteen" ORM rules, and nexa help check and one comment lose the
old counts. ARCHITECTURE 3.75.
812 passed, 2 skipped; every marked figure agrees.
- ARCHITECTURE.md +96 -3
- README.md +76 -47
- bin/eval.py +32 -6
- bin/figures.py +118 -0
- eval-check-2026-09.json +285 -733
- nexa.py +8 -8
- nexa_cmds.py +4 -4
- retrieval-2026-09.json +400 -0
- subjects/README.md +3 -3
- tests/test_eval.py +35 -0
- tests/test_figures.py +132 -1
|
@@ -3856,6 +3856,99 @@ fewer. The exemption stays whole, now on a measurement rather than on a premise
|
|
| 3856 |
and a test pins it. Nothing moves on the three trees, and all 288 hand-classified true
|
| 3857 |
positives are still reported.
|
| 3858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3859 |
## 4. Performance traps, all found by measurement
|
| 3860 |
|
| 3861 |
Within four instrumented runs the report showed the 7B at ~10 tok/s against a
|
|
@@ -4165,9 +4258,9 @@ produces on those files with no model involved.
|
|
| 4165 |
their line numbers, and the excerpt is chosen by symbol so the method carrying
|
| 4166 |
three of them is reviewed rather than cut off. What remains true is the shape of
|
| 4167 |
the risk: the deterministic checkers carry the weight on real code, and the
|
| 4168 |
-
generative half still degrades as the file grows -- a median of
|
| 4169 |
-
out of nineteen on a 7,929-character file
|
| 4170 |
-
longer depends on that half to find anything.
|
| 4171 |
|
| 4172 |
### Untested platforms
|
| 4173 |
|
|
|
|
| 3856 |
and a test pins it. Nothing moves on the three trees, and all 288 hand-classified true
|
| 3857 |
positives are still reported.
|
| 3858 |
|
| 3859 |
+
### 3.74 A claim written as a list
|
| 3860 |
+
|
| 3861 |
+
**Measured 2026-09-27.** `layers.cited_ids` decides what an answer claims to have applied, and
|
| 3862 |
+
it read the `Rules applied:` line from the colon on. The prompt asks for the ids comma separated
|
| 3863 |
+
on that line, and the 7B does not always oblige. Re-capturing the README's plain-script sample
|
| 3864 |
+
(3.75), it wrote the label on its own and listed its four ids under it, one per line. That
|
| 3865 |
+
answer is a fixture now, `LOAD_PRICES_BULLETED`. Read from the label's line, the list was an
|
| 3866 |
+
empty claim: the knowledge layer printed "the answer cites no rule ids" under an answer citing
|
| 3867 |
+
four, and returned UNKNOWN without asking whether any of them exist.
|
| 3868 |
+
|
| 3869 |
+
The second half is the one that mattered. On the line, an id with no rule behind it is a
|
| 3870 |
+
critical finding and a FAIL. In a list it was never looked at, so an invented citation in that
|
| 3871 |
+
shape went through. `corpus.report_citations`, behind `nexa verify`, kept its own copy of the
|
| 3872 |
+
parse with the same gap, and calls `cited_ids` now.
|
| 3873 |
+
|
| 3874 |
+
The claim is the label's line and, when nothing follows the colon, the list directly under it.
|
| 3875 |
+
Blank lines before the first item are skipped, and the first line that is not a list item ends
|
| 3876 |
+
it. A code block is still never a source of citations, and a rule named under `Not handled:` is
|
| 3877 |
+
still not a claim to have applied it. Tests pin the recorded answer, an invented id in its list,
|
| 3878 |
+
and where the list ends.
|
| 3879 |
+
|
| 3880 |
+
On the check subjects, the first run of 3.75, made before the fix, had 4 of its 15 reviews
|
| 3881 |
+
citing nothing the parser could read: all four on `load_prices.py`, and all four PASS. The
|
| 3882 |
+
committed run, made after it, has none. The verdicts did not follow the citations, though.
|
| 3883 |
+
`load_prices.py` passed on all five committed runs with its citations read, since UNKNOWN on the
|
| 3884 |
+
knowledge layer never stood in the way of a PASS. What those answers escaped was the check on
|
| 3885 |
+
what they cited, not a verdict.
|
| 3886 |
+
|
| 3887 |
+
### 3.75 The check and retrieval rows, read from their runs
|
| 3888 |
+
|
| 3889 |
+
**Measured 2026-09-27.** The README's `nexa check` rows rested on `eval-check-2026-09.json` from
|
| 3890 |
+
2026-09-14. Two of its five subjects, `subjects/web/src/partners.ts` and `board.js`, went with the
|
| 3891 |
+
TypeScript and JavaScript corpora in 8d155aa, and `nexa check` refuses both now. The rows still
|
| 3892 |
+
agreed with the artifact, and nothing read the artifact, so nothing said so. The retrieval rows
|
| 3893 |
+
had the opposite weakness: re-measured by hand whenever a rule changed, with no run committed to
|
| 3894 |
+
check them against, and item 12 found them a rule behind.
|
| 3895 |
+
|
| 3896 |
+
The check run is redone over the three subjects left, five runs each, and committed over the old
|
| 3897 |
+
one. The old artifact held the second of the two runs the README quoted:
|
| 3898 |
+
|
| 3899 |
+
| | 2026-09-14, five files | 2026-09-27, three files |
|
| 3900 |
+
|---|---|---|
|
| 3901 |
+
| Reviews | 25 | 15 |
|
| 3902 |
+
| Findings reported about the files | 23, in 5 of 5 | 10, in 3 of 3, and 3 advisory apart |
|
| 3903 |
+
| The same findings on every run | 5 of 5 files | 3 of 3 files |
|
| 3904 |
+
| PASS / WARN / REJECT | 0 / 12 / 13 | 5 / 5 / 5 |
|
| 3905 |
+
| Same verdict every run | 3 of 5 | 1 of 3 |
|
| 3906 |
+
| Rules the model cited beyond the checkers | 3 | 1 |
|
| 3907 |
+
|
| 3908 |
+
**Two counts in the eval were wrong, and neither was the tool's.** `file_finding_count` dropped
|
| 3909 |
+
suppressed hits and kept advisory ones, though its comment said live findings only: the advisory
|
| 3910 |
+
grade came after it. On these files that read 13 where `check` prints 10 and `fieldtest` counts
|
| 3911 |
+
10, with 3 advisory apart. It counts what `check` counts now, and advisory hits go in a field of
|
| 3912 |
+
their own that the report prints the way `check` does. The other was the citation parse, 3.74.
|
| 3913 |
+
Both were found reading the first run, which is why the committed run is the second.
|
| 3914 |
+
|
| 3915 |
+
**What the run says.** The deterministic half is what it was: the same ten findings on every
|
| 3916 |
+
review, and the same ten `fieldtest` finds with no model. `load_prices.py` carries four of them
|
| 3917 |
+
where it carried eight, since three of the plain-Python rules were narrowed in between (3.47,
|
| 3918 |
+
3.61). The model
|
| 3919 |
+
cited one rule beyond the checkers in fifteen reviews, `check-constraint-cannot-cross-tables`,
|
| 3920 |
+
the rule pinned into every review, and it cited it on the plain script, where there is no
|
| 3921 |
+
constraint for it to be about. The verdicts spread evenly, and only `load_prices.py` held one for
|
| 3922 |
+
all five runs: PASS, for an answer that reproduces the file with its defects repaired. The file
|
| 3923 |
+
itself still exits 1. `--no-model` took 0.42 to 0.52 seconds on the subjects and 0.78 on
|
| 3924 |
+
`area_core.py`, against 0.26 on 2026-09-14, so the README says under a second.
|
| 3925 |
+
|
| 3926 |
+
**figures.py reads both runs now.** `check.*` comes from `eval-check-2026-09.json`: files, runs
|
| 3927 |
+
and reviews, the findings taken once per file, whether they came out the same on every run, the
|
| 3928 |
+
verdict counts, stability, and citations beyond the checkers. `--check` fails on a row naming a
|
| 3929 |
+
file the repository no longer has, which is the drift this entry started from, and on files
|
| 3930 |
+
reviewed unevenly.
|
| 3931 |
+
|
| 3932 |
+
`retrieval.*` comes from `retrieval-2026-09.json`, the rows of `nexa eval --retrieval --json`,
|
| 3933 |
+
which now writes a head beside them: the depth it ranked at, the embedder, and the corpus hash
|
| 3934 |
+
it ranked against. One run at the operating depth answers both published figures, since
|
| 3935 |
+
`retrieve()` fills its slots in one fixed order, and a separate top-3 run agreed with it on all
|
| 3936 |
+
28 rules. `--check` fails when a rule is missing from the run and when the hash differs from
|
| 3937 |
+
`corpus_meta.json`. The ids alone would have caught item 12's drift, a rule added, and missed
|
| 3938 |
+
this entry's other change, a rule reworded.
|
| 3939 |
+
|
| 3940 |
+
That rewording is `constrains-ignores-dotted-names`, whose title said "silently ignores". Odoo
|
| 3941 |
+
18 logs "@constrains parameter ... is not a field name" when the model first collects its
|
| 3942 |
+
constraints (3.70), so the title reads "ignores dotted field names without an error". Retrieval
|
| 3943 |
+
was measured before and after it: 28 of 28 at top 6 and 26 at top 3 both times, and no rule
|
| 3944 |
+
changed rank. The rule itself stays second for its own probe, behind the pinned rule, at a
|
| 3945 |
+
score of 0.700 where it was 0.694.
|
| 3946 |
+
|
| 3947 |
+
**Left.** The README's `nexa ask` stability row and escalation row rest on `eval-2026-09.json`
|
| 3948 |
+
of 2026-09-14, and six of its twelve questions were TypeScript and JavaScript: 10 of the 21
|
| 3949 |
+
escalations and 5 of the 11 rescues. The rows now carry that date and say so. Re-running them
|
| 3950 |
+
needs the 30B for the escalations, which is a session of its own.
|
| 3951 |
+
|
| 3952 |
## 4. Performance traps, all found by measurement
|
| 3953 |
|
| 3954 |
Within four instrumented runs the report showed the 7B at ~10 tok/s against a
|
|
|
|
| 4258 |
their line numbers, and the excerpt is chosen by symbol so the method carrying
|
| 4259 |
three of them is reviewed rather than cut off. What remains true is the shape of
|
| 4260 |
the risk: the deterministic checkers carry the weight on real code, and the
|
| 4261 |
+
generative half still degrades as the file grows -- a median of two symbols named
|
| 4262 |
+
out of nineteen on a 7,929-character file, in the run of 2026-09-27 (3.75). The
|
| 4263 |
+
difference is that the tool no longer depends on that half to find anything.
|
| 4264 |
|
| 4265 |
### Untested platforms
|
| 4266 |
|
|
@@ -36,7 +36,7 @@ def _compute_area_level(self):
|
|
| 36 |
The method reads `parent_id.area_level` and declares only `parent_id`. Move a district to
|
| 37 |
another region and the district's own level is recomputed; every area beneath it keeps the
|
| 38 |
old one, stored, with nothing to say it changed. The GIS API groups areas by that level.
|
| 39 |
-
`nexa check --no-model` names it, on line 84, in
|
| 40 |
|
| 41 |
It runs entirely on your machine. No API keys, no network at review time, no client
|
| 42 |
code leaving the laptop — which for a consultancy under an NDA is not a preference.
|
|
@@ -431,15 +431,14 @@ The verdict carries it too, so a review of part of a file cannot print a bare PA
|
|
| 431 |
WARN (INSUFFICIENT EVIDENCE: 9 of 20 symbols were not reviewed)
|
| 432 |
```
|
| 433 |
|
| 434 |
-
**`--no-model` stops after the checkers.**
|
| 435 |
output every run — because what the generative half adds on top of the checkers is
|
| 436 |
-
small
|
| 437 |
-
the
|
| 438 |
-
half produced the identical 23 findings both times.
|
| 439 |
|
| 440 |
That is the argument. A person reading a file wants the part no pattern can decide,
|
| 441 |
and it is worth the wait. A CI gate wants the part that is the same every time, and
|
| 442 |
-
|
| 443 |
the same command.
|
| 444 |
|
| 445 |
It prints **no verdict**, because nothing generated an answer to have a verdict
|
|
@@ -460,18 +459,31 @@ rules have nothing to match in it, the <!--fig:corpus.origin.python-->8<!--/fig-
|
|
| 460 |
which of the two it is rather than leaving the reader to infer it from a short list:
|
| 461 |
|
| 462 |
```
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
Rules retrieved:
|
| 469 |
-
|
| 470 |
-
0.
|
| 471 |
-
0.
|
| 472 |
-
0.
|
|
|
|
|
|
|
| 473 |
```
|
| 474 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
Unlike `nexa ask`, this one has **surroundings to read**: the nearest
|
| 476 |
`__manifest__.py` gives the Odoo series, the file gives its model names and existing
|
| 477 |
imports, `ir.model.access.csv` and the record rules in XML say what Python cannot.
|
|
@@ -608,7 +620,7 @@ Same principle as `z.infer<typeof Schema>`: one definition, derived artifacts, n
|
|
| 608 |
| Pattern false positives | 0 cross-hits across the corpus |
|
| 609 |
| Recall under meaning-preserving rewrite | 82/82 |
|
| 610 |
| Satisfied patterns bypassable by a comment | 0 of <!--fig:corpus.satisfied_patterns-->1<!--/fig--> |
|
| 611 |
-
| Retrievable by their own probe | 28 of 28 at top 6, 26 of them at top 3 |
|
| 612 |
| Hit rate on <!--fig:oca.lines-->33,960<!--/fig--> lines of reviewed OCA code | <!--fig:oca.rate-->3.0<!--/fig--> per 1000 lines: <!--fig:oca.findings-->101<!--/fig--> findings |
|
| 613 |
| Those findings, read by hand | **<!--fig:oca.read.true-->85<!--/fig--> true positives**, <!--fig:oca.read.false-->6<!--/fig--> false, <!--fig:oca.read.undecidable-->7<!--/fig--> undecidable from one file, <!--fig:oca.unread-->3<!--/fig--> not yet read |
|
| 614 |
| Hit rate on <!--fig:openspp.lines-->226,937<!--/fig--> lines of OpenSPP (Odoo 19) | <!--fig:openspp.rate-->0.7<!--/fig--> per 1000 lines: <!--fig:openspp.findings-->161<!--/fig--> findings, +<!--fig:openspp.decided-->127<!--/fig--> another tool had already flagged, +<!--fig:openspp.advisory-->82<!--/fig--> advisory |
|
|
@@ -619,13 +631,13 @@ Same principle as `z.infer<typeof Schema>`: one definition, derived artifacts, n
|
|
| 619 |
| Hit rate on <!--fig:core.lines-->559,663<!--/fig--> lines of odoo core 18.0 | <!--fig:core.rate-->4.2<!--/fig--> per 1000 lines: <!--fig:core.findings-->2,357<!--/fig--> findings, <!--fig:core.read-->89<!--/fig--> of them read by hand: <!--fig:core.read.true-->45<!--/fig--> true, <!--fig:core.read.false-->44<!--/fig--> false |
|
| 620 |
| Hand-classified true positives still reported | **<!--fig:survival.found-->288<!--/fig--> of <!--fig:survival.true-->288<!--/fig-->**, across <!--fig:survival.artifacts-->9<!--/fig--> classifications |
|
| 621 |
| **Recall against real Odoo bugfixes** | **<!--fig:recall.all.caught-->20<!--/fig--> of <!--fig:recall.all.in_scope-->334<!--/fig-->, <!--fig:recall.all.pct-->6<!--/fig-->%, across three ecosystems. See below.** |
|
| 622 |
-
| Verdict stability, 12 questions x 5 runs | 12/12 same verdict every run |
|
| 623 |
-
| Escalation rescued a bad draft | 11 of 21, 95% CI 32–72% |
|
| 624 |
-
| **`nexa check`,
|
| 625 |
-
| The same
|
| 626 |
-
| Rules the model added beyond the checkers | 1
|
| 627 |
-
| `nexa check` verdict stability |
|
| 628 |
-
| `nexa check --no-model` |
|
| 629 |
|
| 630 |
<!--fig:corpus.checkable-->27<!--/fig--> of <!--fig:corpus.rules-->28<!--/fig--> rules carry an automated check. The one that does not —
|
| 631 |
`schema-change-needs-migration`, whose violation lives in the diff between two module
|
|
@@ -1274,9 +1286,9 @@ nexa eval --retrieval
|
|
| 1274 |
asks each rule its own probe and reports whether it comes back. No generation, so it
|
| 1275 |
needs only the embedding model and finishes in seconds.
|
| 1276 |
|
| 1277 |
-
All 28 are retrievable at the operating `TOP_K` of 6, and 26 of them rank
|
| 1278 |
-
3
|
| 1279 |
-
|
| 1280 |
`prefer-sql-constraint` came back first for *"stop anyone entering a negative quantity
|
| 1281 |
on a sale order line"*; with 27 it stopped coming back at all, because that is the
|
| 1282 |
generic validation question every constraint rule here answers.
|
|
@@ -1290,6 +1302,11 @@ The report states its own limits. Pinned rules are prepended regardless of score
|
|
| 1290 |
ranking against them measures the pin rather than the wording, and the header says so
|
| 1291 |
instead of counting them.
|
| 1292 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1293 |
### And what does `nexa check` actually report?
|
| 1294 |
|
| 1295 |
Every number above was measured on `nexa ask`. `nexa check` is the command you
|
|
@@ -1305,42 +1322,54 @@ adds the four things only a file can produce: which corpus it was routed to, how
|
|
| 1305 |
much of it fitted, what the surroundings on disk let the context layer say, and
|
| 1306 |
whether the answer mentions the file at all.
|
| 1307 |
|
| 1308 |
-
|
| 1309 |
|
| 1310 |
| | |
|
| 1311 |
|---|---|
|
| 1312 |
-
| Findings reported about the files | **
|
| 1313 |
-
| …which is what the corpus can find in them |
|
| 1314 |
-
|
|
| 1315 |
-
|
|
| 1316 |
-
|
|
| 1317 |
-
| Same verdict every run |
|
| 1318 |
-
| Rules the model cited that no checker found | **1**, across
|
| 1319 |
-
|
| 1320 |
-
**The deterministic half does everything it is capable of.** Those
|
| 1321 |
-
exactly what `nexa fieldtest` extracts from the same
|
| 1322 |
-
installed: an incomplete `@api.depends` on
|
| 1323 |
-
`@api.constrains` reading two fields it does not
|
| 1324 |
-
`
|
| 1325 |
-
|
|
|
|
|
|
|
|
|
|
| 1326 |
|
| 1327 |
The first run of this eval reported **none** of them, because every layer ran over
|
| 1328 |
the model's reply and nothing had ever read the file.
|
| 1329 |
|
| 1330 |
-
**The generative half adds close to nothing
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
| 1334 |
-
|
|
|
|
|
|
|
| 1335 |
|
| 1336 |
That is worth knowing rather than worth hiding. It says where to spend effort:
|
| 1337 |
another rule in the corpus is worth more than another prompt.
|
| 1338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1339 |
**Coverage is evidence.** `fsm_visit.py` is 7,929 characters against a
|
| 1340 |
6,000-character budget, so nine of its twenty symbols do not fit — but the excerpt
|
| 1341 |
is chosen by symbol and the flagged ones go first, so `_check_no_overlap`, its
|
| 1342 |
-
last method and the home of three of its six findings
|
| 1343 |
-
off 1,900 characters early. What is left out is named, and
|
|
|
|
| 1344 |
|
| 1345 |
```
|
| 1346 |
WARN (INSUFFICIENT EVIDENCE: 9 of 20 symbols were not reviewed)
|
|
|
|
| 36 |
The method reads `parent_id.area_level` and declares only `parent_id`. Move a district to
|
| 37 |
another region and the district's own level is recomputed; every area beneath it keeps the
|
| 38 |
old one, stored, with nothing to say it changed. The GIS API groups areas by that level.
|
| 39 |
+
`nexa check --no-model` names it, on line 84, in under a second.
|
| 40 |
|
| 41 |
It runs entirely on your machine. No API keys, no network at review time, no client
|
| 42 |
code leaving the laptop — which for a consultancy under an NDA is not a preference.
|
|
|
|
| 431 |
WARN (INSUFFICIENT EVIDENCE: 9 of 20 symbols were not reviewed)
|
| 432 |
```
|
| 433 |
|
| 434 |
+
**`--no-model` stops after the checkers.** Under a second, no Ollama, and the same
|
| 435 |
output every run — because what the generative half adds on top of the checkers is
|
| 436 |
+
small: **<!--fig:check.beyond-->1<!--/fig-->** citation beyond them across <!--fig:check.reviews-->15<!--/fig--> reviews. On every one of those
|
| 437 |
+
reviews the deterministic half produced the identical <!--fig:check.findings-->10<!--/fig--> findings.
|
|
|
|
| 438 |
|
| 439 |
That is the argument. A person reading a file wants the part no pattern can decide,
|
| 440 |
and it is worth the wait. A CI gate wants the part that is the same every time, and
|
| 441 |
+
only <!--fig:check.stable-->1<!--/fig--> of the <!--fig:check.files-->3<!--/fig--> files reached the same verdict on all <!--fig:check.runs-->5<!--/fig--> runs. Those had been
|
| 442 |
the same command.
|
| 443 |
|
| 444 |
It prints **no verdict**, because nothing generated an answer to have a verdict
|
|
|
|
| 459 |
which of the two it is rather than leaving the reader to infer it from a short list:
|
| 460 |
|
| 461 |
```
|
| 462 |
+
$ nexa check subjects/etl/load_prices.py
|
| 463 |
+
|
| 464 |
+
checking load_prices.py as odoo
|
| 465 |
+
! no Odoo imports here and no __manifest__.py above it -- the 20 ORM rules
|
| 466 |
+
have nothing to match, so this file is effectively reviewed against the
|
| 467 |
+
8 plain-Python ones
|
| 468 |
+
|
| 469 |
+
4 checker finding(s) in the file
|
| 470 |
+
critical unmeasured line 29 python.subprocess-shell-true-is-injection
|
| 471 |
+
subprocess.run("cp " + target + " " + local, shell=True, check=True)
|
| 472 |
+
...
|
| 473 |
|
| 474 |
Rules retrieved:
|
| 475 |
+
PINNED [critical] odoo.check-constraint-cannot-cross-tables
|
| 476 |
+
0.721 [critical] python.assert-is-not-validation
|
| 477 |
+
0.691 [critical] python.subprocess-shell-true-is-injection
|
| 478 |
+
0.600 [high] python.bare-except-catches-the-exit
|
| 479 |
+
0.580 [high] python.mutable-default-argument
|
| 480 |
+
0.732 [critical] python.eval-and-pickle-execute-their-input
|
| 481 |
```
|
| 482 |
|
| 483 |
+
The four rules the checkers fired on come back ahead of a better cosine, because a
|
| 484 |
+
finding on line 29 is better evidence than a similarity score, and the last place
|
| 485 |
+
goes to similarity. The one ORM rule in the list is pinned into every review.
|
| 486 |
+
|
| 487 |
Unlike `nexa ask`, this one has **surroundings to read**: the nearest
|
| 488 |
`__manifest__.py` gives the Odoo series, the file gives its model names and existing
|
| 489 |
imports, `ir.model.access.csv` and the record rules in XML say what Python cannot.
|
|
|
|
| 620 |
| Pattern false positives | 0 cross-hits across the corpus |
|
| 621 |
| Recall under meaning-preserving rewrite | 82/82 |
|
| 622 |
| Satisfied patterns bypassable by a comment | 0 of <!--fig:corpus.satisfied_patterns-->1<!--/fig--> |
|
| 623 |
+
| Retrievable by their own probe | <!--fig:retrieval.found-->28<!--/fig--> of <!--fig:retrieval.rules-->28<!--/fig--> at top <!--fig:retrieval.k-->6<!--/fig-->, <!--fig:retrieval.top3-->26<!--/fig--> of them at top 3 |
|
| 624 |
| Hit rate on <!--fig:oca.lines-->33,960<!--/fig--> lines of reviewed OCA code | <!--fig:oca.rate-->3.0<!--/fig--> per 1000 lines: <!--fig:oca.findings-->101<!--/fig--> findings |
|
| 625 |
| Those findings, read by hand | **<!--fig:oca.read.true-->85<!--/fig--> true positives**, <!--fig:oca.read.false-->6<!--/fig--> false, <!--fig:oca.read.undecidable-->7<!--/fig--> undecidable from one file, <!--fig:oca.unread-->3<!--/fig--> not yet read |
|
| 626 |
| Hit rate on <!--fig:openspp.lines-->226,937<!--/fig--> lines of OpenSPP (Odoo 19) | <!--fig:openspp.rate-->0.7<!--/fig--> per 1000 lines: <!--fig:openspp.findings-->161<!--/fig--> findings, +<!--fig:openspp.decided-->127<!--/fig--> another tool had already flagged, +<!--fig:openspp.advisory-->82<!--/fig--> advisory |
|
|
|
|
| 631 |
| Hit rate on <!--fig:core.lines-->559,663<!--/fig--> lines of odoo core 18.0 | <!--fig:core.rate-->4.2<!--/fig--> per 1000 lines: <!--fig:core.findings-->2,357<!--/fig--> findings, <!--fig:core.read-->89<!--/fig--> of them read by hand: <!--fig:core.read.true-->45<!--/fig--> true, <!--fig:core.read.false-->44<!--/fig--> false |
|
| 632 |
| Hand-classified true positives still reported | **<!--fig:survival.found-->288<!--/fig--> of <!--fig:survival.true-->288<!--/fig-->**, across <!--fig:survival.artifacts-->9<!--/fig--> classifications |
|
| 633 |
| **Recall against real Odoo bugfixes** | **<!--fig:recall.all.caught-->20<!--/fig--> of <!--fig:recall.all.in_scope-->334<!--/fig-->, <!--fig:recall.all.pct-->6<!--/fig-->%, across three ecosystems. See below.** |
|
| 634 |
+
| Verdict stability, `nexa ask`, 12 questions x 5 runs on 2026-09-14 | 12/12 same verdict every run; 6 of the 12 were TypeScript and JavaScript, since removed |
|
| 635 |
+
| Escalation rescued a bad draft, the same run | 11 of 21, 95% CI 32–72%; 10 of the 21 were TypeScript and JavaScript |
|
| 636 |
+
| **`nexa check`, <!--fig:check.files-->3<!--/fig--> files x <!--fig:check.runs-->5<!--/fig--> runs** | **<!--fig:check.findings-->10<!--/fig--> findings reported, in <!--fig:check.flagged-->3<!--/fig--> of <!--fig:check.files-->3<!--/fig--> files — every one the corpus can make**, and <!--fig:check.advisory-->3<!--/fig--> advisory reported apart |
|
| 637 |
+
| The same findings on every run | <!--fig:check.steady-->3<!--/fig--> of <!--fig:check.files-->3<!--/fig--> files — the deterministic half does not move |
|
| 638 |
+
| Rules the model added beyond the checkers | <!--fig:check.beyond-->1<!--/fig--> across <!--fig:check.reviews-->15<!--/fig--> reviews |
|
| 639 |
+
| `nexa check` verdict stability | <!--fig:check.stable-->1<!--/fig--> of <!--fig:check.files-->3<!--/fig--> files gave the same verdict every run |
|
| 640 |
+
| `nexa check --no-model` | under a second, identical output every run |
|
| 641 |
|
| 642 |
<!--fig:corpus.checkable-->27<!--/fig--> of <!--fig:corpus.rules-->28<!--/fig--> rules carry an automated check. The one that does not —
|
| 643 |
`schema-change-needs-migration`, whose violation lives in the diff between two module
|
|
|
|
| 1286 |
asks each rule its own probe and reports whether it comes back. No generation, so it
|
| 1287 |
needs only the embedding model and finishes in seconds.
|
| 1288 |
|
| 1289 |
+
All <!--fig:retrieval.found-->28<!--/fig--> are retrievable at the operating `TOP_K` of <!--fig:retrieval.k-->6<!--/fig-->, and <!--fig:retrieval.top3-->26<!--/fig--> of them rank
|
| 1290 |
+
in the top 3. That number got harder rather than easier when the corpus became one
|
| 1291 |
+
stack: with 19 rules competing,
|
| 1292 |
`prefer-sql-constraint` came back first for *"stop anyone entering a negative quantity
|
| 1293 |
on a sale order line"*; with 27 it stopped coming back at all, because that is the
|
| 1294 |
generic validation question every constraint rule here answers.
|
|
|
|
| 1302 |
ranking against them measures the pin rather than the wording, and the header says so
|
| 1303 |
instead of counting them.
|
| 1304 |
|
| 1305 |
+
The run behind those figures is committed as `retrieval-2026-09.json`, with the hash
|
| 1306 |
+
of the corpus it ranked, and `python bin/figures.py --check` fails once the corpus
|
| 1307 |
+
moves under it. That includes a rule that was only reworded: it keeps its id and
|
| 1308 |
+
moves its embedding.
|
| 1309 |
+
|
| 1310 |
### And what does `nexa check` actually report?
|
| 1311 |
|
| 1312 |
Every number above was measured on `nexa ask`. `nexa check` is the command you
|
|
|
|
| 1322 |
much of it fitted, what the surroundings on disk let the context layer say, and
|
| 1323 |
whether the answer mentions the file at all.
|
| 1324 |
|
| 1325 |
+
<!--fig:check.files-->3<!--/fig--> files, <!--fig:check.runs-->5<!--/fig--> runs each, committed as `eval-check-2026-09.json`:
|
| 1326 |
|
| 1327 |
| | |
|
| 1328 |
|---|---|
|
| 1329 |
+
| Findings reported about the files | **<!--fig:check.findings-->10<!--/fig-->**, in <!--fig:check.flagged-->3<!--/fig--> of <!--fig:check.files-->3<!--/fig--> files, and <!--fig:check.advisory-->3<!--/fig--> advisory reported apart |
|
| 1330 |
+
| …which is what the corpus can find in them | all of them — `nexa fieldtest` finds the same, no model |
|
| 1331 |
+
| PASS | <!--fig:check.verdict.PASS-->5<!--/fig--> of <!--fig:check.reviews-->15<!--/fig-->, 95% CI 15–58% |
|
| 1332 |
+
| WARN | <!--fig:check.verdict.WARN-->5<!--/fig--> of <!--fig:check.reviews-->15<!--/fig-->, 95% CI 15–58% |
|
| 1333 |
+
| REJECT | <!--fig:check.verdict.REJECT-->5<!--/fig--> of <!--fig:check.reviews-->15<!--/fig-->, 95% CI 15–58% |
|
| 1334 |
+
| Same verdict every run | <!--fig:check.stable-->1<!--/fig--> of <!--fig:check.files-->3<!--/fig--> files |
|
| 1335 |
+
| Rules the model cited that no checker found | **<!--fig:check.beyond-->1<!--/fig-->**, across <!--fig:check.reviews-->15<!--/fig--> reviews |
|
| 1336 |
+
|
| 1337 |
+
**The deterministic half does everything it is capable of.** Those findings are
|
| 1338 |
+
exactly what `nexa fieldtest` extracts from the same three files with no model
|
| 1339 |
+
installed: an incomplete `@api.depends` on two stored computes, a float compared
|
| 1340 |
+
with `==` against zero, an `@api.constrains` reading two fields it does not
|
| 1341 |
+
declare, `shell=True`, an `assert` doing validation's job, a bare `except` and a
|
| 1342 |
+
mutable default argument. Each carries a line number, none depends on what a 7B
|
| 1343 |
+
says, and they came out identical on every review. The advisory three, two `sudo()`
|
| 1344 |
+
calls and a check that could be a database constraint, are printed apart and gate
|
| 1345 |
+
nothing, as `check` itself prints them.
|
| 1346 |
|
| 1347 |
The first run of this eval reported **none** of them, because every layer ran over
|
| 1348 |
the model's reply and nothing had ever read the file.
|
| 1349 |
|
| 1350 |
+
**The generative half adds close to nothing.** Across <!--fig:check.reviews-->15<!--/fig--> reviews the model cited
|
| 1351 |
+
<!--fig:check.beyond-->1<!--/fig--> rule the checkers had not already found. In the run of 2026-09-27 that one
|
| 1352 |
+
was `check-constraint-cannot-cross-tables`, the rule pinned into every review, cited
|
| 1353 |
+
on the plain script, where there is no constraint for it to be about. Handing the
|
| 1354 |
+
model the findings did make its answers more about the file (the symbols they name
|
| 1355 |
+
went from 34 to 90 on the five files measured on 2026-09-14), but what it *adds* to
|
| 1356 |
+
the corpus's own verdict is next to nothing.
|
| 1357 |
|
| 1358 |
That is worth knowing rather than worth hiding. It says where to spend effort:
|
| 1359 |
another rule in the corpus is worth more than another prompt.
|
| 1360 |
|
| 1361 |
+
**PASS is about the answer, not the file.** In the run of 2026-09-27 all five PASS
|
| 1362 |
+
verdicts were `load_prices.py`, whose answer reproduces the file with all five of its
|
| 1363 |
+
functions named, and nothing that ran over the answer's code found a problem in it.
|
| 1364 |
+
The file itself still has its four findings, and `nexa check` exits 1 on it every
|
| 1365 |
+
time: the exit code follows the file.
|
| 1366 |
+
|
| 1367 |
**Coverage is evidence.** `fsm_visit.py` is 7,929 characters against a
|
| 1368 |
6,000-character budget, so nine of its twenty symbols do not fit — but the excerpt
|
| 1369 |
is chosen by symbol and the flagged ones go first, so `_check_no_overlap`, its
|
| 1370 |
+
last method and the home of three of its six findings (one of the three advisory), is
|
| 1371 |
+
reviewed rather than cut off 1,900 characters early. What is left out is named, and
|
| 1372 |
+
the verdict says so:
|
| 1373 |
|
| 1374 |
```
|
| 1375 |
WARN (INSUFFICIENT EVIDENCE: 9 of 20 symbols were not reviewed)
|
|
@@ -45,6 +45,7 @@ import os
|
|
| 45 |
import re
|
| 46 |
import sys
|
| 47 |
import time
|
|
|
|
| 48 |
|
| 49 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 50 |
sys.path.insert(0, HERE)
|
|
@@ -486,6 +487,12 @@ def quiet_check(path, forced=None):
|
|
| 486 |
found_ids = {hit.rule for hit in plan["findings"] if not hit.suppressed}
|
| 487 |
cited = layers.cited_ids(text)
|
| 488 |
beyond = sorted(cited - found_ids)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 489 |
|
| 490 |
eval_ns = final.get("eval_duration") or 0
|
| 491 |
return {
|
|
@@ -504,12 +511,14 @@ def quiet_check(path, forced=None):
|
|
| 504 |
"coverage": plan["coverage"],
|
| 505 |
"symbols_shown": len(plan["shown_names"]),
|
| 506 |
"symbols_total": len(plan["shown_names"]) + len(plan["omitted_names"]),
|
| 507 |
-
"file_findings": sorted(
|
| 508 |
# Live findings only, so this and file_findings count the same things.
|
| 509 |
# A hit on a line another tool already flagged is reported (3.33) and is
|
| 510 |
-
# not part of what the checkers are telling this run to look at
|
| 511 |
-
|
| 512 |
-
|
|
|
|
|
|
|
| 513 |
"file_suppressed": plan["suppressed"],
|
| 514 |
"cited": sorted(cited),
|
| 515 |
"cited_beyond_checkers": beyond,
|
|
@@ -574,7 +583,7 @@ def evaluate_checks(paths, runs, forced=None):
|
|
| 574 |
"dropped_chars": 0, "coverage": "?",
|
| 575 |
"symbols_shown": 0, "symbols_total": 0,
|
| 576 |
"file_findings": [], "file_finding_count": 0,
|
| 577 |
-
"file_suppressed": 0, "facts": 0,
|
| 578 |
"cited": [], "cited_beyond_checkers": [],
|
| 579 |
"insufficient": [],
|
| 580 |
"context_verdict": "?", "context_checked": 0,
|
|
@@ -797,6 +806,11 @@ def summarise_check(rows, runs, skipped=0):
|
|
| 797 |
print(" {0} more sat on lines another tool had already flagged, reported"
|
| 798 |
.format(quiet))
|
| 799 |
print(" by `check` and left out of the counts above (3.33).")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 800 |
print()
|
| 801 |
|
| 802 |
print("CONTEXT -- the layer `ask` can never run")
|
|
@@ -937,8 +951,20 @@ def main(argv):
|
|
| 937 |
return 2
|
| 938 |
code = summarise_retrieval(rows, k)
|
| 939 |
if args.json:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 940 |
with io.open(args.json, "w", encoding="utf-8") as fh:
|
| 941 |
-
fh.write(json.dumps(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 942 |
print("raw rows written to {0}".format(args.json))
|
| 943 |
return code
|
| 944 |
|
|
|
|
| 45 |
import re
|
| 46 |
import sys
|
| 47 |
import time
|
| 48 |
+
from datetime import datetime, timezone
|
| 49 |
|
| 50 |
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 51 |
sys.path.insert(0, HERE)
|
|
|
|
| 487 |
found_ids = {hit.rule for hit in plan["findings"] if not hit.suppressed}
|
| 488 |
cited = layers.cited_ids(text)
|
| 489 |
beyond = sorted(cited - found_ids)
|
| 490 |
+
# Counted the way `nexa check` and fieldtest count: an advisory rule fired,
|
| 491 |
+
# so citing it adds nothing above, but check prints it apart and never
|
| 492 |
+
# counts it. Counting it here made this eval report 13 findings on the
|
| 493 |
+
# subjects where fieldtest reports 10 and 3 advisory.
|
| 494 |
+
live = [hit for hit in plan["findings"]
|
| 495 |
+
if not hit.suppressed and not getattr(hit, "advisory", False)]
|
| 496 |
|
| 497 |
eval_ns = final.get("eval_duration") or 0
|
| 498 |
return {
|
|
|
|
| 511 |
"coverage": plan["coverage"],
|
| 512 |
"symbols_shown": len(plan["shown_names"]),
|
| 513 |
"symbols_total": len(plan["shown_names"]) + len(plan["omitted_names"]),
|
| 514 |
+
"file_findings": sorted({hit.rule for hit in live}),
|
| 515 |
# Live findings only, so this and file_findings count the same things.
|
| 516 |
# A hit on a line another tool already flagged is reported (3.33) and is
|
| 517 |
+
# not part of what the checkers are telling this run to look at, and an
|
| 518 |
+
# advisory one is reported and gates nothing.
|
| 519 |
+
"file_finding_count": len(live),
|
| 520 |
+
"file_advisory": sum(1 for hit in plan["findings"]
|
| 521 |
+
if not hit.suppressed and getattr(hit, "advisory", False)),
|
| 522 |
"file_suppressed": plan["suppressed"],
|
| 523 |
"cited": sorted(cited),
|
| 524 |
"cited_beyond_checkers": beyond,
|
|
|
|
| 583 |
"dropped_chars": 0, "coverage": "?",
|
| 584 |
"symbols_shown": 0, "symbols_total": 0,
|
| 585 |
"file_findings": [], "file_finding_count": 0,
|
| 586 |
+
"file_advisory": 0, "file_suppressed": 0, "facts": 0,
|
| 587 |
"cited": [], "cited_beyond_checkers": [],
|
| 588 |
"insufficient": [],
|
| 589 |
"context_verdict": "?", "context_checked": 0,
|
|
|
|
| 806 |
print(" {0} more sat on lines another tool had already flagged, reported"
|
| 807 |
.format(quiet))
|
| 808 |
print(" by `check` and left out of the counts above (3.33).")
|
| 809 |
+
advisory = sum(r.get("file_advisory") or 0 for r in files.values())
|
| 810 |
+
if advisory:
|
| 811 |
+
print(" {0} more came from advisory rules, which `check` prints apart and"
|
| 812 |
+
.format(advisory))
|
| 813 |
+
print(" counts in nothing.")
|
| 814 |
print()
|
| 815 |
|
| 816 |
print("CONTEXT -- the layer `ask` can never run")
|
|
|
|
| 951 |
return 2
|
| 952 |
code = summarise_retrieval(rows, k)
|
| 953 |
if args.json:
|
| 954 |
+
# The rows alone cannot be checked against anything. A rank means
|
| 955 |
+
# nothing without the depth it was taken at, and a run from before
|
| 956 |
+
# the last rule edit ranks rules that no longer read that way: the
|
| 957 |
+
# ids stay the same when only the wording moves. Both go in beside
|
| 958 |
+
# the rows, as recall's head did (3.53), so figures.py can tell.
|
| 959 |
with io.open(args.json, "w", encoding="utf-8") as fh:
|
| 960 |
+
fh.write(json.dumps({
|
| 961 |
+
"generated_utc": datetime.now(timezone.utc).isoformat(
|
| 962 |
+
timespec="seconds"),
|
| 963 |
+
"k": k,
|
| 964 |
+
"embedder": corpus.EMBED_MODEL,
|
| 965 |
+
"corpus_sha256": evidence.corpus_sha256(),
|
| 966 |
+
"rows": rows,
|
| 967 |
+
}, indent=2, ensure_ascii=False))
|
| 968 |
print("raw rows written to {0}".format(args.json))
|
| 969 |
return code
|
| 970 |
|
|
@@ -47,6 +47,16 @@ A classification pinned to a different commit than the fieldtest artifact is a
|
|
| 47 |
fault rather than a mismatch to be matched around: line numbers from two
|
| 48 |
commits say nothing about each other.
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
Needs no checkout, no model and no network: only the committed artifacts.
|
| 51 |
Stdlib only.
|
| 52 |
"""
|
|
@@ -81,6 +91,9 @@ RECALLS_XML = collections.OrderedDict([
|
|
| 81 |
])
|
| 82 |
CLASSIFICATION_GLOB = "classification-*-2026-09.json"
|
| 83 |
CORPUS_RULES = "corpus/rules/odoo.json"
|
|
|
|
|
|
|
|
|
|
| 84 |
DOCUMENTS = ("README.md", "ARCHITECTURE.md")
|
| 85 |
|
| 86 |
VERDICTS = ("true", "false", "undecidable")
|
|
@@ -390,6 +403,109 @@ def corpus_figures(fig):
|
|
| 390 |
fig.put("corpus.origin." + origin, _count(n), src)
|
| 391 |
|
| 392 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
def compute():
|
| 394 |
fig = Figures()
|
| 395 |
corpus_figures(fig)
|
|
@@ -398,6 +514,8 @@ def compute():
|
|
| 398 |
classification_figures(fig, trees)
|
| 399 |
recall_figures(fig)
|
| 400 |
recall_figures(fig, RECALLS_XML, "recall.xml")
|
|
|
|
|
|
|
| 401 |
return fig
|
| 402 |
|
| 403 |
|
|
|
|
| 47 |
fault rather than a mismatch to be matched around: line numbers from two
|
| 48 |
commits say nothing about each other.
|
| 49 |
|
| 50 |
+
THE TWO EVAL RUNS
|
| 51 |
+
-----------------
|
| 52 |
+
`nexa eval --retrieval` and `nexa eval --check` need a model to run, and CI has
|
| 53 |
+
none, so their rows are committed and read here like any other artifact. Each
|
| 54 |
+
goes stale in its own way, and the check says so rather than recomputing a
|
| 55 |
+
number from rows that describe something else. A retrieval run records the
|
| 56 |
+
corpus hash it ranked, because a reworded rule keeps its id; a check run names
|
| 57 |
+
its files, and a file the repository no longer has is a file nothing can
|
| 58 |
+
re-review.
|
| 59 |
+
|
| 60 |
Needs no checkout, no model and no network: only the committed artifacts.
|
| 61 |
Stdlib only.
|
| 62 |
"""
|
|
|
|
| 91 |
])
|
| 92 |
CLASSIFICATION_GLOB = "classification-*-2026-09.json"
|
| 93 |
CORPUS_RULES = "corpus/rules/odoo.json"
|
| 94 |
+
CORPUS_META = "corpus/corpus_meta.json"
|
| 95 |
+
RETRIEVAL = "retrieval-2026-09.json"
|
| 96 |
+
CHECK = "eval-check-2026-09.json"
|
| 97 |
DOCUMENTS = ("README.md", "ARCHITECTURE.md")
|
| 98 |
|
| 99 |
VERDICTS = ("true", "false", "undecidable")
|
|
|
|
| 403 |
fig.put("corpus.origin." + origin, _count(n), src)
|
| 404 |
|
| 405 |
|
| 406 |
+
def retrieval_figures(fig):
|
| 407 |
+
"""Which rules their own probe brings back, from one committed run.
|
| 408 |
+
|
| 409 |
+
One run at the operating depth answers both published questions. retrieve()
|
| 410 |
+
fills its slots in one fixed order, pins first and then by score, so a rule
|
| 411 |
+
ranked 3 or better at k=6 is exactly a rule a k=3 run returns.
|
| 412 |
+
"""
|
| 413 |
+
src = RETRIEVAL
|
| 414 |
+
art = _load(src)
|
| 415 |
+
rows = art["rows"]
|
| 416 |
+
ranked = [r for r in rows if r.get("rank")]
|
| 417 |
+
fig.put("retrieval.rules", _count(len(rows)), src)
|
| 418 |
+
fig.put("retrieval.k", _count(art["k"]), src)
|
| 419 |
+
fig.put("retrieval.found", _count(len(ranked)), src)
|
| 420 |
+
fig.put("retrieval.top3", _count(sum(r["rank"] <= 3 for r in ranked)), src)
|
| 421 |
+
if art["k"] < 3:
|
| 422 |
+
fig.faults.append("{0}: ranked at k={1}, which cannot say what a top 3 "
|
| 423 |
+
"returns".format(src, art["k"]))
|
| 424 |
+
|
| 425 |
+
# The drift item 12 fixed by hand: a rule added after the run, which the
|
| 426 |
+
# rows then cannot know about.
|
| 427 |
+
corpus_ids = {r["id"] for r in _load(CORPUS_RULES)}
|
| 428 |
+
measured = {r["id"] for r in rows}
|
| 429 |
+
for rid in sorted(corpus_ids - measured):
|
| 430 |
+
fig.faults.append("{0}: {1} is in the corpus and was never asked its "
|
| 431 |
+
"probe".format(src, rid))
|
| 432 |
+
for rid in sorted(measured - corpus_ids):
|
| 433 |
+
fig.faults.append("{0}: {1} was measured and is not in the corpus".format(src, rid))
|
| 434 |
+
|
| 435 |
+
# And the drift the ids cannot see. A reworded title keeps its id and moves
|
| 436 |
+
# its embedding, so the run is pinned to the corpus hash it ranked, as a
|
| 437 |
+
# classification is pinned to the commit its lines were read at.
|
| 438 |
+
ranked_on = art.get("corpus_sha256")
|
| 439 |
+
now = _load(CORPUS_META).get("corpus_sha256")
|
| 440 |
+
if not ranked_on or ranked_on != now:
|
| 441 |
+
fig.faults.append("{0}: ranked against corpus {1}, which is {2} now; re-run "
|
| 442 |
+
"nexa eval --retrieval --json {0}".format(
|
| 443 |
+
src, (ranked_on or "unrecorded")[:12], (now or "?")[:12]))
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def _file_row(runs):
|
| 447 |
+
"""The row that speaks for a file: the first one that got as far as reading
|
| 448 |
+
it. A failed generation reports 0 characters and no findings, and letting it
|
| 449 |
+
represent the file would publish a run that broke as a file that is clean."""
|
| 450 |
+
for row in runs:
|
| 451 |
+
if row.get("chars"):
|
| 452 |
+
return row
|
| 453 |
+
return runs[0]
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def check_figures(fig):
|
| 457 |
+
"""What `nexa eval --check` measured, from its committed rows.
|
| 458 |
+
|
| 459 |
+
One row per review. The checker findings are a fact about the file and are
|
| 460 |
+
taken once per file; whether they came out the same on every run is a
|
| 461 |
+
figure of its own, because "the deterministic half does not move" is a
|
| 462 |
+
published claim, and a file whose findings moved is counted out of it
|
| 463 |
+
rather than averaged into it.
|
| 464 |
+
"""
|
| 465 |
+
src = CHECK
|
| 466 |
+
rows = _load(src)
|
| 467 |
+
files = collections.OrderedDict()
|
| 468 |
+
for row in rows:
|
| 469 |
+
files.setdefault(row["path"], []).append(row)
|
| 470 |
+
|
| 471 |
+
# The drift 13a found: two of five subjects deleted, and every figure still
|
| 472 |
+
# counting them because nothing read the paths.
|
| 473 |
+
for path in files:
|
| 474 |
+
if not os.path.isfile(os.path.join(ROOT, path)):
|
| 475 |
+
fig.faults.append("{0}: {1} is not in the repository, so nothing can "
|
| 476 |
+
"re-review it".format(src, path))
|
| 477 |
+
runs = sorted({len(r) for r in files.values()})
|
| 478 |
+
if len(runs) > 1:
|
| 479 |
+
fig.faults.append("{0}: files were reviewed unevenly, {1} times".format(
|
| 480 |
+
src, " or ".join(str(n) for n in runs)))
|
| 481 |
+
|
| 482 |
+
fig.put("check.files", _count(len(files)), src)
|
| 483 |
+
fig.put("check.runs", _count(runs[-1] if runs else 0), src)
|
| 484 |
+
fig.put("check.reviews", _count(len(rows)), src)
|
| 485 |
+
|
| 486 |
+
speaking = [_file_row(r) for r in files.values()]
|
| 487 |
+
fig.put("check.findings", _count(sum(r["file_finding_count"] for r in speaking)), src)
|
| 488 |
+
fig.put("check.advisory", _count(sum(r.get("file_advisory") or 0 for r in speaking)), src)
|
| 489 |
+
fig.put("check.flagged", _count(sum(1 for r in speaking if r["file_finding_count"])), src)
|
| 490 |
+
steady = 0
|
| 491 |
+
for reviews in files.values():
|
| 492 |
+
read = [(tuple(r["file_findings"]), r["file_finding_count"])
|
| 493 |
+
for r in reviews if r["verdict"] != "ERROR"]
|
| 494 |
+
if read and len(set(read)) == 1:
|
| 495 |
+
steady += 1
|
| 496 |
+
fig.put("check.steady", _count(steady), src)
|
| 497 |
+
|
| 498 |
+
verdicts = collections.Counter(r["verdict"] for r in rows)
|
| 499 |
+
for verdict in sorted(set(verdicts) | {"PASS", "WARN", "REJECT"}):
|
| 500 |
+
fig.put("check.verdict." + verdict, _count(verdicts[verdict]), src)
|
| 501 |
+
fig.put("check.stable", _count(sum(1 for reviews in files.values()
|
| 502 |
+
if len({r["verdict"] for r in reviews}) == 1)), src)
|
| 503 |
+
|
| 504 |
+
beyond = [rid for r in rows for rid in r.get("cited_beyond_checkers") or []]
|
| 505 |
+
fig.put("check.beyond", _count(len(beyond)), src)
|
| 506 |
+
fig.put("check.beyond.rules", _count(len(set(beyond))), src)
|
| 507 |
+
|
| 508 |
+
|
| 509 |
def compute():
|
| 510 |
fig = Figures()
|
| 511 |
corpus_figures(fig)
|
|
|
|
| 514 |
classification_figures(fig, trees)
|
| 515 |
recall_figures(fig)
|
| 516 |
recall_figures(fig, RECALLS_XML, "recall.xml")
|
| 517 |
+
retrieval_figures(fig)
|
| 518 |
+
check_figures(fig)
|
| 519 |
return fig
|
| 520 |
|
| 521 |
|
|
@@ -1,23 +1,30 @@
|
|
| 1 |
[
|
| 2 |
{
|
| 3 |
-
"verdict": "
|
| 4 |
"insufficient": [],
|
| 5 |
-
"seconds":
|
| 6 |
-
"tokens_per_second":
|
| 7 |
-
"rules_fired": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
"layers_with_findings": [
|
| 9 |
-
"
|
|
|
|
|
|
|
| 10 |
],
|
| 11 |
"retrieved": [
|
| 12 |
"odoo.check-constraint-cannot-cross-tables",
|
| 13 |
"odoo.stored-compute-needs-complete-depends",
|
| 14 |
-
"odoo.
|
| 15 |
"odoo.float-comparison-needs-float-utils",
|
| 16 |
-
"odoo.
|
| 17 |
-
"odoo.constrains-
|
| 18 |
],
|
| 19 |
"stack": "odoo",
|
| 20 |
-
"routed_by": "
|
| 21 |
"chars": 2441,
|
| 22 |
"dropped_chars": 0,
|
| 23 |
"coverage": "whole file",
|
|
@@ -25,40 +32,47 @@
|
|
| 25 |
"symbols_total": 0,
|
| 26 |
"file_findings": [
|
| 27 |
"odoo.float-comparison-needs-float-utils",
|
| 28 |
-
"odoo.onchange-is-not-validation",
|
| 29 |
"odoo.stored-compute-needs-complete-depends"
|
| 30 |
],
|
| 31 |
-
"file_finding_count":
|
|
|
|
| 32 |
"file_suppressed": 0,
|
| 33 |
-
"cited": [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
"cited_beyond_checkers": [],
|
| 35 |
"facts": 4,
|
| 36 |
-
"context_verdict": "
|
| 37 |
-
"context_checked":
|
| 38 |
"names_declared": 7,
|
| 39 |
"names_mentioned": 2,
|
| 40 |
-
"answer_chars":
|
| 41 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 42 |
},
|
| 43 |
{
|
| 44 |
"verdict": "WARN",
|
| 45 |
"insufficient": [],
|
| 46 |
-
"seconds":
|
| 47 |
-
"tokens_per_second":
|
| 48 |
-
"rules_fired": [
|
|
|
|
|
|
|
| 49 |
"layers_with_findings": [
|
|
|
|
| 50 |
"structural"
|
| 51 |
],
|
| 52 |
"retrieved": [
|
| 53 |
"odoo.check-constraint-cannot-cross-tables",
|
| 54 |
"odoo.stored-compute-needs-complete-depends",
|
| 55 |
-
"odoo.
|
| 56 |
"odoo.float-comparison-needs-float-utils",
|
| 57 |
-
"odoo.
|
| 58 |
-
"odoo.constrains-
|
| 59 |
],
|
| 60 |
"stack": "odoo",
|
| 61 |
-
"routed_by": "
|
| 62 |
"chars": 2441,
|
| 63 |
"dropped_chars": 0,
|
| 64 |
"coverage": "whole file",
|
|
@@ -66,43 +80,49 @@
|
|
| 66 |
"symbols_total": 0,
|
| 67 |
"file_findings": [
|
| 68 |
"odoo.float-comparison-needs-float-utils",
|
| 69 |
-
"odoo.onchange-is-not-validation",
|
| 70 |
"odoo.stored-compute-needs-complete-depends"
|
| 71 |
],
|
| 72 |
-
"file_finding_count":
|
|
|
|
| 73 |
"file_suppressed": 0,
|
| 74 |
-
"cited": [
|
|
|
|
|
|
|
|
|
|
| 75 |
"cited_beyond_checkers": [],
|
| 76 |
"facts": 4,
|
| 77 |
-
"context_verdict": "
|
| 78 |
-
"context_checked":
|
| 79 |
"names_declared": 7,
|
| 80 |
-
"names_mentioned":
|
| 81 |
-
"answer_chars":
|
| 82 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 83 |
},
|
| 84 |
{
|
| 85 |
"verdict": "REJECT",
|
| 86 |
-
"insufficient": [
|
| 87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
],
|
| 89 |
-
"seconds": 20.2,
|
| 90 |
-
"tokens_per_second": 41.0,
|
| 91 |
-
"rules_fired": [],
|
| 92 |
"layers_with_findings": [
|
| 93 |
-
"
|
| 94 |
-
"
|
|
|
|
| 95 |
],
|
| 96 |
"retrieved": [
|
| 97 |
"odoo.check-constraint-cannot-cross-tables",
|
| 98 |
"odoo.stored-compute-needs-complete-depends",
|
| 99 |
-
"odoo.
|
| 100 |
"odoo.float-comparison-needs-float-utils",
|
| 101 |
-
"odoo.
|
| 102 |
-
"odoo.constrains-
|
| 103 |
],
|
| 104 |
"stack": "odoo",
|
| 105 |
-
"routed_by": "
|
| 106 |
"chars": 2441,
|
| 107 |
"dropped_chars": 0,
|
| 108 |
"coverage": "whole file",
|
|
@@ -110,47 +130,47 @@
|
|
| 110 |
"symbols_total": 0,
|
| 111 |
"file_findings": [
|
| 112 |
"odoo.float-comparison-needs-float-utils",
|
| 113 |
-
"odoo.onchange-is-not-validation",
|
| 114 |
"odoo.stored-compute-needs-complete-depends"
|
| 115 |
],
|
| 116 |
-
"file_finding_count":
|
|
|
|
| 117 |
"file_suppressed": 0,
|
| 118 |
"cited": [
|
| 119 |
-
"odoo.constrains-must-list-every-field-read",
|
| 120 |
"odoo.float-comparison-needs-float-utils",
|
| 121 |
-
"odoo.
|
| 122 |
-
"odoo.
|
| 123 |
-
],
|
| 124 |
-
"cited_beyond_checkers": [
|
| 125 |
-
"odoo.constrains-must-list-every-field-read"
|
| 126 |
],
|
|
|
|
| 127 |
"facts": 4,
|
| 128 |
-
"context_verdict": "
|
| 129 |
-
"context_checked":
|
| 130 |
"names_declared": 7,
|
| 131 |
"names_mentioned": 2,
|
| 132 |
-
"answer_chars":
|
| 133 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 134 |
},
|
| 135 |
{
|
| 136 |
"verdict": "WARN",
|
| 137 |
"insufficient": [],
|
| 138 |
-
"seconds": 4.
|
| 139 |
-
"tokens_per_second":
|
| 140 |
-
"rules_fired": [
|
|
|
|
|
|
|
| 141 |
"layers_with_findings": [
|
|
|
|
| 142 |
"structural"
|
| 143 |
],
|
| 144 |
"retrieved": [
|
| 145 |
"odoo.check-constraint-cannot-cross-tables",
|
| 146 |
"odoo.stored-compute-needs-complete-depends",
|
| 147 |
-
"odoo.
|
| 148 |
"odoo.float-comparison-needs-float-utils",
|
| 149 |
-
"odoo.
|
| 150 |
-
"odoo.constrains-
|
| 151 |
],
|
| 152 |
"stack": "odoo",
|
| 153 |
-
"routed_by": "
|
| 154 |
"chars": 2441,
|
| 155 |
"dropped_chars": 0,
|
| 156 |
"coverage": "whole file",
|
|
@@ -158,45 +178,49 @@
|
|
| 158 |
"symbols_total": 0,
|
| 159 |
"file_findings": [
|
| 160 |
"odoo.float-comparison-needs-float-utils",
|
| 161 |
-
"odoo.onchange-is-not-validation",
|
| 162 |
"odoo.stored-compute-needs-complete-depends"
|
| 163 |
],
|
| 164 |
-
"file_finding_count":
|
|
|
|
| 165 |
"file_suppressed": 0,
|
| 166 |
"cited": [
|
| 167 |
-
"odoo.
|
|
|
|
| 168 |
],
|
| 169 |
"cited_beyond_checkers": [],
|
| 170 |
"facts": 4,
|
| 171 |
-
"context_verdict": "
|
| 172 |
-
"context_checked":
|
| 173 |
"names_declared": 7,
|
| 174 |
"names_mentioned": 1,
|
| 175 |
-
"answer_chars":
|
| 176 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 177 |
},
|
| 178 |
{
|
| 179 |
"verdict": "WARN",
|
| 180 |
"insufficient": [],
|
| 181 |
-
"seconds":
|
| 182 |
-
"tokens_per_second":
|
| 183 |
"rules_fired": [
|
| 184 |
-
"odoo.
|
|
|
|
|
|
|
| 185 |
],
|
| 186 |
"layers_with_findings": [
|
| 187 |
-
"
|
| 188 |
-
"
|
|
|
|
| 189 |
],
|
| 190 |
"retrieved": [
|
| 191 |
"odoo.check-constraint-cannot-cross-tables",
|
| 192 |
"odoo.stored-compute-needs-complete-depends",
|
| 193 |
-
"odoo.
|
| 194 |
"odoo.float-comparison-needs-float-utils",
|
| 195 |
-
"odoo.
|
| 196 |
-
"odoo.constrains-
|
| 197 |
],
|
| 198 |
"stack": "odoo",
|
| 199 |
-
"routed_by": "
|
| 200 |
"chars": 2441,
|
| 201 |
"dropped_chars": 0,
|
| 202 |
"coverage": "whole file",
|
|
@@ -204,68 +228,74 @@
|
|
| 204 |
"symbols_total": 0,
|
| 205 |
"file_findings": [
|
| 206 |
"odoo.float-comparison-needs-float-utils",
|
| 207 |
-
"odoo.onchange-is-not-validation",
|
| 208 |
"odoo.stored-compute-needs-complete-depends"
|
| 209 |
],
|
| 210 |
-
"file_finding_count":
|
|
|
|
| 211 |
"file_suppressed": 0,
|
| 212 |
-
"cited": [
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
"cited_beyond_checkers": [],
|
| 214 |
"facts": 4,
|
| 215 |
-
"context_verdict": "
|
| 216 |
-
"context_checked":
|
| 217 |
"names_declared": 7,
|
| 218 |
"names_mentioned": 2,
|
| 219 |
-
"answer_chars":
|
| 220 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 221 |
},
|
| 222 |
{
|
| 223 |
-
"verdict": "
|
| 224 |
"insufficient": [
|
| 225 |
"9 of 20 symbols were not reviewed"
|
| 226 |
],
|
| 227 |
-
"seconds":
|
| 228 |
-
"tokens_per_second":
|
| 229 |
"rules_fired": [
|
|
|
|
| 230 |
"odoo.prefer-sql-constraint",
|
| 231 |
-
"odoo.
|
| 232 |
],
|
| 233 |
"layers_with_findings": [
|
|
|
|
|
|
|
| 234 |
"semantic"
|
| 235 |
],
|
| 236 |
"retrieved": [
|
| 237 |
"odoo.check-constraint-cannot-cross-tables",
|
| 238 |
"odoo.constrains-must-list-every-field-read",
|
| 239 |
"odoo.stored-compute-needs-complete-depends",
|
|
|
|
| 240 |
"odoo.prefer-sql-constraint",
|
| 241 |
-
"odoo.
|
| 242 |
-
"odoo.constrains-ignores-dotted-names"
|
| 243 |
],
|
| 244 |
"stack": "odoo",
|
| 245 |
-
"routed_by": "
|
| 246 |
"chars": 7929,
|
| 247 |
-
"dropped_chars":
|
| 248 |
"coverage": "by symbol",
|
| 249 |
"symbols_shown": 11,
|
| 250 |
"symbols_total": 20,
|
| 251 |
"file_findings": [
|
| 252 |
"odoo.constrains-must-list-every-field-read",
|
| 253 |
-
"odoo.prefer-sql-constraint",
|
| 254 |
"odoo.stored-compute-needs-complete-depends"
|
| 255 |
],
|
| 256 |
-
"file_finding_count":
|
|
|
|
| 257 |
"file_suppressed": 0,
|
| 258 |
"cited": [
|
| 259 |
-
"odoo.constrains-must-list-every-field-read"
|
| 260 |
-
"odoo.stored-compute-needs-complete-depends"
|
| 261 |
],
|
| 262 |
"cited_beyond_checkers": [],
|
| 263 |
"facts": 4,
|
| 264 |
-
"context_verdict": "
|
| 265 |
-
"context_checked":
|
| 266 |
"names_declared": 19,
|
| 267 |
-
"names_mentioned":
|
| 268 |
-
"answer_chars":
|
| 269 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 270 |
},
|
| 271 |
{
|
|
@@ -273,48 +303,54 @@
|
|
| 273 |
"insufficient": [
|
| 274 |
"9 of 20 symbols were not reviewed"
|
| 275 |
],
|
| 276 |
-
"seconds":
|
| 277 |
-
"tokens_per_second":
|
| 278 |
"rules_fired": [
|
| 279 |
"odoo.prefer-sql-constraint",
|
|
|
|
| 280 |
"odoo.stored-compute-needs-complete-depends"
|
| 281 |
],
|
| 282 |
"layers_with_findings": [
|
| 283 |
-
"
|
|
|
|
|
|
|
|
|
|
| 284 |
],
|
| 285 |
"retrieved": [
|
| 286 |
"odoo.check-constraint-cannot-cross-tables",
|
| 287 |
"odoo.constrains-must-list-every-field-read",
|
| 288 |
"odoo.stored-compute-needs-complete-depends",
|
|
|
|
| 289 |
"odoo.prefer-sql-constraint",
|
| 290 |
-
"odoo.
|
| 291 |
-
"odoo.constrains-ignores-dotted-names"
|
| 292 |
],
|
| 293 |
"stack": "odoo",
|
| 294 |
-
"routed_by": "
|
| 295 |
"chars": 7929,
|
| 296 |
-
"dropped_chars":
|
| 297 |
"coverage": "by symbol",
|
| 298 |
"symbols_shown": 11,
|
| 299 |
"symbols_total": 20,
|
| 300 |
"file_findings": [
|
| 301 |
"odoo.constrains-must-list-every-field-read",
|
| 302 |
-
"odoo.prefer-sql-constraint",
|
| 303 |
"odoo.stored-compute-needs-complete-depends"
|
| 304 |
],
|
| 305 |
-
"file_finding_count":
|
|
|
|
| 306 |
"file_suppressed": 0,
|
| 307 |
"cited": [
|
| 308 |
"odoo.constrains-must-list-every-field-read",
|
| 309 |
-
"odoo.
|
|
|
|
|
|
|
| 310 |
],
|
| 311 |
"cited_beyond_checkers": [],
|
| 312 |
"facts": 4,
|
| 313 |
-
"context_verdict": "
|
| 314 |
-
"context_checked":
|
| 315 |
"names_declared": 19,
|
| 316 |
-
"names_mentioned":
|
| 317 |
-
"answer_chars":
|
| 318 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 319 |
},
|
| 320 |
{
|
|
@@ -322,97 +358,106 @@
|
|
| 322 |
"insufficient": [
|
| 323 |
"9 of 20 symbols were not reviewed"
|
| 324 |
],
|
| 325 |
-
"seconds":
|
| 326 |
-
"tokens_per_second":
|
| 327 |
"rules_fired": [
|
| 328 |
"odoo.prefer-sql-constraint",
|
|
|
|
| 329 |
"odoo.stored-compute-needs-complete-depends"
|
| 330 |
],
|
| 331 |
"layers_with_findings": [
|
| 332 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
],
|
| 334 |
"retrieved": [
|
| 335 |
"odoo.check-constraint-cannot-cross-tables",
|
| 336 |
"odoo.constrains-must-list-every-field-read",
|
| 337 |
"odoo.stored-compute-needs-complete-depends",
|
|
|
|
| 338 |
"odoo.prefer-sql-constraint",
|
| 339 |
-
"odoo.
|
| 340 |
-
"odoo.constrains-ignores-dotted-names"
|
| 341 |
],
|
| 342 |
"stack": "odoo",
|
| 343 |
-
"routed_by": "
|
| 344 |
"chars": 7929,
|
| 345 |
-
"dropped_chars":
|
| 346 |
"coverage": "by symbol",
|
| 347 |
"symbols_shown": 11,
|
| 348 |
"symbols_total": 20,
|
| 349 |
"file_findings": [
|
| 350 |
"odoo.constrains-must-list-every-field-read",
|
| 351 |
-
"odoo.prefer-sql-constraint",
|
| 352 |
"odoo.stored-compute-needs-complete-depends"
|
| 353 |
],
|
| 354 |
-
"file_finding_count":
|
|
|
|
| 355 |
"file_suppressed": 0,
|
| 356 |
"cited": [
|
| 357 |
"odoo.constrains-must-list-every-field-read",
|
| 358 |
-
"odoo.
|
|
|
|
|
|
|
| 359 |
],
|
| 360 |
"cited_beyond_checkers": [],
|
| 361 |
"facts": 4,
|
| 362 |
-
"context_verdict": "
|
| 363 |
-
"context_checked":
|
| 364 |
"names_declared": 19,
|
| 365 |
-
"names_mentioned":
|
| 366 |
-
"answer_chars":
|
| 367 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 368 |
},
|
| 369 |
{
|
| 370 |
-
"verdict": "
|
| 371 |
"insufficient": [
|
| 372 |
"9 of 20 symbols were not reviewed"
|
| 373 |
],
|
| 374 |
-
"seconds":
|
| 375 |
-
"tokens_per_second":
|
| 376 |
"rules_fired": [
|
|
|
|
| 377 |
"odoo.prefer-sql-constraint",
|
| 378 |
-
"odoo.
|
| 379 |
],
|
| 380 |
"layers_with_findings": [
|
|
|
|
|
|
|
| 381 |
"semantic"
|
| 382 |
],
|
| 383 |
"retrieved": [
|
| 384 |
"odoo.check-constraint-cannot-cross-tables",
|
| 385 |
"odoo.constrains-must-list-every-field-read",
|
| 386 |
"odoo.stored-compute-needs-complete-depends",
|
|
|
|
| 387 |
"odoo.prefer-sql-constraint",
|
| 388 |
-
"odoo.
|
| 389 |
-
"odoo.constrains-ignores-dotted-names"
|
| 390 |
],
|
| 391 |
"stack": "odoo",
|
| 392 |
-
"routed_by": "
|
| 393 |
"chars": 7929,
|
| 394 |
-
"dropped_chars":
|
| 395 |
"coverage": "by symbol",
|
| 396 |
"symbols_shown": 11,
|
| 397 |
"symbols_total": 20,
|
| 398 |
"file_findings": [
|
| 399 |
"odoo.constrains-must-list-every-field-read",
|
| 400 |
-
"odoo.prefer-sql-constraint",
|
| 401 |
"odoo.stored-compute-needs-complete-depends"
|
| 402 |
],
|
| 403 |
-
"file_finding_count":
|
|
|
|
| 404 |
"file_suppressed": 0,
|
| 405 |
"cited": [
|
| 406 |
-
"odoo.constrains-must-list-every-field-read"
|
| 407 |
-
"odoo.stored-compute-needs-complete-depends"
|
| 408 |
],
|
| 409 |
"cited_beyond_checkers": [],
|
| 410 |
"facts": 4,
|
| 411 |
-
"context_verdict": "
|
| 412 |
-
"context_checked":
|
| 413 |
"names_declared": 19,
|
| 414 |
-
"names_mentioned":
|
| 415 |
-
"answer_chars":
|
| 416 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 417 |
},
|
| 418 |
{
|
|
@@ -420,12 +465,16 @@
|
|
| 420 |
"insufficient": [
|
| 421 |
"9 of 20 symbols were not reviewed"
|
| 422 |
],
|
| 423 |
-
"seconds":
|
| 424 |
-
"tokens_per_second":
|
| 425 |
"rules_fired": [
|
|
|
|
|
|
|
| 426 |
"odoo.stored-compute-needs-complete-depends"
|
| 427 |
],
|
| 428 |
"layers_with_findings": [
|
|
|
|
|
|
|
| 429 |
"semantic",
|
| 430 |
"structural"
|
| 431 |
],
|
|
@@ -433,62 +482,60 @@
|
|
| 433 |
"odoo.check-constraint-cannot-cross-tables",
|
| 434 |
"odoo.constrains-must-list-every-field-read",
|
| 435 |
"odoo.stored-compute-needs-complete-depends",
|
|
|
|
| 436 |
"odoo.prefer-sql-constraint",
|
| 437 |
-
"odoo.
|
| 438 |
-
"odoo.constrains-ignores-dotted-names"
|
| 439 |
],
|
| 440 |
"stack": "odoo",
|
| 441 |
-
"routed_by": "
|
| 442 |
"chars": 7929,
|
| 443 |
-
"dropped_chars":
|
| 444 |
"coverage": "by symbol",
|
| 445 |
"symbols_shown": 11,
|
| 446 |
"symbols_total": 20,
|
| 447 |
"file_findings": [
|
| 448 |
"odoo.constrains-must-list-every-field-read",
|
| 449 |
-
"odoo.prefer-sql-constraint",
|
| 450 |
"odoo.stored-compute-needs-complete-depends"
|
| 451 |
],
|
| 452 |
-
"file_finding_count":
|
|
|
|
| 453 |
"file_suppressed": 0,
|
| 454 |
"cited": [
|
| 455 |
"odoo.constrains-must-list-every-field-read",
|
| 456 |
"odoo.prefer-sql-constraint",
|
| 457 |
-
"odoo.stored-compute-needs-complete-depends"
|
|
|
|
| 458 |
],
|
| 459 |
"cited_beyond_checkers": [],
|
| 460 |
"facts": 4,
|
| 461 |
-
"context_verdict": "
|
| 462 |
-
"context_checked":
|
| 463 |
"names_declared": 19,
|
| 464 |
"names_mentioned": 2,
|
| 465 |
-
"answer_chars":
|
| 466 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 467 |
},
|
| 468 |
{
|
| 469 |
-
"verdict": "
|
| 470 |
"insufficient": [],
|
| 471 |
-
"seconds":
|
| 472 |
-
"tokens_per_second":
|
| 473 |
"rules_fired": [
|
| 474 |
-
"python.decimal-for-money-not-float"
|
| 475 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 476 |
-
"python.path-join-does-not-contain-a-path"
|
| 477 |
],
|
| 478 |
"layers_with_findings": [
|
| 479 |
-
"
|
| 480 |
-
"knowledge"
|
| 481 |
],
|
| 482 |
"retrieved": [
|
|
|
|
| 483 |
"python.assert-is-not-validation",
|
| 484 |
"python.subprocess-shell-true-is-injection",
|
| 485 |
-
"python.path-join-does-not-contain-a-path",
|
| 486 |
"python.bare-except-catches-the-exit",
|
| 487 |
"python.mutable-default-argument",
|
| 488 |
-
"python.
|
| 489 |
],
|
| 490 |
-
"stack": "
|
| 491 |
-
"routed_by": "
|
| 492 |
"chars": 2033,
|
| 493 |
"dropped_chars": 0,
|
| 494 |
"coverage": "whole file",
|
|
@@ -497,55 +544,48 @@
|
|
| 497 |
"file_findings": [
|
| 498 |
"python.assert-is-not-validation",
|
| 499 |
"python.bare-except-catches-the-exit",
|
| 500 |
-
"python.decimal-for-money-not-float",
|
| 501 |
"python.mutable-default-argument",
|
| 502 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 503 |
-
"python.path-join-does-not-contain-a-path",
|
| 504 |
"python.subprocess-shell-true-is-injection"
|
| 505 |
],
|
| 506 |
-
"file_finding_count":
|
|
|
|
| 507 |
"file_suppressed": 0,
|
| 508 |
"cited": [
|
| 509 |
"python.assert-is-not-validation",
|
| 510 |
"python.bare-except-catches-the-exit",
|
| 511 |
-
"python.decimal-for-money-not-float",
|
| 512 |
"python.mutable-default-argument",
|
| 513 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 514 |
-
"python.path-join-does-not-contain-a-path",
|
| 515 |
"python.subprocess-shell-true-is-injection"
|
| 516 |
],
|
| 517 |
"cited_beyond_checkers": [],
|
| 518 |
"facts": 0,
|
| 519 |
-
"context_verdict": "
|
| 520 |
-
"context_checked":
|
| 521 |
"names_declared": 5,
|
| 522 |
"names_mentioned": 5,
|
| 523 |
-
"answer_chars":
|
| 524 |
"path": "subjects/etl/load_prices.py"
|
| 525 |
},
|
| 526 |
{
|
| 527 |
-
"verdict": "
|
| 528 |
"insufficient": [],
|
| 529 |
-
"seconds":
|
| 530 |
-
"tokens_per_second":
|
| 531 |
"rules_fired": [
|
| 532 |
-
"python.decimal-for-money-not-float"
|
| 533 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 534 |
-
"python.path-join-does-not-contain-a-path"
|
| 535 |
],
|
| 536 |
"layers_with_findings": [
|
| 537 |
-
"
|
| 538 |
],
|
| 539 |
"retrieved": [
|
|
|
|
| 540 |
"python.assert-is-not-validation",
|
| 541 |
"python.subprocess-shell-true-is-injection",
|
| 542 |
-
"python.path-join-does-not-contain-a-path",
|
| 543 |
"python.bare-except-catches-the-exit",
|
| 544 |
"python.mutable-default-argument",
|
| 545 |
-
"python.
|
| 546 |
],
|
| 547 |
-
"stack": "
|
| 548 |
-
"routed_by": "
|
| 549 |
"chars": 2033,
|
| 550 |
"dropped_chars": 0,
|
| 551 |
"coverage": "whole file",
|
|
@@ -554,54 +594,48 @@
|
|
| 554 |
"file_findings": [
|
| 555 |
"python.assert-is-not-validation",
|
| 556 |
"python.bare-except-catches-the-exit",
|
| 557 |
-
"python.decimal-for-money-not-float",
|
| 558 |
"python.mutable-default-argument",
|
| 559 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 560 |
-
"python.path-join-does-not-contain-a-path",
|
| 561 |
"python.subprocess-shell-true-is-injection"
|
| 562 |
],
|
| 563 |
-
"file_finding_count":
|
|
|
|
| 564 |
"file_suppressed": 0,
|
| 565 |
"cited": [
|
| 566 |
"python.assert-is-not-validation",
|
| 567 |
"python.bare-except-catches-the-exit",
|
| 568 |
-
"python.decimal-for-money-not-float",
|
| 569 |
"python.mutable-default-argument",
|
| 570 |
-
"python.path-join-does-not-contain-a-path",
|
| 571 |
"python.subprocess-shell-true-is-injection"
|
| 572 |
],
|
| 573 |
"cited_beyond_checkers": [],
|
| 574 |
"facts": 0,
|
| 575 |
-
"context_verdict": "
|
| 576 |
-
"context_checked":
|
| 577 |
"names_declared": 5,
|
| 578 |
"names_mentioned": 5,
|
| 579 |
-
"answer_chars":
|
| 580 |
"path": "subjects/etl/load_prices.py"
|
| 581 |
},
|
| 582 |
{
|
| 583 |
-
"verdict": "
|
| 584 |
"insufficient": [],
|
| 585 |
-
"seconds":
|
| 586 |
-
"tokens_per_second":
|
| 587 |
"rules_fired": [
|
| 588 |
-
"python.decimal-for-money-not-float"
|
| 589 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 590 |
-
"python.path-join-does-not-contain-a-path"
|
| 591 |
],
|
| 592 |
"layers_with_findings": [
|
| 593 |
-
"
|
| 594 |
],
|
| 595 |
"retrieved": [
|
|
|
|
| 596 |
"python.assert-is-not-validation",
|
| 597 |
"python.subprocess-shell-true-is-injection",
|
| 598 |
-
"python.path-join-does-not-contain-a-path",
|
| 599 |
"python.bare-except-catches-the-exit",
|
| 600 |
"python.mutable-default-argument",
|
| 601 |
-
"python.
|
| 602 |
],
|
| 603 |
-
"stack": "
|
| 604 |
-
"routed_by": "
|
| 605 |
"chars": 2033,
|
| 606 |
"dropped_chars": 0,
|
| 607 |
"coverage": "whole file",
|
|
@@ -610,49 +644,45 @@
|
|
| 610 |
"file_findings": [
|
| 611 |
"python.assert-is-not-validation",
|
| 612 |
"python.bare-except-catches-the-exit",
|
| 613 |
-
"python.decimal-for-money-not-float",
|
| 614 |
"python.mutable-default-argument",
|
| 615 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 616 |
-
"python.path-join-does-not-contain-a-path",
|
| 617 |
"python.subprocess-shell-true-is-injection"
|
| 618 |
],
|
| 619 |
-
"file_finding_count":
|
|
|
|
| 620 |
"file_suppressed": 0,
|
| 621 |
"cited": [
|
| 622 |
"python.mutable-default-argument"
|
| 623 |
],
|
| 624 |
"cited_beyond_checkers": [],
|
| 625 |
"facts": 0,
|
| 626 |
-
"context_verdict": "
|
| 627 |
-
"context_checked":
|
| 628 |
"names_declared": 5,
|
| 629 |
"names_mentioned": 5,
|
| 630 |
-
"answer_chars":
|
| 631 |
"path": "subjects/etl/load_prices.py"
|
| 632 |
},
|
| 633 |
{
|
| 634 |
-
"verdict": "
|
| 635 |
"insufficient": [],
|
| 636 |
-
"seconds":
|
| 637 |
-
"tokens_per_second":
|
| 638 |
"rules_fired": [
|
| 639 |
-
"python.decimal-for-money-not-float"
|
| 640 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 641 |
-
"python.path-join-does-not-contain-a-path"
|
| 642 |
],
|
| 643 |
"layers_with_findings": [
|
| 644 |
-
"
|
| 645 |
],
|
| 646 |
"retrieved": [
|
|
|
|
| 647 |
"python.assert-is-not-validation",
|
| 648 |
"python.subprocess-shell-true-is-injection",
|
| 649 |
-
"python.path-join-does-not-contain-a-path",
|
| 650 |
"python.bare-except-catches-the-exit",
|
| 651 |
"python.mutable-default-argument",
|
| 652 |
-
"python.
|
| 653 |
],
|
| 654 |
-
"stack": "
|
| 655 |
-
"routed_by": "
|
| 656 |
"chars": 2033,
|
| 657 |
"dropped_chars": 0,
|
| 658 |
"coverage": "whole file",
|
|
@@ -661,47 +691,51 @@
|
|
| 661 |
"file_findings": [
|
| 662 |
"python.assert-is-not-validation",
|
| 663 |
"python.bare-except-catches-the-exit",
|
| 664 |
-
"python.decimal-for-money-not-float",
|
| 665 |
"python.mutable-default-argument",
|
| 666 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 667 |
-
"python.path-join-does-not-contain-a-path",
|
| 668 |
"python.subprocess-shell-true-is-injection"
|
| 669 |
],
|
| 670 |
-
"file_finding_count":
|
|
|
|
| 671 |
"file_suppressed": 0,
|
| 672 |
-
"cited": [
|
| 673 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
"facts": 0,
|
| 675 |
-
"context_verdict": "
|
| 676 |
-
"context_checked":
|
| 677 |
"names_declared": 5,
|
| 678 |
"names_mentioned": 5,
|
| 679 |
-
"answer_chars":
|
| 680 |
"path": "subjects/etl/load_prices.py"
|
| 681 |
},
|
| 682 |
{
|
| 683 |
-
"verdict": "
|
| 684 |
"insufficient": [],
|
| 685 |
-
"seconds":
|
| 686 |
-
"tokens_per_second":
|
| 687 |
"rules_fired": [
|
| 688 |
-
"python.decimal-for-money-not-float"
|
| 689 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 690 |
-
"python.path-join-does-not-contain-a-path"
|
| 691 |
],
|
| 692 |
"layers_with_findings": [
|
| 693 |
-
"
|
| 694 |
],
|
| 695 |
"retrieved": [
|
|
|
|
| 696 |
"python.assert-is-not-validation",
|
| 697 |
"python.subprocess-shell-true-is-injection",
|
| 698 |
-
"python.path-join-does-not-contain-a-path",
|
| 699 |
"python.bare-except-catches-the-exit",
|
| 700 |
"python.mutable-default-argument",
|
| 701 |
-
"python.
|
| 702 |
],
|
| 703 |
-
"stack": "
|
| 704 |
-
"routed_by": "
|
| 705 |
"chars": 2033,
|
| 706 |
"dropped_chars": 0,
|
| 707 |
"coverage": "whole file",
|
|
@@ -710,507 +744,25 @@
|
|
| 710 |
"file_findings": [
|
| 711 |
"python.assert-is-not-validation",
|
| 712 |
"python.bare-except-catches-the-exit",
|
| 713 |
-
"python.decimal-for-money-not-float",
|
| 714 |
"python.mutable-default-argument",
|
| 715 |
-
"python.naive-and-aware-datetimes-do-not-compare",
|
| 716 |
-
"python.path-join-does-not-contain-a-path",
|
| 717 |
"python.subprocess-shell-true-is-injection"
|
| 718 |
],
|
| 719 |
-
"file_finding_count":
|
|
|
|
| 720 |
"file_suppressed": 0,
|
| 721 |
"cited": [
|
| 722 |
-
"python.
|
|
|
|
|
|
|
|
|
|
| 723 |
],
|
| 724 |
"cited_beyond_checkers": [],
|
| 725 |
"facts": 0,
|
| 726 |
-
"context_verdict": "
|
| 727 |
-
"context_checked":
|
| 728 |
"names_declared": 5,
|
| 729 |
"names_mentioned": 5,
|
| 730 |
-
"answer_chars":
|
| 731 |
"path": "subjects/etl/load_prices.py"
|
| 732 |
-
},
|
| 733 |
-
{
|
| 734 |
-
"verdict": "WARN",
|
| 735 |
-
"insufficient": [],
|
| 736 |
-
"seconds": 14.2,
|
| 737 |
-
"tokens_per_second": 41.8,
|
| 738 |
-
"rules_fired": [
|
| 739 |
-
"typescript.unknown-not-any-at-boundaries"
|
| 740 |
-
],
|
| 741 |
-
"layers_with_findings": [
|
| 742 |
-
"domain",
|
| 743 |
-
"structural"
|
| 744 |
-
],
|
| 745 |
-
"retrieved": [
|
| 746 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 747 |
-
"typescript.validate-env-at-startup",
|
| 748 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 749 |
-
"typescript.types-are-erased-at-runtime",
|
| 750 |
-
"typescript.brand-validated-primitives",
|
| 751 |
-
"typescript.safeparse-when-failure-is-expected"
|
| 752 |
-
],
|
| 753 |
-
"stack": "typescript",
|
| 754 |
-
"routed_by": "extension",
|
| 755 |
-
"chars": 1687,
|
| 756 |
-
"dropped_chars": 0,
|
| 757 |
-
"coverage": "whole file",
|
| 758 |
-
"symbols_shown": 0,
|
| 759 |
-
"symbols_total": 0,
|
| 760 |
-
"file_findings": [
|
| 761 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 762 |
-
"typescript.validate-env-at-startup"
|
| 763 |
-
],
|
| 764 |
-
"file_finding_count": 2,
|
| 765 |
-
"file_suppressed": 0,
|
| 766 |
-
"cited": [
|
| 767 |
-
"typescript.types-are-erased-at-runtime",
|
| 768 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 769 |
-
"typescript.validate-env-at-startup"
|
| 770 |
-
],
|
| 771 |
-
"cited_beyond_checkers": [
|
| 772 |
-
"typescript.types-are-erased-at-runtime"
|
| 773 |
-
],
|
| 774 |
-
"facts": 1,
|
| 775 |
-
"context_verdict": "UNKNOWN",
|
| 776 |
-
"context_checked": 0,
|
| 777 |
-
"names_declared": 7,
|
| 778 |
-
"names_mentioned": 7,
|
| 779 |
-
"answer_chars": 2073,
|
| 780 |
-
"path": "subjects/web/src/partners.ts"
|
| 781 |
-
},
|
| 782 |
-
{
|
| 783 |
-
"verdict": "REJECT",
|
| 784 |
-
"insufficient": [],
|
| 785 |
-
"seconds": 12.8,
|
| 786 |
-
"tokens_per_second": 41.4,
|
| 787 |
-
"rules_fired": [
|
| 788 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 789 |
-
"typescript.unknown-not-any-at-boundaries"
|
| 790 |
-
],
|
| 791 |
-
"layers_with_findings": [
|
| 792 |
-
"domain",
|
| 793 |
-
"structural"
|
| 794 |
-
],
|
| 795 |
-
"retrieved": [
|
| 796 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 797 |
-
"typescript.validate-env-at-startup",
|
| 798 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 799 |
-
"typescript.types-are-erased-at-runtime",
|
| 800 |
-
"typescript.brand-validated-primitives",
|
| 801 |
-
"typescript.safeparse-when-failure-is-expected"
|
| 802 |
-
],
|
| 803 |
-
"stack": "typescript",
|
| 804 |
-
"routed_by": "extension",
|
| 805 |
-
"chars": 1687,
|
| 806 |
-
"dropped_chars": 0,
|
| 807 |
-
"coverage": "whole file",
|
| 808 |
-
"symbols_shown": 0,
|
| 809 |
-
"symbols_total": 0,
|
| 810 |
-
"file_findings": [
|
| 811 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 812 |
-
"typescript.validate-env-at-startup"
|
| 813 |
-
],
|
| 814 |
-
"file_finding_count": 2,
|
| 815 |
-
"file_suppressed": 0,
|
| 816 |
-
"cited": [
|
| 817 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 818 |
-
"typescript.validate-env-at-startup"
|
| 819 |
-
],
|
| 820 |
-
"cited_beyond_checkers": [],
|
| 821 |
-
"facts": 1,
|
| 822 |
-
"context_verdict": "UNKNOWN",
|
| 823 |
-
"context_checked": 0,
|
| 824 |
-
"names_declared": 7,
|
| 825 |
-
"names_mentioned": 6,
|
| 826 |
-
"answer_chars": 2036,
|
| 827 |
-
"path": "subjects/web/src/partners.ts"
|
| 828 |
-
},
|
| 829 |
-
{
|
| 830 |
-
"verdict": "WARN",
|
| 831 |
-
"insufficient": [],
|
| 832 |
-
"seconds": 9.2,
|
| 833 |
-
"tokens_per_second": 41.5,
|
| 834 |
-
"rules_fired": [
|
| 835 |
-
"typescript.unknown-not-any-at-boundaries"
|
| 836 |
-
],
|
| 837 |
-
"layers_with_findings": [
|
| 838 |
-
"domain",
|
| 839 |
-
"structural"
|
| 840 |
-
],
|
| 841 |
-
"retrieved": [
|
| 842 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 843 |
-
"typescript.validate-env-at-startup",
|
| 844 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 845 |
-
"typescript.types-are-erased-at-runtime",
|
| 846 |
-
"typescript.brand-validated-primitives",
|
| 847 |
-
"typescript.safeparse-when-failure-is-expected"
|
| 848 |
-
],
|
| 849 |
-
"stack": "typescript",
|
| 850 |
-
"routed_by": "extension",
|
| 851 |
-
"chars": 1687,
|
| 852 |
-
"dropped_chars": 0,
|
| 853 |
-
"coverage": "whole file",
|
| 854 |
-
"symbols_shown": 0,
|
| 855 |
-
"symbols_total": 0,
|
| 856 |
-
"file_findings": [
|
| 857 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 858 |
-
"typescript.validate-env-at-startup"
|
| 859 |
-
],
|
| 860 |
-
"file_finding_count": 2,
|
| 861 |
-
"file_suppressed": 0,
|
| 862 |
-
"cited": [
|
| 863 |
-
"typescript.types-are-erased-at-runtime",
|
| 864 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 865 |
-
"typescript.validate-env-at-startup"
|
| 866 |
-
],
|
| 867 |
-
"cited_beyond_checkers": [
|
| 868 |
-
"typescript.types-are-erased-at-runtime"
|
| 869 |
-
],
|
| 870 |
-
"facts": 1,
|
| 871 |
-
"context_verdict": "UNKNOWN",
|
| 872 |
-
"context_checked": 0,
|
| 873 |
-
"names_declared": 7,
|
| 874 |
-
"names_mentioned": 2,
|
| 875 |
-
"answer_chars": 1400,
|
| 876 |
-
"path": "subjects/web/src/partners.ts"
|
| 877 |
-
},
|
| 878 |
-
{
|
| 879 |
-
"verdict": "WARN",
|
| 880 |
-
"insufficient": [],
|
| 881 |
-
"seconds": 12.8,
|
| 882 |
-
"tokens_per_second": 41.7,
|
| 883 |
-
"rules_fired": [
|
| 884 |
-
"typescript.unknown-not-any-at-boundaries"
|
| 885 |
-
],
|
| 886 |
-
"layers_with_findings": [
|
| 887 |
-
"domain",
|
| 888 |
-
"structural"
|
| 889 |
-
],
|
| 890 |
-
"retrieved": [
|
| 891 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 892 |
-
"typescript.validate-env-at-startup",
|
| 893 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 894 |
-
"typescript.types-are-erased-at-runtime",
|
| 895 |
-
"typescript.brand-validated-primitives",
|
| 896 |
-
"typescript.safeparse-when-failure-is-expected"
|
| 897 |
-
],
|
| 898 |
-
"stack": "typescript",
|
| 899 |
-
"routed_by": "extension",
|
| 900 |
-
"chars": 1687,
|
| 901 |
-
"dropped_chars": 0,
|
| 902 |
-
"coverage": "whole file",
|
| 903 |
-
"symbols_shown": 0,
|
| 904 |
-
"symbols_total": 0,
|
| 905 |
-
"file_findings": [
|
| 906 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 907 |
-
"typescript.validate-env-at-startup"
|
| 908 |
-
],
|
| 909 |
-
"file_finding_count": 2,
|
| 910 |
-
"file_suppressed": 0,
|
| 911 |
-
"cited": [
|
| 912 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 913 |
-
"typescript.validate-env-at-startup"
|
| 914 |
-
],
|
| 915 |
-
"cited_beyond_checkers": [],
|
| 916 |
-
"facts": 1,
|
| 917 |
-
"context_verdict": "UNKNOWN",
|
| 918 |
-
"context_checked": 0,
|
| 919 |
-
"names_declared": 7,
|
| 920 |
-
"names_mentioned": 7,
|
| 921 |
-
"answer_chars": 2080,
|
| 922 |
-
"path": "subjects/web/src/partners.ts"
|
| 923 |
-
},
|
| 924 |
-
{
|
| 925 |
-
"verdict": "REJECT",
|
| 926 |
-
"insufficient": [],
|
| 927 |
-
"seconds": 12.8,
|
| 928 |
-
"tokens_per_second": 41.6,
|
| 929 |
-
"rules_fired": [
|
| 930 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 931 |
-
"typescript.unknown-not-any-at-boundaries"
|
| 932 |
-
],
|
| 933 |
-
"layers_with_findings": [
|
| 934 |
-
"domain",
|
| 935 |
-
"structural"
|
| 936 |
-
],
|
| 937 |
-
"retrieved": [
|
| 938 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 939 |
-
"typescript.validate-env-at-startup",
|
| 940 |
-
"typescript.odoo-returns-false-for-every-empty-field",
|
| 941 |
-
"typescript.types-are-erased-at-runtime",
|
| 942 |
-
"typescript.brand-validated-primitives",
|
| 943 |
-
"typescript.safeparse-when-failure-is-expected"
|
| 944 |
-
],
|
| 945 |
-
"stack": "typescript",
|
| 946 |
-
"routed_by": "extension",
|
| 947 |
-
"chars": 1687,
|
| 948 |
-
"dropped_chars": 0,
|
| 949 |
-
"coverage": "whole file",
|
| 950 |
-
"symbols_shown": 0,
|
| 951 |
-
"symbols_total": 0,
|
| 952 |
-
"file_findings": [
|
| 953 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 954 |
-
"typescript.validate-env-at-startup"
|
| 955 |
-
],
|
| 956 |
-
"file_finding_count": 2,
|
| 957 |
-
"file_suppressed": 0,
|
| 958 |
-
"cited": [
|
| 959 |
-
"typescript.unknown-not-any-at-boundaries",
|
| 960 |
-
"typescript.validate-env-at-startup"
|
| 961 |
-
],
|
| 962 |
-
"cited_beyond_checkers": [],
|
| 963 |
-
"facts": 1,
|
| 964 |
-
"context_verdict": "UNKNOWN",
|
| 965 |
-
"context_checked": 0,
|
| 966 |
-
"names_declared": 7,
|
| 967 |
-
"names_mentioned": 7,
|
| 968 |
-
"answer_chars": 2055,
|
| 969 |
-
"path": "subjects/web/src/partners.ts"
|
| 970 |
-
},
|
| 971 |
-
{
|
| 972 |
-
"verdict": "REJECT",
|
| 973 |
-
"insufficient": [],
|
| 974 |
-
"seconds": 17.0,
|
| 975 |
-
"tokens_per_second": 41.1,
|
| 976 |
-
"rules_fired": [
|
| 977 |
-
"javascript.json-parse-returns-any"
|
| 978 |
-
],
|
| 979 |
-
"layers_with_findings": [
|
| 980 |
-
"domain"
|
| 981 |
-
],
|
| 982 |
-
"retrieved": [
|
| 983 |
-
"javascript.json-parse-returns-any",
|
| 984 |
-
"javascript.async-errors-need-explicit-handling",
|
| 985 |
-
"javascript.number-parsing-is-lossy",
|
| 986 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 987 |
-
"javascript.loose-equality-and-falsy-defaults",
|
| 988 |
-
"javascript.validate-at-the-boundary-once"
|
| 989 |
-
],
|
| 990 |
-
"stack": "javascript",
|
| 991 |
-
"routed_by": "extension",
|
| 992 |
-
"chars": 1401,
|
| 993 |
-
"dropped_chars": 0,
|
| 994 |
-
"coverage": "whole file",
|
| 995 |
-
"symbols_shown": 0,
|
| 996 |
-
"symbols_total": 0,
|
| 997 |
-
"file_findings": [
|
| 998 |
-
"javascript.async-errors-need-explicit-handling",
|
| 999 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1000 |
-
"javascript.json-parse-returns-any",
|
| 1001 |
-
"javascript.number-parsing-is-lossy"
|
| 1002 |
-
],
|
| 1003 |
-
"file_finding_count": 4,
|
| 1004 |
-
"file_suppressed": 0,
|
| 1005 |
-
"cited": [
|
| 1006 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1007 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1008 |
-
"javascript.json-parse-returns-any",
|
| 1009 |
-
"javascript.number-parsing-is-lossy"
|
| 1010 |
-
],
|
| 1011 |
-
"cited_beyond_checkers": [],
|
| 1012 |
-
"facts": 1,
|
| 1013 |
-
"context_verdict": "UNKNOWN",
|
| 1014 |
-
"context_checked": 0,
|
| 1015 |
-
"names_declared": 1,
|
| 1016 |
-
"names_mentioned": 1,
|
| 1017 |
-
"answer_chars": 2424,
|
| 1018 |
-
"path": "subjects/web/src/board.js"
|
| 1019 |
-
},
|
| 1020 |
-
{
|
| 1021 |
-
"verdict": "REJECT",
|
| 1022 |
-
"insufficient": [],
|
| 1023 |
-
"seconds": 16.9,
|
| 1024 |
-
"tokens_per_second": 41.1,
|
| 1025 |
-
"rules_fired": [
|
| 1026 |
-
"javascript.json-parse-returns-any"
|
| 1027 |
-
],
|
| 1028 |
-
"layers_with_findings": [
|
| 1029 |
-
"domain"
|
| 1030 |
-
],
|
| 1031 |
-
"retrieved": [
|
| 1032 |
-
"javascript.json-parse-returns-any",
|
| 1033 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1034 |
-
"javascript.number-parsing-is-lossy",
|
| 1035 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1036 |
-
"javascript.loose-equality-and-falsy-defaults",
|
| 1037 |
-
"javascript.validate-at-the-boundary-once"
|
| 1038 |
-
],
|
| 1039 |
-
"stack": "javascript",
|
| 1040 |
-
"routed_by": "extension",
|
| 1041 |
-
"chars": 1401,
|
| 1042 |
-
"dropped_chars": 0,
|
| 1043 |
-
"coverage": "whole file",
|
| 1044 |
-
"symbols_shown": 0,
|
| 1045 |
-
"symbols_total": 0,
|
| 1046 |
-
"file_findings": [
|
| 1047 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1048 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1049 |
-
"javascript.json-parse-returns-any",
|
| 1050 |
-
"javascript.number-parsing-is-lossy"
|
| 1051 |
-
],
|
| 1052 |
-
"file_finding_count": 4,
|
| 1053 |
-
"file_suppressed": 0,
|
| 1054 |
-
"cited": [
|
| 1055 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1056 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1057 |
-
"javascript.json-parse-returns-any",
|
| 1058 |
-
"javascript.number-parsing-is-lossy"
|
| 1059 |
-
],
|
| 1060 |
-
"cited_beyond_checkers": [],
|
| 1061 |
-
"facts": 1,
|
| 1062 |
-
"context_verdict": "UNKNOWN",
|
| 1063 |
-
"context_checked": 0,
|
| 1064 |
-
"names_declared": 1,
|
| 1065 |
-
"names_mentioned": 1,
|
| 1066 |
-
"answer_chars": 2595,
|
| 1067 |
-
"path": "subjects/web/src/board.js"
|
| 1068 |
-
},
|
| 1069 |
-
{
|
| 1070 |
-
"verdict": "REJECT",
|
| 1071 |
-
"insufficient": [],
|
| 1072 |
-
"seconds": 15.8,
|
| 1073 |
-
"tokens_per_second": 40.6,
|
| 1074 |
-
"rules_fired": [
|
| 1075 |
-
"javascript.json-parse-returns-any"
|
| 1076 |
-
],
|
| 1077 |
-
"layers_with_findings": [
|
| 1078 |
-
"domain"
|
| 1079 |
-
],
|
| 1080 |
-
"retrieved": [
|
| 1081 |
-
"javascript.json-parse-returns-any",
|
| 1082 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1083 |
-
"javascript.number-parsing-is-lossy",
|
| 1084 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1085 |
-
"javascript.loose-equality-and-falsy-defaults",
|
| 1086 |
-
"javascript.validate-at-the-boundary-once"
|
| 1087 |
-
],
|
| 1088 |
-
"stack": "javascript",
|
| 1089 |
-
"routed_by": "extension",
|
| 1090 |
-
"chars": 1401,
|
| 1091 |
-
"dropped_chars": 0,
|
| 1092 |
-
"coverage": "whole file",
|
| 1093 |
-
"symbols_shown": 0,
|
| 1094 |
-
"symbols_total": 0,
|
| 1095 |
-
"file_findings": [
|
| 1096 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1097 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1098 |
-
"javascript.json-parse-returns-any",
|
| 1099 |
-
"javascript.number-parsing-is-lossy"
|
| 1100 |
-
],
|
| 1101 |
-
"file_finding_count": 4,
|
| 1102 |
-
"file_suppressed": 0,
|
| 1103 |
-
"cited": [
|
| 1104 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1105 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1106 |
-
"javascript.json-parse-returns-any",
|
| 1107 |
-
"javascript.number-parsing-is-lossy"
|
| 1108 |
-
],
|
| 1109 |
-
"cited_beyond_checkers": [],
|
| 1110 |
-
"facts": 1,
|
| 1111 |
-
"context_verdict": "UNKNOWN",
|
| 1112 |
-
"context_checked": 0,
|
| 1113 |
-
"names_declared": 1,
|
| 1114 |
-
"names_mentioned": 1,
|
| 1115 |
-
"answer_chars": 2434,
|
| 1116 |
-
"path": "subjects/web/src/board.js"
|
| 1117 |
-
},
|
| 1118 |
-
{
|
| 1119 |
-
"verdict": "REJECT",
|
| 1120 |
-
"insufficient": [],
|
| 1121 |
-
"seconds": 17.4,
|
| 1122 |
-
"tokens_per_second": 39.8,
|
| 1123 |
-
"rules_fired": [
|
| 1124 |
-
"javascript.json-parse-returns-any"
|
| 1125 |
-
],
|
| 1126 |
-
"layers_with_findings": [
|
| 1127 |
-
"domain"
|
| 1128 |
-
],
|
| 1129 |
-
"retrieved": [
|
| 1130 |
-
"javascript.json-parse-returns-any",
|
| 1131 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1132 |
-
"javascript.number-parsing-is-lossy",
|
| 1133 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1134 |
-
"javascript.loose-equality-and-falsy-defaults",
|
| 1135 |
-
"javascript.validate-at-the-boundary-once"
|
| 1136 |
-
],
|
| 1137 |
-
"stack": "javascript",
|
| 1138 |
-
"routed_by": "extension",
|
| 1139 |
-
"chars": 1401,
|
| 1140 |
-
"dropped_chars": 0,
|
| 1141 |
-
"coverage": "whole file",
|
| 1142 |
-
"symbols_shown": 0,
|
| 1143 |
-
"symbols_total": 0,
|
| 1144 |
-
"file_findings": [
|
| 1145 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1146 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1147 |
-
"javascript.json-parse-returns-any",
|
| 1148 |
-
"javascript.number-parsing-is-lossy"
|
| 1149 |
-
],
|
| 1150 |
-
"file_finding_count": 4,
|
| 1151 |
-
"file_suppressed": 0,
|
| 1152 |
-
"cited": [
|
| 1153 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1154 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1155 |
-
"javascript.json-parse-returns-any",
|
| 1156 |
-
"javascript.number-parsing-is-lossy"
|
| 1157 |
-
],
|
| 1158 |
-
"cited_beyond_checkers": [],
|
| 1159 |
-
"facts": 1,
|
| 1160 |
-
"context_verdict": "UNKNOWN",
|
| 1161 |
-
"context_checked": 0,
|
| 1162 |
-
"names_declared": 1,
|
| 1163 |
-
"names_mentioned": 1,
|
| 1164 |
-
"answer_chars": 2586,
|
| 1165 |
-
"path": "subjects/web/src/board.js"
|
| 1166 |
-
},
|
| 1167 |
-
{
|
| 1168 |
-
"verdict": "REJECT",
|
| 1169 |
-
"insufficient": [],
|
| 1170 |
-
"seconds": 17.2,
|
| 1171 |
-
"tokens_per_second": 40.2,
|
| 1172 |
-
"rules_fired": [
|
| 1173 |
-
"javascript.json-parse-returns-any"
|
| 1174 |
-
],
|
| 1175 |
-
"layers_with_findings": [
|
| 1176 |
-
"domain"
|
| 1177 |
-
],
|
| 1178 |
-
"retrieved": [
|
| 1179 |
-
"javascript.json-parse-returns-any",
|
| 1180 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1181 |
-
"javascript.number-parsing-is-lossy",
|
| 1182 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1183 |
-
"javascript.loose-equality-and-falsy-defaults",
|
| 1184 |
-
"javascript.validate-at-the-boundary-once"
|
| 1185 |
-
],
|
| 1186 |
-
"stack": "javascript",
|
| 1187 |
-
"routed_by": "extension",
|
| 1188 |
-
"chars": 1401,
|
| 1189 |
-
"dropped_chars": 0,
|
| 1190 |
-
"coverage": "whole file",
|
| 1191 |
-
"symbols_shown": 0,
|
| 1192 |
-
"symbols_total": 0,
|
| 1193 |
-
"file_findings": [
|
| 1194 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1195 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1196 |
-
"javascript.json-parse-returns-any",
|
| 1197 |
-
"javascript.number-parsing-is-lossy"
|
| 1198 |
-
],
|
| 1199 |
-
"file_finding_count": 4,
|
| 1200 |
-
"file_suppressed": 0,
|
| 1201 |
-
"cited": [
|
| 1202 |
-
"javascript.async-errors-need-explicit-handling",
|
| 1203 |
-
"javascript.date-parsing-is-implementation-defined",
|
| 1204 |
-
"javascript.json-parse-returns-any",
|
| 1205 |
-
"javascript.number-parsing-is-lossy"
|
| 1206 |
-
],
|
| 1207 |
-
"cited_beyond_checkers": [],
|
| 1208 |
-
"facts": 1,
|
| 1209 |
-
"context_verdict": "UNKNOWN",
|
| 1210 |
-
"context_checked": 0,
|
| 1211 |
-
"names_declared": 1,
|
| 1212 |
-
"names_mentioned": 1,
|
| 1213 |
-
"answer_chars": 2583,
|
| 1214 |
-
"path": "subjects/web/src/board.js"
|
| 1215 |
}
|
| 1216 |
]
|
|
|
|
| 1 |
[
|
| 2 |
{
|
| 3 |
+
"verdict": "REJECT",
|
| 4 |
"insufficient": [],
|
| 5 |
+
"seconds": 28.8,
|
| 6 |
+
"tokens_per_second": 26.3,
|
| 7 |
+
"rules_fired": [
|
| 8 |
+
"odoo.constrains-must-list-every-field-read",
|
| 9 |
+
"odoo.float-comparison-needs-float-utils",
|
| 10 |
+
"odoo.prefer-sql-constraint",
|
| 11 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 12 |
+
],
|
| 13 |
"layers_with_findings": [
|
| 14 |
+
"context",
|
| 15 |
+
"domain",
|
| 16 |
+
"semantic"
|
| 17 |
],
|
| 18 |
"retrieved": [
|
| 19 |
"odoo.check-constraint-cannot-cross-tables",
|
| 20 |
"odoo.stored-compute-needs-complete-depends",
|
| 21 |
+
"odoo.sudo-bypasses-record-rules",
|
| 22 |
"odoo.float-comparison-needs-float-utils",
|
| 23 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 24 |
+
"odoo.constrains-must-list-every-field-read"
|
| 25 |
],
|
| 26 |
"stack": "odoo",
|
| 27 |
+
"routed_by": "extension",
|
| 28 |
"chars": 2441,
|
| 29 |
"dropped_chars": 0,
|
| 30 |
"coverage": "whole file",
|
|
|
|
| 32 |
"symbols_total": 0,
|
| 33 |
"file_findings": [
|
| 34 |
"odoo.float-comparison-needs-float-utils",
|
|
|
|
| 35 |
"odoo.stored-compute-needs-complete-depends"
|
| 36 |
],
|
| 37 |
+
"file_finding_count": 2,
|
| 38 |
+
"file_advisory": 1,
|
| 39 |
"file_suppressed": 0,
|
| 40 |
+
"cited": [
|
| 41 |
+
"odoo.float-comparison-needs-float-utils",
|
| 42 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 43 |
+
"odoo.sudo-bypasses-record-rules"
|
| 44 |
+
],
|
| 45 |
"cited_beyond_checkers": [],
|
| 46 |
"facts": 4,
|
| 47 |
+
"context_verdict": "WARN",
|
| 48 |
+
"context_checked": 20,
|
| 49 |
"names_declared": 7,
|
| 50 |
"names_mentioned": 2,
|
| 51 |
+
"answer_chars": 2117,
|
| 52 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 53 |
},
|
| 54 |
{
|
| 55 |
"verdict": "WARN",
|
| 56 |
"insufficient": [],
|
| 57 |
+
"seconds": 4.1,
|
| 58 |
+
"tokens_per_second": 26.8,
|
| 59 |
+
"rules_fired": [
|
| 60 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 61 |
+
],
|
| 62 |
"layers_with_findings": [
|
| 63 |
+
"context",
|
| 64 |
"structural"
|
| 65 |
],
|
| 66 |
"retrieved": [
|
| 67 |
"odoo.check-constraint-cannot-cross-tables",
|
| 68 |
"odoo.stored-compute-needs-complete-depends",
|
| 69 |
+
"odoo.sudo-bypasses-record-rules",
|
| 70 |
"odoo.float-comparison-needs-float-utils",
|
| 71 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 72 |
+
"odoo.constrains-must-list-every-field-read"
|
| 73 |
],
|
| 74 |
"stack": "odoo",
|
| 75 |
+
"routed_by": "extension",
|
| 76 |
"chars": 2441,
|
| 77 |
"dropped_chars": 0,
|
| 78 |
"coverage": "whole file",
|
|
|
|
| 80 |
"symbols_total": 0,
|
| 81 |
"file_findings": [
|
| 82 |
"odoo.float-comparison-needs-float-utils",
|
|
|
|
| 83 |
"odoo.stored-compute-needs-complete-depends"
|
| 84 |
],
|
| 85 |
+
"file_finding_count": 2,
|
| 86 |
+
"file_advisory": 1,
|
| 87 |
"file_suppressed": 0,
|
| 88 |
+
"cited": [
|
| 89 |
+
"odoo.float-comparison-needs-float-utils",
|
| 90 |
+
"odoo.sudo-bypasses-record-rules"
|
| 91 |
+
],
|
| 92 |
"cited_beyond_checkers": [],
|
| 93 |
"facts": 4,
|
| 94 |
+
"context_verdict": "WARN",
|
| 95 |
+
"context_checked": 20,
|
| 96 |
"names_declared": 7,
|
| 97 |
+
"names_mentioned": 1,
|
| 98 |
+
"answer_chars": 401,
|
| 99 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 100 |
},
|
| 101 |
{
|
| 102 |
"verdict": "REJECT",
|
| 103 |
+
"insufficient": [],
|
| 104 |
+
"seconds": 13.5,
|
| 105 |
+
"tokens_per_second": 27.2,
|
| 106 |
+
"rules_fired": [
|
| 107 |
+
"odoo.constrains-must-list-every-field-read",
|
| 108 |
+
"odoo.float-comparison-needs-float-utils",
|
| 109 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 110 |
],
|
|
|
|
|
|
|
|
|
|
| 111 |
"layers_with_findings": [
|
| 112 |
+
"context",
|
| 113 |
+
"domain",
|
| 114 |
+
"semantic"
|
| 115 |
],
|
| 116 |
"retrieved": [
|
| 117 |
"odoo.check-constraint-cannot-cross-tables",
|
| 118 |
"odoo.stored-compute-needs-complete-depends",
|
| 119 |
+
"odoo.sudo-bypasses-record-rules",
|
| 120 |
"odoo.float-comparison-needs-float-utils",
|
| 121 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 122 |
+
"odoo.constrains-must-list-every-field-read"
|
| 123 |
],
|
| 124 |
"stack": "odoo",
|
| 125 |
+
"routed_by": "extension",
|
| 126 |
"chars": 2441,
|
| 127 |
"dropped_chars": 0,
|
| 128 |
"coverage": "whole file",
|
|
|
|
| 130 |
"symbols_total": 0,
|
| 131 |
"file_findings": [
|
| 132 |
"odoo.float-comparison-needs-float-utils",
|
|
|
|
| 133 |
"odoo.stored-compute-needs-complete-depends"
|
| 134 |
],
|
| 135 |
+
"file_finding_count": 2,
|
| 136 |
+
"file_advisory": 1,
|
| 137 |
"file_suppressed": 0,
|
| 138 |
"cited": [
|
|
|
|
| 139 |
"odoo.float-comparison-needs-float-utils",
|
| 140 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 141 |
+
"odoo.sudo-bypasses-record-rules"
|
|
|
|
|
|
|
|
|
|
| 142 |
],
|
| 143 |
+
"cited_beyond_checkers": [],
|
| 144 |
"facts": 4,
|
| 145 |
+
"context_verdict": "WARN",
|
| 146 |
+
"context_checked": 20,
|
| 147 |
"names_declared": 7,
|
| 148 |
"names_mentioned": 2,
|
| 149 |
+
"answer_chars": 1471,
|
| 150 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 151 |
},
|
| 152 |
{
|
| 153 |
"verdict": "WARN",
|
| 154 |
"insufficient": [],
|
| 155 |
+
"seconds": 4.2,
|
| 156 |
+
"tokens_per_second": 26.4,
|
| 157 |
+
"rules_fired": [
|
| 158 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 159 |
+
],
|
| 160 |
"layers_with_findings": [
|
| 161 |
+
"context",
|
| 162 |
"structural"
|
| 163 |
],
|
| 164 |
"retrieved": [
|
| 165 |
"odoo.check-constraint-cannot-cross-tables",
|
| 166 |
"odoo.stored-compute-needs-complete-depends",
|
| 167 |
+
"odoo.sudo-bypasses-record-rules",
|
| 168 |
"odoo.float-comparison-needs-float-utils",
|
| 169 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 170 |
+
"odoo.constrains-must-list-every-field-read"
|
| 171 |
],
|
| 172 |
"stack": "odoo",
|
| 173 |
+
"routed_by": "extension",
|
| 174 |
"chars": 2441,
|
| 175 |
"dropped_chars": 0,
|
| 176 |
"coverage": "whole file",
|
|
|
|
| 178 |
"symbols_total": 0,
|
| 179 |
"file_findings": [
|
| 180 |
"odoo.float-comparison-needs-float-utils",
|
|
|
|
| 181 |
"odoo.stored-compute-needs-complete-depends"
|
| 182 |
],
|
| 183 |
+
"file_finding_count": 2,
|
| 184 |
+
"file_advisory": 1,
|
| 185 |
"file_suppressed": 0,
|
| 186 |
"cited": [
|
| 187 |
+
"odoo.float-comparison-needs-float-utils",
|
| 188 |
+
"odoo.sudo-bypasses-record-rules"
|
| 189 |
],
|
| 190 |
"cited_beyond_checkers": [],
|
| 191 |
"facts": 4,
|
| 192 |
+
"context_verdict": "WARN",
|
| 193 |
+
"context_checked": 20,
|
| 194 |
"names_declared": 7,
|
| 195 |
"names_mentioned": 1,
|
| 196 |
+
"answer_chars": 401,
|
| 197 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 198 |
},
|
| 199 |
{
|
| 200 |
"verdict": "WARN",
|
| 201 |
"insufficient": [],
|
| 202 |
+
"seconds": 17.8,
|
| 203 |
+
"tokens_per_second": 26.8,
|
| 204 |
"rules_fired": [
|
| 205 |
+
"odoo.float-comparison-needs-float-utils",
|
| 206 |
+
"odoo.prefer-sql-constraint",
|
| 207 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 208 |
],
|
| 209 |
"layers_with_findings": [
|
| 210 |
+
"context",
|
| 211 |
+
"domain",
|
| 212 |
+
"semantic"
|
| 213 |
],
|
| 214 |
"retrieved": [
|
| 215 |
"odoo.check-constraint-cannot-cross-tables",
|
| 216 |
"odoo.stored-compute-needs-complete-depends",
|
| 217 |
+
"odoo.sudo-bypasses-record-rules",
|
| 218 |
"odoo.float-comparison-needs-float-utils",
|
| 219 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 220 |
+
"odoo.constrains-must-list-every-field-read"
|
| 221 |
],
|
| 222 |
"stack": "odoo",
|
| 223 |
+
"routed_by": "extension",
|
| 224 |
"chars": 2441,
|
| 225 |
"dropped_chars": 0,
|
| 226 |
"coverage": "whole file",
|
|
|
|
| 228 |
"symbols_total": 0,
|
| 229 |
"file_findings": [
|
| 230 |
"odoo.float-comparison-needs-float-utils",
|
|
|
|
| 231 |
"odoo.stored-compute-needs-complete-depends"
|
| 232 |
],
|
| 233 |
+
"file_finding_count": 2,
|
| 234 |
+
"file_advisory": 1,
|
| 235 |
"file_suppressed": 0,
|
| 236 |
+
"cited": [
|
| 237 |
+
"odoo.float-comparison-needs-float-utils",
|
| 238 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 239 |
+
"odoo.sudo-bypasses-record-rules"
|
| 240 |
+
],
|
| 241 |
"cited_beyond_checkers": [],
|
| 242 |
"facts": 4,
|
| 243 |
+
"context_verdict": "WARN",
|
| 244 |
+
"context_checked": 20,
|
| 245 |
"names_declared": 7,
|
| 246 |
"names_mentioned": 2,
|
| 247 |
+
"answer_chars": 1940,
|
| 248 |
"path": "subjects/odoo_module/models/fsm_job.py"
|
| 249 |
},
|
| 250 |
{
|
| 251 |
+
"verdict": "WARN",
|
| 252 |
"insufficient": [
|
| 253 |
"9 of 20 symbols were not reviewed"
|
| 254 |
],
|
| 255 |
+
"seconds": 15.3,
|
| 256 |
+
"tokens_per_second": 27.6,
|
| 257 |
"rules_fired": [
|
| 258 |
+
"odoo.depends-must-not-name-unread-fields",
|
| 259 |
"odoo.prefer-sql-constraint",
|
| 260 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 261 |
],
|
| 262 |
"layers_with_findings": [
|
| 263 |
+
"context",
|
| 264 |
+
"domain",
|
| 265 |
"semantic"
|
| 266 |
],
|
| 267 |
"retrieved": [
|
| 268 |
"odoo.check-constraint-cannot-cross-tables",
|
| 269 |
"odoo.constrains-must-list-every-field-read",
|
| 270 |
"odoo.stored-compute-needs-complete-depends",
|
| 271 |
+
"odoo.sudo-bypasses-record-rules",
|
| 272 |
"odoo.prefer-sql-constraint",
|
| 273 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
|
|
|
| 274 |
],
|
| 275 |
"stack": "odoo",
|
| 276 |
+
"routed_by": "extension",
|
| 277 |
"chars": 7929,
|
| 278 |
+
"dropped_chars": 2662,
|
| 279 |
"coverage": "by symbol",
|
| 280 |
"symbols_shown": 11,
|
| 281 |
"symbols_total": 20,
|
| 282 |
"file_findings": [
|
| 283 |
"odoo.constrains-must-list-every-field-read",
|
|
|
|
| 284 |
"odoo.stored-compute-needs-complete-depends"
|
| 285 |
],
|
| 286 |
+
"file_finding_count": 4,
|
| 287 |
+
"file_advisory": 2,
|
| 288 |
"file_suppressed": 0,
|
| 289 |
"cited": [
|
| 290 |
+
"odoo.constrains-must-list-every-field-read"
|
|
|
|
| 291 |
],
|
| 292 |
"cited_beyond_checkers": [],
|
| 293 |
"facts": 4,
|
| 294 |
+
"context_verdict": "WARN",
|
| 295 |
+
"context_checked": 20,
|
| 296 |
"names_declared": 19,
|
| 297 |
+
"names_mentioned": 2,
|
| 298 |
+
"answer_chars": 1404,
|
| 299 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 300 |
},
|
| 301 |
{
|
|
|
|
| 303 |
"insufficient": [
|
| 304 |
"9 of 20 symbols were not reviewed"
|
| 305 |
],
|
| 306 |
+
"seconds": 15.5,
|
| 307 |
+
"tokens_per_second": 27.3,
|
| 308 |
"rules_fired": [
|
| 309 |
"odoo.prefer-sql-constraint",
|
| 310 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 311 |
"odoo.stored-compute-needs-complete-depends"
|
| 312 |
],
|
| 313 |
"layers_with_findings": [
|
| 314 |
+
"context",
|
| 315 |
+
"domain",
|
| 316 |
+
"semantic",
|
| 317 |
+
"structural"
|
| 318 |
],
|
| 319 |
"retrieved": [
|
| 320 |
"odoo.check-constraint-cannot-cross-tables",
|
| 321 |
"odoo.constrains-must-list-every-field-read",
|
| 322 |
"odoo.stored-compute-needs-complete-depends",
|
| 323 |
+
"odoo.sudo-bypasses-record-rules",
|
| 324 |
"odoo.prefer-sql-constraint",
|
| 325 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
|
|
|
| 326 |
],
|
| 327 |
"stack": "odoo",
|
| 328 |
+
"routed_by": "extension",
|
| 329 |
"chars": 7929,
|
| 330 |
+
"dropped_chars": 2662,
|
| 331 |
"coverage": "by symbol",
|
| 332 |
"symbols_shown": 11,
|
| 333 |
"symbols_total": 20,
|
| 334 |
"file_findings": [
|
| 335 |
"odoo.constrains-must-list-every-field-read",
|
|
|
|
| 336 |
"odoo.stored-compute-needs-complete-depends"
|
| 337 |
],
|
| 338 |
+
"file_finding_count": 4,
|
| 339 |
+
"file_advisory": 2,
|
| 340 |
"file_suppressed": 0,
|
| 341 |
"cited": [
|
| 342 |
"odoo.constrains-must-list-every-field-read",
|
| 343 |
+
"odoo.prefer-sql-constraint",
|
| 344 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 345 |
+
"odoo.sudo-bypasses-record-rules"
|
| 346 |
],
|
| 347 |
"cited_beyond_checkers": [],
|
| 348 |
"facts": 4,
|
| 349 |
+
"context_verdict": "WARN",
|
| 350 |
+
"context_checked": 20,
|
| 351 |
"names_declared": 19,
|
| 352 |
+
"names_mentioned": 2,
|
| 353 |
+
"answer_chars": 1676,
|
| 354 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 355 |
},
|
| 356 |
{
|
|
|
|
| 358 |
"insufficient": [
|
| 359 |
"9 of 20 symbols were not reviewed"
|
| 360 |
],
|
| 361 |
+
"seconds": 18.2,
|
| 362 |
+
"tokens_per_second": 26.5,
|
| 363 |
"rules_fired": [
|
| 364 |
"odoo.prefer-sql-constraint",
|
| 365 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 366 |
"odoo.stored-compute-needs-complete-depends"
|
| 367 |
],
|
| 368 |
"layers_with_findings": [
|
| 369 |
+
"context",
|
| 370 |
+
"domain",
|
| 371 |
+
"semantic",
|
| 372 |
+
"structural",
|
| 373 |
+
"syntactic"
|
| 374 |
],
|
| 375 |
"retrieved": [
|
| 376 |
"odoo.check-constraint-cannot-cross-tables",
|
| 377 |
"odoo.constrains-must-list-every-field-read",
|
| 378 |
"odoo.stored-compute-needs-complete-depends",
|
| 379 |
+
"odoo.sudo-bypasses-record-rules",
|
| 380 |
"odoo.prefer-sql-constraint",
|
| 381 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
|
|
|
| 382 |
],
|
| 383 |
"stack": "odoo",
|
| 384 |
+
"routed_by": "extension",
|
| 385 |
"chars": 7929,
|
| 386 |
+
"dropped_chars": 2662,
|
| 387 |
"coverage": "by symbol",
|
| 388 |
"symbols_shown": 11,
|
| 389 |
"symbols_total": 20,
|
| 390 |
"file_findings": [
|
| 391 |
"odoo.constrains-must-list-every-field-read",
|
|
|
|
| 392 |
"odoo.stored-compute-needs-complete-depends"
|
| 393 |
],
|
| 394 |
+
"file_finding_count": 4,
|
| 395 |
+
"file_advisory": 2,
|
| 396 |
"file_suppressed": 0,
|
| 397 |
"cited": [
|
| 398 |
"odoo.constrains-must-list-every-field-read",
|
| 399 |
+
"odoo.prefer-sql-constraint",
|
| 400 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 401 |
+
"odoo.sudo-bypasses-record-rules"
|
| 402 |
],
|
| 403 |
"cited_beyond_checkers": [],
|
| 404 |
"facts": 4,
|
| 405 |
+
"context_verdict": "WARN",
|
| 406 |
+
"context_checked": 20,
|
| 407 |
"names_declared": 19,
|
| 408 |
+
"names_mentioned": 2,
|
| 409 |
+
"answer_chars": 1936,
|
| 410 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 411 |
},
|
| 412 |
{
|
| 413 |
+
"verdict": "WARN",
|
| 414 |
"insufficient": [
|
| 415 |
"9 of 20 symbols were not reviewed"
|
| 416 |
],
|
| 417 |
+
"seconds": 14.8,
|
| 418 |
+
"tokens_per_second": 27.0,
|
| 419 |
"rules_fired": [
|
| 420 |
+
"odoo.depends-must-not-name-unread-fields",
|
| 421 |
"odoo.prefer-sql-constraint",
|
| 422 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 423 |
],
|
| 424 |
"layers_with_findings": [
|
| 425 |
+
"context",
|
| 426 |
+
"domain",
|
| 427 |
"semantic"
|
| 428 |
],
|
| 429 |
"retrieved": [
|
| 430 |
"odoo.check-constraint-cannot-cross-tables",
|
| 431 |
"odoo.constrains-must-list-every-field-read",
|
| 432 |
"odoo.stored-compute-needs-complete-depends",
|
| 433 |
+
"odoo.sudo-bypasses-record-rules",
|
| 434 |
"odoo.prefer-sql-constraint",
|
| 435 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
|
|
|
| 436 |
],
|
| 437 |
"stack": "odoo",
|
| 438 |
+
"routed_by": "extension",
|
| 439 |
"chars": 7929,
|
| 440 |
+
"dropped_chars": 2662,
|
| 441 |
"coverage": "by symbol",
|
| 442 |
"symbols_shown": 11,
|
| 443 |
"symbols_total": 20,
|
| 444 |
"file_findings": [
|
| 445 |
"odoo.constrains-must-list-every-field-read",
|
|
|
|
| 446 |
"odoo.stored-compute-needs-complete-depends"
|
| 447 |
],
|
| 448 |
+
"file_finding_count": 4,
|
| 449 |
+
"file_advisory": 2,
|
| 450 |
"file_suppressed": 0,
|
| 451 |
"cited": [
|
| 452 |
+
"odoo.constrains-must-list-every-field-read"
|
|
|
|
| 453 |
],
|
| 454 |
"cited_beyond_checkers": [],
|
| 455 |
"facts": 4,
|
| 456 |
+
"context_verdict": "WARN",
|
| 457 |
+
"context_checked": 20,
|
| 458 |
"names_declared": 19,
|
| 459 |
+
"names_mentioned": 2,
|
| 460 |
+
"answer_chars": 1584,
|
| 461 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 462 |
},
|
| 463 |
{
|
|
|
|
| 465 |
"insufficient": [
|
| 466 |
"9 of 20 symbols were not reviewed"
|
| 467 |
],
|
| 468 |
+
"seconds": 13.9,
|
| 469 |
+
"tokens_per_second": 27.1,
|
| 470 |
"rules_fired": [
|
| 471 |
+
"odoo.prefer-sql-constraint",
|
| 472 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 473 |
"odoo.stored-compute-needs-complete-depends"
|
| 474 |
],
|
| 475 |
"layers_with_findings": [
|
| 476 |
+
"context",
|
| 477 |
+
"domain",
|
| 478 |
"semantic",
|
| 479 |
"structural"
|
| 480 |
],
|
|
|
|
| 482 |
"odoo.check-constraint-cannot-cross-tables",
|
| 483 |
"odoo.constrains-must-list-every-field-read",
|
| 484 |
"odoo.stored-compute-needs-complete-depends",
|
| 485 |
+
"odoo.sudo-bypasses-record-rules",
|
| 486 |
"odoo.prefer-sql-constraint",
|
| 487 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
|
|
|
| 488 |
],
|
| 489 |
"stack": "odoo",
|
| 490 |
+
"routed_by": "extension",
|
| 491 |
"chars": 7929,
|
| 492 |
+
"dropped_chars": 2662,
|
| 493 |
"coverage": "by symbol",
|
| 494 |
"symbols_shown": 11,
|
| 495 |
"symbols_total": 20,
|
| 496 |
"file_findings": [
|
| 497 |
"odoo.constrains-must-list-every-field-read",
|
|
|
|
| 498 |
"odoo.stored-compute-needs-complete-depends"
|
| 499 |
],
|
| 500 |
+
"file_finding_count": 4,
|
| 501 |
+
"file_advisory": 2,
|
| 502 |
"file_suppressed": 0,
|
| 503 |
"cited": [
|
| 504 |
"odoo.constrains-must-list-every-field-read",
|
| 505 |
"odoo.prefer-sql-constraint",
|
| 506 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 507 |
+
"odoo.sudo-bypasses-record-rules"
|
| 508 |
],
|
| 509 |
"cited_beyond_checkers": [],
|
| 510 |
"facts": 4,
|
| 511 |
+
"context_verdict": "WARN",
|
| 512 |
+
"context_checked": 20,
|
| 513 |
"names_declared": 19,
|
| 514 |
"names_mentioned": 2,
|
| 515 |
+
"answer_chars": 1444,
|
| 516 |
"path": "subjects/odoo_module/models/fsm_visit.py"
|
| 517 |
},
|
| 518 |
{
|
| 519 |
+
"verdict": "PASS",
|
| 520 |
"insufficient": [],
|
| 521 |
+
"seconds": 22.4,
|
| 522 |
+
"tokens_per_second": 26.9,
|
| 523 |
"rules_fired": [
|
| 524 |
+
"python.decimal-for-money-not-float"
|
|
|
|
|
|
|
| 525 |
],
|
| 526 |
"layers_with_findings": [
|
| 527 |
+
"adversarial"
|
|
|
|
| 528 |
],
|
| 529 |
"retrieved": [
|
| 530 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 531 |
"python.assert-is-not-validation",
|
| 532 |
"python.subprocess-shell-true-is-injection",
|
|
|
|
| 533 |
"python.bare-except-catches-the-exit",
|
| 534 |
"python.mutable-default-argument",
|
| 535 |
+
"python.eval-and-pickle-execute-their-input"
|
| 536 |
],
|
| 537 |
+
"stack": "odoo",
|
| 538 |
+
"routed_by": "extension",
|
| 539 |
"chars": 2033,
|
| 540 |
"dropped_chars": 0,
|
| 541 |
"coverage": "whole file",
|
|
|
|
| 544 |
"file_findings": [
|
| 545 |
"python.assert-is-not-validation",
|
| 546 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 547 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 548 |
"python.subprocess-shell-true-is-injection"
|
| 549 |
],
|
| 550 |
+
"file_finding_count": 4,
|
| 551 |
+
"file_advisory": 0,
|
| 552 |
"file_suppressed": 0,
|
| 553 |
"cited": [
|
| 554 |
"python.assert-is-not-validation",
|
| 555 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 556 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 557 |
"python.subprocess-shell-true-is-injection"
|
| 558 |
],
|
| 559 |
"cited_beyond_checkers": [],
|
| 560 |
"facts": 0,
|
| 561 |
+
"context_verdict": "PASS",
|
| 562 |
+
"context_checked": 20,
|
| 563 |
"names_declared": 5,
|
| 564 |
"names_mentioned": 5,
|
| 565 |
+
"answer_chars": 2300,
|
| 566 |
"path": "subjects/etl/load_prices.py"
|
| 567 |
},
|
| 568 |
{
|
| 569 |
+
"verdict": "PASS",
|
| 570 |
"insufficient": [],
|
| 571 |
+
"seconds": 25.1,
|
| 572 |
+
"tokens_per_second": 26.8,
|
| 573 |
"rules_fired": [
|
| 574 |
+
"python.decimal-for-money-not-float"
|
|
|
|
|
|
|
| 575 |
],
|
| 576 |
"layers_with_findings": [
|
| 577 |
+
"adversarial"
|
| 578 |
],
|
| 579 |
"retrieved": [
|
| 580 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 581 |
"python.assert-is-not-validation",
|
| 582 |
"python.subprocess-shell-true-is-injection",
|
|
|
|
| 583 |
"python.bare-except-catches-the-exit",
|
| 584 |
"python.mutable-default-argument",
|
| 585 |
+
"python.eval-and-pickle-execute-their-input"
|
| 586 |
],
|
| 587 |
+
"stack": "odoo",
|
| 588 |
+
"routed_by": "extension",
|
| 589 |
"chars": 2033,
|
| 590 |
"dropped_chars": 0,
|
| 591 |
"coverage": "whole file",
|
|
|
|
| 594 |
"file_findings": [
|
| 595 |
"python.assert-is-not-validation",
|
| 596 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 597 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 598 |
"python.subprocess-shell-true-is-injection"
|
| 599 |
],
|
| 600 |
+
"file_finding_count": 4,
|
| 601 |
+
"file_advisory": 0,
|
| 602 |
"file_suppressed": 0,
|
| 603 |
"cited": [
|
| 604 |
"python.assert-is-not-validation",
|
| 605 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 606 |
"python.mutable-default-argument",
|
|
|
|
| 607 |
"python.subprocess-shell-true-is-injection"
|
| 608 |
],
|
| 609 |
"cited_beyond_checkers": [],
|
| 610 |
"facts": 0,
|
| 611 |
+
"context_verdict": "PASS",
|
| 612 |
+
"context_checked": 20,
|
| 613 |
"names_declared": 5,
|
| 614 |
"names_mentioned": 5,
|
| 615 |
+
"answer_chars": 2793,
|
| 616 |
"path": "subjects/etl/load_prices.py"
|
| 617 |
},
|
| 618 |
{
|
| 619 |
+
"verdict": "PASS",
|
| 620 |
"insufficient": [],
|
| 621 |
+
"seconds": 17.0,
|
| 622 |
+
"tokens_per_second": 26.9,
|
| 623 |
"rules_fired": [
|
| 624 |
+
"python.decimal-for-money-not-float"
|
|
|
|
|
|
|
| 625 |
],
|
| 626 |
"layers_with_findings": [
|
| 627 |
+
"adversarial"
|
| 628 |
],
|
| 629 |
"retrieved": [
|
| 630 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 631 |
"python.assert-is-not-validation",
|
| 632 |
"python.subprocess-shell-true-is-injection",
|
|
|
|
| 633 |
"python.bare-except-catches-the-exit",
|
| 634 |
"python.mutable-default-argument",
|
| 635 |
+
"python.eval-and-pickle-execute-their-input"
|
| 636 |
],
|
| 637 |
+
"stack": "odoo",
|
| 638 |
+
"routed_by": "extension",
|
| 639 |
"chars": 2033,
|
| 640 |
"dropped_chars": 0,
|
| 641 |
"coverage": "whole file",
|
|
|
|
| 644 |
"file_findings": [
|
| 645 |
"python.assert-is-not-validation",
|
| 646 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 647 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 648 |
"python.subprocess-shell-true-is-injection"
|
| 649 |
],
|
| 650 |
+
"file_finding_count": 4,
|
| 651 |
+
"file_advisory": 0,
|
| 652 |
"file_suppressed": 0,
|
| 653 |
"cited": [
|
| 654 |
"python.mutable-default-argument"
|
| 655 |
],
|
| 656 |
"cited_beyond_checkers": [],
|
| 657 |
"facts": 0,
|
| 658 |
+
"context_verdict": "PASS",
|
| 659 |
+
"context_checked": 20,
|
| 660 |
"names_declared": 5,
|
| 661 |
"names_mentioned": 5,
|
| 662 |
+
"answer_chars": 1836,
|
| 663 |
"path": "subjects/etl/load_prices.py"
|
| 664 |
},
|
| 665 |
{
|
| 666 |
+
"verdict": "PASS",
|
| 667 |
"insufficient": [],
|
| 668 |
+
"seconds": 21.1,
|
| 669 |
+
"tokens_per_second": 26.6,
|
| 670 |
"rules_fired": [
|
| 671 |
+
"python.decimal-for-money-not-float"
|
|
|
|
|
|
|
| 672 |
],
|
| 673 |
"layers_with_findings": [
|
| 674 |
+
"adversarial"
|
| 675 |
],
|
| 676 |
"retrieved": [
|
| 677 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 678 |
"python.assert-is-not-validation",
|
| 679 |
"python.subprocess-shell-true-is-injection",
|
|
|
|
| 680 |
"python.bare-except-catches-the-exit",
|
| 681 |
"python.mutable-default-argument",
|
| 682 |
+
"python.eval-and-pickle-execute-their-input"
|
| 683 |
],
|
| 684 |
+
"stack": "odoo",
|
| 685 |
+
"routed_by": "extension",
|
| 686 |
"chars": 2033,
|
| 687 |
"dropped_chars": 0,
|
| 688 |
"coverage": "whole file",
|
|
|
|
| 691 |
"file_findings": [
|
| 692 |
"python.assert-is-not-validation",
|
| 693 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 694 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 695 |
"python.subprocess-shell-true-is-injection"
|
| 696 |
],
|
| 697 |
+
"file_finding_count": 4,
|
| 698 |
+
"file_advisory": 0,
|
| 699 |
"file_suppressed": 0,
|
| 700 |
+
"cited": [
|
| 701 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 702 |
+
"python.assert-is-not-validation",
|
| 703 |
+
"python.bare-except-catches-the-exit",
|
| 704 |
+
"python.mutable-default-argument",
|
| 705 |
+
"python.subprocess-shell-true-is-injection"
|
| 706 |
+
],
|
| 707 |
+
"cited_beyond_checkers": [
|
| 708 |
+
"odoo.check-constraint-cannot-cross-tables"
|
| 709 |
+
],
|
| 710 |
"facts": 0,
|
| 711 |
+
"context_verdict": "PASS",
|
| 712 |
+
"context_checked": 20,
|
| 713 |
"names_declared": 5,
|
| 714 |
"names_mentioned": 5,
|
| 715 |
+
"answer_chars": 2265,
|
| 716 |
"path": "subjects/etl/load_prices.py"
|
| 717 |
},
|
| 718 |
{
|
| 719 |
+
"verdict": "PASS",
|
| 720 |
"insufficient": [],
|
| 721 |
+
"seconds": 25.0,
|
| 722 |
+
"tokens_per_second": 26.7,
|
| 723 |
"rules_fired": [
|
| 724 |
+
"python.decimal-for-money-not-float"
|
|
|
|
|
|
|
| 725 |
],
|
| 726 |
"layers_with_findings": [
|
| 727 |
+
"adversarial"
|
| 728 |
],
|
| 729 |
"retrieved": [
|
| 730 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 731 |
"python.assert-is-not-validation",
|
| 732 |
"python.subprocess-shell-true-is-injection",
|
|
|
|
| 733 |
"python.bare-except-catches-the-exit",
|
| 734 |
"python.mutable-default-argument",
|
| 735 |
+
"python.eval-and-pickle-execute-their-input"
|
| 736 |
],
|
| 737 |
+
"stack": "odoo",
|
| 738 |
+
"routed_by": "extension",
|
| 739 |
"chars": 2033,
|
| 740 |
"dropped_chars": 0,
|
| 741 |
"coverage": "whole file",
|
|
|
|
| 744 |
"file_findings": [
|
| 745 |
"python.assert-is-not-validation",
|
| 746 |
"python.bare-except-catches-the-exit",
|
|
|
|
| 747 |
"python.mutable-default-argument",
|
|
|
|
|
|
|
| 748 |
"python.subprocess-shell-true-is-injection"
|
| 749 |
],
|
| 750 |
+
"file_finding_count": 4,
|
| 751 |
+
"file_advisory": 0,
|
| 752 |
"file_suppressed": 0,
|
| 753 |
"cited": [
|
| 754 |
+
"python.assert-is-not-validation",
|
| 755 |
+
"python.bare-except-catches-the-exit",
|
| 756 |
+
"python.mutable-default-argument",
|
| 757 |
+
"python.subprocess-shell-true-is-injection"
|
| 758 |
],
|
| 759 |
"cited_beyond_checkers": [],
|
| 760 |
"facts": 0,
|
| 761 |
+
"context_verdict": "PASS",
|
| 762 |
+
"context_checked": 20,
|
| 763 |
"names_declared": 5,
|
| 764 |
"names_mentioned": 5,
|
| 765 |
+
"answer_chars": 2798,
|
| 766 |
"path": "subjects/etl/load_prices.py"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 767 |
}
|
| 768 |
]
|
|
@@ -309,16 +309,16 @@ COMMANDS = {
|
|
| 309 |
"refused by name -- Odoo rules over a .ts file find nothing, and\n"
|
| 310 |
"nothing reads as a clean bill of health.\n\n"
|
| 311 |
" --no-model\n"
|
| 312 |
-
" stop after the checkers. No generation, no Ollama,
|
| 313 |
-
"
|
| 314 |
-
" What the model adds on top of the checkers
|
| 315 |
-
"
|
| 316 |
-
"
|
| 317 |
-
"
|
| 318 |
" So the generative half stays the default, because a person\n"
|
| 319 |
" reading a file wants the part no pattern can decide. And a\n"
|
| 320 |
-
" gate does not, because
|
| 321 |
-
"
|
| 322 |
" It prints no verdict, because there is nothing to have a\n"
|
| 323 |
" verdict about: a clean run says no pattern fired, and says\n"
|
| 324 |
" so in those words. Exit 1 if anything did.",
|
|
|
|
| 309 |
"refused by name -- Odoo rules over a .ts file find nothing, and\n"
|
| 310 |
"nothing reads as a clean bill of health.\n\n"
|
| 311 |
" --no-model\n"
|
| 312 |
+
" stop after the checkers. No generation, no Ollama, under a\n"
|
| 313 |
+
" second, and the same output every run.\n\n"
|
| 314 |
+
" What the model adds on top of the checkers is measured by\n"
|
| 315 |
+
" nexa eval --check: it rarely cites a rule the checkers had\n"
|
| 316 |
+
" not already found, and the checkers give the identical\n"
|
| 317 |
+
" findings on every run.\n\n"
|
| 318 |
" So the generative half stays the default, because a person\n"
|
| 319 |
" reading a file wants the part no pattern can decide. And a\n"
|
| 320 |
+
" gate does not, because the same file does not always reach\n"
|
| 321 |
+
" the same verdict twice.\n\n"
|
| 322 |
" It prints no verdict, because there is nothing to have a\n"
|
| 323 |
" verdict about: a clean run says no pattern fired, and says\n"
|
| 324 |
" so in those words. Exit 1 if anything did.",
|
|
@@ -326,10 +326,10 @@ def cmd_check(args):
|
|
| 326 |
print()
|
| 327 |
|
| 328 |
# Everything above is the checkers, and the checkers are the whole of
|
| 329 |
-
# --no-model. Measured
|
| 330 |
-
# checkers had not already
|
| 331 |
-
# no pattern can decide -- and a pipeline that only wants the part
|
| 332 |
-
# CAN decide should not be paying seconds and non-determinism for it.
|
| 333 |
if no_model:
|
| 334 |
print(" {0}--no-model: no model ran{1}".format(DIM, RESET))
|
| 335 |
print(" {0}This is not a verdict about the file. What a checker can "
|
|
|
|
| 326 |
print()
|
| 327 |
|
| 328 |
# Everything above is the checkers, and the checkers are the whole of
|
| 329 |
+
# --no-model. Measured by `nexa eval --check`, the model rarely cites a rule
|
| 330 |
+
# the checkers had not already found, so the generative half is buying the
|
| 331 |
+
# part no pattern can decide -- and a pipeline that only wants the part
|
| 332 |
+
# patterns CAN decide should not be paying seconds and non-determinism for it.
|
| 333 |
if no_model:
|
| 334 |
print(" {0}--no-model: no model ran{1}".format(DIM, RESET))
|
| 335 |
print(" {0}This is not a verdict about the file. What a checker can "
|
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"generated_utc": "2026-09-27T21:48:25+00:00",
|
| 3 |
+
"k": 6,
|
| 4 |
+
"embedder": "nomic-embed-text",
|
| 5 |
+
"corpus_sha256": "85e403bf09cb22dee3c0401af08234ec5197e78a070e2f11cd66a72fb2c590a6",
|
| 6 |
+
"rows": [
|
| 7 |
+
{
|
| 8 |
+
"id": "odoo.onchange-is-not-validation",
|
| 9 |
+
"stack": "odoo",
|
| 10 |
+
"probe": "I added an onchange to reject a bad value but a CSV import still creates the record",
|
| 11 |
+
"pinned": false,
|
| 12 |
+
"rank": 2,
|
| 13 |
+
"rank_unpinned": 1,
|
| 14 |
+
"score": 0.7373,
|
| 15 |
+
"instead": [
|
| 16 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 17 |
+
"odoo.constrains-iterate-recordset",
|
| 18 |
+
"odoo.noupdate-on-user-editable-data"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"id": "odoo.constrains-must-list-every-field-read",
|
| 23 |
+
"stack": "odoo",
|
| 24 |
+
"probe": "my constraint does not run when only one of the two dates is written",
|
| 25 |
+
"pinned": false,
|
| 26 |
+
"rank": 2,
|
| 27 |
+
"rank_unpinned": 1,
|
| 28 |
+
"score": 0.7273,
|
| 29 |
+
"instead": [
|
| 30 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 31 |
+
"odoo.prefer-sql-constraint",
|
| 32 |
+
"odoo.sql-constraints-are-ignored-from-19"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"id": "odoo.constrains-ignores-dotted-names",
|
| 37 |
+
"stack": "odoo",
|
| 38 |
+
"probe": "constrains on partner_id.country_id never fires and raises no error",
|
| 39 |
+
"pinned": false,
|
| 40 |
+
"rank": 2,
|
| 41 |
+
"rank_unpinned": 1,
|
| 42 |
+
"score": 0.7002,
|
| 43 |
+
"instead": [
|
| 44 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 45 |
+
"odoo.constrains-must-list-every-field-read",
|
| 46 |
+
"odoo.onchange-is-not-validation"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"id": "odoo.float-comparison-needs-float-utils",
|
| 51 |
+
"stack": "odoo",
|
| 52 |
+
"probe": "comparing a price or a quantity to zero gives the wrong answer sometimes",
|
| 53 |
+
"pinned": false,
|
| 54 |
+
"rank": 2,
|
| 55 |
+
"rank_unpinned": 1,
|
| 56 |
+
"score": 0.7337,
|
| 57 |
+
"instead": [
|
| 58 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 59 |
+
"python.decimal-for-money-not-float",
|
| 60 |
+
"python.assert-is-not-validation"
|
| 61 |
+
]
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"id": "odoo.constrains-iterate-recordset",
|
| 65 |
+
"stack": "odoo",
|
| 66 |
+
"probe": "expected singleton error when a batch import writes several records at once",
|
| 67 |
+
"pinned": false,
|
| 68 |
+
"rank": 2,
|
| 69 |
+
"rank_unpinned": 1,
|
| 70 |
+
"score": 0.8097,
|
| 71 |
+
"instead": [
|
| 72 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 73 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 74 |
+
"odoo.onchange-is-not-validation"
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"id": "odoo.prefer-sql-constraint",
|
| 79 |
+
"stack": "odoo",
|
| 80 |
+
"probe": "should this check be a database constraint or an api.constrains method",
|
| 81 |
+
"pinned": false,
|
| 82 |
+
"rank": 2,
|
| 83 |
+
"rank_unpinned": 1,
|
| 84 |
+
"score": 0.8032,
|
| 85 |
+
"instead": [
|
| 86 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 87 |
+
"odoo.constrains-must-list-every-field-read",
|
| 88 |
+
"odoo.constrains-ignores-dotted-names"
|
| 89 |
+
]
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"id": "odoo.sql-constraints-are-ignored-from-19",
|
| 93 |
+
"stack": "odoo",
|
| 94 |
+
"probe": "after upgrading to Odoo 19 my unique constraint is not enforced and duplicates are saved",
|
| 95 |
+
"pinned": false,
|
| 96 |
+
"rank": 2,
|
| 97 |
+
"rank_unpinned": 1,
|
| 98 |
+
"score": 0.7976,
|
| 99 |
+
"instead": [
|
| 100 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 101 |
+
"odoo.prefer-sql-constraint",
|
| 102 |
+
"odoo.constrains-iterate-recordset"
|
| 103 |
+
]
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"id": "odoo.check-constraint-cannot-cross-tables",
|
| 107 |
+
"stack": "odoo",
|
| 108 |
+
"probe": "my CHECK constraint will not install because it reads the product stock",
|
| 109 |
+
"pinned": true,
|
| 110 |
+
"rank": 1,
|
| 111 |
+
"rank_unpinned": null,
|
| 112 |
+
"score": 1.0,
|
| 113 |
+
"instead": [
|
| 114 |
+
"odoo.sql-constraints-are-ignored-from-19",
|
| 115 |
+
"odoo.constrains-must-list-every-field-read",
|
| 116 |
+
"odoo.prefer-sql-constraint"
|
| 117 |
+
]
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"id": "odoo.check-company-on-relations",
|
| 121 |
+
"stack": "odoo",
|
| 122 |
+
"probe": "a user in one company can select another company's journal on a document",
|
| 123 |
+
"pinned": false,
|
| 124 |
+
"rank": 2,
|
| 125 |
+
"rank_unpinned": 1,
|
| 126 |
+
"score": 0.6868,
|
| 127 |
+
"instead": [
|
| 128 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 129 |
+
"odoo.sudo-bypasses-record-rules",
|
| 130 |
+
"odoo.onchange-is-not-validation"
|
| 131 |
+
]
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
"id": "odoo.stored-compute-needs-complete-depends",
|
| 135 |
+
"stack": "odoo",
|
| 136 |
+
"probe": "my stored computed total goes stale and never recomputes after a line changes",
|
| 137 |
+
"pinned": false,
|
| 138 |
+
"rank": 2,
|
| 139 |
+
"rank_unpinned": 1,
|
| 140 |
+
"score": 0.7324,
|
| 141 |
+
"instead": [
|
| 142 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 143 |
+
"odoo.depends-must-not-name-unread-fields",
|
| 144 |
+
"python.decimal-for-money-not-float"
|
| 145 |
+
]
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"id": "odoo.ondelete-is-a-decision",
|
| 149 |
+
"stack": "odoo",
|
| 150 |
+
"probe": "what happens to the child records when someone deletes the parent",
|
| 151 |
+
"pinned": false,
|
| 152 |
+
"rank": 2,
|
| 153 |
+
"rank_unpinned": 1,
|
| 154 |
+
"score": 0.5918,
|
| 155 |
+
"instead": [
|
| 156 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 157 |
+
"odoo.sudo-bypasses-record-rules",
|
| 158 |
+
"odoo.check-company-on-relations"
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"id": "odoo.recursion-check-on-hierarchy",
|
| 163 |
+
"stack": "odoo",
|
| 164 |
+
"probe": "the server hangs when a category is made its own parent",
|
| 165 |
+
"pinned": false,
|
| 166 |
+
"rank": 2,
|
| 167 |
+
"rank_unpinned": 1,
|
| 168 |
+
"score": 0.6896,
|
| 169 |
+
"instead": [
|
| 170 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 171 |
+
"odoo.constrains-must-list-every-field-read",
|
| 172 |
+
"odoo.constrains-ignores-dotted-names"
|
| 173 |
+
]
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"id": "odoo.schema-change-needs-migration",
|
| 177 |
+
"stack": "odoo",
|
| 178 |
+
"probe": "I made an existing field required and the module upgrade fails on live data",
|
| 179 |
+
"pinned": false,
|
| 180 |
+
"rank": 2,
|
| 181 |
+
"rank_unpinned": 1,
|
| 182 |
+
"score": 0.7745,
|
| 183 |
+
"instead": [
|
| 184 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 185 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 186 |
+
"odoo.noupdate-on-user-editable-data"
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
"id": "odoo.sudo-bypasses-record-rules",
|
| 191 |
+
"stack": "odoo",
|
| 192 |
+
"probe": "a search returns records belonging to every company instead of the current one",
|
| 193 |
+
"pinned": false,
|
| 194 |
+
"rank": 3,
|
| 195 |
+
"rank_unpinned": 2,
|
| 196 |
+
"score": 0.7199,
|
| 197 |
+
"instead": [
|
| 198 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 199 |
+
"odoo.check-company-on-relations",
|
| 200 |
+
"odoo.constrains-must-list-every-field-read"
|
| 201 |
+
]
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"id": "odoo.no-monkey-patching",
|
| 205 |
+
"stack": "odoo",
|
| 206 |
+
"probe": "change what a core sale.order method does without editing the sale module",
|
| 207 |
+
"pinned": false,
|
| 208 |
+
"rank": 2,
|
| 209 |
+
"rank_unpinned": 1,
|
| 210 |
+
"score": 0.6225,
|
| 211 |
+
"instead": [
|
| 212 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 213 |
+
"odoo.schema-change-needs-migration",
|
| 214 |
+
"odoo.onchange-is-not-validation"
|
| 215 |
+
]
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"id": "odoo.noupdate-on-user-editable-data",
|
| 219 |
+
"stack": "odoo",
|
| 220 |
+
"probe": "the customer's configuration resets itself every time we upgrade the module",
|
| 221 |
+
"pinned": false,
|
| 222 |
+
"rank": 3,
|
| 223 |
+
"rank_unpinned": 2,
|
| 224 |
+
"score": 0.6179,
|
| 225 |
+
"instead": [
|
| 226 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 227 |
+
"odoo.schema-change-needs-migration",
|
| 228 |
+
"odoo.no-monkey-patching"
|
| 229 |
+
]
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"id": "odoo.commit-inside-a-loop-splits-the-transaction",
|
| 233 |
+
"stack": "odoo",
|
| 234 |
+
"probe": "half my records were updated and the rest were not after the job raised",
|
| 235 |
+
"pinned": false,
|
| 236 |
+
"rank": 6,
|
| 237 |
+
"rank_unpinned": 5,
|
| 238 |
+
"score": 0.5035,
|
| 239 |
+
"instead": [
|
| 240 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 241 |
+
"odoo.noupdate-on-user-editable-data",
|
| 242 |
+
"odoo.check-company-on-relations"
|
| 243 |
+
]
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"id": "odoo.sql-needs-query-parameters",
|
| 247 |
+
"stack": "odoo",
|
| 248 |
+
"probe": "a raw query breaks when a partner name contains an apostrophe",
|
| 249 |
+
"pinned": false,
|
| 250 |
+
"rank": 2,
|
| 251 |
+
"rank_unpinned": 1,
|
| 252 |
+
"score": 0.6372,
|
| 253 |
+
"instead": [
|
| 254 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 255 |
+
"odoo.check-company-on-relations",
|
| 256 |
+
"odoo.constrains-ignores-dotted-names"
|
| 257 |
+
]
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"id": "odoo.depends-must-not-name-unread-fields",
|
| 261 |
+
"stack": "odoo",
|
| 262 |
+
"probe": "my api.depends lists a field the compute never uses",
|
| 263 |
+
"pinned": false,
|
| 264 |
+
"rank": 3,
|
| 265 |
+
"rank_unpinned": 2,
|
| 266 |
+
"score": 0.7893,
|
| 267 |
+
"instead": [
|
| 268 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 269 |
+
"odoo.stored-compute-needs-complete-depends",
|
| 270 |
+
"odoo.constrains-ignores-dotted-names"
|
| 271 |
+
]
|
| 272 |
+
},
|
| 273 |
+
{
|
| 274 |
+
"id": "odoo.except-order-follows-the-hierarchy",
|
| 275 |
+
"stack": "odoo",
|
| 276 |
+
"probe": "my except AccessError block never runs and the API returns 409",
|
| 277 |
+
"pinned": false,
|
| 278 |
+
"rank": 2,
|
| 279 |
+
"rank_unpinned": 1,
|
| 280 |
+
"score": 0.6631,
|
| 281 |
+
"instead": [
|
| 282 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 283 |
+
"odoo.constrains-must-list-every-field-read",
|
| 284 |
+
"python.eval-and-pickle-execute-their-input"
|
| 285 |
+
]
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"id": "python.assert-is-not-validation",
|
| 289 |
+
"stack": "odoo",
|
| 290 |
+
"probe": "check that an amount is positive before using it",
|
| 291 |
+
"pinned": false,
|
| 292 |
+
"rank": 2,
|
| 293 |
+
"rank_unpinned": 1,
|
| 294 |
+
"score": 0.5731,
|
| 295 |
+
"instead": [
|
| 296 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 297 |
+
"python.decimal-for-money-not-float",
|
| 298 |
+
"odoo.float-comparison-needs-float-utils"
|
| 299 |
+
]
|
| 300 |
+
},
|
| 301 |
+
{
|
| 302 |
+
"id": "python.mutable-default-argument",
|
| 303 |
+
"stack": "odoo",
|
| 304 |
+
"probe": "a list keeps items from the previous call to the same function",
|
| 305 |
+
"pinned": false,
|
| 306 |
+
"rank": 2,
|
| 307 |
+
"rank_unpinned": 1,
|
| 308 |
+
"score": 0.623,
|
| 309 |
+
"instead": [
|
| 310 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 311 |
+
"odoo.constrains-must-list-every-field-read",
|
| 312 |
+
"odoo.stored-compute-needs-complete-depends"
|
| 313 |
+
]
|
| 314 |
+
},
|
| 315 |
+
{
|
| 316 |
+
"id": "python.bare-except-catches-the-exit",
|
| 317 |
+
"stack": "odoo",
|
| 318 |
+
"probe": "ctrl-c stops working and the script will not exit",
|
| 319 |
+
"pinned": false,
|
| 320 |
+
"rank": 2,
|
| 321 |
+
"rank_unpinned": 1,
|
| 322 |
+
"score": 0.6574,
|
| 323 |
+
"instead": [
|
| 324 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 325 |
+
"python.subprocess-shell-true-is-injection",
|
| 326 |
+
"python.assert-is-not-validation"
|
| 327 |
+
]
|
| 328 |
+
},
|
| 329 |
+
{
|
| 330 |
+
"id": "python.eval-and-pickle-execute-their-input",
|
| 331 |
+
"stack": "odoo",
|
| 332 |
+
"probe": "turn a config string sent by a client into a Python object",
|
| 333 |
+
"pinned": false,
|
| 334 |
+
"rank": 5,
|
| 335 |
+
"rank_unpinned": 4,
|
| 336 |
+
"score": 0.5698,
|
| 337 |
+
"instead": [
|
| 338 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 339 |
+
"odoo.sql-needs-query-parameters",
|
| 340 |
+
"odoo.onchange-is-not-validation"
|
| 341 |
+
]
|
| 342 |
+
},
|
| 343 |
+
{
|
| 344 |
+
"id": "python.subprocess-shell-true-is-injection",
|
| 345 |
+
"stack": "odoo",
|
| 346 |
+
"probe": "run a command line built from a filename the user gave me",
|
| 347 |
+
"pinned": false,
|
| 348 |
+
"rank": 2,
|
| 349 |
+
"rank_unpinned": 1,
|
| 350 |
+
"score": 0.5807,
|
| 351 |
+
"instead": [
|
| 352 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 353 |
+
"python.eval-and-pickle-execute-their-input",
|
| 354 |
+
"odoo.sql-needs-query-parameters"
|
| 355 |
+
]
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"id": "python.path-join-does-not-contain-a-path",
|
| 359 |
+
"stack": "odoo",
|
| 360 |
+
"probe": "open a file the user named inside my uploads directory",
|
| 361 |
+
"pinned": false,
|
| 362 |
+
"rank": 2,
|
| 363 |
+
"rank_unpinned": 1,
|
| 364 |
+
"score": 0.5659,
|
| 365 |
+
"instead": [
|
| 366 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 367 |
+
"python.eval-and-pickle-execute-their-input",
|
| 368 |
+
"odoo.check-company-on-relations"
|
| 369 |
+
]
|
| 370 |
+
},
|
| 371 |
+
{
|
| 372 |
+
"id": "python.decimal-for-money-not-float",
|
| 373 |
+
"stack": "odoo",
|
| 374 |
+
"probe": "totals are off by a fraction of a penny after adding prices up",
|
| 375 |
+
"pinned": false,
|
| 376 |
+
"rank": 3,
|
| 377 |
+
"rank_unpinned": 2,
|
| 378 |
+
"score": 0.5922,
|
| 379 |
+
"instead": [
|
| 380 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 381 |
+
"odoo.float-comparison-needs-float-utils",
|
| 382 |
+
"python.assert-is-not-validation"
|
| 383 |
+
]
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"id": "python.naive-and-aware-datetimes-do-not-compare",
|
| 387 |
+
"stack": "odoo",
|
| 388 |
+
"probe": "TypeError comparing offset-naive and offset-aware datetimes",
|
| 389 |
+
"pinned": false,
|
| 390 |
+
"rank": 2,
|
| 391 |
+
"rank_unpinned": 1,
|
| 392 |
+
"score": 0.7583,
|
| 393 |
+
"instead": [
|
| 394 |
+
"odoo.check-constraint-cannot-cross-tables",
|
| 395 |
+
"odoo.constrains-must-list-every-field-read",
|
| 396 |
+
"odoo.float-comparison-needs-float-utils"
|
| 397 |
+
]
|
| 398 |
+
}
|
| 399 |
+
]
|
| 400 |
+
}
|
|
@@ -22,9 +22,9 @@ the file:
|
|
| 22 |
it — do the retrieved rules declare a range covering 17.0? — which is the entire
|
| 23 |
difference between `check` and `ask`.
|
| 24 |
- `etl/load_prices.py` has no manifest above it and imports no odoo. It is there
|
| 25 |
-
because the corpus composes
|
| 26 |
-
|
| 27 |
-
|
| 28 |
- `odoo_module/models/fsm_visit.py` is 7,929 characters against a 6,000-character
|
| 29 |
budget, and three of its six checker findings sit in its last method. It is the
|
| 30 |
file that shows whether the excerpt is chosen well.
|
|
|
|
| 22 |
it — do the retrieved rules declare a range covering 17.0? — which is the entire
|
| 23 |
difference between `check` and `ask`.
|
| 24 |
- `etl/load_prices.py` has no manifest above it and imports no odoo. It is there
|
| 25 |
+
because the corpus composes its plain-Python rules in alongside the ORM ones,
|
| 26 |
+
and this is the file that shows what a review says when only the plain-Python
|
| 27 |
+
ones can match.
|
| 28 |
- `odoo_module/models/fsm_visit.py` is 7,929 characters against a 6,000-character
|
| 29 |
budget, and three of its six checker findings sit in its last method. It is the
|
| 30 |
file that shows whether the excerpt is chosen well.
|
|
@@ -294,6 +294,27 @@ def test_the_report_counts_live_findings_and_says_so_about_the_rest(capsys):
|
|
| 294 |
assert "3 more sat on lines another tool had already flagged" in out
|
| 295 |
|
| 296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
# ----------------------------------------------------- the confidence interval
|
| 298 |
|
| 299 |
def test_wilson_stays_inside_zero_and_one_at_tiny_n():
|
|
@@ -439,3 +460,17 @@ def test_a_k_that_cannot_fail_says_so(capsys):
|
|
| 439 |
rows = [retrieval_row("odoo.onchange-is-not-validation")]
|
| 440 |
evalmod.summarise_retrieval(rows, k=40)
|
| 441 |
assert "nothing can miss" in capsys.readouterr().out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
assert "3 more sat on lines another tool had already flagged" in out
|
| 295 |
|
| 296 |
|
| 297 |
+
def test_a_review_counts_findings_the_way_check_does(fake_ollama, seeded_index):
|
| 298 |
+
"""An advisory finding is printed apart by `nexa check` and counted in
|
| 299 |
+
nothing. This eval counted it anyway, and reported 13 findings on the
|
| 300 |
+
subjects where fieldtest reports 10 and 3 advisory."""
|
| 301 |
+
fake_ollama(["The file is fine as it is."])
|
| 302 |
+
path = evalmod.DEFAULT_SUBJECTS[0]
|
| 303 |
+
hits = [h for h in subject.plan(path)["findings"] if not h.suppressed]
|
| 304 |
+
live = [h for h in hits if not h.advisory]
|
| 305 |
+
advisory = [h for h in hits if h.advisory]
|
| 306 |
+
assert advisory, "this subject no longer has an advisory finding to count"
|
| 307 |
+
row = evalmod.quiet_check(path)
|
| 308 |
+
assert row["file_finding_count"] == len(live)
|
| 309 |
+
assert row["file_findings"] == sorted({h.rule for h in live})
|
| 310 |
+
assert row["file_advisory"] == len(advisory)
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def test_the_report_says_what_it_left_out_as_advisory(capsys):
|
| 314 |
+
evalmod.summarise_check(rows_for(["PASS"], file_advisory=2), 1)
|
| 315 |
+
assert "2 more came from advisory rules" in capsys.readouterr().out
|
| 316 |
+
|
| 317 |
+
|
| 318 |
# ----------------------------------------------------- the confidence interval
|
| 319 |
|
| 320 |
def test_wilson_stays_inside_zero_and_one_at_tiny_n():
|
|
|
|
| 460 |
rows = [retrieval_row("odoo.onchange-is-not-validation")]
|
| 461 |
evalmod.summarise_retrieval(rows, k=40)
|
| 462 |
assert "nothing can miss" in capsys.readouterr().out
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def test_the_retrieval_artifact_says_its_depth_and_its_corpus(tmp_path, monkeypatch):
|
| 466 |
+
"""Bare rows could be checked against nothing: a rank means nothing without
|
| 467 |
+
the k it was taken at, and a reworded rule keeps its id. figures.py reads
|
| 468 |
+
both from here."""
|
| 469 |
+
monkeypatch.setattr(evalmod, "retrievability", lambda stack, k: [
|
| 470 |
+
retrieval_row("odoo.onchange-is-not-validation")])
|
| 471 |
+
out = tmp_path / "retrieval.json"
|
| 472 |
+
assert evalmod.main(["--retrieval", "--top-k", "4", "--json", str(out)]) == 0
|
| 473 |
+
art = json.loads(out.read_text(encoding="utf-8"))
|
| 474 |
+
assert art["k"] == 4
|
| 475 |
+
assert art["corpus_sha256"] == evalmod.evidence.corpus_sha256()
|
| 476 |
+
assert [r["id"] for r in art["rows"]] == ["odoo.onchange-is-not-validation"]
|
|
@@ -33,9 +33,26 @@ def _row(rule, path, line, verdict):
|
|
| 33 |
return {"rule": rule, "path": path, "line": line, "verdict": verdict}
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
@pytest.fixture()
|
| 37 |
def tree(tmp_path, monkeypatch):
|
| 38 |
-
"""One ecosystem, 2,000 lines, four findings of which two are counted.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
root = str(tmp_path)
|
| 40 |
_write(root, "ft.json", {
|
| 41 |
"roots": ["repo"], "checkouts": {"repo": SHA},
|
|
@@ -64,8 +81,24 @@ def tree(tmp_path, monkeypatch):
|
|
| 64 |
"semantic_check": "b"},
|
| 65 |
{"id": "odoo.z", "severity": "critical"},
|
| 66 |
])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
monkeypatch.setattr(figures, "ROOT", root)
|
| 68 |
monkeypatch.setattr(figures, "CORPUS_RULES", "rules.json")
|
|
|
|
|
|
|
|
|
|
| 69 |
monkeypatch.setattr(figures, "FIELDTESTS", {"x": "ft.json"})
|
| 70 |
monkeypatch.setattr(figures, "RECALLS", {"x": "rc.json"})
|
| 71 |
monkeypatch.setattr(figures, "RECALLS_XML", {"x": "rc.json"})
|
|
@@ -212,6 +245,104 @@ def test_an_agreeing_document_passes(tree, capsys):
|
|
| 212 |
assert "every marked figure agrees" in capsys.readouterr().out
|
| 213 |
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
def test_the_committed_documents_agree_with_the_committed_artifacts(capsys):
|
| 216 |
"""The check CI runs. A failure here names the sentence to correct."""
|
| 217 |
code = figures.main(["--check"])
|
|
|
|
| 33 |
return {"rule": rule, "path": path, "line": line, "verdict": verdict}
|
| 34 |
|
| 35 |
|
| 36 |
+
def _probe(rid, rank):
|
| 37 |
+
return {"id": rid, "probe": "a question", "rank": rank}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _review(path, verdict, findings, beyond=(), chars=100, advisory=0):
|
| 41 |
+
return {"path": path, "verdict": verdict, "chars": chars,
|
| 42 |
+
"file_findings": list(findings), "file_finding_count": len(findings),
|
| 43 |
+
"file_advisory": advisory, "cited_beyond_checkers": list(beyond)}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
CORPUS_SHA = "c" * 64
|
| 47 |
+
|
| 48 |
+
|
| 49 |
@pytest.fixture()
|
| 50 |
def tree(tmp_path, monkeypatch):
|
| 51 |
+
"""One ecosystem, 2,000 lines, four findings of which two are counted.
|
| 52 |
+
|
| 53 |
+
Beside it, one retrieval run over the four rules and one check run over
|
| 54 |
+
two files, twice each.
|
| 55 |
+
"""
|
| 56 |
root = str(tmp_path)
|
| 57 |
_write(root, "ft.json", {
|
| 58 |
"roots": ["repo"], "checkouts": {"repo": SHA},
|
|
|
|
| 81 |
"semantic_check": "b"},
|
| 82 |
{"id": "odoo.z", "severity": "critical"},
|
| 83 |
])
|
| 84 |
+
_write(root, "meta.json", {"corpus_sha256": CORPUS_SHA})
|
| 85 |
+
_write(root, "retrieval.json", {"k": 6, "corpus_sha256": CORPUS_SHA, "rows": [
|
| 86 |
+
_probe("odoo.w", 1), _probe("odoo.x", 3), _probe("python.y", 5),
|
| 87 |
+
_probe("odoo.z", None)]})
|
| 88 |
+
os.mkdir(os.path.join(root, "s"))
|
| 89 |
+
_write(root, "s/a.py", "")
|
| 90 |
+
_write(root, "s/b.py", "")
|
| 91 |
+
_write(root, "check.json", [
|
| 92 |
+
_review("s/a.py", "WARN", ["odoo.a", "odoo.b"], advisory=1),
|
| 93 |
+
_review("s/a.py", "WARN", ["odoo.a", "odoo.b"], advisory=1),
|
| 94 |
+
_review("s/b.py", "PASS", []),
|
| 95 |
+
_review("s/b.py", "REJECT", [], beyond=["odoo.c"]),
|
| 96 |
+
])
|
| 97 |
monkeypatch.setattr(figures, "ROOT", root)
|
| 98 |
monkeypatch.setattr(figures, "CORPUS_RULES", "rules.json")
|
| 99 |
+
monkeypatch.setattr(figures, "CORPUS_META", "meta.json")
|
| 100 |
+
monkeypatch.setattr(figures, "RETRIEVAL", "retrieval.json")
|
| 101 |
+
monkeypatch.setattr(figures, "CHECK", "check.json")
|
| 102 |
monkeypatch.setattr(figures, "FIELDTESTS", {"x": "ft.json"})
|
| 103 |
monkeypatch.setattr(figures, "RECALLS", {"x": "rc.json"})
|
| 104 |
monkeypatch.setattr(figures, "RECALLS_XML", {"x": "rc.json"})
|
|
|
|
| 245 |
assert "every marked figure agrees" in capsys.readouterr().out
|
| 246 |
|
| 247 |
|
| 248 |
+
# ------------------------------------------------------ the two eval runs
|
| 249 |
+
|
| 250 |
+
def test_one_top_6_run_answers_the_top_3_question_too(tree):
|
| 251 |
+
_classify(tree, [])
|
| 252 |
+
fig = figures.compute()
|
| 253 |
+
assert fig.values["retrieval.rules"] == "4"
|
| 254 |
+
assert fig.values["retrieval.k"] == "6"
|
| 255 |
+
assert fig.values["retrieval.found"] == "3"
|
| 256 |
+
assert fig.values["retrieval.top3"] == "2"
|
| 257 |
+
assert not fig.faults
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def test_a_rule_the_retrieval_run_never_asked_is_a_fault(tree):
|
| 261 |
+
"""Item 12's drift: a 28th rule, and a row still saying 27 of 27."""
|
| 262 |
+
_classify(tree, [])
|
| 263 |
+
rules = figures._load("rules.json") + [{"id": "odoo.new", "severity": "high"}]
|
| 264 |
+
_write(tree, "rules.json", rules)
|
| 265 |
+
fig = figures.compute()
|
| 266 |
+
assert ("retrieval.json: odoo.new is in the corpus and was never asked "
|
| 267 |
+
"its probe") in fig.faults
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def test_a_retrieval_run_on_another_corpus_is_a_fault_though_every_id_matches(tree):
|
| 271 |
+
"""A reworded title keeps its id. Only the hash sees it."""
|
| 272 |
+
_classify(tree, [])
|
| 273 |
+
_write(tree, "meta.json", {"corpus_sha256": "d" * 64})
|
| 274 |
+
fig = figures.compute()
|
| 275 |
+
assert fig.faults == ["retrieval.json: ranked against corpus cccccccccccc, which is "
|
| 276 |
+
"dddddddddddd now; re-run nexa eval --retrieval --json "
|
| 277 |
+
"retrieval.json"]
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def test_a_retrieval_run_too_shallow_for_its_top_3_is_a_fault(tree):
|
| 281 |
+
_classify(tree, [])
|
| 282 |
+
art = figures._load("retrieval.json")
|
| 283 |
+
art["k"] = 2
|
| 284 |
+
_write(tree, "retrieval.json", art)
|
| 285 |
+
fig = figures.compute()
|
| 286 |
+
assert any("cannot say what a top 3 returns" in f for f in fig.faults)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def test_the_check_run_counts_findings_once_per_file(tree):
|
| 290 |
+
_classify(tree, [])
|
| 291 |
+
fig = figures.compute()
|
| 292 |
+
assert fig.values["check.files"] == "2"
|
| 293 |
+
assert fig.values["check.runs"] == "2"
|
| 294 |
+
assert fig.values["check.reviews"] == "4"
|
| 295 |
+
assert fig.values["check.findings"] == "2"
|
| 296 |
+
assert fig.values["check.advisory"] == "1"
|
| 297 |
+
assert fig.values["check.flagged"] == "1"
|
| 298 |
+
assert fig.values["check.steady"] == "2"
|
| 299 |
+
assert fig.values["check.verdict.WARN"] == "2"
|
| 300 |
+
assert fig.values["check.verdict.PASS"] == "1"
|
| 301 |
+
assert fig.values["check.verdict.REJECT"] == "1"
|
| 302 |
+
assert fig.values["check.stable"] == "1"
|
| 303 |
+
assert fig.values["check.beyond"] == "1"
|
| 304 |
+
assert fig.values["check.beyond.rules"] == "1"
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def test_findings_that_moved_between_runs_are_not_steady(tree):
|
| 308 |
+
_classify(tree, [])
|
| 309 |
+
_write(tree, "check.json", [_review("s/a.py", "WARN", ["odoo.a"]),
|
| 310 |
+
_review("s/a.py", "WARN", ["odoo.a", "odoo.b"])])
|
| 311 |
+
fig = figures.compute()
|
| 312 |
+
assert fig.values["check.steady"] == "0"
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def test_a_failed_review_neither_speaks_for_its_file_nor_unsteadies_it(tree):
|
| 316 |
+
"""An ERROR row reports no characters and no findings. Taken as the file's
|
| 317 |
+
row it would publish a run that broke as a file that is clean."""
|
| 318 |
+
_classify(tree, [])
|
| 319 |
+
_write(tree, "check.json", [_review("s/a.py", "ERROR", [], chars=0),
|
| 320 |
+
_review("s/a.py", "WARN", ["odoo.a"])])
|
| 321 |
+
fig = figures.compute()
|
| 322 |
+
assert fig.values["check.findings"] == "1"
|
| 323 |
+
assert fig.values["check.steady"] == "1"
|
| 324 |
+
assert fig.values["check.stable"] == "0"
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def test_a_check_row_for_a_file_that_is_gone_is_a_fault(tree):
|
| 328 |
+
"""13a's drift: two subjects deleted, and the rows still counted them."""
|
| 329 |
+
_classify(tree, [])
|
| 330 |
+
_write(tree, "check.json", [_review("s/a.py", "WARN", []),
|
| 331 |
+
_review("s/gone.py", "WARN", [])])
|
| 332 |
+
fig = figures.compute()
|
| 333 |
+
assert fig.faults == ["check.json: s/gone.py is not in the repository, so "
|
| 334 |
+
"nothing can re-review it"]
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def test_files_reviewed_unevenly_are_a_fault(tree):
|
| 338 |
+
_classify(tree, [])
|
| 339 |
+
_write(tree, "check.json", [_review("s/a.py", "WARN", []),
|
| 340 |
+
_review("s/a.py", "WARN", []),
|
| 341 |
+
_review("s/b.py", "WARN", [])])
|
| 342 |
+
fig = figures.compute()
|
| 343 |
+
assert "check.json: files were reviewed unevenly, 1 or 2 times" in fig.faults
|
| 344 |
+
|
| 345 |
+
|
| 346 |
def test_the_committed_documents_agree_with_the_committed_artifacts(capsys):
|
| 347 |
"""The check CI runs. A failure here names the sentence to correct."""
|
| 348 |
code = figures.main(["--check"])
|