docs: record shared memory formats and addressing

This commit is contained in:
Codex
2026-07-04 19:58:47 +02:00
parent a098962eec
commit 9546ad6c7b

View File

@@ -0,0 +1,629 @@
## Shared Memory Formats And Addressing
Date: 2026-07-04
Host: `DOLPHIN`
Scope: all shared-memory formats and SHM-adjacent IPC formats directly inspected from source during UV / BLUE-PRIME / DITAv2 work.
This document is intentionally concrete. It separates:
1. the **transport container** (`Zinc` region, or `iceoryx2` service),
2. the **payload framing inside that container**,
3. the **semantic payload schema** written by a given subsystem,
4. the **addressing rule** by which a writer and a reader find the same object.
It also records which format is actually in use for each known subsystem as inspected on this host.
### Short Answer
- A **Zinc region is addressed by its logical region name**, passed to `SharedRegion.create(name, ...)` / `SharedRegion.open(name)`.
- On Linux, Zinc materializes that as a POSIX SHM object named **`/zinc_<logical_name>`**, which appears in `/dev/shm` as **`zinc_<logical_name>`**.
- Therefore:
- reader/writer open **`uv_shadow_state`**
- the OS object visible under `/dev/shm` is **`zinc_uv_shadow_state`**
- opening `zinc_uv_shadow_state` through the Zinc API is wrong and fails
- Two algo instances avoid collision by using **different logical prefixes** and deriving all region names from that prefix.
For `iceoryx2`, the address is not a Zinc region name. It is the **service name** such as `uv/pulse`.
---
## 1. What Exists
### 1.1 Formats actually encountered
| Layer | Transport | Address form | Payload framing | Current role |
|---|---|---|---|---|
| Zinc region container | Zinc shared memory | logical name `name`; OS object `/dev/shm/zinc_<name>` | Zinc internal region header, then user data area | base transport for Zinc-backed regions |
| DITAv2 plane packet | Zinc region data area | `<prefix>_intent`, `<prefix>_state`, `<prefix>_control`, `<prefix>_venue` | `!QQ` = `(seq, json_size)` + UTF-8 JSON | DITAv2 real Zinc plane |
| DITAv2 control packet | Zinc region data area | `<prefix>_control` | same `!QQ + JSON` envelope | DITAv2 control plane |
| UV / BLUE-PRIME snapshot | Zinc region data area | `uv_shadow_state` | `UVZINC01` + dual-seq seqlock header + UTF-8 JSON | authoritative BLUE-PRIME shadow snapshot, live |
| UV hook frame | Zinc region data area | `uv_shadow_blue_prime_hooks` | same `UVZINC01` + dual-seq + UTF-8 JSON | hook observability frame, live/auxiliary |
| UV test scratch snapshot | Zinc region data area | `uv_t6_<id>_state` | same `UVZINC01` + dual-seq + UTF-8 JSON | test / temporary namespaces |
| UV pulse bridge payload | `iceoryx2` publish-subscribe service | service name `uv/pulse` | fixed 40-byte `PulseFrame` POD | optional derived feed for Rust TUI; not authoritative |
### 1.2 Not shared memory, but easy to confuse with it
The old file transport in `prod/clean_arch/violet/uv/shm.py`:
- explicit mode only
- writes JSON files under `UV_SHM_ROOT` / `/dev/shm/uv`
- **not** the runtime BLUE-PRIME contract
- retained for tests / fallback diagnostics only
That path is not documented further here because the request is specifically about shared memory.
---
## 2. Zinc Region Container Format
Authoritative sources:
- `zinc/core/src/header.rs`
- `zinc/core/src/region.rs`
- `zinc/core/src/platform/unix.rs`
- `zinc/adapters/python/zinc/_ffi.py`
- `zinc/adapters/python/zinc/__init__.py`
### 2.1 Addressing
Zinc validates and opens a **logical name** such as:
- `uv_shadow_state`
- `vst_dita_state`
- `dolphin_violet_control`
The Linux backend then maps that to a POSIX SHM object:
- logical name: `uv_shadow_state`
- OS shm object: `/zinc_uv_shadow_state`
- visible filesystem entry: `/dev/shm/zinc_uv_shadow_state`
This mapping is implemented in `zinc/core/src/platform/unix.rs`.
### 2.2 Name rules
Valid logical characters are:
- ASCII alphanumeric
- `_`
- `-`
Slashes are not allowed in raw Zinc names. Callers that start from path-like prefixes sanitize before opening.
### 2.3 Region layout
Every Zinc region is:
1. one page of Zinc-owned metadata/header
2. followed by the user data area
The Rust core exposes the user data area by returning `page_size()` bytes past the mapping base. The Python adapter likewise exposes only the user data area through `SharedRegion.as_buffer()`.
This point matters:
- the **underlying region really is Zinc**
- but a reader using `as_buffer()` does **not** see the Zinc header at byte 0
- it sees byte 0 of the **user payload**
That is why a live UV reader sees `UVZINC01` at the first visible bytes even though the mapped OS object is a Zinc region.
### 2.4 Zinc internal header
From `zinc/core/src/header.rs`:
- `magic: u64` = `"ZINC_REG"`
- `version: u16` = currently `2`
- `flags: u16`
- `notify_seq: AtomicU32`
- `capacity: u64`
- `ref_count: AtomicU32`
- `owner_pid: AtomicI32`
- `created_at: u64`
- `name_hash: u64`
- `ring_head: AtomicU64`
- `ring_tail: AtomicU64`
This header is aligned to one cache line and occupies the Zinc-managed metadata area, not the caller-visible payload buffer.
### 2.5 Notify/wait semantics
Zinc provides:
- `notify()`
- `wait(timeout_ms)`
These operate on `notify_seq` in the Zinc header. The payload framing on top of Zinc is owned by the higher-level subsystem.
---
## 3. DITAv2 Real Zinc Plane Format
Authoritative sources:
- `prod/clean_arch/dita_v2/real_zinc_plane.py`
- `prod/clean_arch/dita_v2/real_control_plane.py`
### 3.1 Addressing
`RealZincPlane(prefix=...)` derives names as:
- `base = prefix.strip("/").replace("/", "_")`
- `intent_name = f"{base}_intent"`
- `state_name = f"{base}_state"`
- `control_name = f"{base}_control"`
- `venue_name = f"{base}_venue"`
So if `prefix="vst/dita"`:
- logical names become:
- `vst_dita_intent`
- `vst_dita_state`
- `vst_dita_control`
- `vst_dita_venue`
- OS objects become:
- `/dev/shm/zinc_vst_dita_intent`
- `/dev/shm/zinc_vst_dita_state`
- `/dev/shm/zinc_vst_dita_control`
- `/dev/shm/zinc_vst_dita_venue`
### 3.2 Region capacities
Defaults in `RealZincPlane.__init__`:
- `intent_capacity = 1 << 20` = 1 MiB
- `state_capacity = 1 << 20` = 1 MiB
- `control_capacity = 1 << 20` = 1 MiB
- `venue_region` also uses `control_capacity`
### 3.3 Payload framing inside the Zinc data area
DITAv2 does **not** use the `UVZINC01` header.
It uses:
- `struct.pack("!QQ", seq, len(json_bytes))`
- followed by UTF-8 JSON bytes
That is:
- bytes `0..8`: `seq` as big-endian `u64`
- bytes `8..16`: JSON length as big-endian `u64`
- bytes `16..16+size`: JSON bytes
There is no dual-seq torn-read guard here. Readers trust:
- the header is present
- `size` is sane
- the JSON decodes
### 3.4 Semantic payloads
By region:
- `*_intent`: `{"items": [...]}` where items are serialized `KernelIntent`s
- `*_state`: `{"slots": [...]}` where slots are serialized `TradeSlot`s
- `*_control`: `{"control": {...}}` where value is a `KernelControlSnapshot`
- `*_venue`: `{"venue": {...}}` where value is a `VenueTelemetrySnapshot`
### 3.5 Write semantics
Writers:
- increment a region-local sequence
- build `!QQ + JSON`
- copy packet into the entire visible data buffer
- zero the tail
- call `region.notify()`
This is a simple packet format, not a seqlock format.
### 3.6 Current use
This is the intended real shared-memory format for DITAv2 when `RealZincPlane` / `RealZincControlPlane` are active.
At the time of this writing, I did **not** find live openable regions under the tested names:
- `vst_dita_intent`
- `vst_dita_state`
- `vst_dita_control`
- `vst_dita_venue`
- `dolphin_violet_intent`
- `dolphin_violet_state`
- `dolphin_violet_control`
- `dolphin_violet_venue`
So this format is source-authoritative, but not directly observed live under those tested prefixes at sample time.
---
## 4. UV / BLUE-PRIME Zinc Snapshot Format
Authoritative source:
- `prod/clean_arch/violet/uv/shm.py`
This is the important one for current UV / BLUE-PRIME observability.
### 4.1 Addressing
Default prefix:
- `UV_ZINC_PREFIX`, default `uv_shadow`
Logical region naming rule:
- `prefix.strip("/").replace("/", "_")`
- plus `_<slot>`
- except slot `"blue_prime"` and slot `"state"` are both normalized to `_<prefix>_state`
Examples:
- `publish("blue_prime", ...)` -> logical region `uv_shadow_state`
- `publish("state", ...)` -> logical region `uv_shadow_state`
- `publish("blue_prime_hooks", ...)` -> logical region `uv_shadow_blue_prime_hooks`
### 4.2 Region capacities
Default:
- `UV_ZINC_STATE_BYTES`, default `64 << 20` = 64 MiB
Observed live regions:
- `/dev/shm/zinc_uv_shadow_state`
- `/dev/shm/zinc_uv_shadow_blue_prime_hooks`
Observed test/scratch leftovers:
- `/dev/shm/zinc_uv_t6_8933e28c84_state`
- `/dev/shm/zinc_uv_t6_a1f7e3254b_state`
- `/dev/shm/zinc_uv_t6_b16924a96a_state`
- `/dev/shm/zinc_uv_t6_eed1be7947_state`
### 4.3 Payload framing inside the Zinc data area
Header:
- magic = `b"UVZINC01"`
- struct = `struct.Struct("!8sQQQ")`
Visible data-area layout:
1. bytes `0..8`: magic `"UVZINC01"`
2. bytes `8..16`: `seq_a` big-endian `u64`
3. bytes `16..24`: `seq_b` big-endian `u64`
4. bytes `24..32`: JSON payload size big-endian `u64`
5. bytes `32..32+size`: UTF-8 JSON payload
### 4.4 Write semantics
Writer uses a simple seqlock pattern:
1. compute next logical sequence `seq`
2. derive:
- `seq_even = seq * 2`
- `seq_odd = seq_even - 1`
3. write header with odd/in-flight sequence and `size = 0`
4. copy JSON body
5. optionally zero one byte after body
6. write header again with even/stable sequence and real `size`
7. call `region.notify()`
### 4.5 Read semantics
Reader accepts payload only if all are true:
- magic == `UVZINC01`
- `seq_a != 0`
- `seq_a` is even
- `seq_a == seq_b`
- `size` is in bounds
- after copying the body, rereading the header yields the exact same
- magic
- `seq_a`
- `seq_b`
- `size`
If any of that fails, the read is treated as torn / not yet initialized.
### 4.6 Semantic payloads
This format carries JSON snapshots rather than a fixed struct. Current known uses:
- `uv_shadow_state`
- authoritative BLUE-PRIME snapshot
- includes domains such as `meta`, `scan`, `live_inputs`, `engine`, `decision`, `efsm`, `dita`, `ram`, `source_trace`
- `uv_shadow_blue_prime_hooks`
- one published hook runner frame per scan
- keys include `scan`, `hooks`, `hook_count`, `ok_count`, `total_us`
### 4.7 Current use
This is the **live authoritative BLUE-PRIME shared-memory format** currently observed on host.
Verification made on host:
- `SharedRegion.open("uv_shadow_state")` succeeds
- `SharedRegion.open("zinc_uv_shadow_state")` fails
- first bytes of visible region buffer are `55565a494e433031...` = `UVZINC01`
That is the definitive proof that:
- the region container is Zinc
- the payload framing currently in use inside the container is `UVZINC01`
---
## 5. UV Hook Frame Region
Authoritative source:
- `prod/clean_arch/violet/uv/hooks/runner.py`
The hook runner publishes:
- `self.shm.publish("blue_prime_hooks", frame)`
Because `ShmChannel` defaults to Zinc-backed transport, that becomes:
- logical region: `uv_shadow_blue_prime_hooks`
- OS shm object: `/dev/shm/zinc_uv_shadow_blue_prime_hooks`
The payload framing is the same `UVZINC01` seqlock JSON envelope documented above.
The semantic payload differs:
- per-scan hook effects
- timing / ordering / success counts
This is auxiliary observability, not the main state snapshot.
---
## 6. iceoryx2 Pulse Bridge Format
Authoritative sources:
- `uv_tui/crates/pulse_frame/src/lib.rs`
- `uv_tui/bridge/src/lib.rs`
- `uv_tui/tui/src/lib.rs`
### 6.1 What it is
This is not the authoritative shared-memory snapshot. It is a **derived bridge feed**:
1. bridge opens Zinc region `uv_shadow_state`
2. bridge decodes the `UVZINC01` payload
3. bridge extracts a reduced pulse contract
4. bridge republishes that reduced contract over `iceoryx2` service `uv/pulse`
### 6.2 Addressing
Address is a service name, not a Zinc region name:
- service: `uv/pulse`
Current code hardcodes a singleton default service. Unlike Zinc prefixes, this is **not yet namespaced per parallel algo instance**.
### 6.3 Payload framing
Fixed `PulseFrame`, length 40 bytes:
- `observe_mono_ns: u64`
- `scan_number: u64`
- `region_seq: u64`
- `publish_latency_us: f64`
- `has_entry: u8`
- trailing reserved padding
Serialized little-endian by the shared `pulse_frame` crate.
### 6.4 Current use
- optional
- derived
- non-authoritative
- useful for the Rust TUI, especially when decoupling bridge and display
As of current Rust TUI work, the TUI can also read the Zinc region directly and no longer requires the bridge.
---
## 7. Addressing Rules, Precisely
### 7.1 Zinc regions
There are **three names** to keep distinct:
1. **logical region name**
- what code passes to Zinc
- example: `uv_shadow_state`
2. **POSIX SHM object name**
- what the Linux SHM API sees
- example: `/zinc_uv_shadow_state`
3. **filesystem entry under `/dev/shm`**
- example: `/dev/shm/zinc_uv_shadow_state`
The reader/writer contract uses **(1)**.
The operator inspecting `/dev/shm` sees **(3)**.
Confusing (1) and (3) is the common failure mode.
### 7.2 How a writer and reader agree on the same region
They must share:
- the same transport family
- Zinc or `iceoryx2`
- the same prefix / service namespace
- the same slot derivation rule
- the same payload framing
- the same semantic schema
For Zinc-backed UV:
- prefix source: `UV_ZINC_PREFIX`
- slot naming logic: `_slot_region(prefix, slot)`
- payload format: `UVZINC01`
- state slot: `blue_prime` -> `uv_shadow_state`
For DITAv2:
- prefix passed to `RealZincPlane(prefix=...)`
- suffixes: `_intent`, `_state`, `_control`, `_venue`
- payload format: `!QQ + JSON`
For `iceoryx2`:
- service name must match exactly
- payload struct must match exactly
### 7.3 How two running algo instances know “their” region
They do not discover “theirs” by magic. They must be started with a namespace choice.
Correct pattern:
- instance A:
- `UV_ZINC_PREFIX=uv_shadow_a`
- state region -> `uv_shadow_a_state`
- instance B:
- `UV_ZINC_PREFIX=uv_shadow_b`
- state region -> `uv_shadow_b_state`
Then:
- A writer writes only `uv_shadow_a_*`
- A TUI for A opens only `uv_shadow_a_*`
- B writer/TUI use `uv_shadow_b_*`
For test runs, this pattern is already used:
- `uv_t6_<id>` prefixes produce regions like `uv_t6_8933e28c84_state`
### 7.4 Collision rule
If two independent algo instances reuse the same Zinc logical region name:
- they are not isolated
- last writer wins
- readers observe a mixed stream
- observability becomes invalid
So region naming is not cosmetic. It is the namespace boundary.
---
## 8. What Is In Use For What, Now
### 8.1 BLUE-PRIME authoritative observability
- transport container: Zinc region
- logical region: `uv_shadow_state`
- payload framing inside region: `UVZINC01`
- semantic schema: BLUE-PRIME snapshot JSON
- status: live and observed
### 8.2 BLUE-PRIME hook observability
- transport container: Zinc region
- logical region: `uv_shadow_blue_prime_hooks`
- payload framing inside region: `UVZINC01`
- semantic schema: hook runner frame JSON
- status: live region observed
### 8.3 UV Rust TUI direct mode
- reads: Zinc region directly
- logical region by default: `uv_shadow_state`
- expects payload framing: `UVZINC01`
- status: implemented and verified
### 8.4 UV Rust TUI bridge mode
- bridge reads Zinc region `uv_shadow_state`
- bridge republishes `PulseFrame` over `iceoryx2` service `uv/pulse`
- TUI subscribes to `uv/pulse`
- status: implemented and verified, but secondary to direct Zinc reads
### 8.5 DITAv2 real shared-memory plane
- transport container: Zinc region
- logical regions: `<prefix>_{intent,state,control,venue}`
- payload framing: `!QQ + JSON`
- status: source-authoritative, but not directly observed live under tested prefixes during this inspection
---
## 9. Operator Notes
### 9.1 To inspect the live authoritative UV region
Use the logical name:
```python
from zinc import SharedRegion
r = SharedRegion.open("uv_shadow_state")
buf = r.as_buffer()
```
Do **not** do:
```python
SharedRegion.open("zinc_uv_shadow_state")
```
That is the `/dev/shm` object name, not the logical Zinc name.
### 9.2 To tell whether a region uses DITAv2 or UV framing
Look at the first visible bytes of `as_buffer()`:
- `UVZINC01` -> UV / BLUE-PRIME seqlock payload
- otherwise, if the first 16 bytes parse as `!QQ` and the JSON decodes, likely DITAv2 packet framing
You will **not** see `ZINC_REG` there through normal adapter reads, because that Zinc header lives before the exposed data area.
### 9.3 To run two independent observability stacks
Assign different prefixes up front. Example:
```bash
export UV_ZINC_PREFIX=uv_shadow_main
export UV_ZINC_PREFIX=uv_shadow_soak
```
Then point each consumer at its matching logical region names.
If `iceoryx2` bridge mode is used in parallel too, it needs the same kind of namespacing. Current bridge service default is singleton `uv/pulse`; that should be parameterized if simultaneous parallel bridges are required.
---
## 10. Bottom Line
The current live BLUE-PRIME observability stack is:
- **container:** Zinc shared memory
- **live authoritative region:** `uv_shadow_state`
- **live payload framing:** `UVZINC01` seqlock JSON
- **live hook side region:** `uv_shadow_blue_prime_hooks`
DITAv2s real Zinc plane is a different format:
- same Zinc container idea
- different region family
- different payload envelope: `!QQ + JSON`
The Rust `PulseFrame` is yet another layer:
- `iceoryx2` service payload
- derived from the authoritative Zinc region
- not itself the source of truth
The rule for addressing is simple and must be followed strictly:
- **open logical Zinc names through Zinc**
- **inspect `/dev/shm/zinc_*` only as an operator artifact**
- **namespace parallel instances by prefix**
- **match the payload framing to the subsystem**