Skip to content

feat(typechecker): carrier-aware Echo gate — float locals soundly reject (ADR-0010)#46

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/dazzling-albattani-ubS9r
Jun 18, 2026
Merged

feat(typechecker): carrier-aware Echo gate — float locals soundly reject (ADR-0010)#46
hyperpolymath merged 1 commit into
mainfrom
claude/dazzling-albattani-ubS9r

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Closes the float-locals soundness gap that ADR-0010 explicitly flagged as required-next. The Safe-only reverse {} gate now consults the carrier of the target variable — including float locals, not just annotated params. cargo fmt/clippy clean; 127 lib tests + all integration suites pass.

The fix (contained — no surgery)

Echo admissibility is checked at the reverse-block site inside check_control_stmt, and at that point self.env already holds the inferred types of params and prior local assignments. Locals carry no annotation, but x = 2.5 records x : Float in the env. So check_echo_admissible / check_echo_admissible_with_residue now build a CarrierEnv from self.env (numeric Type → BasicType; non-numeric and not-yet-inferred Any omitted → default Int/Safe) and classify via echo::classify_stmts_in_env.

No reordering, no new inference pass — the env is already populated at the gate.

Effect

Block float carrier int/rational/…
reverse { x += v } (Safe-only) rejected (Neutral — lossy reverse-add) accepted (Safe)
reversible { x += v } -> tok admitted (token retains rounding residue) accepted

So float reversal is correctly routed to the token policy rather than wrongly Safe-admitted.

Tests & safety

  • 4 typechecker tests: float carrier rejected; inferred-float-local rejected end-to-end (x = 2.5 then reverse { x += 1.0 }); int contrast ok; float reversible … -> tok admitted.
  • Whole-suite check: no existing test or example relied on a float reverse {} being accepted, so the stricter (correct) rule breaks nothing.

Docs

ADR-0010 §Status/§Consequences/§Open updated (slice landed; the one remaining minor corner — carrier-awareness for plain assignments nested inside reversible-if branches — is noted). STATE.a2ml records the landing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BJmfoz1ZS1Pejy9LLMY742


Generated by Claude Code

…ect (ADR-0010)

Closes the float-locals soundness gap flagged in ADR-0010: the Safe-only reverse
gate now consults the carrier of the target variable, including float LOCALS.

The Echo admissibility check runs at the reverse-block site inside
check_control_stmt, where self.env already holds the INFERRED types of params
and prior local assignments (locals carry no annotation, but `x = 2.5` records
x : Float in the env). So check_echo_admissible / check_echo_admissible_with_residue
now build a CarrierEnv from self.env (numeric Type -> BasicType; non-numeric and
not-yet-inferred Any omitted -> default Int = Safe) and classify via
echo::classify_stmts_in_env.

Effect:
  * `reverse { x += v }` over a float local (or param) is correctly REJECTED
    under Safe-only -- float reverse-add is lossy (Neutral), was wrongly
    Safe-admitted before.
  * `reversible { x += v } -> tok` over a float still ADMITTED -- the token
    retains the rounding residue (Neutral admissible with residue).
  * int/rational/complex/symbolic/hex/binary unchanged (Safe).

Contained change: no reordering, no new inference pass -- the inferred type env
is already populated at the gate. 4 typechecker tests (float carrier rejected;
inferred-float-local rejected end-to-end; int contrast ok; float reversible-tok
admitted). Whole-suite check: nothing relied on float reverse{} being accepted,
so the stricter rule breaks no existing test/example.

ADR-0010 + STATE.a2ml updated: slice landed; remaining minor corner noted
(carrier-awareness inside reversible-if branches).

cargo fmt + clippy clean; 127 lib tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJmfoz1ZS1Pejy9LLMY742
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 51 issues detected

Severity Count
🔴 Critical 0
🟠 High 25
🟡 Medium 26
View findings
[
  {
    "reason": "No test directory or test files found",
    "type": "no_tests",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper",
    "action": "flag",
    "rule_module": "honest_completion",
    "severity": "high",
    "deduction": 20
  },
  {
    "reason": "Issue in secret-scanner.yml",
    "type": "missing_workflow",
    "file": "secret-scanner.yml",
    "action": "create",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "Issue in scorecard.yml",
    "type": "scorecard_wrapper_missing_job_permissions",
    "file": "scorecard.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "high"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (10 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/crates/jtv-core/benches/interpreter_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "unwrap() without prior check -- DoS via panic (6 occurrences, CWE-754)",
    "type": "unwrap_without_check",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/crates/jtv-core/benches/parser_bench.rs",
    "action": "flag",
    "rule_module": "code_safety",
    "severity": "high"
  },
  {
    "reason": "Js.Array2 deprecated -- use Array (3 occurrences)",
    "type": "deprecated_api",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/vscode-extension/src/Extension.res",
    "action": "search_replace",
    "rule_module": "migration_rules",
    "severity": "high"
  },
  {
    "reason": "Js.Promise deprecated -- use Promise (2 occurrences)",
    "type": "deprecated_api",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/vscode-extension/src/Extension.res",
    "action": "module_replace",
    "rule_module": "migration_rules",
    "severity": "medium"
  },
  {
    "reason": "Belt.Array deprecated -- use Array (30 occurrences)",
    "type": "deprecated_api",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/packages/jtv-analyzer/src/Main.res",
    "action": "module_replace",
    "rule_module": "migration_rules",
    "severity": "high"
  },
  {
    "reason": "Belt.Option deprecated -- use Option (2 occurrences)",
    "type": "deprecated_api",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/packages/jtv-analyzer/src/Main.res",
    "action": "module_replace",
    "rule_module": "migration_rules",
    "severity": "medium"
  },
  {
    "reason": "Js.Array2 deprecated -- use Array (7 occurrences)",
    "type": "deprecated_api",
    "file": "/home/runner/work/julia-the-viper/julia-the-viper/packages/jtv-analyzer/src/Main.res",
    "action": "search_replace",
    "rule_module": "migration_rules",
    "severity": "high"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath
hyperpolymath marked this pull request as ready for review June 18, 2026 07:02
@hyperpolymath
hyperpolymath merged commit 5c45b8c into main Jun 18, 2026
30 checks passed
@hyperpolymath
hyperpolymath deleted the claude/dazzling-albattani-ubS9r branch June 18, 2026 07:02
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.

2 participants