DBax127 Claude Opus 5 commited on
Commit
5aba0e1
·
1 Parent(s): 921d283

context: the version floor is a gap, not a finding

Browse files

Two of this layer's findings never depended on the answer:

this module targets Odoo 17.0; the corpus was checked against 18.0
tsconfig has strict off, so the compile-time half is not enforced

Both are true and both were in the wrong slot. A finding says something in THIS
ANSWER is wrong or unproven. These say the corpus was verified against a
different series, and that this project has not finished turning strict on --
facts about the corpus and the tree, which hold whatever the answer says.

As findings they were a floor. Every Odoo module off the newest series and every
project mid-migration drew a WARN on every run, so PASS was unreachable for most
real users and the WARN stopped carrying information -- the same argument that
keeps `check` silent about a file that fits.

The deciding detail: 0 of the 15 Odoo rules carries an odoo_versions field, so
_version_applies -- the check that names a retrieved rule which genuinely does
not hold on this series -- has never fired and cannot. The blanket warning was
standing in for per-rule data that does not exist yet, and spending a verdict on
it. That per-rule check keeps its finding; only the blanket one moves.

Both are gaps now, under "Not checked", and the layer reports UNKNOWN when they
were all it had. Contextual confidence reads none, which is the claim the WARN
was making without taking the verdict with it.

WHAT IT DOES TO THE MEASUREMENT, INCLUDING THE UNCOMFORTABLE PART

Arithmetic on the rows in eval-check-2026-09.json -- not a second run, and
labelled as such in both documents -- moves five of the twenty WARNs to PASS:

fsm_job.py WARN x5 -> WARN x5
fsm_visit.py WARN x5 -> PASS x5
load_prices.py WARN x5 -> WARN x5
partners.ts WARN x5 -> WARN x5
board.js REJECT x5 -> REJECT x5

All five are the truncated file: 24% of it never sent, a 435-character answer
naming 1 of its 19 symbols, and across five runs the context fact was the only
thing any layer found. That file now reads PASS.

It was never being caught for being unreviewed. It was being caught for its
manifest, and a right verdict for an unrelated reason is an accident rather than
a safety property. Removing the accident is what makes the real gap visible:
truncation is not evidence-bearing. A review of the first 6,000 characters of a
7,929-character file should not reach PASS without the verdict saying so, and
today the only thing that says so is a line of prose above the answer.

Not fixed here. It belongs with the per-rule version data and the by-symbol
review, not with a floor that happened to mask it.

The eval is not re-run: Ollama is down on this machine and a projection is not a
measurement. Both documents say which numbers were measured and which were
derived.

4 tests: the mismatch as a gap, the per-rule check that still warns, strict off
as a gap and strict on as something checked. 340 total.

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

Files changed (4) hide show
  1. ARCHITECTURE.md +56 -7
  2. README.md +30 -13
  3. bin/layers.py +26 -10
  4. tests/test_layers.py +38 -3
ARCHITECTURE.md CHANGED
@@ -695,7 +695,8 @@ running the command could reach them. They are now `bin/subject.py`, so
695
  copy of them that drifts away quietly.
696
 
697
  **Five files, five runs each, 25 reviews.** `subjects/` is committed — one file
698
- per route `check` can take, one of them too long to fit.
 
699
 
700
  | | |
701
  |---|---|
@@ -708,12 +709,12 @@ per route `check` can take, one of them too long to fit.
708
 
709
  **The stability number is not the one `ask` reports.** 12/12 on `ask` means the
710
  model landed in the same place five times running. Here, four of the five files
711
- carry a context finding decided before generation starts — a module on Odoo 17.0
712
- against a corpus checked against 18.0, a `tsconfig` with `strict` off — so their
713
- verdict cannot move whatever the model writes. `subjects/` was built that way on
714
- purpose, to give the layer `ask` can never run something real to do. The
715
- consequence is worth stating plainly: that WARN is an environment fact, not a
716
- review, and every module not on the corpus's checked series has it.
717
 
718
  **The checkers never look at the file.** They run over the answer. On the Python
719
  and TypeScript subjects the model reproduced the file, so the file's own defects
@@ -741,6 +742,54 @@ found.
741
  sampled from anybody's production tree. `nexa eval --check <path>` takes a real
742
  one; `subjects/README.md` says what the committed set does and does not buy.
743
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
744
  ## 4. Performance traps, all found by measurement
745
 
746
  Within four instrumented runs the report showed the 7B at ~10 tok/s against a
 
695
  copy of them that drifts away quietly.
696
 
697
  **Five files, five runs each, 25 reviews.** `subjects/` is committed — one file
698
+ per route `check` can take, one of them too long to fit. Measured before the
699
+ context floor was demoted; 3.27 says what that does to the table.
700
 
701
  | | |
702
  |---|---|
 
709
 
710
  **The stability number is not the one `ask` reports.** 12/12 on `ask` means the
711
  model landed in the same place five times running. Here, four of the five files
712
+ carried a context finding decided before generation starts — a module on Odoo
713
+ 17.0 against a corpus checked against 18.0, a `tsconfig` with `strict` off — so
714
+ their verdict could not move whatever the model wrote, and 5 of 5 was mostly a
715
+ constant. `subjects/` was built that way on purpose, to give the layer `ask` can
716
+ never run something real to do; what it exposed instead was that the layer was
717
+ answering with the wrong kind of statement. Both are gaps now (3.27).
718
 
719
  **The checkers never look at the file.** They run over the answer. On the Python
720
  and TypeScript subjects the model reproduced the file, so the file's own defects
 
742
  sampled from anybody's production tree. `nexa eval --check <path>` takes a real
743
  one; `subjects/README.md` says what the committed set does and does not buy.
744
 
745
+ ### 3.27 The context floor was a finding, and is now a gap
746
+
747
+ The context layer had two findings that did not depend on the answer:
748
+
749
+ this module targets Odoo 17.0; the corpus was checked against 18.0, so
750
+ version-specific behaviour in these rules is unverified here
751
+
752
+ tsconfig has strict off, so the compile-time half of any type-based
753
+ guarantee in this answer is not actually enforced
754
+
755
+ Both are true, both are worth knowing, and both were in the wrong slot. A
756
+ finding says something in THIS ANSWER is wrong or unproven. These say the corpus
757
+ was verified against a different series, and that this project has not finished
758
+ turning strict on -- facts about the corpus and the tree, which hold whatever
759
+ the answer says.
760
+
761
+ As findings they were a floor. Any Odoo module not on the newest series, and any
762
+ project mid-migration, drew a WARN on every run forever, so PASS was unreachable
763
+ for most real users and the WARN stopped carrying information. That is the same
764
+ argument that keeps `check` silent about a file that fits: a warning on every
765
+ run trains people to skip it.
766
+
767
+ The deciding detail: **0 of the 15 Odoo rules carries an `odoo_versions`
768
+ field.** `_version_applies` -- the check that produces a real finding, naming a
769
+ retrieved rule that genuinely does not hold on this series -- has therefore
770
+ never fired and cannot. The blanket warning was standing in for per-rule data
771
+ that does not exist yet, and spending a verdict to do it.
772
+
773
+ Both are gaps now, printed under "Not checked", and the layer reports UNKNOWN
774
+ when they were all it had. Contextual confidence reads `none`, which is the same
775
+ claim the WARN was making without burning the verdict. The per-rule version
776
+ check keeps its finding.
777
+
778
+ **What it does to 3.26, and the part that is uncomfortable.** Arithmetic on the
779
+ recorded rows -- not a second run -- moves five of the twenty WARNs to PASS, and
780
+ all five are `fsm_visit.py`: the file that lost 24% of itself to truncation and
781
+ came back with a 435-character answer naming 1 of its 19 symbols. Five runs, and
782
+ the context fact was the only thing any layer found.
783
+
784
+ That file now reads PASS. It was never being caught for being unreviewed; it was
785
+ being caught for its manifest, and getting the right verdict for an unrelated
786
+ reason is an accident rather than a safety property. Removing the accident is
787
+ what makes the gap visible: **truncation itself is not evidence-bearing.** A
788
+ review of the first 6,000 characters of a 7,929-character file should not be
789
+ able to reach PASS without saying so, and today the only thing that says so is a
790
+ line of prose above the answer. That belongs with the per-rule version data and
791
+ the by-symbol review, not with a floor that happened to mask it.
792
+
793
  ## 4. Performance traps, all found by measurement
794
 
795
  Within four instrumented runs the report showed the 7B at ~10 tok/s against a
README.md CHANGED
@@ -146,15 +146,22 @@ Rules retrieved:
146
  `--stack` overrides the detection in either direction. Unlike `nexa ask`, this one has **surroundings to read**: the nearest
147
  `__manifest__.py` gives the Odoo series, the file gives its model names and existing
148
  imports, the nearest `tsconfig.json` says whether `strict` is on. Those facts go into
149
- the question *and* into the context layer, so an answer can be flagged as right in
150
- general and wrong for this module:
 
 
 
151
 
152
  ```
153
- [medium] context
154
- this module targets Odoo 17.0; the corpus was checked against 18.0, so
155
- version-specific behaviour in these rules is unverified here
 
156
  ```
157
 
 
 
 
158
  ### Ask about your own code
159
 
160
  ```bash
@@ -269,7 +276,7 @@ Same principle as `z.infer<typeof Schema>`: one definition, derived artifacts, n
269
  | **Recall against 56 real Odoo bugfixes** | **1. See below.** |
270
  | Verdict stability, 12 questions x 5 runs | 12/12 same verdict every run |
271
  | Escalation rescued a bad draft | 12 of 22, 95% CI 35–73% |
272
- | **`nexa check`, 5 files x 5 runs** | **0 of 25 reached PASS. See below.** |
273
 
274
  29 of 32 rules carry an automated check. The remaining three are decidable only with
275
  context a snippet does not contain, so they return **no verdict** — never a false pass
@@ -453,7 +460,8 @@ adds the four things only a file can produce: which corpus it was routed to, how
453
  much of it fitted, what the surroundings on disk let the context layer say, and
454
  whether the answer mentions the file at all.
455
 
456
- Five files, five runs each:
 
457
 
458
  | | |
459
  |---|---|
@@ -463,12 +471,21 @@ Five files, five runs each:
463
  | Same verdict every run | 5 of 5 files |
464
  | Files whose answer named nothing they declare | 2 of 5 |
465
 
466
- **That stability is not `ask`'s stability.** Four of the five files carry a
467
- context finding decided before the model runs — a module on Odoo 17.0 against a
468
- corpus checked against 18.0, a `tsconfig` with `strict` off — so the verdict
469
- cannot move whatever the answer says. The subject set was built that way on
470
- purpose; the consequence is that every module not on the checked series has a
471
- WARN floor that is an environment fact, not a review.
 
 
 
 
 
 
 
 
 
472
 
473
  **The checkers run over the answer, not over the file.** On the Python and
474
  TypeScript subjects the model reproduced the file, so its real defects came back
 
146
  `--stack` overrides the detection in either direction. Unlike `nexa ask`, this one has **surroundings to read**: the nearest
147
  `__manifest__.py` gives the Odoo series, the file gives its model names and existing
148
  imports, the nearest `tsconfig.json` says whether `strict` is on. Those facts go into
149
+ the question *and* into the context layer.
150
+
151
+ Where they say the corpus was verified somewhere else, that is reported as a **gap**,
152
+ not a finding — it is true of the corpus whatever the answer says, and a verdict that
153
+ fires on every run in every project off the newest series stops being read:
154
 
155
  ```
156
+ Not checked
157
+ context version-specific behaviour
158
+ this module targets Odoo 17.0 and the corpus was checked against
159
+ 18.0, so anything version-specific in these rules is unverified here
160
  ```
161
 
162
+ A rule that was actually retrieved and genuinely does not apply to this series is a
163
+ different claim, and stays a finding with the rule id attached.
164
+
165
  ### Ask about your own code
166
 
167
  ```bash
 
276
  | **Recall against 56 real Odoo bugfixes** | **1. See below.** |
277
  | Verdict stability, 12 questions x 5 runs | 12/12 same verdict every run |
278
  | Escalation rescued a bad draft | 12 of 22, 95% CI 35–73% |
279
+ | **`nexa check`, 5 files x 5 runs** | **0 of 25 reached PASS, and not for a good reason. See below.** |
280
 
281
  29 of 32 rules carry an automated check. The remaining three are decidable only with
282
  context a snippet does not contain, so they return **no verdict** — never a false pass
 
460
  much of it fitted, what the surroundings on disk let the context layer say, and
461
  whether the answer mentions the file at all.
462
 
463
+ Five files, five runs each, measured **before** the context floor was demoted
464
+ (see below):
465
 
466
  | | |
467
  |---|---|
 
471
  | Same verdict every run | 5 of 5 files |
472
  | Files whose answer named nothing they declare | 2 of 5 |
473
 
474
+ **That 0 was not strictness, it was an accident.** Four of the five files carried
475
+ a context finding decided before the model ran — a module on Odoo 17.0 against a
476
+ corpus checked against 18.0, a `tsconfig` with `strict` off — so their verdict
477
+ could not move whatever the answer said, and 5 of 5 "stable" was mostly a
478
+ constant. Both are now **gaps** rather than findings, for the reason above: they
479
+ are true of the corpus and the project, not of the answer, and none of the 15
480
+ Odoo rules carries an `odoo_versions` field, so the blanket warning was standing
481
+ in for per-rule data that does not exist yet.
482
+
483
+ Arithmetic on the recorded rows — not a second run — puts the demotion at **five
484
+ WARNs becoming PASS**, and every one of them is the truncated file, where the
485
+ context fact was the only thing any layer found across five runs. Which is the
486
+ uncomfortable half: the file that lost 24% of itself and came back with a
487
+ 435-character answer naming 1 of its 19 symbols now reads PASS. It was never
488
+ being caught for that. It was being caught for its manifest.
489
 
490
  **The checkers run over the answer, not over the file.** On the Python and
491
  TypeScript subjects the model reproduced the file, so its real defects came back
bin/layers.py CHANGED
@@ -585,14 +585,25 @@ def layer_context(rules, ctx):
585
  verified_against = (meta.get("checked_against") or {}).get(ctx.get("stack"))
586
 
587
  if ctx.get("stack") == "odoo":
588
- if series:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  checked.append("Odoo series {0} from {1}".format(
590
  series, os.path.basename(ctx.get("manifest") or "manifest")))
591
- if verified_against and series != verified_against:
592
- findings.append(Finding("medium",
593
- "this module targets Odoo {0}; the corpus was checked "
594
- "against {1}, so version-specific behaviour in these "
595
- "rules is unverified here".format(series, verified_against)))
596
  else:
597
  gaps.append(("Odoo series", "no __manifest__.py with a version was found "
598
  "above this file"))
@@ -608,10 +619,15 @@ def layer_context(rules, ctx):
608
  declared, series), rule=r["id"]))
609
 
610
  if ctx.get("ts_strict") is False:
611
- findings.append(Finding("medium",
612
- "tsconfig has strict off, so the compile-time half of any "
613
- "type-based guarantee in this answer is not actually enforced"))
614
- checked.append("tsconfig strict")
 
 
 
 
 
615
  elif ctx.get("ts_strict") is True:
616
  checked.append("tsconfig strict")
617
  elif ctx.get("stack") in ("typescript", "javascript"):
 
585
  verified_against = (meta.get("checked_against") or {}).get(ctx.get("stack"))
586
 
587
  if ctx.get("stack") == "odoo":
588
+ if series and verified_against and series != verified_against:
589
+ # A gap, not a finding, and the difference is the whole point of
590
+ # having both. A finding says something in THIS ANSWER is wrong or
591
+ # unproven. This says the corpus was verified against a different
592
+ # series, which is a fact about the corpus and holds whatever the
593
+ # answer says -- so as a finding it made WARN the floor for every
594
+ # module not on the newest series, and a verdict that fires on every
595
+ # run stops being read. Same argument as the truncation warning that
596
+ # deliberately says nothing about a file that fits.
597
+ #
598
+ # The per-rule check below is the one that stays a finding: it names
599
+ # a rule that was actually retrieved and actually does not apply.
600
+ gaps.append(("version-specific behaviour",
601
+ "this module targets Odoo {0} and the corpus was checked "
602
+ "against {1}, so anything version-specific in these rules "
603
+ "is unverified here".format(series, verified_against)))
604
+ elif series:
605
  checked.append("Odoo series {0} from {1}".format(
606
  series, os.path.basename(ctx.get("manifest") or "manifest")))
 
 
 
 
 
607
  else:
608
  gaps.append(("Odoo series", "no __manifest__.py with a version was found "
609
  "above this file"))
 
619
  declared, series), rule=r["id"]))
620
 
621
  if ctx.get("ts_strict") is False:
622
+ # Demoted with the series mismatch above, for the same reason: it fires
623
+ # on the project rather than on the answer, and it fires on every run in
624
+ # any project that has not finished turning strict on. Making it a
625
+ # finding again would mean checking whether this answer leans on the
626
+ # type system at all -- its declared enforcement layer -- rather than
627
+ # assuming every answer does.
628
+ gaps.append(("type-based guarantees",
629
+ "tsconfig has strict off, so the compile-time half of any "
630
+ "type-based guarantee in this answer is not enforced here"))
631
  elif ctx.get("ts_strict") is True:
632
  checked.append("tsconfig strict")
633
  elif ctx.get("stack") in ("typescript", "javascript"):
tests/test_layers.py CHANGED
@@ -120,12 +120,47 @@ def test_context_that_actually_checked_something_can_pass(odoo_rules):
120
  assert result.verdict in (layers.PASS, layers.WARN)
121
 
122
 
123
- def test_context_mismatched_series_warns(odoo_rules):
124
- """The corpus was checked against 18.0; a 15.0 module deserves to be told."""
 
 
 
 
 
 
 
 
 
125
  result = layers.layer_context(odoo_rules, _ctx(odoo_series="15.0",
126
  manifest="__manifest__.py"))
 
 
 
 
 
 
 
 
 
 
 
127
  assert result.verdict == layers.WARN
128
- assert any("15.0" in f.message for f in result.findings)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
 
131
  # ------------------------------------------------------------ semantic gating
 
120
  assert result.verdict in (layers.PASS, layers.WARN)
121
 
122
 
123
+ def test_context_mismatched_series_is_a_gap_and_not_a_finding(odoo_rules):
124
+ """The corpus was checked against 18.0 and a 15.0 module deserves to be told
125
+ -- under 'Not checked', which is where things that are true of the corpus
126
+ rather than of the answer belong.
127
+
128
+ It was a finding, and as a finding it was the floor: no module off the
129
+ newest series could reach PASS however good the answer was, on any run. A
130
+ verdict that fires every time stops being read, and none of the 15 Odoo
131
+ rules carries an odoo_versions field, so the blanket warning was standing in
132
+ for per-rule data that does not exist yet.
133
+ """
134
  result = layers.layer_context(odoo_rules, _ctx(odoo_series="15.0",
135
  manifest="__manifest__.py"))
136
+ assert result.findings == []
137
+ assert result.verdict == layers.UNKNOWN, "nothing was established, and it says so"
138
+ assert any("15.0" in why and "18.0" in why for _, why in result.not_checked)
139
+
140
+
141
+ def test_context_still_warns_about_a_rule_that_does_not_apply(odoo_rules):
142
+ """The half that survived the demotion, and the half that earns a finding:
143
+ this one names a rule that was retrieved and genuinely does not hold here."""
144
+ versioned = [dict(odoo_rules[0], odoo_versions="17.0-18.0")]
145
+ result = layers.layer_context(versioned, _ctx(odoo_series="15.0",
146
+ manifest="__manifest__.py"))
147
  assert result.verdict == layers.WARN
148
+ assert any(f.rule == odoo_rules[0]["id"] for f in result.findings)
149
+
150
+
151
+ def test_strict_off_is_a_gap_too():
152
+ """Same shape as the series mismatch: true of the project, not of the
153
+ answer, and true on every run until somebody finishes the migration."""
154
+ result = layers.layer_context([], _ctx(stack="typescript", ts_strict=False))
155
+ assert result.findings == []
156
+ assert result.verdict == layers.UNKNOWN
157
+ assert any("strict off" in why for _, why in result.not_checked)
158
+
159
+
160
+ def test_strict_on_is_something_this_layer_checked():
161
+ result = layers.layer_context([], _ctx(stack="typescript", ts_strict=True))
162
+ assert result.verdict == layers.PASS
163
+ assert "tsconfig strict" in result.checked
164
 
165
 
166
  # ------------------------------------------------------------ semantic gating