Skip to content

Commit a417317

Browse files
claudehyperpolymath
authored andcommitted
feat(echo): surface @echo(...) function-grade annotation + upper-bound 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
1 parent 3b9e91a commit a417317

10 files changed

Lines changed: 218 additions & 26 deletions

File tree

.machine_readable/6a2/STATE.a2ml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ project = "jtv"
77
version = "0.0.1"
88
last-updated = "2026-06-18"
99
status = "active"
10-
session = "2026-06-18 — Echo arc completed + number systems stratified. (b) Echo+Epistemic first-class DONE (proof side, ADR-0009): JtvEcho.lean SECTION 5 = Epistemic lattice + Echo×Epistemic product FunctionEffect lattice + comm/assoc/idem composition laws (matching the already-merged echo.rs/epistemic.rs/effect.rs). Number systems STRATIFIED (ADR-0007 D6 realized as ADR-0010): JtvEcho.lean SECTION 6 3-level NumAlgebra tower 1:1 with the Echo lattice (hex/binary=ℤ-encodings collapse to int's tier; float=approxGroup→Neutral); echo.rs carrier-aware classification (CarrierEnv; reversible += graded shape⊔carrier; default-carrier=Int sound) + effect.rs param-seeded env; number.rs runtime-value bridge Value::reversal_echo. PR #44 + #45 (bridge+ADR-0010) merged; float-LOCALS soundness gate also LANDED (typechecker.rs check_echo_admissible{,_with_residue} seed CarrierEnv from the inferred env self.env → reverse{} over a float local correctly rejected, float routes to reversible{}->tok). lake + cargo green (127 lib tests, 0 sorry/unwrap). NEXT rung: value-level semantics beyond τ=int (gap-005)"
10+
session = "2026-06-18 — Surface Echo grades (ADR-0009 D1) + repo rename julia-the-viper→jtv. SURFACE ECHO: @echo(Safe|Neutral|Breaking) annotation on functions (grammar echo_marker + parser loop, order-independent with @pure); FunctionDecl.echo_annotation — the Echo enum moved ast.rs with an echo.rs `pub use` re-export to break the ast↔echo cycle; upper-bound check typechecker.rs check_echo_annotations (inferred ⊑ annotated, against the carrier-aware call-graph-composed effect.rs resolved_effects); pretty.rs + formatter.rs emit @echo (round-trip). 133 lib tests, fmt/clippy green. RENAME: full julia-the-viper→jtv slug + Julia the Viper→JtV prose across the repo (PR #47 merged) + nextgen-languages catalogue (#87 merged, rebuilt after main dropped all submodules); etymology kept as a README/CLAUDE footnote; the GitHub Settings repo-rename is the user's remaining manual step. NEXT rung: grade in TypeAnnotation::Function (function-valued params) + @epi(...) surface; value-level semantics beyond τ=int (gap-005)"
1111

1212
[project-context]
1313
name = "JtV"
@@ -59,7 +59,7 @@ actions = [
5959
"Governance hardening (branch claude/jtv-governance-hardening): pin governance-reusable@main → SHA (consistent with sibling standards reusables); add secret-scanner.yml wrapper (standards reusable exists at 524523c); add 'actions' language to codeql.yml; harden proof-regression download-then-run; NOTE the reusable-call-job missing-timeout Hypatia findings are false-positives (timeout-minutes is invalid on reusable-call jobs)",
6060
"Number-system stratification (ADR-0007 D6 / ADR-0010) — LANDED 2026-06-18: per-system additive-algebra → Echo-tier classification at proof level (JtvEcho.lean SECTION 6), type level (echo.rs carrier_echo + CarrierEnv carrier-aware += grading, effect.rs param-seeded env), value level (number.rs Value::reversal_echo), and the carrier-aware TYPECHECKER GATE (typechecker.rs check_echo_admissible{,_with_residue} seed CarrierEnv from the inferred env self.env → reverse{} over a float LOCAL correctly rejected; float routes to reversible{}->tok). STILL OPEN: lift type_preservation semantics beyond τ=int (gap-005); carrier-awareness inside reversible-if branches (rare corner); keep addition-only (×/÷ generated)",
6161
"v2 (c) token/residue neutral-reversal bridge — LANDED 2026-06-15 (ADR-0007 D5 Neutral tier): JtvEcho admissibleWithResidue + blockEcho_admissibleWithResidue; JtvTheorems execBackwardWithToken + rev_forward_backward_with_token + rev_backward_naive_fails_self_ref; echo.rs self-ref Breaking->Neutral; typechecker.rs check_echo_admissible_with_residue (token unlocks Neutral); reversible.rs RecordedOp::Snapshot residue. NEXT: (b) Echo as a first-class function effect",
62-
"Echo as first-class function effect ('(b)') — LANDED 2026-06-18 (ADR-0009): echo.rs function_echo + epistemic.rs function_epistemic + effect.rs FunctionEffect row + resolved_effects call-graph fixpoint; JtvEcho.lean SECTION 5 mechanises the Echo×Epistemic product lattice + comm/assoc/idem composition laws",
62+
"Echo as first-class function effect ('(b)') — LANDED 2026-06-18 (ADR-0009): echo.rs function_echo + epistemic.rs function_epistemic + effect.rs FunctionEffect row + resolved_effects call-graph fixpoint; JtvEcho.lean SECTION 5 mechanises the Echo×Epistemic product lattice + comm/assoc/idem composition laws. SURFACE side LANDED 2026-06-18: @echo(Safe|Neutral|Breaking) annotation on FunctionDecl (grammar echo_marker + parser, order-independent with @pure; Echo enum moved to ast.rs + echo.rs re-export to break the cycle) + upper-bound check typechecker.rs check_echo_annotations (inferred ⊑ annotated, via resolved_effects) + pretty/formatter @echo round-trip. REMAINING: grade in TypeAnnotation::Function (function-valued params) + @epi(...) surface",
6363
"Verification bridge: correlation tests between Lean semantics and Rust interpreter",
6464
"PataCL Phase 1 → unblocks JtV coproc implementation Phase 2"
6565
]
@@ -88,7 +88,8 @@ sessions = [
8888
{ date = "2026-06-17", subject = "License + hardening + ADR-0008. License (gap-006 RESOLVED, PR #36 merged): full per-file SPDX = MPL-2.0 (code) + CC-BY-SA-4.0 (docs); 190 MPL + 77 CC-BY-SA headers; PALIMPSEST.adoc retired; LICENSING.md rewritten; stray MIT/GPL/PMPL/or-later normalised. (PR #34 security clean + #35 v2-c bridge also merged earlier.) Workflow hardening (PR #37 merged): curl|sh installers (proof-regression elan, setup.sh just) -> download-then-run; rsr_check.rs unsafe_block self-trip cleared. Deferred (need hyperpolymath/standards reusable, out of session MCP scope): secret-scanner.yml, scorecard.yml job-perms. ADR-0008 (this commit): neg -> reverse-only; purist/adulterated dialect via feature-gated sugar + discouragement lint + purity certificate (resolves ADR-0007 D2); implementation is follow-on." },
8989
{ date = "2026-06-18", subject = "neg-dialect impl slice 1 + ADR-0009. PR #39 merged: jtv-core dialect.rs purity certificate (ADR-0008 D4) -- read-only AST scan stamping a program purist-jtv vs adulterated-jtv by counting expression-level neg sugar; reverse-block subtraction stays purist; 5 tests; cleared a self-introduced Hypatia unwrap_dangerous_default critical via Option::iter().sum(). ADR-0009 (this commit): Echo + Epistemic as first-class GRADED FUNCTION EFFECTS -- Echo grade {Safe<=Neutral<=Breaking} carried in the arrow, composing by the existing join; Epistemic = knowledge/observability axis {Opaque<=Partial<=Transparent} as a parallel graded effect (dual to Echo: loss vs revelation); product-lattice effect row; orthogonal to Purity + the dialect certificate. Implementation slice-wise (Echo-effect first). Still parked: (b) impl, number-system semantics, license wave-2, standards-blocked hardening (secret-scanner/scorecard)." },
9090
{ date = "2026-06-18", subject = "Echo arc completed + number-system stratification (PR #44 merged via rebase → main: SECTION 5+6 + carrier-aware echo; then bridge+ADR-0010 PR). (b) Echo+Epistemic first-class DONE on the proof side: JtvEcho.lean SECTION 5 = Epistemic lattice (hidden/bounded/full) + Echo×Epistemic product FunctionEffect lattice + comm/assoc/idem composition laws (mirrors the already-merged echo.rs/epistemic.rs/effect.rs). Number systems STRATIFIED (ADR-0007 D6 realized as ADR-0010): JtvEcho.lean SECTION 6 = 3-level NumAlgebra tower (abelianGroup/approxGroup/nonGroup) 1:1 with the Echo lattice; theorems hex_binary_collapse (hex/binary are ℤ-encodings, share int's tier) / exact_groups_safe / float_not_safe (IEEE-754 non-associative → approxGroup → Neutral) / no_current_system_breaks / reversal_tier_by_algebra. echo.rs made carrier-aware: NumAlgebra/additive_algebra/carrier_echo + CarrierEnv threaded through classify_*_in_env/function_echo_in_env; a reversible += is graded shape⊔carrier (float += → Neutral); shape-only wrappers preserve prior behaviour (empty env ⇒ Int=Safe); default-carrier=Int is sound (literals default to int). effect.rs own_effect seeds the env from param annotations. number.rs runtime-value bridge Value::number_system + Value::reversal_echo (delegates to carrier_echo — single source of truth). ADR-0010 reconciles with ADR-0007 D6 (float is approxGroup not D6-cancellative; D6's cancellative/idempotent Neutral/Breaking routes anticipated-but-uninhabited). lake + cargo fmt/clippy/test green (124 lib tests), 0 sorry/admit/axiom, no unwrap/unwrap_or. OPEN: float-LOCALS soundness slice (locals carry no annotation → thread typechecker inferred types into CarrierEnv, else a reverse{} over a float local is wrongly Safe-admitted); value-level type_preservation beyond τ=int (gap-005)." },
91-
{ date = "2026-06-18", subject = "Float-locals soundness slice (typechecker carrier-aware gate). Echo admissibility is checked at the reverse-block site inside check_control_stmt, where self.env already holds the INFERRED types of params + prior local assignments — so typechecker.rs check_echo_admissible{,_with_residue} now build a CarrierEnv from self.env (Type→BasicType for the 7 numeric Types; non-numeric/Any omitted → default Int=Safe) and call echo::classify_stmts_in_env. Effect: a reverse{} over a float LOCAL (typed only by inference, e.g. x = 2.5) is correctly REJECTED under Safe-only (was wrongly Safe-admitted); reversible{}->tok still admits it (token retains the rounding residue). Contained change (no reordering, no new inference pass). 4 typechecker tests (float carrier rejected; inferred-float-local rejected end-to-end; int contrast ok; float reversible-tok admitted). ADR-0010 §Status/§Consequences/§Open updated (slice landed; remaining minor corner = carrier-awareness inside reversible-if branches). cargo fmt/clippy/test green (127 lib tests; cleared a clippy approx_constant deny on a 3.14 literal → 2.5). Whole-suite check: no existing test/example relied on a float reverse{} being accepted, so the stricter rule broke nothing." }
91+
{ date = "2026-06-18", subject = "Float-locals soundness slice (typechecker carrier-aware gate). Echo admissibility is checked at the reverse-block site inside check_control_stmt, where self.env already holds the INFERRED types of params + prior local assignments — so typechecker.rs check_echo_admissible{,_with_residue} now build a CarrierEnv from self.env (Type→BasicType for the 7 numeric Types; non-numeric/Any omitted → default Int=Safe) and call echo::classify_stmts_in_env. Effect: a reverse{} over a float LOCAL (typed only by inference, e.g. x = 2.5) is correctly REJECTED under Safe-only (was wrongly Safe-admitted); reversible{}->tok still admits it (token retains the rounding residue). Contained change (no reordering, no new inference pass). 4 typechecker tests (float carrier rejected; inferred-float-local rejected end-to-end; int contrast ok; float reversible-tok admitted). ADR-0010 §Status/§Consequences/§Open updated (slice landed; remaining minor corner = carrier-awareness inside reversible-if branches). cargo fmt/clippy/test green (127 lib tests; cleared a clippy approx_constant deny on a 3.14 literal → 2.5). Whole-suite check: no existing test/example relied on a float reverse{} being accepted, so the stricter rule broke nothing." },
92+
{ date = "2026-06-18", subject = "Surface Echo grades (ADR-0009 D1) — Bundle A (parameterised syntax + upper-bound policy). @echo(Safe|Neutral|Breaking) annotation on functions: grammar.pest echo_marker = '@echo' '(' echo_grade ')' and function_decl markers now (purity_marker|echo_marker)*; parser.rs consumes leading markers in a loop (order-independent with @pure) → FunctionDecl.echo_annotation. The Echo enum moved echo.rs→ast.rs (gains Serialize) with a `pub use crate::ast::Echo` re-export in echo.rs to break the ast↔echo cycle so FunctionDecl can hold it; all `echo::Echo` sites unchanged. UPPER-BOUND check typechecker.rs check_echo_annotations (3rd pass of check_program): inferred ⊑ annotated against the carrier-aware call-graph-composed effect.rs resolved_effects — a function may declare MORE loss than it incurs, never less (modular: caller checks against callee's declared ceiling). The reverse-gate still uses the ACTUAL inferred grade, so an annotation can't loosen reversal soundness. pretty.rs + formatter.rs emit @echo after purity (round-trip fidelity, pinned by a pretty round_trip test). 6 new tests (2 parser incl. either-order, 3 typechecker accept/reject/absent, 1 round-trip); 133 lib tests, fmt/clippy green. effect.rs header note refreshed. REMAINING: grade in TypeAnnotation::Function (function-valued params); parallel @epi(...) surface." }
9293
]
9394

9495
[design-artefact-locations]

crates/jtv-core/src/ast.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,29 @@ pub enum LogicalOp {
200200

201201
// ===== FUNCTIONS =====
202202

203+
/// The three loss classes of the Echo effect taxonomy (spec v2 §8–9); lattice
204+
/// order `Safe ⊑ Neutral ⊑ Breaking`. Defined here, rather than in `echo.rs`, so
205+
/// the AST can carry an `@echo(...)` annotation without a module cycle
206+
/// (`echo.rs` imports `ast`). `echo.rs` re-exports it and owns its lattice ops.
207+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
208+
pub enum Echo {
209+
/// No loss — injective / reversible.
210+
Safe,
211+
/// Structured loss — non-total erasure, residue retained.
212+
Neutral,
213+
/// Total erasure — irreversible.
214+
Breaking,
215+
}
216+
203217
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
204218
pub struct FunctionDecl {
205219
pub name: String,
206220
pub params: Vec<Param>,
207221
pub return_type: Option<TypeAnnotation>,
208222
pub purity: Purity,
223+
/// Optional `@echo(...)` grade ceiling (ADR-0009 D1). The checker verifies
224+
/// the inferred (composed) echo does not exceed it: `inferred ⊑ annotated`.
225+
pub echo_annotation: Option<Echo>,
209226
pub body: Vec<ControlStmt>,
210227
}
211228

crates/jtv-core/src/echo.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,10 @@
3131
use crate::ast::*;
3232
use std::collections::HashMap;
3333

34-
/// The three loss classes of the Echo taxonomy.
35-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36-
pub enum Echo {
37-
/// No loss — injective / reversible.
38-
Safe,
39-
/// Structured loss — non-total erasure, residue retained.
40-
Neutral,
41-
/// Total erasure — irreversible.
42-
Breaking,
43-
}
34+
/// The three loss classes of the Echo taxonomy. The enum lives in `ast` (so the
35+
/// AST can carry an `@echo(...)` annotation without a module cycle); it is
36+
/// re-exported here, where its lattice operations are defined.
37+
pub use crate::ast::Echo;
4438

4539
impl Echo {
4640
/// Least upper bound. `Breaking` is absorbing; `Safe` is the unit.

crates/jtv-core/src/effect.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
// Epistemic via `function_epistemic`); `resolved_effects` joins in the effects
77
// of the functions it calls, transitively, to a fixpoint over the call graph.
88
//
9-
// This is the *composition* half of Echo+Epistemic-as-function-effects. It works
10-
// over the existing AST via an effect environment — it does not yet store the
11-
// grade in the type AST (`TypeAnnotation::Function`); that surface-level wiring
12-
// is a later slice.
9+
// This is the *composition* half of Echo+Epistemic-as-function-effects. The
10+
// surface `@echo(...)` annotation on a `FunctionDecl` and its upper-bound check
11+
// (`inferred ⊑ annotated`, ADR-0009 D1) landed via the typechecker's
12+
// `check_echo_annotations`, which consumes `resolved_effects` here. Carrying the
13+
// grade in the *type* AST (`TypeAnnotation::Function`, for function-valued
14+
// params) and the parallel `@epi(...)` surface remain later slices.
1315

1416
use crate::ast::*;
1517
use crate::echo::{function_echo_in_env, CarrierEnv, Echo};
@@ -266,6 +268,7 @@ mod tests {
266268
.collect(),
267269
return_type: None,
268270
purity: Purity::Pure,
271+
echo_annotation: None,
269272
body,
270273
}
271274
}

crates/jtv-core/src/epistemic.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ mod tests {
133133
.collect(),
134134
return_type: None,
135135
purity: Purity::Pure,
136+
echo_annotation: None,
136137
body,
137138
}
138139
}

crates/jtv-core/src/formatter.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ impl Formatter {
120120
Purity::Impure => {}
121121
}
122122

123+
// Echo grade annotation
124+
if let Some(echo) = func.echo_annotation {
125+
self.output.push_str(&format!(
126+
"@echo({}) ",
127+
match echo {
128+
Echo::Safe => "Safe",
129+
Echo::Neutral => "Neutral",
130+
Echo::Breaking => "Breaking",
131+
}
132+
));
133+
}
134+
123135
// Function signature
124136
self.output.push_str("fn ");
125137
self.output.push_str(&func.name);

crates/jtv-core/src/grammar.pest

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,15 @@ encoding_clause = { "encoding" ~ string }
122122

123123
// ===== FUNCTIONS =====
124124
function_decl = {
125-
purity_marker? ~ "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ (":" ~ return_type)? ~ block
125+
(purity_marker | echo_marker)* ~ "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ (":" ~ return_type)? ~ block
126126
}
127127

128128
purity_marker = { "@pure" | "@total" }
129129

130+
// ADR-0009 D1: an optional Echo grade ceiling on the function arrow.
131+
echo_marker = { "@echo" ~ "(" ~ echo_grade ~ ")" }
132+
echo_grade = { "Safe" | "Neutral" | "Breaking" }
133+
130134
param_list = { param ~ ("," ~ param)* }
131135
param = { identifier ~ (":" ~ type_annotation)? }
132136

0 commit comments

Comments
 (0)