← All posts

Cure v0.23.0 :: Packaging, Proof, and Polish

by Aleksei Matiushkin

release packaging registry signing transparency stdlib telemetry

v0.23.0 is the release that finally ships the remote package-registry story. The work was scaffolded in v0.18.0, tentatively pencilled into every release between v0.19.0 and v0.22.0, and rescheduled every time so that those releases could focus on the language surface.

With the language itself in a stable place after v0.22.0, v0.23.0 lands the registry in full -- HTTP client, lockfile, Ed25519 signing, Rekor-style transparency log, Hex cross-publishing -- alongside a broad ergonomics upgrade that catches the day-to-day cycle up to the language.

Remote package registry

Six new modules in Cure.Project.*:

  • Cure.Project.Registry -- read-only HTTP client over OTP's :httpc. Every response body is content-addressed by its SHA-256 and cached on disk under ~/.cure/registry_cache/. Hashes are re-verified on every read; a tampered cache entry is thrown away and the request re-issued. Every call emits structured events on the new :registry stage of Cure.Pipeline.Events (:fetch_start, :fetch_ok, :fetch_failed, :cache_hit, :hash_mismatch).

  • Cure.Project.Lock -- reads and writes Cure.lock. The shipped format is the one the v0.17.0 design document specified:

    [lock.http]
    version = "1.2.3"
    hash = "sha256:..."
    dependencies = ["json ~> 2.0"]
    

    resolve_with_lock/2 short-circuits the resolver when every current constraint is still satisfied by the locked version. The result is that a second cure deps after a clean checkout touches neither the network nor the backtracking solver.

  • Cure.Project.Signing -- Ed25519 key management on top of OTP's built-in :crypto support. Keys live under ~/.cure/keys/ with tight file permissions. The signing message is name || NUL || version || NUL || sha256(tarball); the same canonicalisation runs on both sides. Clients keep a trusted.toml that maps maintainer handles to their public-key bytes; a signature that does not verify against a trusted key surfaces as E041.

  • Cure.Project.Transparency -- client-side verifier for the Rekor-style append-only publish log. Every log entry is canonicalised as key-sorted JSON and its hash is chained against the previous entry. When the /log endpoint is unreachable the verifier returns {:ok, :unverified} and emits a warning under E042; operators who want strict verification flip config :cure, strict_transparency: true.

  • Cure.Project.Publisher -- assembles the package tarball, signs it, and uploads it via a JSON envelope containing base64 tarball and signature bytes. build_hex_tarball/1 produces a Hex-compatible tarball (VERSION, CHECKSUM, metadata.config, contents.tar.gz) for mix hex.publish to consume.

  • Cure.Project.Json -- minimal internal JSON codec. The compiler speaks its own JSON for the registry protocol so no external dependency has to land in the core.

New CLI surface

  • cure publish / cure publish --dry-run / cure publish --hex
  • cure search <query> and cure info <name>[:version]
  • cure keys generate <handle> and cure keys list
  • cure doctor -- environment + project + source health report, exits non-zero on any warning or error so it can gate CI
  • cure fix [--dry-run] -- project-wide safe rewrites (line endings, trailing whitespace, tabs -> two spaces, blank-line runs, trailing newline)
  • cure test --cover -- runs the self-hosted test suite through OTP's :cover and emits an HTML report under _build/cure/cover/index.html

Standard library (up to 27 modules)

  • Std.Json -- the runtime companion to the v0.21.0 @derive(JSON) extension. The Value ADT covers every JSON shape (Null | Bool(Bool) | Num(Float) | Str(String) | Arr(List(Value)) | Obj(List(JsonPair))). Encoder and decoder are backed by :cure_std_json on the Erlang side.

  • Std.Http -- thin wrapper over :httpc. get/1, get/2 (with headers), post/3, head/1. Returns Result(Response, HttpError) so callers can compose HTTP with plain Result-chains. The registry client dogfoods the same runtime module.

  • Std.Gen.shrink_int/1, shrink_list/1, shrink/1 -- shrinking primitives. Integer shrinking halves toward zero; list shrinking drops elements, shortest-first; the polymorphic shrink/1 dispatches on runtime shape (int / list / tuple).

  • Std.Test.forall_shrunk/3 and forall_shrunk_default/2 -- shrinking-aware property runner. On the first failing sample the runner walks the shrinker until no smaller value fails the property and raises {:property_failed_with_shrunk, minimal}. The reported counterexample is the minimum, not the first draw -- the single biggest DX win for property-based testing.

Infrastructure

  • Cure.Telemetry -- optional :telemetry bridge. Subscribes to every Cure.Pipeline.Events stage and re-emits events under [:cure, :pipeline, <stage>, <event_type>]. Production deployments can feed compilation and Z3 latency into an existing observability stack without extra glue. The :telemetry dependency is declared optional in mix.exs; if it is not on the load path the bridge silently becomes a no-op.

  • Cure.Doctor -- structured diagnostic. Checks Elixir / OTP / Z3 presence, the registry URL, project file sanity, lockfile freshness, open type holes, undocumented public functions. Each finding carries a severity, a code, a hint, and an actionable fix.

  • Cure.Fix -- project-wide safe rewrites. Walks lib/**/*.cure and test/**/*.cure, applies five conservative transforms, leaves files alone when nothing changes.

  • Cure.Cover -- instrumentation harness around OTP's :cover. Renders an HTML report plus a per-module text summary.

Showcase example

  • examples/cure_brainloop/ -- the top-pick showcase from the internal ideas_for_cure_apps.md. A toy expression-language interpreter with a REPL FSM. Uses ADTs, records, refinement types, protocols, effects, FSM callback mode, OTP interop, FFI, and the new Std.Json module in one self-contained codebase. Ships with an ExUnit suite that covers lexer, parser, evaluator, and environment semantics.

Error catalog

Five new codes E038-E042:

  • E038 Registry Fetch Failed
  • E039 Registry Hash Mismatch
  • E040 Registry Package Not Found
  • E041 Registry Signature Invalid
  • E042 Transparency Log Unreachable

Documentation

  • docs/PACKAGE_REGISTRY.md rewritten from a v0.17.0-era design document into the authoritative shipped contract: endpoints, lockfile format, signing message, transparency log entry schema, failure-mode matrix.
  • docs/PUBLISHING.md -- end-to-end walkthrough covering key generation, dry-run verification, live publish, Hex cross-publish, key rotation, strict-transparency mode, and troubleshooting.

By the numbers

  • 1144 tests pass (up from 1114; 3 doctests + 1141 tests).
  • mix credo --strict: 0 issues across 167 source files.
  • mix cure.check.stdlib: 27/27 (up from 25/25).
  • mix cure.check.examples: 44/44.
  • Five new error-catalog codes E038-E042.
  • Two new documentation files (docs/PUBLISHING.md, major revision of docs/PACKAGE_REGISTRY.md).

What's next (v0.24.0)

With the registry story closed, v0.24.0 returns to the language itself:

  • Monomorphisation. Specialise polymorphic functions whose call sites all use concrete types.
  • Profile-guided optimisation. Feed profiler data into the inliner and pattern-aware SMT encoder.
  • IDE reach. Ship first-class Helix / Zed configurations and upgrade the VS Code extension to track the current LSP surface.
  • REPL-level hot reload. Recompile-and-rebind on every file save for long-running REPL sessions.

Getting started

git clone https://github.com/am-kantox/cure-lang.git
cd cure
mix deps.get && mix test
mix escript.build
./cure version            # Cure 0.23.0
./cure doctor             # check your environment

The repository is at github.com/am-kantox/cure-lang. v0.22.0 cleaned up the language corners; v0.23.0 gives the ecosystem a home.