Pular para o conteúdo

FlatBuffers Policy Schema

Este conteúdo não está disponível em sua língua ainda.

The binary files under a study’s policy/ directory are FlatBuffers buffers. Cobre’s runtime writes and reads them through a hand-rolled, allocation-free path in Rust, but external consumers (Python, C++, TypeScript, Java, Go, …) can use the canonical schema file shipped with the source tree to generate a typed reader in any language flatc supports.

File pathRoot table
policy/manifest.binCheckpointManifest (study-global; written last, read first)
policy/cuts/NNN.binStageCuts
policy/basis/NNN.binStageBasis
policy/states/NNN.binStageStates (only when exports.states = true)

NNN is a three-digit, zero-padded id: the pool id for cuts/, the stage id for basis/ and states/. manifest.bin is a single study-global file with no id. The reader derives identity from inside each buffer, never from the file name. The CheckpointManifest root carries the study graph (nodes/edges), the stage count, the producer provenance, and the format_version marker; it replaces the hand-editable policy/metadata.json of earlier releases.

The schema lives at crates/cobre-io/schemas/policy.fbs under namespace Cobre.IO.Policy. It declares file_identifier "CBVF" and file_extension "bin", but still no root_type--root-type is still required per file to select the entry point for flatc. The reader rejects any buffer whose leading identifier is not CBVF, so a buffer written by an earlier release (which carries no identifier at all) is refused before decoding, not silently misparsed.

flatc ships a converter that turns any FlatBuffers buffer into JSON when given the schema. This is the closest thing to a human-readable view of a policy checkpoint:

Terminal window
flatc -t --strict-json \
--root-type StageCuts \
crates/cobre-io/schemas/policy.fbs \
-- output/policy/cuts/000.bin
# writes 000.json next to the .bin

For the basis or states files, swap the --root-type argument for StageBasis or StageStates. --raw-binary is only needed to force flatc to read a buffer written by an earlier release, which carries no CBVF identifier — a current buffer is self-describing and flatc -t verifies the identifier on its own.

flatc emits idiomatic source code for any of its supported target languages. Pick the one matching your toolchain.

Terminal window
flatc --python crates/cobre-io/schemas/policy.fbs
# emits Cobre/IO/Policy/{AffinePiece,EntitySlot,EntityType,StageCuts,StageBasis,StageStates}.py
from Cobre.IO.Policy.StageCuts import StageCuts
with open("output/policy/cuts/000.bin", "rb") as f:
buf = bytearray(f.read())
cuts = StageCuts.GetRootAs(buf, 0)
print("stage_id =", cuts.StageId())
for i in range(cuts.CutsLength()):
piece = cuts.Cuts(i)
print(piece.PieceId(), piece.Intercept(), [piece.Coefficients(j) for j in range(piece.CoefficientsLength())])
Terminal window
flatc --cpp crates/cobre-io/schemas/policy.fbs
# emits policy_generated.h
Terminal window
flatc --ts crates/cobre-io/schemas/policy.fbs
# emits TypeScript modules under cobre/io/policy/

For other targets see flatc --help.

The authoritative description of every field lives in policy.fbs itself — every field carries an inline doc comment. The Output Format page has a tabular summary suitable for reading on the web.

Reserved slots: AffinePiece.reserved_7 and EntitySlot.delivery_anchor

Section titled “Reserved slots: AffinePiece.reserved_7 and EntitySlot.delivery_anchor”

This release renamed the Cut table to AffinePiece and, in the same breaking change, reclaimed one field id and permanently burned two others:

  • AffinePiece.intercept (id 4) now occupies the slot formerly held by the pre-v0.5.0 domination_count field. This id reclaim is the exceptional case in this page’s versioning policy — ordinarily a burned id is never reused.
  • AffinePiece.reserved_7 (id 7) is marked deprecated. It held a former always-empty vector field, deleted in this release. The vtable slot number is permanently burned so no future field can reuse it.
  • EntitySlot.delivery_anchor (id 4) is marked deprecated. It held a former month-integer anchor (year * 12 + (month - 1)), replaced by EntitySlot.delivery_date (id 5) — a YYYYMMDD calendar date (year * 10000 + month * 100 + day), with sentinel -2147483648 (i32::MIN) when the slot has no delivery semantics.

Unlike an ordinary appended field, none of this is wire-backward-compatible: a checkpoint written by an earlier release used field id 4 on AffinePiece for domination_count, not intercept, so it is rejected outright on load rather than read with graceful-absence defaults — see Versioning policy below.

Generated readers emit no accessor for either deprecated field; generated writers cannot emit them. The Cobre runtime’s own writer never sets them in policy files written by the current release.

The schema is not consumed by Cobre’s own build. Two independent implementations describe the same wire format:

  • The schema file crates/cobre-io/schemas/policy.fbs, with explicit (id: N) attributes on every field.
  • The hand-rolled writer/reader in crates/cobre-io/src/output/policy/codec.rs, which encodes vtable slots via the *_FIELD_*: u16 constants. The slot offset is (field_id + 2) * 2.

A conformance test, tests/flatbuffers_schema_conformance.rs in cobre-io, round-trips representative buffers in both directions:

  • Hand-rolled writer → flatc -t → JSON: catches the writer emitting a slot the schema does not declare, or at the wrong offset.
  • JSON → flatc -b → hand-rolled reader: catches the schema declaring a slot the reader expects at a different offset.

The test is gated behind the flatc-conformance cargo feature so that the everyday cargo test does not depend on flatc. To run it:

Terminal window
cargo test -p cobre-io \
--features flatc-conformance \
--test flatbuffers_schema_conformance

If you change either the schema or the slot constants, run the conformance test before merging. The CI workflow that has flatc available runs it on every pull request that touches policy/codec.rs or the schema file.

This format carries a hard version gate on top of the field-level rules below. policy/manifest.bin’s CheckpointManifest carries a required format_version marker, read first — before any .bin payload is parsed — and every .bin payload (the manifest included) carries the CBVF file_identifier described above. A checkpoint written by an earlier release has no manifest.bin at all, so it is rejected at that first read as a missing file — surfaced as cobre.errors.OutputError in Python, and as a CLI message prefixed failed to read policy checkpoint: …. A manifest.bin present but stamped with a different format_version fails the version gate instead (FORMAT_VERSION is 1). Either way there is no conversion path: the only remedy for a checkpoint written by an earlier release is to re-export or retrain.

FlatBuffers’ graceful-absence rule lets us add new fields to any table without breaking older readers, as long as new fields are appended at the end with the next available id. This is the only schema change that does not require an output-format version bump — the format_version gate above governs everything else:

  • Adding a field at the next free id → backward compatible at the wire level only. Old readers see the field as absent and use the FlatBuffers default (zero / empty vector). New readers see the value when the writer was new enough to emit it. Wire compatibility does not imply the values are safe to consume: a field can change what existing fields mean (the cost_scale_factor provenance marker marks cut coefficients as canonical currency units, which a reader that ignores it silently misinterprets — no error, wrong numbers). Cross-release policy compatibility is governed by the one-directional contract in Cost-Scale Canonicalization, not by this wire-level rule.
  • Removing a field → mark it deprecated, never reuse the id. See AffinePiece.reserved_7 and EntitySlot.delivery_anchor for worked examples. AffinePiece.intercept reclaiming id 4 from the former domination_count field is the one exception this page documents — a sanctioned part of this release’s breaking format change, not a precedent for reusing a burned id under the ordinary rule.
  • Changing a field’s type → breaking. Bumps the major output format version.
  • Renaming a field → breaking for flatc-generated code (the accessor name changes). Avoid; if necessary, treat as a major bump.
  • Reordering fields → harmless if (id: N) attributes stay put. The wire layout is determined by the ids, not by source order.