DBax127 Claude Opus 5 commited on
Commit
9012cfa
·
1 Parent(s): 904b5fc

sudo: the rule the classification killed, and the marker it could not read

Browse files

The other 163 OpenSPP findings are now classified, which is all 268. One rule
was 106 of them, every one critical, and reading them put it at 6 true and 97
false. That is not a tuning problem. Its own sentence is that sudo "drops
ir.rule filtering" and the regex could see the sudo and never the filtering:
73 fired on models the tree gives no ir.rule at all, and 10 more on
spp.vocabulary.code, whose single rule is [(1,'=',1)] and filters nothing.

So context.tree_facts now reads every <record model="ir.rule"> in the walk,
and the rule is an AST pass that fires only where a filtering rule exists. The
trigger only ever suppresses: an unwalked tree, a partial walk, or a model the
walk never saw declared each read UNKNOWN and the finding stands, so `nexa
check` on one file behaves as it did before.

That last clause is there because the first version did not have it and cost
three true positives. A walk of an addons tree sees the addons' rules and not
Odoo's own, so product.template -- which carries core's company rule from the
`product` module -- read as ABSENT, and a paginated API listing every company's
products stopped being reported. Silence about a model nobody declared is
UNKNOWN. The docstring that claimed a parse miss fails safe was true before
that change and false after it, and now says which way it actually falls.

Separately, and the reason the rule was so loud: 105 of those 106 findings
carried a `# nosemgrep:` about that exact construct and not one shared a line
with it. The call is chained across four lines; the checker anchors on
`.sudo()` and the marker sits above. scan_source now reads a marker across the
simple statement it sits in and the line above -- where semgrep, ruff and
pylint all document their own markers as belonging. Compound statements stay
out: a marker on an `if` header covering the block is the mute button this
project will not build, and this still cannot reach the next statement.

Measured on OpenSPP: 268 live findings to 162, 1.18 per kloc to 0.71, sudo 106
to 2. All 6 classified true positives kept, which is the only thing that makes
the rest of it safe.

Re-measured on OCA, because changing a checker invalidates every number it
produced. 102 hits, 3.0 per kloc, 79 true, 10 false, 13 undecidable. Three
findings are new -- the AST pass also sees .sudo().search_count( and
.sudo().search_read( -- and all three are false. One false positive left, a
sql-needs-query-parameters carrying a marker on the line above it. All 79 true
positives survived both changes. The Python field test is unmoved at 0.3.

recall --aim re-run: 13 on-target of 44, still the best-aimed rule in the
corpus by a distance. Like ondelete's, that grade is what the rule is worth
WITHOUT its trigger, because recall.py builds no tree_facts and every trigger
reads UNKNOWN there. The trigger only suppresses, so it is a floor.

Named and not fixed: float-comparison-needs-float-utils is 2 true and 5 false,
and all five compare an integer to zero -- search_count, a read_group _count,
SELECT COUNT(*) -- which the regex cannot see because it matches on the
variable's name. That needs assignment tracing, so it is written down rather
than quietly left out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

README.md CHANGED
@@ -114,11 +114,18 @@ half of the wrapper.
114
  | `check-company-on-relations` | A record in one company pointing at another's. A UI `domain` constrains a dropdown and enforces nothing. |
115
 
116
  Measured against **reviewed, merged** OCA code — 33,960 lines that passed human review
117
- and shipped — the corpus produces 2.9 findings per thousand lines. All of them
118
- classified by hand: **79 true positives, 8 false, 13 undecidable from a single file.**
119
  Every verdict is in `classification-oca-2026-09.json`, so the number can be re-opened
120
  rather than taken on trust.
121
 
 
 
 
 
 
 
 
122
  ## What it is not
123
 
124
  **Not a general linter.** 43 rules. It will not replace `pylint-odoo`, and after the
@@ -424,16 +431,18 @@ Same principle as `z.infer<typeof Schema>`: one definition, derived artifacts, n
424
  |---|---|
425
  | Rules | 43 — 19 Odoo, 10 TypeScript, 8 Python, 6 JavaScript |
426
  | Critical | 17 |
427
- | Machine-checkable | 40 (93%) — 31 by pattern, 9 by AST |
428
  | Traceable to upstream docs | 41 (95%), each audited for topic fit |
429
  | Pattern false positives | 0 cross-hits across the corpus |
430
  | Recall under meaning-preserving rewrite | 99/99 |
431
- | Satisfied patterns bypassable by a comment | 0 of 14 |
432
  | Retrievable by their own probe | 43 of 43, at top 3 |
433
- | Hit rate on 33,960 lines of reviewed OCA code | 2.9 per 1000 lines |
434
- | Those 100 hits, classified by hand | **79 true positives**, 8 false, 13 undecidable from one file |
435
- | Hit rate on 226,937 lines of OpenSPP (Odoo 19) | 1.2 per 1000 lines, +26 another tool had already flagged, +71 advisory |
436
- | The two `depends`/`constrains` rules on OpenSPP, classified by hand | **87 true positives**, 15 false, 3 undecidable — 85% |
 
 
437
  | Hit rate on 73,762 lines of TypeScript (hono, trpc) | 3.8 per 1000 lines — **measured, not yet classified** |
438
  | Hit rate on 9,304 lines of Odoo frontend JavaScript | 4.6 per 1000 lines — 21 true positives, 22 false |
439
  | Hit rate on 10,225 lines of Python (llm-router) | 0.3 per 1000 lines |
@@ -490,7 +499,7 @@ This runs as part of `nexa build`. A checker that has never been attacked is a c
490
  nobody has tested.
491
 
492
  The probes hold the corpus to a measured standard, and it currently meets all three:
493
- **64/64** rewrites still caught, **0 of 12** satisfied patterns bypassable by a
494
  comment, **0** contradictions between rules. Those numbers are regenerated on every
495
  build, so a checker cannot quietly weaken.
496
 
@@ -526,11 +535,20 @@ own denominator. Under the old counter today's tree reads 34,566 against that
526
  honest.
527
 
528
  All 100 were then read by hand — **79 true positives, 8 false, 13 undecidable from a
529
- single file** — and every verdict is in `classification-oca-2026-09.json`. The 8 is
530
  down from 24 on the first pass: classifying them produced a worklist, and four of the
531
  seven classes turned out to be one predicate each on evidence already in the file. All
532
  79 true positives survived that narrowing, which is the only thing that makes it safe.
533
 
 
 
 
 
 
 
 
 
 
534
  ### The same treatment on a second ecosystem
535
 
536
  A precision number from one ecosystem is a precision number for that ecosystem. The two
@@ -558,6 +576,55 @@ Odoo rejects a non-stored field in `@api.constrains` with *"parameter is not wri
558
  The gap the finding points at is real; the fix it names cannot be applied. OpenSPP hit
559
  that same Odoo behaviour themselves and removed an `@api.constrains("age")` over it.
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  What the first run found:
562
 
563
  - **`onchange-is-not-validation`** fired on all 16 `@api.onchange` declarations that
@@ -732,9 +799,27 @@ person who had already decided.
732
 
733
  They do not anchor to the same line, though, and that is worth knowing. Semgrep matches
734
  the whole loop and its marker sits on the `for`; this checker reports the `commit()` ten
735
- lines inside it. `scan_source` only reads a marker on the finding's own line, so it does
736
- not carry across deliberately, because a marker that silences findings it was not
737
- written about is the mute button this project will not build.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
 
739
  ### Every version claim is checked against two kinds of evidence
740
 
 
114
  | `check-company-on-relations` | A record in one company pointing at another's. A UI `domain` constrains a dropdown and enforces nothing. |
115
 
116
  Measured against **reviewed, merged** OCA code — 33,960 lines that passed human review
117
+ and shipped — the corpus produces 3.0 findings per thousand lines. All of them
118
+ classified by hand: **79 true positives, 10 false, 13 undecidable from a single file.**
119
  Every verdict is in `classification-oca-2026-09.json`, so the number can be re-opened
120
  rather than taken on trust.
121
 
122
+ On 226,937 lines of OpenSPP the rate is 0.7, and those findings have been read too:
123
+ **138 true, 124 false, 6 undecidable** across `classification-openspp-2026-09.json` and
124
+ `classification-openspp-rest-2026-09.json`. One rule accounts for 97 of those false
125
+ positives and has been rewritten because of it — that story is
126
+ [below](#the-rule-that-classification-killed), and it is the more useful half of
127
+ publishing a classification.
128
+
129
  ## What it is not
130
 
131
  **Not a general linter.** 43 rules. It will not replace `pylint-odoo`, and after the
 
431
  |---|---|
432
  | Rules | 43 — 19 Odoo, 10 TypeScript, 8 Python, 6 JavaScript |
433
  | Critical | 17 |
434
+ | Machine-checkable | 40 (93%) — 30 by pattern, 10 by AST |
435
  | Traceable to upstream docs | 41 (95%), each audited for topic fit |
436
  | Pattern false positives | 0 cross-hits across the corpus |
437
  | Recall under meaning-preserving rewrite | 99/99 |
438
+ | Satisfied patterns bypassable by a comment | 0 of 13 |
439
  | Retrievable by their own probe | 43 of 43, at top 3 |
440
+ | Hit rate on 33,960 lines of reviewed OCA code | 3.0 per 1000 lines |
441
+ | Those 102 hits, classified by hand | **79 true positives**, 10 false, 13 undecidable from one file |
442
+ | Hit rate on 226,937 lines of OpenSPP (Odoo 19) | 0.7 per 1000 lines, +68 another tool had already flagged, +71 advisory |
443
+ | Every one of those findings, classified by hand | **138 true positives**, 124 false, 6 undecidable |
444
+ | …the two `depends`/`constrains` rules alone | **87 true**, 15 false, 3 undecidable — 85% |
445
+ | …`sudo-bypasses-record-rules` alone, before it was narrowed | 6 true, 97 false — 5.8%, and the reason it is now an AST pass |
446
  | Hit rate on 73,762 lines of TypeScript (hono, trpc) | 3.8 per 1000 lines — **measured, not yet classified** |
447
  | Hit rate on 9,304 lines of Odoo frontend JavaScript | 4.6 per 1000 lines — 21 true positives, 22 false |
448
  | Hit rate on 10,225 lines of Python (llm-router) | 0.3 per 1000 lines |
 
499
  nobody has tested.
500
 
501
  The probes hold the corpus to a measured standard, and it currently meets all three:
502
+ **99/99** rewrites still caught, **0 of 13** satisfied patterns bypassable by a
503
  comment, **0** contradictions between rules. Those numbers are regenerated on every
504
  build, so a checker cannot quietly weaken.
505
 
 
535
  honest.
536
 
537
  All 100 were then read by hand — **79 true positives, 8 false, 13 undecidable from a
538
+ single file** — and every verdict is in `classification-oca-2026-09.json`. The 8 was
539
  down from 24 on the first pass: classifying them produced a worklist, and four of the
540
  seven classes turned out to be one predicate each on evidence already in the file. All
541
  79 true positives survived that narrowing, which is the only thing that makes it safe.
542
 
543
+ **Re-measured after the sudo rewrite below: 102 hits, 3.0 per 1000 lines, 79 true, 10
544
+ false, 13 undecidable.** Two changes moved it in opposite directions and both are worth
545
+ naming. The AST pass also sees `.sudo().search_count(` and `.sudo().search_read(`, which
546
+ the regex could not, so three findings are new — and all three are false, one of them the
547
+ same `registry-model` class the original classification already had. Against that, one
548
+ false positive left, a `sql-needs-query-parameters` carrying a marker on the line above
549
+ it that suppression now reads. **All 79 true positives survived both changes**, which is
550
+ again the only thing that makes the rest of it safe.
551
+
552
  ### The same treatment on a second ecosystem
553
 
554
  A precision number from one ecosystem is a precision number for that ecosystem. The two
 
576
  The gap the finding points at is real; the fix it names cannot be applied. OpenSPP hit
577
  that same Odoo behaviour themselves and removed an `@api.constrains("age")` over it.
578
 
579
+ ### The rule that classification killed
580
+
581
+ The other 163 findings were read the same way, and one rule accounted for most of what
582
+ was wrong with the output. `sudo-bypasses-record-rules` was a regex for `.sudo().search(`
583
+ and made **106 of the 268 live findings, every one marked critical**. Classified:
584
+ **6 true, 97 false, 3 undecidable — 5.8%.**
585
+
586
+ That is not a rule that needs tuning. It was asking half a question. Its own sentence is
587
+ that sudo "drops ir.rule filtering", and the regex could see the `sudo` and never the
588
+ filtering:
589
+
590
+ - **73** were on models the tree gives **no `ir.rule` at all**, so there was nothing to drop.
591
+ - **10** more on `spp.vocabulary.code`, whose single rule is `[(1, '=', 1)]` — a grant
592
+ that matches every row and filters nothing.
593
+ - **14** re-applied scoping by hand, in a spelling the rule did not recognise because it
594
+ named only `company_id`.
595
+
596
+ So it is now an AST pass that asks the tree the second half: `context.tree_facts` reads
597
+ every `<record model="ir.rule">` in the walk and the checker fires only where a filtering
598
+ rule exists. **The trigger only ever suppresses.** A tree that was not walked, a partial
599
+ walk, a model the walk never saw declared — each reads UNKNOWN and the finding stands,
600
+ so `nexa check` on a single file behaves exactly as it did before.
601
+
602
+ That last clause was learned the hard way, in the first version of this trigger. A walk
603
+ of an addons tree sees the addons' rules and not Odoo's own, so `product.template` — which
604
+ carries core's company rule from the `product` module — read as ABSENT and **three true
605
+ positives disappeared**: a paginated API listing every company's products. Silence about a
606
+ model nobody declared is UNKNOWN, not "no rule".
607
+
608
+ | | before | after |
609
+ |---|---|---|
610
+ | `sudo-bypasses-record-rules`, live | 106 | **2** |
611
+ | All live findings on OpenSPP | 268 | **162** |
612
+ | Per 1000 lines | 1.18 | **0.71** |
613
+ | Of the 6 classified true positives, kept | — | **6** |
614
+
615
+ The last row is the only one that makes the others safe.
616
+
617
+ ### What is still wrong, named
618
+
619
+ `float-comparison-needs-float-utils` is next at **2 true, 5 false**. All five compare an
620
+ *integer* to zero — `search_count`, a `read_group` `_count`, `SELECT COUNT(*)` — and the
621
+ regex cannot see it because it matches on the variable's name, not its value. Fixing that
622
+ means tracing the assignment, so it is written down here rather than quietly left out.
623
+
624
+ `onchange-is-not-validation` is **4 true, 3 false**, and all three false ones are an
625
+ onchange that sets a field rather than enforcing anything — which the rule's existing
626
+ narrowing was supposed to exclude and does not catch when the write is to a Many2one.
627
+
628
  What the first run found:
629
 
630
  - **`onchange-is-not-validation`** fired on all 16 `@api.onchange` declarations that
 
799
 
800
  They do not anchor to the same line, though, and that is worth knowing. Semgrep matches
801
  the whole loop and its marker sits on the `for`; this checker reports the `commit()` ten
802
+ lines inside it, so the marker does not carry across. A marker that silences findings it
803
+ was not written about is the mute button this project will not build.
804
+
805
+ **Reading a marker only on the finding's own line was too strict, and there is now a
806
+ number on it.** Of the 106 `.sudo().search(` findings on OpenSPP, **105 carried a
807
+ `# nosemgrep:` about that exact construct and not one shared a line with it** — the call
808
+ is chained across four lines and the checker anchors on `.sudo()` while the marker sits
809
+ above it. A hundred and five sites somebody had already read and justified were being
810
+ re-reported as fresh critical findings.
811
+
812
+ So `scan_source` now reads a marker across the **statement** it sits in, and the line
813
+ immediately above it — which is where semgrep, ruff and pylint all document their own
814
+ markers as belonging. The unit is a *simple* statement, one with no body of its own:
815
+ compound statements are deliberately excluded, because letting a marker on an `if`
816
+ header cover the whole block is the mute button again. It is the same principle read
817
+ more carefully, not a weaker one — and it still cannot reach the next statement.
818
+
819
+ On its own that took OpenSPP's suppressed count from 26 to 133. It reads 68 now, because
820
+ the rule that produced most of those markers was narrowed in the same pass and no longer
821
+ makes the findings at all — see below. Both changes were worth making: one stops
822
+ re-reporting what somebody has already read, the other stops asking the wrong question.
823
 
824
  ### Every version claim is checked against two kinds of evidence
825
 
bin/context.py CHANGED
@@ -31,6 +31,23 @@ MANIFEST_NAMES = ("__manifest__.py", "__openerp__.py")
31
  MAX_WALK_UP = 6
32
 
33
  _VERSION_RE = re.compile(r"[\"']version[\"']\s*:\s*[\"']([0-9.]+)[\"']")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  _NAME_RE = re.compile(r"[\"']name[\"']\s*:\s*[\"']([^\"']+)[\"']")
35
  _MODEL_RE = re.compile(r"^\s*_(?:name|inherit)\s*=\s*[\"']([a-z0-9_.]+)[\"']", re.M)
36
  _ODOO_IMPORT_RE = re.compile(r"^\s*from\s+odoo[\w.]*\s+import\s+(.+)$", re.M)
@@ -273,6 +290,7 @@ def tree_facts(paths, complete=True):
273
 
274
  declared, deleted = set(), set()
275
  unlink_tokens = set()
 
276
  multi_company = False
277
  seen = 0
278
  # Every series the manifests in this walk name. Kept as a set because
@@ -305,6 +323,8 @@ def tree_facts(paths, complete=True):
305
  if not multi_company and any(m in body for m in
306
  semantics.MULTI_COMPANY_MARKERS):
307
  multi_company = True
 
 
308
 
309
  # Access tokens are matched FORWARDS, from each declared model to the token
310
  # it would have. Parsing `model_sale_order_line` back into a dotted name is
@@ -318,10 +338,85 @@ def tree_facts(paths, complete=True):
318
  pinned = {v for v in series_seen if v}
319
  return {"walked": True, "complete": complete, "files": seen,
320
  "models_declared": declared, "models_deleted": deleted,
 
321
  "multi_company": multi_company,
322
  "odoo_series": pinned.pop() if len(pinned) == 1 else None}
323
 
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  def _models_granted_unlink(body):
326
  """The `model_id:id` tokens an ir.model.access.csv grants perm_unlink on."""
327
  rows = list(csv.reader(io.StringIO(body)))
 
31
  MAX_WALK_UP = 6
32
 
33
  _VERSION_RE = re.compile(r"[\"']version[\"']\s*:\s*[\"']([0-9.]+)[\"']")
34
+
35
+ # An <record model="ir.rule"> and the two fields that decide whether it filters
36
+ # anything. Attribute order is not fixed -- one OpenSPP file writes ref= before
37
+ # name= on every row, which is 10 of that tree's 239 rules -- so model_id is
38
+ # matched without assuming which attribute comes first.
39
+ _IR_RULE_RE = re.compile(
40
+ r"<record[^>]*\bmodel=[\"']ir\.rule[\"'][^>]*>(.*?)</record>", re.S)
41
+ _RULE_MODEL_RE = re.compile(
42
+ r"<field\b(?=[^>]*\bname=[\"']model_id[\"'])[^>]*\bref=[\"']([\w.]+)[\"']")
43
+ _RULE_DOMAIN_RE = re.compile(
44
+ r"<field\b[^>]*\bname=[\"']domain_force[\"'][^>]*>(.*?)</field>", re.S)
45
+
46
+ # A domain that matches every row filters nothing, so sudo cannot drop it. This
47
+ # is the whole of `[(1, '=', 1)]` in any spacing; anything else is treated as a
48
+ # real filter, because deciding that a domain is vacuous in general means
49
+ # evaluating it.
50
+ _VACUOUS_DOMAIN = re.compile(r"^\[\s*\(\s*1\s*,\s*[\"']=[\"']\s*,\s*1\s*\)\s*\]$")
51
  _NAME_RE = re.compile(r"[\"']name[\"']\s*:\s*[\"']([^\"']+)[\"']")
52
  _MODEL_RE = re.compile(r"^\s*_(?:name|inherit)\s*=\s*[\"']([a-z0-9_.]+)[\"']", re.M)
53
  _ODOO_IMPORT_RE = re.compile(r"^\s*from\s+odoo[\w.]*\s+import\s+(.+)$", re.M)
 
290
 
291
  declared, deleted = set(), set()
292
  unlink_tokens = set()
293
+ ruled = set()
294
  multi_company = False
295
  seen = 0
296
  # Every series the manifests in this walk name. Kept as a set because
 
323
  if not multi_company and any(m in body for m in
324
  semantics.MULTI_COMPANY_MARKERS):
325
  multi_company = True
326
+ if "ir.rule" in body:
327
+ ruled |= _models_with_a_filtering_rule(body)
328
 
329
  # Access tokens are matched FORWARDS, from each declared model to the token
330
  # it would have. Parsing `model_sale_order_line` back into a dotted name is
 
338
  pinned = {v for v in series_seen if v}
339
  return {"walked": True, "complete": complete, "files": seen,
340
  "models_declared": declared, "models_deleted": deleted,
341
+ "models_record_ruled": ruled,
342
  "multi_company": multi_company,
343
  "odoo_series": pinned.pop() if len(pinned) == 1 else None}
344
 
345
 
346
+ def _models_with_a_filtering_rule(body):
347
+ """Models this XML gives an ir.rule that actually filters something.
348
+
349
+ A rule whose domain_force is `[(1, '=', 1)]` is a grant, not a filter: it
350
+ exists to widen access for a group and matches every row. sudo() drops it
351
+ and nothing changes, so a model whose only rules are those is no different
352
+ from a model with none.
353
+
354
+ Measured on OpenSPP, that distinction is most of this trigger's value: of
355
+ the 30 `.sudo().search(` findings sitting on a model with any rule at all,
356
+ 10 were on spp.vocabulary.code, whose single rule is exactly that.
357
+
358
+ The ref is turned into a dotted name forwards -- `model_spp_area` becomes
359
+ spp.area -- which is ambiguous for a model whose own name contains an
360
+ underscore. That is the same ambiguity _models_granted_unlink avoids by
361
+ matching forwards from declared models, and it is not avoidable here
362
+ because the rule names the model and nothing else does.
363
+
364
+ A rule this fails to read therefore reads as "no rule", and for a model the
365
+ tree declares that SUPPRESSES a finding -- the unsafe direction, and the one
366
+ thing in this trigger worth being careful about. Two things hold it down:
367
+ the parse is deliberately loose about attribute order, because one OpenSPP
368
+ file writes `ref=` before `name=` on all ten of its rules and a stricter
369
+ pattern silently lost them; and a model whose rules are all unreadable is a
370
+ model nobody can show is protected, which is a finding worth making anyway.
371
+ """
372
+ found = set()
373
+ for block in _IR_RULE_RE.findall(body):
374
+ ref = _RULE_MODEL_RE.search(block)
375
+ if not ref:
376
+ continue
377
+ token = ref.group(1).rsplit(".", 1)[-1]
378
+ if not token.startswith("model_"):
379
+ continue
380
+ domain = _RULE_DOMAIN_RE.search(block)
381
+ text = " ".join(domain.group(1).split()) if domain else ""
382
+ if text and _VACUOUS_DOMAIN.match(text):
383
+ continue
384
+ found.add(token[len("model_"):].replace("_", "."))
385
+ return found
386
+
387
+
388
+ def model_is_record_ruled(facts, model):
389
+ """Would sudo() on this model actually drop a filter?
390
+
391
+ PRESENT if the tree declares an ir.rule for it that filters. ABSENT if a
392
+ whole tree was walked, the tree DECLARES the model, and no filtering rule
393
+ came with it. UNKNOWN otherwise.
394
+
395
+ The rule this backs says sudo "drops ir.rule filtering". Where there is no
396
+ filtering to drop, the finding describes something that cannot happen, and
397
+ on OpenSPP that was 83 of 106 findings.
398
+
399
+ The model must be declared here for ABSENT to mean anything, and getting
400
+ that wrong is measurable rather than theoretical. A walk of an addons tree
401
+ sees the addons' rules and not Odoo's own: product.template carries core's
402
+ company rule, which lives in the `product` module and not in the tree being
403
+ read. Answering ABSENT from that silence dropped three true positives in
404
+ spp_api_v2_products on the first run of this trigger -- a sudo lookup of
405
+ every company's products, which is exactly the leak the rule is about.
406
+
407
+ So silence about a model nobody declared is UNKNOWN, and the finding
408
+ stands. The tree can say "we declare this and give it no rule". It cannot
409
+ say anything about a model it never saw.
410
+ """
411
+ if not facts or not facts.get("walked") or not model:
412
+ return UNKNOWN
413
+ if model in facts.get("models_record_ruled", ()):
414
+ return PRESENT
415
+ if not facts.get("complete"):
416
+ return UNKNOWN
417
+ return ABSENT if model in facts.get("models_declared", ()) else UNKNOWN
418
+
419
+
420
  def _models_granted_unlink(body):
421
  """The `model_id:id` tokens an ir.model.access.csv grants perm_unlink on."""
422
  rows = list(csv.reader(io.StringIO(body)))
bin/layers.py CHANGED
@@ -39,6 +39,7 @@ not-run rather than silently passing them.
39
 
40
  Stdlib only.
41
  """
 
42
  import io
43
  import json
44
  import os
@@ -266,6 +267,52 @@ SUPPRESSION_MARKERS = ("noqa", "nosemgrep", "nosec", "pylint: disable",
266
  "pylint:disable", "type: ignore", "eslint-disable",
267
  "@ts-ignore", "@ts-expect-error")
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
  class SourceHit(object):
271
  """One checker firing on a source file: what, where, and two things about
@@ -343,9 +390,30 @@ def scan_source(body, rules, stack, errors=None, facts=None, gaps=None):
343
  # very markers this looks for.
344
  rows = body.splitlines()
345
 
 
 
 
 
 
 
 
 
 
346
  def marked(line):
347
  text = rows[line - 1] if 0 < line <= len(rows) else ""
348
- return any(marker in text for marker in SUPPRESSION_MARKERS)
 
 
 
 
 
 
 
 
 
 
 
 
349
 
350
  # One parse for the whole file, before any semantic checker runs, so that
351
  # "nothing to say" and "could not read it" stop being the same output. Only
 
39
 
40
  Stdlib only.
41
  """
42
+ import ast
43
  import io
44
  import json
45
  import os
 
267
  "pylint:disable", "type: ignore", "eslint-disable",
268
  "@ts-ignore", "@ts-expect-error")
269
 
270
+ # Statement kinds a marker is allowed to reach across. Every one of these is a
271
+ # SIMPLE statement: it has no body of its own, so its line span is the span of
272
+ # one expression and nothing else can hide inside it. Compound statements are
273
+ # deliberately absent -- letting a marker on an `if` header cover the whole
274
+ # block is exactly the mute button this project will not build.
275
+ _SIMPLE_STMTS = (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Expr,
276
+ ast.Return, ast.Raise, ast.Assert, ast.Delete)
277
+
278
+
279
+ def _simple_statement_spans(body):
280
+ """(start, end) for every simple statement in a Python source, widest last.
281
+
282
+ A finding anchors on one line, and a suppression marker written about that
283
+ finding may sit on another line of the SAME expression. Measured on
284
+ OpenSPP: 105 of 106 `.sudo().search(` findings carry a `# nosemgrep:` about
285
+ that exact construct, and not one of them shares a line with it, because
286
+ the call is chained across four lines and the checker anchors on `.sudo()`
287
+ while the marker sits above it.
288
+
289
+ Reading the marker only on the finding's own line therefore reported 106
290
+ critical findings at sites somebody had already read and justified. Reading
291
+ it anywhere nearby would be worse: a marker three lines up belongs to
292
+ whatever it was written about, which may be a different statement entirely.
293
+
294
+ The statement is the honest unit. A marker inside an expression is about
295
+ that expression, and it cannot reach the next one.
296
+ """
297
+ tree, _ = semantics.parse_python(body)
298
+ if tree is None:
299
+ return []
300
+ spans = []
301
+ for node in ast.walk(tree):
302
+ if isinstance(node, _SIMPLE_STMTS):
303
+ start = getattr(node, "lineno", None)
304
+ end = getattr(node, "end_lineno", None)
305
+ if start and end:
306
+ # start - 1 is the comment line immediately above the statement.
307
+ # Every analyser whose markers this reads -- semgrep, ruff,
308
+ # pylint -- documents that position as belonging to the line
309
+ # below it, and that is where most of these markers are written:
310
+ # 36 of OpenSPP's 105 sit there, above a statement that fits on
311
+ # one line and has no room for anything else. Reading the same
312
+ # convention the marker was written under is not obeying it.
313
+ spans.append((max(1, start - 1), end))
314
+ return spans
315
+
316
 
317
  class SourceHit(object):
318
  """One checker firing on a source file: what, where, and two things about
 
390
  # very markers this looks for.
391
  rows = body.splitlines()
392
 
393
+ # Spans are computed once per file, and only for Python: the other stacks
394
+ # have no parser here, so a marker there still reaches its own line only.
395
+ # That asymmetry is stated rather than hidden -- a JS finding on a chained
396
+ # call will read as unsuppressed when a person had in fact marked it.
397
+ spans = _simple_statement_spans(body) if comment_kind(stack) == "python" else []
398
+
399
+ def _has_marker(text):
400
+ return any(marker in text for marker in SUPPRESSION_MARKERS)
401
+
402
  def marked(line):
403
  text = rows[line - 1] if 0 < line <= len(rows) else ""
404
+ if _has_marker(text):
405
+ return True
406
+ # The smallest simple statement containing this line, if any: a marker
407
+ # written inside one expression covers that expression and stops there.
408
+ best = None
409
+ for start, end in spans:
410
+ if start <= line <= end and (best is None or
411
+ (end - start) < (best[1] - best[0])):
412
+ best = (start, end)
413
+ if best is None:
414
+ return False
415
+ return any(_has_marker(rows[n - 1]) for n in range(best[0], best[1] + 1)
416
+ if 0 < n <= len(rows))
417
 
418
  # One parse for the whole file, before any semantic checker runs, so that
419
  # "nothing to say" and "could not read it" stop being the same output. Only
bin/semantics.py CHANGED
@@ -1837,8 +1837,102 @@ def declares_multi_company(code):
1837
  return any(marker in view for marker in MULTI_COMPANY_MARKERS)
1838
 
1839
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1840
  CHECKS = {
1841
  "handler_order_shadows_a_subclass": handler_order_shadows_a_subclass,
 
1842
  "check_company_missing_on_company_owned_relation":
1843
  check_company_missing_on_company_owned_relation,
1844
  "commit_inside_a_loop": commit_inside_a_loop,
@@ -1858,6 +1952,9 @@ CHECKS = {
1858
  CONTEXT_CHECKS = frozenset([
1859
  "ondelete_leaves_a_dangling_read",
1860
  "check_company_missing_on_company_owned_relation",
 
 
 
1861
  # Reads odoo_series: whether a _sql_constraints block is a constraint at
1862
  # all depends on which Odoo is going to load it.
1863
  "prefer_sql_constraint_applies",
 
1837
  return any(marker in view for marker in MULTI_COMPANY_MARKERS)
1838
 
1839
 
1840
+ def sudo_search_drops_a_real_rule(code, facts=None):
1841
+ """odoo.sudo-bypasses-record-rules
1842
+
1843
+ Fires on `<model>.sudo().search(...)` where the model actually carries an
1844
+ ir.rule that filters something, and the domain does not re-apply company
1845
+ scoping by hand.
1846
+
1847
+ The regex this replaces asked only the first half of the question -- is
1848
+ there a `.sudo().search(` -- and on OpenSPP that was 106 findings of which
1849
+ 6 were the thing the rule describes. The rule's own sentence is that sudo
1850
+ "drops ir.rule filtering". Where the tree declares no filtering rule for
1851
+ that model, there is nothing to drop and the finding is about a mechanism
1852
+ that does not exist here. Classified by hand, that was 83 of the 106:
1853
+ 73 on models with no rule at all, and 10 on spp.vocabulary.code, whose
1854
+ single rule is `[(1, '=', 1)]` and filters nothing.
1855
+
1856
+ The trigger only ever SUPPRESSES. A model the walk could not name, a tree
1857
+ that was not walked, a partial walk -- each reads UNKNOWN and the finding
1858
+ stands, because "no rule was found" and "nobody looked" must not be the
1859
+ same answer on a critical rule. `nexa check` on one file therefore behaves
1860
+ exactly as it did before this trigger existed.
1861
+
1862
+ What it still over-reports, measured and not yet fixed: 14 of the remaining
1863
+ 20 constrain the search to records the caller had already identified -- a
1864
+ group id, a partner id, an area -- which is the rule's advice followed in a
1865
+ spelling the rule does not recognise, since it names only company_id. That
1866
+ is a worklist entry rather than a narrowing, because deciding in general
1867
+ that a domain is already scoped means evaluating it, and a narrowing that
1868
+ guesses would cost the 6 true positives this keeps.
1869
+ """
1870
+ tree, _ = parse_python(code)
1871
+ if tree is None:
1872
+ return []
1873
+
1874
+ findings = []
1875
+ for node in ast.walk(tree):
1876
+ if not isinstance(node, ast.Call):
1877
+ continue
1878
+ if not isinstance(node.func, ast.Attribute):
1879
+ continue
1880
+ if node.func.attr not in ("search", "search_count", "search_read"):
1881
+ continue
1882
+ inner = node.func.value
1883
+ if not (isinstance(inner, ast.Call) and isinstance(inner.func, ast.Attribute)
1884
+ and inner.func.attr == "sudo"):
1885
+ continue
1886
+
1887
+ # The rule's own correct sample re-applies company filtering inside the
1888
+ # domain, so a domain naming company_id is the rule satisfied. Read off
1889
+ # the call's own arguments rather than the file, because a company_id
1890
+ # anywhere else in a module said nothing about THIS search.
1891
+ try:
1892
+ call_src = ast.unparse(node)
1893
+ except Exception: # pragma: no cover
1894
+ call_src = ""
1895
+ if "company_id" in call_src:
1896
+ continue
1897
+
1898
+ model = _env_model_of(inner.func.value)
1899
+ if context.model_is_record_ruled(facts, model) == context.ABSENT:
1900
+ continue
1901
+
1902
+ where = " on {0}".format(model) if model else ""
1903
+ findings.append((
1904
+ node.lineno,
1905
+ "sudo().{0}(){1} runs with record rules off -- ir.rule filtering, "
1906
+ "including multi-company isolation, does not apply and the result "
1907
+ "looks correct in single-company testing".format(node.func.attr, where),
1908
+ None))
1909
+ return findings
1910
+
1911
+
1912
+ def _env_model_of(node):
1913
+ """The model name at the head of a chained call, or None.
1914
+
1915
+ Resolves `self.env["res.partner"]` and `env["res.partner"]` through any
1916
+ number of intervening attributes. A head that is a plain name -- the
1917
+ `Partner` in `Partner.sudo().search(...)` -- is not resolvable without
1918
+ following the assignment, so it returns None and the caller treats the
1919
+ record-rule question as unanswered rather than answered no.
1920
+ """
1921
+ while isinstance(node, (ast.Call, ast.Attribute)):
1922
+ node = node.func if isinstance(node, ast.Call) else node.value
1923
+ if isinstance(node, ast.Subscript) and isinstance(node.slice, ast.Constant) \
1924
+ and isinstance(node.slice.value, str):
1925
+ target = node.value
1926
+ name = target.attr if isinstance(target, ast.Attribute) else (
1927
+ target.id if isinstance(target, ast.Name) else None)
1928
+ if name == "env":
1929
+ return node.slice.value
1930
+ return None
1931
+
1932
+
1933
  CHECKS = {
1934
  "handler_order_shadows_a_subclass": handler_order_shadows_a_subclass,
1935
+ "sudo_search_drops_a_real_rule": sudo_search_drops_a_real_rule,
1936
  "check_company_missing_on_company_owned_relation":
1937
  check_company_missing_on_company_owned_relation,
1938
  "commit_inside_a_loop": commit_inside_a_loop,
 
1952
  CONTEXT_CHECKS = frozenset([
1953
  "ondelete_leaves_a_dangling_read",
1954
  "check_company_missing_on_company_owned_relation",
1955
+ # Reads models_record_ruled: whether sudo drops anything depends on whether
1956
+ # the tree declares a rule that filters.
1957
+ "sudo_search_drops_a_real_rule",
1958
  # Reads odoo_series: whether a _sql_constraints block is a constraint at
1959
  # all depends on which Odoo is going to load it.
1960
  "prefer_sql_constraint_applies",
classification-oca-2026-09.json CHANGED
@@ -1,927 +1,948 @@
1
  {
2
- "generated_utc": "2026-09-15",
3
- "stack": "odoo",
4
- "roots": [
5
- "server-tools",
6
- "account-financial-tools"
7
- ],
8
- "files": 606,
9
- "lines": 33960,
10
- "checkouts": {
11
- "server-tools": "60cd54e5e",
12
- "account-financial-tools": "e4c1b86aa"
13
- },
14
- "totals": {
15
- "false": 8,
16
- "true": 79,
17
- "undecidable": 13
18
- },
19
- "classes": {
20
- "registry-model": 4,
21
- "advice-cannot-be-taken": 2,
22
- "storedness-unknown": 13,
23
- "wrong-construct": 2
24
- },
25
- "hits": [
26
- {
27
- "rule": "odoo.sudo-bypasses-record-rules",
28
- "path": "server-tools/attachment_delete_restrict/models/ir_attachment.py",
29
- "line": 71,
30
- "verdict": "false",
31
- "class": "registry-model",
32
- "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
33
- "evidence": "models = self.env[\"ir.model\"].sudo().search([(\"model\", \"in\", res_models)])"
34
- },
35
- {
36
- "rule": "odoo.ondelete-is-a-decision",
37
- "path": "server-tools/attachment_synchronize/models/attachment_queue.py",
38
- "line": 14,
39
- "verdict": "true",
40
- "class": null,
41
- "why": null,
42
- "evidence": "task_id declares no ondelete, so the ORM defaults it to 'set null' -- and task_id.filepath on line 37 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete attachment.synchronize.task"
43
- },
44
- {
45
- "rule": "odoo.ondelete-is-a-decision",
46
- "path": "server-tools/attachment_synchronize/models/attachment_synchronize_task.py",
47
- "line": 64,
48
- "verdict": "true",
49
- "class": null,
50
- "why": null,
51
- "evidence": "backend_id declares no ondelete, so the ORM defaults it to 'set null' -- and backend_id.fs on line 166 reads through it, which yields False rather than raising once the parent is deleted"
52
- },
53
- {
54
- "rule": "odoo.stored-compute-needs-complete-depends",
55
- "path": "server-tools/auditlog/models/http_session.py",
56
- "line": 29,
57
- "verdict": "true",
58
- "class": null,
59
- "why": null,
60
- "evidence": "_compute_display_name() reads 'user_id.name' but @api.depends does not declare it -- the stored value goes stale when 'user_id.name' changes"
61
- },
62
- {
63
- "rule": "odoo.constrains-must-list-every-field-read",
64
- "path": "server-tools/autovacuum_message_attachment/models/vacuum_rule.py",
65
- "line": 100,
66
- "verdict": "true",
67
- "class": null,
68
- "why": null,
69
- "evidence": "_check_inheriting_model() reads 'ttype' but @api.constrains declares only inheriting_model -- writing 'ttype' alone skips the check entirely"
70
- },
71
- {
72
- "rule": "odoo.constrains-must-list-every-field-read",
73
- "path": "server-tools/base_exception/models/exception_rule.py",
74
- "line": 59,
75
- "verdict": "true",
76
- "class": null,
77
- "why": null,
78
- "evidence": "check_exception_type_consistency() reads 'method' but @api.constrains declares only code, domain, exception_type, model -- writing 'method' alone skips the check entirely"
79
- },
80
- {
81
- "rule": "odoo.sql-needs-query-parameters",
82
- "path": "server-tools/base_multi_image/hooks.py",
83
- "line": 47,
84
- "verdict": "false",
85
- "class": "advice-cannot-be-taken",
86
- "why": "a table or column name cannot be a query parameter; both sites already parameterise the values",
87
- "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them"
88
- },
89
- {
90
- "rule": "odoo.depends-must-not-name-unread-fields",
91
- "path": "server-tools/base_multi_image/models/image.py",
92
- "line": 103,
93
- "verdict": "true",
94
- "class": null,
95
- "why": null,
96
- "evidence": "_compute_show_technical() declares 'owner_id', 'owner_model' in @api.depends and never reads them -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
97
- },
98
- {
99
- "rule": "odoo.sql-needs-query-parameters",
100
- "path": "server-tools/database_autovacuum_tuning/models/database_autovacuum_tuning.py",
101
- "line": 24,
102
- "verdict": "false",
103
- "class": "advice-cannot-be-taken",
104
- "why": "a table or column name cannot be a query parameter; both sites already parameterise the values",
105
- "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them"
106
- },
107
- {
108
- "rule": "odoo.sudo-bypasses-record-rules",
109
- "path": "server-tools/database_size/models/ir_model_size.py",
110
- "line": 87,
111
- "verdict": "false",
112
- "class": "registry-model",
113
- "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
114
- "evidence": "model.model: model.name for model in self.env[\"ir.model\"].sudo().search([])"
115
- },
116
- {
117
- "rule": "odoo.ondelete-is-a-decision",
118
- "path": "server-tools/excel_import_export/models/xlsx_template.py",
119
- "line": 108,
120
- "verdict": "true",
121
- "class": null,
122
- "why": null,
123
- "evidence": "result_model_id declares no ondelete, so the ORM defaults it to 'set null' -- and result_model_id.model on line 199 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete ir.model"
124
- },
125
- {
126
- "rule": "odoo.ondelete-is-a-decision",
127
- "path": "server-tools/fetchmail_attach_from_folder/models/fetchmail_server_folder.py",
128
- "line": 25,
129
- "verdict": "true",
130
- "class": null,
131
- "why": null,
132
- "evidence": "server_id declares no ondelete, so the ORM defaults it to 'set null' -- and server_id.connect on line 139 reads through it, which yields False rather than raising once the parent is deleted"
133
- },
134
- {
135
- "rule": "odoo.sudo-bypasses-record-rules",
136
- "path": "server-tools/tracking_manager/models/ir_model.py",
137
- "line": 34,
138
- "verdict": "false",
139
- "class": "registry-model",
140
- "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
141
- "evidence": "models = self.sudo().search([(\"active_custom_tracking\", \"=\", True)])"
142
- },
143
- {
144
- "rule": "odoo.depends-must-not-name-unread-fields",
145
- "path": "server-tools/tracking_manager/models/ir_model.py",
146
- "line": 101,
147
- "verdict": "true",
148
- "class": null,
149
- "why": null,
150
- "evidence": "_compute_automatic_custom_tracking() declares 'active_custom_tracking' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
151
- },
152
- {
153
- "rule": "odoo.stored-compute-needs-complete-depends",
154
- "path": "server-tools/tracking_manager/models/ir_model.py",
155
- "line": 135,
156
- "verdict": "true",
157
- "class": null,
158
- "why": null,
159
- "evidence": "_compute_automatic_custom_tracking_domain() reads 'model' but @api.depends does not declare it -- the stored value goes stale when 'model' changes"
160
- },
161
- {
162
- "rule": "odoo.stored-compute-needs-complete-depends",
163
- "path": "server-tools/tracking_manager/models/ir_model_fields.py",
164
- "line": 30,
165
- "verdict": "true",
166
- "class": null,
167
- "why": null,
168
- "evidence": "_compute_custom_tracking() reads 'model_id' but @api.depends does not declare it -- the stored value goes stale when 'model_id' changes"
169
- },
170
- {
171
- "rule": "odoo.stored-compute-needs-complete-depends",
172
- "path": "server-tools/tracking_manager/models/ir_model_fields.py",
173
- "line": 30,
174
- "verdict": "true",
175
- "class": null,
176
- "why": null,
177
- "evidence": "_compute_custom_tracking() reads 'model_id.automatic_custom_tracking' but @api.depends does not declare it -- the stored value goes stale when 'model_id.automatic_custom_tracking' changes"
178
- },
179
- {
180
- "rule": "odoo.stored-compute-needs-complete-depends",
181
- "path": "server-tools/tracking_manager/models/ir_model_fields.py",
182
- "line": 31,
183
- "verdict": "true",
184
- "class": null,
185
- "why": null,
186
- "evidence": "_compute_custom_tracking() reads 'model_id.automatic_custom_tracking_domain' but @api.depends does not declare it -- the stored value goes stale when 'model_id.automatic_custom_tracking_domain' changes"
187
- },
188
- {
189
- "rule": "odoo.stored-compute-needs-complete-depends",
190
- "path": "server-tools/tracking_manager/models/ir_model_fields.py",
191
- "line": 52,
192
- "verdict": "true",
193
- "class": null,
194
- "why": null,
195
- "evidence": "_compute_trackable() reads 'name' but @api.depends does not declare it -- the stored value goes stale when 'name' changes"
196
- },
197
- {
198
- "rule": "odoo.stored-compute-needs-complete-depends",
199
- "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
200
- "line": 60,
201
- "verdict": "true",
202
- "class": null,
203
- "why": null,
204
- "evidence": "_compute_account_asset_id() reads 'state' but @api.depends does not declare it -- the stored value goes stale when 'state' changes"
205
- },
206
- {
207
- "rule": "odoo.stored-compute-needs-complete-depends",
208
- "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
209
- "line": 64,
210
- "verdict": "true",
211
- "class": null,
212
- "why": null,
213
- "evidence": "_compute_account_asset_id() reads 'account_move_line_ids.account_id' but @api.depends does not declare it -- the stored value goes stale when 'account_move_line_ids.account_id' changes"
214
- },
215
- {
216
- "rule": "odoo.stored-compute-needs-complete-depends",
217
- "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
218
- "line": 72,
219
- "verdict": "true",
220
- "class": null,
221
- "why": null,
222
- "evidence": "_compute_account_asset_id() reads 'profile_id.account_asset_id' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.account_asset_id' changes"
223
- },
224
- {
225
- "rule": "odoo.stored-compute-needs-complete-depends",
226
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
227
- "line": 311,
228
- "verdict": "true",
229
- "class": null,
230
- "why": null,
231
- "evidence": "_compute_group_ids() reads 'profile_id.group_ids' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.group_ids' changes"
232
- },
233
- {
234
- "rule": "odoo.stored-compute-needs-complete-depends",
235
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
236
- "line": 316,
237
- "verdict": "true",
238
- "class": null,
239
- "why": null,
240
- "evidence": "_compute_method() reads 'profile_id.method' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method' changes"
241
- },
242
- {
243
- "rule": "odoo.stored-compute-needs-complete-depends",
244
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
245
- "line": 324,
246
- "verdict": "true",
247
- "class": null,
248
- "why": null,
249
- "evidence": "_compute_method_number() reads 'profile_id.method_number' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_number' changes"
250
- },
251
- {
252
- "rule": "odoo.stored-compute-needs-complete-depends",
253
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
254
- "line": 329,
255
- "verdict": "true",
256
- "class": null,
257
- "why": null,
258
- "evidence": "_compute_method_period() reads 'profile_id.method_period' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_period' changes"
259
- },
260
- {
261
- "rule": "odoo.stored-compute-needs-complete-depends",
262
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
263
- "line": 340,
264
- "verdict": "true",
265
- "class": null,
266
- "why": null,
267
- "evidence": "_compute_method_progress_factor() reads 'profile_id.method_progress_factor' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_progress_factor' changes"
268
- },
269
- {
270
- "rule": "odoo.stored-compute-needs-complete-depends",
271
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
272
- "line": 345,
273
- "verdict": "true",
274
- "class": null,
275
- "why": null,
276
- "evidence": "_compute_method_time() reads 'profile_id.method_time' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_time' changes"
277
- },
278
- {
279
- "rule": "odoo.stored-compute-needs-complete-depends",
280
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
281
- "line": 350,
282
- "verdict": "true",
283
- "class": null,
284
- "why": null,
285
- "evidence": "_compute_days_calc() reads 'profile_id.days_calc' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.days_calc' changes"
286
- },
287
- {
288
- "rule": "odoo.stored-compute-needs-complete-depends",
289
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
290
- "line": 355,
291
- "verdict": "true",
292
- "class": null,
293
- "why": null,
294
- "evidence": "_compute_use_leap_years() reads 'profile_id.use_leap_years' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.use_leap_years' changes"
295
- },
296
- {
297
- "rule": "odoo.stored-compute-needs-complete-depends",
298
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
299
- "line": 363,
300
- "verdict": "true",
301
- "class": null,
302
- "why": null,
303
- "evidence": "_compute_prorrata() reads 'profile_id.prorata' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.prorata' changes"
304
- },
305
- {
306
- "rule": "odoo.stored-compute-needs-complete-depends",
307
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
308
- "line": 368,
309
- "verdict": "undecidable",
310
- "class": "storedness-unknown",
311
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
312
- "evidence": "_compute_account_analytic_id() reads 'profile_id.account_analytic_id' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.account_analytic_id' changes"
313
- },
314
- {
315
- "rule": "odoo.stored-compute-needs-complete-depends",
316
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
317
- "line": 373,
318
- "verdict": "undecidable",
319
- "class": "storedness-unknown",
320
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
321
- "evidence": "_compute_analytic_distribution() reads 'profile_id.analytic_distribution' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.analytic_distribution' changes"
322
- },
323
- {
324
- "rule": "odoo.constrains-must-list-every-field-read",
325
- "path": "account-financial-tools/account_asset_management/models/account_asset.py",
326
- "line": 398,
327
- "verdict": "true",
328
- "class": null,
329
- "why": null,
330
- "evidence": "_check_profile_change() reads 'depreciation_line_ids' but @api.constrains declares only profile_id -- writing 'depreciation_line_ids' alone skips the check entirely"
331
- },
332
- {
333
- "rule": "odoo.recursion-check-on-hierarchy",
334
- "path": "account-financial-tools/account_asset_management/models/account_asset_group.py",
335
- "line": 27,
336
- "verdict": "true",
337
- "class": null,
338
- "why": null,
339
- "evidence": "parent_id = fields.Many2one("
340
- },
341
- {
342
- "rule": "odoo.ondelete-is-a-decision",
343
- "path": "account-financial-tools/account_asset_management/models/account_move.py",
344
- "line": 174,
345
- "verdict": "true",
346
- "class": null,
347
- "why": null,
348
- "evidence": "asset_profile_id declares no ondelete, so the ORM defaults it to 'set null' -- and asset_profile_id.account_asset_id on line 200 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete account.asset.profile"
349
- },
350
- {
351
- "rule": "odoo.stored-compute-needs-complete-depends",
352
- "path": "account-financial-tools/account_asset_management/models/account_move.py",
353
- "line": 192,
354
- "verdict": "true",
355
- "class": null,
356
- "why": null,
357
- "evidence": "_compute_asset_profile() reads 'account_id.asset_profile_id' but @api.depends does not declare it -- the stored value goes stale when 'account_id.asset_profile_id' changes"
358
- },
359
- {
360
- "rule": "odoo.stored-compute-needs-complete-depends",
361
- "path": "account-financial-tools/account_asset_management/models/account_move.py",
362
- "line": 195,
363
- "verdict": "true",
364
- "class": null,
365
- "why": null,
366
- "evidence": "_compute_asset_profile() reads 'asset_id.profile_id' but @api.depends does not declare it -- the stored value goes stale when 'asset_id.profile_id' changes"
367
- },
368
- {
369
- "rule": "odoo.constrains-iterate-recordset",
370
- "path": "account-financial-tools/account_asset_management/wizard/account_asset_remove.py",
371
- "line": 80,
372
- "verdict": "true",
373
- "class": null,
374
- "why": null,
375
- "evidence": "@api.constrains(\"sale_value\", \"company_id\")"
376
- },
377
- {
378
- "rule": "odoo.constrains-must-list-every-field-read",
379
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
380
- "line": 130,
381
- "verdict": "true",
382
- "class": null,
383
- "why": null,
384
- "evidence": "_check_deposit() reads 'company_id' but @api.constrains declares only cash_journal_id, currency_id -- writing 'company_id' alone skips the check entirely"
385
- },
386
- {
387
- "rule": "odoo.stored-compute-needs-complete-depends",
388
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
389
- "line": 203,
390
- "verdict": "true",
391
- "class": null,
392
- "why": null,
393
- "evidence": "_compute_is_reconcile() reads 'cash_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'cash_journal_id' changes"
394
- },
395
- {
396
- "rule": "odoo.stored-compute-needs-complete-depends",
397
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
398
- "line": 203,
399
- "verdict": "true",
400
- "class": null,
401
- "why": null,
402
- "evidence": "_compute_is_reconcile() reads 'cash_journal_id.default_account_id' but @api.depends does not declare it -- the stored value goes stale when 'cash_journal_id.default_account_id' changes"
403
- },
404
- {
405
- "rule": "odoo.stored-compute-needs-complete-depends",
406
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
407
- "line": 203,
408
- "verdict": "true",
409
- "class": null,
410
- "why": null,
411
- "evidence": "_compute_is_reconcile() reads 'move_id.line_ids.account_id' but @api.depends does not declare it -- the stored value goes stale when 'move_id.line_ids.account_id' changes"
412
- },
413
- {
414
- "rule": "odoo.float-comparison-needs-float-utils",
415
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
416
- "line": 271,
417
- "verdict": "false",
418
- "class": "wrong-construct",
419
- "why": "parent_id pointing at another model / == on an Integer / a commit on a separate cursor",
420
- "evidence": "self.line_ids.filtered(lambda x: x.qty == 0).unlink()"
421
- },
422
- {
423
- "rule": "odoo.recursion-check-on-hierarchy",
424
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
425
- "line": 381,
426
- "verdict": "false",
427
- "class": "wrong-construct",
428
- "why": "parent_id pointing at another model / == on an Integer / a commit on a separate cursor",
429
- "evidence": "parent_id = fields.Many2one(\"account.cash.deposit\", ondelete=\"cascade\")"
430
- },
431
- {
432
- "rule": "odoo.stored-compute-needs-complete-depends",
433
- "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
434
- "line": 420,
435
- "verdict": "true",
436
- "class": null,
437
- "why": null,
438
- "evidence": "_compute_subtotal() reads 'cash_unit_id.total_value' but @api.depends does not declare it -- the stored value goes stale when 'cash_unit_id.total_value' changes"
439
- },
440
- {
441
- "rule": "odoo.stored-compute-needs-complete-depends",
442
- "path": "account-financial-tools/account_cash_deposit/models/cash_unit.py",
443
- "line": 114,
444
- "verdict": "undecidable",
445
- "class": "storedness-unknown",
446
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
447
- "evidence": "_compute_display_name() reads 'total_value' but @api.depends does not declare it -- the stored value goes stale when 'total_value' changes"
448
- },
449
- {
450
- "rule": "odoo.check-company-on-relations",
451
- "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
452
- "line": 41,
453
- "verdict": "true",
454
- "class": null,
455
- "why": null,
456
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
457
- },
458
- {
459
- "rule": "odoo.stored-compute-needs-complete-depends",
460
- "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
461
- "line": 132,
462
- "verdict": "true",
463
- "class": null,
464
- "why": null,
465
- "evidence": "_compute_check_deposit() reads 'company_id.currency_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.currency_id' changes"
466
- },
467
- {
468
- "rule": "odoo.stored-compute-needs-complete-depends",
469
- "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
470
- "line": 145,
471
- "verdict": "true",
472
- "class": null,
473
- "why": null,
474
- "evidence": "_compute_currency_id() reads 'company_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id' changes"
475
- },
476
- {
477
- "rule": "odoo.stored-compute-needs-complete-depends",
478
- "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
479
- "line": 146,
480
- "verdict": "true",
481
- "class": null,
482
- "why": null,
483
- "evidence": "_compute_currency_id() reads 'journal_id.currency_id' but @api.depends does not declare it -- the stored value goes stale when 'journal_id.currency_id' changes"
484
- },
485
- {
486
- "rule": "odoo.stored-compute-needs-complete-depends",
487
- "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
488
- "line": 151,
489
- "verdict": "true",
490
- "class": null,
491
- "why": null,
492
- "evidence": "_compute_in_hand_check_account_id() reads 'journal_id.inbound_payment_method_line_ids' but @api.depends does not declare it -- the stored value goes stale when 'journal_id.inbound_payment_method_line_ids' changes"
493
- },
494
- {
495
- "rule": "odoo.ondelete-is-a-decision",
496
- "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
497
- "line": 17,
498
- "verdict": "true",
499
- "class": null,
500
- "why": null,
501
- "evidence": "mis_report_instance_id declares no ondelete, so the ORM defaults it to 'set null' -- and mis_report_instance_id.currency_id on line 105 reads through it, which yields False rather than raising once the parent is deleted"
502
- },
503
- {
504
- "rule": "odoo.ondelete-is-a-decision",
505
- "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
506
- "line": 35,
507
- "verdict": "true",
508
- "class": null,
509
- "why": null,
510
- "evidence": "mis_report_instance_period_id declares no ondelete, so the ORM defaults it to 'set null' -- and mis_report_instance_period_id.date_from on line 103 reads through it, which yields False rather than raising once the parent is deleted"
511
- },
512
- {
513
- "rule": "odoo.constrains-must-list-every-field-read",
514
- "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
515
- "line": 47,
516
- "verdict": "true",
517
- "class": null,
518
- "why": null,
519
- "evidence": "_check_mis_builder() reads 'cell_type' but @api.constrains declares only mis_report_instance_id, mis_report_kpi_id -- writing 'cell_type' alone skips the check entirely"
520
- },
521
- {
522
- "rule": "odoo.constrains-must-list-every-field-read",
523
- "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
524
- "line": 74,
525
- "verdict": "true",
526
- "class": null,
527
- "why": null,
528
- "evidence": "_check_mis_builder() reads 'mis_report_instance_period_id' but @api.constrains declares only mis_report_instance_id, mis_report_kpi_id -- writing 'mis_report_instance_period_id' alone skips the check entirely"
529
- },
530
- {
531
- "rule": "odoo.stored-compute-needs-complete-depends",
532
- "path": "account-financial-tools/account_journal_general_sequence/models/account_journal.py",
533
- "line": 42,
534
- "verdict": "true",
535
- "class": null,
536
- "why": null,
537
- "evidence": "_compute_entry_number_sequence() reads 'company_id.name' but @api.depends does not declare it -- the stored value goes stale when 'company_id.name' changes"
538
- },
539
- {
540
- "rule": "odoo.check-company-on-relations",
541
- "path": "account-financial-tools/account_loan/models/account_loan.py",
542
- "line": 144,
543
- "verdict": "true",
544
- "class": null,
545
- "why": null,
546
- "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
547
- },
548
- {
549
- "rule": "odoo.check-company-on-relations",
550
- "path": "account-financial-tools/account_loan/models/account_loan.py",
551
- "line": 149,
552
- "verdict": "true",
553
- "class": null,
554
- "why": null,
555
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
556
- },
557
- {
558
- "rule": "odoo.check-company-on-relations",
559
- "path": "account-financial-tools/account_loan/models/account_loan.py",
560
- "line": 156,
561
- "verdict": "true",
562
- "class": null,
563
- "why": null,
564
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
565
- },
566
- {
567
- "rule": "odoo.check-company-on-relations",
568
- "path": "account-financial-tools/account_loan/models/account_loan.py",
569
- "line": 162,
570
- "verdict": "true",
571
- "class": null,
572
- "why": null,
573
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
574
- },
575
- {
576
- "rule": "odoo.check-company-on-relations",
577
- "path": "account-financial-tools/account_loan/models/account_loan.py",
578
- "line": 170,
579
- "verdict": "true",
580
- "class": null,
581
- "why": null,
582
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
583
- },
584
- {
585
- "rule": "odoo.depends-must-not-name-unread-fields",
586
- "path": "account-financial-tools/account_loan/models/account_loan.py",
587
- "line": 246,
588
- "verdict": "true",
589
- "class": null,
590
- "why": null,
591
- "evidence": "_compute_total_amounts() declares 'currency_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
592
- },
593
- {
594
- "rule": "odoo.stored-compute-needs-complete-depends",
595
- "path": "account-financial-tools/account_loan/models/account_loan_line.py",
596
- "line": 132,
597
- "verdict": "true",
598
- "class": null,
599
- "why": null,
600
- "evidence": "_compute_rate() reads 'pending_principal_amount' but @api.depends does not declare it -- the stored value goes stale when 'pending_principal_amount' changes"
601
- },
602
- {
603
- "rule": "odoo.stored-compute-needs-complete-depends",
604
- "path": "account-financial-tools/account_loan/models/account_loan_line.py",
605
- "line": 139,
606
- "verdict": "true",
607
- "class": null,
608
- "why": null,
609
- "evidence": "_compute_interests_amount() reads 'pending_principal_amount' but @api.depends does not declare it -- the stored value goes stale when 'pending_principal_amount' changes"
610
- },
611
- {
612
- "rule": "odoo.stored-compute-needs-complete-depends",
613
- "path": "account-financial-tools/account_loan/models/account_loan_line.py",
614
- "line": 174,
615
- "verdict": "true",
616
- "class": null,
617
- "why": null,
618
- "evidence": "_compute_principal_amount() reads 'interests_amount' but @api.depends does not declare it -- the stored value goes stale when 'interests_amount' changes"
619
- },
620
- {
621
- "rule": "odoo.stored-compute-needs-complete-depends",
622
- "path": "account-financial-tools/account_loan/models/account_loan_line.py",
623
- "line": 174,
624
- "verdict": "true",
625
- "class": null,
626
- "why": null,
627
- "evidence": "_compute_principal_amount() reads 'payment_amount' but @api.depends does not declare it -- the stored value goes stale when 'payment_amount' changes"
628
- },
629
- {
630
- "rule": "odoo.ondelete-is-a-decision",
631
- "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
632
- "line": 59,
633
- "verdict": "true",
634
- "class": null,
635
- "why": null,
636
- "evidence": "oca_purchase_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and oca_purchase_line_id.order_id on line 84 reads through it, which yields False rather than raising once the parent is deleted"
637
- },
638
- {
639
- "rule": "odoo.stored-compute-needs-complete-depends",
640
- "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
641
- "line": 84,
642
- "verdict": "true",
643
- "class": null,
644
- "why": null,
645
- "evidence": "_compute_purchase_id() reads 'oca_purchase_line_id.order_id' but @api.depends does not declare it -- the stored value goes stale when 'oca_purchase_line_id.order_id' changes"
646
- },
647
- {
648
- "rule": "odoo.stored-compute-needs-complete-depends",
649
- "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
650
- "line": 84,
651
- "verdict": "true",
652
- "class": null,
653
- "why": null,
654
- "evidence": "_compute_purchase_id() reads 'purchase_line_id.order_id' but @api.depends does not declare it -- the stored value goes stale when 'purchase_line_id.order_id' changes"
655
- },
656
- {
657
- "rule": "odoo.stored-compute-needs-complete-depends",
658
- "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
659
- "line": 17,
660
- "verdict": "undecidable",
661
- "class": "storedness-unknown",
662
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
663
- "evidence": "_compute_display_name() reads 'order_id' but @api.depends does not declare it -- the stored value goes stale when 'order_id' changes"
664
- },
665
- {
666
- "rule": "odoo.stored-compute-needs-complete-depends",
667
- "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
668
- "line": 17,
669
- "verdict": "undecidable",
670
- "class": "storedness-unknown",
671
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
672
- "evidence": "_compute_display_name() reads 'order_id.name' but @api.depends does not declare it -- the stored value goes stale when 'order_id.name' changes"
673
- },
674
- {
675
- "rule": "odoo.stored-compute-needs-complete-depends",
676
- "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
677
- "line": 17,
678
- "verdict": "undecidable",
679
- "class": "storedness-unknown",
680
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
681
- "evidence": "_compute_display_name() reads 'product_id' but @api.depends does not declare it -- the stored value goes stale when 'product_id' changes"
682
- },
683
- {
684
- "rule": "odoo.stored-compute-needs-complete-depends",
685
- "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
686
- "line": 17,
687
- "verdict": "undecidable",
688
- "class": "storedness-unknown",
689
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
690
- "evidence": "_compute_display_name() reads 'product_id.name' but @api.depends does not declare it -- the stored value goes stale when 'product_id.name' changes"
691
- },
692
- {
693
- "rule": "odoo.stored-compute-needs-complete-depends",
694
- "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
695
- "line": 18,
696
- "verdict": "undecidable",
697
- "class": "storedness-unknown",
698
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
699
- "evidence": "_compute_display_name() reads 'order_id.state' but @api.depends does not declare it -- the stored value goes stale when 'order_id.state' changes"
700
- },
701
- {
702
- "rule": "odoo.ondelete-is-a-decision",
703
- "path": "account-financial-tools/account_move_line_tax_editable/models/account_move_line.py",
704
- "line": 14,
705
- "verdict": "true",
706
- "class": null,
707
- "why": null,
708
- "evidence": "tax_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and tax_line_id.refund_repartition_line_ids on line 27 reads through it, which yields False rather than raising once the parent is deleted"
709
- },
710
- {
711
- "rule": "odoo.ondelete-is-a-decision",
712
- "path": "account-financial-tools/account_move_template/models/account_move_template.py",
713
- "line": 132,
714
- "verdict": "true",
715
- "class": null,
716
- "why": null,
717
- "evidence": "partner_id declares no ondelete, so the ORM defaults it to 'set null' -- and partner_id.category_id on line 206 reads through it, which yields False rather than raising once the parent is deleted"
718
- },
719
- {
720
- "rule": "odoo.stored-compute-needs-complete-depends",
721
- "path": "account-financial-tools/account_move_template/models/account_move_template.py",
722
- "line": 206,
723
- "verdict": "undecidable",
724
- "class": "storedness-unknown",
725
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
726
- "evidence": "_compute_analytic_distribution() reads 'partner_id.category_id' but @api.depends does not declare it -- the stored value goes stale when 'partner_id.category_id' changes"
727
- },
728
- {
729
- "rule": "odoo.stored-compute-needs-complete-depends",
730
- "path": "account-financial-tools/account_move_template/models/account_move_template.py",
731
- "line": 209,
732
- "verdict": "undecidable",
733
- "class": "storedness-unknown",
734
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
735
- "evidence": "_compute_analytic_distribution() reads 'account_id.code' but @api.depends does not declare it -- the stored value goes stale when 'account_id.code' changes"
736
- },
737
- {
738
- "rule": "odoo.stored-compute-needs-complete-depends",
739
- "path": "account-financial-tools/account_move_template/models/account_move_template.py",
740
- "line": 210,
741
- "verdict": "undecidable",
742
- "class": "storedness-unknown",
743
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
744
- "evidence": "_compute_analytic_distribution() reads 'template_id' but @api.depends does not declare it -- the stored value goes stale when 'template_id' changes"
745
- },
746
- {
747
- "rule": "odoo.stored-compute-needs-complete-depends",
748
- "path": "account-financial-tools/account_move_template/models/account_move_template.py",
749
- "line": 210,
750
- "verdict": "undecidable",
751
- "class": "storedness-unknown",
752
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
753
- "evidence": "_compute_analytic_distribution() reads 'template_id.company_id' but @api.depends does not declare it -- the stored value goes stale when 'template_id.company_id' changes"
754
- },
755
- {
756
- "rule": "odoo.check-company-on-relations",
757
- "path": "account-financial-tools/account_payroll_sheet_import/models/payroll_import_mapping.py",
758
- "line": 12,
759
- "verdict": "true",
760
- "class": null,
761
- "why": null,
762
- "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
763
- },
764
- {
765
- "rule": "odoo.check-company-on-relations",
766
- "path": "account-financial-tools/account_payroll_sheet_import/models/payroll_import_mapping.py",
767
- "line": 36,
768
- "verdict": "true",
769
- "class": null,
770
- "why": null,
771
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
772
- },
773
- {
774
- "rule": "odoo.stored-compute-needs-complete-depends",
775
- "path": "account-financial-tools/account_sequence_option/models/account_move.py",
776
- "line": 53,
777
- "verdict": "undecidable",
778
- "class": "storedness-unknown",
779
- "why": "the compute's field is declared in another module, so storedness cannot be read here",
780
- "evidence": "_compute_name() reads 'create_date' but @api.depends does not declare it -- the stored value goes stale when 'create_date' changes"
781
- },
782
- {
783
- "rule": "odoo.constrains-must-list-every-field-read",
784
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_move_line.py",
785
- "line": 71,
786
- "verdict": "true",
787
- "class": null,
788
- "why": null,
789
- "evidence": "_check_spread_account_balance_sheet() reads 'move_id' but @api.constrains declares only account_id, spread_id -- writing 'move_id' alone skips the check entirely"
790
- },
791
- {
792
- "rule": "odoo.ondelete-is-a-decision",
793
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
794
- "line": 22,
795
- "verdict": "true",
796
- "class": null,
797
- "why": null,
798
- "evidence": "template_id declares no ondelete, so the ORM defaults it to 'set null' -- and template_id.spread_type on line 310 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete account.spread.template"
799
- },
800
- {
801
- "rule": "odoo.check-company-on-relations",
802
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
803
- "line": 55,
804
- "verdict": "true",
805
- "class": null,
806
- "why": null,
807
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
808
- },
809
- {
810
- "rule": "odoo.check-company-on-relations",
811
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
812
- "line": 62,
813
- "verdict": "true",
814
- "class": null,
815
- "why": null,
816
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
817
- },
818
- {
819
- "rule": "odoo.ondelete-is-a-decision",
820
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
821
- "line": 113,
822
- "verdict": "true",
823
- "class": null,
824
- "why": null,
825
- "evidence": "invoice_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and invoice_line_id.name on line 537 reads through it, which yields False rather than raising once the parent is deleted"
826
- },
827
- {
828
- "rule": "odoo.stored-compute-needs-complete-depends",
829
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
830
- "line": 218,
831
- "verdict": "true",
832
- "class": null,
833
- "why": null,
834
- "evidence": "_compute_all_posted() reads 'currency_id' but @api.depends does not declare it -- the stored value goes stale when 'currency_id' changes"
835
- },
836
- {
837
- "rule": "odoo.stored-compute-needs-complete-depends",
838
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
839
- "line": 218,
840
- "verdict": "true",
841
- "class": null,
842
- "why": null,
843
- "evidence": "_compute_all_posted() reads 'currency_id.rounding' but @api.depends does not declare it -- the stored value goes stale when 'currency_id.rounding' changes"
844
- },
845
- {
846
- "rule": "odoo.stored-compute-needs-complete-depends",
847
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
848
- "line": 268,
849
- "verdict": "true",
850
- "class": null,
851
- "why": null,
852
- "evidence": "_compute_journal_id() reads 'company_id.default_spread_expense_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_expense_journal_id' changes"
853
- },
854
- {
855
- "rule": "odoo.stored-compute-needs-complete-depends",
856
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
857
- "line": 270,
858
- "verdict": "true",
859
- "class": null,
860
- "why": null,
861
- "evidence": "_compute_journal_id() reads 'company_id.default_spread_revenue_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_revenue_journal_id' changes"
862
- },
863
- {
864
- "rule": "odoo.stored-compute-needs-complete-depends",
865
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
866
- "line": 280,
867
- "verdict": "true",
868
- "class": null,
869
- "why": null,
870
- "evidence": "_compute_debit_account_id() reads 'company_id.default_spread_revenue_account_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_revenue_account_id' changes"
871
- },
872
- {
873
- "rule": "odoo.stored-compute-needs-complete-depends",
874
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
875
- "line": 288,
876
- "verdict": "true",
877
- "class": null,
878
- "why": null,
879
- "evidence": "_compute_credit_account_id() reads 'company_id.default_spread_expense_account_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_expense_account_id' changes"
880
- },
881
- {
882
- "rule": "odoo.check-company-on-relations",
883
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
884
- "line": 20,
885
- "verdict": "true",
886
- "class": null,
887
- "why": null,
888
- "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
889
- },
890
- {
891
- "rule": "odoo.check-company-on-relations",
892
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
893
- "line": 33,
894
- "verdict": "true",
895
- "class": null,
896
- "why": null,
897
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
898
- },
899
- {
900
- "rule": "odoo.check-company-on-relations",
901
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
902
- "line": 41,
903
- "verdict": "true",
904
- "class": null,
905
- "why": null,
906
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
907
- },
908
- {
909
- "rule": "odoo.check-company-on-relations",
910
- "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
911
- "line": 176,
912
- "verdict": "true",
913
- "class": null,
914
- "why": null,
915
- "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
916
- },
917
- {
918
- "rule": "odoo.sudo-bypasses-record-rules",
919
- "path": "account-financial-tools/purchase_unreconciled/wizards/purchase_unreconciled_exceeded.py",
920
- "line": 14,
921
- "verdict": "false",
922
- "class": "registry-model",
923
- "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
924
- "evidence": "(m.model, m.name) for m in self.env[\"ir.model\"].sudo().search([])"
925
- }
926
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
  }
 
1
  {
2
+ "generated_utc": "2026-09-16T07:04:18+00:00",
3
+ "stack": "odoo",
4
+ "roots": [
5
+ "server-tools",
6
+ "account-financial-tools"
7
+ ],
8
+ "files": 606,
9
+ "lines": 33960,
10
+ "checkouts": {
11
+ "server-tools": "60cd54e5e",
12
+ "account-financial-tools": "e4c1b86aa"
13
+ },
14
+ "totals": {
15
+ "false": 10,
16
+ "true": 79,
17
+ "undecidable": 13
18
+ },
19
+ "classes": {
20
+ "storedness-unknown": 13,
21
+ "registry-model": 5,
22
+ "wrong-construct": 2,
23
+ "advice-cannot-be-taken": 1,
24
+ "no-record-rule-on-this-model": 1,
25
+ "scoped-by-hand": 1
26
+ },
27
+ "hits": [
28
+ {
29
+ "rule": "odoo.sudo-bypasses-record-rules",
30
+ "path": "server-tools/attachment_delete_restrict/models/ir_attachment.py",
31
+ "line": 71,
32
+ "verdict": "false",
33
+ "class": "registry-model",
34
+ "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
35
+ "evidence": "models = self.env[\"ir.model\"].sudo().search([(\"model\", \"in\", res_models)])"
36
+ },
37
+ {
38
+ "rule": "odoo.ondelete-is-a-decision",
39
+ "path": "server-tools/attachment_synchronize/models/attachment_queue.py",
40
+ "line": 14,
41
+ "verdict": "true",
42
+ "class": null,
43
+ "why": null,
44
+ "evidence": "task_id declares no ondelete, so the ORM defaults it to 'set null' -- and task_id.filepath on line 37 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete attachment.synchronize.task"
45
+ },
46
+ {
47
+ "rule": "odoo.ondelete-is-a-decision",
48
+ "path": "server-tools/attachment_synchronize/models/attachment_synchronize_task.py",
49
+ "line": 64,
50
+ "verdict": "true",
51
+ "class": null,
52
+ "why": null,
53
+ "evidence": "backend_id declares no ondelete, so the ORM defaults it to 'set null' -- and backend_id.fs on line 166 reads through it, which yields False rather than raising once the parent is deleted"
54
+ },
55
+ {
56
+ "rule": "odoo.stored-compute-needs-complete-depends",
57
+ "path": "server-tools/auditlog/models/http_session.py",
58
+ "line": 29,
59
+ "verdict": "true",
60
+ "class": null,
61
+ "why": null,
62
+ "evidence": "_compute_display_name() reads 'user_id.name' but @api.depends does not declare it -- the stored value goes stale when 'user_id.name' changes"
63
+ },
64
+ {
65
+ "rule": "odoo.constrains-must-list-every-field-read",
66
+ "path": "server-tools/autovacuum_message_attachment/models/vacuum_rule.py",
67
+ "line": 100,
68
+ "verdict": "true",
69
+ "class": null,
70
+ "why": null,
71
+ "evidence": "_check_inheriting_model() reads 'ttype' but @api.constrains declares only inheriting_model -- writing 'ttype' alone skips the check entirely"
72
+ },
73
+ {
74
+ "rule": "odoo.constrains-must-list-every-field-read",
75
+ "path": "server-tools/base_exception/models/exception_rule.py",
76
+ "line": 59,
77
+ "verdict": "true",
78
+ "class": null,
79
+ "why": null,
80
+ "evidence": "check_exception_type_consistency() reads 'method' but @api.constrains declares only code, domain, exception_type, model -- writing 'method' alone skips the check entirely"
81
+ },
82
+ {
83
+ "rule": "odoo.depends-must-not-name-unread-fields",
84
+ "path": "server-tools/base_multi_image/models/image.py",
85
+ "line": 103,
86
+ "verdict": "true",
87
+ "class": null,
88
+ "why": null,
89
+ "evidence": "_compute_show_technical() declares 'owner_id', 'owner_model' in @api.depends and never reads them -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
90
+ },
91
+ {
92
+ "rule": "odoo.sql-needs-query-parameters",
93
+ "path": "server-tools/database_autovacuum_tuning/models/database_autovacuum_tuning.py",
94
+ "line": 24,
95
+ "verdict": "false",
96
+ "class": "advice-cannot-be-taken",
97
+ "why": "a table or column name cannot be a query parameter; both sites already parameterise the values",
98
+ "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them"
99
+ },
100
+ {
101
+ "rule": "odoo.sudo-bypasses-record-rules",
102
+ "path": "server-tools/database_size/models/ir_model_size.py",
103
+ "line": 87,
104
+ "verdict": "false",
105
+ "class": "registry-model",
106
+ "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
107
+ "evidence": "model.model: model.name for model in self.env[\"ir.model\"].sudo().search([])"
108
+ },
109
+ {
110
+ "rule": "odoo.ondelete-is-a-decision",
111
+ "path": "server-tools/excel_import_export/models/xlsx_template.py",
112
+ "line": 108,
113
+ "verdict": "true",
114
+ "class": null,
115
+ "why": null,
116
+ "evidence": "result_model_id declares no ondelete, so the ORM defaults it to 'set null' -- and result_model_id.model on line 199 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete ir.model"
117
+ },
118
+ {
119
+ "rule": "odoo.ondelete-is-a-decision",
120
+ "path": "server-tools/fetchmail_attach_from_folder/models/fetchmail_server_folder.py",
121
+ "line": 25,
122
+ "verdict": "true",
123
+ "class": null,
124
+ "why": null,
125
+ "evidence": "server_id declares no ondelete, so the ORM defaults it to 'set null' -- and server_id.connect on line 139 reads through it, which yields False rather than raising once the parent is deleted"
126
+ },
127
+ {
128
+ "rule": "odoo.sudo-bypasses-record-rules",
129
+ "path": "server-tools/tracking_manager/models/ir_model.py",
130
+ "line": 34,
131
+ "verdict": "false",
132
+ "class": "registry-model",
133
+ "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
134
+ "evidence": "models = self.sudo().search([(\"active_custom_tracking\", \"=\", True)])"
135
+ },
136
+ {
137
+ "rule": "odoo.depends-must-not-name-unread-fields",
138
+ "path": "server-tools/tracking_manager/models/ir_model.py",
139
+ "line": 101,
140
+ "verdict": "true",
141
+ "class": null,
142
+ "why": null,
143
+ "evidence": "_compute_automatic_custom_tracking() declares 'active_custom_tracking' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
144
+ },
145
+ {
146
+ "rule": "odoo.stored-compute-needs-complete-depends",
147
+ "path": "server-tools/tracking_manager/models/ir_model.py",
148
+ "line": 135,
149
+ "verdict": "true",
150
+ "class": null,
151
+ "why": null,
152
+ "evidence": "_compute_automatic_custom_tracking_domain() reads 'model' but @api.depends does not declare it -- the stored value goes stale when 'model' changes"
153
+ },
154
+ {
155
+ "rule": "odoo.stored-compute-needs-complete-depends",
156
+ "path": "server-tools/tracking_manager/models/ir_model_fields.py",
157
+ "line": 30,
158
+ "verdict": "true",
159
+ "class": null,
160
+ "why": null,
161
+ "evidence": "_compute_custom_tracking() reads 'model_id' but @api.depends does not declare it -- the stored value goes stale when 'model_id' changes"
162
+ },
163
+ {
164
+ "rule": "odoo.stored-compute-needs-complete-depends",
165
+ "path": "server-tools/tracking_manager/models/ir_model_fields.py",
166
+ "line": 30,
167
+ "verdict": "true",
168
+ "class": null,
169
+ "why": null,
170
+ "evidence": "_compute_custom_tracking() reads 'model_id.automatic_custom_tracking' but @api.depends does not declare it -- the stored value goes stale when 'model_id.automatic_custom_tracking' changes"
171
+ },
172
+ {
173
+ "rule": "odoo.stored-compute-needs-complete-depends",
174
+ "path": "server-tools/tracking_manager/models/ir_model_fields.py",
175
+ "line": 31,
176
+ "verdict": "true",
177
+ "class": null,
178
+ "why": null,
179
+ "evidence": "_compute_custom_tracking() reads 'model_id.automatic_custom_tracking_domain' but @api.depends does not declare it -- the stored value goes stale when 'model_id.automatic_custom_tracking_domain' changes"
180
+ },
181
+ {
182
+ "rule": "odoo.stored-compute-needs-complete-depends",
183
+ "path": "server-tools/tracking_manager/models/ir_model_fields.py",
184
+ "line": 52,
185
+ "verdict": "true",
186
+ "class": null,
187
+ "why": null,
188
+ "evidence": "_compute_trackable() reads 'name' but @api.depends does not declare it -- the stored value goes stale when 'name' changes"
189
+ },
190
+ {
191
+ "rule": "odoo.stored-compute-needs-complete-depends",
192
+ "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
193
+ "line": 60,
194
+ "verdict": "true",
195
+ "class": null,
196
+ "why": null,
197
+ "evidence": "_compute_account_asset_id() reads 'state' but @api.depends does not declare it -- the stored value goes stale when 'state' changes"
198
+ },
199
+ {
200
+ "rule": "odoo.stored-compute-needs-complete-depends",
201
+ "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
202
+ "line": 64,
203
+ "verdict": "true",
204
+ "class": null,
205
+ "why": null,
206
+ "evidence": "_compute_account_asset_id() reads 'account_move_line_ids.account_id' but @api.depends does not declare it -- the stored value goes stale when 'account_move_line_ids.account_id' changes"
207
+ },
208
+ {
209
+ "rule": "odoo.stored-compute-needs-complete-depends",
210
+ "path": "account-financial-tools/account_asset_force_account/models/account_asset.py",
211
+ "line": 72,
212
+ "verdict": "true",
213
+ "class": null,
214
+ "why": null,
215
+ "evidence": "_compute_account_asset_id() reads 'profile_id.account_asset_id' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.account_asset_id' changes"
216
+ },
217
+ {
218
+ "rule": "odoo.stored-compute-needs-complete-depends",
219
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
220
+ "line": 311,
221
+ "verdict": "true",
222
+ "class": null,
223
+ "why": null,
224
+ "evidence": "_compute_group_ids() reads 'profile_id.group_ids' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.group_ids' changes"
225
+ },
226
+ {
227
+ "rule": "odoo.stored-compute-needs-complete-depends",
228
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
229
+ "line": 316,
230
+ "verdict": "true",
231
+ "class": null,
232
+ "why": null,
233
+ "evidence": "_compute_method() reads 'profile_id.method' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method' changes"
234
+ },
235
+ {
236
+ "rule": "odoo.stored-compute-needs-complete-depends",
237
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
238
+ "line": 324,
239
+ "verdict": "true",
240
+ "class": null,
241
+ "why": null,
242
+ "evidence": "_compute_method_number() reads 'profile_id.method_number' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_number' changes"
243
+ },
244
+ {
245
+ "rule": "odoo.stored-compute-needs-complete-depends",
246
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
247
+ "line": 329,
248
+ "verdict": "true",
249
+ "class": null,
250
+ "why": null,
251
+ "evidence": "_compute_method_period() reads 'profile_id.method_period' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_period' changes"
252
+ },
253
+ {
254
+ "rule": "odoo.stored-compute-needs-complete-depends",
255
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
256
+ "line": 340,
257
+ "verdict": "true",
258
+ "class": null,
259
+ "why": null,
260
+ "evidence": "_compute_method_progress_factor() reads 'profile_id.method_progress_factor' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_progress_factor' changes"
261
+ },
262
+ {
263
+ "rule": "odoo.stored-compute-needs-complete-depends",
264
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
265
+ "line": 345,
266
+ "verdict": "true",
267
+ "class": null,
268
+ "why": null,
269
+ "evidence": "_compute_method_time() reads 'profile_id.method_time' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.method_time' changes"
270
+ },
271
+ {
272
+ "rule": "odoo.stored-compute-needs-complete-depends",
273
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
274
+ "line": 350,
275
+ "verdict": "true",
276
+ "class": null,
277
+ "why": null,
278
+ "evidence": "_compute_days_calc() reads 'profile_id.days_calc' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.days_calc' changes"
279
+ },
280
+ {
281
+ "rule": "odoo.stored-compute-needs-complete-depends",
282
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
283
+ "line": 355,
284
+ "verdict": "true",
285
+ "class": null,
286
+ "why": null,
287
+ "evidence": "_compute_use_leap_years() reads 'profile_id.use_leap_years' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.use_leap_years' changes"
288
+ },
289
+ {
290
+ "rule": "odoo.stored-compute-needs-complete-depends",
291
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
292
+ "line": 363,
293
+ "verdict": "true",
294
+ "class": null,
295
+ "why": null,
296
+ "evidence": "_compute_prorrata() reads 'profile_id.prorata' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.prorata' changes"
297
+ },
298
+ {
299
+ "rule": "odoo.stored-compute-needs-complete-depends",
300
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
301
+ "line": 368,
302
+ "verdict": "undecidable",
303
+ "class": "storedness-unknown",
304
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
305
+ "evidence": "_compute_account_analytic_id() reads 'profile_id.account_analytic_id' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.account_analytic_id' changes"
306
+ },
307
+ {
308
+ "rule": "odoo.stored-compute-needs-complete-depends",
309
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
310
+ "line": 373,
311
+ "verdict": "undecidable",
312
+ "class": "storedness-unknown",
313
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
314
+ "evidence": "_compute_analytic_distribution() reads 'profile_id.analytic_distribution' but @api.depends does not declare it -- the stored value goes stale when 'profile_id.analytic_distribution' changes"
315
+ },
316
+ {
317
+ "rule": "odoo.constrains-must-list-every-field-read",
318
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
319
+ "line": 398,
320
+ "verdict": "true",
321
+ "class": null,
322
+ "why": null,
323
+ "evidence": "_check_profile_change() reads 'depreciation_line_ids' but @api.constrains declares only profile_id -- writing 'depreciation_line_ids' alone skips the check entirely"
324
+ },
325
+ {
326
+ "rule": "odoo.recursion-check-on-hierarchy",
327
+ "path": "account-financial-tools/account_asset_management/models/account_asset_group.py",
328
+ "line": 27,
329
+ "verdict": "true",
330
+ "class": null,
331
+ "why": null,
332
+ "evidence": "parent_id = fields.Many2one("
333
+ },
334
+ {
335
+ "rule": "odoo.ondelete-is-a-decision",
336
+ "path": "account-financial-tools/account_asset_management/models/account_move.py",
337
+ "line": 174,
338
+ "verdict": "true",
339
+ "class": null,
340
+ "why": null,
341
+ "evidence": "asset_profile_id declares no ondelete, so the ORM defaults it to 'set null' -- and asset_profile_id.account_asset_id on line 200 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete account.asset.profile"
342
+ },
343
+ {
344
+ "rule": "odoo.stored-compute-needs-complete-depends",
345
+ "path": "account-financial-tools/account_asset_management/models/account_move.py",
346
+ "line": 192,
347
+ "verdict": "true",
348
+ "class": null,
349
+ "why": null,
350
+ "evidence": "_compute_asset_profile() reads 'account_id.asset_profile_id' but @api.depends does not declare it -- the stored value goes stale when 'account_id.asset_profile_id' changes"
351
+ },
352
+ {
353
+ "rule": "odoo.stored-compute-needs-complete-depends",
354
+ "path": "account-financial-tools/account_asset_management/models/account_move.py",
355
+ "line": 195,
356
+ "verdict": "true",
357
+ "class": null,
358
+ "why": null,
359
+ "evidence": "_compute_asset_profile() reads 'asset_id.profile_id' but @api.depends does not declare it -- the stored value goes stale when 'asset_id.profile_id' changes"
360
+ },
361
+ {
362
+ "rule": "odoo.constrains-iterate-recordset",
363
+ "path": "account-financial-tools/account_asset_management/wizard/account_asset_remove.py",
364
+ "line": 80,
365
+ "verdict": "true",
366
+ "class": null,
367
+ "why": null,
368
+ "evidence": "@api.constrains(\"sale_value\", \"company_id\")"
369
+ },
370
+ {
371
+ "rule": "odoo.constrains-must-list-every-field-read",
372
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
373
+ "line": 130,
374
+ "verdict": "true",
375
+ "class": null,
376
+ "why": null,
377
+ "evidence": "_check_deposit() reads 'company_id' but @api.constrains declares only cash_journal_id, currency_id -- writing 'company_id' alone skips the check entirely"
378
+ },
379
+ {
380
+ "rule": "odoo.stored-compute-needs-complete-depends",
381
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
382
+ "line": 203,
383
+ "verdict": "true",
384
+ "class": null,
385
+ "why": null,
386
+ "evidence": "_compute_is_reconcile() reads 'cash_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'cash_journal_id' changes"
387
+ },
388
+ {
389
+ "rule": "odoo.stored-compute-needs-complete-depends",
390
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
391
+ "line": 203,
392
+ "verdict": "true",
393
+ "class": null,
394
+ "why": null,
395
+ "evidence": "_compute_is_reconcile() reads 'cash_journal_id.default_account_id' but @api.depends does not declare it -- the stored value goes stale when 'cash_journal_id.default_account_id' changes"
396
+ },
397
+ {
398
+ "rule": "odoo.stored-compute-needs-complete-depends",
399
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
400
+ "line": 203,
401
+ "verdict": "true",
402
+ "class": null,
403
+ "why": null,
404
+ "evidence": "_compute_is_reconcile() reads 'move_id.line_ids.account_id' but @api.depends does not declare it -- the stored value goes stale when 'move_id.line_ids.account_id' changes"
405
+ },
406
+ {
407
+ "rule": "odoo.float-comparison-needs-float-utils",
408
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
409
+ "line": 271,
410
+ "verdict": "false",
411
+ "class": "wrong-construct",
412
+ "why": "parent_id pointing at another model / == on an Integer / a commit on a separate cursor",
413
+ "evidence": "self.line_ids.filtered(lambda x: x.qty == 0).unlink()"
414
+ },
415
+ {
416
+ "rule": "odoo.recursion-check-on-hierarchy",
417
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
418
+ "line": 381,
419
+ "verdict": "false",
420
+ "class": "wrong-construct",
421
+ "why": "parent_id pointing at another model / == on an Integer / a commit on a separate cursor",
422
+ "evidence": "parent_id = fields.Many2one(\"account.cash.deposit\", ondelete=\"cascade\")"
423
+ },
424
+ {
425
+ "rule": "odoo.stored-compute-needs-complete-depends",
426
+ "path": "account-financial-tools/account_cash_deposit/models/account_cash_deposit.py",
427
+ "line": 420,
428
+ "verdict": "true",
429
+ "class": null,
430
+ "why": null,
431
+ "evidence": "_compute_subtotal() reads 'cash_unit_id.total_value' but @api.depends does not declare it -- the stored value goes stale when 'cash_unit_id.total_value' changes"
432
+ },
433
+ {
434
+ "rule": "odoo.stored-compute-needs-complete-depends",
435
+ "path": "account-financial-tools/account_cash_deposit/models/cash_unit.py",
436
+ "line": 114,
437
+ "verdict": "undecidable",
438
+ "class": "storedness-unknown",
439
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
440
+ "evidence": "_compute_display_name() reads 'total_value' but @api.depends does not declare it -- the stored value goes stale when 'total_value' changes"
441
+ },
442
+ {
443
+ "rule": "odoo.check-company-on-relations",
444
+ "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
445
+ "line": 41,
446
+ "verdict": "true",
447
+ "class": null,
448
+ "why": null,
449
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
450
+ },
451
+ {
452
+ "rule": "odoo.stored-compute-needs-complete-depends",
453
+ "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
454
+ "line": 132,
455
+ "verdict": "true",
456
+ "class": null,
457
+ "why": null,
458
+ "evidence": "_compute_check_deposit() reads 'company_id.currency_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.currency_id' changes"
459
+ },
460
+ {
461
+ "rule": "odoo.stored-compute-needs-complete-depends",
462
+ "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
463
+ "line": 145,
464
+ "verdict": "true",
465
+ "class": null,
466
+ "why": null,
467
+ "evidence": "_compute_currency_id() reads 'company_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id' changes"
468
+ },
469
+ {
470
+ "rule": "odoo.stored-compute-needs-complete-depends",
471
+ "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
472
+ "line": 146,
473
+ "verdict": "true",
474
+ "class": null,
475
+ "why": null,
476
+ "evidence": "_compute_currency_id() reads 'journal_id.currency_id' but @api.depends does not declare it -- the stored value goes stale when 'journal_id.currency_id' changes"
477
+ },
478
+ {
479
+ "rule": "odoo.stored-compute-needs-complete-depends",
480
+ "path": "account-financial-tools/account_check_deposit/models/account_check_deposit.py",
481
+ "line": 151,
482
+ "verdict": "true",
483
+ "class": null,
484
+ "why": null,
485
+ "evidence": "_compute_in_hand_check_account_id() reads 'journal_id.inbound_payment_method_line_ids' but @api.depends does not declare it -- the stored value goes stale when 'journal_id.inbound_payment_method_line_ids' changes"
486
+ },
487
+ {
488
+ "rule": "odoo.ondelete-is-a-decision",
489
+ "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
490
+ "line": 17,
491
+ "verdict": "true",
492
+ "class": null,
493
+ "why": null,
494
+ "evidence": "mis_report_instance_id declares no ondelete, so the ORM defaults it to 'set null' -- and mis_report_instance_id.currency_id on line 105 reads through it, which yields False rather than raising once the parent is deleted"
495
+ },
496
+ {
497
+ "rule": "odoo.ondelete-is-a-decision",
498
+ "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
499
+ "line": 35,
500
+ "verdict": "true",
501
+ "class": null,
502
+ "why": null,
503
+ "evidence": "mis_report_instance_period_id declares no ondelete, so the ORM defaults it to 'set null' -- and mis_report_instance_period_id.date_from on line 103 reads through it, which yields False rather than raising once the parent is deleted"
504
+ },
505
+ {
506
+ "rule": "odoo.constrains-must-list-every-field-read",
507
+ "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
508
+ "line": 47,
509
+ "verdict": "true",
510
+ "class": null,
511
+ "why": null,
512
+ "evidence": "_check_mis_builder() reads 'cell_type' but @api.constrains declares only mis_report_instance_id, mis_report_kpi_id -- writing 'cell_type' alone skips the check entirely"
513
+ },
514
+ {
515
+ "rule": "odoo.constrains-must-list-every-field-read",
516
+ "path": "account-financial-tools/account_dashboard_banner_mis_builder/models/account_dashboard_banner_cell.py",
517
+ "line": 74,
518
+ "verdict": "true",
519
+ "class": null,
520
+ "why": null,
521
+ "evidence": "_check_mis_builder() reads 'mis_report_instance_period_id' but @api.constrains declares only mis_report_instance_id, mis_report_kpi_id -- writing 'mis_report_instance_period_id' alone skips the check entirely"
522
+ },
523
+ {
524
+ "rule": "odoo.stored-compute-needs-complete-depends",
525
+ "path": "account-financial-tools/account_journal_general_sequence/models/account_journal.py",
526
+ "line": 42,
527
+ "verdict": "true",
528
+ "class": null,
529
+ "why": null,
530
+ "evidence": "_compute_entry_number_sequence() reads 'company_id.name' but @api.depends does not declare it -- the stored value goes stale when 'company_id.name' changes"
531
+ },
532
+ {
533
+ "rule": "odoo.check-company-on-relations",
534
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
535
+ "line": 144,
536
+ "verdict": "true",
537
+ "class": null,
538
+ "why": null,
539
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
540
+ },
541
+ {
542
+ "rule": "odoo.check-company-on-relations",
543
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
544
+ "line": 149,
545
+ "verdict": "true",
546
+ "class": null,
547
+ "why": null,
548
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
549
+ },
550
+ {
551
+ "rule": "odoo.check-company-on-relations",
552
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
553
+ "line": 156,
554
+ "verdict": "true",
555
+ "class": null,
556
+ "why": null,
557
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
558
+ },
559
+ {
560
+ "rule": "odoo.check-company-on-relations",
561
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
562
+ "line": 162,
563
+ "verdict": "true",
564
+ "class": null,
565
+ "why": null,
566
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
567
+ },
568
+ {
569
+ "rule": "odoo.check-company-on-relations",
570
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
571
+ "line": 170,
572
+ "verdict": "true",
573
+ "class": null,
574
+ "why": null,
575
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
576
+ },
577
+ {
578
+ "rule": "odoo.depends-must-not-name-unread-fields",
579
+ "path": "account-financial-tools/account_loan/models/account_loan.py",
580
+ "line": 246,
581
+ "verdict": "true",
582
+ "class": null,
583
+ "why": null,
584
+ "evidence": "_compute_total_amounts() declares 'currency_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorator stops describing the method"
585
+ },
586
+ {
587
+ "rule": "odoo.stored-compute-needs-complete-depends",
588
+ "path": "account-financial-tools/account_loan/models/account_loan_line.py",
589
+ "line": 132,
590
+ "verdict": "true",
591
+ "class": null,
592
+ "why": null,
593
+ "evidence": "_compute_rate() reads 'pending_principal_amount' but @api.depends does not declare it -- the stored value goes stale when 'pending_principal_amount' changes"
594
+ },
595
+ {
596
+ "rule": "odoo.stored-compute-needs-complete-depends",
597
+ "path": "account-financial-tools/account_loan/models/account_loan_line.py",
598
+ "line": 139,
599
+ "verdict": "true",
600
+ "class": null,
601
+ "why": null,
602
+ "evidence": "_compute_interests_amount() reads 'pending_principal_amount' but @api.depends does not declare it -- the stored value goes stale when 'pending_principal_amount' changes"
603
+ },
604
+ {
605
+ "rule": "odoo.stored-compute-needs-complete-depends",
606
+ "path": "account-financial-tools/account_loan/models/account_loan_line.py",
607
+ "line": 174,
608
+ "verdict": "true",
609
+ "class": null,
610
+ "why": null,
611
+ "evidence": "_compute_principal_amount() reads 'interests_amount' but @api.depends does not declare it -- the stored value goes stale when 'interests_amount' changes"
612
+ },
613
+ {
614
+ "rule": "odoo.stored-compute-needs-complete-depends",
615
+ "path": "account-financial-tools/account_loan/models/account_loan_line.py",
616
+ "line": 174,
617
+ "verdict": "true",
618
+ "class": null,
619
+ "why": null,
620
+ "evidence": "_compute_principal_amount() reads 'payment_amount' but @api.depends does not declare it -- the stored value goes stale when 'payment_amount' changes"
621
+ },
622
+ {
623
+ "rule": "odoo.ondelete-is-a-decision",
624
+ "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
625
+ "line": 59,
626
+ "verdict": "true",
627
+ "class": null,
628
+ "why": null,
629
+ "evidence": "oca_purchase_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and oca_purchase_line_id.order_id on line 84 reads through it, which yields False rather than raising once the parent is deleted"
630
+ },
631
+ {
632
+ "rule": "odoo.stored-compute-needs-complete-depends",
633
+ "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
634
+ "line": 84,
635
+ "verdict": "true",
636
+ "class": null,
637
+ "why": null,
638
+ "evidence": "_compute_purchase_id() reads 'oca_purchase_line_id.order_id' but @api.depends does not declare it -- the stored value goes stale when 'oca_purchase_line_id.order_id' changes"
639
+ },
640
+ {
641
+ "rule": "odoo.stored-compute-needs-complete-depends",
642
+ "path": "account-financial-tools/account_move_line_purchase_info/models/account_move.py",
643
+ "line": 84,
644
+ "verdict": "true",
645
+ "class": null,
646
+ "why": null,
647
+ "evidence": "_compute_purchase_id() reads 'purchase_line_id.order_id' but @api.depends does not declare it -- the stored value goes stale when 'purchase_line_id.order_id' changes"
648
+ },
649
+ {
650
+ "rule": "odoo.stored-compute-needs-complete-depends",
651
+ "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
652
+ "line": 17,
653
+ "verdict": "undecidable",
654
+ "class": "storedness-unknown",
655
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
656
+ "evidence": "_compute_display_name() reads 'order_id' but @api.depends does not declare it -- the stored value goes stale when 'order_id' changes"
657
+ },
658
+ {
659
+ "rule": "odoo.stored-compute-needs-complete-depends",
660
+ "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
661
+ "line": 17,
662
+ "verdict": "undecidable",
663
+ "class": "storedness-unknown",
664
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
665
+ "evidence": "_compute_display_name() reads 'order_id.name' but @api.depends does not declare it -- the stored value goes stale when 'order_id.name' changes"
666
+ },
667
+ {
668
+ "rule": "odoo.stored-compute-needs-complete-depends",
669
+ "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
670
+ "line": 17,
671
+ "verdict": "undecidable",
672
+ "class": "storedness-unknown",
673
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
674
+ "evidence": "_compute_display_name() reads 'product_id' but @api.depends does not declare it -- the stored value goes stale when 'product_id' changes"
675
+ },
676
+ {
677
+ "rule": "odoo.stored-compute-needs-complete-depends",
678
+ "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
679
+ "line": 17,
680
+ "verdict": "undecidable",
681
+ "class": "storedness-unknown",
682
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
683
+ "evidence": "_compute_display_name() reads 'product_id.name' but @api.depends does not declare it -- the stored value goes stale when 'product_id.name' changes"
684
+ },
685
+ {
686
+ "rule": "odoo.stored-compute-needs-complete-depends",
687
+ "path": "account-financial-tools/account_move_line_sale_info/models/sale_order_line.py",
688
+ "line": 18,
689
+ "verdict": "undecidable",
690
+ "class": "storedness-unknown",
691
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
692
+ "evidence": "_compute_display_name() reads 'order_id.state' but @api.depends does not declare it -- the stored value goes stale when 'order_id.state' changes"
693
+ },
694
+ {
695
+ "rule": "odoo.ondelete-is-a-decision",
696
+ "path": "account-financial-tools/account_move_line_tax_editable/models/account_move_line.py",
697
+ "line": 14,
698
+ "verdict": "true",
699
+ "class": null,
700
+ "why": null,
701
+ "evidence": "tax_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and tax_line_id.refund_repartition_line_ids on line 27 reads through it, which yields False rather than raising once the parent is deleted"
702
+ },
703
+ {
704
+ "rule": "odoo.ondelete-is-a-decision",
705
+ "path": "account-financial-tools/account_move_template/models/account_move_template.py",
706
+ "line": 132,
707
+ "verdict": "true",
708
+ "class": null,
709
+ "why": null,
710
+ "evidence": "partner_id declares no ondelete, so the ORM defaults it to 'set null' -- and partner_id.category_id on line 206 reads through it, which yields False rather than raising once the parent is deleted"
711
+ },
712
+ {
713
+ "rule": "odoo.stored-compute-needs-complete-depends",
714
+ "path": "account-financial-tools/account_move_template/models/account_move_template.py",
715
+ "line": 206,
716
+ "verdict": "undecidable",
717
+ "class": "storedness-unknown",
718
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
719
+ "evidence": "_compute_analytic_distribution() reads 'partner_id.category_id' but @api.depends does not declare it -- the stored value goes stale when 'partner_id.category_id' changes"
720
+ },
721
+ {
722
+ "rule": "odoo.stored-compute-needs-complete-depends",
723
+ "path": "account-financial-tools/account_move_template/models/account_move_template.py",
724
+ "line": 209,
725
+ "verdict": "undecidable",
726
+ "class": "storedness-unknown",
727
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
728
+ "evidence": "_compute_analytic_distribution() reads 'account_id.code' but @api.depends does not declare it -- the stored value goes stale when 'account_id.code' changes"
729
+ },
730
+ {
731
+ "rule": "odoo.stored-compute-needs-complete-depends",
732
+ "path": "account-financial-tools/account_move_template/models/account_move_template.py",
733
+ "line": 210,
734
+ "verdict": "undecidable",
735
+ "class": "storedness-unknown",
736
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
737
+ "evidence": "_compute_analytic_distribution() reads 'template_id' but @api.depends does not declare it -- the stored value goes stale when 'template_id' changes"
738
+ },
739
+ {
740
+ "rule": "odoo.stored-compute-needs-complete-depends",
741
+ "path": "account-financial-tools/account_move_template/models/account_move_template.py",
742
+ "line": 210,
743
+ "verdict": "undecidable",
744
+ "class": "storedness-unknown",
745
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
746
+ "evidence": "_compute_analytic_distribution() reads 'template_id.company_id' but @api.depends does not declare it -- the stored value goes stale when 'template_id.company_id' changes"
747
+ },
748
+ {
749
+ "rule": "odoo.check-company-on-relations",
750
+ "path": "account-financial-tools/account_payroll_sheet_import/models/payroll_import_mapping.py",
751
+ "line": 12,
752
+ "verdict": "true",
753
+ "class": null,
754
+ "why": null,
755
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
756
+ },
757
+ {
758
+ "rule": "odoo.check-company-on-relations",
759
+ "path": "account-financial-tools/account_payroll_sheet_import/models/payroll_import_mapping.py",
760
+ "line": 36,
761
+ "verdict": "true",
762
+ "class": null,
763
+ "why": null,
764
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
765
+ },
766
+ {
767
+ "rule": "odoo.stored-compute-needs-complete-depends",
768
+ "path": "account-financial-tools/account_sequence_option/models/account_move.py",
769
+ "line": 53,
770
+ "verdict": "undecidable",
771
+ "class": "storedness-unknown",
772
+ "why": "the compute's field is declared in another module, so storedness cannot be read here",
773
+ "evidence": "_compute_name() reads 'create_date' but @api.depends does not declare it -- the stored value goes stale when 'create_date' changes"
774
+ },
775
+ {
776
+ "rule": "odoo.constrains-must-list-every-field-read",
777
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_move_line.py",
778
+ "line": 71,
779
+ "verdict": "true",
780
+ "class": null,
781
+ "why": null,
782
+ "evidence": "_check_spread_account_balance_sheet() reads 'move_id' but @api.constrains declares only account_id, spread_id -- writing 'move_id' alone skips the check entirely"
783
+ },
784
+ {
785
+ "rule": "odoo.ondelete-is-a-decision",
786
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
787
+ "line": 22,
788
+ "verdict": "true",
789
+ "class": null,
790
+ "why": null,
791
+ "evidence": "template_id declares no ondelete, so the ORM defaults it to 'set null' -- and template_id.spread_type on line 310 reads through it, which yields False rather than raising once the parent is deleted, and this tree does delete account.spread.template"
792
+ },
793
+ {
794
+ "rule": "odoo.check-company-on-relations",
795
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
796
+ "line": 55,
797
+ "verdict": "true",
798
+ "class": null,
799
+ "why": null,
800
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
801
+ },
802
+ {
803
+ "rule": "odoo.check-company-on-relations",
804
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
805
+ "line": 62,
806
+ "verdict": "true",
807
+ "class": null,
808
+ "why": null,
809
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
810
+ },
811
+ {
812
+ "rule": "odoo.ondelete-is-a-decision",
813
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
814
+ "line": 113,
815
+ "verdict": "true",
816
+ "class": null,
817
+ "why": null,
818
+ "evidence": "invoice_line_id declares no ondelete, so the ORM defaults it to 'set null' -- and invoice_line_id.name on line 537 reads through it, which yields False rather than raising once the parent is deleted"
819
+ },
820
+ {
821
+ "rule": "odoo.stored-compute-needs-complete-depends",
822
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
823
+ "line": 218,
824
+ "verdict": "true",
825
+ "class": null,
826
+ "why": null,
827
+ "evidence": "_compute_all_posted() reads 'currency_id' but @api.depends does not declare it -- the stored value goes stale when 'currency_id' changes"
828
+ },
829
+ {
830
+ "rule": "odoo.stored-compute-needs-complete-depends",
831
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
832
+ "line": 218,
833
+ "verdict": "true",
834
+ "class": null,
835
+ "why": null,
836
+ "evidence": "_compute_all_posted() reads 'currency_id.rounding' but @api.depends does not declare it -- the stored value goes stale when 'currency_id.rounding' changes"
837
+ },
838
+ {
839
+ "rule": "odoo.stored-compute-needs-complete-depends",
840
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
841
+ "line": 268,
842
+ "verdict": "true",
843
+ "class": null,
844
+ "why": null,
845
+ "evidence": "_compute_journal_id() reads 'company_id.default_spread_expense_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_expense_journal_id' changes"
846
+ },
847
+ {
848
+ "rule": "odoo.stored-compute-needs-complete-depends",
849
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
850
+ "line": 270,
851
+ "verdict": "true",
852
+ "class": null,
853
+ "why": null,
854
+ "evidence": "_compute_journal_id() reads 'company_id.default_spread_revenue_journal_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_revenue_journal_id' changes"
855
+ },
856
+ {
857
+ "rule": "odoo.stored-compute-needs-complete-depends",
858
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
859
+ "line": 280,
860
+ "verdict": "true",
861
+ "class": null,
862
+ "why": null,
863
+ "evidence": "_compute_debit_account_id() reads 'company_id.default_spread_revenue_account_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_revenue_account_id' changes"
864
+ },
865
+ {
866
+ "rule": "odoo.stored-compute-needs-complete-depends",
867
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread.py",
868
+ "line": 288,
869
+ "verdict": "true",
870
+ "class": null,
871
+ "why": null,
872
+ "evidence": "_compute_credit_account_id() reads 'company_id.default_spread_expense_account_id' but @api.depends does not declare it -- the stored value goes stale when 'company_id.default_spread_expense_account_id' changes"
873
+ },
874
+ {
875
+ "rule": "odoo.check-company-on-relations",
876
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
877
+ "line": 20,
878
+ "verdict": "true",
879
+ "class": null,
880
+ "why": null,
881
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.journal, and this codebase runs multi-company"
882
+ },
883
+ {
884
+ "rule": "odoo.check-company-on-relations",
885
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
886
+ "line": 33,
887
+ "verdict": "true",
888
+ "class": null,
889
+ "why": null,
890
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
891
+ },
892
+ {
893
+ "rule": "odoo.check-company-on-relations",
894
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
895
+ "line": 41,
896
+ "verdict": "true",
897
+ "class": null,
898
+ "why": null,
899
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
900
+ },
901
+ {
902
+ "rule": "odoo.check-company-on-relations",
903
+ "path": "account-financial-tools/account_spread_cost_revenue/models/account_spread_template.py",
904
+ "line": 176,
905
+ "verdict": "true",
906
+ "class": null,
907
+ "why": null,
908
+ "evidence": "relation to account.account with no check_company, on a model that has its own company_id -- a record in one company can reference another company's account.account, and this codebase runs multi-company"
909
+ },
910
+ {
911
+ "rule": "odoo.sudo-bypasses-record-rules",
912
+ "path": "account-financial-tools/purchase_unreconciled/wizards/purchase_unreconciled_exceeded.py",
913
+ "line": 14,
914
+ "verdict": "false",
915
+ "class": "registry-model",
916
+ "why": "sudo() on ir.model, which has no company_id and so no tenant boundary",
917
+ "evidence": "(m.model, m.name) for m in self.env[\"ir.model\"].sudo().search([])"
918
+ },
919
+ {
920
+ "rule": "odoo.sudo-bypasses-record-rules",
921
+ "path": "server-tools/base_sequence_option/models/ir_sequence_option.py",
922
+ "line": 95,
923
+ "verdict": "false",
924
+ "class": "no-record-rule-on-this-model",
925
+ "why": "ir.sequence.option is a configuration model with no tenant boundary; the call is self.sudo() so the checker cannot name the model and keeps the finding",
926
+ "evidence": null
927
+ },
928
+ {
929
+ "rule": "odoo.sudo-bypasses-record-rules",
930
+ "path": "account-financial-tools/account_asset_management/models/account_asset.py",
931
+ "line": 1190,
932
+ "verdict": "false",
933
+ "class": "scoped-by-hand",
934
+ "why": "reads every company's recompute triggers and then checks asset.company_id against them by hand, which is what the rule's own correct sample does",
935
+ "evidence": null
936
+ },
937
+ {
938
+ "rule": "odoo.sudo-bypasses-record-rules",
939
+ "path": "account-financial-tools/account_lock_date_update/wizards/account_update_lock_date.py",
940
+ "line": 60,
941
+ "verdict": "false",
942
+ "class": "registry-model",
943
+ "why": "search_read on ir.model.fields, which has no company_id and so no tenant boundary",
944
+ "evidence": null
945
+ }
946
+ ],
947
+ "note": "Re-measured after sudo-bypasses-record-rules became an AST pass and suppression learned to read a marker across the statement it sits in. Three findings are new: the AST pass also sees .sudo().search_count( and .sudo().search_read(, which the regex could not, and all three are false. One false positive left -- a sql-needs-query-parameters in base_multi_image/hooks.py that carries a marker on the line above it. All 79 true positives survived both changes."
948
  }
classification-openspp-rest-2026-09.json ADDED
@@ -0,0 +1,1640 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_utc": "2026-09-16T06:57:44+00:00",
3
+ "stack": "odoo",
4
+ "roots": [
5
+ "D:/OpenSPP2-19.0"
6
+ ],
7
+ "checkouts": {
8
+ "OpenSPP2-19.0": "cfa0dc8ee3432ab2dd095a4882a861cc9dfba039"
9
+ },
10
+ "scope": "every remaining live finding on OpenSPP after the two depends/constrains rules were classified separately: the other 163, read by hand against the file. Measured against fieldtest-openspp-2026-09.json, i.e. the corpus AS IT WAS when these were read -- sudo-bypasses-record-rules was still a regex for `.sudo().search(` at that point. This file is the evidence that changed it, so it is deliberately not re-run against the narrowed rule; the line numbers are the regex's, and the AST pass that replaced it anchors a chained call one line earlier.",
11
+ "files": 1351,
12
+ "lines": 226937,
13
+ "totals": {
14
+ "true": 51,
15
+ "false": 109,
16
+ "undecidable": 3
17
+ },
18
+ "by_rule": {
19
+ "odoo.sudo-bypasses-record-rules": {
20
+ "false": 97,
21
+ "undecidable": 3,
22
+ "true": 6
23
+ },
24
+ "odoo.ondelete-is-a-decision": {
25
+ "true": 14
26
+ },
27
+ "odoo.depends-must-not-name-unread-fields": {
28
+ "true": 10,
29
+ "false": 1
30
+ },
31
+ "odoo.check-company-on-relations": {
32
+ "true": 8,
33
+ "false": 1
34
+ },
35
+ "odoo.onchange-is-not-validation": {
36
+ "false": 3,
37
+ "true": 4
38
+ },
39
+ "odoo.float-comparison-needs-float-utils": {
40
+ "true": 2,
41
+ "false": 5
42
+ },
43
+ "odoo.recursion-check-on-hierarchy": {
44
+ "true": 6
45
+ },
46
+ "odoo.sql-needs-query-parameters": {
47
+ "false": 2
48
+ },
49
+ "odoo.commit-inside-a-loop-splits-the-transaction": {
50
+ "true": 1
51
+ }
52
+ },
53
+ "false_classes": {
54
+ "no-record-rule-on-this-model": 73,
55
+ "scoped-by-hand": 14,
56
+ "rule-is-unconditional": 10,
57
+ "integer-not-float": 5,
58
+ "onchange-does-not-validate": 3,
59
+ "identifier-not-value": 2,
60
+ "sql-view-model": 1,
61
+ "deliberate-recompute-trigger": 1
62
+ },
63
+ "hits": [
64
+ {
65
+ "rule": "odoo.ondelete-is-a-decision",
66
+ "path": "fastapi/models/fastapi_endpoint.py",
67
+ "line": 40,
68
+ "evidence": "user_id declares no ondelete, so the ORM defaults it to 'set null' -- and user_id.company_id on line 106 reads through it, which yields False rather t",
69
+ "verdict": "true",
70
+ "class": null,
71
+ "why": "the construct and the harm the rule names are both present"
72
+ },
73
+ {
74
+ "rule": "odoo.ondelete-is-a-decision",
75
+ "path": "spp_alerts/models/alert_rule.py",
76
+ "line": 116,
77
+ "evidence": "monitored_field_id declares no ondelete, so the ORM defaults it to 'set null' -- and monitored_field_id.name on line 327 reads through it, which yield",
78
+ "verdict": "true",
79
+ "class": null,
80
+ "why": "the construct and the harm the rule names are both present"
81
+ },
82
+ {
83
+ "rule": "odoo.ondelete-is-a-decision",
84
+ "path": "spp_alerts/models/alert_rule.py",
85
+ "line": 123,
86
+ "evidence": "date_field_id declares no ondelete, so the ORM defaults it to 'set null' -- and date_field_id.name on line 360 reads through it, which yields False ra",
87
+ "verdict": "true",
88
+ "class": null,
89
+ "why": "the construct and the harm the rule names are both present"
90
+ },
91
+ {
92
+ "rule": "odoo.ondelete-is-a-decision",
93
+ "path": "spp_approval/models/approval_definition.py",
94
+ "line": 58,
95
+ "evidence": "approval_group_id declares no ondelete, so the ORM defaults it to 'set null' -- and approval_group_id.user_ids on line 158 reads through it, which yie",
96
+ "verdict": "true",
97
+ "class": null,
98
+ "why": "the construct and the harm the rule names are both present"
99
+ },
100
+ {
101
+ "rule": "odoo.recursion-check-on-hierarchy",
102
+ "path": "spp_area/models/area_core.py",
103
+ "line": 26,
104
+ "evidence": "parent_id = fields.Many2one(_name, \"Parent\")",
105
+ "verdict": "true",
106
+ "class": null,
107
+ "why": "the construct and the harm the rule names are both present"
108
+ },
109
+ {
110
+ "rule": "odoo.recursion-check-on-hierarchy",
111
+ "path": "spp_area/models/area_core.py",
112
+ "line": 230,
113
+ "evidence": "parent_id = fields.Many2one(_type_model, \"Parent\")",
114
+ "verdict": "true",
115
+ "class": null,
116
+ "why": "the construct and the harm the rule names are both present"
117
+ },
118
+ {
119
+ "rule": "odoo.recursion-check-on-hierarchy",
120
+ "path": "spp_audit/models/spp_audit_rule.py",
121
+ "line": 142,
122
+ "evidence": "parent_id = fields.Many2one(",
123
+ "verdict": "true",
124
+ "class": null,
125
+ "why": "the construct and the harm the rule names are both present"
126
+ },
127
+ {
128
+ "rule": "odoo.depends-must-not-name-unread-fields",
129
+ "path": "spp_base_common/models/phone_number.py",
130
+ "line": 84,
131
+ "evidence": "_compute_phone_sanitized() declares 'country_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the d",
132
+ "verdict": "true",
133
+ "class": null,
134
+ "why": "the construct and the harm the rule names are both present"
135
+ },
136
+ {
137
+ "rule": "odoo.depends-must-not-name-unread-fields",
138
+ "path": "spp_change_request_v2/wizards/conflict_wizard.py",
139
+ "line": 77,
140
+ "evidence": "_compute_can_override() declares 'change_request_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and t",
141
+ "verdict": "true",
142
+ "class": null,
143
+ "why": "the construct and the harm the rule names are both present"
144
+ },
145
+ {
146
+ "rule": "odoo.depends-must-not-name-unread-fields",
147
+ "path": "spp_change_request_v2/wizards/document_upload_wizard.py",
148
+ "line": 172,
149
+ "evidence": "_compute_category_domain() declares 'change_request_id', 'change_request_id.request_type_id', 'change_request_id.request_type_id.required_document_typ",
150
+ "verdict": "true",
151
+ "class": null,
152
+ "why": "the construct and the harm the rule names are both present"
153
+ },
154
+ {
155
+ "rule": "odoo.onchange-is-not-validation",
156
+ "path": "spp_claim_169/wizards/verify_qr_wizard.py",
157
+ "line": 66,
158
+ "evidence": "@api.onchange(\"auto_detect_key\")",
159
+ "verdict": "false",
160
+ "class": "onchange-does-not-validate",
161
+ "why": "the onchange clears a field; it enforces nothing"
162
+ },
163
+ {
164
+ "rule": "odoo.commit-inside-a-loop-splits-the-transaction",
165
+ "path": "spp_consent/models/consent.py",
166
+ "line": 926,
167
+ "evidence": "cr.commit() inside a loop -- an exception on a later iteration leaves the earlier ones written, and the caller cannot roll the batch back",
168
+ "verdict": "true",
169
+ "class": null,
170
+ "why": "the construct and the harm the rule names are both present"
171
+ },
172
+ {
173
+ "rule": "odoo.recursion-check-on-hierarchy",
174
+ "path": "spp_consent/models/consent_purpose.py",
175
+ "line": 43,
176
+ "evidence": "parent_id = fields.Many2one(",
177
+ "verdict": "true",
178
+ "class": null,
179
+ "why": "the construct and the harm the rule names are both present"
180
+ },
181
+ {
182
+ "rule": "odoo.ondelete-is-a-decision",
183
+ "path": "spp_disability_registry/models/assessment.py",
184
+ "line": 406,
185
+ "evidence": "severity_level_id declares no ondelete, so the ORM defaults it to 'set null' -- and severity_level_id.display on line 431 reads through it, which yiel",
186
+ "verdict": "true",
187
+ "class": null,
188
+ "why": "the construct and the harm the rule names are both present"
189
+ },
190
+ {
191
+ "rule": "odoo.recursion-check-on-hierarchy",
192
+ "path": "spp_dms/models/dms_directory.py",
193
+ "line": 20,
194
+ "evidence": "parent_id = fields.Many2one(",
195
+ "verdict": "true",
196
+ "class": null,
197
+ "why": "the construct and the harm the rule names are both present"
198
+ },
199
+ {
200
+ "rule": "odoo.ondelete-is-a-decision",
201
+ "path": "spp_drims/models/donation.py",
202
+ "line": 72,
203
+ "evidence": "donor_id declares no ondelete, so the ORM defaults it to 'set null' -- and donor_id.name on line 279 reads through it, which yields False rather than ",
204
+ "verdict": "true",
205
+ "class": null,
206
+ "why": "the construct and the harm the rule names are both present"
207
+ },
208
+ {
209
+ "rule": "odoo.check-company-on-relations",
210
+ "path": "spp_drims/models/donation.py",
211
+ "line": 96,
212
+ "evidence": "relation to stock.warehouse with no check_company, on a model that has its own company_id -- a record in one company can reference another company's s",
213
+ "verdict": "true",
214
+ "class": null,
215
+ "why": "the construct and the harm the rule names are both present"
216
+ },
217
+ {
218
+ "rule": "odoo.ondelete-is-a-decision",
219
+ "path": "spp_drims/models/donation_line.py",
220
+ "line": 72,
221
+ "evidence": "disposition_id declares no ondelete, so the ORM defaults it to 'set null' -- and disposition_id.code on line 109 reads through it, which yields False ",
222
+ "verdict": "true",
223
+ "class": null,
224
+ "why": "the construct and the harm the rule names are both present"
225
+ },
226
+ {
227
+ "rule": "odoo.depends-must-not-name-unread-fields",
228
+ "path": "spp_drims/models/personnel.py",
229
+ "line": 137,
230
+ "evidence": "_compute_days_deployed() declares 'status' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorat",
231
+ "verdict": "true",
232
+ "class": null,
233
+ "why": "the construct and the harm the rule names are both present"
234
+ },
235
+ {
236
+ "rule": "odoo.check-company-on-relations",
237
+ "path": "spp_drims/models/request.py",
238
+ "line": 155,
239
+ "evidence": "relation to stock.warehouse with no check_company, on a model that has its own company_id -- a record in one company can reference another company's s",
240
+ "verdict": "true",
241
+ "class": null,
242
+ "why": "the construct and the harm the rule names are both present"
243
+ },
244
+ {
245
+ "rule": "odoo.check-company-on-relations",
246
+ "path": "spp_drims/models/returns.py",
247
+ "line": 48,
248
+ "evidence": "relation to stock.warehouse with no check_company, on a model that has its own company_id -- a record in one company can reference another company's s",
249
+ "verdict": "true",
250
+ "class": null,
251
+ "why": "the construct and the harm the rule names are both present"
252
+ },
253
+ {
254
+ "rule": "odoo.onchange-is-not-validation",
255
+ "path": "spp_drims/wizard/create_return_wizard.py",
256
+ "line": 189,
257
+ "evidence": "@api.onchange(\"quantity_to_return\")",
258
+ "verdict": "true",
259
+ "class": null,
260
+ "why": "the construct and the harm the rule names are both present"
261
+ },
262
+ {
263
+ "rule": "odoo.depends-must-not-name-unread-fields",
264
+ "path": "spp_drims/wizard/report_4w_wizard.py",
265
+ "line": 65,
266
+ "evidence": "_compute_cluster_ids_domain() declares 'incident_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and t",
267
+ "verdict": "true",
268
+ "class": null,
269
+ "why": "the construct and the harm the rule names are both present"
270
+ },
271
+ {
272
+ "rule": "odoo.depends-must-not-name-unread-fields",
273
+ "path": "spp_gis_report/models/gis_report.py",
274
+ "line": 416,
275
+ "evidence": "_compute_next_refresh() declares 'refresh_interval' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and th",
276
+ "verdict": "true",
277
+ "class": null,
278
+ "why": "the construct and the harm the rule names are both present"
279
+ },
280
+ {
281
+ "rule": "odoo.depends-must-not-name-unread-fields",
282
+ "path": "spp_grm/models/grm_ticket.py",
283
+ "line": 419,
284
+ "evidence": "_compute_user_id() declares 'category_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decorato",
285
+ "verdict": "true",
286
+ "class": null,
287
+ "why": "the construct and the harm the rule names are both present"
288
+ },
289
+ {
290
+ "rule": "odoo.depends-must-not-name-unread-fields",
291
+ "path": "spp_grm/models/grm_ticket.py",
292
+ "line": 463,
293
+ "evidence": "_compute_sla_deadline() declares 'severity' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the decora",
294
+ "verdict": "true",
295
+ "class": null,
296
+ "why": "the construct and the harm the rule names are both present"
297
+ },
298
+ {
299
+ "rule": "odoo.ondelete-is-a-decision",
300
+ "path": "spp_grm/models/grm_ticket_category.py",
301
+ "line": 29,
302
+ "evidence": "company_id declares no ondelete, so the ORM defaults it to 'set null' -- and company_id.name on line 118 reads through it, which yields False rather t",
303
+ "verdict": "true",
304
+ "class": null,
305
+ "why": "the construct and the harm the rule names are both present"
306
+ },
307
+ {
308
+ "rule": "odoo.onchange-is-not-validation",
309
+ "path": "spp_grm_case_link/wizard/escalate_to_case_wizard.py",
310
+ "line": 104,
311
+ "evidence": "@api.onchange(\"team_id\")",
312
+ "verdict": "false",
313
+ "class": "onchange-does-not-validate",
314
+ "why": "the onchange sets a supervisor from the team; it enforces nothing"
315
+ },
316
+ {
317
+ "rule": "odoo.ondelete-is-a-decision",
318
+ "path": "spp_grm_cel/models/grm_escalation_rule.py",
319
+ "line": 111,
320
+ "evidence": "notification_template_id declares no ondelete, so the ORM defaults it to 'set null' -- and notification_template_id.send_mail on line 508 reads throug",
321
+ "verdict": "true",
322
+ "class": null,
323
+ "why": "the construct and the harm the rule names are both present"
324
+ },
325
+ {
326
+ "rule": "odoo.recursion-check-on-hierarchy",
327
+ "path": "spp_hazard/models/hazard_category.py",
328
+ "line": 28,
329
+ "evidence": "parent_id = fields.Many2one(",
330
+ "verdict": "true",
331
+ "class": null,
332
+ "why": "the construct and the harm the rule names are both present"
333
+ },
334
+ {
335
+ "rule": "odoo.ondelete-is-a-decision",
336
+ "path": "spp_key_management/models/asymmetric_key_support.py",
337
+ "line": 78,
338
+ "evidence": "provider_registry_id declares no ondelete, so the ORM defaults it to 'set null' -- and provider_registry_id.get_provider on line 392 reads through it,",
339
+ "verdict": "true",
340
+ "class": null,
341
+ "why": "the construct and the harm the rule names are both present"
342
+ },
343
+ {
344
+ "rule": "odoo.float-comparison-needs-float-utils",
345
+ "path": "spp_metric_service/models/distribution_service.py",
346
+ "line": 144,
347
+ "evidence": "if total == 0:",
348
+ "verdict": "true",
349
+ "class": null,
350
+ "why": "the construct and the harm the rule names are both present"
351
+ },
352
+ {
353
+ "rule": "odoo.float-comparison-needs-float-utils",
354
+ "path": "spp_metric_service/models/distribution_service.py",
355
+ "line": 161,
356
+ "evidence": "if not sorted_amounts or total == 0:",
357
+ "verdict": "true",
358
+ "class": null,
359
+ "why": "the construct and the harm the rule names are both present"
360
+ },
361
+ {
362
+ "rule": "odoo.float-comparison-needs-float-utils",
363
+ "path": "spp_metric_service/models/fairness_service.py",
364
+ "line": 52,
365
+ "evidence": "if total_population == 0:",
366
+ "verdict": "false",
367
+ "class": "integer-not-float",
368
+ "why": "search_count returns an int, so == 0 is an exact comparison"
369
+ },
370
+ {
371
+ "rule": "odoo.float-comparison-needs-float-utils",
372
+ "path": "spp_metric_service/models/fairness_service.py",
373
+ "line": 240,
374
+ "evidence": "if group_total == 0:",
375
+ "verdict": "false",
376
+ "class": "integer-not-float",
377
+ "why": "a read_group _count is an int, so == 0 is an exact comparison"
378
+ },
379
+ {
380
+ "rule": "odoo.float-comparison-needs-float-utils",
381
+ "path": "spp_metric_service/models/fairness_service.py",
382
+ "line": 316,
383
+ "evidence": "if group_total == 0:",
384
+ "verdict": "false",
385
+ "class": "integer-not-float",
386
+ "why": "a read_group _count is an int, so == 0 is an exact comparison"
387
+ },
388
+ {
389
+ "rule": "odoo.float-comparison-needs-float-utils",
390
+ "path": "spp_metric_service/models/fairness_service.py",
391
+ "line": 381,
392
+ "evidence": "if group_total == 0:",
393
+ "verdict": "false",
394
+ "class": "integer-not-float",
395
+ "why": "a read_group _count is an int, so == 0 is an exact comparison"
396
+ },
397
+ {
398
+ "rule": "odoo.onchange-is-not-validation",
399
+ "path": "spp_programs/models/duplicate.py",
400
+ "line": 17,
401
+ "evidence": "@api.onchange(\"beneficiary_ids\")",
402
+ "verdict": "true",
403
+ "class": null,
404
+ "why": "the construct and the harm the rule names are both present"
405
+ },
406
+ {
407
+ "rule": "odoo.check-company-on-relations",
408
+ "path": "spp_programs/models/entitlement.py",
409
+ "line": 92,
410
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's a",
411
+ "verdict": "true",
412
+ "class": null,
413
+ "why": "the construct and the harm the rule names are both present"
414
+ },
415
+ {
416
+ "rule": "odoo.ondelete-is-a-decision",
417
+ "path": "spp_programs/models/entitlement.py",
418
+ "line": 754,
419
+ "evidence": "product_id declares no ondelete, so the ORM defaults it to 'set null' -- and product_id.name on line 839 reads through it, which yields False rather t",
420
+ "verdict": "true",
421
+ "class": null,
422
+ "why": "the construct and the harm the rule names are both present"
423
+ },
424
+ {
425
+ "rule": "odoo.check-company-on-relations",
426
+ "path": "spp_programs/models/entitlement.py",
427
+ "line": 761,
428
+ "evidence": "relation to stock.warehouse with no check_company, on a model that has its own company_id -- a record in one company can reference another company's s",
429
+ "verdict": "true",
430
+ "class": null,
431
+ "why": "the construct and the harm the rule names are both present"
432
+ },
433
+ {
434
+ "rule": "odoo.check-company-on-relations",
435
+ "path": "spp_programs/models/entitlement.py",
436
+ "line": 771,
437
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's a",
438
+ "verdict": "true",
439
+ "class": null,
440
+ "why": "the construct and the harm the rule names are both present"
441
+ },
442
+ {
443
+ "rule": "odoo.check-company-on-relations",
444
+ "path": "spp_programs/models/payment.py",
445
+ "line": 66,
446
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's a",
447
+ "verdict": "true",
448
+ "class": null,
449
+ "why": "the construct and the harm the rule names are both present"
450
+ },
451
+ {
452
+ "rule": "odoo.ondelete-is-a-decision",
453
+ "path": "spp_programs/models/programs.py",
454
+ "line": 52,
455
+ "evidence": "company_id declares no ondelete, so the ORM defaults it to 'set null' -- and company_id.currency_id on line 725 reads through it, which yields False r",
456
+ "verdict": "true",
457
+ "class": null,
458
+ "why": "the construct and the harm the rule names are both present"
459
+ },
460
+ {
461
+ "rule": "odoo.check-company-on-relations",
462
+ "path": "spp_programs/models/programs.py",
463
+ "line": 134,
464
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's a",
465
+ "verdict": "true",
466
+ "class": null,
467
+ "why": "the construct and the harm the rule names are both present"
468
+ },
469
+ {
470
+ "rule": "odoo.check-company-on-relations",
471
+ "path": "spp_programs/models/accounting/fund_report.py",
472
+ "line": 17,
473
+ "evidence": "relation to account.journal with no check_company, on a model that has its own company_id -- a record in one company can reference another company's a",
474
+ "verdict": "false",
475
+ "class": "sql-view-model",
476
+ "why": "a read-only SQL view model; check_company has nothing to enforce on it"
477
+ },
478
+ {
479
+ "rule": "odoo.sql-needs-query-parameters",
480
+ "path": "spp_programs/models/accounting/fund_report.py",
481
+ "line": 77,
482
+ "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
483
+ "verdict": "false",
484
+ "class": "identifier-not-value",
485
+ "why": "the f-string interpolates self._table, an identifier, and the values are bound"
486
+ },
487
+ {
488
+ "rule": "odoo.float-comparison-needs-float-utils",
489
+ "path": "spp_programs/models/managers/pagination_utils.py",
490
+ "line": 36,
491
+ "evidence": "if total == 0:",
492
+ "verdict": "false",
493
+ "class": "integer-not-float",
494
+ "why": "SELECT COUNT(*) returns an int, so == 0 is an exact comparison"
495
+ },
496
+ {
497
+ "rule": "odoo.sql-needs-query-parameters",
498
+ "path": "spp_programs/models/managers/pagination_utils.py",
499
+ "line": 51,
500
+ "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
501
+ "verdict": "false",
502
+ "class": "identifier-not-value",
503
+ "why": "the f-string interpolates a table name; the values go through params"
504
+ },
505
+ {
506
+ "rule": "odoo.onchange-is-not-validation",
507
+ "path": "spp_programs/models/managers/recurrence_mixin.py",
508
+ "line": 51,
509
+ "evidence": "@api.onchange(\"day\")",
510
+ "verdict": "true",
511
+ "class": null,
512
+ "why": "the construct and the harm the rule names are both present"
513
+ },
514
+ {
515
+ "rule": "odoo.ondelete-is-a-decision",
516
+ "path": "spp_programs/models/stock/stock.py",
517
+ "line": 9,
518
+ "evidence": "entitlement_id declares no ondelete, so the ORM defaults it to 'set null' -- and entitlement_id.cycle_id on line 19 reads through it, which yields Fal",
519
+ "verdict": "true",
520
+ "class": null,
521
+ "why": "the construct and the harm the rule names are both present"
522
+ },
523
+ {
524
+ "rule": "odoo.onchange-is-not-validation",
525
+ "path": "spp_programs/wizard/create_program_wizard.py",
526
+ "line": 136,
527
+ "evidence": "@api.onchange(\"day\")",
528
+ "verdict": "true",
529
+ "class": null,
530
+ "why": "the construct and the harm the rule names are both present"
531
+ },
532
+ {
533
+ "rule": "odoo.depends-must-not-name-unread-fields",
534
+ "path": "spp_registry/models/group.py",
535
+ "line": 34,
536
+ "evidence": "_compute_force_recompute_canary() declares 'group_membership_ids', 'group_membership_ids.individual' in @api.depends and never reads them -- the value",
537
+ "verdict": "false",
538
+ "class": "deliberate-recompute-trigger",
539
+ "why": "a canary field whose whole purpose is to declare fields it does not read"
540
+ },
541
+ {
542
+ "rule": "odoo.depends-must-not-name-unread-fields",
543
+ "path": "spp_service_points/models/registrant.py",
544
+ "line": 100,
545
+ "evidence": "_compute_phone_sanitized() declares 'country_id' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the d",
546
+ "verdict": "true",
547
+ "class": null,
548
+ "why": "the construct and the harm the rule names are both present"
549
+ },
550
+ {
551
+ "rule": "odoo.ondelete-is-a-decision",
552
+ "path": "spp_studio/models/studio_field.py",
553
+ "line": 113,
554
+ "evidence": "link_model_id declares no ondelete, so the ORM defaults it to 'set null' -- and link_model_id.model on line 368 reads through it, which yields False r",
555
+ "verdict": "true",
556
+ "class": null,
557
+ "why": "the construct and the harm the rule names are both present"
558
+ },
559
+ {
560
+ "rule": "odoo.depends-must-not-name-unread-fields",
561
+ "path": "spp_studio/models/studio_field.py",
562
+ "line": 185,
563
+ "evidence": "_compute_target_model() declares 'target_type' in @api.depends and never reads it -- the value recomputes on writes that cannot change it, and the dec",
564
+ "verdict": "true",
565
+ "class": null,
566
+ "why": "the construct and the harm the rule names are both present"
567
+ },
568
+ {
569
+ "rule": "odoo.onchange-is-not-validation",
570
+ "path": "spp_studio/wizard/variable_remap_wizard.py",
571
+ "line": 110,
572
+ "evidence": "@api.onchange(\"variable_id\")",
573
+ "verdict": "false",
574
+ "class": "onchange-does-not-validate",
575
+ "why": "the onchange sets the target model; it enforces nothing"
576
+ },
577
+ {
578
+ "rule": "odoo.sudo-bypasses-record-rules",
579
+ "path": "fastapi/models/fastapi_endpoint_demo.py",
580
+ "line": 89,
581
+ "model": "res.users",
582
+ "verdict": "false",
583
+ "class": "scoped-by-hand",
584
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
585
+ "evidence": null
586
+ },
587
+ {
588
+ "rule": "odoo.sudo-bypasses-record-rules",
589
+ "path": "spp_analytics/models/service_cache.py",
590
+ "line": 69,
591
+ "model": "spp.analytics.cache.entry",
592
+ "verdict": "false",
593
+ "class": "no-record-rule-on-this-model",
594
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
595
+ "evidence": null
596
+ },
597
+ {
598
+ "rule": "odoo.sudo-bypasses-record-rules",
599
+ "path": "spp_analytics/models/service_cache.py",
600
+ "line": 184,
601
+ "model": "spp.analytics.cache.entry",
602
+ "verdict": "false",
603
+ "class": "no-record-rule-on-this-model",
604
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
605
+ "evidence": null
606
+ },
607
+ {
608
+ "rule": "odoo.sudo-bypasses-record-rules",
609
+ "path": "spp_analytics/models/service_cache.py",
610
+ "line": 241,
611
+ "model": "spp.analytics.cache.entry",
612
+ "verdict": "false",
613
+ "class": "no-record-rule-on-this-model",
614
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
615
+ "evidence": null
616
+ },
617
+ {
618
+ "rule": "odoo.sudo-bypasses-record-rules",
619
+ "path": "spp_analytics/models/service_scope_resolver.py",
620
+ "line": 169,
621
+ "model": "res.partner",
622
+ "verdict": "false",
623
+ "class": "scoped-by-hand",
624
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
625
+ "evidence": null
626
+ },
627
+ {
628
+ "rule": "odoo.sudo-bypasses-record-rules",
629
+ "path": "spp_analytics/models/service_scope_resolver.py",
630
+ "line": 312,
631
+ "model": "res.partner",
632
+ "verdict": "false",
633
+ "class": "scoped-by-hand",
634
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
635
+ "evidence": null
636
+ },
637
+ {
638
+ "rule": "odoo.sudo-bypasses-record-rules",
639
+ "path": "spp_api_v2/middleware/auth.py",
640
+ "line": 104,
641
+ "model": "spp.api.client",
642
+ "verdict": "false",
643
+ "class": "no-record-rule-on-this-model",
644
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
645
+ "evidence": null
646
+ },
647
+ {
648
+ "rule": "odoo.sudo-bypasses-record-rules",
649
+ "path": "spp_api_v2/services/group_service.py",
650
+ "line": 40,
651
+ "model": "spp.registry.id",
652
+ "verdict": "false",
653
+ "class": "no-record-rule-on-this-model",
654
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
655
+ "evidence": null
656
+ },
657
+ {
658
+ "rule": "odoo.sudo-bypasses-record-rules",
659
+ "path": "spp_api_v2/services/group_service.py",
660
+ "line": 324,
661
+ "model": "spp.vocabulary.code",
662
+ "verdict": "false",
663
+ "class": "rule-is-unconditional",
664
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
665
+ "evidence": null
666
+ },
667
+ {
668
+ "rule": "odoo.sudo-bypasses-record-rules",
669
+ "path": "spp_api_v2/services/group_service.py",
670
+ "line": 427,
671
+ "model": "spp.vocabulary.code",
672
+ "verdict": "false",
673
+ "class": "rule-is-unconditional",
674
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
675
+ "evidence": null
676
+ },
677
+ {
678
+ "rule": "odoo.sudo-bypasses-record-rules",
679
+ "path": "spp_api_v2/services/group_service.py",
680
+ "line": 486,
681
+ "model": "spp.registry.id",
682
+ "verdict": "false",
683
+ "class": "no-record-rule-on-this-model",
684
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
685
+ "evidence": null
686
+ },
687
+ {
688
+ "rule": "odoo.sudo-bypasses-record-rules",
689
+ "path": "spp_api_v2/services/group_service.py",
690
+ "line": 500,
691
+ "model": "spp.registry.id",
692
+ "verdict": "false",
693
+ "class": "no-record-rule-on-this-model",
694
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
695
+ "evidence": null
696
+ },
697
+ {
698
+ "rule": "odoo.sudo-bypasses-record-rules",
699
+ "path": "spp_api_v2/services/group_service.py",
700
+ "line": 638,
701
+ "model": "spp.group.membership",
702
+ "verdict": "false",
703
+ "class": "scoped-by-hand",
704
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
705
+ "evidence": null
706
+ },
707
+ {
708
+ "rule": "odoo.sudo-bypasses-record-rules",
709
+ "path": "spp_api_v2/services/group_service.py",
710
+ "line": 686,
711
+ "model": "spp.vocabulary.code",
712
+ "verdict": "false",
713
+ "class": "rule-is-unconditional",
714
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
715
+ "evidence": null
716
+ },
717
+ {
718
+ "rule": "odoo.sudo-bypasses-record-rules",
719
+ "path": "spp_api_v2/services/group_service.py",
720
+ "line": 807,
721
+ "model": "spp.vocabulary.code",
722
+ "verdict": "false",
723
+ "class": "rule-is-unconditional",
724
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
725
+ "evidence": null
726
+ },
727
+ {
728
+ "rule": "odoo.sudo-bypasses-record-rules",
729
+ "path": "spp_api_v2/services/group_service.py",
730
+ "line": 912,
731
+ "model": "spp.vocabulary.code",
732
+ "verdict": "false",
733
+ "class": "rule-is-unconditional",
734
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
735
+ "evidence": null
736
+ },
737
+ {
738
+ "rule": "odoo.sudo-bypasses-record-rules",
739
+ "path": "spp_api_v2/services/group_service.py",
740
+ "line": 1018,
741
+ "model": "spp.group.membership",
742
+ "verdict": "false",
743
+ "class": "scoped-by-hand",
744
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
745
+ "evidence": null
746
+ },
747
+ {
748
+ "rule": "odoo.sudo-bypasses-record-rules",
749
+ "path": "spp_api_v2/services/group_service.py",
750
+ "line": 1055,
751
+ "model": "spp.vocabulary.code",
752
+ "verdict": "false",
753
+ "class": "rule-is-unconditional",
754
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
755
+ "evidence": null
756
+ },
757
+ {
758
+ "rule": "odoo.sudo-bypasses-record-rules",
759
+ "path": "spp_api_v2/services/group_service.py",
760
+ "line": 1199,
761
+ "model": "spp.group.membership",
762
+ "verdict": "false",
763
+ "class": "scoped-by-hand",
764
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
765
+ "evidence": null
766
+ },
767
+ {
768
+ "rule": "odoo.sudo-bypasses-record-rules",
769
+ "path": "spp_api_v2/services/individual_service.py",
770
+ "line": 42,
771
+ "model": "spp.registry.id",
772
+ "verdict": "false",
773
+ "class": "no-record-rule-on-this-model",
774
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
775
+ "evidence": null
776
+ },
777
+ {
778
+ "rule": "odoo.sudo-bypasses-record-rules",
779
+ "path": "spp_api_v2/services/individual_service.py",
780
+ "line": 85,
781
+ "model": "spp.registry.id",
782
+ "verdict": "false",
783
+ "class": "no-record-rule-on-this-model",
784
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
785
+ "evidence": null
786
+ },
787
+ {
788
+ "rule": "odoo.sudo-bypasses-record-rules",
789
+ "path": "spp_api_v2/services/individual_service.py",
790
+ "line": 357,
791
+ "model": "spp.vocabulary.code",
792
+ "verdict": "false",
793
+ "class": "rule-is-unconditional",
794
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
795
+ "evidence": null
796
+ },
797
+ {
798
+ "rule": "odoo.sudo-bypasses-record-rules",
799
+ "path": "spp_api_v2/services/individual_service.py",
800
+ "line": 454,
801
+ "model": "spp.vocabulary.code",
802
+ "verdict": "false",
803
+ "class": "rule-is-unconditional",
804
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
805
+ "evidence": null
806
+ },
807
+ {
808
+ "rule": "odoo.sudo-bypasses-record-rules",
809
+ "path": "spp_api_v2/services/individual_service.py",
810
+ "line": 745,
811
+ "model": "spp.group.membership",
812
+ "verdict": "false",
813
+ "class": "scoped-by-hand",
814
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
815
+ "evidence": null
816
+ },
817
+ {
818
+ "rule": "odoo.sudo-bypasses-record-rules",
819
+ "path": "spp_api_v2/services/search_service.py",
820
+ "line": 100,
821
+ "model": null,
822
+ "verdict": "undecidable",
823
+ "class": "model-not-resolvable",
824
+ "why": "the searched model is a variable, so the record-rule question cannot be answered from the call site",
825
+ "evidence": null
826
+ },
827
+ {
828
+ "rule": "odoo.sudo-bypasses-record-rules",
829
+ "path": "spp_api_v2/services/search_service.py",
830
+ "line": 139,
831
+ "model": "spp.registry.id",
832
+ "verdict": "false",
833
+ "class": "no-record-rule-on-this-model",
834
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
835
+ "evidence": null
836
+ },
837
+ {
838
+ "rule": "odoo.sudo-bypasses-record-rules",
839
+ "path": "spp_api_v2/services/search_service.py",
840
+ "line": 250,
841
+ "model": "spp.vocabulary.code",
842
+ "verdict": "false",
843
+ "class": "rule-is-unconditional",
844
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
845
+ "evidence": null
846
+ },
847
+ {
848
+ "rule": "odoo.sudo-bypasses-record-rules",
849
+ "path": "spp_api_v2/services/search_service.py",
850
+ "line": 278,
851
+ "model": "spp.group.membership",
852
+ "verdict": "true",
853
+ "class": null,
854
+ "why": "an unscoped sweep of every membership on a model whose rules filter ended and disabled rows",
855
+ "evidence": null
856
+ },
857
+ {
858
+ "rule": "odoo.sudo-bypasses-record-rules",
859
+ "path": "spp_api_v2/services/search_service.py",
860
+ "line": 294,
861
+ "model": "spp.registry.id",
862
+ "verdict": "false",
863
+ "class": "no-record-rule-on-this-model",
864
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
865
+ "evidence": null
866
+ },
867
+ {
868
+ "rule": "odoo.sudo-bypasses-record-rules",
869
+ "path": "spp_api_v2/services/search_service.py",
870
+ "line": 327,
871
+ "model": "spp.vocabulary.code",
872
+ "verdict": "false",
873
+ "class": "rule-is-unconditional",
874
+ "why": "the only ir.rule on this model is [(1,'=',1)], so sudo drops no filtering",
875
+ "evidence": null
876
+ },
877
+ {
878
+ "rule": "odoo.sudo-bypasses-record-rules",
879
+ "path": "spp_api_v2_cycles/services/cycle_service.py",
880
+ "line": 30,
881
+ "model": "spp.cycle",
882
+ "verdict": "false",
883
+ "class": "no-record-rule-on-this-model",
884
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
885
+ "evidence": null
886
+ },
887
+ {
888
+ "rule": "odoo.sudo-bypasses-record-rules",
889
+ "path": "spp_api_v2_cycles/services/cycle_service.py",
890
+ "line": 98,
891
+ "model": "spp.cycle",
892
+ "verdict": "false",
893
+ "class": "no-record-rule-on-this-model",
894
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
895
+ "evidence": null
896
+ },
897
+ {
898
+ "rule": "odoo.sudo-bypasses-record-rules",
899
+ "path": "spp_api_v2_entitlements/services/entitlement_service.py",
900
+ "line": 47,
901
+ "model": "spp.entitlement.inkind",
902
+ "verdict": "false",
903
+ "class": "no-record-rule-on-this-model",
904
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
905
+ "evidence": null
906
+ },
907
+ {
908
+ "rule": "odoo.sudo-bypasses-record-rules",
909
+ "path": "spp_api_v2_entitlements/services/entitlement_service.py",
910
+ "line": 211,
911
+ "model": "spp.entitlement.inkind",
912
+ "verdict": "false",
913
+ "class": "no-record-rule-on-this-model",
914
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
915
+ "evidence": null
916
+ },
917
+ {
918
+ "rule": "odoo.sudo-bypasses-record-rules",
919
+ "path": "spp_api_v2_entitlements/services/entitlement_service.py",
920
+ "line": 232,
921
+ "model": "spp.registry.id",
922
+ "verdict": "false",
923
+ "class": "no-record-rule-on-this-model",
924
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
925
+ "evidence": null
926
+ },
927
+ {
928
+ "rule": "odoo.sudo-bypasses-record-rules",
929
+ "path": "spp_api_v2_gis/routers/geofence.py",
930
+ "line": 75,
931
+ "model": "spp.hazard.incident",
932
+ "verdict": "false",
933
+ "class": "no-record-rule-on-this-model",
934
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
935
+ "evidence": null
936
+ },
937
+ {
938
+ "rule": "odoo.sudo-bypasses-record-rules",
939
+ "path": "spp_api_v2_gis/routers/ogc_features.py",
940
+ "line": 339,
941
+ "model": "spp.gis.report",
942
+ "verdict": "false",
943
+ "class": "no-record-rule-on-this-model",
944
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
945
+ "evidence": null
946
+ },
947
+ {
948
+ "rule": "odoo.sudo-bypasses-record-rules",
949
+ "path": "spp_api_v2_gis/services/export_service.py",
950
+ "line": 101,
951
+ "model": "spp.gis.report",
952
+ "verdict": "false",
953
+ "class": "no-record-rule-on-this-model",
954
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
955
+ "evidence": null
956
+ },
957
+ {
958
+ "rule": "odoo.sudo-bypasses-record-rules",
959
+ "path": "spp_api_v2_gis/services/layers_service.py",
960
+ "line": 217,
961
+ "model": "spp.area",
962
+ "verdict": "false",
963
+ "class": "no-record-rule-on-this-model",
964
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
965
+ "evidence": null
966
+ },
967
+ {
968
+ "rule": "odoo.sudo-bypasses-record-rules",
969
+ "path": "spp_api_v2_gis/services/layers_service.py",
970
+ "line": 224,
971
+ "model": "spp.area",
972
+ "verdict": "false",
973
+ "class": "no-record-rule-on-this-model",
974
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
975
+ "evidence": null
976
+ },
977
+ {
978
+ "rule": "odoo.sudo-bypasses-record-rules",
979
+ "path": "spp_api_v2_gis/services/layers_service.py",
980
+ "line": 417,
981
+ "model": "spp.gis.report",
982
+ "verdict": "false",
983
+ "class": "no-record-rule-on-this-model",
984
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
985
+ "evidence": null
986
+ },
987
+ {
988
+ "rule": "odoo.sudo-bypasses-record-rules",
989
+ "path": "spp_api_v2_gis/services/layers_service.py",
990
+ "line": 485,
991
+ "model": "spp.gis.report",
992
+ "verdict": "false",
993
+ "class": "no-record-rule-on-this-model",
994
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
995
+ "evidence": null
996
+ },
997
+ {
998
+ "rule": "odoo.sudo-bypasses-record-rules",
999
+ "path": "spp_api_v2_gis/services/layers_service.py",
1000
+ "line": 492,
1001
+ "model": "spp.gis.report.data",
1002
+ "verdict": "false",
1003
+ "class": "no-record-rule-on-this-model",
1004
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1005
+ "evidence": null
1006
+ },
1007
+ {
1008
+ "rule": "odoo.sudo-bypasses-record-rules",
1009
+ "path": "spp_api_v2_gis/services/layers_service.py",
1010
+ "line": 708,
1011
+ "model": "spp.area",
1012
+ "verdict": "false",
1013
+ "class": "no-record-rule-on-this-model",
1014
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1015
+ "evidence": null
1016
+ },
1017
+ {
1018
+ "rule": "odoo.sudo-bypasses-record-rules",
1019
+ "path": "spp_api_v2_gis/services/ogc_service.py",
1020
+ "line": 513,
1021
+ "model": "spp.gis.report",
1022
+ "verdict": "false",
1023
+ "class": "no-record-rule-on-this-model",
1024
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1025
+ "evidence": null
1026
+ },
1027
+ {
1028
+ "rule": "odoo.sudo-bypasses-record-rules",
1029
+ "path": "spp_api_v2_gis/services/qml_template_service.py",
1030
+ "line": 169,
1031
+ "model": "spp.gis.report.data",
1032
+ "verdict": "false",
1033
+ "class": "no-record-rule-on-this-model",
1034
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1035
+ "evidence": null
1036
+ },
1037
+ {
1038
+ "rule": "odoo.sudo-bypasses-record-rules",
1039
+ "path": "spp_api_v2_products/services/product_category_service.py",
1040
+ "line": 31,
1041
+ "model": "product.category",
1042
+ "verdict": "false",
1043
+ "class": "no-record-rule-on-this-model",
1044
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1045
+ "evidence": null
1046
+ },
1047
+ {
1048
+ "rule": "odoo.sudo-bypasses-record-rules",
1049
+ "path": "spp_api_v2_products/services/product_category_service.py",
1050
+ "line": 62,
1051
+ "model": "product.category",
1052
+ "verdict": "false",
1053
+ "class": "no-record-rule-on-this-model",
1054
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1055
+ "evidence": null
1056
+ },
1057
+ {
1058
+ "rule": "odoo.sudo-bypasses-record-rules",
1059
+ "path": "spp_api_v2_products/services/product_service.py",
1060
+ "line": 31,
1061
+ "model": "product.template",
1062
+ "verdict": "true",
1063
+ "class": null,
1064
+ "why": "product.template carries Odoo's company rule and the lookup constrains no company",
1065
+ "evidence": null
1066
+ },
1067
+ {
1068
+ "rule": "odoo.sudo-bypasses-record-rules",
1069
+ "path": "spp_api_v2_products/services/product_service.py",
1070
+ "line": 43,
1071
+ "model": "product.template",
1072
+ "verdict": "true",
1073
+ "class": null,
1074
+ "why": "product.template carries Odoo's company rule and the lookup constrains no company",
1075
+ "evidence": null
1076
+ },
1077
+ {
1078
+ "rule": "odoo.sudo-bypasses-record-rules",
1079
+ "path": "spp_api_v2_products/services/product_service.py",
1080
+ "line": 97,
1081
+ "model": "product.template",
1082
+ "verdict": "true",
1083
+ "class": null,
1084
+ "why": "product.template carries Odoo's company rule and the listing constrains no company",
1085
+ "evidence": null
1086
+ },
1087
+ {
1088
+ "rule": "odoo.sudo-bypasses-record-rules",
1089
+ "path": "spp_api_v2_products/services/uom_service.py",
1090
+ "line": 30,
1091
+ "model": "uom.uom",
1092
+ "verdict": "false",
1093
+ "class": "no-record-rule-on-this-model",
1094
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1095
+ "evidence": null
1096
+ },
1097
+ {
1098
+ "rule": "odoo.sudo-bypasses-record-rules",
1099
+ "path": "spp_api_v2_products/services/uom_service.py",
1100
+ "line": 63,
1101
+ "model": "uom.uom",
1102
+ "verdict": "false",
1103
+ "class": "no-record-rule-on-this-model",
1104
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1105
+ "evidence": null
1106
+ },
1107
+ {
1108
+ "rule": "odoo.sudo-bypasses-record-rules",
1109
+ "path": "spp_api_v2_programs/services/program_membership_service.py",
1110
+ "line": 51,
1111
+ "model": "spp.registry.id",
1112
+ "verdict": "false",
1113
+ "class": "no-record-rule-on-this-model",
1114
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1115
+ "evidence": null
1116
+ },
1117
+ {
1118
+ "rule": "odoo.sudo-bypasses-record-rules",
1119
+ "path": "spp_api_v2_programs/services/program_membership_service.py",
1120
+ "line": 119,
1121
+ "model": "spp.registry.id",
1122
+ "verdict": "false",
1123
+ "class": "no-record-rule-on-this-model",
1124
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1125
+ "evidence": null
1126
+ },
1127
+ {
1128
+ "rule": "odoo.sudo-bypasses-record-rules",
1129
+ "path": "spp_api_v2_programs/services/program_membership_service.py",
1130
+ "line": 135,
1131
+ "model": "spp.program.membership",
1132
+ "verdict": "false",
1133
+ "class": "scoped-by-hand",
1134
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1135
+ "evidence": null
1136
+ },
1137
+ {
1138
+ "rule": "odoo.sudo-bypasses-record-rules",
1139
+ "path": "spp_api_v2_programs/services/program_membership_service.py",
1140
+ "line": 158,
1141
+ "model": "spp.program.membership",
1142
+ "verdict": "false",
1143
+ "class": "scoped-by-hand",
1144
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1145
+ "evidence": null
1146
+ },
1147
+ {
1148
+ "rule": "odoo.sudo-bypasses-record-rules",
1149
+ "path": "spp_api_v2_programs/services/program_membership_service.py",
1150
+ "line": 385,
1151
+ "model": "spp.registry.id",
1152
+ "verdict": "false",
1153
+ "class": "no-record-rule-on-this-model",
1154
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1155
+ "evidence": null
1156
+ },
1157
+ {
1158
+ "rule": "odoo.sudo-bypasses-record-rules",
1159
+ "path": "spp_api_v2_programs/services/program_service.py",
1160
+ "line": 48,
1161
+ "model": "spp.program.id",
1162
+ "verdict": "false",
1163
+ "class": "no-record-rule-on-this-model",
1164
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1165
+ "evidence": null
1166
+ },
1167
+ {
1168
+ "rule": "odoo.sudo-bypasses-record-rules",
1169
+ "path": "spp_api_v2_simulation/routers/scenario.py",
1170
+ "line": 527,
1171
+ "model": "res.currency",
1172
+ "verdict": "false",
1173
+ "class": "no-record-rule-on-this-model",
1174
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1175
+ "evidence": null
1176
+ },
1177
+ {
1178
+ "rule": "odoo.sudo-bypasses-record-rules",
1179
+ "path": "spp_api_v2_simulation/routers/scenario.py",
1180
+ "line": 538,
1181
+ "model": "spp.program",
1182
+ "verdict": "true",
1183
+ "class": null,
1184
+ "why": "a duplicate-name check across every company on a company-ruled model",
1185
+ "evidence": null
1186
+ },
1187
+ {
1188
+ "rule": "odoo.sudo-bypasses-record-rules",
1189
+ "path": "spp_api_v2_simulation/services/analytics_api_service.py",
1190
+ "line": 93,
1191
+ "model": "res.partner",
1192
+ "verdict": "false",
1193
+ "class": "scoped-by-hand",
1194
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1195
+ "evidence": null
1196
+ },
1197
+ {
1198
+ "rule": "odoo.sudo-bypasses-record-rules",
1199
+ "path": "spp_api_v2_simulation/services/simulation_api_service.py",
1200
+ "line": 32,
1201
+ "model": "spp.simulation.scenario.template",
1202
+ "verdict": "false",
1203
+ "class": "no-record-rule-on-this-model",
1204
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1205
+ "evidence": null
1206
+ },
1207
+ {
1208
+ "rule": "odoo.sudo-bypasses-record-rules",
1209
+ "path": "spp_approval/models/approval_review.py",
1210
+ "line": 205,
1211
+ "model": "spp.approval.definition",
1212
+ "verdict": "false",
1213
+ "class": "scoped-by-hand",
1214
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1215
+ "evidence": null
1216
+ },
1217
+ {
1218
+ "rule": "odoo.sudo-bypasses-record-rules",
1219
+ "path": "spp_approval/models/approval_review.py",
1220
+ "line": 217,
1221
+ "model": "spp.approval.definition",
1222
+ "verdict": "true",
1223
+ "class": null,
1224
+ "why": "every manager-type definition, unscoped, on a company-ruled model",
1225
+ "evidence": null
1226
+ },
1227
+ {
1228
+ "rule": "odoo.sudo-bypasses-record-rules",
1229
+ "path": "spp_attachment_av_scan/models/ir_attachment.py",
1230
+ "line": 337,
1231
+ "model": "spp.encryption.provider",
1232
+ "verdict": "false",
1233
+ "class": "no-record-rule-on-this-model",
1234
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1235
+ "evidence": null
1236
+ },
1237
+ {
1238
+ "rule": "odoo.sudo-bypasses-record-rules",
1239
+ "path": "spp_attendance/controllers/controllers.py",
1240
+ "line": 108,
1241
+ "model": null,
1242
+ "verdict": "undecidable",
1243
+ "class": "model-not-resolvable",
1244
+ "why": "the searched model is a variable, so the record-rule question cannot be answered from the call site",
1245
+ "evidence": null
1246
+ },
1247
+ {
1248
+ "rule": "odoo.sudo-bypasses-record-rules",
1249
+ "path": "spp_attendance/controllers/controllers.py",
1250
+ "line": 111,
1251
+ "model": null,
1252
+ "verdict": "undecidable",
1253
+ "class": "model-not-resolvable",
1254
+ "why": "the searched model is a variable, so the record-rule question cannot be answered from the call site",
1255
+ "evidence": null
1256
+ },
1257
+ {
1258
+ "rule": "odoo.sudo-bypasses-record-rules",
1259
+ "path": "spp_attendance/controllers/controllers.py",
1260
+ "line": 215,
1261
+ "model": "spp.attendance.subscriber",
1262
+ "verdict": "false",
1263
+ "class": "no-record-rule-on-this-model",
1264
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1265
+ "evidence": null
1266
+ },
1267
+ {
1268
+ "rule": "odoo.sudo-bypasses-record-rules",
1269
+ "path": "spp_attendance/controllers/controllers.py",
1270
+ "line": 367,
1271
+ "model": "spp.attendance.list",
1272
+ "verdict": "false",
1273
+ "class": "no-record-rule-on-this-model",
1274
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1275
+ "evidence": null
1276
+ },
1277
+ {
1278
+ "rule": "odoo.sudo-bypasses-record-rules",
1279
+ "path": "spp_attendance/controllers/controllers.py",
1280
+ "line": 411,
1281
+ "model": "spp.attendance.list",
1282
+ "verdict": "false",
1283
+ "class": "no-record-rule-on-this-model",
1284
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1285
+ "evidence": null
1286
+ },
1287
+ {
1288
+ "rule": "odoo.sudo-bypasses-record-rules",
1289
+ "path": "spp_attendance/controllers/controllers.py",
1290
+ "line": 436,
1291
+ "model": "spp.attendance.subscriber",
1292
+ "verdict": "false",
1293
+ "class": "no-record-rule-on-this-model",
1294
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1295
+ "evidence": null
1296
+ },
1297
+ {
1298
+ "rule": "odoo.sudo-bypasses-record-rules",
1299
+ "path": "spp_attendance/controllers/controllers.py",
1300
+ "line": 593,
1301
+ "model": "spp.attendance.type",
1302
+ "verdict": "false",
1303
+ "class": "no-record-rule-on-this-model",
1304
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1305
+ "evidence": null
1306
+ },
1307
+ {
1308
+ "rule": "odoo.sudo-bypasses-record-rules",
1309
+ "path": "spp_attendance/controllers/controllers.py",
1310
+ "line": 625,
1311
+ "model": "spp.attendance.location",
1312
+ "verdict": "false",
1313
+ "class": "no-record-rule-on-this-model",
1314
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1315
+ "evidence": null
1316
+ },
1317
+ {
1318
+ "rule": "odoo.sudo-bypasses-record-rules",
1319
+ "path": "spp_attendance/controllers/controllers.py",
1320
+ "line": 706,
1321
+ "model": "spp.attendance.subscriber",
1322
+ "verdict": "false",
1323
+ "class": "no-record-rule-on-this-model",
1324
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1325
+ "evidence": null
1326
+ },
1327
+ {
1328
+ "rule": "odoo.sudo-bypasses-record-rules",
1329
+ "path": "spp_attendance/models/attendance_subscriber.py",
1330
+ "line": 134,
1331
+ "model": "spp.attendance.list",
1332
+ "verdict": "false",
1333
+ "class": "no-record-rule-on-this-model",
1334
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1335
+ "evidence": null
1336
+ },
1337
+ {
1338
+ "rule": "odoo.sudo-bypasses-record-rules",
1339
+ "path": "spp_branding_kit/__init__.py",
1340
+ "line": 41,
1341
+ "model": "ir.ui.menu",
1342
+ "verdict": "false",
1343
+ "class": "scoped-by-hand",
1344
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1345
+ "evidence": null
1346
+ },
1347
+ {
1348
+ "rule": "odoo.sudo-bypasses-record-rules",
1349
+ "path": "spp_dci_client_crvs/middleware/signature.py",
1350
+ "line": 70,
1351
+ "model": "spp.dci.crvs.sender",
1352
+ "verdict": "false",
1353
+ "class": "no-record-rule-on-this-model",
1354
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1355
+ "evidence": null
1356
+ },
1357
+ {
1358
+ "rule": "odoo.sudo-bypasses-record-rules",
1359
+ "path": "spp_dci_client_dr/middleware/signature.py",
1360
+ "line": 70,
1361
+ "model": "spp.dci.dr.sender",
1362
+ "verdict": "false",
1363
+ "class": "no-record-rule-on-this-model",
1364
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1365
+ "evidence": null
1366
+ },
1367
+ {
1368
+ "rule": "odoo.sudo-bypasses-record-rules",
1369
+ "path": "spp_dci_client_dr/routers/callback.py",
1370
+ "line": 199,
1371
+ "model": "spp.registry.id",
1372
+ "verdict": "false",
1373
+ "class": "no-record-rule-on-this-model",
1374
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1375
+ "evidence": null
1376
+ },
1377
+ {
1378
+ "rule": "odoo.sudo-bypasses-record-rules",
1379
+ "path": "spp_dci_client_dr/routers/callback.py",
1380
+ "line": 216,
1381
+ "model": "spp.registry.id",
1382
+ "verdict": "false",
1383
+ "class": "no-record-rule-on-this-model",
1384
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1385
+ "evidence": null
1386
+ },
1387
+ {
1388
+ "rule": "odoo.sudo-bypasses-record-rules",
1389
+ "path": "spp_dci_client_ibr/middleware/signature.py",
1390
+ "line": 70,
1391
+ "model": "spp.dci.ibr.sender",
1392
+ "verdict": "false",
1393
+ "class": "no-record-rule-on-this-model",
1394
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1395
+ "evidence": null
1396
+ },
1397
+ {
1398
+ "rule": "odoo.sudo-bypasses-record-rules",
1399
+ "path": "spp_dci_client_ibr/routers/callback.py",
1400
+ "line": 166,
1401
+ "model": "spp.dci.duplication.check",
1402
+ "verdict": "false",
1403
+ "class": "no-record-rule-on-this-model",
1404
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1405
+ "evidence": null
1406
+ },
1407
+ {
1408
+ "rule": "odoo.sudo-bypasses-record-rules",
1409
+ "path": "spp_dci_client_sr/routers/callback.py",
1410
+ "line": 262,
1411
+ "model": "spp.registry.id",
1412
+ "verdict": "false",
1413
+ "class": "no-record-rule-on-this-model",
1414
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1415
+ "evidence": null
1416
+ },
1417
+ {
1418
+ "rule": "odoo.sudo-bypasses-record-rules",
1419
+ "path": "spp_dci_client_sr/routers/callback.py",
1420
+ "line": 279,
1421
+ "model": "spp.registry.id",
1422
+ "verdict": "false",
1423
+ "class": "no-record-rule-on-this-model",
1424
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1425
+ "evidence": null
1426
+ },
1427
+ {
1428
+ "rule": "odoo.sudo-bypasses-record-rules",
1429
+ "path": "spp_dci_server/middleware/rate_limit.py",
1430
+ "line": 205,
1431
+ "model": "spp.dci.sender.registry",
1432
+ "verdict": "false",
1433
+ "class": "no-record-rule-on-this-model",
1434
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1435
+ "evidence": null
1436
+ },
1437
+ {
1438
+ "rule": "odoo.sudo-bypasses-record-rules",
1439
+ "path": "spp_dci_server/middleware/signature.py",
1440
+ "line": 278,
1441
+ "model": "spp.api.client",
1442
+ "verdict": "false",
1443
+ "class": "no-record-rule-on-this-model",
1444
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1445
+ "evidence": null
1446
+ },
1447
+ {
1448
+ "rule": "odoo.sudo-bypasses-record-rules",
1449
+ "path": "spp_dci_server/models/transaction.py",
1450
+ "line": 571,
1451
+ "model": "spp.dci.subscription",
1452
+ "verdict": "false",
1453
+ "class": "no-record-rule-on-this-model",
1454
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1455
+ "evidence": null
1456
+ },
1457
+ {
1458
+ "rule": "odoo.sudo-bypasses-record-rules",
1459
+ "path": "spp_dci_server/models/transaction.py",
1460
+ "line": 684,
1461
+ "model": "spp.dci.transaction",
1462
+ "verdict": "false",
1463
+ "class": "no-record-rule-on-this-model",
1464
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1465
+ "evidence": null
1466
+ },
1467
+ {
1468
+ "rule": "odoo.sudo-bypasses-record-rules",
1469
+ "path": "spp_dci_server/routers/async_router.py",
1470
+ "line": 262,
1471
+ "model": "spp.dci.sender.registry",
1472
+ "verdict": "false",
1473
+ "class": "no-record-rule-on-this-model",
1474
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1475
+ "evidence": null
1476
+ },
1477
+ {
1478
+ "rule": "odoo.sudo-bypasses-record-rules",
1479
+ "path": "spp_dci_server/routers/async_router.py",
1480
+ "line": 426,
1481
+ "model": "spp.dci.sender.registry",
1482
+ "verdict": "false",
1483
+ "class": "no-record-rule-on-this-model",
1484
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1485
+ "evidence": null
1486
+ },
1487
+ {
1488
+ "rule": "odoo.sudo-bypasses-record-rules",
1489
+ "path": "spp_dci_server/routers/async_router.py",
1490
+ "line": 438,
1491
+ "model": "spp.dci.subscription",
1492
+ "verdict": "false",
1493
+ "class": "no-record-rule-on-this-model",
1494
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1495
+ "evidence": null
1496
+ },
1497
+ {
1498
+ "rule": "odoo.sudo-bypasses-record-rules",
1499
+ "path": "spp_dci_server/routers/async_router.py",
1500
+ "line": 573,
1501
+ "model": "spp.dci.transaction",
1502
+ "verdict": "false",
1503
+ "class": "no-record-rule-on-this-model",
1504
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1505
+ "evidence": null
1506
+ },
1507
+ {
1508
+ "rule": "odoo.sudo-bypasses-record-rules",
1509
+ "path": "spp_dci_server/routers/bulk_upload.py",
1510
+ "line": 304,
1511
+ "model": "spp.dci.sender.registry",
1512
+ "verdict": "false",
1513
+ "class": "no-record-rule-on-this-model",
1514
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1515
+ "evidence": null
1516
+ },
1517
+ {
1518
+ "rule": "odoo.sudo-bypasses-record-rules",
1519
+ "path": "spp_dci_server/routers/callbacks.py",
1520
+ "line": 277,
1521
+ "model": "spp.dci.sender.registry",
1522
+ "verdict": "false",
1523
+ "class": "no-record-rule-on-this-model",
1524
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1525
+ "evidence": null
1526
+ },
1527
+ {
1528
+ "rule": "odoo.sudo-bypasses-record-rules",
1529
+ "path": "spp_dci_server/routers/receipt.py",
1530
+ "line": 140,
1531
+ "model": "spp.dci.subscription",
1532
+ "verdict": "false",
1533
+ "class": "no-record-rule-on-this-model",
1534
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1535
+ "evidence": null
1536
+ },
1537
+ {
1538
+ "rule": "odoo.sudo-bypasses-record-rules",
1539
+ "path": "spp_gis/models/base.py",
1540
+ "line": 73,
1541
+ "model": "ir.ui.view",
1542
+ "verdict": "false",
1543
+ "class": "no-record-rule-on-this-model",
1544
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1545
+ "evidence": null
1546
+ },
1547
+ {
1548
+ "rule": "odoo.sudo-bypasses-record-rules",
1549
+ "path": "spp_key_management/models/key_provider_database.py",
1550
+ "line": 322,
1551
+ "model": "spp.encryption.key",
1552
+ "verdict": "false",
1553
+ "class": "no-record-rule-on-this-model",
1554
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1555
+ "evidence": null
1556
+ },
1557
+ {
1558
+ "rule": "odoo.sudo-bypasses-record-rules",
1559
+ "path": "spp_key_management/models/key_provider_database.py",
1560
+ "line": 348,
1561
+ "model": "spp.encryption.key",
1562
+ "verdict": "false",
1563
+ "class": "no-record-rule-on-this-model",
1564
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1565
+ "evidence": null
1566
+ },
1567
+ {
1568
+ "rule": "odoo.sudo-bypasses-record-rules",
1569
+ "path": "spp_programs/models/managers/compliance_manager.py",
1570
+ "line": 306,
1571
+ "model": "res.partner",
1572
+ "verdict": "false",
1573
+ "class": "scoped-by-hand",
1574
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1575
+ "evidence": null
1576
+ },
1577
+ {
1578
+ "rule": "odoo.sudo-bypasses-record-rules",
1579
+ "path": "spp_programs/models/managers/entitlement_manager_default_compliance.py",
1580
+ "line": 34,
1581
+ "model": "res.partner",
1582
+ "verdict": "false",
1583
+ "class": "scoped-by-hand",
1584
+ "why": "a real rule exists, and the domain constrains the search to records the caller already identified",
1585
+ "evidence": null
1586
+ },
1587
+ {
1588
+ "rule": "odoo.sudo-bypasses-record-rules",
1589
+ "path": "spp_programs/models/managers/payment_manager.py",
1590
+ "line": 167,
1591
+ "model": "spp.payment.batch.tag",
1592
+ "verdict": "false",
1593
+ "class": "no-record-rule-on-this-model",
1594
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1595
+ "evidence": null
1596
+ },
1597
+ {
1598
+ "rule": "odoo.sudo-bypasses-record-rules",
1599
+ "path": "spp_registry/models/group_membership.py",
1600
+ "line": 266,
1601
+ "model": "ir.cron.trigger",
1602
+ "verdict": "false",
1603
+ "class": "no-record-rule-on-this-model",
1604
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1605
+ "evidence": null
1606
+ },
1607
+ {
1608
+ "rule": "odoo.sudo-bypasses-record-rules",
1609
+ "path": "spp_studio_api_v2/routers/studio.py",
1610
+ "line": 480,
1611
+ "model": "spp.registry.id",
1612
+ "verdict": "false",
1613
+ "class": "no-record-rule-on-this-model",
1614
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1615
+ "evidence": null
1616
+ },
1617
+ {
1618
+ "rule": "odoo.sudo-bypasses-record-rules",
1619
+ "path": "spp_studio_change_requests/models/studio_change_request_type.py",
1620
+ "line": 312,
1621
+ "model": "ir.model.fields",
1622
+ "verdict": "false",
1623
+ "class": "no-record-rule-on-this-model",
1624
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1625
+ "evidence": null
1626
+ },
1627
+ {
1628
+ "rule": "odoo.sudo-bypasses-record-rules",
1629
+ "path": "spp_studio_events/models/studio_event_type.py",
1630
+ "line": 424,
1631
+ "model": "ir.model.fields",
1632
+ "verdict": "false",
1633
+ "class": "no-record-rule-on-this-model",
1634
+ "why": "no ir.rule is declared for this model anywhere in the tree, so sudo bypasses nothing",
1635
+ "evidence": null
1636
+ }
1637
+ ],
1638
+ "what_this_changed": "sudo-bypasses-record-rules went 6 true / 97 false / 3 undecidable, 5.8%. 73 of the 97 were on models the tree gives no ir.rule at all and 10 on a model whose only rule is [(1,'=',1)], so sudo dropped nothing at 83 of 106 sites. The rule is now an AST pass that asks the tree whether a filtering rule exists, and answers UNKNOWN -- keeping the finding -- for any model the walk did not see declared. The 14 remaining false positives constrain the search by hand to records the caller already named, in a spelling the rule does not recognise because it names only company_id. That is a worklist entry, not a narrowing: deciding a domain is already scoped means evaluating it.",
1639
+ "worklist": "float-comparison-needs-float-utils is the next one down at 2 true / 5 false. All five compare an INTEGER to zero -- search_count, a read_group _count, SELECT COUNT(*) -- which the regex cannot see because it matches on the variable's name. Fixing it means tracing the assignment, so it is named here and not yet done. onchange-is-not-validation is 4 true / 3 false, and all three false ones are an onchange that sets a field rather than enforcing anything, which the rule's existing narrowing was supposed to exclude and does not catch when the write is to a Many2one."
1640
+ }
corpus/build_corpus.py CHANGED
@@ -989,8 +989,12 @@ ENRICH = {
989
  "source": ODOO_ORM,
990
  },
991
  "odoo.sudo-bypasses-record-rules": {
992
- "violation_pattern": r"\.sudo\(\)\s*\.\s*search\(",
993
- "satisfied_pattern": r"company_id",
 
 
 
 
994
  # Record rules are a security concept, not an ORM one. The ORM reference
995
  # does not mention sudo at all.
996
  "source": ODOO_SECURITY,
@@ -1208,9 +1212,18 @@ ENRICH = {
1208
  # the one-bit version of this axis and is now the gate that falls out of it.
1209
 
1210
  LIKELIHOOD = {
1211
- # 13 of 41 placements on a line somebody changed, the best-aimed rule in the
1212
- # corpus and the only one measured to be right about the bug more often than
1213
- # it is right about the file.
 
 
 
 
 
 
 
 
 
1214
  "odoo.sudo-bypasses-record-rules": "defect",
1215
 
1216
  # 1 of 42, and 7 in the right method describing something else. The trigger
 
989
  "source": ODOO_ORM,
990
  },
991
  "odoo.sudo-bypasses-record-rules": {
992
+ # Was a regex for `.sudo().search(`, which asked only whether the
993
+ # construct was present. Measured on OpenSPP it made 106 findings and 6
994
+ # were the defect: the other 100 were on models the tree gives no
995
+ # filtering rule, where sudo drops nothing. The AST pass asks the tree
996
+ # the second half of the question.
997
+ "semantic_check": "sudo_search_drops_a_real_rule",
998
  # Record rules are a security concept, not an ORM one. The ORM reference
999
  # does not mention sudo at all.
1000
  "source": ODOO_SECURITY,
 
1212
  # the one-bit version of this axis and is now the gate that falls out of it.
1213
 
1214
  LIKELIHOOD = {
1215
+ # 13 on-target of 44 placements, re-measured after this rule became an AST
1216
+ # pass. Still the best-aimed rule in the corpus by a distance: the next one
1217
+ # down lands on the line once. The total moved from 41 because the AST pass
1218
+ # also sees .sudo().search_count( and .sudo().search_read(, which the regex
1219
+ # it replaced could not.
1220
+ #
1221
+ # Like ondelete's, this grade is what the rule is worth WITHOUT its trigger.
1222
+ # recall.py builds no tree_facts, so context.model_is_record_ruled reads
1223
+ # UNKNOWN on every file it measures and the checker keeps every finding it
1224
+ # can make. The trigger only ever suppresses, so the aimed subset is a
1225
+ # subset of what was measured here -- the grade is a floor for the narrowed
1226
+ # rule rather than a description of it.
1227
  "odoo.sudo-bypasses-record-rules": "defect",
1228
 
1229
  # 1 of 42, and 7 in the right method describing something else. The trigger
corpus/corpus_meta.json CHANGED
@@ -1,14 +1,14 @@
1
  {
2
- "built_utc": "2026-09-16T00:03:33.130288+00:00",
3
  "rules": 43,
4
  "machine_checkable": 40,
5
- "by_pattern": 31,
6
- "by_semantic_check": 9,
7
  "checked_against": {
8
  "odoo": "18.0",
9
  "python": "3.12",
10
  "typescript": "5.x",
11
  "javascript": "ES2023"
12
  },
13
- "corpus_sha256": "815ebc6467b1d939dcd18077685a6318fed2cd938d4a670628fd6950fdc1c6ff"
14
  }
 
1
  {
2
+ "built_utc": "2026-09-16T07:06:37.091305+00:00",
3
  "rules": 43,
4
  "machine_checkable": 40,
5
+ "by_pattern": 30,
6
+ "by_semantic_check": 10,
7
  "checked_against": {
8
  "odoo": "18.0",
9
  "python": "3.12",
10
  "typescript": "5.x",
11
  "javascript": "ES2023"
12
  },
13
+ "corpus_sha256": "a7e3adddd78657b2c19cab0616f95a93de4784d5a422089c3eae4552b0750f35"
14
  }
corpus/rules/odoo.json CHANGED
@@ -321,8 +321,7 @@
321
  "record-rules"
322
  ],
323
  "stack": "odoo",
324
- "violation_pattern": "\\.sudo\\(\\)\\s*\\.\\s*search\\(",
325
- "satisfied_pattern": "company_id",
326
  "source": "https://www.odoo.com/documentation/18.0/developer/reference/backend/security.html",
327
  "likelihood": "defect",
328
  "advisory": false,
 
321
  "record-rules"
322
  ],
323
  "stack": "odoo",
324
+ "semantic_check": "sudo_search_drops_a_real_rule",
 
325
  "source": "https://www.odoo.com/documentation/18.0/developer/reference/backend/security.html",
326
  "likelihood": "defect",
327
  "advisory": false,
fieldtest-openspp-2026-09.json CHANGED
@@ -15,6 +15,16 @@
15
  "multi_company": true
16
  },
17
  "hits": [
 
 
 
 
 
 
 
 
 
 
18
  {
19
  "rule": "odoo.ondelete-is-a-decision",
20
  "severity": "high",
@@ -39,7 +49,7 @@
39
  "rule": "odoo.sudo-bypasses-record-rules",
40
  "severity": "critical",
41
  "line": 89,
42
- "evidence": "partner = env[\"res.users\"].sudo().search([(\"login\", \"=\", api_key)], limit=1).partner_id",
43
  "suppressed": false,
44
  "advisory": false,
45
  "likelihood": "defect",
@@ -85,21 +95,11 @@
85
  "likelihood": "preference",
86
  "path": "spp_analytics/models/analytics_access.py"
87
  },
88
- {
89
- "rule": "odoo.sudo-bypasses-record-rules",
90
- "severity": "critical",
91
- "line": 300,
92
- "evidence": "child_areas = self.env[\"spp.area\"].sudo().search(domain) # nosemgrep: odoo-sudo-without-context",
93
- "suppressed": true,
94
- "advisory": false,
95
- "likelihood": "defect",
96
- "path": "spp_analytics/models/analytics_access.py"
97
- },
98
  {
99
  "rule": "odoo.sudo-bypasses-record-rules",
100
  "severity": "critical",
101
  "line": 88,
102
- "evidence": "for stat in stat_model.sudo().search([(\"active\", \"=\", True)]): # nosemgrep: odoo-sudo-without-conte",
103
  "suppressed": true,
104
  "advisory": false,
105
  "likelihood": "defect",
@@ -109,7 +109,7 @@
109
  "rule": "odoo.sudo-bypasses-record-rules",
110
  "severity": "critical",
111
  "line": 99,
112
- "evidence": "for var in var_model.sudo().search(domain): # nosemgrep: odoo-sudo-without-context",
113
  "suppressed": true,
114
  "advisory": false,
115
  "likelihood": "defect",
@@ -119,7 +119,7 @@
119
  "rule": "odoo.sudo-bypasses-record-rules",
120
  "severity": "critical",
121
  "line": 157,
122
- "evidence": "stat = stat_model.sudo().search([(\"name\", \"=\", stat_name)], limit=1) # nosemgrep: odoo-sudo-without",
123
  "suppressed": true,
124
  "advisory": false,
125
  "likelihood": "defect",
@@ -129,7 +129,7 @@
129
  "rule": "odoo.sudo-bypasses-record-rules",
130
  "severity": "critical",
131
  "line": 173,
132
- "evidence": "variable = var_model.sudo().search([(\"name\", \"=\", stat_name)], limit=1) # nosemgrep: odoo-sudo-with",
133
  "suppressed": true,
134
  "advisory": false,
135
  "likelihood": "defect",
@@ -139,7 +139,7 @@
139
  "rule": "odoo.sudo-bypasses-record-rules",
140
  "severity": "critical",
141
  "line": 245,
142
- "evidence": "statistic_records = statistic_model.sudo().search( # nosemgrep: odoo-sudo-without-context",
143
  "suppressed": true,
144
  "advisory": false,
145
  "likelihood": "defect",
@@ -148,48 +148,8 @@
148
  {
149
  "rule": "odoo.sudo-bypasses-record-rules",
150
  "severity": "critical",
151
- "line": 69,
152
- "evidence": ".sudo()",
153
- "suppressed": false,
154
- "advisory": false,
155
- "likelihood": "defect",
156
- "path": "spp_analytics/models/service_cache.py"
157
- },
158
- {
159
- "rule": "odoo.sudo-bypasses-record-rules",
160
- "severity": "critical",
161
- "line": 184,
162
- "evidence": ".sudo()",
163
- "suppressed": false,
164
- "advisory": false,
165
- "likelihood": "defect",
166
- "path": "spp_analytics/models/service_cache.py"
167
- },
168
- {
169
- "rule": "odoo.sudo-bypasses-record-rules",
170
- "severity": "critical",
171
- "line": 210,
172
- "evidence": "entries = self.env[\"spp.analytics.cache.entry\"].sudo().search([]) # nosemgrep: odoo-sudo-without-co",
173
- "suppressed": true,
174
- "advisory": false,
175
- "likelihood": "defect",
176
- "path": "spp_analytics/models/service_cache.py"
177
- },
178
- {
179
- "rule": "odoo.sudo-bypasses-record-rules",
180
- "severity": "critical",
181
- "line": 241,
182
- "evidence": ".sudo()",
183
- "suppressed": false,
184
- "advisory": false,
185
- "likelihood": "defect",
186
- "path": "spp_analytics/models/service_cache.py"
187
- },
188
- {
189
- "rule": "odoo.sudo-bypasses-record-rules",
190
- "severity": "critical",
191
- "line": 158,
192
- "evidence": "child_areas = self.env[\"spp.area\"].sudo().search(domain) # nosemgrep: odoo-sudo-without-context",
193
  "suppressed": true,
194
  "advisory": false,
195
  "likelihood": "defect",
@@ -198,43 +158,13 @@
198
  {
199
  "rule": "odoo.sudo-bypasses-record-rules",
200
  "severity": "critical",
201
- "line": 169,
202
- "evidence": ".sudo()",
203
- "suppressed": false,
204
- "advisory": false,
205
- "likelihood": "defect",
206
- "path": "spp_analytics/models/service_scope_resolver.py"
207
- },
208
- {
209
- "rule": "odoo.sudo-bypasses-record-rules",
210
- "severity": "critical",
211
- "line": 215,
212
- "evidence": "areas = self.env[\"spp.area\"].sudo().search([(\"tag_ids\", \"in\", tag_ids)]) # nosemgrep: odoo-sudo-wit",
213
  "suppressed": true,
214
  "advisory": false,
215
  "likelihood": "defect",
216
  "path": "spp_analytics/models/service_scope_resolver.py"
217
  },
218
- {
219
- "rule": "odoo.sudo-bypasses-record-rules",
220
- "severity": "critical",
221
- "line": 312,
222
- "evidence": ".sudo()",
223
- "suppressed": false,
224
- "advisory": false,
225
- "likelihood": "defect",
226
- "path": "spp_analytics/models/service_scope_resolver.py"
227
- },
228
- {
229
- "rule": "odoo.sudo-bypasses-record-rules",
230
- "severity": "critical",
231
- "line": 104,
232
- "evidence": ".sudo()",
233
- "suppressed": false,
234
- "advisory": false,
235
- "likelihood": "defect",
236
- "path": "spp_api_v2/middleware/auth.py"
237
- },
238
  {
239
  "rule": "odoo.prefer-sql-constraint",
240
  "severity": "high",
@@ -259,7 +189,7 @@
259
  "rule": "odoo.sudo-bypasses-record-rules",
260
  "severity": "critical",
261
  "line": 88,
262
- "evidence": "return self.sudo().search( # nosemgrep: odoo-sudo-without-context",
263
  "suppressed": true,
264
  "advisory": false,
265
  "likelihood": "defect",
@@ -298,18 +228,8 @@
298
  {
299
  "rule": "odoo.sudo-bypasses-record-rules",
300
  "severity": "critical",
301
- "line": 40,
302
- "evidence": ".sudo()",
303
- "suppressed": false,
304
- "advisory": false,
305
- "likelihood": "defect",
306
- "path": "spp_api_v2/services/group_service.py"
307
- },
308
- {
309
- "rule": "odoo.sudo-bypasses-record-rules",
310
- "severity": "critical",
311
- "line": 85,
312
- "evidence": "reg_ids = self.env[\"spp.registry.id\"].sudo().search(domain) # nosemgrep: odoo-sudo-without-context",
313
  "suppressed": true,
314
  "advisory": false,
315
  "likelihood": "defect",
@@ -318,59 +238,9 @@
318
  {
319
  "rule": "odoo.sudo-bypasses-record-rules",
320
  "severity": "critical",
321
- "line": 324,
322
- "evidence": ".sudo()",
323
- "suppressed": false,
324
- "advisory": false,
325
- "likelihood": "defect",
326
- "path": "spp_api_v2/services/group_service.py"
327
- },
328
- {
329
- "rule": "odoo.sudo-bypasses-record-rules",
330
- "severity": "critical",
331
- "line": 427,
332
- "evidence": ".sudo()",
333
- "suppressed": false,
334
- "advisory": false,
335
- "likelihood": "defect",
336
- "path": "spp_api_v2/services/group_service.py"
337
- },
338
- {
339
- "rule": "odoo.sudo-bypasses-record-rules",
340
- "severity": "critical",
341
- "line": 486,
342
- "evidence": ".sudo()",
343
- "suppressed": false,
344
- "advisory": false,
345
- "likelihood": "defect",
346
- "path": "spp_api_v2/services/group_service.py"
347
- },
348
- {
349
- "rule": "odoo.sudo-bypasses-record-rules",
350
- "severity": "critical",
351
- "line": 500,
352
- "evidence": ".sudo()",
353
- "suppressed": false,
354
- "advisory": false,
355
- "likelihood": "defect",
356
- "path": "spp_api_v2/services/group_service.py"
357
- },
358
- {
359
- "rule": "odoo.sudo-bypasses-record-rules",
360
- "severity": "critical",
361
- "line": 638,
362
- "evidence": ".sudo()",
363
- "suppressed": false,
364
- "advisory": false,
365
- "likelihood": "defect",
366
- "path": "spp_api_v2/services/group_service.py"
367
- },
368
- {
369
- "rule": "odoo.sudo-bypasses-record-rules",
370
- "severity": "critical",
371
- "line": 686,
372
- "evidence": ".sudo()",
373
- "suppressed": false,
374
  "advisory": false,
375
  "likelihood": "defect",
376
  "path": "spp_api_v2/services/group_service.py"
@@ -378,9 +248,9 @@
378
  {
379
  "rule": "odoo.sudo-bypasses-record-rules",
380
  "severity": "critical",
381
- "line": 807,
382
- "evidence": ".sudo()",
383
- "suppressed": false,
384
  "advisory": false,
385
  "likelihood": "defect",
386
  "path": "spp_api_v2/services/group_service.py"
@@ -388,9 +258,9 @@
388
  {
389
  "rule": "odoo.sudo-bypasses-record-rules",
390
  "severity": "critical",
391
- "line": 912,
392
- "evidence": ".sudo()",
393
- "suppressed": false,
394
  "advisory": false,
395
  "likelihood": "defect",
396
  "path": "spp_api_v2/services/group_service.py"
@@ -398,9 +268,9 @@
398
  {
399
  "rule": "odoo.sudo-bypasses-record-rules",
400
  "severity": "critical",
401
- "line": 1018,
402
- "evidence": ".sudo()",
403
- "suppressed": false,
404
  "advisory": false,
405
  "likelihood": "defect",
406
  "path": "spp_api_v2/services/group_service.py"
@@ -408,9 +278,9 @@
408
  {
409
  "rule": "odoo.sudo-bypasses-record-rules",
410
  "severity": "critical",
411
- "line": 1055,
412
- "evidence": ".sudo()",
413
- "suppressed": false,
414
  "advisory": false,
415
  "likelihood": "defect",
416
  "path": "spp_api_v2/services/group_service.py"
@@ -419,58 +289,18 @@
419
  "rule": "odoo.sudo-bypasses-record-rules",
420
  "severity": "critical",
421
  "line": 1199,
422
- "evidence": "memberships = self.env[\"spp.group.membership\"].sudo().search(domain, order=\"create_date desc\")",
423
- "suppressed": false,
424
  "advisory": false,
425
  "likelihood": "defect",
426
  "path": "spp_api_v2/services/group_service.py"
427
  },
428
- {
429
- "rule": "odoo.sudo-bypasses-record-rules",
430
- "severity": "critical",
431
- "line": 42,
432
- "evidence": ".sudo()",
433
- "suppressed": false,
434
- "advisory": false,
435
- "likelihood": "defect",
436
- "path": "spp_api_v2/services/individual_service.py"
437
- },
438
- {
439
- "rule": "odoo.sudo-bypasses-record-rules",
440
- "severity": "critical",
441
- "line": 85,
442
- "evidence": "reg_ids = self.env[\"spp.registry.id\"].sudo().search(domain)",
443
- "suppressed": false,
444
- "advisory": false,
445
- "likelihood": "defect",
446
- "path": "spp_api_v2/services/individual_service.py"
447
- },
448
- {
449
- "rule": "odoo.sudo-bypasses-record-rules",
450
- "severity": "critical",
451
- "line": 357,
452
- "evidence": ".sudo()",
453
- "suppressed": false,
454
- "advisory": false,
455
- "likelihood": "defect",
456
- "path": "spp_api_v2/services/individual_service.py"
457
- },
458
- {
459
- "rule": "odoo.sudo-bypasses-record-rules",
460
- "severity": "critical",
461
- "line": 454,
462
- "evidence": ".sudo()",
463
- "suppressed": false,
464
- "advisory": false,
465
- "likelihood": "defect",
466
- "path": "spp_api_v2/services/individual_service.py"
467
- },
468
  {
469
  "rule": "odoo.sudo-bypasses-record-rules",
470
  "severity": "critical",
471
  "line": 745,
472
- "evidence": "memberships = self.env[\"spp.group.membership\"].sudo().search(domain, limit=limit, order=\"start_date ",
473
- "suppressed": false,
474
  "advisory": false,
475
  "likelihood": "defect",
476
  "path": "spp_api_v2/services/individual_service.py"
@@ -478,9 +308,9 @@
478
  {
479
  "rule": "odoo.sudo-bypasses-record-rules",
480
  "severity": "critical",
481
- "line": 100,
482
- "evidence": "records = Partner.sudo().search(domain, limit=limit, offset=offset, order=order)",
483
- "suppressed": false,
484
  "advisory": false,
485
  "likelihood": "defect",
486
  "path": "spp_api_v2/services/search_service.py"
@@ -488,9 +318,9 @@
488
  {
489
  "rule": "odoo.sudo-bypasses-record-rules",
490
  "severity": "critical",
491
- "line": 139,
492
- "evidence": ".sudo()",
493
- "suppressed": false,
494
  "advisory": false,
495
  "likelihood": "defect",
496
  "path": "spp_api_v2/services/search_service.py"
@@ -498,8 +328,8 @@
498
  {
499
  "rule": "odoo.sudo-bypasses-record-rules",
500
  "severity": "critical",
501
- "line": 176,
502
- "evidence": "records = Partner.sudo().search(domain, limit=limit, order=order) # nosemgrep: odoo-sudo-without-co",
503
  "suppressed": true,
504
  "advisory": false,
505
  "likelihood": "defect",
@@ -508,9 +338,9 @@
508
  {
509
  "rule": "odoo.sudo-bypasses-record-rules",
510
  "severity": "critical",
511
- "line": 250,
512
- "evidence": ".sudo()",
513
- "suppressed": false,
514
  "advisory": false,
515
  "likelihood": "defect",
516
  "path": "spp_api_v2/services/search_service.py"
@@ -519,28 +349,8 @@
519
  "rule": "odoo.sudo-bypasses-record-rules",
520
  "severity": "critical",
521
  "line": 278,
522
- "evidence": "active_memberships = self.env[\"spp.group.membership\"].sudo().search([(\"is_ended\", \"=\", False)])",
523
- "suppressed": false,
524
- "advisory": false,
525
- "likelihood": "defect",
526
- "path": "spp_api_v2/services/search_service.py"
527
- },
528
- {
529
- "rule": "odoo.sudo-bypasses-record-rules",
530
- "severity": "critical",
531
- "line": 294,
532
- "evidence": ".sudo()",
533
- "suppressed": false,
534
- "advisory": false,
535
- "likelihood": "defect",
536
- "path": "spp_api_v2/services/search_service.py"
537
- },
538
- {
539
- "rule": "odoo.sudo-bypasses-record-rules",
540
- "severity": "critical",
541
- "line": 327,
542
- "evidence": ".sudo()",
543
- "suppressed": false,
544
  "advisory": false,
545
  "likelihood": "defect",
546
  "path": "spp_api_v2/services/search_service.py"
@@ -549,198 +359,28 @@
549
  "rule": "odoo.sudo-bypasses-record-rules",
550
  "severity": "critical",
551
  "line": 30,
552
- "evidence": ".sudo()",
553
- "suppressed": false,
554
- "advisory": false,
555
- "likelihood": "defect",
556
- "path": "spp_api_v2_cycles/services/cycle_service.py"
557
- },
558
- {
559
- "rule": "odoo.sudo-bypasses-record-rules",
560
- "severity": "critical",
561
- "line": 98,
562
- "evidence": ".sudo()",
563
- "suppressed": false,
564
- "advisory": false,
565
- "likelihood": "defect",
566
- "path": "spp_api_v2_cycles/services/cycle_service.py"
567
- },
568
- {
569
- "rule": "odoo.sudo-bypasses-record-rules",
570
- "severity": "critical",
571
- "line": 33,
572
- "evidence": ".sudo() # nosemgrep: odoo-sudo-on-sensitive-models",
573
  "suppressed": true,
574
  "advisory": false,
575
  "likelihood": "defect",
576
- "path": "spp_api_v2_entitlements/services/entitlement_service.py"
577
- },
578
- {
579
- "rule": "odoo.sudo-bypasses-record-rules",
580
- "severity": "critical",
581
- "line": 47,
582
- "evidence": ".sudo()",
583
- "suppressed": false,
584
- "advisory": false,
585
- "likelihood": "defect",
586
- "path": "spp_api_v2_entitlements/services/entitlement_service.py"
587
  },
588
  {
589
  "rule": "odoo.sudo-bypasses-record-rules",
590
  "severity": "critical",
591
- "line": 141,
592
- "evidence": ".sudo() # nosemgrep: odoo-sudo-on-sensitive-models",
593
  "suppressed": true,
594
  "advisory": false,
595
  "likelihood": "defect",
596
- "path": "spp_api_v2_entitlements/services/entitlement_service.py"
597
- },
598
- {
599
- "rule": "odoo.sudo-bypasses-record-rules",
600
- "severity": "critical",
601
- "line": 211,
602
- "evidence": ".sudo()",
603
- "suppressed": false,
604
- "advisory": false,
605
- "likelihood": "defect",
606
- "path": "spp_api_v2_entitlements/services/entitlement_service.py"
607
- },
608
- {
609
- "rule": "odoo.sudo-bypasses-record-rules",
610
- "severity": "critical",
611
- "line": 232,
612
- "evidence": ".sudo()",
613
- "suppressed": false,
614
- "advisory": false,
615
- "likelihood": "defect",
616
- "path": "spp_api_v2_entitlements/services/entitlement_service.py"
617
- },
618
- {
619
- "rule": "odoo.sudo-bypasses-record-rules",
620
- "severity": "critical",
621
- "line": 75,
622
- "evidence": "incident = env[\"spp.hazard.incident\"].sudo().search([(\"code\", \"=\", request.incident_code)], limit=1)",
623
- "suppressed": false,
624
- "advisory": false,
625
- "likelihood": "defect",
626
- "path": "spp_api_v2_gis/routers/geofence.py"
627
- },
628
- {
629
- "rule": "odoo.sudo-bypasses-record-rules",
630
- "severity": "critical",
631
- "line": 339,
632
- "evidence": "report = env[\"spp.gis.report\"].sudo().search([(\"code\", \"=\", report_code)], limit=1)",
633
- "suppressed": false,
634
- "advisory": false,
635
- "likelihood": "defect",
636
- "path": "spp_api_v2_gis/routers/ogc_features.py"
637
- },
638
- {
639
- "rule": "odoo.sudo-bypasses-record-rules",
640
- "severity": "critical",
641
- "line": 101,
642
- "evidence": "reports = self.env[\"spp.gis.report\"].sudo().search([(\"active\", \"=\", True)])",
643
- "suppressed": false,
644
- "advisory": false,
645
- "likelihood": "defect",
646
- "path": "spp_api_v2_gis/services/export_service.py"
647
- },
648
- {
649
- "rule": "odoo.sudo-bypasses-record-rules",
650
- "severity": "critical",
651
- "line": 217,
652
- "evidence": "parent_area = self.env[\"spp.area\"].sudo().search([(\"code\", \"=\", parent_area_code)], limit=1)",
653
- "suppressed": false,
654
- "advisory": false,
655
- "likelihood": "defect",
656
- "path": "spp_api_v2_gis/services/layers_service.py"
657
- },
658
- {
659
- "rule": "odoo.sudo-bypasses-record-rules",
660
- "severity": "critical",
661
- "line": 224,
662
- "evidence": "matching_areas = self.env[\"spp.area\"].sudo().search([(\"geo_polygon\", \"gis_intersects\", bbox_geojson)",
663
- "suppressed": false,
664
- "advisory": false,
665
- "likelihood": "defect",
666
- "path": "spp_api_v2_gis/services/layers_service.py"
667
- },
668
- {
669
- "rule": "odoo.sudo-bypasses-record-rules",
670
- "severity": "critical",
671
- "line": 417,
672
- "evidence": "report = self.env[\"spp.gis.report\"].sudo().search([(\"code\", \"=\", layer_id)], limit=1)",
673
- "suppressed": false,
674
- "advisory": false,
675
- "likelihood": "defect",
676
- "path": "spp_api_v2_gis/services/layers_service.py"
677
- },
678
- {
679
- "rule": "odoo.sudo-bypasses-record-rules",
680
- "severity": "critical",
681
- "line": 485,
682
- "evidence": "report = self.env[\"spp.gis.report\"].sudo().search([(\"code\", \"=\", report_code)], limit=1)",
683
- "suppressed": false,
684
- "advisory": false,
685
- "likelihood": "defect",
686
- "path": "spp_api_v2_gis/services/layers_service.py"
687
- },
688
- {
689
- "rule": "odoo.sudo-bypasses-record-rules",
690
- "severity": "critical",
691
- "line": 492,
692
- "evidence": ".sudo()",
693
- "suppressed": false,
694
- "advisory": false,
695
- "likelihood": "defect",
696
- "path": "spp_api_v2_gis/services/layers_service.py"
697
- },
698
- {
699
- "rule": "odoo.sudo-bypasses-record-rules",
700
- "severity": "critical",
701
- "line": 708,
702
- "evidence": "areas = self.env[\"spp.area\"].sudo().search([(\"code\", \"in\", area_codes)])",
703
- "suppressed": false,
704
- "advisory": false,
705
- "likelihood": "defect",
706
- "path": "spp_api_v2_gis/services/layers_service.py"
707
- },
708
- {
709
- "rule": "odoo.sudo-bypasses-record-rules",
710
- "severity": "critical",
711
- "line": 513,
712
- "evidence": "report = self.env[\"spp.gis.report\"].sudo().search([(\"code\", \"=\", report_code)], limit=1)",
713
- "suppressed": false,
714
- "advisory": false,
715
- "likelihood": "defect",
716
- "path": "spp_api_v2_gis/services/ogc_service.py"
717
- },
718
- {
719
- "rule": "odoo.sudo-bypasses-record-rules",
720
- "severity": "critical",
721
- "line": 169,
722
- "evidence": ".sudo()",
723
- "suppressed": false,
724
- "advisory": false,
725
- "likelihood": "defect",
726
- "path": "spp_api_v2_gis/services/qml_template_service.py"
727
- },
728
- {
729
- "rule": "odoo.sudo-bypasses-record-rules",
730
- "severity": "critical",
731
- "line": 31,
732
- "evidence": ".sudo()",
733
- "suppressed": false,
734
- "advisory": false,
735
- "likelihood": "defect",
736
  "path": "spp_api_v2_products/services/product_category_service.py"
737
  },
738
  {
739
  "rule": "odoo.sudo-bypasses-record-rules",
740
  "severity": "critical",
741
- "line": 62,
742
- "evidence": ".sudo()",
743
- "suppressed": false,
744
  "advisory": false,
745
  "likelihood": "defect",
746
  "path": "spp_api_v2_products/services/product_category_service.py"
@@ -748,9 +388,9 @@
748
  {
749
  "rule": "odoo.sudo-bypasses-record-rules",
750
  "severity": "critical",
751
- "line": 31,
752
- "evidence": ".sudo()",
753
- "suppressed": false,
754
  "advisory": false,
755
  "likelihood": "defect",
756
  "path": "spp_api_v2_products/services/product_service.py"
@@ -758,9 +398,9 @@
758
  {
759
  "rule": "odoo.sudo-bypasses-record-rules",
760
  "severity": "critical",
761
- "line": 43,
762
- "evidence": ".sudo()",
763
- "suppressed": false,
764
  "advisory": false,
765
  "likelihood": "defect",
766
  "path": "spp_api_v2_products/services/product_service.py"
@@ -768,9 +408,9 @@
768
  {
769
  "rule": "odoo.sudo-bypasses-record-rules",
770
  "severity": "critical",
771
- "line": 97,
772
- "evidence": ".sudo()",
773
- "suppressed": false,
774
  "advisory": false,
775
  "likelihood": "defect",
776
  "path": "spp_api_v2_products/services/product_service.py"
@@ -778,19 +418,19 @@
778
  {
779
  "rule": "odoo.sudo-bypasses-record-rules",
780
  "severity": "critical",
781
- "line": 30,
782
- "evidence": ".sudo()",
783
- "suppressed": false,
784
  "advisory": false,
785
  "likelihood": "defect",
786
- "path": "spp_api_v2_products/services/uom_service.py"
787
  },
788
  {
789
  "rule": "odoo.sudo-bypasses-record-rules",
790
  "severity": "critical",
791
- "line": 63,
792
- "evidence": ".sudo()",
793
- "suppressed": false,
794
  "advisory": false,
795
  "likelihood": "defect",
796
  "path": "spp_api_v2_products/services/uom_service.py"
@@ -798,29 +438,29 @@
798
  {
799
  "rule": "odoo.sudo-bypasses-record-rules",
800
  "severity": "critical",
801
- "line": 51,
802
- "evidence": ".sudo()",
803
- "suppressed": false,
804
  "advisory": false,
805
  "likelihood": "defect",
806
- "path": "spp_api_v2_programs/services/program_membership_service.py"
807
  },
808
  {
809
  "rule": "odoo.sudo-bypasses-record-rules",
810
  "severity": "critical",
811
- "line": 94,
812
- "evidence": "records = Membership.sudo().search( # nosemgrep: odoo-sudo-without-context",
813
  "suppressed": true,
814
  "advisory": false,
815
  "likelihood": "defect",
816
- "path": "spp_api_v2_programs/services/program_membership_service.py"
817
  },
818
  {
819
  "rule": "odoo.sudo-bypasses-record-rules",
820
  "severity": "critical",
821
- "line": 119,
822
- "evidence": ".sudo()",
823
- "suppressed": false,
824
  "advisory": false,
825
  "likelihood": "defect",
826
  "path": "spp_api_v2_programs/services/program_membership_service.py"
@@ -828,9 +468,9 @@
828
  {
829
  "rule": "odoo.sudo-bypasses-record-rules",
830
  "severity": "critical",
831
- "line": 135,
832
- "evidence": ".sudo()",
833
- "suppressed": false,
834
  "advisory": false,
835
  "likelihood": "defect",
836
  "path": "spp_api_v2_programs/services/program_membership_service.py"
@@ -838,9 +478,9 @@
838
  {
839
  "rule": "odoo.sudo-bypasses-record-rules",
840
  "severity": "critical",
841
- "line": 158,
842
- "evidence": ".sudo()",
843
- "suppressed": false,
844
  "advisory": false,
845
  "likelihood": "defect",
846
  "path": "spp_api_v2_programs/services/program_membership_service.py"
@@ -848,9 +488,9 @@
848
  {
849
  "rule": "odoo.sudo-bypasses-record-rules",
850
  "severity": "critical",
851
- "line": 385,
852
- "evidence": ".sudo()",
853
- "suppressed": false,
854
  "advisory": false,
855
  "likelihood": "defect",
856
  "path": "spp_api_v2_programs/services/program_membership_service.py"
@@ -858,9 +498,9 @@
858
  {
859
  "rule": "odoo.sudo-bypasses-record-rules",
860
  "severity": "critical",
861
- "line": 48,
862
- "evidence": ".sudo()",
863
- "suppressed": false,
864
  "advisory": false,
865
  "likelihood": "defect",
866
  "path": "spp_api_v2_programs/services/program_service.py"
@@ -868,8 +508,8 @@
868
  {
869
  "rule": "odoo.sudo-bypasses-record-rules",
870
  "severity": "critical",
871
- "line": 66,
872
- "evidence": ".sudo() # nosemgrep: odoo-sudo-on-sensitive-models",
873
  "suppressed": true,
874
  "advisory": false,
875
  "likelihood": "defect",
@@ -879,8 +519,8 @@
879
  "rule": "odoo.sudo-bypasses-record-rules",
880
  "severity": "critical",
881
  "line": 527,
882
- "evidence": "currency = env[\"res.currency\"].sudo().search([(\"name\", \"=\", request.currency_code)], limit=1)",
883
- "suppressed": false,
884
  "advisory": false,
885
  "likelihood": "defect",
886
  "path": "spp_api_v2_simulation/routers/scenario.py"
@@ -888,9 +528,9 @@
888
  {
889
  "rule": "odoo.sudo-bypasses-record-rules",
890
  "severity": "critical",
891
- "line": 538,
892
- "evidence": ".sudo()",
893
- "suppressed": false,
894
  "advisory": false,
895
  "likelihood": "defect",
896
  "path": "spp_api_v2_simulation/routers/scenario.py"
@@ -899,22 +539,12 @@
899
  "rule": "odoo.sudo-bypasses-record-rules",
900
  "severity": "critical",
901
  "line": 93,
902
- "evidence": "partner_ids = self.env[\"res.partner\"].sudo().search(domain).ids",
903
- "suppressed": false,
904
  "advisory": false,
905
  "likelihood": "defect",
906
  "path": "spp_api_v2_simulation/services/analytics_api_service.py"
907
  },
908
- {
909
- "rule": "odoo.sudo-bypasses-record-rules",
910
- "severity": "critical",
911
- "line": 32,
912
- "evidence": ".sudo()",
913
- "suppressed": false,
914
- "advisory": false,
915
- "likelihood": "defect",
916
- "path": "spp_api_v2_simulation/services/simulation_api_service.py"
917
- },
918
  {
919
  "rule": "odoo.ondelete-is-a-decision",
920
  "severity": "high",
@@ -938,9 +568,9 @@
938
  {
939
  "rule": "odoo.sudo-bypasses-record-rules",
940
  "severity": "critical",
941
- "line": 205,
942
- "evidence": ".sudo()",
943
- "suppressed": false,
944
  "advisory": false,
945
  "likelihood": "defect",
946
  "path": "spp_approval/models/approval_review.py"
@@ -949,8 +579,8 @@
949
  "rule": "odoo.sudo-bypasses-record-rules",
950
  "severity": "critical",
951
  "line": 217,
952
- "evidence": "manager_definitions = self.env[\"spp.approval.definition\"].sudo().search([(\"approval_type\", \"=\", \"man",
953
- "suppressed": false,
954
  "advisory": false,
955
  "likelihood": "defect",
956
  "path": "spp_approval/models/approval_review.py"
@@ -1075,22 +705,12 @@
1075
  "likelihood": "unmeasured",
1076
  "path": "spp_attachment_av_scan/models/av_scanner_backend.py"
1077
  },
1078
- {
1079
- "rule": "odoo.sudo-bypasses-record-rules",
1080
- "severity": "critical",
1081
- "line": 337,
1082
- "evidence": ".sudo()",
1083
- "suppressed": false,
1084
- "advisory": false,
1085
- "likelihood": "defect",
1086
- "path": "spp_attachment_av_scan/models/ir_attachment.py"
1087
- },
1088
  {
1089
  "rule": "odoo.sudo-bypasses-record-rules",
1090
  "severity": "critical",
1091
  "line": 108,
1092
- "evidence": "entity = request.env[model_name].sudo().search([(\"id\", \"=\", model_id)], limit=1)",
1093
- "suppressed": false,
1094
  "advisory": false,
1095
  "likelihood": "defect",
1096
  "path": "spp_attendance/controllers/controllers.py"
@@ -1099,92 +719,12 @@
1099
  "rule": "odoo.sudo-bypasses-record-rules",
1100
  "severity": "critical",
1101
  "line": 111,
1102
- "evidence": "model_ids = request.env[model_name].sudo().search([])",
1103
- "suppressed": false,
1104
- "advisory": false,
1105
- "likelihood": "defect",
1106
- "path": "spp_attendance/controllers/controllers.py"
1107
- },
1108
- {
1109
- "rule": "odoo.sudo-bypasses-record-rules",
1110
- "severity": "critical",
1111
- "line": 215,
1112
- "evidence": "req.env[\"spp.attendance.subscriber\"].sudo().search([(\"person_identifier\", \"=\", person_id)], limit=1)",
1113
- "suppressed": false,
1114
- "advisory": false,
1115
- "likelihood": "defect",
1116
- "path": "spp_attendance/controllers/controllers.py"
1117
- },
1118
- {
1119
- "rule": "odoo.sudo-bypasses-record-rules",
1120
- "severity": "critical",
1121
- "line": 367,
1122
- "evidence": "attendance_list_id = req.env[\"spp.attendance.list\"].sudo().search([(\"id\", \"=\", attendance_id)], limi",
1123
- "suppressed": false,
1124
- "advisory": false,
1125
- "likelihood": "defect",
1126
- "path": "spp_attendance/controllers/controllers.py"
1127
- },
1128
- {
1129
- "rule": "odoo.sudo-bypasses-record-rules",
1130
- "severity": "critical",
1131
- "line": 411,
1132
- "evidence": "attendance_list_ids = req.env[\"spp.attendance.list\"].sudo().search([(\"id\", \"in\", ids)])",
1133
- "suppressed": false,
1134
- "advisory": false,
1135
- "likelihood": "defect",
1136
- "path": "spp_attendance/controllers/controllers.py"
1137
- },
1138
- {
1139
- "rule": "odoo.sudo-bypasses-record-rules",
1140
- "severity": "critical",
1141
- "line": 436,
1142
- "evidence": "req.env[\"spp.attendance.subscriber\"].sudo().search([(\"person_identifier\", \"=\", person_identifier)], ",
1143
- "suppressed": false,
1144
- "advisory": false,
1145
- "likelihood": "defect",
1146
- "path": "spp_attendance/controllers/controllers.py"
1147
- },
1148
- {
1149
- "rule": "odoo.sudo-bypasses-record-rules",
1150
- "severity": "critical",
1151
- "line": 593,
1152
- "evidence": "attendance_type_ids = req.env[\"spp.attendance.type\"].sudo().search([])",
1153
- "suppressed": false,
1154
- "advisory": false,
1155
- "likelihood": "defect",
1156
- "path": "spp_attendance/controllers/controllers.py"
1157
- },
1158
- {
1159
- "rule": "odoo.sudo-bypasses-record-rules",
1160
- "severity": "critical",
1161
- "line": 625,
1162
- "evidence": "attendance_location_ids = req.env[\"spp.attendance.location\"].sudo().search([])",
1163
- "suppressed": false,
1164
- "advisory": false,
1165
- "likelihood": "defect",
1166
- "path": "spp_attendance/controllers/controllers.py"
1167
- },
1168
- {
1169
- "rule": "odoo.sudo-bypasses-record-rules",
1170
- "severity": "critical",
1171
- "line": 706,
1172
- "evidence": ".sudo()",
1173
- "suppressed": false,
1174
  "advisory": false,
1175
  "likelihood": "defect",
1176
  "path": "spp_attendance/controllers/controllers.py"
1177
  },
1178
- {
1179
- "rule": "odoo.sudo-bypasses-record-rules",
1180
- "severity": "critical",
1181
- "line": 134,
1182
- "evidence": "set(self.env[\"spp.attendance.list\"].sudo().search(present_domain).mapped(\"attendance_date\"))",
1183
- "suppressed": false,
1184
- "advisory": false,
1185
- "likelihood": "defect",
1186
- "path": "spp_attendance/models/attendance_subscriber.py"
1187
- },
1188
  {
1189
  "rule": "odoo.recursion-check-on-hierarchy",
1190
  "severity": "high",
@@ -1195,16 +735,6 @@
1195
  "likelihood": "unmeasured",
1196
  "path": "spp_audit/models/spp_audit_rule.py"
1197
  },
1198
- {
1199
- "rule": "odoo.sudo-bypasses-record-rules",
1200
- "severity": "critical",
1201
- "line": 245,
1202
- "evidence": "return self.env[\"spp.audit.rule\"].sudo().search(domain) # nosemgrep: odoo-sudo-without-context",
1203
- "suppressed": true,
1204
- "advisory": false,
1205
- "likelihood": "defect",
1206
- "path": "spp_audit/models/spp_audit_rule.py"
1207
- },
1208
  {
1209
  "rule": "odoo.depends-must-not-name-unread-fields",
1210
  "severity": "medium",
@@ -1219,8 +749,8 @@
1219
  "rule": "odoo.sudo-bypasses-record-rules",
1220
  "severity": "critical",
1221
  "line": 41,
1222
- "evidence": "theme_menu = env[\"ir.ui.menu\"].sudo().search([(\"name\", \"ilike\", \"Theme Store\")], limit=1)",
1223
- "suppressed": false,
1224
  "advisory": false,
1225
  "likelihood": "defect",
1226
  "path": "spp_branding_kit/__init__.py"
@@ -1655,16 +1185,6 @@
1655
  "likelihood": "preference",
1656
  "path": "spp_dci_client/models/data_source.py"
1657
  },
1658
- {
1659
- "rule": "odoo.sudo-bypasses-record-rules",
1660
- "severity": "critical",
1661
- "line": 70,
1662
- "evidence": "crvs_sender = env[\"spp.dci.crvs.sender\"].sudo().search([(\"sender_id\", \"=\", sender_id)], limit=1)",
1663
- "suppressed": false,
1664
- "advisory": false,
1665
- "likelihood": "defect",
1666
- "path": "spp_dci_client_crvs/middleware/signature.py"
1667
- },
1668
  {
1669
  "rule": "odoo.prefer-sql-constraint",
1670
  "severity": "high",
@@ -1685,16 +1205,6 @@
1685
  "likelihood": "preference",
1686
  "path": "spp_dci_client_crvs/models/crvs_sender.py"
1687
  },
1688
- {
1689
- "rule": "odoo.sudo-bypasses-record-rules",
1690
- "severity": "critical",
1691
- "line": 70,
1692
- "evidence": "dr_sender = env[\"spp.dci.dr.sender\"].sudo().search([(\"sender_id\", \"=\", sender_id)], limit=1)",
1693
- "suppressed": false,
1694
- "advisory": false,
1695
- "likelihood": "defect",
1696
- "path": "spp_dci_client_dr/middleware/signature.py"
1697
- },
1698
  {
1699
  "rule": "odoo.constrains-must-list-every-field-read",
1700
  "severity": "critical",
@@ -1725,36 +1235,6 @@
1725
  "likelihood": "preference",
1726
  "path": "spp_dci_client_dr/models/dr_sender.py"
1727
  },
1728
- {
1729
- "rule": "odoo.sudo-bypasses-record-rules",
1730
- "severity": "critical",
1731
- "line": 199,
1732
- "evidence": ".sudo()",
1733
- "suppressed": false,
1734
- "advisory": false,
1735
- "likelihood": "defect",
1736
- "path": "spp_dci_client_dr/routers/callback.py"
1737
- },
1738
- {
1739
- "rule": "odoo.sudo-bypasses-record-rules",
1740
- "severity": "critical",
1741
- "line": 216,
1742
- "evidence": ".sudo()",
1743
- "suppressed": false,
1744
- "advisory": false,
1745
- "likelihood": "defect",
1746
- "path": "spp_dci_client_dr/routers/callback.py"
1747
- },
1748
- {
1749
- "rule": "odoo.sudo-bypasses-record-rules",
1750
- "severity": "critical",
1751
- "line": 70,
1752
- "evidence": "ibr_sender = env[\"spp.dci.ibr.sender\"].sudo().search([(\"sender_id\", \"=\", sender_id)], limit=1)",
1753
- "suppressed": false,
1754
- "advisory": false,
1755
- "likelihood": "defect",
1756
- "path": "spp_dci_client_ibr/middleware/signature.py"
1757
- },
1758
  {
1759
  "rule": "odoo.prefer-sql-constraint",
1760
  "severity": "high",
@@ -1765,16 +1245,6 @@
1765
  "likelihood": "preference",
1766
  "path": "spp_dci_client_ibr/models/ibr_sender.py"
1767
  },
1768
- {
1769
- "rule": "odoo.sudo-bypasses-record-rules",
1770
- "severity": "critical",
1771
- "line": 166,
1772
- "evidence": ".sudo()",
1773
- "suppressed": false,
1774
- "advisory": false,
1775
- "likelihood": "defect",
1776
- "path": "spp_dci_client_ibr/routers/callback.py"
1777
- },
1778
  {
1779
  "rule": "odoo.stored-compute-needs-complete-depends",
1780
  "severity": "critical",
@@ -1785,46 +1255,6 @@
1785
  "likelihood": "construct",
1786
  "path": "spp_dci_client_sr/models/sr_record.py"
1787
  },
1788
- {
1789
- "rule": "odoo.sudo-bypasses-record-rules",
1790
- "severity": "critical",
1791
- "line": 262,
1792
- "evidence": ".sudo()",
1793
- "suppressed": false,
1794
- "advisory": false,
1795
- "likelihood": "defect",
1796
- "path": "spp_dci_client_sr/routers/callback.py"
1797
- },
1798
- {
1799
- "rule": "odoo.sudo-bypasses-record-rules",
1800
- "severity": "critical",
1801
- "line": 279,
1802
- "evidence": ".sudo()",
1803
- "suppressed": false,
1804
- "advisory": false,
1805
- "likelihood": "defect",
1806
- "path": "spp_dci_client_sr/routers/callback.py"
1807
- },
1808
- {
1809
- "rule": "odoo.sudo-bypasses-record-rules",
1810
- "severity": "critical",
1811
- "line": 205,
1812
- "evidence": ".sudo()",
1813
- "suppressed": false,
1814
- "advisory": false,
1815
- "likelihood": "defect",
1816
- "path": "spp_dci_server/middleware/rate_limit.py"
1817
- },
1818
- {
1819
- "rule": "odoo.sudo-bypasses-record-rules",
1820
- "severity": "critical",
1821
- "line": 278,
1822
- "evidence": "client = env[\"spp.api.client\"].sudo().search([(\"client_id\", \"=\", client_id), (\"active\", \"=\", True)],",
1823
- "suppressed": false,
1824
- "advisory": false,
1825
- "likelihood": "defect",
1826
- "path": "spp_dci_server/middleware/signature.py"
1827
- },
1828
  {
1829
  "rule": "odoo.prefer-sql-constraint",
1830
  "severity": "high",
@@ -1855,96 +1285,6 @@
1855
  "likelihood": "construct",
1856
  "path": "spp_dci_server/models/subscription.py"
1857
  },
1858
- {
1859
- "rule": "odoo.sudo-bypasses-record-rules",
1860
- "severity": "critical",
1861
- "line": 571,
1862
- "evidence": "self.env[\"spp.dci.subscription\"].sudo().search([(\"original_transaction_id\", \"=\", transaction_id)])",
1863
- "suppressed": false,
1864
- "advisory": false,
1865
- "likelihood": "defect",
1866
- "path": "spp_dci_server/models/transaction.py"
1867
- },
1868
- {
1869
- "rule": "odoo.sudo-bypasses-record-rules",
1870
- "severity": "critical",
1871
- "line": 684,
1872
- "evidence": "referenced_txn = self.env[\"spp.dci.transaction\"].sudo().search(domain, limit=1)",
1873
- "suppressed": false,
1874
- "advisory": false,
1875
- "likelihood": "defect",
1876
- "path": "spp_dci_server/models/transaction.py"
1877
- },
1878
- {
1879
- "rule": "odoo.sudo-bypasses-record-rules",
1880
- "severity": "critical",
1881
- "line": 262,
1882
- "evidence": "sender = env[\"spp.dci.sender.registry\"].sudo().search([(\"sender_id\", \"=\", verified_sender_id)], limi",
1883
- "suppressed": false,
1884
- "advisory": false,
1885
- "likelihood": "defect",
1886
- "path": "spp_dci_server/routers/async_router.py"
1887
- },
1888
- {
1889
- "rule": "odoo.sudo-bypasses-record-rules",
1890
- "severity": "critical",
1891
- "line": 426,
1892
- "evidence": "sender = env[\"spp.dci.sender.registry\"].sudo().search([(\"sender_id\", \"=\", verified_sender_id)], limi",
1893
- "suppressed": false,
1894
- "advisory": false,
1895
- "likelihood": "defect",
1896
- "path": "spp_dci_server/routers/async_router.py"
1897
- },
1898
- {
1899
- "rule": "odoo.sudo-bypasses-record-rules",
1900
- "severity": "critical",
1901
- "line": 438,
1902
- "evidence": ".sudo()",
1903
- "suppressed": false,
1904
- "advisory": false,
1905
- "likelihood": "defect",
1906
- "path": "spp_dci_server/routers/async_router.py"
1907
- },
1908
- {
1909
- "rule": "odoo.sudo-bypasses-record-rules",
1910
- "severity": "critical",
1911
- "line": 573,
1912
- "evidence": "transactions = env[\"spp.dci.transaction\"].sudo().search(domain, limit=1)",
1913
- "suppressed": false,
1914
- "advisory": false,
1915
- "likelihood": "defect",
1916
- "path": "spp_dci_server/routers/async_router.py"
1917
- },
1918
- {
1919
- "rule": "odoo.sudo-bypasses-record-rules",
1920
- "severity": "critical",
1921
- "line": 304,
1922
- "evidence": "sender = env[\"spp.dci.sender.registry\"].sudo().search([(\"sender_id\", \"=\", sender_id)], limit=1)",
1923
- "suppressed": false,
1924
- "advisory": false,
1925
- "likelihood": "defect",
1926
- "path": "spp_dci_server/routers/bulk_upload.py"
1927
- },
1928
- {
1929
- "rule": "odoo.sudo-bypasses-record-rules",
1930
- "severity": "critical",
1931
- "line": 277,
1932
- "evidence": "sender = env[\"spp.dci.sender.registry\"].sudo().search([(\"sender_id\", \"=\", sender_id_str)], limit=1)",
1933
- "suppressed": false,
1934
- "advisory": false,
1935
- "likelihood": "defect",
1936
- "path": "spp_dci_server/routers/callbacks.py"
1937
- },
1938
- {
1939
- "rule": "odoo.sudo-bypasses-record-rules",
1940
- "severity": "critical",
1941
- "line": 140,
1942
- "evidence": ".sudo()",
1943
- "suppressed": false,
1944
- "advisory": false,
1945
- "likelihood": "defect",
1946
- "path": "spp_dci_server/routers/receipt.py"
1947
- },
1948
  {
1949
  "rule": "odoo.ondelete-is-a-decision",
1950
  "severity": "high",
@@ -2365,16 +1705,6 @@
2365
  "likelihood": "unmeasured",
2366
  "path": "spp_farmer_registry_cr/details/cr_land_parcel.py"
2367
  },
2368
- {
2369
- "rule": "odoo.sudo-bypasses-record-rules",
2370
- "severity": "critical",
2371
- "line": 659,
2372
- "evidence": "records = self.env[\"spp.service.point\"].sudo().search([(\"name\", \"in\", names)]) # nosemgrep",
2373
- "suppressed": true,
2374
- "advisory": false,
2375
- "likelihood": "defect",
2376
- "path": "spp_farmer_registry_demo/models/farmer_demo_generator.py"
2377
- },
2378
  {
2379
  "rule": "odoo.sql-needs-query-parameters",
2380
  "severity": "critical",
@@ -2388,21 +1718,11 @@
2388
  {
2389
  "rule": "odoo.sudo-bypasses-record-rules",
2390
  "severity": "critical",
2391
- "line": 856,
2392
- "evidence": "sp_records = self.env[\"spp.service.point\"].sudo().search([]) # nosemgrep",
2393
  "suppressed": true,
2394
  "advisory": false,
2395
  "likelihood": "defect",
2396
- "path": "spp_farmer_registry_demo/models/seeded_farm_generator.py"
2397
- },
2398
- {
2399
- "rule": "odoo.sudo-bypasses-record-rules",
2400
- "severity": "critical",
2401
- "line": 73,
2402
- "evidence": ".sudo()",
2403
- "suppressed": false,
2404
- "advisory": false,
2405
- "likelihood": "defect",
2406
  "path": "spp_gis/models/base.py"
2407
  },
2408
  {
@@ -2635,6 +1955,26 @@
2635
  "likelihood": "preference",
2636
  "path": "spp_grm_cel/models/grm_escalation_rule.py"
2637
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2638
  {
2639
  "rule": "odoo.constrains-must-list-every-field-read",
2640
  "severity": "critical",
@@ -2745,26 +2085,6 @@
2745
  "likelihood": "preference",
2746
  "path": "spp_key_management/models/encryption_key.py"
2747
  },
2748
- {
2749
- "rule": "odoo.sudo-bypasses-record-rules",
2750
- "severity": "critical",
2751
- "line": 322,
2752
- "evidence": ".sudo()",
2753
- "suppressed": false,
2754
- "advisory": false,
2755
- "likelihood": "defect",
2756
- "path": "spp_key_management/models/key_provider_database.py"
2757
- },
2758
- {
2759
- "rule": "odoo.sudo-bypasses-record-rules",
2760
- "severity": "critical",
2761
- "line": 348,
2762
- "evidence": ".sudo()",
2763
- "suppressed": false,
2764
- "advisory": false,
2765
- "likelihood": "defect",
2766
- "path": "spp_key_management/models/key_provider_database.py"
2767
- },
2768
  {
2769
  "rule": "odoo.float-comparison-needs-float-utils",
2770
  "severity": "high",
@@ -2895,6 +2215,16 @@
2895
  "likelihood": "preference",
2896
  "path": "spp_programs/models/cycle.py"
2897
  },
 
 
 
 
 
 
 
 
 
 
2898
  {
2899
  "rule": "odoo.prefer-sql-constraint",
2900
  "severity": "high",
@@ -2995,6 +2325,16 @@
2995
  "likelihood": "preference",
2996
  "path": "spp_programs/models/programs.py"
2997
  },
 
 
 
 
 
 
 
 
 
 
2998
  {
2999
  "rule": "odoo.check-company-on-relations",
3000
  "severity": "critical",
@@ -3010,7 +2350,7 @@
3010
  "severity": "critical",
3011
  "line": 77,
3012
  "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
3013
- "suppressed": false,
3014
  "advisory": false,
3015
  "likelihood": "unmeasured",
3016
  "path": "spp_programs/models/accounting/fund_report.py"
@@ -3018,8 +2358,8 @@
3018
  {
3019
  "rule": "odoo.sudo-bypasses-record-rules",
3020
  "severity": "critical",
3021
- "line": 257,
3022
- "evidence": ".sudo() # nosemgrep: odoo-sudo-on-sensitive-models",
3023
  "suppressed": true,
3024
  "advisory": false,
3025
  "likelihood": "defect",
@@ -3029,8 +2369,8 @@
3029
  "rule": "odoo.sudo-bypasses-record-rules",
3030
  "severity": "critical",
3031
  "line": 306,
3032
- "evidence": "compliant_partners = self.env[\"res.partner\"].sudo().search(domain)",
3033
- "suppressed": false,
3034
  "advisory": false,
3035
  "likelihood": "defect",
3036
  "path": "spp_programs/models/managers/compliance_manager.py"
@@ -3038,8 +2378,8 @@
3038
  {
3039
  "rule": "odoo.sudo-bypasses-record-rules",
3040
  "severity": "critical",
3041
- "line": 38,
3042
- "evidence": ".sudo() # nosemgrep: odoo-sudo-on-sensitive-models",
3043
  "suppressed": true,
3044
  "advisory": false,
3045
  "likelihood": "defect",
@@ -3048,9 +2388,9 @@
3048
  {
3049
  "rule": "odoo.sudo-bypasses-record-rules",
3050
  "severity": "critical",
3051
- "line": 34,
3052
- "evidence": ".sudo()",
3053
- "suppressed": false,
3054
  "advisory": false,
3055
  "likelihood": "defect",
3056
  "path": "spp_programs/models/managers/entitlement_manager_default_compliance.py"
@@ -3110,21 +2450,11 @@
3110
  "severity": "critical",
3111
  "line": 51,
3112
  "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
3113
- "suppressed": false,
3114
  "advisory": false,
3115
  "likelihood": "unmeasured",
3116
  "path": "spp_programs/models/managers/pagination_utils.py"
3117
  },
3118
- {
3119
- "rule": "odoo.sudo-bypasses-record-rules",
3120
- "severity": "critical",
3121
- "line": 167,
3122
- "evidence": ".sudo()",
3123
- "suppressed": false,
3124
- "advisory": false,
3125
- "likelihood": "defect",
3126
- "path": "spp_programs/models/managers/payment_manager.py"
3127
- },
3128
  {
3129
  "rule": "odoo.constrains-must-list-every-field-read",
3130
  "severity": "critical",
@@ -3188,9 +2518,9 @@
3188
  {
3189
  "rule": "odoo.sudo-bypasses-record-rules",
3190
  "severity": "critical",
3191
- "line": 266,
3192
- "evidence": ".sudo() # system table ordinary users cannot read; _trigger creates as sudo anyway",
3193
- "suppressed": false,
3194
  "advisory": false,
3195
  "likelihood": "defect",
3196
  "path": "spp_registry/models/group_membership.py"
@@ -3209,7 +2539,7 @@
3209
  "rule": "odoo.sudo-bypasses-record-rules",
3210
  "severity": "critical",
3211
  "line": 127,
3212
- "evidence": "clash = self.sudo().search(domain, limit=1) # nosemgrep: odoo-sudo-without-context",
3213
  "suppressed": true,
3214
  "advisory": false,
3215
  "likelihood": "defect",
@@ -3548,12 +2878,42 @@
3548
  {
3549
  "rule": "odoo.sudo-bypasses-record-rules",
3550
  "severity": "critical",
3551
- "line": 480,
3552
- "evidence": ".sudo()",
3553
- "suppressed": false,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3554
  "advisory": false,
3555
  "likelihood": "defect",
3556
- "path": "spp_studio_api_v2/routers/studio.py"
3557
  },
3558
  {
3559
  "rule": "odoo.constrains-must-list-every-field-read",
@@ -3568,9 +2928,9 @@
3568
  {
3569
  "rule": "odoo.sudo-bypasses-record-rules",
3570
  "severity": "critical",
3571
- "line": 312,
3572
- "evidence": ".sudo()",
3573
- "suppressed": false,
3574
  "advisory": false,
3575
  "likelihood": "defect",
3576
  "path": "spp_studio_change_requests/models/studio_change_request_type.py"
@@ -3609,8 +2969,8 @@
3609
  "rule": "odoo.sudo-bypasses-record-rules",
3610
  "severity": "critical",
3611
  "line": 424,
3612
- "evidence": "wizard_model = self.env[\"ir.model\"].sudo().search([(\"model\", \"=\", \"spp.event.data.entry.wizard\")], l",
3613
- "suppressed": false,
3614
  "advisory": false,
3615
  "likelihood": "defect",
3616
  "path": "spp_studio_events/models/studio_event_type.py"
 
15
  "multi_company": true
16
  },
17
  "hits": [
18
+ {
19
+ "rule": "odoo.sudo-bypasses-record-rules",
20
+ "severity": "critical",
21
+ "line": 65,
22
+ "evidence": "sudo().search_count() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
23
+ "suppressed": false,
24
+ "advisory": false,
25
+ "likelihood": "defect",
26
+ "path": "endpoint_route_handler/models/endpoint_route_handler.py"
27
+ },
28
  {
29
  "rule": "odoo.ondelete-is-a-decision",
30
  "severity": "high",
 
49
  "rule": "odoo.sudo-bypasses-record-rules",
50
  "severity": "critical",
51
  "line": 89,
52
+ "evidence": "sudo().search() on res.users runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
53
  "suppressed": false,
54
  "advisory": false,
55
  "likelihood": "defect",
 
95
  "likelihood": "preference",
96
  "path": "spp_analytics/models/analytics_access.py"
97
  },
 
 
 
 
 
 
 
 
 
 
98
  {
99
  "rule": "odoo.sudo-bypasses-record-rules",
100
  "severity": "critical",
101
  "line": 88,
102
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
103
  "suppressed": true,
104
  "advisory": false,
105
  "likelihood": "defect",
 
109
  "rule": "odoo.sudo-bypasses-record-rules",
110
  "severity": "critical",
111
  "line": 99,
112
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
113
  "suppressed": true,
114
  "advisory": false,
115
  "likelihood": "defect",
 
119
  "rule": "odoo.sudo-bypasses-record-rules",
120
  "severity": "critical",
121
  "line": 157,
122
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
123
  "suppressed": true,
124
  "advisory": false,
125
  "likelihood": "defect",
 
129
  "rule": "odoo.sudo-bypasses-record-rules",
130
  "severity": "critical",
131
  "line": 173,
132
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
133
  "suppressed": true,
134
  "advisory": false,
135
  "likelihood": "defect",
 
139
  "rule": "odoo.sudo-bypasses-record-rules",
140
  "severity": "critical",
141
  "line": 245,
142
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
143
  "suppressed": true,
144
  "advisory": false,
145
  "likelihood": "defect",
 
148
  {
149
  "rule": "odoo.sudo-bypasses-record-rules",
150
  "severity": "critical",
151
+ "line": 168,
152
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  "suppressed": true,
154
  "advisory": false,
155
  "likelihood": "defect",
 
158
  {
159
  "rule": "odoo.sudo-bypasses-record-rules",
160
  "severity": "critical",
161
+ "line": 311,
162
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
 
 
 
 
 
 
 
 
 
 
163
  "suppressed": true,
164
  "advisory": false,
165
  "likelihood": "defect",
166
  "path": "spp_analytics/models/service_scope_resolver.py"
167
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  {
169
  "rule": "odoo.prefer-sql-constraint",
170
  "severity": "high",
 
189
  "rule": "odoo.sudo-bypasses-record-rules",
190
  "severity": "critical",
191
  "line": 88,
192
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
193
  "suppressed": true,
194
  "advisory": false,
195
  "likelihood": "defect",
 
228
  {
229
  "rule": "odoo.sudo-bypasses-record-rules",
230
  "severity": "critical",
231
+ "line": 637,
232
+ "evidence": "sudo().search() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
 
 
 
 
 
 
 
 
 
 
233
  "suppressed": true,
234
  "advisory": false,
235
  "likelihood": "defect",
 
238
  {
239
  "rule": "odoo.sudo-bypasses-record-rules",
240
  "severity": "critical",
241
+ "line": 1017,
242
+ "evidence": "sudo().search() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
243
+ "suppressed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  "advisory": false,
245
  "likelihood": "defect",
246
  "path": "spp_api_v2/services/group_service.py"
 
248
  {
249
  "rule": "odoo.sudo-bypasses-record-rules",
250
  "severity": "critical",
251
+ "line": 1157,
252
+ "evidence": "sudo().search_count() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
253
+ "suppressed": true,
254
  "advisory": false,
255
  "likelihood": "defect",
256
  "path": "spp_api_v2/services/group_service.py"
 
258
  {
259
  "rule": "odoo.sudo-bypasses-record-rules",
260
  "severity": "critical",
261
+ "line": 1165,
262
+ "evidence": "sudo().search_count() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
263
+ "suppressed": true,
264
  "advisory": false,
265
  "likelihood": "defect",
266
  "path": "spp_api_v2/services/group_service.py"
 
268
  {
269
  "rule": "odoo.sudo-bypasses-record-rules",
270
  "severity": "critical",
271
+ "line": 1169,
272
+ "evidence": "sudo().search_count() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
273
+ "suppressed": true,
274
  "advisory": false,
275
  "likelihood": "defect",
276
  "path": "spp_api_v2/services/group_service.py"
 
278
  {
279
  "rule": "odoo.sudo-bypasses-record-rules",
280
  "severity": "critical",
281
+ "line": 1174,
282
+ "evidence": "sudo().search_count() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
283
+ "suppressed": true,
284
  "advisory": false,
285
  "likelihood": "defect",
286
  "path": "spp_api_v2/services/group_service.py"
 
289
  "rule": "odoo.sudo-bypasses-record-rules",
290
  "severity": "critical",
291
  "line": 1199,
292
+ "evidence": "sudo().search() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
293
+ "suppressed": true,
294
  "advisory": false,
295
  "likelihood": "defect",
296
  "path": "spp_api_v2/services/group_service.py"
297
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  {
299
  "rule": "odoo.sudo-bypasses-record-rules",
300
  "severity": "critical",
301
  "line": 745,
302
+ "evidence": "sudo().search() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
303
+ "suppressed": true,
304
  "advisory": false,
305
  "likelihood": "defect",
306
  "path": "spp_api_v2/services/individual_service.py"
 
308
  {
309
  "rule": "odoo.sudo-bypasses-record-rules",
310
  "severity": "critical",
311
+ "line": 79,
312
+ "evidence": "sudo().search_count() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
313
+ "suppressed": true,
314
  "advisory": false,
315
  "likelihood": "defect",
316
  "path": "spp_api_v2/services/search_service.py"
 
318
  {
319
  "rule": "odoo.sudo-bypasses-record-rules",
320
  "severity": "critical",
321
+ "line": 100,
322
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
323
+ "suppressed": true,
324
  "advisory": false,
325
  "likelihood": "defect",
326
  "path": "spp_api_v2/services/search_service.py"
 
328
  {
329
  "rule": "odoo.sudo-bypasses-record-rules",
330
  "severity": "critical",
331
+ "line": 160,
332
+ "evidence": "sudo().search_count() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
333
  "suppressed": true,
334
  "advisory": false,
335
  "likelihood": "defect",
 
338
  {
339
  "rule": "odoo.sudo-bypasses-record-rules",
340
  "severity": "critical",
341
+ "line": 176,
342
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
343
+ "suppressed": true,
344
  "advisory": false,
345
  "likelihood": "defect",
346
  "path": "spp_api_v2/services/search_service.py"
 
349
  "rule": "odoo.sudo-bypasses-record-rules",
350
  "severity": "critical",
351
  "line": 278,
352
+ "evidence": "sudo().search() on spp.group.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
353
+ "suppressed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  "advisory": false,
355
  "likelihood": "defect",
356
  "path": "spp_api_v2/services/search_service.py"
 
359
  "rule": "odoo.sudo-bypasses-record-rules",
360
  "severity": "critical",
361
  "line": 30,
362
+ "evidence": "sudo().search() on product.category runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
363
  "suppressed": true,
364
  "advisory": false,
365
  "likelihood": "defect",
366
+ "path": "spp_api_v2_products/services/product_category_service.py"
 
 
 
 
 
 
 
 
 
 
367
  },
368
  {
369
  "rule": "odoo.sudo-bypasses-record-rules",
370
  "severity": "critical",
371
+ "line": 54,
372
+ "evidence": "sudo().search_count() on product.category runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
373
  "suppressed": true,
374
  "advisory": false,
375
  "likelihood": "defect",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  "path": "spp_api_v2_products/services/product_category_service.py"
377
  },
378
  {
379
  "rule": "odoo.sudo-bypasses-record-rules",
380
  "severity": "critical",
381
+ "line": 61,
382
+ "evidence": "sudo().search() on product.category runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
383
+ "suppressed": true,
384
  "advisory": false,
385
  "likelihood": "defect",
386
  "path": "spp_api_v2_products/services/product_category_service.py"
 
388
  {
389
  "rule": "odoo.sudo-bypasses-record-rules",
390
  "severity": "critical",
391
+ "line": 30,
392
+ "evidence": "sudo().search() on product.template runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
393
+ "suppressed": true,
394
  "advisory": false,
395
  "likelihood": "defect",
396
  "path": "spp_api_v2_products/services/product_service.py"
 
398
  {
399
  "rule": "odoo.sudo-bypasses-record-rules",
400
  "severity": "critical",
401
+ "line": 42,
402
+ "evidence": "sudo().search() on product.template runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
403
+ "suppressed": true,
404
  "advisory": false,
405
  "likelihood": "defect",
406
  "path": "spp_api_v2_products/services/product_service.py"
 
408
  {
409
  "rule": "odoo.sudo-bypasses-record-rules",
410
  "severity": "critical",
411
+ "line": 89,
412
+ "evidence": "sudo().search_count() on product.template runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
413
+ "suppressed": true,
414
  "advisory": false,
415
  "likelihood": "defect",
416
  "path": "spp_api_v2_products/services/product_service.py"
 
418
  {
419
  "rule": "odoo.sudo-bypasses-record-rules",
420
  "severity": "critical",
421
+ "line": 96,
422
+ "evidence": "sudo().search() on product.template runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
423
+ "suppressed": true,
424
  "advisory": false,
425
  "likelihood": "defect",
426
+ "path": "spp_api_v2_products/services/product_service.py"
427
  },
428
  {
429
  "rule": "odoo.sudo-bypasses-record-rules",
430
  "severity": "critical",
431
+ "line": 29,
432
+ "evidence": "sudo().search() on uom.uom runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
433
+ "suppressed": true,
434
  "advisory": false,
435
  "likelihood": "defect",
436
  "path": "spp_api_v2_products/services/uom_service.py"
 
438
  {
439
  "rule": "odoo.sudo-bypasses-record-rules",
440
  "severity": "critical",
441
+ "line": 56,
442
+ "evidence": "sudo().search_count() on uom.uom runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
443
+ "suppressed": true,
444
  "advisory": false,
445
  "likelihood": "defect",
446
+ "path": "spp_api_v2_products/services/uom_service.py"
447
  },
448
  {
449
  "rule": "odoo.sudo-bypasses-record-rules",
450
  "severity": "critical",
451
+ "line": 62,
452
+ "evidence": "sudo().search() on uom.uom runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
453
  "suppressed": true,
454
  "advisory": false,
455
  "likelihood": "defect",
456
+ "path": "spp_api_v2_products/services/uom_service.py"
457
  },
458
  {
459
  "rule": "odoo.sudo-bypasses-record-rules",
460
  "severity": "critical",
461
+ "line": 93,
462
+ "evidence": "sudo().search_count() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
463
+ "suppressed": true,
464
  "advisory": false,
465
  "likelihood": "defect",
466
  "path": "spp_api_v2_programs/services/program_membership_service.py"
 
468
  {
469
  "rule": "odoo.sudo-bypasses-record-rules",
470
  "severity": "critical",
471
+ "line": 94,
472
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
473
+ "suppressed": true,
474
  "advisory": false,
475
  "likelihood": "defect",
476
  "path": "spp_api_v2_programs/services/program_membership_service.py"
 
478
  {
479
  "rule": "odoo.sudo-bypasses-record-rules",
480
  "severity": "critical",
481
+ "line": 134,
482
+ "evidence": "sudo().search() on spp.program.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
483
+ "suppressed": true,
484
  "advisory": false,
485
  "likelihood": "defect",
486
  "path": "spp_api_v2_programs/services/program_membership_service.py"
 
488
  {
489
  "rule": "odoo.sudo-bypasses-record-rules",
490
  "severity": "critical",
491
+ "line": 157,
492
+ "evidence": "sudo().search() on spp.program.membership runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
493
+ "suppressed": true,
494
  "advisory": false,
495
  "likelihood": "defect",
496
  "path": "spp_api_v2_programs/services/program_membership_service.py"
 
498
  {
499
  "rule": "odoo.sudo-bypasses-record-rules",
500
  "severity": "critical",
501
+ "line": 47,
502
+ "evidence": "sudo().search() on spp.program.id runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
503
+ "suppressed": true,
504
  "advisory": false,
505
  "likelihood": "defect",
506
  "path": "spp_api_v2_programs/services/program_service.py"
 
508
  {
509
  "rule": "odoo.sudo-bypasses-record-rules",
510
  "severity": "critical",
511
+ "line": 65,
512
+ "evidence": "sudo().search() on spp.program runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
513
  "suppressed": true,
514
  "advisory": false,
515
  "likelihood": "defect",
 
519
  "rule": "odoo.sudo-bypasses-record-rules",
520
  "severity": "critical",
521
  "line": 527,
522
+ "evidence": "sudo().search() on res.currency runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
523
+ "suppressed": true,
524
  "advisory": false,
525
  "likelihood": "defect",
526
  "path": "spp_api_v2_simulation/routers/scenario.py"
 
528
  {
529
  "rule": "odoo.sudo-bypasses-record-rules",
530
  "severity": "critical",
531
+ "line": 537,
532
+ "evidence": "sudo().search() on spp.program runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
533
+ "suppressed": true,
534
  "advisory": false,
535
  "likelihood": "defect",
536
  "path": "spp_api_v2_simulation/routers/scenario.py"
 
539
  "rule": "odoo.sudo-bypasses-record-rules",
540
  "severity": "critical",
541
  "line": 93,
542
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
543
+ "suppressed": true,
544
  "advisory": false,
545
  "likelihood": "defect",
546
  "path": "spp_api_v2_simulation/services/analytics_api_service.py"
547
  },
 
 
 
 
 
 
 
 
 
 
548
  {
549
  "rule": "odoo.ondelete-is-a-decision",
550
  "severity": "high",
 
568
  {
569
  "rule": "odoo.sudo-bypasses-record-rules",
570
  "severity": "critical",
571
+ "line": 204,
572
+ "evidence": "sudo().search() on spp.approval.definition runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
573
+ "suppressed": true,
574
  "advisory": false,
575
  "likelihood": "defect",
576
  "path": "spp_approval/models/approval_review.py"
 
579
  "rule": "odoo.sudo-bypasses-record-rules",
580
  "severity": "critical",
581
  "line": 217,
582
+ "evidence": "sudo().search() on spp.approval.definition runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
583
+ "suppressed": true,
584
  "advisory": false,
585
  "likelihood": "defect",
586
  "path": "spp_approval/models/approval_review.py"
 
705
  "likelihood": "unmeasured",
706
  "path": "spp_attachment_av_scan/models/av_scanner_backend.py"
707
  },
 
 
 
 
 
 
 
 
 
 
708
  {
709
  "rule": "odoo.sudo-bypasses-record-rules",
710
  "severity": "critical",
711
  "line": 108,
712
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
713
+ "suppressed": true,
714
  "advisory": false,
715
  "likelihood": "defect",
716
  "path": "spp_attendance/controllers/controllers.py"
 
719
  "rule": "odoo.sudo-bypasses-record-rules",
720
  "severity": "critical",
721
  "line": 111,
722
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
723
+ "suppressed": true,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  "advisory": false,
725
  "likelihood": "defect",
726
  "path": "spp_attendance/controllers/controllers.py"
727
  },
 
 
 
 
 
 
 
 
 
 
728
  {
729
  "rule": "odoo.recursion-check-on-hierarchy",
730
  "severity": "high",
 
735
  "likelihood": "unmeasured",
736
  "path": "spp_audit/models/spp_audit_rule.py"
737
  },
 
 
 
 
 
 
 
 
 
 
738
  {
739
  "rule": "odoo.depends-must-not-name-unread-fields",
740
  "severity": "medium",
 
749
  "rule": "odoo.sudo-bypasses-record-rules",
750
  "severity": "critical",
751
  "line": 41,
752
+ "evidence": "sudo().search() on ir.ui.menu runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
753
+ "suppressed": true,
754
  "advisory": false,
755
  "likelihood": "defect",
756
  "path": "spp_branding_kit/__init__.py"
 
1185
  "likelihood": "preference",
1186
  "path": "spp_dci_client/models/data_source.py"
1187
  },
 
 
 
 
 
 
 
 
 
 
1188
  {
1189
  "rule": "odoo.prefer-sql-constraint",
1190
  "severity": "high",
 
1205
  "likelihood": "preference",
1206
  "path": "spp_dci_client_crvs/models/crvs_sender.py"
1207
  },
 
 
 
 
 
 
 
 
 
 
1208
  {
1209
  "rule": "odoo.constrains-must-list-every-field-read",
1210
  "severity": "critical",
 
1235
  "likelihood": "preference",
1236
  "path": "spp_dci_client_dr/models/dr_sender.py"
1237
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1238
  {
1239
  "rule": "odoo.prefer-sql-constraint",
1240
  "severity": "high",
 
1245
  "likelihood": "preference",
1246
  "path": "spp_dci_client_ibr/models/ibr_sender.py"
1247
  },
 
 
 
 
 
 
 
 
 
 
1248
  {
1249
  "rule": "odoo.stored-compute-needs-complete-depends",
1250
  "severity": "critical",
 
1255
  "likelihood": "construct",
1256
  "path": "spp_dci_client_sr/models/sr_record.py"
1257
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1258
  {
1259
  "rule": "odoo.prefer-sql-constraint",
1260
  "severity": "high",
 
1285
  "likelihood": "construct",
1286
  "path": "spp_dci_server/models/subscription.py"
1287
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1288
  {
1289
  "rule": "odoo.ondelete-is-a-decision",
1290
  "severity": "high",
 
1705
  "likelihood": "unmeasured",
1706
  "path": "spp_farmer_registry_cr/details/cr_land_parcel.py"
1707
  },
 
 
 
 
 
 
 
 
 
 
1708
  {
1709
  "rule": "odoo.sql-needs-query-parameters",
1710
  "severity": "critical",
 
1718
  {
1719
  "rule": "odoo.sudo-bypasses-record-rules",
1720
  "severity": "critical",
1721
+ "line": 72,
1722
+ "evidence": "sudo().search() on ir.ui.view runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
1723
  "suppressed": true,
1724
  "advisory": false,
1725
  "likelihood": "defect",
 
 
 
 
 
 
 
 
 
 
1726
  "path": "spp_gis/models/base.py"
1727
  },
1728
  {
 
1955
  "likelihood": "preference",
1956
  "path": "spp_grm_cel/models/grm_escalation_rule.py"
1957
  },
1958
+ {
1959
+ "rule": "odoo.sudo-bypasses-record-rules",
1960
+ "severity": "critical",
1961
+ "line": 687,
1962
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
1963
+ "suppressed": true,
1964
+ "advisory": false,
1965
+ "likelihood": "defect",
1966
+ "path": "spp_grm_cel/models/grm_escalation_rule.py"
1967
+ },
1968
+ {
1969
+ "rule": "odoo.sudo-bypasses-record-rules",
1970
+ "severity": "critical",
1971
+ "line": 238,
1972
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
1973
+ "suppressed": true,
1974
+ "advisory": false,
1975
+ "likelihood": "defect",
1976
+ "path": "spp_grm_cel/models/grm_routing_rule.py"
1977
+ },
1978
  {
1979
  "rule": "odoo.constrains-must-list-every-field-read",
1980
  "severity": "critical",
 
2085
  "likelihood": "preference",
2086
  "path": "spp_key_management/models/encryption_key.py"
2087
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2088
  {
2089
  "rule": "odoo.float-comparison-needs-float-utils",
2090
  "severity": "high",
 
2215
  "likelihood": "preference",
2216
  "path": "spp_programs/models/cycle.py"
2217
  },
2218
+ {
2219
+ "rule": "odoo.sudo-bypasses-record-rules",
2220
+ "severity": "critical",
2221
+ "line": 1266,
2222
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2223
+ "suppressed": true,
2224
+ "advisory": false,
2225
+ "likelihood": "defect",
2226
+ "path": "spp_programs/models/cycle.py"
2227
+ },
2228
  {
2229
  "rule": "odoo.prefer-sql-constraint",
2230
  "severity": "high",
 
2325
  "likelihood": "preference",
2326
  "path": "spp_programs/models/programs.py"
2327
  },
2328
+ {
2329
+ "rule": "odoo.sudo-bypasses-record-rules",
2330
+ "severity": "critical",
2331
+ "line": 308,
2332
+ "evidence": "sudo().search_count() on ir.mail_server runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2333
+ "suppressed": true,
2334
+ "advisory": false,
2335
+ "likelihood": "defect",
2336
+ "path": "spp_programs/models/programs.py"
2337
+ },
2338
  {
2339
  "rule": "odoo.check-company-on-relations",
2340
  "severity": "critical",
 
2350
  "severity": "critical",
2351
  "line": 77,
2352
  "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
2353
+ "suppressed": true,
2354
  "advisory": false,
2355
  "likelihood": "unmeasured",
2356
  "path": "spp_programs/models/accounting/fund_report.py"
 
2358
  {
2359
  "rule": "odoo.sudo-bypasses-record-rules",
2360
  "severity": "critical",
2361
+ "line": 256,
2362
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2363
  "suppressed": true,
2364
  "advisory": false,
2365
  "likelihood": "defect",
 
2369
  "rule": "odoo.sudo-bypasses-record-rules",
2370
  "severity": "critical",
2371
  "line": 306,
2372
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2373
+ "suppressed": true,
2374
  "advisory": false,
2375
  "likelihood": "defect",
2376
  "path": "spp_programs/models/managers/compliance_manager.py"
 
2378
  {
2379
  "rule": "odoo.sudo-bypasses-record-rules",
2380
  "severity": "critical",
2381
+ "line": 37,
2382
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2383
  "suppressed": true,
2384
  "advisory": false,
2385
  "likelihood": "defect",
 
2388
  {
2389
  "rule": "odoo.sudo-bypasses-record-rules",
2390
  "severity": "critical",
2391
+ "line": 33,
2392
+ "evidence": "sudo().search() on res.partner runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2393
+ "suppressed": true,
2394
  "advisory": false,
2395
  "likelihood": "defect",
2396
  "path": "spp_programs/models/managers/entitlement_manager_default_compliance.py"
 
2450
  "severity": "critical",
2451
  "line": 51,
2452
  "evidence": "the query handed to cr.execute() is built by an f-string -- pass the values as the second argument so the driver quotes them",
2453
+ "suppressed": true,
2454
  "advisory": false,
2455
  "likelihood": "unmeasured",
2456
  "path": "spp_programs/models/managers/pagination_utils.py"
2457
  },
 
 
 
 
 
 
 
 
 
 
2458
  {
2459
  "rule": "odoo.constrains-must-list-every-field-read",
2460
  "severity": "critical",
 
2518
  {
2519
  "rule": "odoo.sudo-bypasses-record-rules",
2520
  "severity": "critical",
2521
+ "line": 265,
2522
+ "evidence": "sudo().search() on ir.cron.trigger runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2523
+ "suppressed": true,
2524
  "advisory": false,
2525
  "likelihood": "defect",
2526
  "path": "spp_registry/models/group_membership.py"
 
2539
  "rule": "odoo.sudo-bypasses-record-rules",
2540
  "severity": "critical",
2541
  "line": 127,
2542
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2543
  "suppressed": true,
2544
  "advisory": false,
2545
  "likelihood": "defect",
 
2878
  {
2879
  "rule": "odoo.sudo-bypasses-record-rules",
2880
  "severity": "critical",
2881
+ "line": 107,
2882
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2883
+ "suppressed": true,
2884
+ "advisory": false,
2885
+ "likelihood": "defect",
2886
+ "path": "spp_studio_api_v2/services/variable_value_service.py"
2887
+ },
2888
+ {
2889
+ "rule": "odoo.sudo-bypasses-record-rules",
2890
+ "severity": "critical",
2891
+ "line": 174,
2892
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2893
+ "suppressed": true,
2894
+ "advisory": false,
2895
+ "likelihood": "defect",
2896
+ "path": "spp_studio_api_v2/services/variable_value_service.py"
2897
+ },
2898
+ {
2899
+ "rule": "odoo.sudo-bypasses-record-rules",
2900
+ "severity": "critical",
2901
+ "line": 225,
2902
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2903
+ "suppressed": true,
2904
+ "advisory": false,
2905
+ "likelihood": "defect",
2906
+ "path": "spp_studio_api_v2/services/variable_value_service.py"
2907
+ },
2908
+ {
2909
+ "rule": "odoo.sudo-bypasses-record-rules",
2910
+ "severity": "critical",
2911
+ "line": 276,
2912
+ "evidence": "sudo().search() runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2913
+ "suppressed": true,
2914
  "advisory": false,
2915
  "likelihood": "defect",
2916
+ "path": "spp_studio_api_v2/services/variable_value_service.py"
2917
  },
2918
  {
2919
  "rule": "odoo.constrains-must-list-every-field-read",
 
2928
  {
2929
  "rule": "odoo.sudo-bypasses-record-rules",
2930
  "severity": "critical",
2931
+ "line": 311,
2932
+ "evidence": "sudo().search() on ir.model.fields runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2933
+ "suppressed": true,
2934
  "advisory": false,
2935
  "likelihood": "defect",
2936
  "path": "spp_studio_change_requests/models/studio_change_request_type.py"
 
2969
  "rule": "odoo.sudo-bypasses-record-rules",
2970
  "severity": "critical",
2971
  "line": 424,
2972
+ "evidence": "sudo().search() on ir.model runs with record rules off -- ir.rule filtering, including multi-company isolation, does not apply and the result looks correct in single-company testing",
2973
+ "suppressed": true,
2974
  "advisory": false,
2975
  "likelihood": "defect",
2976
  "path": "spp_studio_events/models/studio_event_type.py"