Skip to content

Add: the BufferHandle/Tensor wire ABI, its codec, and create_buffer - #1599

Open
YunjiQin wants to merge 2 commits into
hw-native-sys:mainfrom
YunjiQin:p1-b-abi-foundation
Open

Add: the BufferHandle/Tensor wire ABI, its codec, and create_buffer#1599
YunjiQin wants to merge 2 commits into
hw-native-sys:mainfrom
YunjiQin:p1-b-abi-foundation

Conversation

@YunjiQin

@YunjiQin YunjiQin commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

PR-1 of the P1-B split: the wire ABI on its own

Split out of #1453 at reviewer request — that PR is ~3× the repo's oversize threshold for core
churn, and this is the piece that most needs to be read in its own context: a frozen wire ABI.
Field sets, enum values, and byte offsets are hard to change once they ship, and in #1453 they sat
underneath a large consumer migration.

One commit, +2218/−3 across 12 files — almost pure addition and zero behavior change. Nothing
in the tree dispatches a Tensor yet.

What lands here

Three wire types in src/common/task_interface/buffer_handle.h,
each pinned by static_assert on size and every field offset so a layout drift fails the build:

Type Size What it is
CanonicalIdentity 32 B owner_instance_id + buffer_id + generation. The key the owner registry and every consumer import cache share. Fixed-length, no length field, padding excluded from equality/hash.
BufferHandleDescriptor 88 B The self-describing backing: address space, access, backend kind, extent, length-delimited backend body.
BufferRef 144 B The blob element: descriptor embedded whole + a strided view. No materialized address.

One validator. validate_buffer_ref bounds body_len and ndims, checks the
address_space × backend_kind matrix, requires strides > 0 and a known dtype, and rejects a view
extending past its backing. The blob codec lives beside the types it encodes and validates each
element on extraction, so no decoder can hand out an unchecked element.

Owner and consumer halves. Worker.create_buffer allocates a POSIX-shm backing under a fresh
identity and unlinks it on close(); ImportRegistry is the consumer side (map-once, by identity).

Also: bounds-checks get_element_size (it indexed its table with a raw u8 that now arrives
from the wire — a genuine OOB read), and moves MAX_TENSOR_DIMS into data_type.h so the view
types can share it.

Reviewing this

The three questions worth spending time on:

  1. Are the field sets right? Each one is permanent. owner_worker_path_id is deliberately
    diagnostic-only and takes part in no routing, visibility, or identity decision.
  2. Is validate_buffer_ref complete? It is the only thing standing between a corrupted or
    hostile blob and a pointer dereference. test_decode_rejects_corrupted_field walks a
    field-by-field corruption matrix through the decoder; test_decode_rejects_arbitrary_bytes
    throws 256 random buffers plus all-zeros and all-ones at it — none may be accepted, none may
    crash.
  3. Is the identity model sound? owner_instance_id is a per-incarnation random draw, so a
    buffer_id reused by a later process cannot collide with a live identity. generation starts
    at 1 and 0 is rejected on decode.

The design rationale for keeping Tensor and ChipTensor as two types — with the two rejected
unifications and why — is in docs/buffer-handle-abi.md.

What is deliberately NOT here

create_buffer ships an unwired capability: you can allocate a handle, name a view over it, and
materialize a blob, but TaskArgs still carries ChipTensor, so nothing dispatches a Tensor yet.
That is the intended cut — submitting one, the per-consumer split (chip leaf / Python sub-worker /
L4→L3 re-export), the device-memory allocators (alloc_shared_tensor, alloc_child_tensor), and
the submit-time guards land with the dispatch wire.

docs/buffer-handle-abi.md describes the model end to end, including the parts this PR does not
ship — the submit path, the three-way consumer split, the submit-time checks. Its "Scope / status"
section says plainly which of those the tree has and which the wire flip adds, so the page is a
complete reference without implying the whole thing is live. Two other places name the device POD
ChipTensor, which is the model's term for it; its Python binding is still exported as Tensor and
is renamed with the wire flip — the page says so where it matters.

The Python binding surface for the wire type (materialize_bufferref_blob) and the
TensorChipTensor binding rename were prepared and then deliberately held back from this PR,
to keep it to the ABI and its own tests.

Verification

  • pyut: 913 passed, 2 skipped.
  • cpput: 67/67 no-hardware, including the new test_buffer_handle (layout, validation, codec).
  • a2a3sim scene tests: 43 passed.
  • pre-commit clean (check-headers, clang-format, clang-tidy, cpplint, markdownlint, ruff, pyright).

Follow-ups from the same split: capability matrix + access ⊆ granted guards; device-memory
ownership Orchestrator→Worker; deleting the create_host_buffer/MAP_HOST subsystem; and the wire
flip itself (#1453, to be rebased onto this).

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f48e3e54-5d30-48cd-91c2-0060843ce8c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces a typed BufferHandle and tensor-view ABI with canonical identities, backend descriptors, validation, blob serialization, Python materialization, worker-owned shared-memory allocation, bindings, documentation, and C++/Python tests.

Changes

BufferHandle ABI

Layer / File(s) Summary
ABI contracts and documentation
src/common/task_interface/*, docs/*, mkdocs.yml, tests/ut/cpp/*
Defines fixed-layout identities, descriptors, tensor views, backend tags, validation rules, blob codecs, shared datatype constants, documentation, and ABI-focused tests.
Python packing and materialization
python/simpler/buffer_handle.py, python/bindings/task_interface.cpp, tests/ut/py/test_buffer_handle.py
Adds Python wire-format types, handle constructors, shared-memory and pointer-backed materialization, blob helpers, bindings, and decode/runtime validation tests.
Worker allocation and teardown
python/simpler/worker.py
Adds Worker.create_buffer, per-worker identity tracking, owned-handle registration, and shared-memory cleanup during teardown.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

A rabbit hops through shared memory bright,
Naming each buffer with identity right.
Strides curl softly, blobs cross the way,
Workers clean handles at close of day.
“Nibble the ABI—hooray!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately summarizes the main change: the BufferHandle/Tensor wire ABI, its codec, and create_buffer.
Description check ✅ Passed The description is directly about the same ABI, codec, and create_buffer work described in the changeset.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/ut/cpp/types/test_buffer_handle.cpp (1)

71-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

EnumValuesAreFrozen doesn't pin BackendKind::FORK_COW.

Every other BackendKind value (0-4) is checked here but FORK_COW = 5 is missing, even though the test's stated purpose is to "pin the sizes, enum values, and the blob codec from the outside". Trivial to add for parity with the rest of this test.

✅ Proposed addition
     EXPECT_EQ(static_cast<uint8_t>(BackendKind::DEVICE_MALLOC), 4);
+    EXPECT_EQ(static_cast<uint8_t>(BackendKind::FORK_COW), 5);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ut/cpp/types/test_buffer_handle.cpp` around lines 71 - 82, Add an
assertion in BufferHandleAbi.EnumValuesAreFrozen that verifies
BackendKind::FORK_COW converts to uint8_t value 5, preserving the existing
checks for BackendKind values 0–4.
src/common/task_interface/buffer_handle.h (1)

313-328: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

BufferRefBlobView::ref(i) has no bounds check on i.

ref(i) memcpy's sizeof(BufferRef) bytes at ref_bytes + i * sizeof(BufferRef) with no check that i < ref_count (or i >= 0). Current callers (bufferref_blob_descriptors/refs/scalars in task_interface.cpp) all loop i < view.ref_count, so this isn't exploitable today, but the function is documented as the single shared extraction point for three receive boundaries, so a future caller passing an unchecked index would read out of the validated region.

Optional: add an i < ref_count assert/throw inside ref() itself for defense-in-depth.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/task_interface/buffer_handle.h` around lines 313 - 328, Add bounds
validation to BufferRefBlobView::ref before computing the byte offset or calling
memcpy, rejecting indices below zero or greater than or equal to ref_count with
the established assertion or exception mechanism. Preserve the existing
BufferRef validation and extraction behavior for valid indices.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/buffer-handle-abi.md`:
- Around line 174-186: Add a VMM_WINDOW row to the Backends table, documenting
that it materializes as the device VA carved by allocate_domain and is used for
communication-domain window buffers. Keep it alongside DEVICE_MALLOC and
preserve the table’s wire-ABI completeness statement.

In `@python/simpler/buffer_handle.py`:
- Around line 239-242: Update the layout comment immediately above
_BUFFER_REF_TAIL to use the actual wire sizes: BufferRef 144 B and
BufferHandleDescriptor 88 B, while preserving the existing field description and
size assertion.

In `@src/common/task_interface/buffer_handle.h`:
- Around line 80-96: Update validate_buffer_ref to reject any BufferRef whose
backend_kind is FORK_COW unless access is exactly READ, preserving the existing
address_space/backend_kind validation for all other combinations. Add or extend
C++ validation tests to cover FORK_COW with WRITE and READWRITE access as
rejected, while retaining acceptance for FORK_COW with READ.

---

Nitpick comments:
In `@src/common/task_interface/buffer_handle.h`:
- Around line 313-328: Add bounds validation to BufferRefBlobView::ref before
computing the byte offset or calling memcpy, rejecting indices below zero or
greater than or equal to ref_count with the established assertion or exception
mechanism. Preserve the existing BufferRef validation and extraction behavior
for valid indices.

In `@tests/ut/cpp/types/test_buffer_handle.cpp`:
- Around line 71-82: Add an assertion in BufferHandleAbi.EnumValuesAreFrozen
that verifies BackendKind::FORK_COW converts to uint8_t value 5, preserving the
existing checks for BackendKind values 0–4.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d442b3d1-f5cd-43eb-b613-298f4efd8dd7

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd6c3b and cb27557.

📒 Files selected for processing (12)
  • docs/README.md
  • docs/buffer-handle-abi.md
  • mkdocs.yml
  • python/bindings/task_interface.cpp
  • python/simpler/buffer_handle.py
  • python/simpler/worker.py
  • src/common/task_interface/buffer_handle.h
  • src/common/task_interface/data_type.h
  • src/common/task_interface/tensor.h
  • tests/ut/cpp/CMakeLists.txt
  • tests/ut/cpp/types/test_buffer_handle.cpp
  • tests/ut/py/test_buffer_handle.py
💤 Files with no reviewable changes (1)
  • src/common/task_interface/tensor.h

Comment thread docs/buffer-handle-abi.md Outdated
Comment on lines +174 to +186
## Backends

| Backend | Materializes to | Used for |
| ------- | --------------- | -------- |
| `POSIX_SHM` | a named shm mapped into the consumer | `create_buffer` |
| `FORK_SHM` | the same VA, no map | a pre-fork `MAP_SHARED` host buffer (e.g. `share_memory_()`), writable from the child |
| `FORK_COW` | the same VA, no map | a pre-fork plain host buffer: copy-on-write, so **READ only** |
| `DEVICE_MALLOC` | the device pointer, no map (chip-local) | device memory on a next-level worker |
| `REMOTE_SIDECAR` | resolved via the remote transport | an arg to a remote L3; the descriptor rides in the sidecar |

The enum is complete because it is wire ABI: a value must mean the same thing in
every process of a run, so backends are declared here even where the code that
allocates or materializes them arrives later.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Backends table omits VMM_WINDOW.

The table lists POSIX_SHM, FORK_SHM, FORK_COW, DEVICE_MALLOC, REMOTE_SIDECAR but not VMM_WINDOW, even though wrap_vmm_window() (device VA wrap, address_space=DEVICE) is already implemented in python/simpler/buffer_handle.py as part of this same PR. Worth adding a row (e.g. "the device VA carved by allocate_domain | comm-domain window buffers") for completeness alongside DEVICE_MALLOC.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/buffer-handle-abi.md` around lines 174 - 186, Add a VMM_WINDOW row to
the Backends table, documenting that it materializes as the device VA carved by
allocate_domain and is used for communication-domain window buffers. Keep it
alongside DEVICE_MALLOC and preserve the table’s wire-ABI completeness
statement.

Comment thread python/simpler/buffer_handle.py Outdated
Comment on lines +239 to +242
# BufferRef (272 B): BufferHandleDescriptor(216) + byte_offset u64, ndims u32, shapes[MAX] u32,
# strides[MAX] u32, dtype u8, _pad[3].
_BUFFER_REF_TAIL = struct.Struct(f"<QI{MAX_TENSOR_DIMS}I{MAX_TENSOR_DIMS}IB3x")
assert BUFFER_HANDLE_DESCRIPTOR_BYTES + _BUFFER_REF_TAIL.size == BUFFER_REF_BYTES, "BufferRef layout drift"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale/incorrect size comment.

The comment says "BufferRef (272 B): BufferHandleDescriptor(216)", but the actual wire sizes (per buffer_handle.h's static_asserts and the assert two lines below) are BufferRef=144B and BufferHandleDescriptor=88B — matching every other comment/doc in this PR. Looks like a leftover from an earlier draft of the layout.

✏️ Proposed fix
-# BufferRef (272 B): BufferHandleDescriptor(216) + byte_offset u64, ndims u32, shapes[MAX] u32,
-#   strides[MAX] u32, dtype u8, _pad[3].
+# BufferRef (144 B): BufferHandleDescriptor(88) + byte_offset u64, ndims u32, shapes[MAX] u32,
+#   strides[MAX] u32, dtype u8, _pad[3].
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# BufferRef (272 B): BufferHandleDescriptor(216) + byte_offset u64, ndims u32, shapes[MAX] u32,
# strides[MAX] u32, dtype u8, _pad[3].
_BUFFER_REF_TAIL = struct.Struct(f"<QI{MAX_TENSOR_DIMS}I{MAX_TENSOR_DIMS}IB3x")
assert BUFFER_HANDLE_DESCRIPTOR_BYTES + _BUFFER_REF_TAIL.size == BUFFER_REF_BYTES, "BufferRef layout drift"
# BufferRef (144 B): BufferHandleDescriptor(88) + byte_offset u64, ndims u32, shapes[MAX] u32,
# strides[MAX] u32, dtype u8, _pad[3].
_BUFFER_REF_TAIL = struct.Struct(f"<QI{MAX_TENSOR_DIMS}I{MAX_TENSOR_DIMS}IB3x")
assert BUFFER_HANDLE_DESCRIPTOR_BYTES + _BUFFER_REF_TAIL.size == BUFFER_REF_BYTES, "BufferRef layout drift"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/simpler/buffer_handle.py` around lines 239 - 242, Update the layout
comment immediately above _BUFFER_REF_TAIL to use the actual wire sizes:
BufferRef 144 B and BufferHandleDescriptor 88 B, while preserving the existing
field description and size assertion.

Comment on lines +80 to +96
// Materialization backend of a handle. The consumer resolves a BufferRef to a local address via the
// import registry keyed by canonical identity; this tag selects how. REMOTE_SIDECAR is reserved for
// P2 and rejected on decode in P1. Values are frozen; 6.. reserved (unknown tag => reject).
//
// FORK_SHM and FORK_COW materialize identically — the body is a base VA the child already has,
// inherited across the fork — but the kernel's write semantics are opposite, so they are separate
// tags rather than one tag plus a hint. A child's write to a MAP_SHARED page lands in the physical
// page the parent reads; a write to a copy-on-write page splits it into a private copy the parent
// never sees, silently. FORK_COW therefore grants READ only, and that is enforced on decode.
enum class BackendKind : uint8_t {
FORK_SHM = 0,
POSIX_SHM = 1,
VMM_WINDOW = 2,
REMOTE_SIDECAR = 3,
DEVICE_MALLOC = 4,
FORK_COW = 5,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

validate_buffer_ref doesn't enforce the FORK_COW read-only invariant it documents.

The BackendKind doc comment states "FORK_COW therefore grants READ only, and that is enforced on decode." But validate_buffer_ref — described as "the single implementation behind all three trust boundaries" (builder, blob decode, materialization) — never checks access against backend_kind; it only gates address_space × backend_kind. A wire BufferRef with backend_kind=FORK_COW and access=WRITE/READWRITE currently passes this validator.

Today this is latent: python/simpler/buffer_handle.py's BufferHandleDescriptor.__post_init__ independently re-checks this on every Python-side unpack, so the two consumption paths shown in this PR (mapped_args_from_blobTensor.unpackBufferHandleDescriptor.unpack) are still protected. But a pure-C++ consumer (the builder TaskArgs.add_tensor, or any future device-side/dispatch consumer explicitly called out in this validator's own docstring) gets no such protection, and the header's own claim about validate_buffer_ref is factually wrong for this field. The C++ unit test suite also has no case for it (EnumValuesAreFrozen/test_decode_rejects_corrupted_field equivalents never exercise FORK_COW + non-READ access).

Since the PR explicitly frames this validator as needing "isolated review before consumer migration," this is worth closing now rather than after a future consumer starts relying on it.

🔒 Proposed fix
     // address_space x backend_kind capability gate. REMOTE_SIDECAR is legal in either space.
     const auto backend = static_cast<BackendKind>(h.backend_kind);
     const bool device_space = h.address_space == static_cast<uint8_t>(AddressSpace::DEVICE);
     if (backend != BackendKind::REMOTE_SIDECAR) {
         const bool device_backend = backend == BackendKind::VMM_WINDOW || backend == BackendKind::DEVICE_MALLOC;
         if (device_backend != device_space) reject("invalid Tensor: unsupported address_space x backend_kind");
     }
+
+    // FORK_COW is copy-on-write: a write splits the page into a private copy the owner never sees,
+    // silently. Grant READ only, mirroring BufferHandleDescriptor.__post_init__ in
+    // simpler.buffer_handle so the two decode paths cannot drift apart.
+    if (backend == BackendKind::FORK_COW && h.access != static_cast<uint8_t>(AccessMode::READ)) {
+        reject("invalid Tensor: FORK_COW backend grants READ only");
+    }

Also applies to: 236-273

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/task_interface/buffer_handle.h` around lines 80 - 96, Update
validate_buffer_ref to reject any BufferRef whose backend_kind is FORK_COW
unless access is exactly READ, preserving the existing
address_space/backend_kind validation for all other combinations. Add or extend
C++ validation tests to cover FORK_COW with WRITE and READWRITE access as
rejected, while retaining acceptance for FORK_COW with READ.

@YunjiQin
YunjiQin force-pushed the p1-b-abi-foundation branch from b2e2bc9 to 57a89fd Compare July 30, 2026 12:39
At L3 and above a task argument is currently a raw pointer plus a
`child_memory` bool, which cannot say which process the address is valid in,
which backing it belongs to, or whether the holder may write it. This adds the
typed ABI that replaces it, with nothing consuming it yet.

Three wire types in one header, each pinned by static_assert on size and every
field offset, so a layout drift fails the build rather than the run:

- `CanonicalIdentity` (32 B) — owner_instance_id + buffer_id + generation. The
  key an owner registry and every consumer import cache share. Fixed-length
  with no length field, so hashing and comparison cannot read past it, and
  padding is excluded from equality so two decodes of one backing key alike.
- `BufferHandleDescriptor` (88 B) — the self-describing backing: address space,
  access, backend kind, extent, and a length-delimited backend body.
- `BufferRef` (144 B) — the blob element: the descriptor embedded whole plus a
  strided view. No materialized address; a consumer resolves it on receipt.

`validate_buffer_ref` is the single validator every trust boundary runs. It
bounds `body_len` and `ndims`, checks the address_space × backend matrix,
requires strides > 0 and a known dtype, and rejects a view that extends past
its backing. The blob codec lives beside the types it encodes, and its reader
validates each element as it is extracted, so a decoder cannot hand out an
element nobody checked.

`Worker.create_buffer` allocates a POSIX-shm backing under a fresh identity;
the Worker owns every handle it created and unlinks the backings on close().
`ImportRegistry` is the consumer half — map-once, keyed by identity.

Also bounds-checks `get_element_size`, which indexed its table with a raw u8
that now reaches it from the wire, and moves `MAX_TENSOR_DIMS` to `data_type.h`
so the view types can share it with `Tensor`.

The ABI is frozen once it ships, which is why it lands on its own: the byte
layout, the enum values, and the validator are what this change is asking to
have reviewed. Naming a `Tensor` and submitting it, the per-consumer
materialization split, and the submit-time guards follow separately.
@YunjiQin YunjiQin changed the title P1-B PR-1: the BufferHandle/Tensor wire ABI, its codec, and create_buffer Add: the BufferHandle/Tensor wire ABI, its codec, and create_buffer Jul 30, 2026
@YunjiQin

Copy link
Copy Markdown
Contributor Author

Went through the CodeRabbit findings — all five held up against the current code, none was a false positive. Fixed in 9fe7bb9.

The one that mattered: validate_buffer_ref is documented as the single gate every receive boundary runs, but the FORK_COW⇒READ rule only existed in the Python mirror's descriptor constructor. A FORK_COW descriptor arriving over the wire with a write grant passed C++ decode — and the failure is silent, not loud: the consumer's first write splits the page into a private copy and the owner observes nothing. Both mirrors reject it now, with a test covering WRITE and READWRITE plus the FORK_SHM case that stays legal.

  • BufferRefBlobView::ref(i) bounds check — agreed and added. Not reachable today (every caller loops to ref_count), but it is the documented extraction point for three consumers and nothing tells a fourth that the bound is the caller's job.
  • FORK_COW = 5 not pinned — correct, it was the one enumerator the frozen-value test skipped.
  • Stale BufferRef layout comment — correct: it still said 272 B / 216 B from before the ABI was slimmed. Actual is 144 / 88, which the assertion on the next line already enforced.
  • VMM_WINDOW missing from the backend table — correct, the table listed five of the six wire values.

On the docstring-coverage warning (32% vs 80%): added docstrings where they state a contract that is not evident from the signature — the two wire enums, BufferHandle.to_descriptor / close, and ImportRegistry.resolve / unregister / close (notably: close on the owner unlinks, close on the registry does not, which is the kind of thing worth writing down). I deliberately did not document the mechanical dunders (__len__, __post_init__, __getitem__) to reach the threshold — the repo's own gate has no docstring check, and one-line restatements of what the signature already says are what the project's comment rule steers away from.

Verified after the fixes: pyut 913 passed, cpput 67/67, a2a3sim scene tests 43 passed.

`validate_buffer_ref` is the one gate every receive boundary runs, but the
FORK_COW rule lived only in the Python mirror's descriptor constructor. A
descriptor arriving over the wire as FORK_COW with a write grant therefore
passed the C++ decode: the consumer's first write would split the page into a
private copy and the owner would observe nothing, with no error anywhere. Both
mirrors of the ABI now reject it, and the test covers the WRITE and READWRITE
grants as well as the FORK_SHM case that stays legal.

`BufferRefBlobView::ref` also bounds-checks its index. Every caller loops to
`ref_count` today, so nothing reaches past the validated region, but the method
is the documented extraction point for three separate consumers and a future
one has no way to see that the bound is the caller's job.

Also: pin `FORK_COW = 5` alongside the other enumerators, which the frozen-value
test had skipped; give the `BufferRef` layout comment the post-slimming sizes it
describes (144 / 88, not 272 / 216 — the assertion below it was already right);
add the missing `VMM_WINDOW` row to the backend table, which listed five of the
six wire values; and document the two wire enums plus the handle and registry
lifecycle calls, where the contract is not evident from the signature.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant