From 0cd82e8b5edad8bffb09c603e6ae0b0a5258ca97 Mon Sep 17 00:00:00 2001 From: squid-protocol Date: Fri, 11 Sep 2026 16:19:44 -0400 Subject: [PATCH] fix(embedded_python): the state_mutation hardware arm counted getter reads as writes The hardware-toggle arm `\.(?:value|on|off|high|low|toggle)\s*\(` matched on call syntax alone, so it counted reads as mutations -- a violation of the state_mutation contract (#2765, docs/state_mutation_rule_contract.md): corollary 3 (a read is not a write) and corollary 4 (a token another rule owns is not a second signal). In the meow_turtle corpus this fired on `if recovery_pin.value() == 0:` (a pure getter read) and double-counted `val = self.pin.value()` (the assignment arm already counts the write to `val`; the `.value()` getter added a spurious second hit). `.value(` is BOTH getter and setter in MicroPython/CircuitPython, so it now requires a non-empty argument: `pin.value(1)` writes, `pin.value()` reads. `.on/.off/.high/.low/.toggle` are the no-arg imperative write forms and now match the no-arg shape only, so a parameterised `.on(evt, cb)` -- an event subscription `events`/`listeners` own -- no longer reads as a mutation. Every real write in the corpus is preserved (all `.on()/.off()/ .toggle()` there are no-arg; the five `.value(arg)` setters still count); only the two getter reads drop. The change only tightens quantifiers, so it does not add ReDoS surface. Single-language regex fix under the existing (stated) #2765 contract, not a contract change. Golden masters regenerated (both legs): the diff is confined to embedded_python/meow_turtle state_mutation cells and their rollups, with no file crossing the density aperture. rosetta stays 46/46 (its embedded_python fixture uses none of these tokens) -- no paired corpus rebless owed. New negative cases added to test_state_mutation_contract_2765 and the contract's audit row updated (894 -> 892). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PXAjGXaZKr2Ge6jjZshPRD --- docs/state_mutation_rule_contract.md | 2 +- .../languages/embedded_python.py | 17 ++++++- .../test_state_mutation_contract_2765.py | 23 ++++++++-- tests/golden_master_audit.json | 44 +++++++++---------- tests/golden_master_zero_dep_audit.json | 44 +++++++++---------- 5 files changed, 80 insertions(+), 50 deletions(-) diff --git a/docs/state_mutation_rule_contract.md b/docs/state_mutation_rule_contract.md index 4240dc96..8657ef9e 100644 --- a/docs/state_mutation_rule_contract.md +++ b/docs/state_mutation_rule_contract.md @@ -119,7 +119,7 @@ rule was already inside the contract and is untouched. | `css` | `None` | n/a | contract-level absence (ledgered) | | `dart` | 764 -> 2052 | 2 | too narrow: only *unspaced* `x=1` matched; `x = 1` was invisible | | `dockerfile` | 13 -> 3 | 4 -> 2 | too broad: `ENV` is `globals` (corollary 4) | -| `embedded_python` | 191 -> 894 | 2 | agrees (#2817): same plain-assignment arm as `python`, hardware-toggle arm kept | +| `embedded_python` | 191 -> 894 -> 892 | 2 | agrees (#2817); hardware arm tightened to the contract: `.value(` requires an argument (the no-arg getter is a read, corollary 3), and `.on/.off/.high/.low/.toggle` match the no-arg form only (a parameterised `.on(evt, cb)` is an event subscription `events`/`listeners` own, corollary 4) | | `fortran` | 6344 -> 5066 | 3 | too broad: `INTEGER :: X = 1`, `CALL f(UNIT = 10)` mid-line specifiers | | `go` | 3344 -> 1548 | 5 -> 2 | too broad: `:=` declarations, `_ = x` discards, `append(` double-counting its own `=` | | `groovy` | 730 -> 895 | 2 | too broad/narrow: `@Setter`/`@Data` counted; `+=`, `++`, `.add(` did not | diff --git a/gitgalaxy/standards/language_standards/languages/embedded_python.py b/gitgalaxy/standards/language_standards/languages/embedded_python.py index ea18578e..1e540ce0 100644 --- a/gitgalaxy/standards/language_standards/languages/embedded_python.py +++ b/gitgalaxy/standards/language_standards/languages/embedded_python.py @@ -113,9 +113,22 @@ # #2817: count a plain assignment statement (`x = v`, `obj.attr = v`, # `d[k] = v`) as a write -- see python.py for the space-before-`=` black # anchor, the `==`/kwarg/default/annotated exclusions, and why re.M. - # The hardware-toggle arm (`.value(`/`.on(`/...) is kept. + # + # Hardware-toggle arm (#2765 contract corollaries 3 & 4): a call by + # itself is not a write -- a read is not a write, and a token another + # rule owns is not a second signal. + # * `.value(` is BOTH getter and setter in MicroPython/CircuitPython: + # `pin.value(1)` writes, `pin.value()` reads. Require a non-empty + # argument so the getter (`if pin.value() == 0`, `x = pin.value()`) + # is not miscounted -- the `x = ...` case still counts once via the + # assignment arm above, not twice. + # * `.on()/.off()/.high()/.low()/.toggle()` are the no-arg imperative + # write forms; require empty parens so a parameterised `.on(evt, cb)` + # -- an event SUBSCRIPTION owned by `events`/`listeners` -- does not + # read as a mutation. r"(?:^|;)[ \t]*[A-Za-z_]\w*(?:\.[A-Za-z_]\w*|\[[^\]\n]{0,80}\])*[ \t]+=(?![=])(?![^\n(]{0,300},[ \t]*$)" - r"|\bglobal\b|\bnonlocal\b|\b(?:self|cls)\.\w+[ \t]*=|:=|(?:\.\w+)?\.(?:append|extend|update|pop|remove|insert|clear)\s*\(|\.(?:value|on|off|high|low|toggle)\s*\(", + r"|\bglobal\b|\bnonlocal\b|\b(?:self|cls)\.\w+[ \t]*=|:=|(?:\.\w+)?\.(?:append|extend|update|pop|remove|insert|clear)\s*\(" + r"|\.value\s*\(\s*[^)\s]|\.(?:on|off|high|low|toggle)\s*\(\s*\)", re.M, ), # 12. dead_code (Commented Logic / Deprecated Trails) diff --git a/tests/extraction/languages/test_state_mutation_contract_2765.py b/tests/extraction/languages/test_state_mutation_contract_2765.py index 7647483d..cdd98cd9 100644 --- a/tests/extraction/languages/test_state_mutation_contract_2765.py +++ b/tests/extraction/languages/test_state_mutation_contract_2765.py @@ -295,9 +295,21 @@ def _rule(lang): ["print(self.value)", "x == 1", "foo(x=1)", "def f(x=1):", "x: int = 1"], ), "embedded_python": ( - # #2817: same plain-assignment arm as python; keeps the hardware-toggle arm. - ["x = 1", "obj.attr = 1", "led.value(1)", "global counter"], - ["led.value == 1", "x == 1", "foo(x=1)", "def f(x=1):", "x: int = 1"], + # #2817: same plain-assignment arm as python. Hardware-toggle arm (corollaries + # 3 & 4): `.value(arg)` and the no-arg `.on()/.off()/.high()/.low()/.toggle()` + # imperative forms are writes; a `.value()` getter, an event-subscribe + # `.on(evt, cb)`, and a `.value` read are not. + ["x = 1", "obj.attr = 1", "led.value(1)", "global counter", "led.on()", "pin.toggle()"], + [ + "led.value == 1", + "x == 1", + "foo(x=1)", + "def f(x=1):", + "x: int = 1", + "if pin.value() == 0:", # corollary 3: the value getter is a read + "return sensor.value()", # corollary 3: getter read, no assignment + "emitter.on('evt', cb)", # corollary 4: event subscription (events/listeners own it) + ], ), # --- corollary 4: a token another rule owns is not a second signal ---------------- "dockerfile": ( @@ -411,6 +423,11 @@ def _rule(lang): ("embedded_python", "x = 1", 1), ("embedded_python", "led.value(1)", 1), ("embedded_python", "foo(x=1)", 0), + # #2765 corollary 3/4: reading a getter into an assignment is ONE write (the + # assignment), not two -- the `.value()` getter must not add a second hit. + ("embedded_python", "val = self.pin.value()", 1), + ("embedded_python", "if pin.value() == 0:", 0), + ("embedded_python", "led.on()", 1), ("ruby", "x = 1", 1), ("ruby", "arr[0] = 1", 1), ("ruby", "CONST = 1", 0), # constant assignment is freeze_hits, not flux diff --git a/tests/golden_master_audit.json b/tests/golden_master_audit.json index 4c3fa052..8553dbdc 100644 --- a/tests/golden_master_audit.json +++ b/tests/golden_master_audit.json @@ -12,8 +12,8 @@ }, "Target Root Name": "data", "Absolute Project Path": "/srv/storage_16tb/projects/gitgalaxy/language-crucible/data", - "Analysis ISO Timestamp": "2026-09-11T19:28:03.177289+00:00", - "Total Scan Duration": "28.94 seconds" + "Analysis ISO Timestamp": "2026-09-11T20:08:04.195787+00:00", + "Total Scan Duration": "29.25 seconds" }, "Source Control Footprint (Immutable Anchor)": { "Active Branch": "HEAD", @@ -360,7 +360,7 @@ "embedded_python": { "files": 7, "loc": 2020, - "impact": 2868.0000000000005 + "impact": 2862.0000000000005 }, "shell": { "files": 213, @@ -4097,10 +4097,10 @@ }, "embedded_python/meow_turtle": { "file_count": 14, - "total_mass": 3584.72, + "total_mass": 3578.72, "avg_exposures": { "cognitive_load": 38.92, - "safety_score": 90.11, + "safety_score": 90.1, "tech_debt": 26.75, "verification": 30.28, "api_exposure": 29.98, @@ -612353,14 +612353,14 @@ } }, "embedded_python/meow_turtle": { - "Directory Group Magnitude": 3584.72, + "Directory Group Magnitude": 3578.72, "File Count": 14, "Ecosystem Fingerprint (Archetypes)": { "Unclassified": "100.0%" }, "Average Risk Exposures": { "Cognitive Load Exposure": "38.92%", - "Error & Exception Exposure": "90.11%", + "Error & Exception Exposure": "90.1%", "Tech Debt Exposure": "26.75%", "Testing Exposure": "30.28%", "API Exposure": "29.98%", @@ -615201,9 +615201,9 @@ "Identity Proof": "Metadata Anchor (boot.py)" }, "2. Topological Coordinates": { - "X": -4027.29, - "Y": -68.24, - "Z": 9411.11 + "X": -4027.36, + "Y": -68.23, + "Z": 9411.03 }, "3. Architectural Profile": { "Repository Archetype": "Unclassified", @@ -615215,7 +615215,7 @@ "Total LOC": 644, "Coding LOC": 354, "Documentation LOC": 197, - "Structural Magnitude": 318.78, + "Structural Magnitude": 315.78, "Control Flow Ratio": "20.3%", "Popularity Rank": 0, "Raw Churn Frequency": 0.0, @@ -615225,7 +615225,7 @@ }, "4. Vulnerability & Risk Exposures": { "Cognitive Load Exposure": "45.25%", - "Error & Exception Exposure": "98.32%", + "Error & Exception Exposure": "98.24%", "Tech Debt Exposure": "14.26%", "Testing Exposure": "80.0%", "API Exposure": "9.95%", @@ -615541,9 +615541,9 @@ ], "6. Contextual Mitigations & Amplifications": { "Mitigated Danger": "2 instances", - "Amplified Cascading Flux": "35 instances", + "Amplified Cascading Flux": "34 instances", "High-Risk Execution Commands (Weighted View)": 1, - "State Mutations / Variable Reassignments (Weighted View)": 138 + "State Mutations / Variable Reassignments (Weighted View)": 135 }, "7. Structural Signatures (Net Mitigated Signals)": { "Control Flow Branches": 72, @@ -615556,7 +615556,7 @@ "High-Risk Execution Commands": 3, "I/O and Network Boundaries": 17, "Exposed API / Public Exports": 24, - "State Mutations / Variable Reassignments": 68, + "State Mutations / Variable Reassignments": 67, "Commented-out Code (Dead Logic)": 0, "Structured Documentation Blocks": 27, "Unit Test Assertions": 0, @@ -615865,8 +615865,8 @@ }, "2. Topological Coordinates": { "X": -4277.97, - "Y": 31.92, - "Z": 8811.85 + "Y": 31.91, + "Z": 8811.92 }, "3. Architectural Profile": { "Repository Archetype": "Unclassified", @@ -615878,7 +615878,7 @@ "Total LOC": 509, "Coding LOC": 294, "Documentation LOC": 142, - "Structural Magnitude": 489.78, + "Structural Magnitude": 486.78, "Control Flow Ratio": "27.2%", "Popularity Rank": 1, "Raw Churn Frequency": 0.0, @@ -615888,7 +615888,7 @@ }, "4. Vulnerability & Risk Exposures": { "Cognitive Load Exposure": "44.04%", - "Error & Exception Exposure": "99.69%", + "Error & Exception Exposure": "99.67%", "Tech Debt Exposure": "13.95%", "Testing Exposure": "80.0%", "API Exposure": "44.68%", @@ -616126,9 +616126,9 @@ ], "6. Contextual Mitigations & Amplifications": { "Amplified Race Conditions": "3 instances", - "Amplified Cascading Flux": "76 instances", + "Amplified Cascading Flux": "75 instances", "Asynchronous/Concurrent Execution (Weighted View)": 19, - "State Mutations / Variable Reassignments (Weighted View)": 271 + "State Mutations / Variable Reassignments (Weighted View)": 268 }, "7. Structural Signatures (Net Mitigated Signals)": { "Control Flow Branches": 80, @@ -616141,7 +616141,7 @@ "High-Risk Execution Commands": 0, "I/O and Network Boundaries": 12, "Exposed API / Public Exports": 15, - "State Mutations / Variable Reassignments": 119, + "State Mutations / Variable Reassignments": 118, "Commented-out Code (Dead Logic)": 0, "Structured Documentation Blocks": 23, "Unit Test Assertions": 0, diff --git a/tests/golden_master_zero_dep_audit.json b/tests/golden_master_zero_dep_audit.json index a6b9baa3..bc82a4c3 100644 --- a/tests/golden_master_zero_dep_audit.json +++ b/tests/golden_master_zero_dep_audit.json @@ -12,8 +12,8 @@ }, "Target Root Name": "data", "Absolute Project Path": "/srv/storage_16tb/projects/gitgalaxy/language-crucible/data", - "Analysis ISO Timestamp": "2026-09-11T19:28:36.786794+00:00", - "Total Scan Duration": "27.24 seconds" + "Analysis ISO Timestamp": "2026-09-11T20:08:37.910041+00:00", + "Total Scan Duration": "27.49 seconds" }, "Source Control Footprint (Immutable Anchor)": { "Active Branch": "HEAD", @@ -360,7 +360,7 @@ "embedded_python": { "files": 7, "loc": 2020, - "impact": 2868.0000000000005 + "impact": 2862.0000000000005 }, "shell": { "files": 213, @@ -4097,10 +4097,10 @@ }, "embedded_python/meow_turtle": { "file_count": 14, - "total_mass": 3584.72, + "total_mass": 3578.72, "avg_exposures": { "cognitive_load": 38.92, - "safety_score": 90.11, + "safety_score": 90.1, "tech_debt": 26.75, "verification": 30.28, "api_exposure": 29.98, @@ -612353,14 +612353,14 @@ } }, "embedded_python/meow_turtle": { - "Directory Group Magnitude": 3584.72, + "Directory Group Magnitude": 3578.72, "File Count": 14, "Ecosystem Fingerprint (Archetypes)": { "Unclassified": "100.0%" }, "Average Risk Exposures": { "Cognitive Load Exposure": "38.92%", - "Error & Exception Exposure": "90.11%", + "Error & Exception Exposure": "90.1%", "Tech Debt Exposure": "26.75%", "Testing Exposure": "30.28%", "API Exposure": "29.98%", @@ -615201,9 +615201,9 @@ "Identity Proof": "Metadata Anchor (boot.py)" }, "2. Topological Coordinates": { - "X": -4027.29, - "Y": -68.24, - "Z": 9411.11 + "X": -4027.36, + "Y": -68.23, + "Z": 9411.03 }, "3. Architectural Profile": { "Repository Archetype": "Unclassified", @@ -615215,7 +615215,7 @@ "Total LOC": 644, "Coding LOC": 354, "Documentation LOC": 197, - "Structural Magnitude": 318.78, + "Structural Magnitude": 315.78, "Control Flow Ratio": "20.3%", "Popularity Rank": 0, "Raw Churn Frequency": 0.0, @@ -615225,7 +615225,7 @@ }, "4. Vulnerability & Risk Exposures": { "Cognitive Load Exposure": "45.25%", - "Error & Exception Exposure": "98.32%", + "Error & Exception Exposure": "98.24%", "Tech Debt Exposure": "14.26%", "Testing Exposure": "80.0%", "API Exposure": "9.95%", @@ -615541,9 +615541,9 @@ ], "6. Contextual Mitigations & Amplifications": { "Mitigated Danger": "2 instances", - "Amplified Cascading Flux": "35 instances", + "Amplified Cascading Flux": "34 instances", "High-Risk Execution Commands (Weighted View)": 1, - "State Mutations / Variable Reassignments (Weighted View)": 138 + "State Mutations / Variable Reassignments (Weighted View)": 135 }, "7. Structural Signatures (Net Mitigated Signals)": { "Control Flow Branches": 72, @@ -615556,7 +615556,7 @@ "High-Risk Execution Commands": 3, "I/O and Network Boundaries": 17, "Exposed API / Public Exports": 24, - "State Mutations / Variable Reassignments": 68, + "State Mutations / Variable Reassignments": 67, "Commented-out Code (Dead Logic)": 0, "Structured Documentation Blocks": 27, "Unit Test Assertions": 0, @@ -615865,8 +615865,8 @@ }, "2. Topological Coordinates": { "X": -4277.97, - "Y": 31.92, - "Z": 8811.85 + "Y": 31.91, + "Z": 8811.92 }, "3. Architectural Profile": { "Repository Archetype": "Unclassified", @@ -615878,7 +615878,7 @@ "Total LOC": 509, "Coding LOC": 294, "Documentation LOC": 142, - "Structural Magnitude": 489.78, + "Structural Magnitude": 486.78, "Control Flow Ratio": "27.2%", "Popularity Rank": 1, "Raw Churn Frequency": 0.0, @@ -615888,7 +615888,7 @@ }, "4. Vulnerability & Risk Exposures": { "Cognitive Load Exposure": "44.04%", - "Error & Exception Exposure": "99.69%", + "Error & Exception Exposure": "99.67%", "Tech Debt Exposure": "13.95%", "Testing Exposure": "80.0%", "API Exposure": "44.68%", @@ -616126,9 +616126,9 @@ ], "6. Contextual Mitigations & Amplifications": { "Amplified Race Conditions": "3 instances", - "Amplified Cascading Flux": "76 instances", + "Amplified Cascading Flux": "75 instances", "Asynchronous/Concurrent Execution (Weighted View)": 19, - "State Mutations / Variable Reassignments (Weighted View)": 271 + "State Mutations / Variable Reassignments (Weighted View)": 268 }, "7. Structural Signatures (Net Mitigated Signals)": { "Control Flow Branches": 80, @@ -616141,7 +616141,7 @@ "High-Risk Execution Commands": 0, "I/O and Network Boundaries": 12, "Exposed API / Public Exports": 15, - "State Mutations / Variable Reassignments": 119, + "State Mutations / Variable Reassignments": 118, "Commented-out Code (Dead Logic)": 0, "Structured Documentation Blocks": 23, "Unit Test Assertions": 0,