Skip to content

fix(embedded_python): state_mutation hardware arm counted getter reads as writes - #2972

Merged
squid-protocol merged 1 commit into
mainfrom
fix/embedded-python-state-mutation-getter
Sep 11, 2026
Merged

fix(embedded_python): state_mutation hardware arm counted getter reads as writes#2972
squid-protocol merged 1 commit into
mainfrom
fix/embedded-python-state-mutation-getter

Conversation

@squid-protocol

Copy link
Copy Markdown
Owner

Problem

embedded_python's state_mutation hardware-toggle arm \.(?:value|on|off|high|low|toggle)\s*\( matched on call syntax alone, so it counted reads as writes — a violation of the state_mutation contract (#2765, docs/state_mutation_rule_contract.md):

  • Corollary 3 (a read is not a write): if recovery_pin.value() == 0: (a getter) counted as a mutation.
  • Corollary 4 (a token another rule owns is not a second signal): val = self.pin.value() double-counted — the assignment arm already counts the write to val, and the .value() getter added a spurious second hit; and a parameterised .on(evt, cb) is an event subscription that events/listeners own.

This is not a contract changestate_mutation is already stated (#2765). It's a single-language regex fix under that contract (per the harden-language-extraction skill, which explicitly excludes rule-contract-audit for single-language regex work), where a corpus measurement showing the rule firing on a read "is a bug in the rule, not a quirk of the corpus."

Fix

.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 .on(evt, cb) no longer reads as a mutation. Only tightens quantifiers → no ReDoS surface added.

Verification (investigated against the real meow_turtle corpus, 14 files)

  • Every real write preserved: all corpus .on()/.off()/.toggle() are no-arg (kept); the 5 .value(arg) setters still count. Only the 2 getter reads drop (raw 894 → 892).
  • test_state_mutation_contract_2765.py: added getter/subscribe negatives + one-statement-one-hit counts (val = self.pin.value() → 1, if pin.value() == 0: → 0, led.on() → 1). 102 pass.
  • Golden masters regenerated (both legs); bless_scope = 15 substantive + 5 topological diffs, all embedded_python/meow_turtle (mutation signal, its weighted/cascading views, downstream mass/exposure rollups), no file newly parsed or excluded.
  • rosetta 46/46, 0 regressions (embedded_python fixture uses none of these tokens) — no paired corpus rebless owed.
  • ruff_audit/mypy_audit/dead_key_audit --ci: no new findings. Full pytest tests/: 8746 passed (only the pre-existing test_security_auditor.py xgboost-env failures, identical on origin/main).

Contract audit-table row for embedded_python updated (191 -> 894 -> 892) with the corollary 3/4 rationale.

🤖 Generated with Claude Code

…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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXAjGXaZKr2Ge6jjZshPRD
@github-actions

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

@squid-protocol
squid-protocol marked this pull request as ready for review September 11, 2026 20:25
@squid-protocol
squid-protocol merged commit e28cbfb into main Sep 11, 2026
32 checks passed
@squid-protocol
squid-protocol deleted the fix/embedded-python-state-mutation-getter branch September 11, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant