Skip to content

refactor(spec): move observability seam into the spec layer#830

Open
alexanderlhicks wants to merge 1 commit into
leanEthereum:mainfrom
alexanderlhicks:fix/observability-spec-layer
Open

refactor(spec): move observability seam into the spec layer#830
alexanderlhicks wants to merge 1 commit into
leanEthereum:mainfrom
alexanderlhicks:fix/observability-spec-layer

Conversation

@alexanderlhicks
Copy link
Copy Markdown
Contributor

Summary

Completes the spec → node decoupling. A prior refactor relocated the
Interval type and protocol constants into the spec layer but left one
upward dependency behind: fork_choice.py and state_transition.py
still imported observability from node. This moves that last edge.

node/observability/spec/observability/. The package is purely the
vendor-neutral observer seam: a SpecObserver Protocol, a
_NullObserver no-op default, the observe_* context-manager timers, and
set_observer. The concrete Prometheus backend stays in node/metrics
and the startup set_observer(...) wiring stays in the CLI — only the seam
moves. The test moves to the mirrored tests/lean_spec/spec/observability/
path per the test-structure convention.

After this:

grep -rn "lean_spec.node" src/lean_spec/spec/   # empty

The spec layer no longer depends on node in either direction.

Why move the seam (alternative considered)

The seam is already a dependency-injection design: spec emits through a
Protocol, the default is a no-op so spec imports stay side-effect-free, and
clients register a real observer at startup. That seam is a spec-level
concern — the spec defines what it observes — so it belongs in the spec
layer with the no-op default. The alternative, leaving it in node and
threading an observer object through every spec entry point, was rejected:
it churns every state-transition signature and fights the existing
module-level singleton design.

Safety

Pure relocation, no behavior change — the observer classes and context
managers move verbatim; only import paths change.

Verification

Validated with uv run --no-sync against the installed dependency set:

  • grep -rn "lean_spec.node" src/lean_spec/spec/ → empty.
  • ruff check (full) + ruff format --check → clean.
  • ty check → clean.
  • codespell on changed files → clean.
  • pytest tests/lean_spec/spec/observability tests/lean_spec/spec/forks/lstar → 243 passed.

Note: full just check / full pytest could not be run locally because
main bumped lean-multisig-py to v0.0.6, whose maturin/Rust build
fails in my local environment (unrelated to this change). CI should run the
complete suite against a working build.

🤖 Generated with Claude Code

The spec layer's last upward dependency on node was the observability
import in fork_choice.py and state_transition.py. A prior refactor moved
the Interval type and protocol constants into the spec layer but left
this edge, so spec still reached up into node.

Relocate node/observability/ to spec/observability/. The package is
purely the vendor-neutral observer seam: a SpecObserver Protocol, a
no-op default, the observe_* context-manager timers, and set_observer.
Spec emits through it with a no-op default, so spec imports stay
side-effect-free. The concrete Prometheus backend stays in node/metrics
and the startup set_observer wiring stays in the CLI; only the seam moves.

Pure relocation, no behavior change. The test moves to the mirrored
spec path per the test-structure convention.

After this, grep -rn "lean_spec.node" src/lean_spec/spec/ is empty: the
spec layer no longer depends on node in either direction.

Alternative considered: leave the seam in node and inject an observer
into every spec entry point. Rejected as strictly worse churn that
fights the existing module-level singleton design; the seam is itself a
spec-level concern (the spec defines what it observes), so it belongs in
spec with the no-op default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alexanderlhicks
Copy link
Copy Markdown
Contributor Author

Just for context the two prior refactors were #819 and #817. I happened to basically have them done locally so this PR was tweaked at the last moment when pulling the latest changes from main.

Completes the spec → node decoupling. A prior refactor relocated the
Interval type and protocol constants into the spec layer but left one
upward dependency behind: fork_choice.py and state_transition.py
still imported observability from node. This moves that last edge.

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