Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .changes/0.8.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
## [0.8.1] - 2026-07-01

A single-fix patch release: unknown-field limit accounting for zero-copy
views now matches owned conversion exactly, establishing the guarantee that
a view which decodes successfully always converts to an owned message. No
API changes, and regenerating code is not required — the fix lives in the
runtime.

### Fixed

- **Zero-copy view decoding now charges the unknown-field limit per
re-materializable field** — one per unknown record plus, for unknown group
records, one per nested field — instead of one per coalesced span, and
conversion replays under exactly the field budget and group-nesting depth
recorded at decode time (previously a fixed recursion limit, which could
reject deep unknown groups decoded under a raised `with_recursion_limit`).
Decode-time accounting now matches what `to_owned_message` re-materializes,
which gives views a guarantee: **a view produced by `decode_view` always
converts via `to_owned_message` without error** (the `Result` remains for
hand-written impls and `push_raw`-built views). **Behavioral tightening:**
payloads whose unknown-field count exceeds the limit but previously slipped
through view decode via span coalescing — e.g. a ~2 MiB run of >1M
contiguous 2-byte unknown records under the default limit, or an unknown
group with more nested fields than the limit — now fail at view decode with
`UnknownFieldLimitExceeded`. Consumers that converted such views already
got this error at conversion; view-only consumers that re-encoded such
payloads without converting (e.g. a zero-copy passthrough proxy) now see it
at decode — raise the bound with `DecodeOptions::with_unknown_field_limit`
if such payloads are trusted and expected. The accounting lives in the
runtime (`UnknownFieldsView::push_record`), so previously generated code is
fixed without regeneration. (#266)

[0.8.1]: https://github.com/anthropics/buffa/compare/v0.8.0...v0.8.1
4 changes: 4 additions & 0 deletions .changes/unreleased/breaking-changes-20260701-165230.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
kind: Breaking changes
body: |-
`OwnedView::to_owned_message` and the generated `FooOwnedView::to_owned_message` are now **infallible**, returning the owned message directly instead of `Result<_, DecodeError>`. Every `OwnedView` constructor wire-decodes its view (or, for `unsafe from_parts`, requires wire-decode provenance as part of its strengthened safety contract), and since 0.8.1 a view produced by wire decoding always converts — so the `Result` only ever encoded an unreachable error path. Migration: on call sites whose receiver is an `OwnedView` or a generated `FooOwnedView` handle, delete the `?` / `.unwrap()` / `.expect(...)` — or otherwise unwrap the previously-returned `Result` (a `match` or `.map_err(...)` needs the same treatment). Call sites on plain view types (`FooView::to_owned_message` via the `MessageView` trait) are unchanged and stay fallible, since hand-written impls and `push_raw`-built views can still legitimately fail. **`unsafe from_parts` callers**: the safety contract now requires that the view was produced by wire-decoding the buffer, not merely that its borrows point into it — a hand-assembled view that was legal under the old wording still compiles but now panics at `to_owned_message`, so audit `from_parts` call sites for provenance. A contract violation by a buggy hand-written `MessageView` impl wrapped in `OwnedView` likewise panics with a descriptive message instead of surfacing an error that correct code could never observe. (#268)
time: 2026-07-01T16:52:30.000000000Z
4 changes: 0 additions & 4 deletions .changes/unreleased/fixed-20260701-041207.yaml

This file was deleted.

34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,40 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

Entries for unreleased changes live as fragment files under [`.changes/unreleased/`](.changes/unreleased/); run `task changelog-new` to add one. This file is assembled at release time — do not edit it directly.

## [0.8.1] - 2026-07-01

A single-fix patch release: unknown-field limit accounting for zero-copy
views now matches owned conversion exactly, establishing the guarantee that
a view which decodes successfully always converts to an owned message. No
API changes, and regenerating code is not required — the fix lives in the
runtime.

### Fixed

- **Zero-copy view decoding now charges the unknown-field limit per
re-materializable field** — one per unknown record plus, for unknown group
records, one per nested field — instead of one per coalesced span, and
conversion replays under exactly the field budget and group-nesting depth
recorded at decode time (previously a fixed recursion limit, which could
reject deep unknown groups decoded under a raised `with_recursion_limit`).
Decode-time accounting now matches what `to_owned_message` re-materializes,
which gives views a guarantee: **a view produced by `decode_view` always
converts via `to_owned_message` without error** (the `Result` remains for
hand-written impls and `push_raw`-built views). **Behavioral tightening:**
payloads whose unknown-field count exceeds the limit but previously slipped
through view decode via span coalescing — e.g. a ~2 MiB run of >1M
contiguous 2-byte unknown records under the default limit, or an unknown
group with more nested fields than the limit — now fail at view decode with
`UnknownFieldLimitExceeded`. Consumers that converted such views already
got this error at conversion; view-only consumers that re-encoded such
payloads without converting (e.g. a zero-copy passthrough proxy) now see it
at decode — raise the bound with `DecodeOptions::with_unknown_field_limit`
if such payloads are trusted and expected. The accounting lives in the
runtime (`UnknownFieldsView::push_record`), so previously generated code is
fixed without regeneration. (#266)

[0.8.1]: https://github.com/anthropics/buffa/compare/v0.8.0...v0.8.1

## [0.8.0] - 2026-06-25

The headline of this release is that the owned representation of every field
Expand Down
14 changes: 7 additions & 7 deletions buffa-codegen/src/owned_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,13 +283,13 @@ pub(crate) fn generate_owned_view_wrapper(

/// Convert to the owned message type.
///
/// # Errors
///
/// Returns an error if re-materializing preserved unknown fields
/// fails (e.g. the unknown-field limit is exceeded).
pub fn to_owned_message(
&self,
) -> ::core::result::Result<#owned_path, ::buffa::DecodeError> {
/// Infallible: this type's constructors wire-decode their
/// buffer, and a view produced by wire decoding always
/// converts. Delegates to [`::buffa::OwnedView::to_owned_message`],
/// whose contract also governs handles converted from a raw
/// [`::buffa::OwnedView`].
#[must_use]
pub fn to_owned_message(&self) -> #owned_path {
self.0.to_owned_message()
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading