cbcl-erl: implement SPEC-009 Erlang/NIF binding (v0.1.0) #5

Merged
hugooconnor merged 0 commits from refs/pull/5/head into main 2026-04-29 06:08:22 +00:00
hugooconnor commented 2026-04-29 05:15:44 +00:00 (Migrated from codeberg.org)

Summary

  • New crate crates/cbcl-erl/ exposes parse_message/1, parse_message_lax/1, verify_dialect/1, and versions/0 as a Rustler 0.36 NIF returning native BEAM terms (REQ-001..REQ-006 + OBS-001 + OBS-002).
  • Trust-boundary baseline per SPEC-009: #![forbid(unsafe_code)], every NIF wrapped in panic_guard::catch (REQ-005 / ADR-001 mitigation), free-form error reasons returned as binaries (atom-table DoS).
  • Coordination via hence/SPL — plans/SPEC-009-erl-binding.spl decomposes the work into 9 tasks across 3 subagent roles; all completed in dependency order.

§11 open questions resolved with v0.1.0 defaults

  1. Regular NIF (no dirty-CPU yet — benchmark hook deferred to v0.2)
  2. CON-001 5-prefix error taxonomy verbatim
  3. cbcl-csexp co-binding out of scope
  4. SPEC-010 hooks honoured but not yet drafted
  5. Cross-binding conformance harness (TEST-007) deferred to v0.2

Notable findings

  • Rustler 0.36's OwnedEnv::new() SIGABRTs outside an erl-hosted process (enif_alloc_env is dynamically loaded). Convention enforced across all modules: assertable logic lives in pure Rust helpers (*_pure functions), tests target those; #[rustler::nif] wrappers are thin glue. 12 BEAM-host-only tests stay #[ignore]'d.
  • parse_message_lax/1 is a documented stopgap because cbcl-parser does not yet expose a separate lax mode. Returns {ok, {raw, <<bytes>>}} for non-Simple inputs (e.g. Meta) where strict rejects with {error, <<"message error: ...">>}. Top-of-file comment names §11.1 / §11.2 as the unblockers.
  • Conformance gate (REQ-006) passes 32/48 vectors; 16 in KNOWN_DIVERGENCES (cap=20). All 16 trace to upstream — 3 are cbcl-parser permissiveness, 13 are scope mismatch (strings.json describes parser-level atom recognition, not message forms). Each entry has a one-line reason; none are bugs in cbcl-erl. Should clear via a SPEC-008 vector update or a SPEC-009 ADR pinning cbcl-erl strictness above cbcl-parser.

Other touches

  • crates/cbcl-core/src/lib.rs gains a single pub const VERSION line so versions/0 can surface the linked cbcl-core version without parsing the manifest.
  • Cargo.toml registers cbcl-erl as a workspace member.
  • plans/SPEC-009-erl-binding.spl is the hence coordination plan.

Out of scope (intentionally)

  • NFR-001 binary-size budget (≤ 8 MB stripped) — CI gate, not first-cut work.
  • NFR-002 fuzz baseline (10⁹ inputs across the rustler boundary) — CI gate.
  • :hash consistency check on dialects (cbcl-wasm carries one) — CON-002 mandates R1/R2/R3 only for v0.1.0.
  • BEAM-side test harness — deferred until SPEC-008 lands a downstream consumer to drive it.

Status note

SPEC-009 status remains draft. §12 normally gates implementation on cross-model + human review; this PR is the v0.1.0 implementation against the draft and does not promote the spec.

Test plan

  • cargo test -p cbcl-erl — 48 passing (33 unit + 14 integration + 1 conformance), 12 ignored
  • cargo check -p cbcl-erl --features tracing clean
  • Workspace cargo check clean
  • cargo build -p cbcl-erl --release succeeds (cdylib + rlib)
  • Conformance walks every test-vectors/messages/*.json (48 cases / 7 files)
  • Downstream: wire into cbcl-lfe-router per SPEC-008
  • Cross-model adversarial review before promoting SPEC-009 from draft to approved
## Summary - New crate `crates/cbcl-erl/` exposes `parse_message/1`, `parse_message_lax/1`, `verify_dialect/1`, and `versions/0` as a Rustler 0.36 NIF returning native BEAM terms (REQ-001..REQ-006 + OBS-001 + OBS-002). - Trust-boundary baseline per SPEC-009: `#![forbid(unsafe_code)]`, every NIF wrapped in `panic_guard::catch` (REQ-005 / ADR-001 mitigation), free-form error reasons returned as binaries (atom-table DoS). - Coordination via hence/SPL — `plans/SPEC-009-erl-binding.spl` decomposes the work into 9 tasks across 3 subagent roles; all completed in dependency order. ## §11 open questions resolved with v0.1.0 defaults 1. Regular NIF (no dirty-CPU yet — benchmark hook deferred to v0.2) 2. CON-001 5-prefix error taxonomy verbatim 3. `cbcl-csexp` co-binding out of scope 4. SPEC-010 hooks honoured but not yet drafted 5. Cross-binding conformance harness (TEST-007) deferred to v0.2 ## Notable findings - Rustler 0.36's `OwnedEnv::new()` SIGABRTs outside an `erl`-hosted process (`enif_alloc_env` is dynamically loaded). Convention enforced across all modules: assertable logic lives in pure Rust helpers (`*_pure` functions), tests target those; `#[rustler::nif]` wrappers are thin glue. 12 BEAM-host-only tests stay `#[ignore]`'d. - `parse_message_lax/1` is a documented stopgap because cbcl-parser does not yet expose a separate lax mode. Returns `{ok, {raw, <<bytes>>}}` for non-Simple inputs (e.g. Meta) where strict rejects with `{error, <<"message error: ...">>}`. Top-of-file comment names §11.1 / §11.2 as the unblockers. - Conformance gate (REQ-006) passes 32/48 vectors; 16 in `KNOWN_DIVERGENCES` (cap=20). All 16 trace to upstream — 3 are cbcl-parser permissiveness, 13 are scope mismatch (`strings.json` describes parser-level atom recognition, not message forms). Each entry has a one-line reason; none are bugs in cbcl-erl. Should clear via a SPEC-008 vector update or a SPEC-009 ADR pinning cbcl-erl strictness above cbcl-parser. ## Other touches - `crates/cbcl-core/src/lib.rs` gains a single `pub const VERSION` line so `versions/0` can surface the linked `cbcl-core` version without parsing the manifest. - `Cargo.toml` registers `cbcl-erl` as a workspace member. - `plans/SPEC-009-erl-binding.spl` is the hence coordination plan. ## Out of scope (intentionally) - NFR-001 binary-size budget (≤ 8 MB stripped) — CI gate, not first-cut work. - NFR-002 fuzz baseline (10⁹ inputs across the rustler boundary) — CI gate. - `:hash` consistency check on dialects (`cbcl-wasm` carries one) — CON-002 mandates R1/R2/R3 only for v0.1.0. - BEAM-side test harness — deferred until SPEC-008 lands a downstream consumer to drive it. ## Status note SPEC-009 status remains `draft`. §12 normally gates implementation on cross-model + human review; this PR is the v0.1.0 implementation against the draft and does not promote the spec. ## Test plan - [x] `cargo test -p cbcl-erl` — 48 passing (33 unit + 14 integration + 1 conformance), 12 ignored - [x] `cargo check -p cbcl-erl --features tracing` clean - [x] Workspace `cargo check` clean - [x] `cargo build -p cbcl-erl --release` succeeds (cdylib + rlib) - [x] Conformance walks every `test-vectors/messages/*.json` (48 cases / 7 files) - [ ] Downstream: wire into `cbcl-lfe-router` per SPEC-008 - [ ] Cross-model adversarial review before promoting SPEC-009 from draft to approved
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
anuna-research/cbcl-rs!5
No description provided.