PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
native_order.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "execution.hpp"
6#include "market_driver.hpp"
10
11#include <algorithm>
12#include <array>
13#include <cmath>
14#include <cstddef>
15#include <cstdint>
16#include <exception>
17#include <functional>
18#include <iterator>
19#include <limits>
20#include <map>
21#include <memory>
22#include <optional>
23#include <stdexcept>
24#include <string>
25#include <type_traits>
26#include <utility>
27#include <variant>
28#include <vector>
29
30namespace pineforge::detail {
31class ScratchSlab;
32class NativeHistoryOwner;
34}
35// The matcher (src/native_execution_consumer.hpp) is declared in the engine
36// epoch, as run_failure.hpp and execution_consumer.hpp declare epoch classes,
37// so the friends below name that one class and no other.
38namespace pineforge {
39struct CacheCapacity;
40inline namespace engine_script_run_v20 {
41class NativeExecutionConsumer;
42}
43}
44
46inline namespace native_order_v8 {
47
48// Isolated working-request/value core: current LIVE requests and immutable
49// command history. It does not own positions, cash, paid fees, matching,
50// calendar, host phase, or a second physical book.
51//
52// Identity types remain native_order_v1. Request/core/event values are v8.
53// Physical execution::Action is unchanged; native Reduce uses a typed size
54// source instead of a dummy units field. ExecutionPlan is a transient widening
55// used at the core/consumer boundary.
56
58/// A signed book transaction: finite nonzero units on the side their sign names.
59/// It opens, and it closes against a live opposite book on the way, so a
60/// transaction larger than an opposite position still opens the remainder. An
61/// opening denial rejects the ENTIRE transaction, including that remainder.
63using HandleVisitor = void (*)(void*, const RequestHandle&);
64
65/// A host-maintained, run-scoped roster identity. Zero is invalid and is
66/// never allocated by WorkingRequestCore.
68 std::uint64_t value = 0;
69};
70inline bool operator==(CohortHandle left, CohortHandle right) noexcept {
71 return left.value == right.value;
72}
73inline bool operator!=(CohortHandle left, CohortHandle right) noexcept {
74 return !(left == right);
75}
76inline bool operator<(CohortHandle left, CohortHandle right) noexcept {
77 return left.value < right.value;
78}
79
80/// Exact target exposure for an explicit reversal request. This is distinct
81/// from execution::ReverseTo, which is the transient resolved execution plan.
82struct ReverseTo {
83 double signed_units = 0.0;
84};
85
86enum class HostSizedKind : std::uint8_t { Open = 0, Close = 1 };
87enum class Side : std::uint8_t { Long = 0, Short = 1 };
88/// An opening or a closing whose quantity the HOST resolves, in
89/// NativeStrategyHost::resolve_execution_terms. `side` is required for an
90/// opening, which must declare where it trades. A host-sized request that reaches
91/// a candidate with no units answered is MatchRejectReason::TermsUnresolved. It is
92/// the adapter's sizing seam; a bare host that wants the kernel to resolve a cash
93/// or equity basis uses Sized instead. The C surface admits HostSized as a cohort
94/// close or a Book-scoped WaitForApplied close only (the 1.0 C boundary table).
95struct HostSized {
97 std::optional<Side> side;
98};
99
100/// Resolved host sizing normally remains subject to the run's quantity grid.
101/// A host may instead authenticate literal units for a pure reduction; the
102/// consumer still proves that the positive quantity is representable within
103/// the selected exposure before it can reach settlement.
104enum class ExecutionGridPolicy : std::uint8_t {
107};
108
109/// Native sizing bases (L3). The kernel resolves them into units at the
110/// sizing point. A source adapter lowers its own declaration-level default
111/// quantity onto them and keeps only its rounding and admission quirks
112/// (R5 R2); a host that owns its whole quantity still emits HostSized.
113struct CashValue {
114 double cash = 0.0; // account currency
115};
116/// A fraction in (0, 1] of MARKED equity at the sizing point. The other SizeBasis
117/// alternative, CashValue, is an absolute account-currency amount; both resolve as
118/// units = cash / (price * point_value * fx).
120 double fraction = 0.0; // of marked equity at the sizing point (0.10 = 10 %)
121};
122/// The two bases a Sized intent may name. Extended only by appending, so an
123/// existing aggregate initializer keeps its meaning.
124using SizeBasis = std::variant<CashValue, EquityFraction>;
125
126/// AtMatch resolves the basis at the matching candidate; AtAcceptance freezes
127/// the resolved units when the request is accepted.
128enum class SizeTime : std::uint8_t { AtMatch = 0, AtAcceptance = 1 };
129
130/// WHICH price the basis is converted at. This is the generic "size against the
131/// expected fill price" rule; it names no source language.
132///
133/// Resolved (today's behaviour) uses the price the kernel would otherwise
134/// settle at: the candidate's default resolved price for AtMatch, the raw
135/// acceptance-point price for AtAcceptance.
136///
137/// Signal is the decision-point price at placement -- the current bar's close,
138/// or the last print, at submit -- carried to the expected market fill: it is
139/// adjusted by the run's slippage for the request's own side
140/// (price +/- slippage_ticks * price_tick) and then rounded onto the run's
141/// price grid when NativeRunSpec::price_grid is on (HalfUp is the nearest
142/// tick; NativePriceGrid::None leaves it unrounded). It is frozen when the
143/// request is accepted, so it composes with SizeTime::AtAcceptance and, with
144/// SizeTime::AtMatch, the basis still converts at that frozen signal price at
145/// every later candidate. A Signal request accepted outside a decision point
146/// has no price to freeze and stays unresolvable.
147///
148/// SignalOnTick is the same decision-point rule measured on the INSTRUMENT's
149/// own tick ladder (NativeRunSpec::price_tick) instead of the run's fill grid:
150/// the decision price is rounded onto the ladder (nearest tick, ties away from
151/// zero), carried to the expected fill by the side's tick slippage, and rounded
152/// onto the ladder again. Two rulings are folded into it, both generic:
153///
154/// * price_tick is the instrument's own resolution and the run already
155/// declares it as the slippage multiplier, so a price the market PRINTS
156/// lives on that ladder whether or not the run also quantizes the prices it
157/// BOOKS. NativeRunSpec::price_grid governs fills; it does not govern what
158/// a signal was worth. A run that books unquantized fills can therefore
159/// still size against the printed price, which Signal cannot express.
160/// * the ladder is applied BEFORE the slippage, not only after. Slippage is
161/// a whole number of ticks, so it carries a ladder price to another ladder
162/// price: pre-rounding makes `round(p) +/- n*tick` exact and leaves the
163/// second rounding a pure binary64 re-normalization, where rounding only
164/// after composes two different quantizations of the same print.
165///
166/// Resolved and Signal are unchanged by this alternative, so every established
167/// Sized resolution keeps its value.
168enum class SizePrice : std::uint8_t { Resolved = 0, Signal = 1, SignalOnTick = 2 };
169
170/// A kernel-sized opening. units = cash / (price * point_value * fx), where
171/// cash is the basis value or fraction * marked equity, and price is the
172/// sizing-point price SizePrice names. A host override of
173/// resolve_execution_terms still has the last word: it sees the kernel-resolved
174/// units as the facts' RemainingUnits and may return its own.
175///
176/// SizeTime chooses WHEN the basis is resolved, SizePrice chooses WHICH price
177/// it converts at, and the two compose. Either way the request reaches the
178/// candidate with a deferred quantity and exactly one terms pass: the kernel
179/// resolves (or republishes the acceptance-frozen) units, publishes them as the
180/// facts' RemainingUnits before resolve_execution_terms and
181/// validate_execution_precommit run, and uses the host's units when the host
182/// returns any. An acceptance-time quantity is additionally an admission input
183/// at placement, so an opening the run cannot admit is
184/// RequestRejectReason::PlacementAdmission at submit rather than a rejected
185/// candidate later.
186struct Sized {
190 /// Which price the basis converts at; see SizePrice. Appended after `time`
191 /// because Sized has no positional aggregate initializer outside tests that
192 /// stop at the basis.
194 /// SnapToGrid floors the resolved units onto the run's quantity grid.
195 /// ExplicitUnits keeps the literal quotient, which the ordinary on-grid
196 /// terms gate then refuses when a grid is configured and the quotient is
197 /// off it; the two are identical on an ungridded run.
199 /// Divide the sizing cash by (1 + fee) when the run's fee kind is
200 /// NativeFeeKind::Percent. Every other fee kind is an exact no-op.
202};
203
204/// A reduction of exactly these units, finite and positive. Capped by the live
205/// exposure of the scope the reduce is bound to, and floored onto the run's
206/// quantity_grid like every other engine quantity.
208 double units = 0.0;
209};
210/// A reduction of exactly what the owner's fill opened, bound at the arm and
211/// reported by a QuantityBoundEvent. It is the arm-time size, which is why no
212/// SizeTime::AtArm exists.
214/// Gross claims the whole bound scope; NetOfSiblings first subtracts the units
215/// already claimed by the live sibling reduces bound to that same scope. The
216/// keys are opaque request/owner handles, never source identifiers.
217enum class ScopeClaim : std::uint8_t { Gross = 0, NetOfSiblings = 1 };
218/// Which measurement of the bound scope the fraction is taken of. AtMatch (the
219/// default) reads the scope as it stands at the matching candidate.
220/// AtAcceptance freezes the scope SIZE when the request is accepted -- the
221/// placement-time live basis -- so two 50 % siblings on one 10-unit lot both
222/// claim 5 under Gross even after the first has already executed. NetOfSiblings
223/// then subtracts the live sibling claims from that frozen basis.
224enum class ScopeBasis : std::uint8_t { AtMatch = 0, AtAcceptance = 1 };
225/// A fraction in (0, 1] of the bound scope, resolved at the matching candidate.
226/// The resolution is units = scope * fraction, one binary64 multiplication: a
227/// caller that spells its size as a percent converts percent -> fraction
228/// itself, so no second rounding step is introduced here.
230 double fraction = 1.0;
232 /// Appended last so every existing aggregate initializer keeps its meaning.
234};
235/// How much a Reduce takes: literal units, the owner's own opening, or a fraction
236/// of the bound scope.
237using ReductionSize = std::variant<ExplicitUnits, OwnerOpenedUnits, ScopeFraction>;
238/// A closing-only intent. It never opens, so it stays legal while the run's
239/// opening limits are already exceeded, and it uses CURRENT exposure rather than
240/// the acceptance-cycle book. On a flat book it terminalizes a NoEffectEvent: no
241/// execution identity, no fill, no fee, no physical action.
245/// What a request does. Six alternatives: Flatten (quantity-free whole-book
246/// close), Reduce (closing only), Transact (a signed book transaction), ReverseTo
247/// (an exact signed target exposure), HostSized (the host resolves the quantity)
248/// and Sized (the kernel resolves it from a cash or equity basis). The variant's
249/// size is pinned by the frozen C++ ABI fixtures, so appending an alternative is
250/// an epoch decision.
251using OrderIntent = std::variant<Flatten, Reduce, Transact, ReverseTo, HostSized, Sized>;
252
253/// Match at the next eligible matching point, with no level to reach. A market
254/// request accepted on bar N cannot fill on that bar's already delivered opening.
255struct Market {};
256/// `fill_through` makes the limit a touch trigger (market-if-touched): the
257/// level still gates when the request becomes executable, but its fill is not
258/// bounded by the level, so slippage may carry it past the level.
259struct Limit {
260 double price = 0.0;
261 bool fill_through = false;
262};
263/// A stop trigger: the modeled path has to reach `price` from the adverse side. A
264/// crossing books the level itself; a point already past it (a gapped open) books
265/// that print. Under a quantizing trigger grid the core re-validates the
266/// activation on the same ladder the matcher used, so a hit the matcher reports is
267/// never refused.
268struct Stop {
269 double price = 0.0;
270};
271/// A stop that, once reached, becomes a limit at `limit`. The stop gates and the
272/// limit bounds; an anchored FromOwnerFill level is refused for this trigger,
273/// because the anchor materializes one level and this trigger has two.
274struct StopLimit {
275 double stop = 0.0;
276 double limit = 0.0;
277};
278/// Trail offset spelled in price ticks instead of a price distance. The
279/// acceptance path resolves it against the run's price tick, writes the
280/// product into Trail::offset and clears the spelling, so a stored definition
281/// always carries a plain price distance and can never resolve twice.
283 double ticks = 0.0;
284};
285/// offset is a price distance: the stop rides `offset` behind the running
286/// best. Zero is legal and means "ride the best": the exit is the first
287/// adverse move past it. Negative and nonfinite offsets are rejected.
288/// arm_price absent means the trail is already armed and starts riding at the
289/// first print. Under a FromOwnerFill anchor it means the owner's fill
290/// supplies the threshold, which is that anchor's whole point: absent is the
291/// anchored spelling, 0.0 is the equivalent placeholder kept legal for hosts
292/// that already write it, and any other written level is refused because the
293/// arm would overwrite it.
294///
295/// best_seed is where the running best STARTS, appended last so every existing
296/// {offset}, {offset, arm_price} and {offset, arm_price, ticks} initializer
297/// keeps its meaning. Absent (the default) is the established behaviour: the
298/// best is the arm's own print -- the arm threshold's own ladder point when a
299/// crossing armed it, the first print the trail sees when it was submitted
300/// already armed. A present seed is a floor on that start: at the arm the best
301/// becomes the favourable one of the seed and the arm print (the higher for a
302/// sell trail, the lower for a buy trail), so a host whose position already
303/// reached a level before this request existed does not restart the ride at a
304/// worse print. It is a generic broker shape -- a trail that rides from its
305/// activation rather than from the next print -- and the level is the host's
306/// own number: the kernel neither derives it nor knows what named it. It is
307/// absolute (an anchor moves the arm threshold, never the seed), it must be
308/// finite and positive, and under a price grid it is put on the ladder exactly
309/// like an observed print. It is folded into the request digest only when
310/// present, so every trail without one keeps the digest it had before the
311/// field existed.
312struct Trail {
313 double offset = 0.0;
314 std::optional<double> arm_price;
315 std::optional<TrailTicks> ticks = std::nullopt;
316 std::optional<double> best_seed = std::nullopt;
317};
318using Trigger = std::variant<Market, Limit, Stop, StopLimit, Trail>;
319
320/// How a materialized anchored level is snapped onto the run's price tick
321/// ladder (NativeRunSpec::price_tick). Raw keeps `fill + offset` exactly,
322/// which is the established behaviour. HalfUp is the nearest tick with ties
323/// away from zero. Directional rounds toward the price region the resting leg
324/// needs, relative to the leg's trigger kind and side exactly as
325/// NativeGridRounding documents it: a buy limit down and a sell limit up, a
326/// stop the other way; a trail arm threshold is reached from the favourable
327/// side like a limit and rounds like one. Either rounding needs a positive
328/// price tick, checked at acceptance exactly like a tick-spelled offset. This
329/// is a generic instrument grid: a source language's own trigger projection
330/// is not spelled here (a host restates the level through its arm hook).
331enum class NativeAnchorRounding : std::uint8_t {
332 Raw = 0,
335};
336
337/// Where a request's trigger level comes from. Absolute is the level written
338/// in the trigger itself. FromOwnerFill defers it to the owner's fill: the
339/// level becomes fill + offset when the owner arms the request, so a bracket
340/// leg can be placed before its parent has a price. offset is signed (adverse
341/// is negative) and is spelled in price ticks when `ticks` is set; acceptance
342/// resolves a tick spelling exactly like TrailTicks, in place, once. The
343/// materialized level is then snapped per `rounding`, which is appended last
344/// so every existing aggregate initializer keeps its meaning.
345struct Absolute {};
346/// Defer a level to the owner's fill: at the arm the level becomes fill + offset,
347/// with `offset` signed (adverse is negative) and spelled in price ticks when
348/// `ticks` is set, then rounded by `rounding`. Legal on Limit and Stop prices and
349/// on a Trail arm threshold, and only under a WaitForApplied owner, which is the
350/// one relation that arms. Until then the anchored field is left unwritten (0.0
351/// for a Limit or Stop price, an absent or 0.0 Trail::arm_price), and the ArmedEvent
352/// carries the materialized definition: from then on the request reads as that level.
358using TriggerAnchor = std::variant<Absolute, FromOwnerFill>;
359
360/// Replacement behaviour that is not expressible in the successor request.
361/// retain_trigger_state carries the predecessor's live trigger state (a
362/// tracking trail's best, an already active stop) into the successor instead
363/// of restarting it; predecessor and successor must hold the same trigger
364/// alternative.
365///
366/// keep_handle re-prices the request in place: the new definition keeps the
367/// target's handle, and with it the request's lineage (its predecessor link
368/// and chain root) and its dependents -- a child waiting on it keeps waiting,
369/// a roster that names it keeps it -- where a plain replace retires the handle
370/// and issues a successor. Everything else is the replace: the same checks and
371/// refusals, a new immutable definition and birth, a fresh live row (remaining,
372/// allowance and trigger state restart unless retain_trigger_state keeps the
373/// last), and one ReplacedEvent, whose two definitions share the handle. The
374/// re-price takes the number of the run's request sequence a successor's
375/// incarnation would have taken, as the definition's priority
376/// (RequestDefinition::priority): matching ranks it the newest request, where a
377/// successor stands, and the requests after it are numbered as if the replace
378/// had issued a handle. No handle is issued under that number.
379///
380/// keep_binding carries a close's book binding across the replace. A successor
381/// that starts unbound (an Independent close) and whose predecessor was bound
382/// to the book, or carried such a binding itself, records the binding's cycle
383/// and side (RequestDefinition::kept_binding). Where the successor would bind,
384/// at its first evaluation, a book that still has that cycle and side binds it
385/// with no CloseBoundEvent; any other book binds it, or ends it, exactly as it
386/// does a successor that carried nothing. So the option changes the events a
387/// run records, never what it matches. Anywhere else it carries nothing.
388/// Either option may be given alone.
391 bool keep_handle = false;
392 bool keep_binding = false;
393};
394
395/// Take whatever the point allows, with no per-point cap. The default capacity,
396/// and the only one the Pine adapter emits.
398/// Cap what this request may execute at ONE matching point at `units`. Fixed at
399/// submit and metered per point — including after an arm — so a request cannot be
400/// drained faster than its owner's schedule allows.
402 double units = 0.0;
403};
404using Capacity = std::variant<ImmediateRemaining, PointBudget>;
405
406/// Whether an owner-related request that arms is a working order before its
407/// arm. Working (the default) is the established book: the request is listed
408/// by native_working_requests() from acceptance on. PendingUntilArmed keeps
409/// it out of that enumeration until its ArmedEvent; it is still a live
410/// request the whole time -- accepted, addressable by handle (replace,
411/// cancel, trail_state), counted by cancel_all / cancel_where, and folded
412/// into the continuation identity -- and it never matches before the arm
413/// under either value. Visibility governs enumeration, not addressing.
414enum class NativeArmVisibility : std::uint8_t {
417};
418
419/// When an armed request may first match. The arm happens inside its owner's
420/// fill settlement, at the owner's fill print. AtArmPrint (the default) is the
421/// established book: the armed request is a candidate at that very cursor, so
422/// a level the fill print already satisfies matches at the print. AfterArmPrint
423/// gives the armed request the birth rule of a request submitted from the
424/// owner's fill callback: on the driver point that armed it, it sees only the
425/// path after the print -- a level the print already satisfies does not match
426/// there, a later crossing on that point still does -- and from the next
427/// driver point on it is an ordinary working request. It governs the level
428/// test of a priced trigger (limit, stop, stop-limit, trail arm); a market
429/// trigger has no level and is unaffected. Both are broker models: a
430/// contingent child that enters the book after its parent's trade cannot
431/// trade on that print, a simulated bracket commonly may.
432enum class NativeArmFirstMatch : std::uint8_t {
435};
436
437/// What an armed CLOSING request (Reduce, Flatten, a HostSized close) closes.
438/// OwnerLot (the default) is the established relation: the lot its owner's
439/// fill opened, and nothing a later add brings. Book binds it, at the arm, to
440/// the whole position that fill left -- the very book authority an
441/// Independent close submitted from the owner's fill callback acquires -- so
442/// a protective leg placed with its entry covers later adds and settles
443/// through the book like any other close. A HostSized close may wait for its
444/// owner only under Book: the owner-lot relation sizes from the units the
445/// owner opened, whereas a book close is sized by the host at the match.
446/// A waiting transaction closes nothing, so it must keep OwnerLot.
447enum class NativeArmScope : std::uint8_t {
449 Book = 1,
450};
451
452/// No owner: the request stands on its own book authority. Required for Sized, and
453/// the default.
454struct Independent {};
455/// The one owner relation that arms (the ArmedEvent). `visibility`, then
456/// `first_match`, then `scope` are appended last so every existing {parent},
457/// {parent, visibility} initializer keeps its meaning.
464/// Bind to ONE already-live opening, by its handle and the positive cycle it is
465/// live in. A reduce bound this way closes that lot and nothing a later add
466/// brings.
469 int64_t cycle = 0;
470};
471/// Immutable membership, independent of a source scratch scope. A successful
472/// read changes out; an error leaves it unchanged. No read allocates.
474public:
475 virtual ~OpeningReadback() noexcept = default;
476 virtual std::uint64_t count() const noexcept = 0;
477 virtual detail::ScratchStatus opening_at(
478 std::uint64_t index, RequestHandle& out) const noexcept = 0;
479};
480
481/// Call-local input. Enrollment freezes it before retaining a definition;
482/// neither this object nor its callback/context is retained.
484public:
485 using Reader = detail::ScratchStatus (*)(void*, std::uint64_t, RequestHandle&);
486 OpeningInputCursor(std::uint64_t count, void* context, Reader reader) noexcept
487 : count_(count), context_(context), reader_(reader) {}
488 std::uint64_t count() const noexcept override { return count_; }
490 std::uint64_t index, RequestHandle& out) const noexcept override;
491private:
492 std::uint64_t count_;
493 void* context_;
494 Reader reader_;
495};
496
497/// Freeze and canonicalize a same-run, unique stream. Uses bounded workspace
498/// and an exclusive unlinked file; failure leaves out unchanged (an allocation
499/// failure throws typed out_of_memory). Only creation of the fixed-size owner allocates.
501 const RunIdentity&, const OpeningReadback&,
502 std::shared_ptr<const OpeningReadback>& out);
503
504std::uint64_t opening_count(const std::vector<RequestHandle>&,
505 const OpeningReadback*) noexcept;
506detail::ScratchStatus opening_at(const std::vector<RequestHandle>&,
507 const OpeningReadback*, std::uint64_t, RequestHandle&) noexcept;
508/// Bind to a fixed cohort of already-live openings. Every handle must be unique,
509/// nonzero, of this run and physically live in `cycle`; enrollment rejects the
510/// ENTIRE request if any member is invalid. The accepted definition keeps the
511/// original cohort in canonical handle order even as later retirement narrows the
512/// live subset, and execution still closes lots in physical FIFO order.
514 std::vector<RequestHandle> openings;
515 int64_t cycle = 0;
516 std::shared_ptr<const OpeningReadback> external_openings;
517};
518/// Bind to a host-built roster (cohort_open / cohort_add / cohort_remove), read at
519/// the match rather than frozen at submit. The C surface pairs it with exactly one
520/// intent, a host-sized close, whose units the on_close_units hook answers.
524using Owner = std::variant<Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort>;
525
526enum class GroupEffect : std::uint8_t { Cancel = 0, Reduce = 1 };
527struct NoGroup {};
528struct Member {
529 std::uint64_t group = 0;
530 std::int64_t cohort = 0;
532};
533using Group = std::variant<NoGroup, Member>;
534
535/// Aggregate field order keeps market construction:
536/// Request{Transact{1.0}, "buy", "comment"}
537/// Request{Flatten{}, "flat", ""}
538/// Request{Reduce{ExplicitUnits{3}}, "close", ""}
539/// New fields default to Market / ImmediateRemaining / Independent / NoGroup.
540/// market_request() converts a physical execution::Action (explicit Reduce
541/// units only) into that same market Request. Host market-only methods must
542/// reject nondefault extras; they must not drop them.
553
554/// Complete request schema, including owner text and every opening handle.
555/// intent/trigger/capacity/owner/group/anchor have an unsigned variant `tag`;
556/// label/comment are top-level byte fields. See the production freeze codec.
558public:
559 RequestReadback() noexcept = default;
560 RequestReadback(const detail::CommandScratchStore& store, detail::ScratchHandle version) noexcept
561 : PayloadReadback(store, version, detail::PayloadSchema::request) {}
562 explicit RequestReadback(detail::PayloadReadback view) noexcept : PayloadReadback(view) {}
564 return find("label", out);
565 }
567 return find("comment", out);
568 }
569};
570
572 if (std::holds_alternative<execution::Flatten>(action)) return Flatten{};
573 if (const auto* transact = std::get_if<order_action::Transact>(&action)) {
574 return Transact{transact->signed_units};
575 }
576 const auto& reduce = std::get<order_action::Reduce>(action);
577 return Reduce{ExplicitUnits{reduce.units}};
578}
579
581 std::string label = {},
582 std::string comment = {}) {
583 return Request{intent_from_physical(action), std::move(label), std::move(comment)};
584}
585
586inline bool has_market_defaults(const Request& request) noexcept {
587 return std::holds_alternative<Market>(request.trigger)
588 && std::holds_alternative<ImmediateRemaining>(request.capacity)
589 && std::holds_alternative<Independent>(request.owner)
590 && std::holds_alternative<NoGroup>(request.group)
591 && std::holds_alternative<Absolute>(request.anchor);
592}
593
594struct EventId {
596 uint64_t ordinal = 0;
597};
598
599inline bool operator==(const EventId& a, const EventId& b) {
600 return a.ordinal == b.ordinal && a.run == b.run;
601}
602inline bool operator!=(const EventId& a, const EventId& b) { return !(a == b); }
603
606 double t = 0.0;
607};
608
609inline bool operator==(const MatchCursor& a, const MatchCursor& b) noexcept {
610 return a.point.ordinal == b.point.ordinal && a.t == b.t
611 && a.point.interval_index == b.point.interval_index
612 && a.point.effective_time_ms == b.point.effective_time_ms
613 && a.point.provenance == b.point.provenance
614 && a.point.path_phase == b.point.path_phase;
615}
616inline bool operator!=(const MatchCursor& a, const MatchCursor& b) noexcept { return !(a == b); }
617
618inline std::uint8_t display_waypoint(const MatchCursor& cursor) noexcept {
619 return static_cast<std::uint8_t>(cursor.point.path_phase);
620}
621
624/// A resolved remaining quantity, in units. The kernel publishes a Sized intent's
625/// own resolution here, BEFORE the host's terms hook runs, so an override sees the
626/// kernel's number and may answer with a different one.
628 double q = 0.0;
629};
631/// A dynamic cohort has no currently live member. This is a live deferral,
632/// not a terminal receipt: the consumer retries it at the next candidate.
633struct NoTarget {};
636
640 double q = 0.0;
641};
648
650/// The authority an unarmed WaitForApplied child holds: accepted and live, but
651/// never a matching candidate until its owner's fill arms it — under either
652/// visibility.
662/// The authority an armed leg acquires under NativeArmScope::Book: the whole
663/// position its owner's fill left, on this cycle and side, bound at the fill's
664/// cursor. It is the same authority an Independent close has, which is what lets a
665/// protective leg placed with its entry also cover later adds.
679using Enrollment = std::variant<EnrollmentFromCommand, EnrollmentFromApplied>;
686/// Immutable fixed cohort. Current physical liveness is observed, never stored
687/// here; later fragments of an enrolled provenance remain authorized.
689 std::vector<RequestHandle> openings;
690 int64_t cycle = 0;
693 std::shared_ptr<const OpeningReadback> external_openings;
694};
700
701/// Native authorization receipt, converted to a call-local financial
702/// SelectedOpeningSet only at the consumer's settlement boundary.
704 int64_t cycle = 0;
705 std::vector<uint64_t> incarnations;
706};
707/// What one execution acted on: the whole book, one opening's exposure, or a bound
708/// selection. Reported on ExecutionAppliedEvent::scope. The financial
709/// execution::CloseScope remains the original two-alternative type; this is the
710/// native view, and a selected close's committed row range identifies the actual
711/// contributors.
712using ExecutionScope = std::variant<execution::Book, execution::OpeningExposure, SelectedExposure>;
713
714struct MarketReady {};
715struct LimitReady {};
716struct StopIdle {};
717struct StopActive {};
720struct TrailWaitArm {};
721/// A trail riding its running best. `activation_ordinal` is the ordinal of
722/// this request's own TrailArm ActivatedEvent -- NativeTrailState's
723/// activation_ordinal, kept in state so it outlives the journal window -- and
724/// 0 for a successor that retained its predecessor's tracking
725/// (ReplaceOptions::retain_trigger_state), which never armed itself. A
726/// re-price that kept the handle (ReplaceOptions::keep_handle) keeps it: that
727/// same request armed.
729 double best = 0.0;
730 uint64_t activation_ordinal = 0;
731};
732/// A triggered trail; `activation_ordinal` carries TrailTrack's.
734 double best_at_trigger = 0.0;
735 uint64_t activation_ordinal = 0;
736};
739
742 uint64_t point_ordinal = 0;
743 double initial = 0.0;
744 double left = 0.0;
745};
747 uint64_t point_ordinal = 0;
748};
750 uint64_t point_ordinal = 0;
751};
754
755struct PendingNone {};
757 double total = 0.0;
758 uint64_t count = 0;
760};
761using PendingAdjustments = std::variant<PendingNone, PendingDeferred>;
762
763struct PositionFlat {};
765 int64_t cycle = 0;
767};
768using PositionIdentity = std::variant<PositionFlat, PositionNonflat>;
769
776
777/// Borrowed through the enclosing command/evaluation and any prepare/install
778/// pair that copies its TargetObservation, including nested reads.
780public:
781 virtual ~OpeningObservationReadback() noexcept = default;
782 virtual std::uint64_t count() const noexcept = 0;
783 virtual detail::ScratchStatus observation_at(
784 std::uint64_t index, OpeningObservation& out) const noexcept = 0;
785};
786
789 std::optional<OpeningObservation> opening;
790 std::vector<OpeningObservation> openings;
792};
793
794std::uint64_t opening_observation_count(const TargetObservation&) noexcept;
796 const TargetObservation&, std::uint64_t, OpeningObservation&) noexcept;
797
798/// Who authored a request. Host is every request a host submits, replaces or
799/// cancels, and it is the whole existing population: it folds nothing into the
800/// continuation digest, so no established hash moves. KernelLiquidation marks
801/// the margin model's own Reduce or Flatten; KernelRisk marks the Flatten a
802/// NativeRiskAction::FlattenAndBlock breach issues at its breach point
803/// (native_execution_consumer.cpp, pinned by tests/test_native_risk_limits.cpp).
804enum class RequestOrigin : std::uint8_t {
805 Host = 0,
808};
809
810/// The immutable accepted form of one request: its handle, the request value as
811/// accepted, its birth (acceptance ordinal and decision floor), its origin
812/// (RequestOrigin::Host, or the kernel's own for a liquidation or a risk flatten)
813/// and its authority. Every event carries a shared pointer to it, so a reader
814/// holds the definition the kernel matched against and not a later re-read.
819 std::optional<RequestHandle> predecessor;
820 /// Appended last so every existing aggregate initializer keeps its meaning.
822 /// The first handle of this request's replace chain -- the request the
823 /// chain began with, which is how a cohort roster names every request of
824 /// the chain (cohort_add, cohort_contains). Empty for a request that
825 /// replaced nothing: its chain begins with itself. Set by the core at the
826 /// replace that creates the definition, from the predecessor's own root,
827 /// so it never needs the predecessor's journal events. Appended after
828 /// `origin` for the same reason `origin` was.
829 std::optional<RequestHandle> root;
830 /// Where the definition stands in the run's queue: the number of the
831 /// run's request sequence the command that placed it took. Matching
832 /// breaks a tie at one cursor oldest number first, and the working book
833 /// is held in this order (WorkingRequestCore::live). 0, as every accepted
834 /// request and replace successor has it, means the handle's incarnation,
835 /// the number its command took; a re-price that kept the handle
836 /// (ReplaceOptions::keep_handle) sets the number it took instead. Read it
837 /// through LiveRequest::priority. Appended after `root`, like it.
838 uint64_t priority = 0;
839 /// The book binding a replace carried forward (ReplaceOptions::
840 /// keep_binding): the cycle and side of the book its predecessor was bound
841 /// to. The request starts unbound, and at its first evaluation a live book
842 /// with this cycle and side binds it with no CloseBoundEvent. Empty for
843 /// every other definition.
844 std::optional<PositionNonflat> kept_binding;
845};
846using DefinitionRef = std::shared_ptr<const RequestDefinition>;
847
855 /// Placement-time sizing measurements frozen from the accepting command
856 /// context. Both stay empty for every request that did not ask to freeze
857 /// one, and an empty optional folds nothing into the continuation digest.
858 std::optional<double> sizing_units; // SizeTime::AtAcceptance
859 std::optional<double> sizing_scope; // ScopeBasis::AtAcceptance
860 std::optional<double> sizing_price; // SizePrice::Signal / SignalOnTick
861
862 const RequestHandle& handle() const noexcept { return definition->handle; }
863 const Request& request() const noexcept { return definition->request; }
864 const Birth& birth() const noexcept { return definition->birth; }
865 const std::optional<RequestHandle>& predecessor() const noexcept {
866 return definition->predecessor;
867 }
868 /// Its place in the queue (RequestDefinition::priority): the handle's
869 /// incarnation, unless a re-price kept the handle and took a later number.
870 uint64_t priority() const noexcept {
871 return definition->priority != 0 ? definition->priority : definition->handle.incarnation;
872 }
873};
874
875/// Whether a request may be considered at this driver point: the point's ordinal
876/// must be strictly past the birth acceptance ordinal AND its effective time at or
877/// past the birth's decision-time lower bound. This is the birth gate — it is why
878/// a request accepted on bar N cannot fill on that bar's already delivered
879/// opening — and a request born mid-path in on_native_applied is admitted at the
880/// current cursor instead, on the unconsumed suffix of that segment.
881inline bool point_eligible(const LiveRequest& live,
882 uint64_t point_ordinal,
883 int64_t effective_time_ms) noexcept {
884 return point_eligible(live.birth(), point_ordinal, effective_time_ms);
885}
886
887/// v2 exact binary64 grid: r=abs(q)/s, n=round(r) half away from zero, g=n*s.
888/// Intermediates finite, 1<=n<=2^53, abs(abs(q)-g) <= 4*ulp(max(abs(q),abs(g)))
889/// and < s/2. ulp is nextafter toward +inf. Does not rewrite q.
890inline bool quantity_on_grid(double q, double step) noexcept {
891 if (!std::isfinite(q) || !std::isfinite(step) || step <= 0.0) return false;
892 const double abs_q = std::abs(q);
893 const double r = abs_q / step;
894 if (!std::isfinite(r)) return false;
895 const double n = std::round(r);
896 if (!std::isfinite(n) || n < 1.0 || n > 0x1p53) return false;
897 const double g = n * step;
898 if (!std::isfinite(g)) return false;
899 const double err = std::abs(abs_q - g);
900 const double span = std::max(abs_q, std::abs(g));
901 const double ulp = std::nextafter(span, std::numeric_limits<double>::infinity()) - span;
902 const double ulp_bound = 4.0 * ulp;
903 const double half_step = step / 2.0;
904 if (!std::isfinite(err) || !std::isfinite(ulp_bound) || !std::isfinite(half_step))
905 return false;
906 return err <= ulp_bound && err < half_step;
907}
908
909/// Why an acceptance was refused, on a Rejected SubmitResult. These are decided at
910/// submit, before any request record exists: an invalid quantity or basis, an
911/// off-grid quantity, an owner the intent may not take, a trigger spelling the run
912/// cannot resolve, or a frozen quantity the run's own opening admission refuses
913/// (PlacementAdmission).
914enum class RequestRejectReason : std::uint8_t {
922 /// A Sized{SizeTime::AtAcceptance} whose acceptance-resolved quantity does
923 /// not pass the run's opening admission (allowed directions, max_abs_units,
924 /// max_open_lots, initial margin) at the sizing price.
926};
927
931
932/// What submit answers. Accepted carries a timeline ordinal and a RequestHandle
933/// (session, run, incarnation); Rejected carries a rejection ordinal, no handle
934/// and a reason. Acceptance is not a fill: no lot, no cash and no fee moves here,
935/// and the ordinary fill appears later as an ExecutionAppliedEvent on the same
936/// command history.
939 uint64_t event_ordinal = 0;
940 std::optional<RequestHandle> handle;
941 std::optional<RequestRejectReason> reason;
942};
943
946 uint64_t event_ordinal = 0;
947 std::optional<RequestHandle> successor;
948 std::optional<RequestRejectReason> reason;
949};
950
955
956/// Why a CANDIDATE was refused at a matching point. Every refusal is terminal:
957/// the MatchRejectedEvent ends the request, and a host that still wants it
958/// submits again. OpeningDirection, MaxAbsUnits, MaxOpenLots, InitialMargin and
959/// RiskLimit are the run's admission gates; TermsUnresolved is a host-sized or
960/// basis-sized quantity that could not be resolved; NoOppositeExposure and
961/// InvalidTerms are shapes the answered terms cannot take;
962/// UnrepresentableQuantity is a quantity the settlement cannot book exactly on
963/// the book it met.
964enum class MatchRejectReason : std::uint8_t {
974 /// An opening refused while the run spec's generic risk limits are
975 /// blocking (L9). Reduces never reach this gate.
977 /// The settlement's inspection answered execution::Status::
978 /// UnrepresentableQuantity: a close split binary64 cannot hold (a rest
979 /// below half an ulp of the lot it ends in, a whole lot that cannot
980 /// decrement the rest or move the running sum), a reduction or transaction
981 /// the position absorbs, or an opening the surviving book absorbs; or the
982 /// request core cannot take the fill off the request's own units
983 /// (CoreFailure::NonrepresentableQuantity: a scope of dust closed by a far
984 /// larger request, a point budget too small to move its remaining units);
985 /// or what an off-grid Reduce the grid admitted as a FIFO boundary of its
986 /// scope settles no longer closes whole lots of the book it meets
987 /// (CommandContext::units_are_scope_boundary). The request ends here,
988 /// nothing moves, and the run goes on; the settlement's and the core's
989 /// cases stopped the whole run before the representable-quantity refusal change.
991};
992
993/// Which price a candidate is being offered at: the point's own price, or the
994/// request's trigger level (a crossing that books the level itself). A host reads
995/// it off the terms facts, and the trail suites read it back off the applied event
996/// to tell a level fill from a path fill.
997enum class NativeCandidatePriceKind : std::uint8_t {
1001};
1002
1003enum class OpeningShape : std::uint8_t {
1007};
1008
1015
1016/// The settlement shape one execution takes. Derived by the kernel from the intent
1017/// and the answered terms; a host never constructs it.
1020
1022 return std::visit([](const auto& alternative) -> ExecutionPlan {
1023 return alternative;
1024 }, action);
1025}
1026
1034
1035/// Why a request left the book. Host is an explicit cancel; Group an OCA sibling's
1036/// effect; OwnerGone a parent that was rejected, replaced or cancelled, which ends
1037/// its waiting children; Superseded the kernel withdrawing its own liquidation
1038/// before re-pricing it. Every reason is carried on a CancelledEvent.
1039enum class CancelReason : std::uint8_t {
1040 User = 0,
1044 /// A kernel-originated request the kernel itself withdrew: the liquidation
1045 /// level or its units moved, or the requirement is no longer breached.
1047};
1048
1054
1055enum class ActivationKind : std::uint8_t {
1056 Stop = 0,
1060};
1061
1062enum class CoreFailure : std::uint8_t {
1070 /// A group deduction's pending total that overflows binary64 (or a
1071 /// non-finite reservation, which a valid run does not reach). A deduction
1072 /// binary64 cannot take off its recipient is absorbed instead (the
1073 /// absorbed-deduction rule); it failed the run with this discriminator before.
1078};
1079
1089
1090enum class CommandSurface : std::uint8_t { General = 0, MarketOnly = 1 };
1091
1092/// One request was accepted: the definition, and the timeline ordinal it was
1093/// accepted at. The first event of every request's history.
1095 uint64_t ordinal = 0;
1098 const RequestHandle& handle() const noexcept { return definition->handle; }
1099 const Request& request() const noexcept { return definition->request; }
1100 const Birth& birth() const noexcept { return definition->birth; }
1101};
1102
1109
1111 uint64_t ordinal = 0;
1115 const RequestHandle& predecessor() const noexcept { return predecessor_definition->handle; }
1116 const Request& predecessor_request() const noexcept { return predecessor_definition->request; }
1117 const RequestHandle& successor() const noexcept { return successor_definition->handle; }
1118 const Request& successor_request() const noexcept { return successor_definition->request; }
1119 const Birth& successor_birth() const noexcept { return successor_definition->birth; }
1120 /// Whether the replace kept the handle (ReplaceOptions::keep_handle): the
1121 /// successor definition is the same request re-priced, and only such a
1122 /// definition carries a priority of its own.
1123 bool kept_handle() const noexcept { return successor_definition->priority != 0; }
1124};
1125
1135
1147
1154
1161
1162/// A reduction or a flatten that had nothing to close: terminal, with no execution
1163/// identity, no fill, no fee and no physical action. It is an outcome, not a
1164/// failure.
1166 uint64_t ordinal = 0;
1171 const RequestHandle& handle() const noexcept { return definition->handle; }
1172 const Request& request() const noexcept { return definition->request; }
1173 const Birth& birth() const noexcept { return definition->birth; }
1174};
1175
1176/// One candidate's execution terms, recorded. For a request whose units were
1177/// deferred it also binds them: it spends the pending group chain
1178/// (`prior_adjustment_ids`, `pending_total`), takes `effective_deduction` off the
1179/// resolved units and leaves `remaining_after`. That deduction is 0 with a
1180/// positive `pending_total` and positive resolved units when binary64 cannot
1181/// take the total off them -- absorbed, and the units stand (the absorbed-deduction rule).
1197
1198/// A candidate the run refused, with its MatchRejectReason and the cursor it was
1199/// refused at. It carries an event ordinal but no execution identity, and it is
1200/// the request's last event: every match refusal erases the live request.
1213
1223
1233
1234/// One committed execution: the definition, the cursor, the resolved price and
1235/// units, the scope it acted on, the committed closed-row range, the cycle on
1236/// either side, and the ticket it booked under. Delivered to on_native_applied and
1237/// recorded in native_events(); the account observation that follows it shares its
1238/// ordinal.
1240 uint64_t ordinal = 0;
1242 double raw_price = 0.0;
1243 double resolved_price = 0.0;
1244 double current_ticket = 0.0;
1245 std::size_t first_trade_index = 0;
1246 std::size_t closed_trade_count = 0;
1248 double closed_units = 0.0;
1249 double opened_units = 0.0;
1250 double filled_working = 0.0;
1255 bool terminal = true;
1256 std::optional<AppliedTerminalReason> terminal_reason;
1257 int64_t cycle_before = 0;
1258 int64_t cycle_after = 0;
1261 const RequestHandle& handle() const noexcept { return definition->handle; }
1262 const Request& request() const noexcept { return definition->request; }
1263 const Birth& birth() const noexcept { return definition->birth; }
1264 int64_t effective_time_ms() const noexcept { return cursor.point.effective_time_ms; }
1265 int64_t interval_open_ms() const noexcept { return cursor.point.open_ms; }
1266 int64_t interval_last_traded_close_ms() const noexcept {
1267 return cursor.point.last_traded_close_ms;
1268 }
1269 int interval_index() const noexcept { return cursor.point.interval_index; }
1270 std::uint8_t provenance() const noexcept {
1271 return static_cast<std::uint8_t>(cursor.point.provenance);
1272 }
1273};
1274
1275/// A group effect's deduction from a live sibling whose remaining units are
1276/// known (GroupEffect::Reduce): `requested_delta` is the member fill's
1277/// `filled_working`, `actual_deduction` the part taken -- capped at `before`,
1278/// which then cancels the sibling -- and `after` is fl(before -
1279/// actual_deduction). A deduction binary64 cannot take off `before` (at most
1280/// half an ulp of it: fl(before - d) is `before`) is absorbed (the absorbed-deduction rule):
1281/// `actual_deduction` is 0 and `after` equals `before`, the exact binary64
1282/// result, and the member's fill stands. A deduction that is taken is positive,
1283/// so an `actual_deduction` of 0 names an absorbed one.
1295
1296/// A group effect's deduction deferred into a sibling whose units are not known
1297/// yet: its pending total grows by `deferred_delta`, in commit order, and the
1298/// receipts chain back through `previous_pending_receipt` until the terms or
1299/// the owner fill that binds the sibling's units takes the total off them. A
1300/// fill the total cannot move (fl(total + fill) is the total) is absorbed (R5
1301/// lane K-ULP5): `deferred_delta` is 0, `pending_after` is `pending_before` and
1302/// the event joins no chain. A total below half an ulp of a later fill rounds
1303/// into it, as binary64 addition does.
1315
1316/// A Reduce{OwnerOpenedUnits} was bound, at its owner's fill, to what that fill
1317/// opened. It is how a bracket child's size becomes a number, and it precedes the
1318/// ArmedEvent of the same drain. `effective_deduction` is the part of the child's
1319/// pending group total taken off `source_units`; it is 0 with a positive
1320/// `pending_total` when binary64 cannot take that total off them -- absorbed, and
1321/// the recipient's units stand (the absorbed-deduction rule).
1332
1333/// An anchored leg was materialized, exactly once: fill + offset, then the
1334/// rounding, then resolve_anchored_level, then the representability check, and the
1335/// resulting level written into the leg's trigger with its anchor becoming
1336/// Absolute. Every later reader — the live book, the C working rows, matching —
1337/// sees that level. Under NativeArmScope::Book it also carries the BookClose the
1338/// leg acquired.
1347
1348/// A kernel-issued liquidation that actually filled. It carries the margin
1349/// facts of that fill, so a host reconstructs the outcome without recomputing
1350/// the account: `mark` is the booked resolved price, `equity` and `required`
1351/// are the marked equity and the maintenance requirement of the SURVIVING book
1352/// at that price, `liquidation_price` is the level re-solved for what is left,
1353/// and `position_before` / `position_after` are the signed book on either side
1354/// of the reduction. `applied` names the ExecutionAppliedEvent that booked it.
1356 uint64_t ordinal = 0;
1361 double mark = 0.0;
1362 double equity = 0.0;
1363 double required = 0.0;
1364 double liquidation_price = 0.0;
1365 double units = 0.0;
1366 double position_before = 0.0;
1367 double position_after = 0.0;
1368 const RequestHandle& handle() const noexcept { return definition->handle; }
1369 const Request& request() const noexcept { return definition->request; }
1370};
1371
1372/// Which generic risk limit of NativeRunSpec::risk a NativeRiskEvent reports.
1379
1380/// One generic risk limit breaching (L9). It is not bound to a request: the
1381/// block it opens is an account fact, so this event carries no definition.
1382/// `limit` is the threshold in the unit the breach was measured in — account
1383/// currency for the two loss limits (a percent limit is already resolved
1384/// against its basis equity here), days or fills for the two counts — and
1385/// `observed` is the measured value that reached it. `day_ordinal` is the
1386/// risk day the breach happened on, on the spec's own day basis, and `cursor`
1387/// is the point it was measured at.
1396
1397/// Every alternative of one request's history, as one value. native_events()
1398/// returns them as the NativeEventKind::Command rows, and
1399/// strategy_native_events_v1 flattens the same set into one tagged POD. The
1400/// variant's size is pinned by the frozen C++ ABI fixtures.
1401using CommandEvent = std::variant<AcceptedEvent,
1416 ArmedEvent,
1420
1422public:
1423 explicit JournalRecordSource(const CommandEvent&) noexcept;
1424 std::uint64_t size() const noexcept override { return length_; }
1425 detail::ScratchStatus read(std::uint64_t, detail::ScratchBuffer) const noexcept override;
1426private:
1427 const CommandEvent* event_;
1428 std::uint64_t length_ = 0;
1430};
1432// Owning decode is an explicit diagnostic surface; an allocation failure throws typed out_of_memory.
1435class WorkingRequestCore;
1438 const detail::ScratchByteSource&) noexcept;
1440
1441/// Almost every prepared command yields one history event. Keep that ordinary
1442/// transactional payload inline; the overflow vector preserves the existing
1443/// arbitrary-length behavior for group/lifecycle plans that emit more events.
1445public:
1447 InlineCommandEvents(InlineCommandEvents&& other) noexcept : size_(other.size_) {
1448 for (std::size_t i = 0; i < size_ && i < inline_.size(); ++i) {
1449 inline_[i] = std::move(other.inline_[i]);
1450 }
1451 overflow_ = std::move(other.overflow_);
1452 other.size_ = 0;
1453 }
1455 if (this != &other) {
1456 clear();
1457 size_ = other.size_;
1458 for (std::size_t i = 0; i < size_ && i < inline_.size(); ++i) {
1459 inline_[i] = std::move(other.inline_[i]);
1460 }
1461 overflow_ = std::move(other.overflow_);
1462 other.size_ = 0;
1463 }
1464 return *this;
1465 }
1466 InlineCommandEvents(const InlineCommandEvents& other) : size_(other.size_) {
1467 for (std::size_t i = 0; i < size_ && i < inline_.size(); ++i) {
1468 inline_[i] = other.inline_[i];
1469 }
1470 overflow_ = other.overflow_;
1471 }
1473 if (this != &other) {
1474 clear();
1475 size_ = other.size_;
1476 for (std::size_t i = 0; i < size_ && i < inline_.size(); ++i) {
1477 inline_[i] = other.inline_[i];
1478 }
1479 overflow_ = other.overflow_;
1480 }
1481 return *this;
1482 }
1483
1484 std::size_t size() const noexcept { return size_; }
1485 bool empty() const noexcept { return size_ == 0; }
1486
1487 template<class Event>
1488 void emplace_back(Event&& event) {
1489 push_back(CommandEvent(std::forward<Event>(event)));
1490 }
1491
1493 if (overflow_.empty() && size_ < inline_.size()) {
1494 inline_[size_++].emplace(std::move(event));
1495 return;
1496 }
1497 if (overflow_.empty()) {
1498 overflow_.reserve(inline_.size() * 2U);
1499 for (std::size_t i = 0; i < size_; ++i) {
1500 overflow_.push_back(std::move(*inline_[i]));
1501 inline_[i].reset();
1502 }
1503 }
1504 overflow_.push_back(std::move(event));
1505 ++size_;
1506 }
1507
1508 void clear() noexcept {
1509 for (std::size_t i = 0; i < size_ && i < inline_.size(); ++i) {
1510 inline_[i].reset();
1511 }
1512 overflow_.clear();
1513 size_ = 0;
1514 }
1515
1516 CommandEvent& front() noexcept { return (*this)[0]; }
1517 const CommandEvent& front() const noexcept { return (*this)[0]; }
1518 CommandEvent& operator[](std::size_t index) noexcept {
1519 return overflow_.empty() ? *inline_[index] : overflow_[index];
1520 }
1521 const CommandEvent& operator[](std::size_t index) const noexcept {
1522 return overflow_.empty() ? *inline_[index] : overflow_[index];
1523 }
1524
1525private:
1526 std::array<std::optional<CommandEvent>, 2> inline_{};
1527 std::vector<CommandEvent> overflow_{};
1528 std::size_t size_ = 0;
1529};
1530
1531/// What acceptance needs from the run to judge a command: the point's ordinal and
1532/// coordinate, the price tick the tick spellings resolve against, the quantity
1533/// grid, and the live book. Built by the consumer; a host never constructs one.
1535 int64_t decision_time_ms = 0;
1536 std::optional<double> quantity_grid;
1537 std::optional<OpeningObservation> opening;
1539 std::vector<OpeningObservation> openings;
1540 /// Kernel-resolved acceptance-time units for a Sized{AtAcceptance} request.
1541 /// The execution consumer owns the account facts, so it supplies them here;
1542 /// a producer that leaves it unset accepts the request with a size the
1543 /// matching path then reports as TermsUnresolved. The accepted request
1544 /// carries the value in LiveRequest::sizing_units and still reaches the
1545 /// candidate with a deferred remaining, so the host keeps its one override
1546 /// pass. Appended last so the existing positional aggregate initializers
1547 /// keep their meaning.
1548 std::optional<double> sizing_units;
1549 /// The run's price tick, needed only to resolve a tick-spelled trail
1550 /// offset or trigger anchor. A tick spelling without a usable tick here is
1551 /// rejected rather than silently read as a price distance.
1552 std::optional<double> price_tick;
1553 /// Placement-time sizing measurements the execution consumer owns, supplied
1554 /// only for the intent that asks for them. Appended last, exactly like
1555 /// sizing_units, so the existing positional aggregate initializers keep
1556 /// their meaning.
1557 ///
1558 /// sizing_scope: the bound scope's exposure at acceptance, for a
1559 /// Reduce{ScopeFraction{ScopeBasis::AtAcceptance}}.
1560 /// sizing_price: the frozen signal price, for a Sized whose SizePrice is
1561 /// Signal or SignalOnTick.
1562 /// sizing_admissible: false when the acceptance-resolved quantity of a
1563 /// Sized{SizeTime::AtAcceptance} fails the run's placement admission.
1564 std::optional<double> sizing_scope;
1565 std::optional<double> sizing_price;
1567 /// A Reduce's ExplicitUnits are a FIFO boundary of its scope: the binary64
1568 /// sum, in book order, of the scope's lots through one of them -- the head
1569 /// lot's own size, a prefix, the whole scope. Such a quantity is on the
1570 /// quantity grid whatever the grid predicate answers: these are the book's
1571 /// own quantities, which settlement arithmetic can move off any decimal
1572 /// grid; closing them takes whole lots and splits none (the representable-quantity refusal change).
1573 /// The execution consumer measures it at submit, for a request of
1574 /// ImmediateRemaining capacity; its candidate settles what then remains
1575 /// only on the grid, at a boundary of the scope as it stands or at least at
1576 /// its held total, and refuses anything else (MatchRejectReason::
1577 /// UnrepresentableQuantity). Appended last, like the members above.
1580};
1581
1586 /// Generic current-point delivery. At Open, the consumer sets this only for
1587 /// a market/immediate request born by the pre-open provider. On a continuous
1588 /// OHLC segment, it also admits a request born by an applied callback onto
1589 /// the unconsumed suffix. It is transient and never retained in a request.
1591 /// Set only while resolving a CohortClose candidate. It carries the
1592 /// physical side of the currently live selected roster and is not retained
1593 /// in a request definition.
1594 std::optional<Side> cohort_side;
1595};
1596
1597/// The one policy point of an anchored materialization. The core knows it
1598/// only as a callable over its own values: the leg's live row, the owner's
1599/// fill, the leg's side, the resolved offset (price units) and the kernel
1600/// level after the anchor rounding. It is consulted exactly once per
1601/// materialization, before the ArmedEvent is built, so the ArmedEvent and
1602/// every later reader see the installed level. A returned value is the level
1603/// to install (the kernel's representability check still applies and a
1604/// failure is the existing PreparationError path); nullopt keeps the kernel
1605/// level. An empty callable is exactly the pre-hook behaviour.
1606using AnchoredLevelResolver = std::function<std::optional<double>(
1607 const LiveRequest& leg, const ExecutionAppliedEvent& owner_fill, Side leg_side,
1608 double offset, double kernel_level)>;
1609
1610/// What the consumer hands to prepare_owner_applied for an anchored leg's
1611/// materialization, the way acceptance receives CommandContext::price_tick.
1612/// price_tick is the ladder a rounded anchor snaps to; an anchor whose
1613/// rounding is Raw never reads it. resolve_level is the host's restatement,
1614/// carried as a value so the core stays host-free.
1619
1642
1643/// What the consumer hands to prepare_trigger under an opted-in price grid
1644/// (NativeRunSpec::price_grid == QuantizeFillsAndTriggers), the way acceptance
1645/// receives CommandContext::price_tick: the tick ladder and the rounding the
1646/// matcher tested the activation on. The core then re-validates the reached
1647/// print with the same grid arithmetic (L8b ruling: under that grid the
1648/// tick-quantized print IS the reached price, so a hit the matcher reports is
1649/// never refused), records the quantized print as the activation's reached
1650/// price, and keeps a trail's running best on the ladder. A default-constructed
1651/// value (no ladder) is exactly the raw compare and the raw print every
1652/// activation used before. Host-free: values only.
1653///
1654/// ladder_tick is a different question, appended last so every existing
1655/// aggregate initializer keeps its meaning: the run's DECLARED price tick
1656/// (NativeRunSpec::price_tick), whatever the quantization mode. Nothing is
1657/// tested or rounded on it; it is the ladder a level spelled in ticks names,
1658/// so a trailing stop a whole number of ticks from a best on that ladder is
1659/// the ladder point it names rather than the subtraction's ULP-off neighbour
1660/// (native_matching::ladder_trail_stop, the integer-tick trailing stop). Zero leaves the raw
1661/// arithmetic, which is what a run with no declared tick has.
1663 double price_tick = 0.0; // finite and positive when a ladder is in force
1664 bool half_up = true; // nearest tick, ties away from zero; else directional
1665 double ladder_tick = 0.0; // the run's own price tick, quantized or not
1666};
1667
1670 /// Carries the generic pre-open delivery authorization from the matching
1671 /// evaluation through the synchronous execution preparation.
1673 double raw_price = 0.0;
1674 double resolved_price = 0.0;
1682};
1683
1691
1692/// Where an install put its events: `first_index` is an ABSOLUTE journal
1693/// position -- the count of events the run committed before them -- so it
1694/// stays valid when the front of the journal is retired
1695/// (WorkingRequestCore::history_at reads it back).
1697 std::size_t first_index = 0;
1698 std::size_t count = 0;
1699};
1700
1704
1705enum class InstallError : std::uint8_t {
1709};
1710
1711using InstallResult = std::variant<Installed, InstallError>;
1712
1720
1724
1725/// A working-request preparation that could not produce a representable
1726/// instruction — a nonrepresentable quantity, most often. Reported as a settlement
1727/// failure, which fails the run: it is a contract breach, not a refused candidate.
1733
1734template <class T>
1735using Preparation = std::variant<T, NoChange, PreparationError>;
1736
1737template <class R>
1742
1743template <class R>
1744using InstalledCommand = std::variant<CommandInstalled<R>, InstallError>;
1745
1746class PreparedSubmit;
1747class PreparedReplace;
1748class PreparedCancel;
1749class PreparedMutation;
1750class PreparedExecution;
1751
1753 bool birth_ok = false;
1754 bool waiting = false;
1755 bool driver_ok = false;
1756 bool needs_close_bind = false;
1757 bool ready_to_match = false;
1759 bool is_buy = false;
1760 const Trigger* trigger = nullptr;
1761 const TriggerState* trigger_state = nullptr;
1762 const Remaining* remaining = nullptr;
1763 const Allowance* allowance = nullptr;
1764 const Authority* authority = nullptr;
1765};
1766
1769 /// Canonical origin handles, ordered by origin incarnation rather than by
1770 /// host insertion order. A successor is normalized to its predecessor
1771 /// root before entering this table.
1772 std::vector<RequestHandle> origins;
1773};
1774
1775enum class CohortReceiptOperation : std::uint8_t { Add = 0, Remove = 1 };
1788
1789/// One group-effect receipt: the applied event that caused it, the request it
1790/// acted on, the effect, and the ordinal of the event that recorded the
1791/// outcome. A drain that meets the same cause, recipient and effect again finds
1792/// it and applies nothing (WorkingRequestCore::group_effect_receipt).
1799
1800/// Payload row policy for pending batches and committed-row read caches.
1801/// Shared line metadata is additional; retained history/backing admission are
1802/// independent. Zero disables optional residency. One fixed pending record
1803/// per store remains so prepared installation cannot allocate or do I/O.
1805 std::size_t successor_root_rows = 64;
1806 std::size_t receipt_rows = 64;
1807 std::size_t chain_segment_rows = 16;
1808};
1809
1810/// Storage observations only, excluded from continuation state.
1813 std::size_t receipt_capacity_rows = 0;
1815 std::uint64_t successor_root_reads = 0;
1816 std::uint64_t receipt_reads = 0;
1817 std::uint64_t chain_index_reads = 0;
1818 std::uint64_t chain_id_reads = 0;
1819 std::uint64_t successor_root_read_bytes = 0;
1820 std::uint64_t receipt_read_bytes = 0;
1821 std::uint64_t chain_index_read_bytes = 0;
1823 std::size_t receipt_pending_bytes = 0;
1826 std::size_t receipt_control_bytes = 0;
1828};
1829
1831
1833public:
1834 class CohortView;
1835 class JournalView;
1836 class LiveView;
1839 /// Inclusive committed-row read-cache allowances. Pending write batches
1840 /// keep their internal defaults, independently of these optional caches.
1845 detail::ReadThroughCacheStats tail_read_cache_stats(TailWindowKind kind) const noexcept;
1847 WorkingRequestCore& operator=(const WorkingRequestCore&) = delete;
1848 /// Move transfers the complete run. The source becomes empty/unbound;
1849 /// commands throw invalid_argument without mutation until reset rebinds it.
1850 /// Self move-assignment preserves the current run.
1852 WorkingRequestCore& operator=(WorkingRequestCore&& other) noexcept;
1853 void set_successor_roots_directory(const std::string& directory); // After reset, before the first spill; empty uses TMPDIR or /tmp.
1854 void bind_scratch_slab(const std::shared_ptr<::pineforge::detail::ScratchSlab>& slab);
1856
1857 const RunIdentity& identity() const noexcept { return identity_; }
1858 /// The working book, in queue order: ascending LiveRequest::priority,
1859 /// which is incarnation order until a re-price keeps a handle
1860 /// (ReplaceOptions::keep_handle) and takes its request to the back.
1861 /// The book is a read view (LiveView). Up to a fixed bound its rows are
1862 /// resident; a larger book keeps its newest rows outside the heap, in an
1863 /// external ordered table, and the view reads them back in queue order.
1864 /// A row the view hands out stays valid until the next few spilled rows it
1865 /// reads or the next command; a command that targets a row outside the
1866 /// heap brings that row back first.
1867 LiveView live() const noexcept;
1868 /// How many requests are live, resident or not, in O(1); and the request
1869 /// at queue rank `rank` (std::out_of_range when none).
1870 std::size_t live_size() const noexcept;
1871 const LiveRequest& live_at(std::size_t rank) const;
1872 /// Whether some live rows are outside the heap: live_at and live_back
1873 /// then walk the book in queue order instead of indexing it.
1874 bool book_spilled() const noexcept { return spilled_count() != 0; }
1875 /// The command journal's retained window, oldest first. Commits append at
1876 /// the back and retire_history drops a prefix, so the window holds the
1877 /// absolute journal positions [history_base(), history_end()). A core
1878 /// nothing retires -- every core its consumer runs under a retaining
1879 /// policy -- keeps the whole journal and history_base() stays 0.
1880 /// The window is a read view (JournalView): its newest events are resident
1881 /// and its older ones, past a fixed resident bound, live in external
1882 /// pages that the view reads back through a fixed page cache. A reference
1883 /// it hands out stays valid until the next few page loads or the next
1884 /// command; copy an event that must outlive them.
1885 JournalView history() const noexcept;
1886 /// How many events have been retired from the front of the journal.
1887 std::size_t history_base() const noexcept { return history_base_; }
1888 /// The absolute journal length: every event this run has committed,
1889 /// retained or not. EventRange::first_index counts in this space.
1890 std::size_t history_end() const noexcept;
1891 /// The event at an absolute journal position; std::out_of_range when it
1892 /// is not retained.
1893 const CommandEvent& history_at(std::size_t absolute) const;
1894 /// Retire the journal prefix whose ordinals are <= through_ordinal. It
1895 /// stops ahead of the first event a live request still reads -- the head
1896 /// of a deferred group-adjustment chain (collect_pending_chain) -- and
1897 /// drops group-effect keys whose causes are <= retired_through(). The
1898 /// consumer must fold those keys first; queued applied notifications pin
1899 /// their causes. Live rows, logical counters and cohort rosters do not
1900 /// move. Answers the number of journal events retired.
1901 std::size_t retire_history(uint64_t through_ordinal);
1902 /// The ordinal of the newest retired event, 0 while none is.
1903 uint64_t retired_through() const noexcept { return retired_through_; }
1904 /// Logical roster sequence in allocation order, including empty rosters
1905 /// whose members have been removed. Walking allocates nothing and does
1906 /// no I/O; empty rows are synthesized from the dense handle sequence.
1907 CohortView cohorts() const noexcept;
1908 /// The retained cohort-receipt window, in command order. Its absolute
1909 /// positions are [cohort_receipt_base(), cohort_receipt_count()). A
1910 /// standalone core retains all rows until its reader retires them; the
1911 /// Window consumer retires each row after folding it into its digest.
1912 const std::vector<CohortReceipt>& cohort_receipts() const noexcept {
1913 return cohort_receipts_;
1914 }
1915 std::size_t cohort_receipt_base() const noexcept { return cohort_receipts_base_; }
1916 /// Every cohort receipt this run committed, retained or not.
1917 std::size_t cohort_receipt_count() const noexcept {
1918 return cohort_receipts_base_ + cohort_receipts_.size();
1919 }
1920 /// Drop the prefix strictly below the absolute position through, clamped
1921 /// to the window. The reader must have folded these rows first. Does not
1922 /// change rosters, command counters, prepared-command validity or totals.
1923 std::size_t retire_cohort_receipts(std::size_t through) noexcept;
1924 /// The core's two durable counters: the ordinal of the last committed
1925 /// event and the last incarnation a command consumed. A later command must
1926 /// exceed both.
1927 uint64_t last_ordinal() const noexcept { return last_ordinal_; }
1928 uint64_t last_incarnation() const noexcept { return last_incarnation_; }
1929 /// Group-effect keys in cause/commit order, retaining [base, count).
1930 /// Count is the total ever committed; base advances with retire_history.
1931 /// A cause's recipients stay in drain/queue order, even after keep_handle
1932 /// re-pricing. Older-than-newest causes are InvalidCause unless replayed.
1933 /// A retired index asserts in Debug and throws out_of_range otherwise.
1934 /// Only the newest few keys are resident: the rest of the retained window
1935 /// is read back from an external fixed-record log, so a pinned journal
1936 /// (a live deferred chain) no longer grows the heap with its receipts.
1937 std::size_t group_effect_receipt_base() const noexcept;
1938 std::size_t group_effect_receipt_count() const noexcept;
1940 /// The prior adjustment ids a TermsResolvedEvent or QuantityBoundEvent
1941 /// spent, in chain order, as a paged contract: `visit` is called once per
1942 /// id until it answers false. A chain of up to kInlineAdjustmentIds links
1943 /// is in the event's own prior_adjustment_ids exactly as before; a longer
1944 /// one is not copied into the event (a vector of every id would be O(chain
1945 /// length) in the event and in its prepared token) but kept in an external
1946 /// list this call reads back in fixed pages. Answers false when
1947 /// `resolution` names no such event in the retained window.
1948 bool visit_prior_adjustments(const EventId& resolution,
1949 const std::function<bool(const EventId&)>& visit) const;
1950 /// How many prior adjustment ids that event spent (0 when none or unknown).
1951 std::size_t prior_adjustment_count(const EventId& resolution) const;
1952 /// The longest chain whose ids an event still carries inline.
1953 static constexpr std::size_t kInlineAdjustmentIds = 256;
1954 /// The incarnations this run issued -- every accepted request and every
1955 /// replace successor -- as disjoint ascending [first, last] ranges: the
1956 /// half of the chain index that tells a request that ended from one that
1957 /// never existed. Read-only; a consumer issues one dense range.
1958 const std::vector<std::pair<uint64_t, uint64_t>>& issued_incarnations() const noexcept {
1959 return issued_;
1960 }
1961 const LiveRequest* find_live(const RequestHandle& handle) const;
1962 /// find_live for a reader that keeps the row across further reads of the
1963 /// book. A spilled row's copy lives as long as the answer does, whatever
1964 /// the book reads next; a resident row's answer owns nothing and, like
1965 /// find_live's, stays valid until the next command. Null when no live
1966 /// request has the handle.
1967 std::shared_ptr<const LiveRequest> hold_live(const RequestHandle& handle) const;
1968 /// A read of the book's spilled rows that failed where its reader cannot
1969 /// throw (a noexcept fold of the book): the reader hands the failure here
1970 /// and answers true, and from then on every read of the spilled rows and
1971 /// every command that moves one throws it again -- an I/O failure as the
1972 /// typed scratch failure (scratch_io_failed) -- until reset. False, with
1973 /// nothing kept, when the book has never spilled: it then has no such read.
1974 bool carry_book_read_failure(std::exception_ptr failure) const noexcept;
1975 /// Throws the failure carry_book_read_failure kept, if any.
1977 /// How many spilled rows the book has read back to answer a lookup -- by
1978 /// handle (find_live, hold_live), by rank (live_at, live_back) -- and how
1979 /// many table steps those lookups walked, since this core was built or
1980 /// reset. A walk of live() in queue order is not counted, nor is a read of
1981 /// a row at the table entry such a walk found it at (the matcher reusing a
1982 /// row it scanned): neither searches the book. Diagnostics only: nothing
1983 /// folds them and no decision reads them.
1985 std::uint64_t rows = 0;
1986 std::uint64_t steps = 0;
1987 };
1989 const CommandEvent* event_at(const EventId& id) const;
1990
1991 /// Command-boundary roster maintenance. A rejected add/remove records a
1992 /// durable generic receipt but never emits a market event.
1996 bool cohort_contains(CohortHandle cohort, const RequestHandle& opening) const;
1997
1998 /// R1 producer convenience: prepare then install one command. Bound opening
1999 /// enrollment requires CommandContext observations via prepare_submit.
2001 int64_t decision_time_ms,
2002 uint64_t& next_order_incarnation,
2003 uint64_t& next_timeline_ordinal,
2004 std::optional<double> quantity_grid = std::nullopt);
2005
2007 const Request& request,
2008 int64_t decision_time_ms,
2009 uint64_t& next_order_incarnation,
2010 uint64_t& next_timeline_ordinal,
2011 std::optional<double> quantity_grid = std::nullopt,
2012 ReplaceOptions options = {});
2013
2014 CancelResult cancel(const RequestHandle& target, uint64_t& next_timeline_ordinal);
2015
2016 /// Consumer-only typed prepare/install. Prepare may reserve storage and
2017 /// invalidate live/history references; it does not change logical state.
2018 /// Tokens are move-only, bound to this instance/run/epoch, and do not
2019 /// survive another prepare/install, reset, or move.
2021 const CommandContext& context,
2022 uint64_t& next_order_incarnation,
2023 uint64_t& next_timeline_ordinal,
2026 const Request& request,
2027 const CommandContext& context,
2028 uint64_t& next_order_incarnation,
2029 uint64_t& next_timeline_ordinal,
2030 ReplaceOptions options = {});
2031 /// `reason` lets the kernel withdraw its own request under the durable
2032 /// Superseded receipt. Host cancels keep the default User reason.
2033 PreparedCancel prepare_cancel(const RequestHandle& target, uint64_t& next_timeline_ordinal,
2035
2039
2041 const EvaluationContext& context,
2042 const TargetObservation& observation,
2043 uint64_t& next_timeline_ordinal);
2044 /// The same preparation under an explicit open activation grant: the caller
2045 /// vouches, for this handle alone, that the request is to be read as born
2046 /// before this point. The grant satisfies only the strict ordinal half of
2047 /// the birth gate, and only at an Open cursor whose effective time is not
2048 /// before the request's decision-time lower bound. It relaxes no time floor
2049 /// and no trigger, driver, quantity, margin or price check, it leaves the
2050 /// other birth exceptions as they stand, and it gives
2051 /// EvaluationContext::pre_open_birth_eligible no new meaning (that bool stays
2052 /// market/immediate only). Nothing retains it -- no request, plan or prepared
2053 /// token -- so every call that tests the gate takes it afresh; the check and
2054 /// the apply of an execution each do. The form above is this one with the
2055 /// grant false.
2057 const EvaluationContext& context,
2058 const TargetObservation& observation,
2059 uint64_t& next_timeline_ordinal,
2060 bool open_activation_grant);
2061 /// `grid` carries the run's activation grid (ActivationGrid); the default
2062 /// is the raw rule, so every existing caller keeps its meaning.
2064 const TriggerTransition& transition,
2065 DriverEligibilityClass driver_class,
2066 uint64_t& next_timeline_ordinal,
2067 std::optional<Side> cohort_side = std::nullopt,
2068 const ActivationGrid& grid = {});
2069 /// prepare_trigger under the open activation grant of prepare_evaluation: the
2070 /// cursor test every trigger transition makes (Stop, StopLimit and Trail
2071 /// activation among them) takes the grant too. Every parameter is explicit,
2072 /// so no earlier call can resolve to this form.
2074 const TriggerTransition& transition,
2075 DriverEligibilityClass driver_class,
2076 uint64_t& next_timeline_ordinal,
2077 std::optional<Side> cohort_side,
2078 const ActivationGrid& grid,
2079 bool open_activation_grant);
2081
2083 const ExecutionProposal& proposal,
2084 uint64_t& next_timeline_ordinal);
2085 /// prepare_execution under the open activation grant of prepare_evaluation.
2086 /// The token holds no grant: install_execution does not read the birth gate.
2088 const ExecutionProposal& proposal,
2089 uint64_t& next_timeline_ordinal,
2090 bool open_activation_grant);
2092 const CommittedExecutionFacts& facts) noexcept;
2094 const EvaluationContext& context,
2095 uint64_t& next_timeline_ordinal);
2097 const EvaluationContext& context,
2098 MatchRejectReason reason,
2099 uint64_t& next_timeline_ordinal);
2101 const RequestHandle& target,
2102 const EvaluationContext& context,
2103 MatchRejectReason reason,
2104 std::optional<ExecutionTerms> attempted_terms,
2105 uint64_t& next_timeline_ordinal);
2107 const EvaluationContext& context,
2109 uint64_t& next_timeline_ordinal);
2110
2111 /// Append one MarginCallEvent to the immutable history. The target is the
2112 /// kernel-originated request that filled, which is already terminal by the
2113 /// time its margin facts are recorded, so no live row moves.
2115 uint64_t& next_timeline_ordinal);
2116
2117 /// Append one NativeRiskEvent to the immutable history. It names an
2118 /// account-level breach rather than a request, so it moves no live row and
2119 /// carries no definition; the kernel's own response (an opening block, and
2120 /// with FlattenAndBlock a KernelRisk Flatten) is separate from the record.
2122 uint64_t& next_timeline_ordinal);
2123
2124 /// The allowance that prepare_evaluation would install for this point.
2125 static Allowance evaluated_allowance(const LiveRequest& live, uint64_t point) noexcept;
2126 /// Consumer-only no-event form of the ordinary allowance
2127 /// refresh. It preserves prepare_evaluation's eligibility and liveness
2128 /// checks while avoiding a transient mutation envelope per driver point.
2129 void refresh_point_allowances(uint64_t point, const PositionIdentity& position);
2131 const EvaluationContext& context,
2132 const TargetObservation& observation);
2133 /// refresh_allowance under the open activation grant of prepare_evaluation.
2135 const EvaluationContext& context,
2136 const TargetObservation& observation,
2137 bool open_activation_grant);
2138 /// The same refresh, handed the run's timeline: an unbound close that
2139 /// carried a binding the book still has (ReplaceOptions::keep_binding) is
2140 /// bound here too, to the BookClose its CloseBoundEvent would install, and
2141 /// takes that event's ordinal without recording it. The form above leaves
2142 /// every unbound close to the evaluation.
2144 const EvaluationContext& context,
2145 const TargetObservation& observation,
2146 uint64_t& next_timeline_ordinal);
2147 /// The timeline-handed refresh under the open activation grant.
2149 const EvaluationContext& context,
2150 const TargetObservation& observation,
2151 uint64_t& next_timeline_ordinal,
2152 bool open_activation_grant);
2154 const EvaluationContext& context,
2155 const TargetObservation& observation);
2156 /// Pure arithmetic over the cached pending total. Outputs are assigned only
2157 /// after every validation and subtraction succeeds. A total binary64 cannot
2158 /// take off the resolved units is absorbed: the deduction is 0 and the
2159 /// units stand (the absorbed-deduction rule).
2160 static bool effective_host_units(const PendingAdjustments& pending,
2161 double resolved_units,
2162 double* deduction,
2163 double* after,
2164 bool* exhausted) noexcept;
2165
2166 void reserve(std::size_t expected_events);
2167 /// The live siblings an applied member's fill reaches -- its group, another
2168 /// cohort, born before the fill; none for a fill with no effect, such as a
2169 /// Cancel member's nonterminal one -- in queue order (live()): a request
2170 /// re-priced with its handle kept stands behind every sibling accepted or
2171 /// re-priced before it, where a plain replace's successor stands (the
2172 /// stable group-recipient ordering).
2173 std::vector<RequestHandle> group_recipients(const EventId& applied) const;
2174 std::vector<RequestHandle> waiting_children(const RequestHandle& parent) const;
2175 bool has_waiting_children(const RequestHandle& parent) const;
2176 std::vector<RequestHandle> bound_close_handles() const;
2177 /// The same three lists written over `out`, whose capacity a caller that
2178 /// keeps it reuses (the direct request-core forms): the same handles in the same order.
2179 void group_recipients(const EventId& applied, std::vector<RequestHandle>& out) const;
2180 void waiting_children(const RequestHandle& parent, std::vector<RequestHandle>& out) const;
2181 void bound_close_handles(std::vector<RequestHandle>& out) const;
2182 /// Synchronous queue-order reads. Callbacks may read the core or write a
2183 /// separate scratch store; they must not mutate this core.
2187
2189 const RequestHandle& recipient,
2190 uint64_t& next_timeline_ordinal);
2191 /// `arm` carries the materialization facts of an anchored leg (the price
2192 /// tick a rounded anchor snaps to, the host's level restatement); a
2193 /// default-constructed value is exactly the pre-lane behaviour, so every
2194 /// existing caller keeps its meaning.
2196 const EventId& applied,
2197 const RequestHandle& child,
2198 const std::optional<OpeningObservation>& observation,
2199 uint64_t& next_timeline_ordinal,
2200 const ArmContext& arm = {});
2202 const RequestHandle& child,
2203 const TargetObservation& observation,
2204 uint64_t& next_timeline_ordinal);
2206 const RequestHandle& child,
2207 uint64_t& next_timeline_ordinal);
2208
2209 /// The direct forms (the direct request-core forms). Each apply_* makes the checks of the
2210 /// prepare_* it is named after, in the same order, and answers what that
2211 /// prepare_* followed at once by its install answers: the same NoChange or
2212 /// PreparationError, the same exception at the same point with nothing
2213 /// changed, and otherwise the same events at the same absolute journal
2214 /// positions, the same live rows, receipts, chain index and counters, and a
2215 /// token prepared before it just as stale. What it does not do is stage:
2216 /// no token, plan or row copy stands between the checks and the write --
2217 /// the live row is updated where it stands and each event is appended
2218 /// once. A caller that installs what it prepares straight away uses these;
2219 /// the prepare/install pairs stay for a caller that holds a token across
2220 /// other work, or that may decide not to install (an anchored arm whose
2221 /// restatement hook failed). An execution's direct form comes in the two
2222 /// halves its caller needs around the settlement it proposes:
2223 /// check_execution makes prepare_execution's checks and its seal (the same
2224 /// NoChange, PreparationError or exception, the same reservation and
2225 /// epoch) and holds nothing; apply_execution, after the settlement, makes
2226 /// them again and then does what install_execution does with the token
2227 /// prepare_execution would have made. A caller changes nothing in the core
2228 /// between the two; if the checks no longer pass, apply_execution answers
2229 /// StalePreparation and writes nothing.
2231 const CommandContext& context,
2232 uint64_t& next_order_incarnation,
2233 uint64_t& next_timeline_ordinal,
2236 const Request& request,
2237 const CommandContext& context,
2238 uint64_t& next_order_incarnation,
2239 uint64_t& next_timeline_ordinal,
2240 ReplaceOptions options = {});
2242 uint64_t& next_timeline_ordinal,
2245 const EvaluationContext& context,
2246 const TargetObservation& observation,
2247 uint64_t& next_timeline_ordinal);
2248 /// apply_evaluation under the open activation grant of prepare_evaluation.
2250 const EvaluationContext& context,
2251 const TargetObservation& observation,
2252 uint64_t& next_timeline_ordinal,
2253 bool open_activation_grant);
2255 const TriggerTransition& transition,
2256 DriverEligibilityClass driver_class,
2257 uint64_t& next_timeline_ordinal,
2258 std::optional<Side> cohort_side = std::nullopt,
2259 const ActivationGrid& grid = {});
2260 /// apply_trigger under the open activation grant of prepare_evaluation; every
2261 /// parameter is explicit, like prepare_trigger's grant form.
2263 const TriggerTransition& transition,
2264 DriverEligibilityClass driver_class,
2265 uint64_t& next_timeline_ordinal,
2266 std::optional<Side> cohort_side,
2267 const ActivationGrid& grid,
2268 bool open_activation_grant);
2270 const EvaluationContext& context,
2271 uint64_t& next_timeline_ordinal);
2273 const EvaluationContext& context,
2274 MatchRejectReason reason,
2275 std::optional<ExecutionTerms> attempted_terms,
2276 uint64_t& next_timeline_ordinal);
2278 const EvaluationContext& context,
2280 uint64_t& next_timeline_ordinal);
2282 uint64_t& next_timeline_ordinal);
2284 uint64_t& next_timeline_ordinal);
2286 const RequestHandle& recipient,
2287 uint64_t& next_timeline_ordinal);
2289 const RequestHandle& child,
2290 const std::optional<OpeningObservation>& observation,
2291 uint64_t& next_timeline_ordinal,
2292 const ArmContext& arm = {});
2294 const RequestHandle& child,
2295 const TargetObservation& observation,
2296 uint64_t& next_timeline_ordinal);
2298 const RequestHandle& child,
2299 uint64_t& next_timeline_ordinal);
2301 const ExecutionProposal& proposal,
2302 uint64_t& next_timeline_ordinal);
2303 /// check_execution and apply_execution under the open activation grant of
2304 /// prepare_evaluation. The two make the same birth test, so a caller hands
2305 /// both the grant its scope holds; an apply given a different one answers
2306 /// StalePreparation like any check that no longer passes.
2308 const ExecutionProposal& proposal,
2309 uint64_t& next_timeline_ordinal,
2310 bool open_activation_grant);
2312 const ExecutionProposal& proposal,
2313 const CommittedExecutionFacts& facts,
2314 uint64_t& next_timeline_ordinal);
2316 const ExecutionProposal& proposal,
2317 const CommittedExecutionFacts& facts,
2318 uint64_t& next_timeline_ordinal,
2319 bool open_activation_grant);
2320
2322 const EvaluationContext& context) const noexcept;
2323 /// eligibility_facts and evaluation_eligible under the open activation grant
2324 /// of prepare_evaluation: the grant can only set birth_ok.
2326 const EvaluationContext& context,
2327 bool open_activation_grant) const noexcept;
2329 const EvaluationContext& context) const noexcept;
2331 const EvaluationContext& context,
2332 bool open_activation_grant) const noexcept;
2333 bool trigger_permits_driver(const Trigger& trigger,
2334 const TriggerState& state,
2335 DriverEligibilityClass driver_class,
2336 bool existing_matching_bit) const noexcept;
2338 std::optional<Side> cohort_side = std::nullopt) const noexcept;
2339 /// Borrow the rosters holding origins, in ascending cohort-handle order.
2340 /// No copies or allocation; use only until the next cohort mutation.
2341 const std::map<std::uint64_t, CohortRoster>& nonempty_cohorts() const noexcept {
2342 return cohorts_;
2343 }
2344
2345private:
2346 enum class TargetKind { Live, NotWorking, InvalidHandle };
2347 // Native-private bridge. The existing opaque journal owns the resource;
2348 // this borrow ends at reset/move/destruction, like its other core reads.
2349 const detail::NativeHistoryOwner* managed_history_owner() const noexcept;
2350 friend void bind_journal_codec(WorkingRequestCore&, detail::CommandScratchStore&);
2352 const detail::ScratchByteSource&) noexcept;
2353 friend const detail::ScratchReadback& journal_readback(const WorkingRequestCore&);
2354 friend class PreparedSubmit;
2355 friend class PreparedReplace;
2356 friend class PreparedCancel;
2357 friend class PreparedMutation;
2358 friend class PreparedExecution;
2359 // The matcher reuses a row it scanned outside the heap through the place
2360 // its walk found the row at (book_generation, hold_scanned below).
2362
2363 static void require_identity(const RunIdentity& identity);
2364 static void require_grid(std::optional<double> quantity_grid);
2365 static void require_distinct_counters(uint64_t& next_order_incarnation,
2366 uint64_t& next_timeline_ordinal);
2367 std::optional<RequestRejectReason> validate_request(
2368 const Request& request,
2369 const CommandContext& context,
2370 const std::optional<RequestHandle>& replace_target) const;
2371 /// Resolves tick-spelled trail offsets and trigger anchors in place
2372 /// against context.price_tick. Runs after validate_request, on the staged
2373 /// copy that becomes the stored definition.
2374 static std::optional<RequestRejectReason> resolve_tick_spellings(
2375 Request& request, const CommandContext& context);
2376 LiveRequest make_live(DefinitionRef definition,
2377 const CommandContext& context,
2378 EventId accepted) const;
2379 enum class ReceiptLookup : std::uint8_t { Absent = 0, Present = 1, Conflict = 2 };
2380 /// What a chain walk keeps of the chain's ids besides its total: the ids
2381 /// of a chain of up to kInlineAdjustmentIds links, in chain order, or --
2382 /// for a longer one -- where the walk wrote them (an external list of
2383 /// 8-byte ordinals), so no vector of every id is ever built.
2384 struct ChainIds {
2385 std::vector<EventId> inline_ids;
2386 uint64_t count = 0;
2387 bool external = false;
2388 uint64_t offset = 0;
2389 };
2390 /// Walks the live deferred chain `pending` back from its tail, checking
2391 /// every link, then replays it forward in commit order to reproduce the
2392 /// total's exact accumulation. False on any link that does not check.
2393 bool collect_pending_chain(const PendingAdjustments& pending,
2394 const RequestHandle& recipient,
2395 ChainIds* ids,
2396 double* total);
2397 ReceiptLookup receipt_lookup(const EventId& cause, const RequestHandle& recipient,
2398 GroupEffect effect, uint64_t* outcome) const;
2399 bool authenticate_receipt_outcome(const CommandEvent& event, const EventId& cause,
2400 const RequestHandle& recipient, GroupEffect effect) const;
2401 /// The first retained group-effect key whose cause is above `retired`
2402 /// (the end of the keys when none): the base retire_history moves to. Reads
2403 /// the external log, so it may throw; apply_receipt_base cannot.
2404 std::size_t receipts_first_above(uint64_t retired) const;
2405 void apply_receipt_base(std::size_t base) noexcept;
2406 /// The external stores are created on first use, in the directory
2407 /// set_successor_roots_directory named.
2408 void ensure_receipt_store();
2409 CacheCapacity tail_cache_capacity(TailWindowKind kind) const noexcept;
2410 void prepare_codec_frames();
2411 void ensure_chain_segments();
2412 /// Appends one event to the journal (storage reserved by reserve_plan or
2413 /// seal_direct) and advances the absolute length. A bound codec reports I/O.
2414 void append_event(CommandEvent&& event);
2415 /// A first deferred link starts a chain whose head retire_history must
2416 /// keep; a row that held a chain and no longer does makes the cached
2417 /// floor a lower bound until it is recomputed.
2418 void note_chain_birth(const CommandEvent& event) noexcept;
2419 void note_chain_loss(const LiveRequest& row) noexcept;
2420 /// The working book's row erase: every erase goes through here so the
2421 /// chain floor hears of a chain that left with its row.
2422 void erase_live(std::size_t index) noexcept;
2423 /// A best and offset through native_matching::checked_trail_stop: finite,
2424 /// strictly past the best for a positive offset. `ladder_tick` is the run's
2425 /// price tick (ActivationGrid::ladder_tick); zero keeps the raw subtraction.
2426 bool trail_level_ok(double best, double offset, bool is_buy, double* stop,
2427 double ladder_tick = 0.0) const noexcept;
2428 /// Whether this run's core accepted a request under `handle`, live or
2429 /// not: what a cohort command answers UnknownOrigin by.
2430 bool issued(const RequestHandle& handle) const noexcept;
2431 /// The first handle of `origin`'s replace chain (RequestDefinition::root),
2432 /// read from the live row or, for a request that is no longer working,
2433 /// from the chain index; nullopt for a handle this run never issued.
2434 std::optional<RequestHandle> canonical_cohort_origin(const RequestHandle& origin) const;
2435 bool cohort_exists(CohortHandle cohort) const noexcept;
2436 /// Records an accepted request's incarnation and, for a replace
2437 /// successor, its chain root, in the chain index. Storage is reserved by
2438 /// reserve_plan, so this cannot throw inside commit.
2439 void index_committed(const CommandEvent& event) noexcept;
2440 /// The head of every live deferred group-adjustment chain: the lowest
2441 /// ordinal retire_history must keep, or nullopt when no chain is live.
2442 std::optional<uint64_t> pending_chain_floor() const;
2443
2444 uint64_t usable_ordinal(uint64_t next) const;
2445 uint64_t usable_incarnation(uint64_t next) const;
2446 /// The working book's position of the request under `incarnation`, or
2447 /// live_.size(), while repriced_ names a re-priced request: a search by
2448 /// queue order (the priority repriced_ names, or the handle's own number).
2449 std::size_t repriced_position(uint64_t incarnation) const noexcept;
2450 /// refresh_allowance's body, and its bind for an unbound close that
2451 /// carried a binding, under the open activation grant (false for every
2452 /// form that does not name one).
2453 bool refresh_allowance(const RequestHandle& target, const EvaluationContext& context,
2454 const TargetObservation& observation,
2455 uint64_t* next_timeline_ordinal, bool open_activation_grant);
2456 bool refresh_kept_binding(LiveRequest& live, const EvaluationContext& context,
2457 const TargetObservation& observation,
2458 uint64_t& next_timeline_ordinal);
2459 TargetKind classify_repriced(uint64_t incarnation, std::size_t* live_index) const noexcept;
2460 const LiveRequest* find_repriced(uint64_t incarnation) const noexcept;
2461 /// Records that the request under `incarnation` now stands at
2462 /// `priority` (a keep_handle re-price), dropping the entries of requests
2463 /// that have left the book once they outnumber its rows. The caller has
2464 /// reserved one entry.
2465 void note_repriced(uint64_t incarnation, uint64_t priority) noexcept;
2466 /// classify among the resident rows only.
2467 TargetKind classify_resident(const RequestHandle& handle, std::size_t* live_index) const;
2468 /// The index of the row a command targets in live_. A target outside the
2469 /// heap (a spilled row) is brought back into live_ first -- the one place
2470 /// the book changes without a command committing -- so every command
2471 /// reads and writes its row where the resident book keeps it.
2472 TargetKind classify(const RequestHandle& handle, std::size_t* live_index);
2473 bool bump_epoch() noexcept;
2474 void require_epoch_room() const;
2475 void clear_unbound() noexcept;
2476
2477 struct InstanceBinding {
2478 uint64_t generation = 1;
2479 bool expired = false;
2480 };
2481 struct MutationPlan {
2482 std::weak_ptr<InstanceBinding> instance;
2483 uint64_t generation = 0;
2484 uint64_t epoch = 0;
2485 RunIdentity run;
2486 std::size_t history_size = 0;
2487 uint64_t last_ordinal = 0;
2488 bool consumed = false;
2489 InlineCommandEvents events;
2490 std::uint8_t live_change = 0; // 0 none, 1 push, 2 erase, 3 update
2491 std::size_t live_index = 0;
2492 LiveRequest live_row{};
2493 bool consume_incarnation = false;
2494 uint64_t incarnation_used = 0;
2495 // An ordinal the mutation takes with no event: a kept binding's
2496 // (ReplaceOptions::keep_binding). 0: none.
2497 uint64_t skipped_ordinal = 0;
2498 bool add_receipt = false;
2499 EventId receipt_cause{};
2500 RequestHandle receipt_recipient{};
2501 GroupEffect receipt_effect = GroupEffect::Reduce;
2502 uint64_t receipt_outcome = 0;
2503 // A resolution event whose chain is longer than kInlineAdjustmentIds
2504 // keeps its ids in the external list the chain walk wrote: the event's
2505 // ordinal, where the list starts and how many ids it holds.
2506 bool chain_segment = false;
2507 uint64_t chain_event_ordinal = 0;
2508 uint64_t chain_offset = 0;
2509 uint64_t chain_count = 0;
2510 };
2511 std::optional<InstallError> validate_plan(const MutationPlan& plan) const noexcept;
2512 // prepare_replace / apply_replace past their checks, for a replace that
2513 // keeps the handle or carries a binding (ReplaceOptions::keep_handle /
2514 // keep_binding), and the successor row the two share.
2515 PreparedReplace prepare_replace_kept(std::size_t live_index, const RequestHandle& staged_target,
2516 Request&& staged, uint64_t ordinal, uint64_t incarnation,
2517 const CommandContext& context, ReplaceOptions options,
2518 MutationPlan&& plan);
2519 // apply_replace's two instantiations: <false> is a plain replace,
2520 // <true> (through apply_replace_kept, out of line) one with the options.
2521 template <bool Kept>
2522 CommandInstalled<ReplaceResult> apply_replace_as(const RequestHandle& target,
2523 const Request& request,
2524 const CommandContext& context,
2525 uint64_t& next_order_incarnation,
2526 uint64_t& next_timeline_ordinal,
2527 ReplaceOptions options);
2528 CommandInstalled<ReplaceResult> apply_replace_kept(const RequestHandle& target,
2529 const Request& request,
2530 const CommandContext& context,
2531 uint64_t& next_order_incarnation,
2532 uint64_t& next_timeline_ordinal,
2533 ReplaceOptions options);
2534 LiveRequest kept_successor(std::size_t live_index, const RequestHandle& staged_target,
2535 Request&& staged, uint64_t ordinal, uint64_t incarnation,
2536 const CommandContext& context, ReplaceOptions options) const;
2537 InstallResult commit(MutationPlan& plan) noexcept;
2538 MutationPlan begin_plan() const;
2539 void reserve_plan(const MutationPlan& plan);
2540 void bind_plan(MutationPlan& plan) const;
2541 void seal_plan(MutationPlan& plan);
2542 PreparedMutation finish_mutation(MutationPlan plan);
2543
2544 // The direct forms' halves of a plan (the direct request-core forms). begin_direct makes
2545 // begin_plan's checks; seal_direct reserves what seal_plan would reserve
2546 // for `events` events and the live change and takes the seal's epoch;
2547 // append_direct appends one event exactly as commit does (the journal
2548 // append, the chain index for a command that consumes an incarnation,
2549 // the last ordinal); finish_direct takes commit's epoch and answers the
2550 // range. Nothing after seal_direct can throw. seal_direct also makes the
2551 // journal's room -- it may write the oldest resident events to an
2552 // external page first -- so a pointer into the journal taken before it is
2553 // read before it, as for prepare_*. `chain_segment` reserves the external
2554 // list index row of a resolution whose chain was written externally.
2555 void begin_direct() const;
2556 void seal_direct(std::size_t events, bool push_live, bool add_receipt,
2557 bool consume_incarnation, bool chain_segment = false);
2558 /// Records the external id list of a resolution event just appended by
2559 /// append_direct (room made by seal_direct). Cannot throw.
2560 void note_chain_segment(uint64_t event_ordinal, uint64_t offset, uint64_t count) noexcept;
2561 template <class Event>
2562 void append_direct(Event&& event, bool consume_incarnation);
2563 EventRange finish_direct(std::size_t first) noexcept;
2564 template <class Event>
2565 Installed end_direct(std::size_t live_index, Event&& event);
2566 // What prepare_execution computes for its events and its retained row,
2567 // as values: check_execution and apply_execution make the same checks
2568 // through it (execution_values), so what apply_execution writes is what
2569 // the token would have held.
2570 struct ExecutionValues {
2571 std::size_t live_index = 0;
2572 uint64_t ordinal = 0;
2573 double filled = 0.0;
2574 bool flatten = false;
2575 bool units_exhausted = false;
2576 RemainingProjection remaining_after = RemainingProjectionUnits{};
2577 Allowance allowance_after = AllowanceUnset{};
2578 ExecutionScope scope = execution::Book{};
2579 };
2580 Preparation<ExecutionValues> execution_values(const RequestHandle& target,
2581 const ExecutionProposal& proposal,
2582 uint64_t& next_timeline_ordinal,
2583 bool open_activation_grant);
2584
2585 // The journal, the group-effect keys and the long
2586 // chains' id lists keep a fixed resident part and write the rest to
2587 // run-owned scratch files, created on the first spill. They are defined in
2588 // native_order.cpp and held by shared_ptr so this header stays complete.
2589 struct JournalStore;
2590 struct ReceiptStore;
2591 struct ChainSegments;
2592 // E02: the working book keeps at most a fixed number of rows in live_,
2593 // sorted by priority; its newest rows beyond that wait in BookSpill (an
2594 // external ordered table of serialized rows), spilled_count() of them.
2595 // The queue is the merge of the two in priority order. A row is in
2596 // exactly one of them; a command that targets a spilled row brings it
2597 // back into live_ (classify), and a row that would not fit pushes the
2598 // newest resident rows out first (make_book_room). repriced_ names only
2599 // resident rows.
2600 // The stores' state lives in the stores (the journal's length, the keys'
2601 // base and count, the spilled rows' count) and the directory they spill
2602 // into is the successor roots' (scratch_directory), so that the core
2603 // itself keeps the size it had before the stores existed.
2604 struct BookSpill;
2605 std::size_t spilled_count() const noexcept;
2606 const std::string& scratch_directory() const noexcept;
2607 // A position in the merged queue: the next resident row and the next
2608 // spilled one (a table offset, 0 when none) with its priority.
2609 struct BookCursor {
2610 std::size_t r = 0;
2611 uint64_t at = 0;
2612 uint64_t spilled_priority = 0;
2613 };
2614 void make_book_room();
2615 bool promote_book_row(uint64_t incarnation, std::size_t* index);
2616 const LiveRequest* spilled_row(uint64_t incarnation) const;
2617 std::shared_ptr<LiveRequest> spilled_held(uint64_t incarnation) const;
2618 const LiveRequest* find_resident(const RequestHandle& handle) const noexcept;
2619 // How many times the spilled table has been rebuilt (BookSpill::compact):
2620 // a table offset taken under one generation names nothing under the next.
2621 uint64_t book_generation() const noexcept;
2622 // hold_live for a row a walk of live() stood on outside the heap, at table
2623 // offset `at` (LiveView::const_iterator::spilled_at) under `generation`:
2624 // the row is read from that entry, with no search of the table, while the
2625 // entry still holds the request spilled. Otherwise -- the table rebuilt
2626 // since, the row back in the heap or ended -- it answers hold_live(handle).
2627 std::shared_ptr<const LiveRequest> hold_scanned(const RequestHandle& handle, uint64_t at,
2628 uint64_t generation) const;
2629 void forget_repriced(uint64_t incarnation) noexcept;
2630 BookCursor book_begin() const;
2631 bool book_done(const BookCursor& cursor) const noexcept;
2632 bool book_next_spilled(const BookCursor& cursor) const noexcept;
2633 void book_step(BookCursor& cursor) const;
2634 const LiveRequest& live_back() const;
2635 /// Visits every live row in queue order until `visit` answers false. A
2636 /// spilled row is a copy that lives only for its visit.
2637 template <class Visit>
2638 void visit_book(Visit&& visit) const;
2639
2640 RunIdentity identity_;
2641 std::shared_ptr<InstanceBinding> instance_;
2642 std::vector<LiveRequest> live_;
2643 std::shared_ptr<BookSpill> book_spill_;
2644 uint64_t last_ordinal_ = 0;
2645 uint64_t last_incarnation_ = 0;
2646 uint64_t epoch_ = 0;
2647 // The journal window: how many events were retired from its front and the
2648 // ordinal of the newest retired event. The events themselves -- resident
2649 // chunks plus external pages, the ordinal lookup over both, and the
2650 // absolute length (the position the next event takes) -- are the store's.
2651 std::shared_ptr<JournalStore> journal_;
2652 std::size_t history_base_ = 0;
2653 uint64_t retired_through_ = 0;
2654 // The head of the oldest live deferred chain (kNoChainFloor: none), a
2655 // lower bound unless chain_floor_exact_ (see note_chain_birth /
2656 // note_chain_loss).
2657 static constexpr uint64_t kNoChainFloor = std::numeric_limits<uint64_t>::max();
2658 mutable uint64_t chain_floor_ = kNoChainFloor;
2659 mutable bool chain_floor_exact_ = true;
2660 std::shared_ptr<ChainSegments> chain_segments_;
2661 // Issued ranges are dense for a consumer. Successor roots spill in order;
2662 // the consumer's event fold hashes their logical rows, once each.
2663 struct SuccessorRootIndex;
2664 void reserve_successor_root();
2665 void append_successor_root(uint64_t incarnation, uint64_t root) noexcept;
2666 std::optional<uint64_t> successor_root(uint64_t incarnation) const;
2667 std::vector<std::pair<uint64_t, uint64_t>> issued_;
2668 std::shared_ptr<SuccessorRootIndex> successor_roots_;
2669 // The requests a keep_handle re-price moved in the queue, ascending by
2670 // incarnation: (incarnation, priority). The book is held by priority, so
2671 // a handle lookup reads its priority here; a handle absent here is at its
2672 // own incarnation. Derived from the book (each row's definition carries
2673 // its priority), so nothing folds it; an entry outlives its request
2674 // until the next re-price prunes it, and a lookup through it finds no
2675 // row because no other request takes that priority.
2676 std::vector<std::pair<uint64_t, uint64_t>> repriced_;
2677 // Readback-only base; total/digest stay in the consumer. Keys are in
2678 // cause/drain order, retained at [group_effect_receipt_base(),
2679 // group_effect_receipt_count()) and held by the receipt store (a resident
2680 // tail over an external log), which also keeps the base and the count.
2681 std::shared_ptr<ReceiptStore> receipt_store_;
2682 std::uint64_t next_cohort_handle_ = 1;
2683 // Only rosters with members occupy nodes; erasing an empty roster frees
2684 // its member capacity as well. next_cohort_handle_ records the complete
2685 // dense sequence of allocated handles, including every empty roster.
2686 std::map<std::uint64_t, CohortRoster> cohorts_;
2687 std::vector<CohortReceipt> cohort_receipts_;
2688 // Storage cursor only: the consumer hashes the lifetime count and every
2689 // receipt, independently of how many of their rows have been retired.
2690 std::size_t cohort_receipts_base_ = 0;
2691};
2692
2693// A non-owning read view. Iteration preserves CohortRoster references for
2694// existing typed predicates and iterator arrow reads. An empty row belongs
2695// to its input iterator and is valid until that iterator advances or dies.
2696// Indexed reads return a value, so a saved row from a temporary view remains
2697// valid. Callers that need no copies use the allocation-free iterator walk.
2698class WorkingRequestCore::CohortView {
2699public:
2701 public:
2702 using iterator_category = std::input_iterator_tag;
2704 using difference_type = std::ptrdiff_t;
2705 using pointer = const CohortRoster*;
2706 using reference = const CohortRoster&;
2707
2708 const_iterator() noexcept = default;
2709 reference operator*() const noexcept;
2710 pointer operator->() const noexcept { return &**this; }
2711 const_iterator& operator++() noexcept { ++index_; return *this; }
2713 auto previous = *this;
2714 ++*this;
2715 return previous;
2716 }
2717 friend bool operator==(const const_iterator& left, const const_iterator& right) noexcept {
2718 return left.core_ == right.core_ && left.index_ == right.index_;
2719 }
2720 friend bool operator!=(const const_iterator& left, const const_iterator& right) noexcept {
2721 return !(left == right);
2722 }
2723
2724 private:
2725 friend class CohortView;
2726 const_iterator(const WorkingRequestCore* core, std::size_t index) noexcept
2727 : core_(core), index_(index) {}
2728 const WorkingRequestCore* core_ = nullptr;
2729 std::size_t index_ = 0;
2730 mutable CohortRoster empty_{};
2731 };
2732
2733 std::size_t size() const noexcept {
2734 return static_cast<std::size_t>(core_->next_cohort_handle_ - 1);
2735 }
2736 bool empty() const noexcept { return size() == 0; }
2737 const_iterator begin() const noexcept { return const_iterator(core_, 0); }
2738 const_iterator end() const noexcept { return const_iterator(core_, size()); }
2739 CohortRoster operator[](std::size_t index) const { return *const_iterator(core_, index); }
2740
2741private:
2743 explicit CohortView(const WorkingRequestCore* core) noexcept : core_(core) {}
2744 const WorkingRequestCore* core_;
2745};
2746
2747// A non-owning read view of the command journal's retained window
2748// (WorkingRequestCore::history), with the read interface the window had as a
2749// std::vector<CommandEvent>: size, empty, front, back, indexing relative to
2750// history_base(), random-access iteration, and a conversion to a vector for
2751// a reader that wants an owning copy. The newest events are resident; older
2752// ones sit in external pages that the view reads back through a fixed page
2753// cache, so a reference it returns stays valid until the next few page loads
2754// or the next command, and a read of an old event may do I/O (and so throw).
2755// capacity() and max_size() answer one huge bound: nothing is reserved for an
2756// external window. Positions are absolute inside the iterator, so an
2757// iterator survives a retirement that leaves its event in the window.
2759public:
2761 public:
2762 using iterator_category = std::random_access_iterator_tag;
2764 using difference_type = std::ptrdiff_t;
2765 using pointer = const CommandEvent*;
2766 using reference = const CommandEvent&;
2767
2768 const_iterator() noexcept = default;
2769 reference operator*() const { return core_->history_at(position_); }
2770 pointer operator->() const { return &core_->history_at(position_); }
2771 reference operator[](difference_type offset) const { return *(*this + offset); }
2772 const_iterator& operator++() noexcept { ++position_; return *this; }
2774 const_iterator previous = *this;
2775 ++position_;
2776 return previous;
2777 }
2778 const_iterator& operator--() noexcept { --position_; return *this; }
2780 const_iterator previous = *this;
2781 --position_;
2782 return previous;
2783 }
2785 position_ = static_cast<std::size_t>(static_cast<difference_type>(position_) + offset);
2786 return *this;
2787 }
2788 const_iterator& operator-=(difference_type offset) noexcept { return *this += -offset; }
2790 iterator += offset;
2791 return iterator;
2792 }
2794 iterator += offset;
2795 return iterator;
2796 }
2798 iterator -= offset;
2799 return iterator;
2800 }
2802 const const_iterator& right) noexcept {
2803 return static_cast<difference_type>(left.position_)
2804 - static_cast<difference_type>(right.position_);
2805 }
2806 friend bool operator==(const const_iterator& left, const const_iterator& right) noexcept {
2807 return left.position_ == right.position_;
2808 }
2809 friend bool operator!=(const const_iterator& left, const const_iterator& right) noexcept {
2810 return left.position_ != right.position_;
2811 }
2812 friend bool operator<(const const_iterator& left, const const_iterator& right) noexcept {
2813 return left.position_ < right.position_;
2814 }
2815 friend bool operator>(const const_iterator& left, const const_iterator& right) noexcept {
2816 return left.position_ > right.position_;
2817 }
2818 friend bool operator<=(const const_iterator& left, const const_iterator& right) noexcept {
2819 return left.position_ <= right.position_;
2820 }
2821 friend bool operator>=(const const_iterator& left, const const_iterator& right) noexcept {
2822 return left.position_ >= right.position_;
2823 }
2824
2825 private:
2826 friend class JournalView;
2827 const_iterator(const WorkingRequestCore* core, std::size_t position) noexcept
2828 : core_(core), position_(position) {}
2829 const WorkingRequestCore* core_ = nullptr;
2830 std::size_t position_ = 0;
2831 };
2833 using size_type = std::size_t;
2834 using reference = const CommandEvent&;
2837
2838 std::size_t size() const noexcept { return core_->history_end() - core_->history_base(); }
2839 bool empty() const noexcept { return size() == 0; }
2840 std::size_t max_size() const noexcept {
2841 return static_cast<std::size_t>(std::numeric_limits<std::ptrdiff_t>::max()) / 4;
2842 }
2843 std::size_t capacity() const noexcept { return max_size(); }
2844 const CommandEvent& operator[](std::size_t index) const {
2845 return core_->history_at(core_->history_base() + index);
2846 }
2847 const CommandEvent& at(std::size_t index) const {
2848 if (index >= size()) throw std::out_of_range("native journal index is not retained");
2849 return (*this)[index];
2850 }
2851 const CommandEvent& front() const { return (*this)[0]; }
2852 const CommandEvent& back() const { return (*this)[size() - 1]; }
2853 const_iterator begin() const noexcept { return const_iterator(core_, core_->history_base()); }
2854 const_iterator end() const noexcept { return const_iterator(core_, core_->history_end()); }
2855 const_iterator cbegin() const noexcept { return begin(); }
2856 const_iterator cend() const noexcept { return end(); }
2857 operator std::vector<CommandEvent>() const { return std::vector<CommandEvent>(begin(), end()); }
2858
2859private:
2861 explicit JournalView(const WorkingRequestCore* core) noexcept : core_(core) {}
2862 const WorkingRequestCore* core_;
2863};
2864
2865// A non-owning read view of the working book in queue order
2866// (WorkingRequestCore::live), with the read interface the book had as a
2867// std::vector<LiveRequest>: size, empty, front, back, indexing by queue rank,
2868// forward iteration, and a conversion to a vector for a reader that wants an
2869// owning copy. The resident rows are read in place; the rows outside the heap
2870// (a book past its resident bound) are read back one at a time, in queue
2871// order, so an iteration holds at most one of them. A reference to a spilled
2872// row stays valid until the next few spilled rows are read or the next
2873// command. Iterating costs one external read per spilled row; indexing by rank
2874// walks to it.
2876public:
2878 public:
2879 using iterator_category = std::forward_iterator_tag;
2881 using difference_type = std::ptrdiff_t;
2882 using pointer = const LiveRequest*;
2883 using reference = const LiveRequest&;
2884
2885 const_iterator() noexcept = default;
2886 reference operator*() const;
2887 pointer operator->() const { return &**this; }
2888 /// The row the iterator stands on, held as hold_live holds it: a
2889 /// spilled row stays valid as long as the answer does, however far
2890 /// the iterator or any other reader of the book moves on.
2891 std::shared_ptr<const LiveRequest> hold() const;
2894 const_iterator previous = *this;
2895 ++*this;
2896 return previous;
2897 }
2898 friend bool operator==(const const_iterator& left, const const_iterator& right) noexcept {
2899 return left.core_ == right.core_ && left.cursor_.r == right.cursor_.r
2900 && left.cursor_.at == right.cursor_.at;
2901 }
2902 friend bool operator!=(const const_iterator& left, const const_iterator& right) noexcept {
2903 return !(left == right);
2904 }
2905
2906 private:
2907 friend class LiveView;
2908 friend class ::pineforge::engine_script_run_v20::NativeExecutionConsumer;
2909 const_iterator(const WorkingRequestCore* core, BookCursor cursor) noexcept
2910 : core_(core), cursor_(cursor) {}
2911 // The spilled table's offset of the row the iterator stands on, 0
2912 // when that row is resident (WorkingRequestCore::hold_scanned).
2913 uint64_t spilled_at() const noexcept { return held_ ? cursor_.at : 0; }
2914 // Loads the spilled row the cursor stands on, if it does.
2915 void settle();
2916 const WorkingRequestCore* core_ = nullptr;
2917 BookCursor cursor_{};
2918 std::shared_ptr<LiveRequest> held_;
2919 };
2921 using size_type = std::size_t;
2922 using reference = const LiveRequest&;
2925
2926 std::size_t size() const noexcept { return core_->live_size(); }
2927 bool empty() const noexcept { return size() == 0; }
2928 const LiveRequest& operator[](std::size_t rank) const { return core_->live_at(rank); }
2929 const LiveRequest& at(std::size_t rank) const { return core_->live_at(rank); }
2930 const LiveRequest& front() const { return core_->live_at(0); }
2931 const LiveRequest& back() const { return core_->live_back(); }
2934 const_iterator cbegin() const { return begin(); }
2935 const_iterator cend() const { return end(); }
2936 operator std::vector<LiveRequest>() const;
2937
2938private:
2940 explicit LiveView(const WorkingRequestCore* core) noexcept : core_(core) {}
2941 const WorkingRequestCore* core_;
2942};
2943
2945public:
2946 PreparedSubmit() noexcept;
2948 PreparedSubmit& operator=(PreparedSubmit&&) noexcept;
2951 PreparedSubmit& operator=(const PreparedSubmit&) = delete;
2952 explicit operator bool() const noexcept;
2953 const SubmitResult& predicted() const;
2955
2956private:
2958 struct Impl;
2959 std::unique_ptr<Impl> impl_;
2960 explicit PreparedSubmit(std::unique_ptr<Impl> impl) noexcept;
2961};
2962
2964public:
2967 PreparedReplace& operator=(PreparedReplace&&) noexcept;
2970 PreparedReplace& operator=(const PreparedReplace&) = delete;
2971 explicit operator bool() const noexcept;
2972 const ReplaceResult& predicted() const;
2974
2975private:
2977 struct Impl;
2978 std::unique_ptr<Impl> impl_;
2979 explicit PreparedReplace(std::unique_ptr<Impl> impl) noexcept;
2980};
2981
2983public:
2984 PreparedCancel() noexcept;
2986 PreparedCancel& operator=(PreparedCancel&&) noexcept;
2989 PreparedCancel& operator=(const PreparedCancel&) = delete;
2990 explicit operator bool() const noexcept;
2991 const CancelResult& predicted() const;
2993
2994private:
2996 struct Impl;
2997 std::unique_ptr<Impl> impl_;
2998 explicit PreparedCancel(std::unique_ptr<Impl> impl) noexcept;
2999};
3000
3002public:
3005 PreparedMutation& operator=(PreparedMutation&&) noexcept;
3008 PreparedMutation& operator=(const PreparedMutation&) = delete;
3009 explicit operator bool() const noexcept;
3010
3011private:
3013 struct Impl;
3014 std::unique_ptr<Impl> impl_;
3015 explicit PreparedMutation(std::unique_ptr<Impl> impl) noexcept;
3016};
3017
3019public:
3022 PreparedExecution& operator=(PreparedExecution&&) noexcept;
3025 PreparedExecution& operator=(const PreparedExecution&) = delete;
3026 explicit operator bool() const noexcept;
3027
3028private:
3030 struct Impl;
3031 std::unique_ptr<Impl> impl_;
3032 explicit PreparedExecution(std::unique_ptr<Impl> impl) noexcept;
3033};
3034
3035static_assert(std::is_nothrow_move_constructible_v<Request>);
3036static_assert(std::is_nothrow_move_assignable_v<Request>);
3037static_assert(std::is_nothrow_move_constructible_v<LiveRequest>);
3038static_assert(std::is_nothrow_move_assignable_v<LiveRequest>);
3039static_assert(std::is_nothrow_move_constructible_v<CommandEvent>);
3040static_assert(std::is_nothrow_move_assignable_v<CommandEvent>);
3041static_assert(std::is_nothrow_move_constructible_v<SubmitResult>);
3042static_assert(std::is_nothrow_move_constructible_v<ReplaceResult>);
3043static_assert(std::is_nothrow_move_constructible_v<CancelResult>);
3044static_assert(std::is_nothrow_move_constructible_v<NoEffectEvent>);
3045static_assert(std::is_nothrow_move_constructible_v<MatchRejectedEvent>);
3046static_assert(std::is_nothrow_move_constructible_v<ExecutionAppliedEvent>);
3047static_assert(std::is_nothrow_move_constructible_v<MatchCursor>);
3048static_assert(std::variant_size_v<OrderIntent> == 6);
3049static_assert(std::variant_size_v<ReductionSize> == 3);
3050static_assert(std::variant_size_v<SizeBasis> == 2);
3051static_assert(std::variant_size_v<Remaining> == 5);
3052static_assert(std::variant_size_v<RemainingProjection> == 5);
3053static_assert(std::variant_size_v<Allowance> == 4);
3054static_assert(std::variant_size_v<CommandEvent> == 19);
3055static_assert(std::variant_size_v<ExecutionPlan> == 4);
3056static_assert(std::variant_size_v<ExecutionScope> == 3);
3057static_assert(std::variant_size_v<TriggerState> == 9);
3058static_assert(std::variant_size_v<TriggerAnchor> == 2);
3059
3060} // inline namespace native_order_v8
3061} // namespace pineforge::native_order
3062
3063namespace std {
3064template <>
3065struct hash<pineforge::native_order::CohortHandle> {
3066 std::size_t operator()(pineforge::native_order::CohortHandle value) const noexcept {
3067 return static_cast<std::size_t>(value.value ^ (value.value >> 32));
3068 }
3069};
3070} // namespace std
All reads borrow store/version; no owning row or text reconstruction.
ScratchHandle version() const noexcept
PayloadReadback() noexcept=default
ScratchStatus find(std::string_view name, PayloadField &out) const noexcept
Almost every prepared command yields one history event.
CommandEvent & operator[](std::size_t index) noexcept
const CommandEvent & operator[](std::size_t index) const noexcept
InlineCommandEvents & operator=(InlineCommandEvents &&other) noexcept
InlineCommandEvents & operator=(const InlineCommandEvents &other)
detail::ScratchStatus read(std::uint64_t, detail::ScratchBuffer) const noexcept override
detail::ScratchStatus opening_at(std::uint64_t index, RequestHandle &out) const noexcept override
OpeningInputCursor(std::uint64_t count, void *context, Reader reader) noexcept
detail::ScratchStatus(*)(void *, std::uint64_t, RequestHandle &) Reader
Borrowed through the enclosing command/evaluation and any prepare/install pair that copies its Target...
virtual detail::ScratchStatus observation_at(std::uint64_t index, OpeningObservation &out) const noexcept=0
Immutable membership, independent of a source scratch scope.
virtual detail::ScratchStatus opening_at(std::uint64_t index, RequestHandle &out) const noexcept=0
virtual std::uint64_t count() const noexcept=0
RequestReadback(detail::PayloadReadback view) noexcept
detail::ScratchStatus comment(detail::PayloadField &out) const noexcept
detail::ScratchStatus label(detail::PayloadField &out) const noexcept
friend bool operator==(const const_iterator &left, const const_iterator &right) noexcept
friend bool operator!=(const const_iterator &left, const const_iterator &right) noexcept
friend bool operator==(const const_iterator &left, const const_iterator &right) noexcept
friend bool operator>(const const_iterator &left, const const_iterator &right) noexcept
friend const_iterator operator+(const_iterator iterator, difference_type offset) noexcept
friend bool operator>=(const const_iterator &left, const const_iterator &right) noexcept
friend const_iterator operator+(difference_type offset, const_iterator iterator) noexcept
friend bool operator<(const const_iterator &left, const const_iterator &right) noexcept
friend bool operator!=(const const_iterator &left, const const_iterator &right) noexcept
friend difference_type operator-(const const_iterator &left, const const_iterator &right) noexcept
friend bool operator<=(const const_iterator &left, const const_iterator &right) noexcept
friend const_iterator operator-(const_iterator iterator, difference_type offset) noexcept
friend bool operator==(const const_iterator &left, const const_iterator &right) noexcept
std::shared_ptr< const LiveRequest > hold() const
The row the iterator stands on, held as hold_live holds it: a spilled row stays valid as long as the ...
friend bool operator!=(const const_iterator &left, const const_iterator &right) noexcept
const CommandEvent & history_at(std::size_t absolute) const
The event at an absolute journal position; std::out_of_range when it is not retained.
Preparation< Installed > apply_margin_call(const MarginCallEvent &event, uint64_t &next_timeline_ordinal)
bool refresh_allowance(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal, bool open_activation_grant)
The timeline-handed refresh under the open activation grant.
const LiveRequest * find_live(const RequestHandle &handle) const
void waiting_children(const RequestHandle &parent, std::vector< RequestHandle > &out) const
Preparation< PreparedMutation > prepare_evaluation(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal)
LiveView live() const noexcept
The working book, in queue order: ascending LiveRequest::priority, which is incarnation order until a...
Preparation< PreparedExecution > prepare_execution(const RequestHandle &target, const ExecutionProposal &proposal, uint64_t &next_timeline_ordinal)
const CommandEvent * event_at(const EventId &id) const
void visit_group_recipients(const EventId &, void *, HandleVisitor) const
Synchronous queue-order reads.
void set_successor_roots_directory(const std::string &directory)
uint64_t retired_through() const noexcept
The ordinal of the newest retired event, 0 while none is.
void visit_bound_close_handles(void *, HandleVisitor) const
std::size_t prior_adjustment_count(const EventId &resolution) const
How many prior adjustment ids that event spent (0 when none or unknown).
Preparation< std::monostate > check_execution(const RequestHandle &target, const ExecutionProposal &proposal, uint64_t &next_timeline_ordinal)
CommandInstalled< ReplaceResult > apply_replace(const RequestHandle &target, const Request &request, const CommandContext &context, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, ReplaceOptions options={})
Preparation< Installed > apply_trigger(const RequestHandle &target, const TriggerTransition &transition, DriverEligibilityClass driver_class, uint64_t &next_timeline_ordinal, std::optional< Side > cohort_side=std::nullopt, const ActivationGrid &grid={})
friend class ::pineforge::engine_script_run_v20::NativeExecutionConsumer
bool refresh_allowance(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation)
PreparedReplace prepare_replace(const RequestHandle &target, const Request &request, const CommandContext &context, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, ReplaceOptions options={})
Preparation< Installed > apply_parent_terminal(const EventId &terminal_or_replaced, const RequestHandle &child, uint64_t &next_timeline_ordinal)
InstallResult install_execution(PreparedExecution &&prepared, const CommittedExecutionFacts &facts) noexcept
std::size_t live_size() const noexcept
How many requests are live, resident or not, in O(1); and the request at queue rank rank (std::out_of...
CommandInstalled< CancelResult > apply_cancel(const RequestHandle &target, uint64_t &next_timeline_ordinal, CancelReason reason=CancelReason::User)
CohortView cohorts() const noexcept
Logical roster sequence in allocation order, including empty rosters whose members have been removed.
std::size_t retire_cohort_receipts(std::size_t through) noexcept
Drop the prefix strictly below the absolute position through, clamped to the window.
bool evaluation_eligible(const LiveRequest &live, const EvaluationContext &context) const noexcept
Preparation< Installed > apply_risk_event(const NativeRiskEvent &event, uint64_t &next_timeline_ordinal)
friend detail::ScratchStatus append_journal_record(WorkingRequestCore &, const detail::ScratchByteSource &) noexcept
Preparation< PreparedMutation > prepare_risk_event(const NativeRiskEvent &event, uint64_t &next_timeline_ordinal)
Append one NativeRiskEvent to the immutable history.
Preparation< PreparedMutation > prepare_terms(const RequestHandle &target, const EvaluationContext &context, const TermsResolvedInput &input, uint64_t &next_timeline_ordinal)
bool cohort_contains(CohortHandle cohort, const RequestHandle &opening) const
bool book_spilled() const noexcept
Whether some live rows are outside the heap: live_at and live_back then walk the book in queue order ...
static constexpr std::size_t kInlineAdjustmentIds
The longest chain whose ids an event still carries inline.
bool working_is_buy(const LiveRequest &live, std::optional< Side > cohort_side=std::nullopt) const noexcept
std::size_t group_effect_receipt_base() const noexcept
Group-effect keys in cause/commit order, retaining [base, count).
EligibilityFacts eligibility_facts(const LiveRequest &live, const EvaluationContext &context, bool open_activation_grant) const noexcept
eligibility_facts and evaluation_eligible under the open activation grant of prepare_evaluation: the ...
bool refresh_allowance(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal)
The same refresh, handed the run's timeline: an unbound close that carried a binding the book still h...
const std::vector< std::pair< uint64_t, uint64_t > > & issued_incarnations() const noexcept
The incarnations this run issued – every accepted request and every replace successor – as disjoint a...
Preparation< PreparedMutation > prepare_evaluation(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal, bool open_activation_grant)
The same preparation under an explicit open activation grant: the caller vouches, for this handle alo...
std::vector< RequestHandle > waiting_children(const RequestHandle &parent) const
InstalledCommand< SubmitResult > install_submit(PreparedSubmit &&prepared) noexcept
InstallResult apply_execution(const RequestHandle &target, const ExecutionProposal &proposal, const CommittedExecutionFacts &facts, uint64_t &next_timeline_ordinal, bool open_activation_grant)
std::size_t retire_history(uint64_t through_ordinal)
Retire the journal prefix whose ordinals are <= through_ordinal.
void cohort_add(CohortHandle cohort, RequestHandle origin)
void visit_waiting_children(const RequestHandle &, void *, HandleVisitor) const
std::size_t history_base() const noexcept
How many events have been retired from the front of the journal.
Preparation< PreparedMutation > prepare_match_rejected(const RequestHandle &target, const EvaluationContext &context, MatchRejectReason reason, uint64_t &next_timeline_ordinal)
bool refresh_allowance(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, bool open_activation_grant)
refresh_allowance under the open activation grant of prepare_evaluation.
void refresh_point_allowances(uint64_t point, const PositionIdentity &position)
Consumer-only no-event form of the ordinary allowance refresh.
bool carry_book_read_failure(std::exception_ptr failure) const noexcept
A read of the book's spilled rows that failed where its reader cannot throw (a noexcept fold of the b...
bool refresh_cohort_allowance(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation)
const LiveRequest & live_at(std::size_t rank) const
JournalView history() const noexcept
The command journal's retained window, oldest first.
friend const detail::ScratchReadback & journal_readback(const WorkingRequestCore &)
Preparation< PreparedMutation > prepare_match_rejected(const RequestHandle &target, const EvaluationContext &context, MatchRejectReason reason, std::optional< ExecutionTerms > attempted_terms, uint64_t &next_timeline_ordinal)
InstalledCommand< CancelResult > install_cancel(PreparedCancel &&prepared) noexcept
PreparedSubmit prepare_submit(const Request &request, const CommandContext &context, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, RequestOrigin origin=RequestOrigin::Host)
Consumer-only typed prepare/install.
Preparation< PreparedMutation > prepare_no_effect(const RequestHandle &target, const EvaluationContext &context, uint64_t &next_timeline_ordinal)
friend void bind_journal_codec(WorkingRequestCore &, detail::CommandScratchStore &)
InstalledCommand< ReplaceResult > install_replace(PreparedReplace &&prepared) noexcept
Preparation< PreparedMutation > prepare_bound_expiry(const EventId &physical_cause, const RequestHandle &child, const TargetObservation &observation, uint64_t &next_timeline_ordinal)
Preparation< Installed > apply_owner_applied(const EventId &applied, const RequestHandle &child, const std::optional< OpeningObservation > &observation, uint64_t &next_timeline_ordinal, const ArmContext &arm={})
WorkingRequestCore(RunIdentity identity, TailWindowConfig windows)
WorkingRequestCore(RunIdentity identity, CacheCapacity successor_roots, CacheCapacity receipts, CacheCapacity chain_segments)
Inclusive committed-row read-cache allowances.
Preparation< Installed > apply_evaluation(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal, bool open_activation_grant)
apply_evaluation under the open activation grant of prepare_evaluation.
ReplaceResult replace(const RequestHandle &target, const Request &request, int64_t decision_time_ms, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, std::optional< double > quantity_grid=std::nullopt, ReplaceOptions options={})
Preparation< Installed > apply_match_rejected(const RequestHandle &target, const EvaluationContext &context, MatchRejectReason reason, std::optional< ExecutionTerms > attempted_terms, uint64_t &next_timeline_ordinal)
void bound_close_handles(std::vector< RequestHandle > &out) const
Preparation< std::monostate > check_execution(const RequestHandle &target, const ExecutionProposal &proposal, uint64_t &next_timeline_ordinal, bool open_activation_grant)
check_execution and apply_execution under the open activation grant of prepare_evaluation.
GroupEffectReceipt group_effect_receipt(std::size_t index) const
Preparation< PreparedMutation > prepare_group_effect(const EventId &applied, const RequestHandle &recipient, uint64_t &next_timeline_ordinal)
detail::ReadThroughCacheStats tail_read_cache_stats(TailWindowKind kind) const noexcept
std::vector< RequestHandle > group_recipients(const EventId &applied) const
The live siblings an applied member's fill reaches – its group, another cohort, born before the fill;...
SubmitResult submit(const Request &request, int64_t decision_time_ms, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, std::optional< double > quantity_grid=std::nullopt)
R1 producer convenience: prepare then install one command.
Preparation< Installed > apply_group_effect(const EventId &applied, const RequestHandle &recipient, uint64_t &next_timeline_ordinal)
bool trigger_permits_driver(const Trigger &trigger, const TriggerState &state, DriverEligibilityClass driver_class, bool existing_matching_bit) const noexcept
bool has_waiting_children(const RequestHandle &parent) const
Preparation< PreparedMutation > prepare_trigger(const RequestHandle &target, const TriggerTransition &transition, DriverEligibilityClass driver_class, uint64_t &next_timeline_ordinal, std::optional< Side > cohort_side, const ActivationGrid &grid, bool open_activation_grant)
prepare_trigger under the open activation grant of prepare_evaluation: the cursor test every trigger ...
InstallResult install_mutation(PreparedMutation &&prepared) noexcept
static Allowance evaluated_allowance(const LiveRequest &live, uint64_t point) noexcept
The allowance that prepare_evaluation would install for this point.
InstallResult apply_execution(const RequestHandle &target, const ExecutionProposal &proposal, const CommittedExecutionFacts &facts, uint64_t &next_timeline_ordinal)
Preparation< Installed > apply_no_effect(const RequestHandle &target, const EvaluationContext &context, uint64_t &next_timeline_ordinal)
Preparation< PreparedMutation > prepare_owner_applied(const EventId &applied, const RequestHandle &child, const std::optional< OpeningObservation > &observation, uint64_t &next_timeline_ordinal, const ArmContext &arm={})
arm carries the materialization facts of an anchored leg (the price tick a rounded anchor snaps to,...
std::size_t cohort_receipt_count() const noexcept
Every cohort receipt this run committed, retained or not.
Preparation< PreparedMutation > prepare_trigger(const RequestHandle &target, const TriggerTransition &transition, DriverEligibilityClass driver_class, uint64_t &next_timeline_ordinal, std::optional< Side > cohort_side=std::nullopt, const ActivationGrid &grid={})
grid carries the run's activation grid (ActivationGrid); the default is the raw rule,...
CancelResult cancel(const RequestHandle &target, uint64_t &next_timeline_ordinal)
Preparation< PreparedMutation > prepare_parent_terminal(const EventId &terminal_or_replaced, const RequestHandle &child, uint64_t &next_timeline_ordinal)
std::size_t history_end() const noexcept
The absolute journal length: every event this run has committed, retained or not.
const std::map< std::uint64_t, CohortRoster > & nonempty_cohorts() const noexcept
Borrow the rosters holding origins, in ascending cohort-handle order.
uint64_t last_ordinal() const noexcept
The core's two durable counters: the ordinal of the last committed event and the last incarnation a c...
std::shared_ptr< const LiveRequest > hold_live(const RequestHandle &handle) const
find_live for a reader that keeps the row across further reads of the book.
const std::vector< CohortReceipt > & cohort_receipts() const noexcept
The retained cohort-receipt window, in command order.
Preparation< PreparedExecution > prepare_execution(const RequestHandle &target, const ExecutionProposal &proposal, uint64_t &next_timeline_ordinal, bool open_activation_grant)
prepare_execution under the open activation grant of prepare_evaluation.
void cohort_remove(CohortHandle cohort, RequestHandle origin)
Preparation< PreparedMutation > prepare_margin_call(const MarginCallEvent &event, uint64_t &next_timeline_ordinal)
Append one MarginCallEvent to the immutable history.
static bool effective_host_units(const PendingAdjustments &pending, double resolved_units, double *deduction, double *after, bool *exhausted) noexcept
Pure arithmetic over the cached pending total.
Preparation< Installed > apply_terms(const RequestHandle &target, const EvaluationContext &context, const TermsResolvedInput &input, uint64_t &next_timeline_ordinal)
void bind_scratch_slab(const std::shared_ptr<::pineforge::detail::ScratchSlab > &slab)
bool visit_prior_adjustments(const EventId &resolution, const std::function< bool(const EventId &)> &visit) const
The prior adjustment ids a TermsResolvedEvent or QuantityBoundEvent spent, in chain order,...
bool evaluation_eligible(const LiveRequest &live, const EvaluationContext &context, bool open_activation_grant) const noexcept
void rethrow_book_read_failure() const
Throws the failure carry_book_read_failure kept, if any.
PreparedCancel prepare_cancel(const RequestHandle &target, uint64_t &next_timeline_ordinal, CancelReason reason=CancelReason::User)
reason lets the kernel withdraw its own request under the durable Superseded receipt.
EligibilityFacts eligibility_facts(const LiveRequest &live, const EvaluationContext &context) const noexcept
void group_recipients(const EventId &applied, std::vector< RequestHandle > &out) const
The same three lists written over out, whose capacity a caller that keeps it reuses (the direct reque...
Preparation< Installed > apply_evaluation(const RequestHandle &target, const EvaluationContext &context, const TargetObservation &observation, uint64_t &next_timeline_ordinal)
Preparation< Installed > apply_trigger(const RequestHandle &target, const TriggerTransition &transition, DriverEligibilityClass driver_class, uint64_t &next_timeline_ordinal, std::optional< Side > cohort_side, const ActivationGrid &grid, bool open_activation_grant)
apply_trigger under the open activation grant of prepare_evaluation; every parameter is explicit,...
CommandInstalled< SubmitResult > apply_submit(const Request &request, const CommandContext &context, uint64_t &next_order_incarnation, uint64_t &next_timeline_ordinal, RequestOrigin origin=RequestOrigin::Host)
The direct forms (the direct request-core forms).
CohortHandle cohort_open()
Command-boundary roster maintenance.
Preparation< Installed > apply_bound_expiry(const EventId &physical_cause, const RequestHandle &child, const TargetObservation &observation, uint64_t &next_timeline_ordinal)
std::vector< RequestHandle > bound_close_handles() const
std::variant< Flatten, order_action::Reduce, order_action::Transact > Action
Definition execution.hpp:17
std::variant< ExplicitUnits, OwnerOpenedUnits, ScopeFraction > ReductionSize
How much a Reduce takes: literal units, the owner's own opening, or a fraction of the bound scope.
std::variant< T, NoChange, PreparationError > Preparation
std::variant< PendingNone, PendingDeferred > PendingAdjustments
bool quantity_on_grid(double q, double step) noexcept
v2 exact binary64 grid: r=abs(q)/s, n=round(r) half away from zero, g=n*s.
Request market_request(execution::Action action, std::string label={}, std::string comment={})
bool has_market_defaults(const Request &request) noexcept
std::variant< Independent, WaitForApplied, BindOpening, BindOpenings, BindCohort > Owner
detail::ScratchStatus decode_journal_record(const detail::ScratchByteSource &, detail::ScratchBuffer, CommandEvent &)
std::variant< CashValue, EquityFraction > SizeBasis
The two bases a Sized intent may name.
std::variant< AcceptedEvent, RejectedEvent, ReplacedEvent, ReplaceRejectedEvent, CancelledEvent, NotWorkingEvent, InvalidHandleEvent, NoEffectEvent, MatchRejectedEvent, ExecutionAppliedEvent, CloseBoundEvent, ActivatedEvent, ReservationReducedEvent, DeferredGroupAdjustmentEvent, QuantityBoundEvent, ArmedEvent, TermsResolvedEvent, MarginCallEvent, NativeRiskEvent > CommandEvent
Every alternative of one request's history, as one value.
MatchRejectReason
Why a CANDIDATE was refused at a matching point.
@ UnrepresentableQuantity
The settlement's inspection answered execution::Status:: UnrepresentableQuantity: a close split binar...
@ RiskLimit
An opening refused while the run spec's generic risk limits are blocking (L9).
std::uint64_t opening_observation_count(const TargetObservation &) noexcept
ScopeClaim
Gross claims the whole bound scope; NetOfSiblings first subtracts the units already claimed by the li...
NativeArmVisibility
Whether an owner-related request that arms is a working order before its arm.
std::variant< Market, Limit, Stop, StopLimit, Trail > Trigger
SizePrice
WHICH price the basis is converted at.
std::shared_ptr< const RequestDefinition > DefinitionRef
std::variant< Flatten, Reduce, Transact, ReverseTo, HostSized, Sized > OrderIntent
What a request does.
detail::ScratchStatus freeze_openings(const RunIdentity &, const OpeningReadback &, std::shared_ptr< const OpeningReadback > &out)
Freeze and canonicalize a same-run, unique stream.
SizeTime
AtMatch resolves the basis at the matching candidate; AtAcceptance freezes the resolved units when th...
std::function< std::optional< double >( const LiveRequest &leg, const ExecutionAppliedEvent &owner_fill, Side leg_side, double offset, double kernel_level)> AnchoredLevelResolver
The one policy point of an anchored materialization.
ExecutionGridPolicy
Resolved host sizing normally remains subject to the run's quantity grid.
std::variant< NoGroup, Member > Group
bool operator==(CohortHandle left, CohortHandle right) noexcept
std::variant< PositionFlat, PositionNonflat > PositionIdentity
std::variant< CommandInstalled< R >, InstallError > InstalledCommand
std::variant< Absolute, FromOwnerFill > TriggerAnchor
RiskLimitKind
Which generic risk limit of NativeRunSpec::risk a NativeRiskEvent reports.
std::variant< ImmediateRemaining, PointBudget > Capacity
bool operator<(CohortHandle left, CohortHandle right) noexcept
RequestRejectReason
Why an acceptance was refused, on a Rejected SubmitResult.
@ PlacementAdmission
A Sized{SizeTime::AtAcceptance} whose acceptance-resolved quantity does not pass the run's opening ad...
std::uint64_t opening_count(const std::vector< RequestHandle > &, const OpeningReadback *) noexcept
JournalRecordSource encode_journal_record(const CommandEvent &) noexcept
ScopeBasis
Which measurement of the bound scope the fraction is taken of.
detail::ScratchStatus opening_observation_at(const TargetObservation &, std::uint64_t, OpeningObservation &) noexcept
void(*)(void *, const RequestHandle &) HandleVisitor
NativeAnchorRounding
How a materialized anchored level is snapped onto the run's price tick ladder (NativeRunSpec::price_t...
bool point_eligible(const LiveRequest &live, uint64_t point_ordinal, int64_t effective_time_ms) noexcept
Whether a request may be considered at this driver point: the point's ordinal must be strictly past t...
std::variant< AllowanceUnset, AllowanceUnits, AllowanceAllScope, AllowanceDeferred > Allowance
std::variant< BeginTrailTracking, ObserveTrailExtremum, ActivateStop, ActivateStopLimit, ActivateTrail > TriggerTransition
CancelReason
Why a request left the book.
@ Superseded
A kernel-originated request the kernel itself withdrew: the liquidation level or its units moved,...
@ UnrepresentableReservation
A group deduction's pending total that overflows binary64 (or a non-finite reservation,...
std::variant< execution::Book, execution::OpeningExposure, SelectedExposure > ExecutionScope
What one execution acted on: the whole book, one opening's exposure, or a bound selection.
NativeArmFirstMatch
When an armed request may first match.
std::variant< BookTransaction, Wait, ArmedTransaction, UnboundBookClose, BookClose, OpeningClose, OpeningsClose, CohortClose > Authority
std::variant< Installed, InstallError > InstallResult
ExecutionPlan to_execution_plan(const execution::Action &action)
detail::ScratchStatus append_journal_record(WorkingRequestCore &, const detail::ScratchByteSource &) noexcept
OrderIntent intent_from_physical(const execution::Action &action)
NativeCandidatePriceKind
Which price a candidate is being offered at: the point's own price, or the request's trigger level (a...
const detail::ScratchReadback & journal_readback(const WorkingRequestCore &)
std::variant< RemainingUnbound, RemainingFlattenAll, RemainingUnits, RemainingDeferred, NoTarget > Remaining
std::variant< RemainingProjectionUnbound, RemainingProjectionFlattenAll, RemainingProjectionUnits, RemainingProjectionDeferred, RemainingProjectionNoTarget > RemainingProjection
bool operator!=(CohortHandle left, CohortHandle right) noexcept
std::variant< execution::Flatten, order_action::Reduce, order_action::Transact, execution::ReverseTo > ExecutionPlan
The settlement shape one execution takes.
NativeArmScope
What an armed CLOSING request (Reduce, Flatten, a HostSized close) closes.
std::uint8_t display_waypoint(const MatchCursor &cursor) noexcept
void bind_journal_codec(WorkingRequestCore &, detail::CommandScratchStore &)
std::variant< EnrollmentFromCommand, EnrollmentFromApplied > Enrollment
std::variant< MarketReady, LimitReady, StopIdle, StopActive, StopLimitPending, StopLimitLive, TrailWaitArm, TrailTrack, TrailActive > TriggerState
order_action::Transact Transact
A signed book transaction: finite nonzero units on the side their sign names.
int r(int64_t c)
Definition color.hpp:42
Everything the kernel knows about WHERE a point is, as one owning value: its event ordinal,...
Where a request's trigger level comes from.
One request was accepted: the definition, and the timeline ordinal it was accepted at.
const RequestHandle & handle() const noexcept
What the consumer hands to prepare_trigger under an opted-in price grid (NativeRunSpec::price_grid ==...
What the consumer hands to prepare_owner_applied for an anchored leg's materialization,...
An anchored leg was materialized, exactly once: fill + offset, then the rounding, then resolve_anchor...
Bind to a host-built roster (cohort_open / cohort_add / cohort_remove), read at the match rather than...
Bind to ONE already-live opening, by its handle and the positive cycle it is live in.
Bind to a fixed cohort of already-live openings.
std::shared_ptr< const OpeningReadback > external_openings
The authority an armed leg acquires under NativeArmScope::Book: the whole position its owner's fill l...
Native sizing bases (L3).
A host-maintained, run-scoped roster identity.
std::vector< RequestHandle > origins
Canonical origin handles, ordered by origin incarnation rather than by host insertion order.
What acceptance needs from the run to judge a command: the point's ordinal and coordinate,...
bool units_are_scope_boundary
A Reduce's ExplicitUnits are a FIFO boundary of its scope: the binary64 sum, in book order,...
std::optional< double > sizing_scope
Placement-time sizing measurements the execution consumer owns, supplied only for the intent that ask...
std::optional< double > sizing_units
Kernel-resolved acceptance-time units for a Sized{AtAcceptance} request.
std::optional< double > price_tick
The run's price tick, needed only to resolve a tick-spelled trail offset or trigger anchor.
A group effect's deduction deferred into a sibling whose units are not known yet: its pending total g...
A fraction in (0, 1] of MARKED equity at the sizing point.
std::optional< Side > cohort_side
Set only while resolving a CohortClose candidate.
bool pre_open_birth_eligible
Generic current-point delivery.
Where an install put its events: first_index is an ABSOLUTE journal position – the count of events th...
One committed execution: the definition, the cursor, the resolved price and units,...
bool pre_open_birth_eligible
Carries the generic pre-open delivery authorization from the matching evaluation through the synchron...
A reduction of exactly these units, finite and positive.
Defer a level to the owner's fill: at the arm the level becomes fill + offset, with offset signed (ad...
One group-effect receipt: the applied event that caused it, the request it acted on,...
An opening or a closing whose quantity the HOST resolves, in NativeStrategyHost::resolve_execution_te...
Take whatever the point allows, with no per-point cap.
No owner: the request stands on its own book authority.
fill_through makes the limit a touch trigger (market-if-touched): the level still gates when the requ...
const std::optional< RequestHandle > & predecessor() const noexcept
uint64_t priority() const noexcept
Its place in the queue (RequestDefinition::priority): the handle's incarnation, unless a re-price kep...
std::optional< double > sizing_units
Placement-time sizing measurements frozen from the accepting command context.
const RequestHandle & handle() const noexcept
A kernel-issued liquidation that actually filled.
Match at the next eligible matching point, with no level to reach.
A candidate the run refused, with its MatchRejectReason and the cursor it was refused at.
A reduction or a flatten that had nothing to close: terminal, with no execution identity,...
const RequestHandle & handle() const noexcept
A dynamic cohort has no currently live member.
std::shared_ptr< const OpeningReadback > external_openings
A reduction of exactly what the owner's fill opened, bound at the arm and reported by a QuantityBound...
Cap what this request may execute at ONE matching point at units.
A working-request preparation that could not produce a representable instruction — a nonrepresentable...
A Reduce{OwnerOpenedUnits} was bound, at its owner's fill, to what that fill opened.
A resolved remaining quantity, in units.
Replacement behaviour that is not expressible in the successor request.
bool kept_handle() const noexcept
Whether the replace kept the handle (ReplaceOptions::keep_handle): the successor definition is the sa...
const RequestHandle & successor() const noexcept
const RequestHandle & predecessor() const noexcept
The immutable accepted form of one request: its handle, the request value as accepted,...
uint64_t priority
Where the definition stands in the run's queue: the number of the run's request sequence the command ...
std::optional< RequestHandle > root
The first handle of this request's replace chain – the request the chain began with,...
RequestOrigin origin
Appended last so every existing aggregate initializer keeps its meaning.
std::optional< PositionNonflat > kept_binding
The book binding a replace carried forward (ReplaceOptions:: keep_binding): the cycle and side of the...
Aggregate field order keeps market construction: Request{Transact{1.0}, "buy", "comment"}...
A group effect's deduction from a live sibling whose remaining units are known (GroupEffect::Reduce):...
Exact target exposure for an explicit reversal request.
A fraction in (0, 1] of the bound scope, resolved at the matching candidate.
ScopeBasis basis
Appended last so every existing aggregate initializer keeps its meaning.
Native authorization receipt, converted to a call-local financial SelectedOpeningSet only at the cons...
SizePrice price
Which price the basis converts at; see SizePrice.
ExecutionGridPolicy grid_policy
SnapToGrid floors the resolved units onto the run's quantity grid.
bool reserve_percent_fee
Divide the sizing cash by (1 + fee) when the run's fee kind is NativeFeeKind::Percent.
A stop that, once reached, becomes a limit at limit.
A stop trigger: the modeled path has to reach price from the adverse side.
Payload row policy for pending batches and committed-row read caches.
Storage observations only, excluded from continuation state.
A triggered trail; activation_ordinal carries TrailTrack's.
Trail offset spelled in price ticks instead of a price distance.
offset is a price distance: the stop rides offset behind the running best.
The one owner relation that arms (the ArmedEvent).
The authority an unarmed WaitForApplied child holds: accepted and live, but never a matching candidat...
How many spilled rows the book has read back to answer a lookup – by handle (find_live,...
std::size_t operator()(pineforge::native_order::CohortHandle value) const noexcept