# behavior-contracts

**`behavior-contracts` (bc) is a DSL for declaring what a piece of behavior *means*.**

In most codebases, business logic, persistence, transactions, external calls, error handling and
execution order all end up tangled inside one function in one language. Responsibility boundaries
blur, the implementation and the documentation drift apart, and the order of execution gets baked
into the code.

bc separates the two questions. You declare **what the behavior means** — which components
compose into which result — as a contract. You do **not** write how it is orchestrated: execution
order is derived from the data dependencies, not written by hand. The declaration is ordinary
TypeScript, so it is simultaneously the specification *and* the running code.

```ts
import { behavior, type Int } from "behavior-contracts";

class UserQueries {
  @behavior
  static findByEmail(email: string): User[] {
    return Db.query(`SELECT id, email FROM users WHERE email = ?`, [email]);
  }
}
```

That method *is* the contract. `@behavior` is a marker: at runtime it does nothing — the method
runs as plain TypeScript — and at build time it marks the entry point bc reads.

## Because the meaning is implementation-independent, it can target any language

A declaration says what the behavior means, not how one language happens to implement it. That
independence is what makes the rest possible: **one declaration compiles to Go, Rust, Python and
PHP** — Go and Rust as genuinely native code running at hand-written-SDK-floor parity, Python and
PHP as modules driven by a small shared runtime. Compilation is deterministic and canonically
serialized, so the same declaration produces byte-identical results in every language.

Multi-language codegen is a *consequence* of the design, not the point of it.

```mermaid
flowchart TD
    TS["Declaration — ordinary TypeScript<br/>(the contract AND the running code)"]
    RUN["runs directly in TypeScript"]
    IR["bc-internal IR"]
    ML["Go · Rust · Python · PHP"]
    LEAF["leaf — the one hand-written<br/>I/O function per language"]

    TS --> RUN
    TS -->|"bc generate (build time)"| IR
    IR --> ML
    LEAF -. called by .-> RUN
    LEAF -. called by .-> ML
```

The IR is bc's own internal intermediate — never hand-written or hand-handled. The only code
hand-written per language is one op-agnostic **leaf transport** per leaf (the actual I/O), and the
library that owns the behaviors ships those.

> The declaration style bc implements is **Semantic Contract Programming (SCP)** — "contract the
> meaning itself as components, and build larger meaning by combining them". The full paradigm is
> specified in [`docs/semantic-contract-programming.md`](../docs/semantic-contract-programming.md);
> you do not need to read it to use bc.

> This is the README bundled with the published npm package. It mirrors the monorepo root
> README; the repository also holds the specs, conformance vectors and examples it links to.

## Who this is for

bc sits under a stack of three levels; the two audiences below are levels 2 and 3.

1. **behavior-contracts (bc)** — this package: the declaration substrate and its native codegen.
2. **A library built on bc** — a *bc consumer* (e.g. litedbmodel or graphddb), the
   **library author**. Its job is to express its own domain surface (e.g. litedbmodel's
   decorators) as bc declarations: its own decorators stay metadata-only collectors, and its
   lowering step produces the declaration-limited TypeScript that bc reads. It also **ships the
   per-language leaf transports** (Go / Rust / …). This is the audience that writes `@behavior` /
   `@leaf` methods and hand-writes each leaf transport once per language. It provides everything
   except the end user's model. → **Section A**.
3. **A user of such a library** — the **end user** (the library's consumer, e.g. a
   litedbmodel user). They declare their model with the *library's* own surface and run it. In
   **TypeScript** they simply **call the method** — the markers are no-ops at runtime, so the
   declaration is just TypeScript that runs. For **Go / Rust / Python / PHP** it is `bc generate`
   (the `bc` CLI, run directly) linked to the library's shipped leaf transports, then a call to the
   generated entry point. They write **no declarations for bc, no leaf transport, no IR** — just the
   model and the run call. → **Section B**.

If you are **building** a library on top of bc, read **Section A**. If you are **using** a
library that was built on bc, read **Section B**.

## Features

- **Native typed-native codegen for Go and Rust.** A covered behavior is emitted as
  straight-line native code — direct struct field access, concrete per-node calls, and a
  local error type — running at **SDK-floor parity** (~0–5% overhead vs. a hand-written
  SDK baseline) and **~2–3× faster than the shared interpreter**.
- **Zero-runtime generated modules.** The generated Go/Rust modules import **no**
  `behavior-contracts` runtime at all — a property the compiler verifies (a build gate
  rejects any runtime reference or boxing primitive that leaks into the output).
- **BC owns the wire type; one transport per leaf.** BC generates the concrete wire type
  (`WireValue`/`WireRow`/`WireList`) and all of the strict de-box. The only hand-written
  native code is a single op-agnostic leaf transport that reads its inputs from a `WireRow`
  payload and returns a `WireValue` — no trait, no probe protocol, no per-role signature.
- **Fail-closed, never silently boxed.** Shapes not yet natively coverable are rejected at
  generation time rather than falling back to a boxed/interpreted path labelled as native.
  An ambiguous or non-canonical type is rejected **loudly at build time**.
- **In TypeScript the declaration just runs.** `@behavior` / `@leaf` are no-op markers at
  runtime, so a declared method is an ordinary static method: calling it executes the leaf
  implementations directly at native JS speed — no interpreter, no lowering call, no generated
  file, zero runtime deps.
- **Shared interpreter for Python and PHP.** The same declaration emits an interpreter
  module that runs on the shared runtime with identical semantics (a TypeScript interpreter module
  is available too, when you want an emitted file rather than a direct call).
- **Deterministic, canonical serialization.** Key-sorted, typed-value encoding with a
  canonical decimal float representation — the same behavior always serializes to the same
  bytes, which is what makes cross-language conformance verifiable.

Native de-box codegen is for **Go and Rust**. **TypeScript** needs no codegen at all — the
declaration runs as-is; Python and PHP consume the compiled behavior through the shared
interpreter.

## What you can write in a declaration

A declaration body is ordinary TypeScript, but only the subset below carries meaning across
languages. Everything in it is written as **native TypeScript syntax** — bc reads the syntax and
derives the contract. Anything outside this subset is **rejected loudly at build time** (never
silently reinterpreted).

**Values and references**

| You write | Meaning |
|---|---|
| `email` (a parameter) | an input port |
| `row.title` | a field reference |
| `row?.title` | an optional reference (missing is a value, not an error) |
| `` `A#${id}` `` / `"a" + id` | string concatenation |
| `1`, `"x"`, `true`, `null`, `[…]`, `{…}` | literals (`Int` / `Float` distinguish integer from float) |

**Operators**

| You write | Meaning |
|---|---|
| `+` `-` `*` `/` `%` `-x` | arithmetic (`add` `sub` `mul` `div` `mod` `neg`) |
| `===` `!==` `<` `<=` `>` `>=` | comparison |
| `&&` `\|\|` `!` | boolean logic (operands must be `bool` — no truthiness) |
| `??` | default when null (`coalesce`) |
| `xs.length` | length of an array |

Loose equality (`==` / `!=`) and truthy conditions are rejected — the meaning must be unambiguous
in every target language.

**Control flow**

| You write | Meaning |
|---|---|
| `cond ? a : b` | choose a value, or choose which component runs |
| `if (cond) { … }` | run a component only when the condition holds (otherwise it is skipped) |
| `xs.map(x => …)` | run a component once per element |

Execution order is **not** written: bc derives it from the data dependencies, and independent work
is scheduled concurrently.

**Components (the units of meaning)**

| You write | Meaning |
|---|---|
| `@behavior static m(a: T): R` | a declared behavior — arguments are input ports, the return type is the output |
| `@leaf static f(a: T): R` | a leaf — the boundary where real I/O happens (one hand-written transport per language) |
| `Db.query(sql, params)` | calling a leaf = one component in the graph |
| `const x: T = …` | pins the result type of a node |
| `opt(x)` | make a reference optional |
| `xs.map(f, { into, batched })` | map options: zip the result into each element / batch the calls |
| `refsConnection(…, { dedupeKey, drop, implicitSource })` | fan out over references, de-duplicating them |

`@behavior` and `@leaf` are **markers**: at runtime they do nothing, so the method is an ordinary
static method that just runs. `opt` and `refsConnection` are the only two helpers with no native
TypeScript equivalent.

## Install

```bash
npm install behavior-contracts
```

- ESM-only library; TypeScript type declarations are included.
- Requires Node.js **22+**.
- No runtime dependencies. (`typescript` is a build-time-only optional peer, needed by the
  CLI to codegen from a `.ts` declaration module.)
- If you **run a generated TypeScript module**, keep `behavior-contracts` as a dependency of the
  project that runs it — the generated module pulls what it needs from this package itself.

---

# Section A — For library authors (building a library on bc)

You are writing the layer that maps your library's own DSL/decorators onto bc. Your decorators stay
metadata-only collectors; your lowering step turns that metadata into **declaration-limited
TypeScript** — the `@behavior` / `@leaf` methods below — and `bc generate --from` reads that source
**statically** (it parses the AST; it never executes your module). You also **ship one op-agnostic
leaf transport per leaf, per language** — so your users only declare a model and run it (Section B).

Because the lowering step produces source, one of your decorators can expand into **several** leaves
or a composed behavior: the structure you emit is your choice, and bc reads whatever declaration
comes out.

## Declare in TypeScript

A behavior is a `@behavior static` method. Its **parameters are the inputs** (natural positional
arguments — no bag object), and its **return type is the output**. A leaf is a `@leaf static`
method whose **signature is the contract** bc reads: parameters become the leaf's input ports and
the return type its output (a `T[]` return means the leaf yields many). Numeric precision uses the
`Int` / `Float` branded aliases — plain TS `number` is **rejected** because it is ambiguous between
a 64-bit int and a 64-bit float and bc does not infer.

```ts
import { behavior, leaf, type Int } from "behavior-contracts";

export interface User {
  id: Int;
  email: string;
}

// A leaf is a typed static method. Its SIGNATURE is what bc reads (input ports `sql` / `key`,
// output `User[]`). The BODY is the TypeScript run path — it is never touched by `bc generate`;
// the native languages call the leaf transport of the corresponding name instead.
export class Db {
  @leaf static Select(sql: string, key: string): User[] {
    return runYourQuery(sql, key);
  }
}

export class UserRepo {
  @behavior static findByEmail(email: string) {
    return Db.Select("SELECT id, email FROM users WHERE email = ?", email);
  }
}
```

That module is what your library ships (or generates from its own decorator surface); it is the
`--from <model.ts> --behavior <Class>` the `bc` CLI compiles for Go / Rust / Python / PHP, and in
TypeScript it is simply called (Section B).

Bodies are ordinary TypeScript, restricted to what carries a portable meaning: `+` / `?:` / `&&` /
`if` / `.map` / `?.` / template literals / comparisons all lower to the portable expression and
control vocabulary. Two helpers exist because native syntax cannot express them: `opt(x)` (an
optional reference — `opt(x).field`) and `refsConnection(list, cb, opts)` (a de-duplicated fan-out).
Anything outside the portable vocabulary is rejected loudly at build time rather than silently
mis-lowered.

### Declare where the chain de-boxes

A driver boundary is generically typed, so a leaf may declare its result as the opaque
`WireValue[]`. That result stays **wire** through the chain — nothing is re-boxed between hops — and
the **one point that declares a concrete type is where the de-box happens**. There are two ways to
declare it, and both are ordinary TypeScript that `tsc --noEmit` accepts:

```ts
// The op-agnostic transports: one per hop, no row type in the signature.
export class Db {
  @leaf static Exec(sql: string, params: WireValue[]): WireValue[] { … }
  @leaf static Materialize(rows: WireValue[]): User[] { … }   // declared concrete = the de-box point
}

export class UserRepo {
  // (1) the terminal LEAF declares the concrete type — no cast at the call site.
  @behavior static findAll(sql: string): User[] {
    const rows: WireValue[] = Db.Exec(sql, []);   // opaque hop: stays wire
    return Db.Materialize(rows);                  // the single de-box
  }

  // (2) the BINDING declares it — for one generic leaf that yields a different row type per call
  // site. Opaque → concrete is a narrowing, so TypeScript wants the assertion spelled out; the
  // ANNOTATION is what bc reads, and bc generates the runtime-checked de-box for it.
  @behavior static findAllInline(sql: string): User[] {
    const rows: User[] = Db.Exec(sql, []) as User[];
    return rows;
  }
}
```

A behavior's **declared return type is its output contract**: bc checks it against the type derived
from the body's terminal node and rejects a disagreement loudly (`OUTPUT_TYPE_MISMATCH`) instead of
silently emitting the derived one. So a cast alone never moves the contract — drop the annotation in
(2) and the build fails rather than quietly generating `Vec<WireValue>`. A chain that ends opaque is
rejected too (`BAD_TYPE_NOTATION`): opacity is a hop's property, and a caller needs a real type.

## Ship the per-language leaf transports

For every language your library targets natively, you ship one op-agnostic **leaf transport** per
leaf — this is the library's job, done once for all its users, so the end user writes none of it. A
`*-typed-native` module is runtime-free and fully typed: BC owns and generates the concrete wire
type and all of the strict de-box, so the transport implements **nothing** over it. It is a single
function, with the spec-fixed name, that reads its inputs from the `WireRow` payload by field name,
does the raw I/O, and builds a BC-owned `WireValue`:

- **Go** — `func <Sym>(payload wire.WireRow) (wire.WireValue, error)`
- **Rust** — `fn <sym>(payload: WireRow) -> Result<WireValue, BehaviorError>`

The **same one transport** serves a leaf across **all** its roles and cardinalities
(base / GSI / limited / batched / fanout) — divergent port sets are just different payload fields,
and there is no per-role signature. The generated native hot path stays fully typed; only the leaf
boundary is wire (de-box once). The default symbol name is `Leaf_<comp>` (Go) / `leaf_<comp>`
(Rust), and `--leaf-transport <leaf>=<sym>` overrides it per leaf.

```go
package tx

import "example.com/app/wire"

// One op-agnostic function per leaf. Reads its inputs from the payload by name, does the raw
// I/O, and builds the BC-owned WireValue. The covered module owns all classification/de-box.
func Leaf_Select(payload wire.WireRow) (wire.WireValue, error) {
	email := payload.ProbeString("key").Got // the port the covered module bound
	_ = email
	return wire.WireListOf([]wire.WireValue{
		wire.WireRowOf([]wire.WireField{
			{Key: "id", Val: wire.WireInt(1)},
			{Key: "email", Val: wire.WireStr(email)},
		}),
	}), nil
}
```

`opt` fields are key-omittable (absent → `None`/null); an integer for a `float` slot is widened. A
type the emitter cannot bake, or an output type inconsistent with its node types, is a
generation-time error — never silently-broken code. See
[`docs/strict-typing-and-debox.md`](../docs/strict-typing-and-debox.md) for the full typing + de-box
contract.

### BC-owned wire types & the cross-package / cross-crate layout

BC generates the concrete wire types. By default they live in the covered module and the transport
references them back (legal within a single Rust crate or Go package). For a layout where the
transport lives in its own package/crate, `--shared-types-out` emits the BC-owned wire types (plus
the batched/fanout batch ports structs; Rust also the shared `BehaviorError`) into a shared
package/crate, and the covered module imports them via `--shared-types-import` — so the transport
package returns them with no import cycle (covered → transport → shared). `--leaf-transport-import`
names where the transport symbols are imported from. The library runs these emits once and ships the
result:

```bash
# Go cross-package — emit ONLY the shared wire package (no --out ⇒ no covered module here):
bc generate --lang go-typed-native --from model.ts --behavior UserRepo \
  --shared-types-out wire/wire.go \
  --shared-types-import example.com/app/wire \
  --leaf-transport-import example.com/app/tx

# Rust cross-crate:
bc generate --lang rust-typed-native --from model.ts --behavior UserRepo \
  --shared-types-out wire/src/lib.rs \
  --shared-types-import bcwire \
  --leaf-transport-import bctx
```

Runnable end-to-end samples of the library side + a shared wire seam:
[`examples/building-a-library-on-bc/`](../examples/building-a-library-on-bc/) (custom decorators →
declarations + Go/Rust transports) and
[`examples/library-lowering-patterns/`](../examples/library-lowering-patterns/) (metadata → **emitted**
declaration source, one relation declaration expanding into several leaf calls, and CQRS derived by
the library), plus the standalone cross-layout samples
[`examples/go-cross-package/`](../examples/go-cross-package/) (Go, separate transport package),
[`examples/rust-cross-crate/`](../examples/rust-cross-crate/) (Rust, separate transport crate), and
[`examples/rust-cross-module/`](../examples/rust-cross-module/) (Rust, intra-crate — no shared crate
needed).

## Public API reference

Everything you import from `behavior-contracts` when declaring behaviors — this is the whole list.
Cross-language **codegen** is CLI-only: the `bc` binary is the sole public codegen entry, so no
emitter and no compile function appears here.

| Export | Kind | Purpose |
|---|---|---|
| `behavior` | decorator | Marks a `static` method as one root behavior. No-op at runtime; the entry point bc reads at build time. |
| `leaf` | decorator | Marks a `static` method as one leaf. Its signature is the contract (parameters → input ports, return type → output); its body is the TypeScript run path. |
| `opt` | fn | Optional reference — `opt(x).field` lowers to an absence-tolerant read. Identity at runtime. |
| `refsConnection` | fn | De-duplicated fan-out over a list (`dedupeKey`, optional `implicitSource`), returning `{ items, cursor }`. |
| `AuthoringFailure` | error | Thrown for an invalid declaration (e.g. a non-portable body, ambiguous `number`). |
| `Int` / `Float` | type | Branded numeric aliases that disambiguate 64-bit int vs. float (plain `number` is rejected). |
| `AuthoringFailureCode` | type | Discriminant codes for `AuthoringFailure`. |

## Spec versions

This package tracks the IR/vector protocol versions independently from its
library (semver) version:

| Spec | Version |
|---|---|
| expression | 2 |
| template | 1 |
| plan | 1 |
| canonical | 2 |
| behavior | 5 |
| guard | 3 |
| c2 | 1 |
| provenance | 1 |
| envelope | 1.1 |

---

# Section B — For users of a library built on bc

You are using a library (e.g. litedbmodel or graphddb) that was built on bc. You never write leaf
transports or IR — the library already did all of that (Section A). You **declare a model** with the
library's surface, name your operations, then **run it**.

## 1. Declare your model with the library's surface

You describe your model with the **library's own surface** — its decorators, its schema DSL, whatever
it exposes. bc does **not** dictate this; the library does. An endpoint is a `@behavior static`
method whose body calls the library's leaf:

```ts
import { entity, field, behavior, Db } from "your-library";

@entity("users")
class User {
  @field.int id!: number;
  @field.string email!: string;
}

export class UserRepo {
  @behavior static findByEmail(email: string) {
    return Db.Select("SELECT id, email FROM users WHERE email = ?", email);
  }
}
```

## 2a. Run it in TypeScript — just call it

`@behavior` / `@leaf` are no-op markers at runtime, so the declaration is ordinary TypeScript:
calling the method runs the library's leaf implementation directly, at native JS speed. There is no
lowering call, no interpreter, no `bc generate`, and no generated file:

```ts
import { UserRepo } from "./model.ts"; // your model, declared with the library's decorators

const rows = UserRepo.findByEmail("a@b.c"); // a plain static-method call
// rows === [{ id: 1n, email: "a@b.c" }]    (int is a bigint in bc's value model)
```

Runnable sample:
[`examples/using-a-bc-library/run-ts.mjs`](../examples/using-a-bc-library/run-ts.mjs).

## 2b. Run it in Go / Rust / Python / PHP — `bc generate`

For the other languages the `behavior-contracts` package installs a `bc` binary you run **yourself**
(not through the library). `bc generate --from <model.ts> --behavior <Class>` reads your TypeScript
**source** with the TypeScript compiler — type-extracting each method's parameter and return types
and lowering its body's AST — and compiles source → ir → native (Go / Rust) or a shared-runtime
interpreter module (Python / PHP) in **one pass**. Your module is never executed. You wire it to the
library's shipped leaf transports + BC-owned wire types with `--leaf-transport-import` /
`--shared-types-import` — you write neither; the library ships them (Section A):

```bash
# Go — native, wired to the library's transport + wire packages:
bc generate --lang go-typed-native   --from model.ts --behavior UserRepo \
  --out go/behaviors/covered.go   --shared-types-import bcorm/wire   --leaf-transport-import bcorm/tx

# Rust — native:
bc generate --lang rust-typed-native --from model.ts --behavior UserRepo \
  --out rust/src/behaviors.rs     --shared-types-import bcorm_wire    --leaf-transport-import bcorm_tx

# Python / PHP — the shared-runtime interpreter module:
bc generate --lang python --from model.ts --behavior UserRepo --out behaviors_generated.py
bc generate --lang php    --from model.ts --behavior UserRepo --out BehaviorsGenerated.php

# Drift gate — non-zero exit if the committed --out differs from a fresh generation:
bc check --lang go-typed-native --from model.ts --behavior UserRepo --out go/behaviors/covered.go
```

Full flag reference: `bc generate --help` (also generated at
[`docs/cli-reference.md`](./docs/cli-reference.md)). `--in <ir-doc.json>` is an
internal/advanced input; the consumer surface is `--from`.

## 3. Call the generated entry point

The covered module exposes a typed entry function per behavior: you pass a typed input struct and get
a typed output struct back. The transport + wire wiring is **automatic** — baked into the covered
module at generate time by the import flags — so there is no runtime injection at the call site, and
the covered module de-boxes the leaf's wire result **once** into the concrete typed output:

```go
// go — the wiring to bcorm/tx + bcorm/wire is baked in; the call is just typed-in, typed-out.
rows, err := behaviors.RunNativeRawStruct_findByEmail(behaviors.In_findByEmail{Email: "a@b.c"})
```
```rust
// rust
let rows = run_native_raw_struct_findByEmail(InNRFindByEmail { email: "a@b.c".to_string() })?;
```

A `--lang python` / `--lang php` module runs on the shared runtime with the IR embedded. A
`--lang typescript` interpreter module (an alternative to calling the declaration directly, when you
want an emitted file) embeds the compiled IR and walks it at run time — it wires itself up, so
there is nothing to import by hand.

The complete, runnable end-user example — one model, with the direct TypeScript run and the
Go / Rust `bc generate` runs all printing the same row — is
[`examples/using-a-bc-library/`](../examples/using-a-bc-library/).

---

## Documentation

Deeper design and specification documents live alongside the package:

- [`docs/cli-reference.md`](./docs/cli-reference.md) — the full `bc generate` /
  `bc check` option reference.
- [`docs/strict-typing-and-debox.md`](../docs/strict-typing-and-debox.md) — mandatory typing,
  the strict de-box, the BC-owned wire type, and the leaf transport (the full contract for the
  above).
- [`docs/concept.md`](../docs/concept.md) — the concept and specification overview.
- [`docs/expression-ir.md`](../docs/expression-ir.md) — the Expression IR: the closed set of
  permitted operators and their evaluation semantics.
- [`docs/canonical-serialization.md`](../docs/canonical-serialization.md) — canonical
  serialization rules (key sort order, typed-value encoding, canonical float representation).
- [`docs/template-rendering.md`](../docs/template-rendering.md) — template binding / rendering
  rules.
- [`docs/execution-plan.md`](../docs/execution-plan.md) — execution-plan semantics (stage
  groups, concurrency, skip propagation, error policy).
- [`docs/INTEGRATION.md`](../docs/INTEGRATION.md) — integration guide for consuming the runtime
  across languages.
- [`examples/`](../examples/) — minimal, dependency-free, build-free runnable samples plus the
  Expression IR reference evaluator and golden vectors.

## License

MIT — see [LICENSE](./LICENSE).
