# Stage 3 — Ownership

Verdict on this Mac: **PASS**, with the limits below. Linux: **PENDING**.

`Mojoler.Own` registers three resource types plus a pin type, in `load`. `unload` destroys the mutex and `free`s the private box. The upgrade callback returns 1. No test performed an OTP upgrade.

| Object | Allocator | Owner | Freed by |
| --- | --- | --- | --- |
| Counter / blob / retained / pin object | `enif_alloc_resource` | BEAM resource refcount | resource destructor after the last ref |
| Blob bytes | `calloc` | the blob resource | `free` in `blob_dtor` |
| Retained term | `enif_alloc_env` plus `enif_make_copy` | the retained resource | `enif_free_env` in `retained_dtor` |
| Entry and function table | `calloc` | OTP, after `nif_init` | not freed; bounded by the reload refusal |

`counter_add` takes the module mutex. Close is idempotent. A closed counter returns `{:error, :closed}`. The destructor sends `:counter_destroyed` once. Twenty concurrent adds of 1 land on 20.

`open_blob` copies the input into a `calloc` buffer and stores a token. `blob_poke` writes that buffer in place. The token does not change. Freeze fails while `borrows != 0`, then `enif_make_resource_binary` publishes the same bytes. A second freeze is `{:error, :frozen}`. Poke after freeze is `{:error, :not_mutable}`. Closing the handle leaves an already created resource binary readable.

`blob_pin` calls `enif_keep_resource`. Dropping the blob term does not run `blob_dtor` while the pin term is alive. Dropping the pin does. `blob_dtors/0` is the counter.

`retain_binary` copies into an independent env. The test reads `<<9, 8, 7>>` after the creating process has exited.

Elixir functions return terms. They do not return pointers. A strict no-copy request on a non-binary is `Mojoler.Dsl.Bytes.borrow_len/1`, which returns `{:error, :not_binary}`.

`scripts/probe-abort.sh` runs `abort` in a child BEAM (E26). That path is not in the product libraries.
