Repository navigation
feat(metadata): declare how an encoder batches, and check that it can - #2009
Merged
Merged
Conversation
justinchuby
force-pushed
the
justinchuby/vision-batching-schema
branch
from
August 24, 2026 16:36
6312bc9 to
3f6b368
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2009 +/- ##
==========================================
+ Coverage 80.31% 81.27% +0.96%
==========================================
Files 409 426 +17
Lines 190562 211589 +21027
Branches 190562 211589 +21027
==========================================
+ Hits 153046 171969 +18923
- Misses 32082 33940 +1858
- Partials 5434 5680 +246
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
justinchuby
force-pushed
the
justinchuby/vision-batching-schema
branch
4 times, most recently
from
August 24, 2026 21:42
9d632d8 to
d6df789
Compare
justinchuby
added a commit
that referenced
this pull request
Aug 24, 2026
Two amendments adopted from the schema implementation (#2009), both places where the implementation was stricter than this document and right to be. **The packed axis rule now covers every packed value.** §4 rule 2 scoped `axis == 0` to values a `batch_capacity` component consumes or produces. That scope was too narrow: the runtime splits a packed value per request whenever it hands one back — rule 8's emit path rebases offsets and derives per-request owners with no capacity declaration in sight — and that split wants the same contiguous span for the same aliasing reason. Under the old scope a strided packed emit was legal precisely where nothing had declared it could pay for one. No fixture in the tree declares a non-zero packed axis, so the wider rule rejects nothing that exists. **A preprocessing-program companion must carry the companion role.** §3.3 introduced `pack_offsets` and `pack_owner` but stated their use only descriptively. §4 rule 4 now requires them of any level companion that is a declared program output. The asymmetry with §3.2, where a length value is resolved by name and its role is descriptive, is deliberate and now explained: length vectors have established modality spellings that predate this design and cannot be narrowed to one role, while the companion roles are new and have nothing to accommodate — so requiring them costs nothing and catches a program that wires, say, `grid_dimensions` into a `levels` entry at load rather than shipping a plausible int64 rank-1 vector that means something else. Decisions §10.5 and §10.6 restate both, matrix row 17 gains the two rejections, and the P2 phase box's stale "§3.1 rules 1-7" is corrected to 1-9 to match the body. No other rule, example, or acceptance row changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 24, 2026
…hat a document must say Two contract-visible consequences of the schema implementation's code review (#2009), recorded here so the design of record does not lag the contract. **A ragged emit publishes the account of its raggedness.** §4 rule 8 required a packed emit to declare every level's `offsets` and `owner`, but said nothing about a padded emit, which is the same hole. §3.2 rejects a payload-shaped validity mask precisely so the length vector is the only account of the padding that exists anywhere; a caller that receives the payload without it holds trailing entries it cannot distinguish from real ones. Rule 8 is generalized to cover both, the serving carve-out now admits an `int64` rank-1 `shared` output named as another emitted value's `valid_lengths` as well as one named as an offsets/owner, and §3.2, decisions §10.5/§10.6, matrix row 25, and P2's negative fixtures follow. A `valid_lengths` companion needs no rebasing on delivery — a length is already relative to the item it measures — so a request receives the slice indexing its own items. **A version may gate what a document must say; never what it may say wrongly.** §6.1 gains the distinction, prompted by a rule that demanded an explicit append axis and thereby rejected two shipped diffusion packages that meant the default they were written against. When a new field makes an existing default ambiguous, the demand to state intent explicitly starts at the version that introduced the ambiguity. Range checks and every rule about a value a document did state stay unconditional at every version — relaxing those lets an older spelling assert something false rather than merely leave something unsaid. The two look alike in a validator and are opposites in effect: one preserves packages that predate a question, the other would preserve their bugs. Packages under the exemption should still be migrated, so it stays a guarantee rather than a live path nothing exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
force-pushed
the
justinchuby/vision-batching-schema
branch
5 times, most recently
from
August 24, 2026 23:56
2481092 to
ae7e11e
Compare
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…ed is not published Two corrections to §4 rule 8 and its mirrors in decisions §10.5/§10.6, both raised by the schema PR (#2009) after a review found the second of them as a live contradiction in its validator. **The carve-out's "rank 1" was wrong for a length.** An `offsets` and an `owner` are rank 1 by construction, so the flat phrasing was accurate for them and read as though it were the general rule. It is not: §3.2 fixes a `valid_lengths` at the number of axes outer to the padded one, so a value padded on axis 2 publishes a rank-2 length vector. Under the flat reading, one rule of this design requires that companion and another refuses it — and the refusal necessarily advises `request_aligned`, which is exactly what a companion must not declare. Following the narrower wording would make a correct package wrong. The condition is therefore per *reference*: collect what each declaration naming the value requires of it — role, and the rank that role implies at that site — and admit the value if it satisfies any one. Three consequences are written down because each is a place an implementer must choose. Expectations accumulate rather than overwrite, since one length vector may legitimately bound the same dimension of two outputs. Two declarations demanding different ranks of one value is a genuine contradiction, but it belongs to the padding declaration that is wrong; raising it at the serving rule names an innocent line. And where a padded dimension does not resolve to an axis, the reference carries no rank expectation at all — that declaration is already being rejected, and a second complaint about a rank nothing could compute sends the reader somewhere there is nothing to fix. A value named as a companion but shaped like something else is refused as a malformed companion, naming role and expected rank, rather than falling through to the generic message that advises the one layout a companion may not have. **Declared is not published.** Both halves of rule 8 now require the companion to be emitted — written by some declared step — not merely to appear in the output list. An output nothing writes hands the caller an empty vector beside a ragged payload, which is the precise failure the rule exists to prevent wearing the appearance of compliance. The check is whole-workflow rather than path-sensitive: "written by some declared step" is what is soundly decidable without evaluating branch predicates, and a path-sensitive rule would reject a correct package whose companion is written in a sibling branch of the emit. Acceptance row 25 grows the cases that distinguish the two readings: doubly padded values publishing rank-1 and rank-2 lengths together, a packed value padded on the axes it does not pack publishing three companions at three ranks, a mis-ranked length, and a companion declared but written by no step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
force-pushed
the
justinchuby/vision-batching-schema
branch
from
August 25, 2026 00:27
ae7e11e to
56a27e8
Compare
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…d say why A follow-on to the previous commit, closing the asymmetry the schema PR (#2009) found on its own side of the same rule. The obligation requires a companion to be emitted; the carve-out already required the value that *names* it to be emitted, but the neighbouring decidability sentence still said "decidable from the declared outputs alone", which invites the weaker reading. Asking one side for an emit while accepting a bare declaration on the other is two answers to one question (Rule 10), and the weaker half is exploitable: declare a padded output beside any `shared` vector, write neither, and the vector walks past the serving rule on the strength of a payload that does not exist. Both halves now say emitted, the exploit is written down as the reason, and an expectation is stated to carry both the declaration that claims the value and whether anything writes that declaration. The decidability claim is corrected rather than dropped: the condition is decided from the workflow's own declarations — its outputs and its steps — with no runtime information. That is what made the check affordable, and it survives the stronger reading; only "outputs alone" was wrong. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…xis` The rank clause was corrected two commits ago, but only in the form "the number of axes outer to the padded one". That is the definition; it is not the check, and an implementer has to derive one from the other. The schema PR (#2009) states the same fact as `rank == axis` because that is what its validator evaluates, and the derivation is exactly where the earlier flat "rank 1" survived review on both sides. Both spellings are now given together, in §3.2 where the shape rule lives, in §4 rule 8's carve-out, and in decisions §10.6. No rule changes; a reader of any of the three gets the check without translating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
`token_packed` has been in the schema since the batching redesign, and until now nothing checked it. A package could name an offsets value that did not exist, an owner map that was a float matrix, or pack on an axis its own rank did not have, and the validator would accept every one of them — the layout was accepted for its shape, not its meaning. The two values a packed layout names are precisely what lets a runtime split a packed encoder result back into per-request pieces without any serialized row identity, so a reference that resolves to nothing is a contract no runtime can execute. The other half of the same gap is that nothing in a package said how many request rows one component invocation may carry. Absence has to keep meaning "one row per invocation" — that is the only safe reading for every component whose producer never thought about batching — so the fact is added as an optional declaration that only ever widens what a scheduler may do: - `WorkflowComponent.batch_capacity` states the axis rows stack on, an optional `max_rows` bound, and the `uniform_axes` rows must already agree on before they can share a call. It denies unknown fields and defaults to absent, so every existing package keeps its meaning. - `TensorContract.pad_mask` names the value whose booleans or integers mark which entries of a padded tensor are real. A padded batch is the only shape a fixed-arity component can consume, and recovering validity from a sentinel is exactly the hidden heuristic this schema refuses everywhere else. - The image output content vocabulary gains `item_offsets` and `item_owner`, so a preprocessing program that packs the images of several requests can declare the two values that map items back to rows as content roles rather than by a name a runtime would guess. `BatchLayout::TokenPacked` validation is now non-vacuous: offsets and owner must resolve to a declared workflow value or to a port of the same component, offsets must be a rank-one integer value that is request-aligned on axis 0 (which is what its own schema doc has always said it is), an owner map must be a rank-one integer value that is either shared or packed against the same offsets, and the packed axis must exist. A pad mask must resolve, be bool or integer, fit inside the value it masks, and move with the same rows; a `token_packed` value has no padding to mask at all. A declared capacity must agree with the ports it batches, with any declared `row_scope`, and with a fixed row dimension the graph already pins. The one existing fixture that declared `token_packed` had `image_offsets` as a shared `[batch_plus_one]` value, which the new check rejects; it now declares the request-aligned per-row offsets the field is documented to hold. Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ideo
An encoder that batches clips faces the same questions an image encoder
does — how many rows fit in one invocation, which entries of a padded
tensor are real, and how packed entries map back to request rows — and
one more: those questions have to be answerable per item, per frame, and
per clip, not just per image.
The audit found no video surface at all. `preprocessing` declared an
image program and an audio program; a clip had nowhere to state its frame
sampling or its temporal padding, and the packed-batch content roles
stopped at `item_offsets`/`item_owner`. Meanwhile the still-image program
already carried `temporal_patch_size`, so the temporal axis was half
present under a name that denied it existed.
A frame is an image. The spatial transforms, their parameters, and their
output bindings are identical, so a second program type would have
duplicated forty fields to say the same thing twice. Instead the one
program becomes what it always was — a pixel program — and gains the two
operations a temporal axis adds:
- `preprocessing.video` declares a `VisionPreprocessingProgram`, the
same type `preprocessing.image` declares. Which key it appears under
is what says whether a package preprocesses stills, clips, or both.
- `sample_frames` and `pad_frames` join the transform vocabulary, with
`fps`, `num_frames`, and `frame_stride` as their model data.
`pad_frames` reuses `target_length`, the exact leading-axis length a
pad produces.
- Packing is layered, so each level names its own pair:
`item_offsets`/`item_owner` for the patches an encoder consumes,
`frame_offsets`/`frame_owner` for the frames a clip contributes, and
`clip_offsets`/`clip_owner` when one request carries several clips.
A still image uses the item pair alone.
- `preprocessing.video` is validated exactly like the other two: one
`onnx-genai.video-preprocess@1` adapter, invoked once, whose output
ports agree with the program's declared contracts.
The batching contracts themselves did not change and did not need to.
`batch_capacity`, `pad_mask`, and `token_packed` never mentioned images,
so a clip padded on a temporal axis and a clip packed at frame
granularity are checked by the rules that already existed. What did carry
an image-specific name is renamed rather than aliased: `ImageTransform`,
`ImageOutputBinding`, `ImagePreprocessingProgram`, `ImageSizeSpec`, and
their two vocabularies become `Vision*`, and `ImageOutputValueRange`
becomes `PixelValueRange` — a video output role already read its
normalization from it. Serialized keys are unchanged, so every existing
package still parses.
Tests cover a temporally padded clip encoder end to end (sampling,
`pad_frames`, the frame mask its contract names, and a four-row
capacity), a clip packed at item and frame granularity at once, and the
rejections: a shared frame mask that cannot be permuted with its rows, a
mask out-ranking the clip it masks, a video program without its adapter,
and a frame owner map borrowed from the item packing.
Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A packed batch has more than one granularity once video is in scope. The frames of a clip are packed, the clips of a request are packed, and the frame owner map indexes clips rather than request rows. Until now `token_packed` could only describe the terminal case, so a document either flattened the structure away or left the second level to a convention no reader could check. `BatchLayout::TokenPacked` gains an optional `owner_span` naming the coarser packing its owner indices point into. Absent is exactly what it was: owner indices name request rows. Present shifts what the offsets are counted against — one offset per span rather than one per row — and validation now enforces that reading: the span must resolve and be packed itself, the offsets must be packed the way that span's items are, and an owner map must sit at the level of the items it indexes. Composition is bounded at frames inside clips inside rows, with cycles rejected by name, because ownership is walked on every compaction and a document that needs a third level is far more likely to have looped. `pad_mask` becomes a struct that names its axis. A rank-2 mask over a rank-5 clip could plausibly mark frames, tiles, or patches; guessing from rank is the hidden heuristic this schema exists to refuse. The axis must be one the value has, and never the axis its rows stack along: a compacted batch has no padding rows. A request-scoped value's mask must span both the rows it is permuted with and the axis it marks. An incremental emit of a rank-4-or-deeper value must name its growth axis. The default — the final axis — is right for a token sequence and silently wrong for `[batch, frames, channels, height, width]`, where it is a spatial extent. `uniform_axes` gains no new machinery: a temporal axis is an axis, and the padded clip fixture now requires uniformity on frames and on both spatial extents through one list. The capacity error for a packed port now says where an invocation's rows land instead of implying the port batches rows. `WorkflowLoopIteration` is boxed in both loop forms. It carries a full `TensorContract`, which the new fields grow past the point where clippy's large_enum_variant fires on `WorkflowStep`; serde and the schema are unaffected. Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The architecture review of the design PR rejected the shape this branch
had encoded, and the revised design that answers it changed the spelling
of almost every contract this branch introduced. Rather than ship a
schema that says the same things with different words, this restates the
whole surface in the design's own terms, so a package written against the
document validates against the code.
Three independent kinds of raggedness now sit on three dimensions of one
contract, and each is described by exactly one thing:
* item count is an ownership level, `levels: [{ offsets, owner }]` on
a single physical `token_packed` axis. Nesting frames inside clips
inside rows is a second level over the same axis, not a second axis;
the validator checks the extent chain (level k's owner counts level
k's units, level k's offsets counts its parent's plus one) instead of
inventing a shape. Depth is bounded at two.
* item extent is `padding: [{ dimension, valid_lengths }]`. The
boolean mask this branch previously carried is withdrawn: padding is
prefix-valid, so a length is O(items) where a mask is O(items x
extent) and would force a device-to-host read to interpret.
* item nesting composes the two without a third mechanism.
`batch_capacity` is keyed by shape symbol rather than axis index:
`uniform_dimensions` names the symbols that must agree for a batch to
form, and `budgets: [{ dimensions, max_total }]` bounds the product of a
symbol set. There is no `max_items` integer, because the thing being
limited is always some dimension the contracts already named. Symbols
that no port declares, or that resolve ambiguously, are rejected.
`packed_extent: preserved | produced` says where a packed output's level
zero companions came from: preserved reuses what already described the
extent, produced is described by companions the graph emits. A packed
emit publishes the companions of every level, because a caller can only
split a packed result with the companions that describe it.
The preprocessing vocabulary gains two level-agnostic roles,
`pack_offsets` and `pack_owner`, and generalises `valid_lengths` from the
audio side. It contains no clip, no frame, and no window: a video
packing is the same two roles applied twice.
Row scope is checked against request axes only, and never against a
port's packed axis; the packed item axis is not a row axis. Load-time
validation reads graphs and contracts alone -- offsets monotonicity,
owner range and budget satisfaction are invocation-time value invariants
and stay there. Packed axis zero is required, so a split is a slice.
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The second architecture review of the encoder-batching design found three things this schema got wrong. Each is a case of one declaration answering a question that has more than one answer. A packed value's ownership chain carried a single `packed_extent`, so a component had to say that it produced every level or preserved every level. The canonical video encoder does neither: it merges frames into a chosen number of tokens per clip while leaving the clip-to-request grouping exactly as it received it. `extent` therefore moves onto `OwnershipLevel`, where the question is actually asked. A `produced` level must name companions the component itself outputs. A `preserved` level must not, and must correspond to some input level by its `(offsets, owner)` pair -- by identity, because an encoder that pools an inner level away leaves the outer one at a different index, and matching by position would silently accept the wrong chain. Every structure in this schema denies unknown fields, so a newer document is not one an older runtime ignores: it is one that runtime rejects, field by field, with `unknown field 'batch_capacity'`. That is the least actionable order in which to learn that a package needs a newer build. `version.rs` adds the grammar the ecosystem already speaks -- absent, `v1`, `1`, `1.0` and `v1.0` all normalize to 1.0, so no document in the tree changes meaning -- and a gate that reads the version from the untyped tree and compares it before `serde` sees the bytes. `parse_metadata`, `parse_metadata_json` and `load_metadata` are now the only ways a document becomes typed metadata, and the comfyui lowering, the model.io migration binary and the comfyui config loader go through them. A document that uses a 1.1 field while declaring 1.0 is refused by validation for that reason, in those words. `uniform_dimensions` could name any shape symbol, including the packed extent itself and an ownership level's unit count. Pinning one of those says every group has the same number of items, which is a footprint bound wearing the wrong hat -- `budgets` already says that, and says it in a form that composes. Uniform symbols are now ordinary per-item dimensions only; a redundant singleton budget on a pinned symbol is refused, while a composed budget that multiplies a pinned extent by a count remains the way footprint is bounded. A fixed-frame video is expressed as a pinned temporal dimension with no frame ownership level at all. Also: `valid_lengths` is a new generic content role, and the audio roles that already mean a length keep meaning it; ownership and length companions must carry a role that says which they are; and a declared `owner` map is runtime internal, so a workflow input naming one may not be `externally_suppliable`. Offsets stay suppliable, because a caller can state its own and the runtime rebases them. The comfyui golden document now stamps the canonical `1.0` rather than `v1`. Readers accept both, and the emitted spelling should be the one the grammar calls canonical. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sion Corrections from coordinating against the revised design of record (justinchuby/vision-batching-design 2daeb83). Each is a case of a rule that was right about the wrong quantity. `budgets` rejected a singleton entry only when it named a *pinned* symbol. The property that makes a bound meaningless is not pinning, it is failing to reach the group: `patches` alone counts patches per frame, which is the same number whether the invocation holds one frame or a thousand, and it is equally meaningless whether `patches` is pinned or padded. A `dimensions` list is now read as a nesting path, outermost first, and its first symbol must be a count the scheduler chose -- a packed extent, an ownership level's unit count, or the row axis of a component that pads rather than packs. Inner per-item symbols appear only in later positions, where they multiply a count into a footprint. A fixed frame count drops the frames-to-clips level but keeps the clips-to-rows level, which the previous revision got wrong by asserting a fixed-frame video declared no ownership at all. A request still contributes whatever number of clips it carries, and a request-aligned tensor would have to pad that away -- the cost packing exists to avoid. `FIXED_FRAME_VIDEO_ENCODER` states the shape the design gives: `[clips, frames, patches, features]` packed over clips, with `frames` an ordinary pinned dimension. `SCHEMA_VERSION` goes back to `v1`. Normalizing on read is what makes the four spellings in the wild interchangeable; changing what writers stamp would rewrite bytes and semantic identities to say something no reader distinguishes. The comfyui golden document is byte-identical to main again. `v1.1` is the canonical spelling a batching document declares, `1.1` is an accepted synonym, and `SchemaVersion` displays with the `v` so errors name a version the way a document would write it. A `padding` entry names its length value by name, so nothing dispatches on the value's content role. An audio program points its entry at the `frame_lengths` it already emits rather than emitting a second copy under the generic name, and `LENGTH_CONTENT_ROLES` becomes informative -- `valid_frames` and `valid_samples` leave it, since those are counts a graph consumes rather than statements about padding. Two rules gain the halves they were missing. A companion pair is one mapping wherever it is named, so every port naming it must resolve both of its extent symbols the same way -- not merely pair the same two names. And the serving carve-out is narrow by construction: a `shared` emitted value is admitted only when it is `int64`, rank 1, and named as an offsets or owner of some other emitted value's layout. One deliberate strengthening, recorded rather than assumed: the packed axis must be 0 for every packed value, not only for one a `batch_capacity` component touches. The reason the design gives -- a no-copy view is a contiguous element window -- applies whenever a runtime splits a packed result per request, which includes a packed emit with no capacity in sight. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three defects from review, all of them a rule reaching further than its reason. **A shipped package did not expire.** The rank-4 append rule refused the checked-in diffusion and guided-diffusion packages, which append a latent `[batch, 4, height, width]` along its final axis. They are correct and always were: the final axis is what the default meant, and they had no way to say so explicitly because nothing asked. The rule exists because a rank-4 *media* tensor makes that axis ambiguous, and a rank-4 media tensor is what v1.1 introduced -- so the demand starts there. An older document keeps the default it was written against; a document declaring v1.1 names the axis. An axis that is outside the value's rank names nothing at any version and is still rejected. Version scoping means the validator has to be told the version, so `validate_pipeline_spec` now takes one. Deriving it from the document's fields would have been the same fact stored twice, and defaulting it inside the validator would have made the strictness depend on which entry point a caller happened to use. Both fixtures are migrated to `axis: 3` regardless: stating the axis the default already chose costs nothing and outlives the exemption. **A padded output could hide the only account of its padding.** The serving rule that every emitted rank>0 value declare a row correspondence admitted ownership offsets and owner maps, because those are `shared` by construction. A padded output's `valid_lengths` is `shared` for the same reason -- one entry per position outside the dimension it bounds -- and was rejected. The carve-out now names both, and stays decidable from the declared outputs alone: an unrelated `shared` result is still a per-request answer with no way back to a request. The other half of that symmetry was missing too. `validate_packed_emit_companions` requires a packed emit to publish the companions that split it; nothing required a padded emit to publish the lengths that read it. A caller holding trailing entries that mean nothing, with no way to learn how many, is the failure the padding contract exists to prevent -- and the schema deliberately refuses a payload-shaped validity mask as the alternative, so that vector is the only account there is. **A user-facing error was malformed.** The unquoted-`schema_version` message was missing its line continuations, so it reached a reader with fourteen-space runs in the middle of two sentences. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
An alignment check against the design of record found the published field documentation still describing the canonical spelling as `<major>.<minor>` with an example of `"1.1"`. That is not what this crate does. Every writer in the repository stamps `v1`, and a document that uses a batching field is told to declare `v1.1`; a schema whose own description drops the `v` leaves one format with two house styles and no way to tell which a writer should follow. The field now states the canonical form is `v<major>.<minor>`, lists the spellings that predate it, names `1.1` as an accepted synonym for `v1.1` rather than an equal alternative, and says plainly that a document using nothing new keeps stamping what it already stamped. Three tests pin what the prose now claims, because a house style that only exists in a doc comment is a suggestion: - the canonical spellings are exactly `v1` and `v1.1`, and `v1.1` is the literal a batching document is *told* to write, not merely one the reader tolerates; - `1.1` normalizes to the same version and is not that literal; - every spelling of the first version survives a load verbatim and keeps its own semantic identity, so normalizing for comparison never becomes normalizing on disk. No document bytes change. The two diffusion fixtures this branch touches carry the same `schema_version` they always did; the comfyui golden is byte-identical to main. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both extensions this branch went past the design with are now in the design of record (`justinchuby/vision-batching-design` 7b20e31), so they stop being divergences and start being rules — which means they need tests that say what they are, not tests that happen to cover them. The packed axis must be 0 wherever `token_packed` appears. The design had scoped that to a component declaring `batch_capacity`; the scope was the bug, because the emit path rebases offsets and derives per-request owners with no capacity in sight and wants exactly the same contiguity. The validator already enforced it unconditionally. A test now states the unconditional form, on a workflow that declares no capacity at all, so a future reader cannot mistake the enforcement for an oversight. The companion-role requirement reaches only declarations that have a role. A preprocessing program declares what each output *is*, so a program wiring some other plausible int64 rank-1 vector into a `levels` entry is caught at load; a companion the graph produces is an ONNX output port, and a port contract has nowhere to carry a content role. The validator was already scoped this way -- the role lookup simply finds nothing for a value that is not a declared program output -- but nothing said so, and a `produced` level being quietly exempt is exactly the kind of behaviour that gets "fixed" into a bug. A test now asserts the exemption on a fixture whose companions are graph outputs. The comment on the role rule also records why `padding` deliberately does not get the same treatment, so the two are not read as an inconsistency: length vectors have established modality spellings that programs legitimately emit, so no single role could be demanded without breaking a program that predates the generic name, while the two ownership roles are new and reject nothing that exists. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The three ragged-emit companions do not behave alike on delivery, and the difference was implemented but never asserted. An owner map is invocation-global and is never handed to a request; offsets are per-request but group-relative, so they are delivered rebased to the request's own start; a valid_lengths is already relative to the item it measures and means the same number in any group, so a request states its own and receives the slice indexing its own items. Only the owner rule constrains suppliability, so the third case held by construction. Assert it, so a later attempt to unify the two delivered companions with the internal one fails a test rather than passing silently, and say in the owner rule's comment why it reaches the owner and nothing else. Also quote the general form of the version split in validate_emit_axis: a version may gate what a document must say, but never what a document may say wrongly. The specific rule already worked this way; the principle is what a reader needs to apply it to the next additive field. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion it needs `token_packed` used to carry `offsets` and `owner` directly on the batch layout, which could say only that items sat in request rows. It now carries `levels`, an ownership chain, because an item can itself be a group -- frames in clips in rows -- and one pair cannot say that. A one-level chain is exactly what the flat pair meant, so a shim would be expressible. It is refused rather than translated: this schema is pre-release and rule 3 is to reshape completely rather than keep a second spelling alive, and no in-tree package uses `token_packed`, so nothing ships that a translation would rescue. What a clean break costs is the error, and that cost was unpaid. `serde` reported the old shape as "unknown field `offsets`", which reads like a typo -- a reader goes looking for a misspelling rather than for a migration, and the reported path stopped at the contract rather than the layout. `reject_flat_token_packed` recognizes the retired shape in the preparse, names the containing path, prints the replacement, and says plainly that nothing will convert the document for it. It runs after the version gate, deliberately. The flat pair is a reshape inside the v1 line, so refusing it presumes the document belongs to that line; a document declaring a version this build cannot read is refused for that instead, since a reader that has never seen v2 has no standing to say what a spelling means there. The compatibility claims are corrected to match. Absence-as-compatibility is a statement about fields that were *added*; it was written as though it covered everything, and a document using nothing new now fails to load. The `schema_version` doc and the version test module both say so and point at the refusal. The published schema already required `levels` and admitted no extras, so schema and loader agree; a test pins that agreement rather than leaving it to hold by accident. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rank one The serving carve-out asked whether an emitted `shared` value was named as some declared output's companion and whether it was int64 rank 1. That is right for an ownership offsets or owner map, which are rank 1 by construction, and wrong for a validity length, whose rank is the axis index of the dimension it bounds: one entry per position of the axes outer to it. A rank-2 length for a padded axis 2 is exactly correct and was refused as "a per-request value without a declared batch_layout" -- advice that, if followed, would have made it wrong. The bare name set becomes a map from companion name to what the declarations naming it require: the role, and the rank that role implies at that site. A name can be claimed by several declarations, so expectations are collected rather than overwritten and a value satisfies the carve-out by matching any one of them; two declarations demanding different ranks of one value is a contradiction for validate_padding to report against the declaration that is wrong, not for this rule to report as a missing row axis. Where a padded dimension does not resolve to an axis the expectation carries no rank, because a second complaint about a rank nothing could compute would send a reader to the wrong line. A value named as a companion but shaped like something else now gets that as its error. The generic message told its author to declare a row axis, which is precisely what a companion must not do. Both companion obligations also now require the companion to be emitted, not merely declared. Declaring an output and writing it are different facts, and only the second delivers anything: a workflow that declares its lengths and never emits them satisfies "publishes them" while handing the caller a padded tensor and an empty vector, which is the failure the obligation exists to prevent. The check is whole-workflow rather than path-sensitive, deliberately -- whether two emits reach a caller together depends on a branch predicate, and this crate does not evaluate predicates, so a path-sensitive rule would reject correct packages whose companion is written in a sibling branch. "Somewhere in the declared steps" is what can be decided soundly, and it catches the case that occurs. Signed-off-by: Justin Chu <justinchu@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…hat a document must say Two contract-visible consequences of the schema implementation's code review (#2009), recorded here so the design of record does not lag the contract. **A ragged emit publishes the account of its raggedness.** §4 rule 8 required a packed emit to declare every level's `offsets` and `owner`, but said nothing about a padded emit, which is the same hole. §3.2 rejects a payload-shaped validity mask precisely so the length vector is the only account of the padding that exists anywhere; a caller that receives the payload without it holds trailing entries it cannot distinguish from real ones. Rule 8 is generalized to cover both, the serving carve-out now admits an `int64` rank-1 `shared` output named as another emitted value's `valid_lengths` as well as one named as an offsets/owner, and §3.2, decisions §10.5/§10.6, matrix row 25, and P2's negative fixtures follow. A `valid_lengths` companion needs no rebasing on delivery — a length is already relative to the item it measures — so a request receives the slice indexing its own items. **A version may gate what a document must say; never what it may say wrongly.** §6.1 gains the distinction, prompted by a rule that demanded an explicit append axis and thereby rejected two shipped diffusion packages that meant the default they were written against. When a new field makes an existing default ambiguous, the demand to state intent explicitly starts at the version that introduced the ambiguity. Range checks and every rule about a value a document did state stay unconditional at every version — relaxing those lets an older spelling assert something false rather than merely leave something unsaid. The two look alike in a validator and are opposites in effect: one preserves packages that predate a question, the other would preserve their bugs. Packages under the exemption should still be migrated, so it stays a guarantee rather than a live path nothing exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…ed is not published Two corrections to §4 rule 8 and its mirrors in decisions §10.5/§10.6, both raised by the schema PR (#2009) after a review found the second of them as a live contradiction in its validator. **The carve-out's "rank 1" was wrong for a length.** An `offsets` and an `owner` are rank 1 by construction, so the flat phrasing was accurate for them and read as though it were the general rule. It is not: §3.2 fixes a `valid_lengths` at the number of axes outer to the padded one, so a value padded on axis 2 publishes a rank-2 length vector. Under the flat reading, one rule of this design requires that companion and another refuses it — and the refusal necessarily advises `request_aligned`, which is exactly what a companion must not declare. Following the narrower wording would make a correct package wrong. The condition is therefore per *reference*: collect what each declaration naming the value requires of it — role, and the rank that role implies at that site — and admit the value if it satisfies any one. Three consequences are written down because each is a place an implementer must choose. Expectations accumulate rather than overwrite, since one length vector may legitimately bound the same dimension of two outputs. Two declarations demanding different ranks of one value is a genuine contradiction, but it belongs to the padding declaration that is wrong; raising it at the serving rule names an innocent line. And where a padded dimension does not resolve to an axis, the reference carries no rank expectation at all — that declaration is already being rejected, and a second complaint about a rank nothing could compute sends the reader somewhere there is nothing to fix. A value named as a companion but shaped like something else is refused as a malformed companion, naming role and expected rank, rather than falling through to the generic message that advises the one layout a companion may not have. **Declared is not published.** Both halves of rule 8 now require the companion to be emitted — written by some declared step — not merely to appear in the output list. An output nothing writes hands the caller an empty vector beside a ragged payload, which is the precise failure the rule exists to prevent wearing the appearance of compliance. The check is whole-workflow rather than path-sensitive: "written by some declared step" is what is soundly decidable without evaluating branch predicates, and a path-sensitive rule would reject a correct package whose companion is written in a sibling branch of the emit. Acceptance row 25 grows the cases that distinguish the two readings: doubly padded values publishing rank-1 and rank-2 lengths together, a packed value padded on the axes it does not pack publishing three companions at three ranks, a mis-ranked length, and a companion declared but written by no step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…d say why A follow-on to the previous commit, closing the asymmetry the schema PR (#2009) found on its own side of the same rule. The obligation requires a companion to be emitted; the carve-out already required the value that *names* it to be emitted, but the neighbouring decidability sentence still said "decidable from the declared outputs alone", which invites the weaker reading. Asking one side for an emit while accepting a bare declaration on the other is two answers to one question (Rule 10), and the weaker half is exploitable: declare a padded output beside any `shared` vector, write neither, and the vector walks past the serving rule on the strength of a payload that does not exist. Both halves now say emitted, the exploit is written down as the reason, and an expectation is stated to carry both the declaration that claims the value and whether anything writes that declaration. The decidability claim is corrected rather than dropped: the condition is decided from the workflow's own declarations — its outputs and its steps — with no runtime information. That is what made the check affordable, and it survives the stronger reading; only "outputs alone" was wrong. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…xis` The rank clause was corrected two commits ago, but only in the form "the number of axes outer to the padded one". That is the definition; it is not the check, and an implementer has to derive one from the other. The schema PR (#2009) states the same fact as `rank == axis` because that is what its validator evaluates, and the derivation is exactly where the earlier flat "rank 1" survived review on both sides. Both spellings are now given together, in §3.2 where the shape rule lives, in §4 rule 8's carve-out, and in decisions §10.6. No rule changes; a reader of any of the three gets the check without translating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
PR #2009 merged as 0448f2b, which lands phases P1 and P2 of this design: `WorkflowComponent.batch_capacity`, `TensorContract.padding`, the `levels` ownership chain, the `pack_offsets` / `pack_owner` / `valid_lengths` content roles, the `schema_version` gate, and the load-time validators for all of them. The design of record was still written as if none of it existed, which made its status line, its evidence section, and 57 of its source citations false. What changed: - Status is `partly implemented` rather than `proposed`, naming which phases landed and which did not. The interpreter, preprocessor, scheduler, and backend phases remain unimplemented, so a package can now *declare* a batched encoder that no runtime will group; §6 already requires that asymmetry to be safe, and the note says so. - §2 keeps its observations in the past tense and adds a `Since #2009` note per subsection. The observations are the record of why each field exists and are not recoverable from the field, so they are marked rather than deleted. §2.3, §2.4, and §2.5 are untouched because nothing closed them. - §3.3 and the `valid_lengths` argument no longer claim the roles are absent from the vocabulary; they are declarable and nothing produces them, which is a different and still-open gap. - §6.1's `schema_version` doc-comment defect is recorded as corrected, in the narrow form this design argued for: compatibility comes from a field being absent by default, which buys nothing for a field that was reshaped. - §8 marks P1 and P2 as shipped while keeping the full obligation text, so the phase list stays reviewable against the design instead of decaying into a changelog. - INFERENCE_METADATA_DECISIONS.md §10.4 becomes `What §10.1 did not have`, and §10.5 / §10.6 drop `proposed` for a status note that separates the load-time half now in force from the runtime half that is not. DESIGN.md and docs/README.md follow. - 57 citations re-pinned across the two documents, 39 mechanically and 16 by hand where the merge moved the claim rather than the line. Several were in range but semantically wrong after the `Image*` to `Vision*` rename, which line-number repair alone would not have caught. Two pre-existing defects found while checking: a link to a renamed §6 anchor, and a short-form `redesign_invariants.rs:158` citation appearing before its first full path. Both fixed. Docs only. Verified: every intra-doc link and anchor resolves, every citation is in range and reads as claimed, 28 acceptance rows with uniform columns, `git diff --check` clean, lines within 88 columns. `cargo test -p onnx-genai-metadata --test encoder_batching` passes 104 tests at this main, which is the evidence for the P2 marker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…fy by content #2009 merged as `0448f2bc6`, moving the files this document cites most. The citations were re-pinned in an earlier commit on this branch, but the check that guarded them asked only whether a line number fell inside the file. That is a property of the file's *length*, not of its *content*: a citation can point at unrelated code that reads plausibly and still pass, and the failure is silent. Verification is now content-aware — for each of the 97 ranged citations, the range's **first** line must contain the symbol or comment the surrounding sentence claims, and the range must contain a second distinctive substring. Anchoring on the head line makes the check position-sensitive, so every citation fails under a one-line drift rather than passing. Applying it to the merged tree found three references that were in range and wrong at the head, all of them narrowings of a previously correct range: - `schema/package.rs:158-174` -> `162-174`. The claim is about `batch_invariance`; lines 158-161 are the unrelated `decoding` field, so the citation opened on someone else's declaration. - `ort/value.rs:1543-1551` -> `1544-1551`. The claim names `alias_with_offset`; 1543 is the closing line of the preceding doc comment. - `ort/value.rs:1526-1542` -> `1524-1543`, in both documents. The claim quotes the no-copy-view doc comment; 1526 is a blank `///`. Re-pinned to the whole comment block, since a block citation cannot be off by one. The remaining 54 ranges verify unchanged against the merged tree, including all 80 citations into the 24 files #2009 touched. Records the convention in the header so the next editor re-pins by locating the named symbol rather than by adjusting the number until it lands inside the file. Docs only: no code, schema, or fixture changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
Two amendments adopted from the schema implementation (#2009), both places where the implementation was stricter than this document and right to be. **The packed axis rule now covers every packed value.** §4 rule 2 scoped `axis == 0` to values a `batch_capacity` component consumes or produces. That scope was too narrow: the runtime splits a packed value per request whenever it hands one back — rule 8's emit path rebases offsets and derives per-request owners with no capacity declaration in sight — and that split wants the same contiguous span for the same aliasing reason. Under the old scope a strided packed emit was legal precisely where nothing had declared it could pay for one. No fixture in the tree declares a non-zero packed axis, so the wider rule rejects nothing that exists. **A preprocessing-program companion must carry the companion role.** §3.3 introduced `pack_offsets` and `pack_owner` but stated their use only descriptively. §4 rule 4 now requires them of any level companion that is a declared program output. The asymmetry with §3.2, where a length value is resolved by name and its role is descriptive, is deliberate and now explained: length vectors have established modality spellings that predate this design and cannot be narrowed to one role, while the companion roles are new and have nothing to accommodate — so requiring them costs nothing and catches a program that wires, say, `grid_dimensions` into a `levels` entry at load rather than shipping a plausible int64 rank-1 vector that means something else. Decisions §10.5 and §10.6 restate both, matrix row 17 gains the two rejections, and the P2 phase box's stale "§3.1 rules 1-7" is corrected to 1-9 to match the body. No other rule, example, or acceptance row changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…hat a document must say Two contract-visible consequences of the schema implementation's code review (#2009), recorded here so the design of record does not lag the contract. **A ragged emit publishes the account of its raggedness.** §4 rule 8 required a packed emit to declare every level's `offsets` and `owner`, but said nothing about a padded emit, which is the same hole. §3.2 rejects a payload-shaped validity mask precisely so the length vector is the only account of the padding that exists anywhere; a caller that receives the payload without it holds trailing entries it cannot distinguish from real ones. Rule 8 is generalized to cover both, the serving carve-out now admits an `int64` rank-1 `shared` output named as another emitted value's `valid_lengths` as well as one named as an offsets/owner, and §3.2, decisions §10.5/§10.6, matrix row 25, and P2's negative fixtures follow. A `valid_lengths` companion needs no rebasing on delivery — a length is already relative to the item it measures — so a request receives the slice indexing its own items. **A version may gate what a document must say; never what it may say wrongly.** §6.1 gains the distinction, prompted by a rule that demanded an explicit append axis and thereby rejected two shipped diffusion packages that meant the default they were written against. When a new field makes an existing default ambiguous, the demand to state intent explicitly starts at the version that introduced the ambiguity. Range checks and every rule about a value a document did state stay unconditional at every version — relaxing those lets an older spelling assert something false rather than merely leave something unsaid. The two look alike in a validator and are opposites in effect: one preserves packages that predate a question, the other would preserve their bugs. Packages under the exemption should still be migrated, so it stays a guarantee rather than a live path nothing exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…ed is not published Two corrections to §4 rule 8 and its mirrors in decisions §10.5/§10.6, both raised by the schema PR (#2009) after a review found the second of them as a live contradiction in its validator. **The carve-out's "rank 1" was wrong for a length.** An `offsets` and an `owner` are rank 1 by construction, so the flat phrasing was accurate for them and read as though it were the general rule. It is not: §3.2 fixes a `valid_lengths` at the number of axes outer to the padded one, so a value padded on axis 2 publishes a rank-2 length vector. Under the flat reading, one rule of this design requires that companion and another refuses it — and the refusal necessarily advises `request_aligned`, which is exactly what a companion must not declare. Following the narrower wording would make a correct package wrong. The condition is therefore per *reference*: collect what each declaration naming the value requires of it — role, and the rank that role implies at that site — and admit the value if it satisfies any one. Three consequences are written down because each is a place an implementer must choose. Expectations accumulate rather than overwrite, since one length vector may legitimately bound the same dimension of two outputs. Two declarations demanding different ranks of one value is a genuine contradiction, but it belongs to the padding declaration that is wrong; raising it at the serving rule names an innocent line. And where a padded dimension does not resolve to an axis, the reference carries no rank expectation at all — that declaration is already being rejected, and a second complaint about a rank nothing could compute sends the reader somewhere there is nothing to fix. A value named as a companion but shaped like something else is refused as a malformed companion, naming role and expected rank, rather than falling through to the generic message that advises the one layout a companion may not have. **Declared is not published.** Both halves of rule 8 now require the companion to be emitted — written by some declared step — not merely to appear in the output list. An output nothing writes hands the caller an empty vector beside a ragged payload, which is the precise failure the rule exists to prevent wearing the appearance of compliance. The check is whole-workflow rather than path-sensitive: "written by some declared step" is what is soundly decidable without evaluating branch predicates, and a path-sensitive rule would reject a correct package whose companion is written in a sibling branch of the emit. Acceptance row 25 grows the cases that distinguish the two readings: doubly padded values publishing rank-1 and rank-2 lengths together, a packed value padded on the axes it does not pack publishing three companions at three ranks, a mis-ranked length, and a companion declared but written by no step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…d say why A follow-on to the previous commit, closing the asymmetry the schema PR (#2009) found on its own side of the same rule. The obligation requires a companion to be emitted; the carve-out already required the value that *names* it to be emitted, but the neighbouring decidability sentence still said "decidable from the declared outputs alone", which invites the weaker reading. Asking one side for an emit while accepting a bare declaration on the other is two answers to one question (Rule 10), and the weaker half is exploitable: declare a padded output beside any `shared` vector, write neither, and the vector walks past the serving rule on the strength of a payload that does not exist. Both halves now say emitted, the exploit is written down as the reason, and an expectation is stated to carry both the declaration that claims the value and whether anything writes that declaration. The decidability claim is corrected rather than dropped: the condition is decided from the workflow's own declarations — its outputs and its steps — with no runtime information. That is what made the check affordable, and it survives the stronger reading; only "outputs alone" was wrong. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…xis` The rank clause was corrected two commits ago, but only in the form "the number of axes outer to the padded one". That is the definition; it is not the check, and an implementer has to derive one from the other. The schema PR (#2009) states the same fact as `rank == axis` because that is what its validator evaluates, and the derivation is exactly where the earlier flat "rank 1" survived review on both sides. Both spellings are now given together, in §3.2 where the shape rule lives, in §4 rule 8's carve-out, and in decisions §10.6. No rule changes; a reader of any of the three gets the check without translating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
PR #2009 merged as 0448f2b, which lands phases P1 and P2 of this design: `WorkflowComponent.batch_capacity`, `TensorContract.padding`, the `levels` ownership chain, the `pack_offsets` / `pack_owner` / `valid_lengths` content roles, the `schema_version` gate, and the load-time validators for all of them. The design of record was still written as if none of it existed, which made its status line, its evidence section, and 57 of its source citations false. What changed: - Status is `partly implemented` rather than `proposed`, naming which phases landed and which did not. The interpreter, preprocessor, scheduler, and backend phases remain unimplemented, so a package can now *declare* a batched encoder that no runtime will group; §6 already requires that asymmetry to be safe, and the note says so. - §2 keeps its observations in the past tense and adds a `Since #2009` note per subsection. The observations are the record of why each field exists and are not recoverable from the field, so they are marked rather than deleted. §2.3, §2.4, and §2.5 are untouched because nothing closed them. - §3.3 and the `valid_lengths` argument no longer claim the roles are absent from the vocabulary; they are declarable and nothing produces them, which is a different and still-open gap. - §6.1's `schema_version` doc-comment defect is recorded as corrected, in the narrow form this design argued for: compatibility comes from a field being absent by default, which buys nothing for a field that was reshaped. - §8 marks P1 and P2 as shipped while keeping the full obligation text, so the phase list stays reviewable against the design instead of decaying into a changelog. - INFERENCE_METADATA_DECISIONS.md §10.4 becomes `What §10.1 did not have`, and §10.5 / §10.6 drop `proposed` for a status note that separates the load-time half now in force from the runtime half that is not. DESIGN.md and docs/README.md follow. - 57 citations re-pinned across the two documents, 39 mechanically and 16 by hand where the merge moved the claim rather than the line. Several were in range but semantically wrong after the `Image*` to `Vision*` rename, which line-number repair alone would not have caught. Two pre-existing defects found while checking: a link to a renamed §6 anchor, and a short-form `redesign_invariants.rs:158` citation appearing before its first full path. Both fixed. Docs only. Verified: every intra-doc link and anchor resolves, every citation is in range and reads as claimed, 28 acceptance rows with uniform columns, `git diff --check` clean, lines within 88 columns. `cargo test -p onnx-genai-metadata --test encoder_batching` passes 104 tests at this main, which is the evidence for the P2 marker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…fy by content #2009 merged as `0448f2bc6`, moving the files this document cites most. The citations were re-pinned in an earlier commit on this branch, but the check that guarded them asked only whether a line number fell inside the file. That is a property of the file's *length*, not of its *content*: a citation can point at unrelated code that reads plausibly and still pass, and the failure is silent. Verification is now content-aware — for each of the 97 ranged citations, the range's **first** line must contain the symbol or comment the surrounding sentence claims, and the range must contain a second distinctive substring. Anchoring on the head line makes the check position-sensitive, so every citation fails under a one-line drift rather than passing. Applying it to the merged tree found three references that were in range and wrong at the head, all of them narrowings of a previously correct range: - `schema/package.rs:158-174` -> `162-174`. The claim is about `batch_invariance`; lines 158-161 are the unrelated `decoding` field, so the citation opened on someone else's declaration. - `ort/value.rs:1543-1551` -> `1544-1551`. The claim names `alias_with_offset`; 1543 is the closing line of the preceding doc comment. - `ort/value.rs:1526-1542` -> `1524-1543`, in both documents. The claim quotes the no-copy-view doc comment; 1526 is a blank `///`. Re-pinned to the whole comment block, since a block citation cannot be off by one. The remaining 54 ranges verify unchanged against the merged tree, including all 80 citations into the 24 files #2009 touched. Records the convention in the header so the next editor re-pins by locating the named symbol rather than by adjusting the number until it lands inside the file. Docs only: no code, schema, or fixture changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
Two amendments adopted from the schema implementation (#2009), both places where the implementation was stricter than this document and right to be. **The packed axis rule now covers every packed value.** §4 rule 2 scoped `axis == 0` to values a `batch_capacity` component consumes or produces. That scope was too narrow: the runtime splits a packed value per request whenever it hands one back — rule 8's emit path rebases offsets and derives per-request owners with no capacity declaration in sight — and that split wants the same contiguous span for the same aliasing reason. Under the old scope a strided packed emit was legal precisely where nothing had declared it could pay for one. No fixture in the tree declares a non-zero packed axis, so the wider rule rejects nothing that exists. **A preprocessing-program companion must carry the companion role.** §3.3 introduced `pack_offsets` and `pack_owner` but stated their use only descriptively. §4 rule 4 now requires them of any level companion that is a declared program output. The asymmetry with §3.2, where a length value is resolved by name and its role is descriptive, is deliberate and now explained: length vectors have established modality spellings that predate this design and cannot be narrowed to one role, while the companion roles are new and have nothing to accommodate — so requiring them costs nothing and catches a program that wires, say, `grid_dimensions` into a `levels` entry at load rather than shipping a plausible int64 rank-1 vector that means something else. Decisions §10.5 and §10.6 restate both, matrix row 17 gains the two rejections, and the P2 phase box's stale "§3.1 rules 1-7" is corrected to 1-9 to match the body. No other rule, example, or acceptance row changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…hat a document must say Two contract-visible consequences of the schema implementation's code review (#2009), recorded here so the design of record does not lag the contract. **A ragged emit publishes the account of its raggedness.** §4 rule 8 required a packed emit to declare every level's `offsets` and `owner`, but said nothing about a padded emit, which is the same hole. §3.2 rejects a payload-shaped validity mask precisely so the length vector is the only account of the padding that exists anywhere; a caller that receives the payload without it holds trailing entries it cannot distinguish from real ones. Rule 8 is generalized to cover both, the serving carve-out now admits an `int64` rank-1 `shared` output named as another emitted value's `valid_lengths` as well as one named as an offsets/owner, and §3.2, decisions §10.5/§10.6, matrix row 25, and P2's negative fixtures follow. A `valid_lengths` companion needs no rebasing on delivery — a length is already relative to the item it measures — so a request receives the slice indexing its own items. **A version may gate what a document must say; never what it may say wrongly.** §6.1 gains the distinction, prompted by a rule that demanded an explicit append axis and thereby rejected two shipped diffusion packages that meant the default they were written against. When a new field makes an existing default ambiguous, the demand to state intent explicitly starts at the version that introduced the ambiguity. Range checks and every rule about a value a document did state stay unconditional at every version — relaxing those lets an older spelling assert something false rather than merely leave something unsaid. The two look alike in a validator and are opposites in effect: one preserves packages that predate a question, the other would preserve their bugs. Packages under the exemption should still be migrated, so it stays a guarantee rather than a live path nothing exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…ed is not published Two corrections to §4 rule 8 and its mirrors in decisions §10.5/§10.6, both raised by the schema PR (#2009) after a review found the second of them as a live contradiction in its validator. **The carve-out's "rank 1" was wrong for a length.** An `offsets` and an `owner` are rank 1 by construction, so the flat phrasing was accurate for them and read as though it were the general rule. It is not: §3.2 fixes a `valid_lengths` at the number of axes outer to the padded one, so a value padded on axis 2 publishes a rank-2 length vector. Under the flat reading, one rule of this design requires that companion and another refuses it — and the refusal necessarily advises `request_aligned`, which is exactly what a companion must not declare. Following the narrower wording would make a correct package wrong. The condition is therefore per *reference*: collect what each declaration naming the value requires of it — role, and the rank that role implies at that site — and admit the value if it satisfies any one. Three consequences are written down because each is a place an implementer must choose. Expectations accumulate rather than overwrite, since one length vector may legitimately bound the same dimension of two outputs. Two declarations demanding different ranks of one value is a genuine contradiction, but it belongs to the padding declaration that is wrong; raising it at the serving rule names an innocent line. And where a padded dimension does not resolve to an axis, the reference carries no rank expectation at all — that declaration is already being rejected, and a second complaint about a rank nothing could compute sends the reader somewhere there is nothing to fix. A value named as a companion but shaped like something else is refused as a malformed companion, naming role and expected rank, rather than falling through to the generic message that advises the one layout a companion may not have. **Declared is not published.** Both halves of rule 8 now require the companion to be emitted — written by some declared step — not merely to appear in the output list. An output nothing writes hands the caller an empty vector beside a ragged payload, which is the precise failure the rule exists to prevent wearing the appearance of compliance. The check is whole-workflow rather than path-sensitive: "written by some declared step" is what is soundly decidable without evaluating branch predicates, and a path-sensitive rule would reject a correct package whose companion is written in a sibling branch of the emit. Acceptance row 25 grows the cases that distinguish the two readings: doubly padded values publishing rank-1 and rank-2 lengths together, a packed value padded on the axes it does not pack publishing three companions at three ranks, a mis-ranked length, and a companion declared but written by no step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…d say why A follow-on to the previous commit, closing the asymmetry the schema PR (#2009) found on its own side of the same rule. The obligation requires a companion to be emitted; the carve-out already required the value that *names* it to be emitted, but the neighbouring decidability sentence still said "decidable from the declared outputs alone", which invites the weaker reading. Asking one side for an emit while accepting a bare declaration on the other is two answers to one question (Rule 10), and the weaker half is exploitable: declare a padded output beside any `shared` vector, write neither, and the vector walks past the serving rule on the strength of a payload that does not exist. Both halves now say emitted, the exploit is written down as the reason, and an expectation is stated to carry both the declaration that claims the value and whether anything writes that declaration. The decidability claim is corrected rather than dropped: the condition is decided from the workflow's own declarations — its outputs and its steps — with no runtime information. That is what made the check affordable, and it survives the stronger reading; only "outputs alone" was wrong. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…xis` The rank clause was corrected two commits ago, but only in the form "the number of axes outer to the padded one". That is the definition; it is not the check, and an implementer has to derive one from the other. The schema PR (#2009) states the same fact as `rank == axis` because that is what its validator evaluates, and the derivation is exactly where the earlier flat "rank 1" survived review on both sides. Both spellings are now given together, in §3.2 where the shape rule lives, in §4 rule 8's carve-out, and in decisions §10.6. No rule changes; a reader of any of the three gets the check without translating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
PR #2009 merged as 0448f2b, which lands phases P1 and P2 of this design: `WorkflowComponent.batch_capacity`, `TensorContract.padding`, the `levels` ownership chain, the `pack_offsets` / `pack_owner` / `valid_lengths` content roles, the `schema_version` gate, and the load-time validators for all of them. The design of record was still written as if none of it existed, which made its status line, its evidence section, and 57 of its source citations false. What changed: - Status is `partly implemented` rather than `proposed`, naming which phases landed and which did not. The interpreter, preprocessor, scheduler, and backend phases remain unimplemented, so a package can now *declare* a batched encoder that no runtime will group; §6 already requires that asymmetry to be safe, and the note says so. - §2 keeps its observations in the past tense and adds a `Since #2009` note per subsection. The observations are the record of why each field exists and are not recoverable from the field, so they are marked rather than deleted. §2.3, §2.4, and §2.5 are untouched because nothing closed them. - §3.3 and the `valid_lengths` argument no longer claim the roles are absent from the vocabulary; they are declarable and nothing produces them, which is a different and still-open gap. - §6.1's `schema_version` doc-comment defect is recorded as corrected, in the narrow form this design argued for: compatibility comes from a field being absent by default, which buys nothing for a field that was reshaped. - §8 marks P1 and P2 as shipped while keeping the full obligation text, so the phase list stays reviewable against the design instead of decaying into a changelog. - INFERENCE_METADATA_DECISIONS.md §10.4 becomes `What §10.1 did not have`, and §10.5 / §10.6 drop `proposed` for a status note that separates the load-time half now in force from the runtime half that is not. DESIGN.md and docs/README.md follow. - 57 citations re-pinned across the two documents, 39 mechanically and 16 by hand where the merge moved the claim rather than the line. Several were in range but semantically wrong after the `Image*` to `Vision*` rename, which line-number repair alone would not have caught. Two pre-existing defects found while checking: a link to a renamed §6 anchor, and a short-form `redesign_invariants.rs:158` citation appearing before its first full path. Both fixed. Docs only. Verified: every intra-doc link and anchor resolves, every citation is in range and reads as claimed, 28 acceptance rows with uniform columns, `git diff --check` clean, lines within 88 columns. `cargo test -p onnx-genai-metadata --test encoder_batching` passes 104 tests at this main, which is the evidence for the P2 marker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…fy by content #2009 merged as `0448f2bc6`, moving the files this document cites most. The citations were re-pinned in an earlier commit on this branch, but the check that guarded them asked only whether a line number fell inside the file. That is a property of the file's *length*, not of its *content*: a citation can point at unrelated code that reads plausibly and still pass, and the failure is silent. Verification is now content-aware — for each of the 97 ranged citations, the range's **first** line must contain the symbol or comment the surrounding sentence claims, and the range must contain a second distinctive substring. Anchoring on the head line makes the check position-sensitive, so every citation fails under a one-line drift rather than passing. Applying it to the merged tree found three references that were in range and wrong at the head, all of them narrowings of a previously correct range: - `schema/package.rs:158-174` -> `162-174`. The claim is about `batch_invariance`; lines 158-161 are the unrelated `decoding` field, so the citation opened on someone else's declaration. - `ort/value.rs:1543-1551` -> `1544-1551`. The claim names `alias_with_offset`; 1543 is the closing line of the preceding doc comment. - `ort/value.rs:1526-1542` -> `1524-1543`, in both documents. The claim quotes the no-copy-view doc comment; 1526 is a blank `///`. Re-pinned to the whole comment block, since a block citation cannot be off by one. The remaining 54 ranges verify unchanged against the merged tree, including all 80 citations into the 24 files #2009 touched. Records the convention in the header so the next editor re-pins by locating the named symbol rather than by adjusting the number until it lands inside the file. Docs only: no code, schema, or fixture changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
Two amendments adopted from the schema implementation (#2009), both places where the implementation was stricter than this document and right to be. **The packed axis rule now covers every packed value.** §4 rule 2 scoped `axis == 0` to values a `batch_capacity` component consumes or produces. That scope was too narrow: the runtime splits a packed value per request whenever it hands one back — rule 8's emit path rebases offsets and derives per-request owners with no capacity declaration in sight — and that split wants the same contiguous span for the same aliasing reason. Under the old scope a strided packed emit was legal precisely where nothing had declared it could pay for one. No fixture in the tree declares a non-zero packed axis, so the wider rule rejects nothing that exists. **A preprocessing-program companion must carry the companion role.** §3.3 introduced `pack_offsets` and `pack_owner` but stated their use only descriptively. §4 rule 4 now requires them of any level companion that is a declared program output. The asymmetry with §3.2, where a length value is resolved by name and its role is descriptive, is deliberate and now explained: length vectors have established modality spellings that predate this design and cannot be narrowed to one role, while the companion roles are new and have nothing to accommodate — so requiring them costs nothing and catches a program that wires, say, `grid_dimensions` into a `levels` entry at load rather than shipping a plausible int64 rank-1 vector that means something else. Decisions §10.5 and §10.6 restate both, matrix row 17 gains the two rejections, and the P2 phase box's stale "§3.1 rules 1-7" is corrected to 1-9 to match the body. No other rule, example, or acceptance row changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…hat a document must say Two contract-visible consequences of the schema implementation's code review (#2009), recorded here so the design of record does not lag the contract. **A ragged emit publishes the account of its raggedness.** §4 rule 8 required a packed emit to declare every level's `offsets` and `owner`, but said nothing about a padded emit, which is the same hole. §3.2 rejects a payload-shaped validity mask precisely so the length vector is the only account of the padding that exists anywhere; a caller that receives the payload without it holds trailing entries it cannot distinguish from real ones. Rule 8 is generalized to cover both, the serving carve-out now admits an `int64` rank-1 `shared` output named as another emitted value's `valid_lengths` as well as one named as an offsets/owner, and §3.2, decisions §10.5/§10.6, matrix row 25, and P2's negative fixtures follow. A `valid_lengths` companion needs no rebasing on delivery — a length is already relative to the item it measures — so a request receives the slice indexing its own items. **A version may gate what a document must say; never what it may say wrongly.** §6.1 gains the distinction, prompted by a rule that demanded an explicit append axis and thereby rejected two shipped diffusion packages that meant the default they were written against. When a new field makes an existing default ambiguous, the demand to state intent explicitly starts at the version that introduced the ambiguity. Range checks and every rule about a value a document did state stay unconditional at every version — relaxing those lets an older spelling assert something false rather than merely leave something unsaid. The two look alike in a validator and are opposites in effect: one preserves packages that predate a question, the other would preserve their bugs. Packages under the exemption should still be migrated, so it stays a guarantee rather than a live path nothing exercises. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…ed is not published Two corrections to §4 rule 8 and its mirrors in decisions §10.5/§10.6, both raised by the schema PR (#2009) after a review found the second of them as a live contradiction in its validator. **The carve-out's "rank 1" was wrong for a length.** An `offsets` and an `owner` are rank 1 by construction, so the flat phrasing was accurate for them and read as though it were the general rule. It is not: §3.2 fixes a `valid_lengths` at the number of axes outer to the padded one, so a value padded on axis 2 publishes a rank-2 length vector. Under the flat reading, one rule of this design requires that companion and another refuses it — and the refusal necessarily advises `request_aligned`, which is exactly what a companion must not declare. Following the narrower wording would make a correct package wrong. The condition is therefore per *reference*: collect what each declaration naming the value requires of it — role, and the rank that role implies at that site — and admit the value if it satisfies any one. Three consequences are written down because each is a place an implementer must choose. Expectations accumulate rather than overwrite, since one length vector may legitimately bound the same dimension of two outputs. Two declarations demanding different ranks of one value is a genuine contradiction, but it belongs to the padding declaration that is wrong; raising it at the serving rule names an innocent line. And where a padded dimension does not resolve to an axis, the reference carries no rank expectation at all — that declaration is already being rejected, and a second complaint about a rank nothing could compute sends the reader somewhere there is nothing to fix. A value named as a companion but shaped like something else is refused as a malformed companion, naming role and expected rank, rather than falling through to the generic message that advises the one layout a companion may not have. **Declared is not published.** Both halves of rule 8 now require the companion to be emitted — written by some declared step — not merely to appear in the output list. An output nothing writes hands the caller an empty vector beside a ragged payload, which is the precise failure the rule exists to prevent wearing the appearance of compliance. The check is whole-workflow rather than path-sensitive: "written by some declared step" is what is soundly decidable without evaluating branch predicates, and a path-sensitive rule would reject a correct package whose companion is written in a sibling branch of the emit. Acceptance row 25 grows the cases that distinguish the two readings: doubly padded values publishing rank-1 and rank-2 lengths together, a packed value padded on the axes it does not pack publishing three companions at three ranks, a mis-ranked length, and a companion declared but written by no step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…d say why A follow-on to the previous commit, closing the asymmetry the schema PR (#2009) found on its own side of the same rule. The obligation requires a companion to be emitted; the carve-out already required the value that *names* it to be emitted, but the neighbouring decidability sentence still said "decidable from the declared outputs alone", which invites the weaker reading. Asking one side for an emit while accepting a bare declaration on the other is two answers to one question (Rule 10), and the weaker half is exploitable: declare a padded output beside any `shared` vector, write neither, and the vector walks past the serving rule on the strength of a payload that does not exist. Both halves now say emitted, the exploit is written down as the reason, and an expectation is stated to carry both the declaration that claims the value and whether anything writes that declaration. The decidability claim is corrected rather than dropped: the condition is decided from the workflow's own declarations — its outputs and its steps — with no runtime information. That is what made the check affordable, and it survives the stronger reading; only "outputs alone" was wrong. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…xis` The rank clause was corrected two commits ago, but only in the form "the number of axes outer to the padded one". That is the definition; it is not the check, and an implementer has to derive one from the other. The schema PR (#2009) states the same fact as `rank == axis` because that is what its validator evaluates, and the derivation is exactly where the earlier flat "rank 1" survived review on both sides. Both spellings are now given together, in §3.2 where the shape rule lives, in §4 rule 8's carve-out, and in decisions §10.6. No rule changes; a reader of any of the three gets the check without translating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
PR #2009 merged as 0448f2b, which lands phases P1 and P2 of this design: `WorkflowComponent.batch_capacity`, `TensorContract.padding`, the `levels` ownership chain, the `pack_offsets` / `pack_owner` / `valid_lengths` content roles, the `schema_version` gate, and the load-time validators for all of them. The design of record was still written as if none of it existed, which made its status line, its evidence section, and 57 of its source citations false. What changed: - Status is `partly implemented` rather than `proposed`, naming which phases landed and which did not. The interpreter, preprocessor, scheduler, and backend phases remain unimplemented, so a package can now *declare* a batched encoder that no runtime will group; §6 already requires that asymmetry to be safe, and the note says so. - §2 keeps its observations in the past tense and adds a `Since #2009` note per subsection. The observations are the record of why each field exists and are not recoverable from the field, so they are marked rather than deleted. §2.3, §2.4, and §2.5 are untouched because nothing closed them. - §3.3 and the `valid_lengths` argument no longer claim the roles are absent from the vocabulary; they are declarable and nothing produces them, which is a different and still-open gap. - §6.1's `schema_version` doc-comment defect is recorded as corrected, in the narrow form this design argued for: compatibility comes from a field being absent by default, which buys nothing for a field that was reshaped. - §8 marks P1 and P2 as shipped while keeping the full obligation text, so the phase list stays reviewable against the design instead of decaying into a changelog. - INFERENCE_METADATA_DECISIONS.md §10.4 becomes `What §10.1 did not have`, and §10.5 / §10.6 drop `proposed` for a status note that separates the load-time half now in force from the runtime half that is not. DESIGN.md and docs/README.md follow. - 57 citations re-pinned across the two documents, 39 mechanically and 16 by hand where the merge moved the claim rather than the line. Several were in range but semantically wrong after the `Image*` to `Vision*` rename, which line-number repair alone would not have caught. Two pre-existing defects found while checking: a link to a renamed §6 anchor, and a short-form `redesign_invariants.rs:158` citation appearing before its first full path. Both fixed. Docs only. Verified: every intra-doc link and anchor resolves, every citation is in range and reads as claimed, 28 acceptance rows with uniform columns, `git diff --check` clean, lines within 88 columns. `cargo test -p onnx-genai-metadata --test encoder_batching` passes 104 tests at this main, which is the evidence for the P2 marker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
added a commit
that referenced
this pull request
Aug 25, 2026
…fy by content #2009 merged as `0448f2bc6`, moving the files this document cites most. The citations were re-pinned in an earlier commit on this branch, but the check that guarded them asked only whether a line number fell inside the file. That is a property of the file's *length*, not of its *content*: a citation can point at unrelated code that reads plausibly and still pass, and the failure is silent. Verification is now content-aware — for each of the 97 ranged citations, the range's **first** line must contain the symbol or comment the surrounding sentence claims, and the range must contain a second distinctive substring. Anchoring on the head line makes the check position-sensitive, so every citation fails under a one-line drift rather than passing. Applying it to the merged tree found three references that were in range and wrong at the head, all of them narrowings of a previously correct range: - `schema/package.rs:158-174` -> `162-174`. The claim is about `batch_invariance`; lines 158-161 are the unrelated `decoding` field, so the citation opened on someone else's declaration. - `ort/value.rs:1543-1551` -> `1544-1551`. The claim names `alias_with_offset`; 1543 is the closing line of the preceding doc comment. - `ort/value.rs:1526-1542` -> `1524-1543`, in both documents. The claim quotes the no-copy-view doc comment; 1526 is a blank `///`. Re-pinned to the whole comment block, since a block citation cannot be off by one. The remaining 54 ranges verify unchanged against the merged tree, including all 80 citations into the 24 files #2009 touched. Records the convention in the header so the next editor re-pins by locating the named symbol rather than by adjusting the number until it lands inside the file. Docs only: no code, schema, or fixture changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby
pushed a commit
that referenced
this pull request
Aug 25, 2026
The batching design announced that the v1.1 gate shipped, then repeated pre-#2009 claims that schema_version was still unvalidated and cited lines that now document the opposite. Put the historical gap in past tense and point the normative section at the current normalized contract and pre-deserialization gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Do not merge
Paused behind the design of record, PR #2010. This is the schema half; that PR
is the contract half. Coordinated against
justinchuby/vision-batching-designat
0ea8344a48d494b27740bf133ff0af57ecc50653, anddocs/genai/ENCODER_BATCHING.mdat that SHA is authoritative wherever this branch and that doc disagree.
What this adds
A component may declare how many requests one invocation of it can absorb, and a
tensor may say which of its entries are real. Nothing here executes anything --
the runtime work is downstream. Every package that does not spell
token_packedparses unchanged, because absence of
batch_capacitystill means one request perinvocation;
token_packeditself is reshaped and its old spelling is refused byname, which the "Breaking change" section below states in full.
WorkflowComponent.batch_capacity--uniform_dimensions(shape symbols thatmust agree across the requests sharing an invocation) and
budgets(bounds onwhat the invocation may materialize).
TensorContract.padding-- a list of{ dimension, valid_lengths }entriesnaming the value that says how much of a padded dimension is real.
BatchLayout::TokenPacked { axis, levels }-- a flat payload plus a boundedchain of ownership levels, each
{ offsets, owner, extent }, innermost first.pack_offsets,pack_owner, and the genericvalid_lengthsrole.Video is not a separate mechanism. A frame-to-clip-to-request chain is two
levels of the same ownership construct, and the fixtures cover both a temporally
padded encoder and a packed one.
Second review, resolved
extentis per level, not per layout. A chain may mix: the canonicalvideo fixture produces its inner token-to-clip companions and preserves the
caller's outer clip-to-row pair. An output level omitting
extentisrejected; an input level declaring one is rejected too, since the runtime
built those.
preservedis checked positively -- the pair must belong to aninput port of the same component and agree with it on both extent symbols.
A version gate that runs before typed deserialization.
parse_metadatais a real entry point, and the loaders that used to call
serde_yamldirectly now call it --
schema_version.rsproves both the file path and thein-memory path go through it. Grammar is
[v]major[.minor]with minordefaulting to 0, so absent,
v1,1, and1.0are one version. A documentwhose minor exceeds what the runtime supports is refused with its version as
written, its normalized form, and the highest supported version.
Deliberately stricter than
crates/onnx-model-package/src/lib.rs:563-579,which parses a minor and then ignores it. §6.1 of the design justifies the
divergence: without a gate, a v1.1 document meets
deny_unknown_fieldson anold core and fails with a message about a field name rather than a version.
uniform_dimensionsnames ordinary per-item symbols only -- never aport's flattened packed symbol, never an ownership level's unit count. Both
of those are counts the scheduler chose, and pinning them would be a bound
pretending to be a shape fact.
This revision
Corrections from the coordination round, each a rule that was right about the
wrong quantity.
budget only when its symbol was pinned. Pinning is not the property that makes
a bound meaningless -- failing to reach the group is.
patchesalone countspatches per frame, the same number for one frame or a thousand, pinned or
padded.
dimensionsis now a nesting path read outermost first, and its firstsymbol must be a count the scheduler chose. A per-item symbol may appear in
later positions, where it multiplies a count into a footprint.
only.
FIXED_FRAME_VIDEO_ENCODERis[clips, frames, patches, features]packed over clips,
framesan ordinary pinned dimension. A request-alignedalternative would pad the clip axis, which is the cost packing exists to avoid.
SCHEMA_VERSIONstaysv1. Normalizing on read is what makes thespellings in the wild interchangeable; changing what writers stamp would
rewrite bytes to say something no reader distinguishes. The comfyui golden
document is byte-identical to main.
v1.1is what a batching documentdeclares,
1.1is an accepted synonym, andSchemaVersiondisplays with thevso an error names a version the way a document would write it. A presentbut non-string
schema_versionis refused by the gate rather than deferred.paddingentry at theframe_lengthsit already emits.LENGTH_CONTENT_ROLESis informative only, and
valid_frames/valid_samplesleft it -- those arecounts a graph consumes, not statements about padding.
must agree on the offsets extent and the owner extent, not merely name the
same two values. Identity is the pair, never the level index: the video
fixture names one pair at level 1 of the pixel port and level 0 of the pooled
embedding port.
sharedemitted value is admitted onlywhen it is
int64, carries the rank the reference demands, and is named as anoffsets or owner of another emitted value's layout -- or as the
valid_lengthsof another emitted value'spaddingentry -- in the sameworkflow. The rank is read from the reference: offsets and owner maps are
rank 1 by construction, while a length vector's rank is the number of axes
outer to the padded one (
rank == axis). No newBatchLayoutvariant.A declared owner is internal -- an input named as one may not be
externally_suppliable, since invocation-global positions leak cross-requestidentity, while offsets stay suppliable because the runtime rebases them.
Code review, resolved
Three defects, each a rule reaching further than its reason.
A shipped package did not expire. The rank-4 append rule refused the
checked-in
diffusionanddiffusion_guidedpackages, which append a latent[batch, 4, height, width]along its final axis. They are correct and alwayswere: the final axis is what the default meant, and they had no way to say so
explicitly because nothing asked. The rule's reason is that a rank-4 media
tensor makes that axis ambiguous, and a rank-4 media tensor is what v1.1
introduced -- so the demand starts at
BATCHING_SCHEMA_VERSION. An olderdocument keeps the default it was written against; a document declaring v1.1
names the axis; an axis outside the value's rank names nothing at any version
and is still rejected.
Version scoping means the validator has to be told the version, so
validate_pipeline_specnow takes one. Deriving it from the document's fieldswould be the same fact stored twice, and defaulting it inside the validator
would make strictness depend on which entry point a caller happened to use.
Both fixtures are migrated to explicit
axis: 3regardless -- stating the axisthe default already chose costs nothing and outlives the exemption.
On attribution: the append-axis rule is a state-append rule of this crate, not
an encoder-batching contract, and docs(metadata): design of record for generic component batching #2010 does not legislate it -- nothing here
cites the design as its authority. What the design did adopt from this round
is the general form of the split, now §6.1: a version may gate what a document
must say, but it never gates what a document may say wrongly. A demand
to state intent explicitly starts at the version that made the default
ambiguous, because a document written earlier meant the default it was written
against; range checks and every rule about a value a document did state stay
unconditional, because relaxing those would let an older spelling assert
something false rather than merely leave something unsaid. The two look alike
in a validator and are opposites in effect, so
validate_emit_axisnow quotesthe principle rather than restating the one rule.
A padded output could hide the only account of its padding. The serving
rule that every emitted rank>0 value declare a row correspondence admitted
ownership offsets and owner maps, because those are
sharedby construction.A padded output's
valid_lengthsissharedfor the same reason -- one entryper position outside the dimension it bounds -- and was rejected. The carve-out
now names both and stays decidable from the declared outputs alone: an
unrelated
sharedresult is still a per-request answer with no way back to arequest.
The other half of that symmetry was missing too.
validate_packed_emit_companionsrequired a packed emit to publish the companions that split it; nothing
required a padded emit to publish the lengths that read it. A caller holding
trailing entries that mean nothing, with no way to learn how many, is the
failure the padding contract exists to prevent -- and the schema deliberately
refuses a payload-shaped validity mask as the alternative, so that vector is
the only account there is. §4 rule 8 is now retitled for both halves -- a
ragged emit publishes the account of its raggedness, and serving admits it --
and matrix row 25 carried the four cases this branch tested at that round: a
packed emit with its companions, a padded emit with its lengths, a padded emit
withholding them (rejected), and an unrelated
sharedresult (stillrejected). Row 25 and this branch have both grown since; the round below
records what the flat rank-1 reading cost and what replaced it.
The three companions then differ on delivery, and the difference is now pinned
by a test rather than left implicit. An owner map is invocation-global and is
never delivered. Offsets are per-request but group-relative, so they are
delivered rebased to the request's own start. A
valid_lengthsis alreadyrelative to the item it measures and means the same number in any group, so a
request states its own and receives the slice indexing its own items -- nothing
to rebase and no group position to leak. Only the owner rule constrains
suppliability; the test exists so that a later attempt to unify the delivered
companions with the internal one fails rather than passes quietly.
A user-facing error was malformed. The unquoted-
schema_versionmessagewas missing its line continuations and reached a reader with fourteen-space
runs in the middle of two sentences.
Two extensions this branch proposed, since adopted
Both were carried past the doc deliberately, recorded here rather than assumed,
and both are now rules in the design of record at
0ea8344a4. They are statedas rules with their own tests, not left as coverage that happens to exist.
The packed axis is 0 wherever
token_packedappears, not only in acomponent that declares
batch_capacity. The doc originally scoped the ruleto capacity components; the scope was the bug. The justification it gives --
a no-copy view is a contiguous element window
(
crates/onnx-genai-ort/src/value.rs:1526-1542) -- holds whenever a runtimesplits a packed result per request, and the emit path rebases offsets and
derives per-request owners with no capacity in sight. Under the old scope a
strided packed emit was legal exactly where nothing had declared it could pay
for one. Now §4 rule 2, decisions §10.5 / §10.6, matrix row 17.
A preprocessing-program companion carries the
pack_offsetsorpack_ownercontent role. Now §4 rule 4, with the scope made explicit:the requirement reaches only declarations that have a role. A program says
what each of its outputs is, so one wiring some other plausible int64 rank-1
vector into a
levelsentry is caught at load rather than at the firstsplit; a companion the graph produces is an ONNX output port and has nowhere
to carry a role, so an
extent: producedlevel is not asked for somethingits half of the schema does not have. The validator was already scoped this
way and a test now pins it.
§3.3 records why
paddingdeliberately does not get the same treatment, sothe two are not read as an inconsistency: length vectors already have
established modality spellings (
frame_lengths,sample_lengths) thatprograms legitimately emit, so no single role could be demanded without
breaking a program that predates the generic name, while the two ownership
roles are new and reject nothing that exists. Both rules resolve the
reference by name; only this one constrains what the referenced
declaration may claim to be.
Code review, round two: companions are not all rank one
The serving carve-out asked whether an emitted
sharedvalue was named as somedeclared output's companion and whether it was
int64rank 1. That is rightfor an ownership offsets or owner map, which are rank 1 by construction, and
wrong for a validity length. A length has one entry per position of the axes
outer to the dimension it bounds, so its rank is that dimension's axis index:
rank 1 for a padded axis 1, rank 2 for a padded axis 2. A correct rank-2 length
was refused as "a per-request value without a declared
batch_layout" -- advicethat, if followed, would have made it wrong.
The bare name set is now a map from companion name to what the declarations
naming it require: the role, and the rank that role implies at that site. The
carve-out stays narrow --
int64, referenced by a declared output contract, andat the exact rank that reference demands.
The design doc adopted the correction rather than the code working around it.
#2010 now words the carve-out as
int64at the rank that reference demands,and records why the flat form was wrong: a refusal under it necessarily advised
request_aligned, which is the one layout a companion MUST NOT declare, sofollowing the doc would have made a correct package wrong. The three judgement
calls below are normative there now.
rather than overwritten and a value satisfies the carve-out by matching any one
of them. Two declarations demanding different ranks of one value is a
contradiction, but it is
validate_padding's to report against the declarationthat is wrong -- not this rule's to report as a missing row axis.
rank, because a second complaint about a rank nothing could compute sends a
reader to the wrong line.
its error, naming the role and rank expected of it. The generic message told
its author to declare a row axis, which is precisely what a companion must not
do.
Both companion obligations now require the companion to be emitted, not merely
declared. Declaring an output and writing it are different facts and only the
second delivers anything: a workflow that declares its lengths and never emits
them satisfies the letter of "publishes them" while handing the caller a padded
tensor and an empty vector, which is the failure the obligation exists to
prevent. The same hole existed on the packing side and is closed with it -- one
rule, two spellings. The check is whole-workflow rather than path-sensitive,
deliberately: whether two emits reach a caller together depends on a branch
predicate, and this crate does not evaluate predicates, so a path-sensitive rule
would either reject correct packages whose companion is written in a sibling
branch or claim a precision it does not have. "Somewhere in the declared steps"
is what can be decided soundly, and it catches the case that occurs.
One narrowing found while aligning with the design's wording. The carve-out
asked that the naming output be declared; the companion obligation asks that a
companion be emitted. Those are two answers to one question, and the gap is
exploitable -- a package could carry any
sharedvector past the serving rule bydeclaring a padded output beside it and never emitting one. An expectation now
carries the output that claims it and whether any step writes that output, and
only a written claim admits anything. This is also what the design says: a
companion is admitted when named by another emitted value's layout or padding
entry. A value left out by this is told so, rather than told to declare a row
axis -- the defect is the payload's missing emit, not the companion.
Seven tests over two new fixtures -- a
request_alignedand atoken_packedserving output, each padded on two dimensions with a rank-1 length for axis 1 and
a rank-2 length for axis 2: valid publication of both shapes; a packed result
padded on the axes it does not pack, publishing ownership and lengths together at
three different ranks; a length declared at rank 1 for a padded axis 2, rejected
by role and rank; a declared-but-never-emitted length; a declared-but-never-
emitted owner map; and an unrelated
sharedrank-2 result, still rejected.Breaking change: the flat
token_packedspellingtoken_packedonmaincarriesoffsetsandownerdirectly on the layout,which can say only that items sit in request rows. This PR replaces that with
levels, an ownership chain, because an item can itself be a group -- frames inclips in rows -- and one pair cannot say that. A document written against the
flat spelling does not load, at any declared version.
This is deliberate and unshimmed. A one-level chain is exactly what the flat
pair meant, so a translation would be expressible; it is refused instead because
the schema is pre-release and rule 3 is to reshape completely rather
than keep a second spelling alive, and because no in-tree package uses
token_packedat all -- nothing ships that a translation would rescue.What a clean break owes is the error, and that debt was unpaid until now.
serdealone reported
unknown fieldoffsets, expectedaxisorlevels``, whichreads like a typo and stops at the contract rather than the layout, so a reader
goes hunting for a misspelling instead of finding a migration.
parser::reject_flat_token_packedrecognizes the retired shape in the preparse-- the same place and the same discipline as `reject_retired_model_io`, which
never produces a value and only ever produces a refusal -- and reports the
containing path, the replacement to write, and that nothing will convert the
document for it:
It runs after the version gate. The flat pair is a reshape inside the v1
line, so refusing it presumes the document belongs to that line; a document
declaring a version this build cannot read is refused for that instead, because a
reader that has never seen v2 has no standing to say what a spelling means there.
A test pins the order rather than the outcome: both orders reject a
v2.0document carrying the flat pair, and only the message distinguishes them, so
a_document_from_a_version_this_build_cannot_read_is_refused_for_thatassertsthe migration text is absent and the version is named.
A noted inconsistency, deliberately not fixed here.
reject_retired_model_ioruns before the gate; this one runs after. I am not aligning them in this PR.
Whether pre-gate is wrong for that recognizer depends on what its message
asserts -- if the shape it refuses was retired across the major boundary rather
than inside a line, running first may be correct for it -- and that is a fact to
be established by reading it, not inherited from this one. Reordering a shipped
refusal path is a behavioural change that deserves a PR whose evidence is about
that path, rather than arriving as a side effect of adding a second recognizer.
Two compatibility claims were overstated and are corrected rather than left to
be discovered. "A package that uses nothing new keeps loading" is a statement
about fields that were added; it was written as though it covered everything,
and a flat-
token_packeddocument uses nothing new and no longer loads. Theschema_versionfield doc and theschema_version.rsmodule doc now say so andpoint at the refusal. The published schema needed no change --
token_packedalready requires
levelsand setsadditionalProperties: false, so a producervalidating against the schema alone is told the same thing the loader tells it --
but that agreement now has a test rather than holding by accident, mirroring the
existing
model.iorequirement that the two readers not disagree about what isloadable.
Seven tests in
encoder_batching.rs, one inschema_sync.rs: the refusal namesthe shape, the path, and the finality; it fires at
v1,"1.0",v1.0andv1.1alike; a half-migrated layout keeping one of the two keys is stillexplained rather than dropped back to the unknown-field error; the migrated
document the error prints actually loads; a value merely named
offsetsis notmistaken for a retired layout; a
v2.0document is refused for its version andnot lectured about a v1 migration; and the published schema publishes neither
retired key.
A padded output has one account of how much of it is real
An emit may carry a
valid_length, and an output contract may declarepadding. Both say how much of a result is real. Nothing reconciles them, anda document setting both on the same value is now refused at load.
They are not two views of one fact.
valid_lengthtruncates what a step writesand is invisible in the contract -- a caller reading the metadata cannot see it.
A
paddingentry is part of what the caller reads and names a length vectorthat rule 8 requires the workflow to publish. So a document that sets both can
ship a tensor whose published lengths describe a shape the emit did not
produce, and the caller holds the account the emit is free to contradict. Two
answers to one question, which is the same defect RULES.md rule 10 names.
padding.valid_lengthsis authoritative, and the error says so, because"these two disagree" is not actionable until the reader knows which one to
delete. It names the emit's
valid_length, the output, and every paddeddimension with the length vector bounding it:
token_packedneeds no case of its own and gets none. A packed layout declaresno request axis, so an emit-level length into one is already refused by the
existing rule -- ragged emission with no row axis to be ragged along. The two
rules answer different questions, so a packed and padded output hears from
both, which is correct rather than redundant.
Nor is this version-scoped, and it does not need to be.
paddingis itself a1.1 field, so any document able to express the contradiction has already been
required to declare
v1.1by the existing feature detector; av1documentcannot reach this rule. And it is a rule about what a document did state, not
a demand that it state something new -- the half of the
validate_emit_axissplit that stays unconditional at every version.
Three tests, one fixture pair: the rejection, the same workflow with the
emit-level length removed accepted as its control (guarded so it cannot pass
vacuously), and the packed case still refused for the older reason (guarded with
assert_ne!so a non-matching edit cannot make it vacuous either). Therejection was verified by mutation -- disabling the rule fails it.
Not in scope
Runtime execution, scheduling, and admission. Load-time validation checks the
graph and the contracts; whether a particular tensor's offsets are monotonic is
an invocation-time check on the device that holds them, and this branch does not
force a copy to look.
Verification
cargo test -p onnx-genai-metadata-- 342 across 25 binaries (encoder_batching102,schema_version13,schema_sync3).cargo test -p onnx-genai-engine --test onnx_genai_workflow_conformance-- 15/15,including
mobius_euler_diffusion_workflow_executes_complete_path,mobius_euler_diffusion_workflow_executes_batched,mobius_guided_diffusion_workflow_executes_complete_path, andmobius_video_diffusion_workflow_publishes_causal_temporal_chunks.validate_metadatabinary ontests/fixtures/onnx_genai_workflows/{diffusion,diffusion_guided,video}-- all valid.cargo test -p onnx-genai-genai-config -p onnx-genai-preprocess -p onnx-genai-comfyui-config,cargo test -p onnx-genai-engine --lib(494),cargo check -p onnx-genai-server --all-targets.schema/inference_metadata.schema.jsonregenerated and in sync.cargo fmt --all --check,cargo clippy -p onnx-genai-metadata --all-targets -- -D warnings.