Skip to content

feat(echo): surface @echo(...) function-grade annotation + upper-bound check (ADR-0009 D1)#48

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

feat(echo): surface @echo(...) function-grade annotation + upper-bound check (ADR-0009 D1)#48
hyperpolymath merged 1 commit into
mainfrom
claude/dazzling-albattani-ubS9r

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Surfaces the Echo grade in the function arrow — the "Echo as a first-class function effect" surface slice of ADR-0009 D1 (the proof + effect-composition halves already landed). Built as Bundle A (the design we settled on): parameterised syntax + upper-bound policy.

Syntax — @echo(Safe|Neutral|Breaking)

@echo(Neutral) fn f(x: Int): Int { ... }
@pure @echo(Breaking) fn g(): Int { ... }   // order-independent with @pure/@total

One directive carrying the grade (grammar echo_marker; the parser consumes leading markers in a loop) — scales cleanly to a later @epi(...).

AST — and the cycle break

FunctionDecl.echo_annotation: Option<Echo>. The Echo enum moves from echo.rs to ast.rs so the AST can hold it without the astecho module cycle, and is re-exported pub use crate::ast::Echo from echo.rs — so every existing echo::Echo site is unchanged.

Check — upper bound (inferred ⊑ annotated)

typechecker::check_echo_annotations (a 3rd pass of check_program) verifies the inferred grade doesn't exceed the declared ceiling, against the carrier-aware, call-graph-composed effect::resolved_effects. A function may declare more loss than it incurs, never less — so a caller can be checked against a callee's declared ceiling (modular).

Soundness note: the reverse {} gate still classifies against the actual inferred grade, never the annotation — so no annotation can loosen reversal soundness. The annotation governs the published interface grade only.

Round-trip

pretty.rs + formatter.rs emit @echo(...) after purity, pinned by a pretty-printer round-trip test (annotations no longer drop on format).

Tests / quality

6 new tests (parse + either-order; upper-bound accept/reject/absent; round-trip). 133 lib tests, cargo fmt + clippy clean.

Remaining (noted in effect.rs + STATE)

The grade carried in TypeAnnotation::Function (for function-valued params) and the parallel @epi(...) surface.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BJmfoz1ZS1Pejy9LLMY742


Generated by Claude Code

…d check (ADR-0009 D1)

Surfaces the Echo grade in the function arrow per ADR-0009 D1 -- the
"Echo as a first-class function effect" surface slice (the proof + effect-
composition halves already landed).

Design (Bundle A): parameterised syntax + upper-bound policy.

- Syntax: `@echo(Safe|Neutral|Breaking)` before `fn`, order-independent with
  `@pure`/`@total` (grammar `echo_marker`; parser consumes leading markers in a
  loop). One directive carrying the grade -- scales to a later `@epi(...)`.
- AST: `FunctionDecl.echo_annotation: Option<Echo>`. The `Echo` enum moves from
  `echo.rs` to `ast.rs` (so the AST can hold it without the `ast` <-> `echo`
  cycle) and is re-exported `pub use crate::ast::Echo` from `echo.rs`, so every
  `echo::Echo` site is unchanged.
- Check (upper bound): `typechecker::check_echo_annotations` verifies
  `inferred <= annotated` against the carrier-aware, call-graph-composed
  `effect::resolved_effects`. A function may declare MORE loss than it incurs,
  never less; callers can be checked against a callee's declared ceiling
  (modular). The reverse-block gate still uses the ACTUAL inferred grade, so the
  annotation cannot loosen reversal soundness.
- Round-trip: `pretty.rs` + `formatter.rs` emit `@echo(...)` after purity.

6 tests (parse + either-order, upper-bound accept/reject/absent, round-trip);
133 lib tests, cargo fmt + clippy clean.

Remaining (noted in effect.rs + STATE): the grade in `TypeAnnotation::Function`
(function-valued params) and the parallel `@epi(...)` surface.

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: 55 issues detected

Severity Count
🔴 Critical 0
🟠 High 23
🟡 Medium 32
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 13:17
@hyperpolymath
hyperpolymath merged commit a417317 into main Jun 18, 2026
32 checks passed
@hyperpolymath
hyperpolymath deleted the claude/dazzling-albattani-ubS9r branch June 18, 2026 13:18
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