vyre-libs 0.7.2

vyre Category A library ecosystem - pure-IR compositions over foundation IR and primitive-owned kernels
Documentation
# Authoring a Cat-A op in vyre-libs

This document is the canonical recipe for landing a new Category-A
composition inside `vyre-libs`. A Cat-A op produces a `Program`
assembled from vyre primitives  -  no raw shader source, no
backend-specific code. Every backend that implements `VyreBackend`
runs the same Program with byte-identical output.

Follow the 5-step recipe below. Every step has a test or a lint
protecting it; skipping a step fails CI.

---

## 1. Pick primitives + wrap in `Region`

Every composition is a slice of vyre IR nodes ending in a
[`Node::Region`](https://docs.rs/vyre-foundation) wrapper. The wrapper
is a **debug marker**: it carries the generator name and optional
source-region metadata so conformance certificates and tracing spans
can name where the IR came from. Region is semantically transparent
(see `docs/ir-semantics.md`), so the wrapper never affects execution.

Skeleton:

```rust
use vyre_libs::prelude::*;

fn my_op(input: TensorRef, output: TensorRef) -> Result<Program, TensorRefError> {
    check_tensors(
        "vyre-libs::dialect::my_op",
        &[(&input, DataType::F32), (&output, DataType::F32)],
    )?;
    // ... build `body: Vec<Node>` using the primitives in vyre-ops ...
    let body = vec![/* ... */];

    Ok(Program::wrapped(
        vec![
            BufferDecl::storage(input.name_str(), 0, BufferAccess::ReadOnly, DataType::F32)
                .with_count(input.element_count().expect("checked above")),
            BufferDecl::storage(output.name_str(), 1, BufferAccess::ReadWrite, DataType::F32)
                .with_count(output.element_count().expect("checked above")),
        ],
        [64, 1, 1],
        vec![wrap("vyre-libs::dialect::my_op", body, None)],
    ))
}
```

**Rules enforced by CI:**
- Buffer count must be declared via `BufferDecl::with_count(n)`
  when the shape is known at build time (P1.5).
- Product overflows (m·k, s·d, etc.) must be guarded with
  `checked_mul` or routed through `TensorRef::element_count()`.
- No raw WGSL, WGSL-like strings, or any backend-specific code
  (`scripts/check_no_string_wgsl.sh`).

## 2. Ship a typed builder

Every non-trivial Cat-A op exposes a chainable builder so future
options (workgroup size, region generator override, tenant id) land
as options without breaking call sites:

```rust
pub struct MyOp { input: TensorRef, output: TensorRef, options: BuildOptions }

impl MyOp {
    pub fn new(input: TensorRef, output: TensorRef) -> Self {
        Self { input, output, options: BuildOptions::default() }
    }
    pub fn with_workgroup_size(mut self, s: [u32; 3]) -> Self {
        self.options = self.options.with_workgroup_size(s); self
    }
    pub fn with_region_generator(mut self, n: &'static str) -> Self {
        self.options = self.options.with_region_generator(n); self
    }
    pub fn with_tenant_id(mut self, t: u32) -> Self {
        self.options = self.options.with_tenant_id(t); self
    }
    pub fn build(self) -> Result<Program, TensorRefError> { /* ... */ }
}

```

Existing public functions remain canonical only while they are the one supported
path. A builder cutover migrates every caller and removes the replaced function
in the same major-version change. Do not add wrapper aliases that keep both
surfaces alive.

## 3. Write a CPU reference

Every Cat-A op needs a plain-Rust reference that computes the same
thing the IR claims to. This is the oracle. Example for a 1-D
reduce-sum:

```rust
fn cpu_reduce_sum(input: &[u32]) -> u32 {
    input.iter().copied().sum()
}
```

Keep it trivially correct. Performance doesn't matter here; the
reference's only job is to be obviously right.

## 4. Add a `cat_a_conform` witness test

Every op ships a byte-identity assertion in
`vyre-libs/tests/cat_a_conform.rs`. The test runs the Program
through the reference interpreter and compares byte-for-byte against
the CPU reference on a corpus of inputs:

```rust
#[test]
fn cat_a_my_op_matches_cpu_reference() {
    use vyre_libs::my_op;
    let witnesses: &[(Vec<u32>, u32)] = &[
        (vec![1, 2, 3, 4], 10),
        (vec![0; 16], 0),
    ];
    for (input, expected) in witnesses {
        let program = my_op("in", "out", input.len() as u32);
        let outputs = run_program(
            &program,
            vec![Value::from(u32_bytes(input)), Value::from(vec![0u8; 4])],
        );
        let got = decode_u32_words(&outputs[0])[0];
        assert_eq!(got, *expected, "diverged on {input:?}");
    }
}
```

External pattern sources (BLAKE3 KATs, Aho-Corasick 1975 paper
corpus, Jax/TF reference outputs) are preferred over hand-rolled
witnesses.

## 4b. Declare your op's algebraic laws (optional but recommended)

If your op satisfies any algebraic laws (commutativity, associativity,
identity-element, idempotence, self-inverse), declare them so the
canonical-form + CSE passes can optimize through your op:

```rust
use vyre_foundation::{AlgebraicLaw, AlgebraicLawRegistration};

inventory::submit! {
    AlgebraicLawRegistration::new(
        "vyre-libs::dialect::my_op",
        AlgebraicLaw::Commutative,
    )
}
inventory::submit! {
    AlgebraicLawRegistration::new(
        "vyre-libs::dialect::my_op",
        AlgebraicLaw::Identity { element: 0 },
    )
}
```

The registration is pure metadata  -  CI does not verify the law
holds at this step; that's what the conformance harness (P5.4) does
against a witness corpus. Mis-declaring is a P0 correctness bug:
the optimizer will apply rewrites that produce wrong bytes.

## 5. Register one semantic operation

At the bottom of the operation's source file:

```rust
inventory::submit! {
    vyre_foundation::operation::OperationRegistration::library(
        "vyre-libs::dialect::my_op",
        || my_op("input", "output", 4),
        Some(|| vec![/* deterministic input cases */]),
        Some(|| vec![/* deterministic output cases */]),
    )
}
```

The canonical registry supplies the linked operation to validation, wire
round-trip, optimization, conformance, and generated catalog checks.

---

## Where the op lives

| Dialect | Path | Feature flag |
| --- | --- | --- |
| Linear algebra | `src/math/*.rs` | `math-linalg` |
| Scans + reductions | `src/math/scan.rs` | `math-scan` |
| Broadcasting | `src/math/broadcast.rs` | `math-broadcast` |
| NN activation | `src/nn/*.rs` | `nn-activation` |
| NN linear layers | `src/nn/linear.rs` | `nn-linear` |
| NN normalization | `src/nn/layer_norm.rs` | `nn-norm` |
| NN attention | `src/nn/attention.rs`, `src/nn/softmax.rs` | `nn-attention` |
| Substring search | `src/matching/substring.rs` | `matching-substring` |
| DFA / Aho-Corasick | `src/matching/aho_corasick.rs`, `src/matching/dfa_compile.rs` | `matching-dfa` |
| FNV hash | `src/crypto/fnv1a.rs` | `crypto-fnv` |
| BLAKE3 | `src/crypto/blake3.rs` | `crypto-blake3` |

Add a new dialect by creating `src/<dialect>/mod.rs` + registering a
feature flag in `Cargo.toml`. Re-export the op from `src/lib.rs`'s
`prelude` module so `use vyre_libs::prelude::*;` still works.

## What gets checked before merge

- `cargo check --workspace --all-features` (IR compiles).
- `cargo clippy --workspace --all-features --all-targets -- -D warnings`.
- `cargo test --workspace --all-features` (every op's conformance
  test, plus name-collision / overflow / assign-semantics / region
  lifetime regressions).
- `cargo doc --workspace --all-features --no-deps` (all intra-doc
  links resolve).
- `scripts/check_parity_testing_not_leaked.sh`.
- (WIP P3.7) op fingerprint unchanged vs. last release unless a
  CHANGELOG entry documents the break.

Any gate failing reverts the PR. The frozen core doesn't churn
for new ops; new ops conform to the frozen core.