|
PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
|
pineforge-engine main @ 73817c1 (branch design/native-feature-parity, since merged) and kept on main since; each row records what landed after it.NativeStrategyHost (no PineScript, no codegen, no src/source, no src/compat/pine) and gets every execution feature Pine strategies rely on. The Pine adapter becomes an optional, thin TradingView-parity layer.path:line on this tree: scripts/check_doc_anchors.py re-verifies every one on each preflight (they were first taken at 73817c1, each copied from an input or read fresh for this merge, marked fresh). Unique basenames are used; a path appears only where the basename is ambiguous. The ADR L<n> citations of §2.vi are lines of ADR-0001's draft on PR #255 (head ec77693), carried from inputs F and O; PR #255 merged on 2026-09-20 and the ADR is docs/adr/0001-kernel-adapter-boundary.md.| Key | Input | Rows | native | partial | missing | TV-only rows | Citations |
|---|---|---|---|---|---|---|---|
| F | D1-fable.md | 59 generic + 5 quirk | 30 | 14 | 15 | 5 | 245 |
| O | D1-opus.md | 75 | 33 | 12 | 27 | 3 | 385 |
| S | D1-sol.md | 45 | 13 | 12 | 20 | 9, counted inside "missing" | 131 |
Most status differences are row granularity (one input folds two features into one row). Real disagreements are named in the row's Notes with the ruling that settles them; §3.4 lists the lane-level ones. Test-count claims agree once the measure is named: 22 tests/*.cpp name NativeStrategyHost, 20 of them include no source/compat header (F), 14 files subclass it directly against 430 for source::PineStrategyHost (O) — fresh recount matches all three numbers.
| # | Ruling |
|---|---|
| R5-1 | Classification. Generic execution feature → kernel native API. TradingView quirk → adapter. Mixed feature → kernel gets the generic mechanism with a policy knob, the adapter selects the TV policy, the bare-host default is the plain generic behaviour. |
| R5-2 | Brackets are not a new kernel object. WaitForApplied{RequestHandle parent} (native_order.hpp:453-455) and waiting_children(parent) (native_order.hpp:2129, fresh) exist; L7 delivers a builder/toolkit over them plus relative anchors and the missing offsets. Size M. |
| R5-3 | Price grid is its own kernel lane (L8). Quantity grid is native already; the price grid (booked fill on the tick, optional quantized trigger test) is generic and missing. TV's exact half-tick rule stays in the adapter on top. |
| R5-4 | Close reservations split. Fractional reduce + reservation against sibling exits → kernel, inside the sizing lane. Callsite batching, two-call provenance, the POOC population predicate → adapter. |
| R5-5 | Calc-timing is v1. Sub-bar hook + callback chronology contract; the adapter keeps its current path (zero drift). |
| R5-6 | Native HTF is v1. A native subscription/registration API over the existing kernel feed machinery (prepare_native_security_feeds made native-callable); engine_security.cpp and the Pine scheduler path untouched; TimeframeAggregator self-aggregation is the documented interim. The lookahead default is a user question (§4 U1). |
| R5-7 | C order/callback API is post-v1, last lane, design retained (§2.iii). |
| R5-8 | Coupling extraction splits into NEUTRAL (small PRs after L1) and HASH-VISIBLE (post-v1, explicit campaign exception). F §2.ii a-h is the reference list; O §2.ii and S lane I are merged into it (§2.ii). |
| R5-9 | Risk limits are in scope as their own lane after margin; not a v1.0.0 blocker. |
| R5-10 | Parity discipline per lane: (a) neutrality, (b) twin test diffed by identity, (c) epoch budget. Stated once in §3.1. |
| R5-11 | Lane list and order L0-L13 (§3.2). |
89 merged rows at 73817c1 = 77 generic + 12 TV-quirk; SZ10a (a measured TV quirk) and PG (the price-grid waiver) were added since, for 91. Generic rows, as a bare host saw the generic core at 73817c1: native 34 · partial 14 · missing 29. Of those 43 partial / missing rows, 41 were assigned to a lane, OT1 needs nothing for v1, and FP6 was unassigned pending a user decision (§4 U4, since answered by audit lane N6). On this tree RP5 reads yes, shared (audit lane Q6), and 41 of the 42 rows still recording partial / no carry a Closed marker; OT1 is the one without.
Columns: Feature (adapter mechanism, cited) · Native today (yes / partial / no, cited, as read when the row was written; n/a = pure TV-quirk row; a Closed marker in the cell names what landed since and the kernel-profile test that proves it, the convention scripts/check_design_inventory.py holds) · Class (K kernel, A adapter, K+A generic core + TV variant) · Lane (§3.2) · Sources (input row ids) · Notes (disagreement + ruling; (single-source) = only one input found it).
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| OL1 | Market / limit / stop / stop-limit / trail triggers (trigger_for pine_adapter.cpp:3634; entry() pine_adapter.cpp:7589, order() pine_adapter.cpp:15186) | yes — Trigger native_order.hpp:300-319; geometric matching first_region_entry native_execution_consumer.cpp:6971; submit native_host.hpp:1233 | K | — | F:A1 O:A1 S:O2 | — |
| OL2 | Intents: flatten, reduce by units, reduce owner-opened units, transact, reverse-to (close_all() pine_adapter.cpp:10995 → Flatten{} pine_adapter.cpp:11030; Reduce{ExplicitUnits} pine_adapter.cpp:9863; ReverseTo pine_adapter.cpp:8266) | yes — OrderIntent native_order.hpp:246, reduce sizes ReductionSize native_order.hpp:232, ReverseTo native_order.hpp:77-79, OpeningShape native_order.hpp:998-1002, Flatten native_order.hpp:52 | K | — | F:A4 F:A8 O:A2 O:A3 S:O1 | — |
| OL3 | Amend / cancel one working order (submit_or_replace pine_adapter.cpp:4684 → host.replace pine_adapter.cpp:5112) | yes — replace native_host.hpp:1238-1240, cancel native_host.hpp:1269; ReplacedEvent native_order.hpp:1096-1110; result and event types SubmitStatus native_order.hpp:914-940, ReplacedEvent native_order.hpp:1096-1162 | K | — | F:A2 O:A4 S:O3 | id → handle map is host bookkeeping (F) → OL4 |
| OL4 | Cancel all / cancel by label; label → handle book (cancel(id) pine_adapter.cpp:15080, cancel_all pine_adapter.cpp:15153; live_by_source_key_ pine_adapter.hpp:2771; predecessor state hand-carried projection_predecessor pine_adapter.cpp:5125) | partial — single-handle cancel native_host.hpp:1269; no enumeration (WorkingRequestCore::live() is consumer-internal native_order.hpp:1822); labels exist but are unindexed (Request::label native_order.hpp:540) Closed: cancel_all native_host.hpp:1294, cancel_where(comment) native_host.hpp:1299 and cancel_where(text, NativeRequestField::Label) native_host.hpp:1299 withdraw by predicate and answer how many left the book; native_working_requests() native_host.hpp:1283 is the enumeration; the id → handle book is native_toolkit::OrderBook<Key>, host-side and Pine-free (tests/test_native_order_ergonomics.cpp, tests/test_native_anchored_legs.cpp). | K+A | L7 | F:A3 F:A2 O:A5 O:A14 S:O3 | Disagree: F partial / O no (A5) + partial (A14) / S yes (folded into O3). R5-1: cancel-all / by-label is generic → kernel. TV state inheritance across a same-id re-issue stays A. The adapter's strategy.cancel / strategy.cancel_all keep their per-handle loops, measured by R5 lane F7 (audit M27): TradingView cancels in the SOURCE book, which holds commands of the same script call that have not reached the kernel yet (and a live strategy.close is labelled __close__<id> there), so at the same point cancel_where(id, NativeRequestField::Label) and cancel_all() withdraw 0 — the close flattens at the next open, the reversal lands — where the source cancels withdraw them (tests/test_adapter_brackets_relower.cpp, M27 rows); each withdrawn source handle is also retired one by one. |
| OL5 | Per-point fill capacity, partial fills, residual working quantity (Pine uses only ImmediateRemaining pine_adapter.cpp:5331; residual closes enqueue_pooc_fifo_close pine_adapter.cpp:9609) | yes — Capacity / PointBudget native_order.hpp:396-400, Remaining native_order.hpp:620-642, allowances Allowance native_order.hpp:738-751 | K | — | O:A16 S:O4 | — |
| OL6 | Ownership: legs bound to a parent / opening(s) / cohort; close the lots of a given entry, FIFO or ANY (owner_for_close pine_adapter.cpp:7016, cohort_for pine_adapter.cpp:3207; FIFO prefix computed adapter-side source_fifo_prefix_openings pine_adapter.cpp:9704) | yes — Owner native_order.hpp:510-525, Authority native_order.hpp:693-694, CohortClose native_order.hpp:690-692, SelectedExposure native_order.hpp:698-702, cohort_open native_host.hpp:1309, CancelReason::OwnerGone native_order.hpp:1037 | K | — | F:A6 O:A8 O:A9 S:O5 | FIFO ordering is host-computed (O) |
| OL7 | OCA cancel / reduce groups (group_for pine_adapter.cpp:3643) | yes — Group / GroupEffect native_order.hpp:521-528, CancelReason::Group native_order.hpp:1036-1041, ReservationReducedEvent native_order.hpp:1270-1280 (deferred form DeferredGroupAdjustmentEvent native_order.hpp:1290-1300) | K | — | F:A12 O:A6 O:A7 S:O7 | — |
| OL8 | Immediate execution at the current point (immediately=true) | yes — inspect_current_execution / execute_current native_host.hpp:1124-1125 | K | — | F:A9 | (single-source row; O:F2 cites the same call) |
| OL9 | Birth eligibility: an order accepted at bar N cannot fill on N's already-delivered points | yes — point_eligible native_order.hpp:876-880, the generic birth record OrderBirth include/pineforge/order_birth.hpp:59, contract docs/pages/native-engine.md ("Acceptance is not a fill") | K+A | — | F:A13 O:A10 S:R4 | Disagree: F yes / O partial / S "no, TV quirk". R5-1: the generic core (birth cursor) is native; TV's bar-granular first-bar rule is the A half → OL14. |
| OL10 | TP/SL bracket per entry, armed before the entry fills, OCA between legs (exit() pine_adapter.cpp:11520; lowering submit_one pine_adapter.cpp:12436, BindCohort pine_adapter.cpp:12899, submit_leg pine_adapter.cpp:12192; lifecycle select_exit_suspension exit_lifecycle.cpp:10-66) | partial — every primitive is native (WaitForApplied{parent} native_order.hpp:453-455, Reduce{OwnerOpenedUnits} native_order.hpp:237-241, Member{…, Cancel} native_order.hpp:523-530, waiting_children native_order.hpp:2129); no builder Closed: the builder is native_toolkit::submit_bracket(host, BracketSpec) — take-profit, stop-loss and trail as one parent's WaitForApplied children in one OCA group, with anchor_rounding and visibility knobs — and it adds no kernel behaviour (tests/test_native_toolkit_bracket.cpp). | K (toolkit) | L7 | F:A10 O:A8 S:O6 | Disagree: F "yes (primitives)" + toolkit / S partial + new kernel bracket object, lane size L and prerequisite for everything. R5-2: builder over the existing primitives, size M. |
| OL11 | Bracket levels relative to the parent's fill (profit / loss ticks; derived profit_ticks pine_adapter.cpp:11785; materialize_relative_exits pine_adapter.cpp:14521, pine_adapter.hpp:2508) | partial — composable: the host computes levels in on_native_applied and submits; suffix eligibility makes that same-bar correct born_on_remaining_path native_execution_consumer.cpp:6857. Trigger itself is absolute-only native_order.hpp:313 Closed: Request::anchor = FromOwnerFill{offset, ticks, rounding} (TriggerAnchor native_order.hpp:353) defers the level to the owner's fill, the kernel materializes it once at the arm under NativeAnchorRounding, and resolve_anchored_level native_host.hpp:1032 is where a source language's own level arithmetic lives (tests/test_native_anchored_legs.cpp, tests/test_native_arm_options.cpp). | K | L7 | F:A11 O:A11 S:O6 | Disagree: F partial (toolkit is enough) / O no (kernel TriggerAnchor). R5-2: L7 delivers relative anchors; kernel anchor primary, toolkit materialization is the zero-kernel fallback. |
| OL12 | Working-order view for the host and outside observers (strategy_pending_order_*; PendingIntentView pine_adapter.hpp:1601-1617; the POD is filled only by the adapter pine_birth_reach pine_adapter.cpp:27800) | no — base returns 0 / -1 observe_last_bar_dual_entry_path_v1 engine_consumer.cpp:124-133; the POD is TV-shaped (tv_carry_qty pending_order_mirror.hpp:73, pine_birth_reach pending_order_mirror.hpp:135) Closed for a bare host: native_working_requests() native_host.hpp:1283 returns one owning NativeWorkingRequest native_host.hpp:701 per live request — definition, remaining, trigger state — with the C spelling strategy_native_working_len_v1 / _get_v1. The TV-shaped POD stays frozen and stays the source layer's projection (tests/test_native_limit_fill_through.cpp, tests/test_native_arm_options.cpp, tests/test_native_c_api.c). | K+A | L7 (C++), L13 (C) | F:J5 O:J6 S:X4 | Disagree: O "partial + TV-shaped" / F, S no. A bare host reads zeros → no. R5-1: neutral view → kernel; POD shape and Pine journal retention stay A (POD frozen, §2.ii j). |
| OL13 | (quirk) Same-bar command batching and the seven deferred queues (pending_bracket_legs_ pine_adapter.hpp:2782); KI-62 open-order priority (select order_priority.cpp:13-60, selected update_l4c_priority pine_adapter.cpp:1516); per-bar priced-entry throttle, BUY-before-SELL batch, short-seed plan / collision (activate_short_seed_plan_at_open pine_adapter.cpp:4669, materialize_snapshot pine_adapter.cpp:14495), reversal-gap bracket policy (ShortSeedPlan pine_adapter.hpp:1490-1506, DeferredOpenMarketableSell pine_adapter.hpp:2091, CloseCallsiteState pine_adapter.hpp:2229; apply_reversal_gap_bracket_policy pine_adapter.cpp:22232, reaccept_gapped_bracket_behind_same_id_add pine_adapter.cpp:22466) | n/a — native priority is acceptance / incarnation order native_events native-engine.md:676-678 | A | — | F:A15 O:A15 S:O9 S:R3 | R5-1 names KI-62. S: the kernel exposes deterministic insertion order only. |
| OL14 | (quirk) Exit activation / suspension / revival barriers, historical birth reach (select_exit_activation exit_activation.cpp:44-90, enforced leg_activation pine_adapter.cpp:1413; select_exit_activation exit_activation.cpp:20-90, select_historical_birth_reach order_birth.cpp:5-14, capture_order_birth pine_adapter.cpp:1232) | n/a | A | — | S:R4 O:A10 | TV half of OL9. V19D-P1 (R5 lanes B-ADAPTER, V19-FIX): a margin-call revival never restores an exit the script cancelled: strategy.cancel / cancel_all retire every leg of each exit row they name — the live ones they take off the book (caused at the Cancelled event, after anything the same callback did to the leg), the dormant ones, and the ones the adapter took off the book itself — and drop a gapped stop a declined reversal parked for the next margin-call slice (retire_cancelled_exits pine_adapter.cpp:2284); the revival skips a withdrawn exit (revive_brackets_after_margin pine_adapter.cpp:2372). What tests/test_adapter_margin_revival_cancel.cpp replays on the NYSE:F 15m chart of seven lab tv tapes: badapter-v19dp1-control (no cancel: the margin call closes 928 through X) and v19fix-gapped-stop-control (X's stop at 10.15, gapped by the 05-01 open and parked: the same close), and badapter-v19dp1-cancel, v19fix-cancel-before-pair, v19fix-cancel-at-1000, v19fix-gapped-stop-cancel and v19fix-gapped-stop-cancel-after-pair (a cancel on 05-01 09:30 while X is dormant, on 04-30 15:45 before the reversal pair and on 04-30 10:00 while X is live, and on 05-01 09:30 and after the pair while X is parked; one tape file: margin calls 12 @10.39 and 92 @10.57, 836 open). Untaped, the same row holds the tapes' trades for strategy.cancel_all on 05-01 and before the pair and for strategy.cancel after the pair, and on the same bars with no quantity grid — where the pair holds X in the cancel's own callback — every spelling books what the cancel on the dormant exit books |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| TR1 | Trail arm → track → trigger, with a state query | yes — Trail{offset, arm_price} native_order.hpp:307-310; states TrailWaitArm native_order.hpp:706-724; arm / track / fire TrailWaitArm native_execution_consumer.cpp:6938; NativeTrailState native_host.hpp:689-694, trail_state native_host.hpp:1112 | K | — | F:B1 O:B1 S:T1 | — |
| TR2 | Trail activation / offset in ticks or points, relative to the entry fill (trail_points_to_ticks include/pineforge/compat/pine/trail_ticks.hpp:44 + grid snap exit_lifecycle.cpp:22-27; trail_activation_level pine_adapter.hpp:370) | partial — absolute prices and a raw price distance only Closed: TrailTicks{n} (Trail::ticks native_order.hpp:310) is resolved against NativeRunSpec::price_tick at acceptance, so a tick-spelled trail and its price-spelled equal behave identically bar for bar; a relative arm threshold is the FromOwnerFill anchor of OL11 (tests/test_native_order_ergonomics.cpp, tests/test_native_anchored_legs.cpp). | K | L7 | F:B2 O:B2 S:T2 | Disagree: S classes all trail operand conversion as TV quirk. R5-1: tick / relative spelling is generic. |
| TR3 | Zero-distance trail, "ride the best, exit on any adverse tick" (adapter substitutes a half tick, tick * 0.5, through native_trail_offset_ticks pine_adapter.cpp:11825) | no — the kernel fails the run when offset <= 0 (checked_trail_stop → native_execution_consumer.cpp:5648) Closed: a zero offset is legal and means ride the best — the level is the running best itself and the exit is the first print strictly past it; negative and nonfinite offsets stay rejected (tests/test_native_order_ergonomics.cpp, tests/test_native_anchored_legs.cpp). | K+A | L7 | O:B3 F:B3 S:T2 | Disagree: O kernel / F, S quirk. R5-1: accepting a zero offset is generic; TV zero-offset pricing (zero_trail_sibling_stop pine_adapter.cpp:15499) stays A. |
| TR4 | Re-price a trail without losing the running extreme (adapter skips replace and reads trail_state() pine_adapter.cpp:5080) | partial — the read exists trail_state native_host.hpp:1112; replace always rebuilds trigger state Closed: replace(handle, request, ReplaceOptions{/*retain_trigger_state=*/true}) carries the predecessor's live trigger state — a tracking trail's best, an already active stop — into the successor, refusing the replacement when the retained best leaves no representable level (tests/test_native_order_ergonomics.cpp, tests/test_native_anchored_legs.cpp). | K+A | L7 | O:B4 F:B3 | Status is single-source (O). R5-1: retain-trail replace is generic; TV's "retained best across re-issue" convention (retained_trail_best pine_adapter.hpp:373) is A. |
| TR5 | (quirk) TV trail conventions: half-tick arm threshold against tick-quantized extremes has_trail_request pine_adapter.cpp:13034, zero-offset pricing zero_trail_sibling_stop pine_adapter.cpp:15499, retained best retained_trail_best pine_adapter.hpp:373, operand / restart rules source_trail_points pine_adapter.cpp:11644, trail_one_shot pine_adapter.cpp:13063 | n/a | A | — | F:B3 S:T2 | R5-1 |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| SZ1 | Explicit units (Transact{signed} after a grid floor pine_adapter.cpp:8268, pine_adapter.cpp:15256) | yes — Transact in OrderIntent native_order.hpp:246 | K | — | F:C1 O:C1 S:S1 | — |
| SZ2 | Host override of the resolved price / units (resolve_terms pine_adapter.cpp:15421) | yes — HostSized native_order.hpp:90-93 + resolve_execution_terms native_host.hpp:967-969, invoked resolve_execution_terms native_execution_consumer.cpp:6158, guarded by consume_matched_request's try native_execution_consumer.cpp:6050-6090 | K | — | F:C2 O:H9 S:S2 | Disagree: S "partial" (a hook is not a basis). The hook is native; bases are SZ3-SZ5. It stays the custom-policy seam after L3. |
| SZ3 | Percent-of-equity sizing (default_sizing_units pine_adapter.cpp:3397) | no — HostSized without host units is TermsUnresolved native_execution_consumer.cpp:6187 Closed: Sized{Side, EquityFraction{f}} (OrderIntent native_order.hpp:246) is the sixth intent — the kernel resolves the units from a fraction of marked equity at the sizing point, so a bare host needs no terms override (tests/test_native_sizing_bases.cpp). | K+A | L3 | F:C3 O:C2 S:S3 | TV parts stay A: the percentage's money rounded source_money_round pine_adapter.cpp:643 (not the equity: R5 lane W5B-ENG-MARGIN-RESIDUAL, the private fixture set margin_residual w5b-sz-*), money floor-to-lot source_money_floor_lot pine_adapter.cpp:743 (→ SZ10) |
| SZ4 | Cash-value sizing (default_qty_type pine_adapter.cpp:3268) | no — same Closed: Sized{Side, CashValue{cash}}, the same resolution over an account-currency amount (tests/test_native_sizing_bases.cpp). | K | L3 | F:C2 O:C3 S:S4 | A per-call qty_type (cash or percent of equity on one strategy.entry) is not Pine — TradingView's compiler refuses it, so only generated or C++ callers reach it — and since R5 lane B-ADAPTER it converts through the kernel's native_sized_units exactly as the declared default does (typed_quantity_units pine_adapter.cpp:3416, typed_entry_units pine_adapter.cpp:3434; tests/test_adapter_typed_entry_admission.cpp) |
| SZ5 | Fee-net sizing (commission reserve /(1+c), asked of the kernel as reserve_percent_fee pine_adapter.cpp:3389; percent_commission_live_equity pine_adapter.cpp:5444) | no — only a dead primitive, reserve_percent_commission (since deleted), with no caller in src/ Closed: Sized::reserve_percent_fee divides the sizing cash by 1 + fee_value / 100 under NativeFeeKind::Percent and is an exact no-op for every other fee kind; the dead reserve_percent_commission primitive is gone (tests/test_native_sizing_bases.cpp, tests/test_native_sizing_price_rule.cpp). | K | L3 | F:C3 O:C6 S:S3 | Disagree: F lists the reserve under quirk yet offers a knob / O kernel, "partial" / S knob. R5-1: fee-net option is generic. TradingView's cash-commission reserve stays adapter policy over the kernel's CashValue: a percent-of-equity default quantity under a cash-per-order or cash-per-contract fee takes its percentage of strategy.equity (PineSizingSnapshot::strategy_equity, the open entries' cash fees charged) and leaves out the fee its own order pays (default_sizing_cash, R5 lane PAR-CASHFEE, §3.10; tests/test_cash_fee_sizing_tapes.cpp on the tapes in the private fixture set cash_fee_sizing) |
| SZ6 | Placement-time vs fill-time sizing (sizing_snapshot pine_adapter.cpp:3223, PineSizingSnapshot pine_adapter.hpp:165-177; fill-time exceptions default_sized pine_adapter.cpp:8901) | no Closed: SizeTime::AtMatch (default) resolves at the matching candidate and SizeTime::AtAcceptance freezes the units against the command point, where the run's own opening admission also runs against the frozen quantity (RequestRejectReason::PlacementAdmission). Which price the basis converts at is SizePrice::{Resolved, Signal, SignalOnTick} (tests/test_native_sizing_bases.cpp, tests/test_native_sizing_price_rule.cpp). | K+A | L3 | F:C3 O:C7 S:S3 | Disagree: O adapter-only / F, S generic timing knob. R5-11 (L3 scope) + R5-1: the knob is kernel; TV's frozen tuple stays A through HostSized. |
| SZ7 | Quantity step | yes — quantity_grid admission native_run_spec.hpp:660, native_order.hpp:1522-1540; host-sized terms snap (ExecutionGridPolicy native_order.hpp:99-102); exact grid validation execution_terms_grid_representable native_execution_consumer.cpp:510; the book's own quantities are on the grid – a whole-scope ScopeFraction and a Reduce of a FIFO boundary of its scope (R5 lane K-ULP4) | K | — | F:C4 O:C5 S:S1 | Disagree: F, O partial / S yes. R5-3: the quantity grid is native already. L3's Sized intent reuses the snap policy. |
| SZ8 | Fractional reduce of the position / a cohort (quantize_close_units pine_adapter.cpp:5564, compute_exit_reservation pine_adapter.cpp:5604) | no — sizes are ExplicitUnits / OwnerOpenedUnits native_order.hpp:208-214 / HostSized native_order.hpp:90-93 only Closed: Reduce{ScopeFraction{fraction, claim, basis}} takes a fraction in (0, 1] of the scope the reduce is bound to — the book, one opening, or a cohort roster (tests/test_native_sizing_bases.cpp). | K | L3 | F:A7 O:A12 O:C4 S:S5 | F's "partial" counts the units-reduce of OL2. R5-4 → L3. |
| SZ9 | Exit-quantity reservation against sibling exits (compute_exit_reservation pine_adapter.cpp:5604, reserved_exit_qty pine_adapter.cpp:12155) | partial — PointBudget native_order.hpp:396-399 and group Reduce native_order.hpp:237 give a generic reservation; no net-of-siblings claim for fraction sizes Closed: ScopeClaim::NetOfSiblings subtracts the units already claimed by live sibling reduces bound to the same scope, and ScopeBasis::{AtMatch, AtAcceptance} chooses when that scope is measured (tests/test_native_sizing_bases.cpp). | K+A | L3 | F:A14 S:S5 S:O10 O:A12 | F proposed nothing. R5-4: generic half → L3; the Pine ledger → SZ11, SZ12. |
| SZ10 | (quirk) Ten-significant-digit money rule (source_money_round pine_adapter.cpp:643, source_money_floor_lot pine_adapter.cpp:743; twin tv_money_round pine_policy_support.hpp:9-15); exact percent-exit rounding (quantize_percent_exit_units pine_adapter.cpp:5586) | n/a — kernel money is plain binary64 marked_equity engine_execution.cpp:1753 | A | — | O:C8 F:C3 S:M4 S:S5 | R5-1 |
| SZ10a | (quirk, measured retention) The money band's affordable price, sig10(sig10(E) / notional per unit of price) against the sizing price — at placement (entry, affordable_price pine_adapter.cpp:8944), in the paired-reversal whole drop (close, affordable_price pine_adapter.cpp:10487), at the terms boundary (resolve_terms, affordable_price pine_adapter.cpp:16787), in the precommit money scopes (validate_precommit, affordable_price pine_adapter.cpp:17357, pine_adapter.cpp:17410) and in the carried-money check (apply_reversal_gap_bracket_policy, affordable_price pine_adapter.cpp:22280) | n/a — not a units conversion. Every money-to-units conversion in the adapter is the kernel's native_sized_units under the source's floors since R5 lane B-ADAPTER: the default at placement and at the fill (default_sizing_units pine_adapter.cpp:3397), the typed per-call quantity at placement, at the fill and in the probe (typed_quantity_units pine_adapter.cpp:3416, typed_entry_units pine_adapter.cpp:3434), and the pending-order fill-quantity probe (probe_fill_qty pine_adapter.cpp:27379) | A | — | AUDIT3-opus2 H4 (G2 R2, R3) | Measured: the quotient is TradingView's whole-drop PRICE, pinned by 445 famr3 tapes and 202 famr tapes (two tests that live in the private fixtures); on a seven-digit tie (E 9998.99999996, 10 000 lots at tick(close) 0.9999) the rule admits while the kernel's conversion buys 9999.99999996 lots and binary64 gives E/Q 0.999899999996 (tests/test_adapter_fill_qty_probe.cpp, section 2). |
| SZ11 | (quirk) Same-bar strategy.close callsite batching, two-call provenance, entry-id ledger (enqueue_pooc_fifo_close pine_adapter.cpp:9609; ledger close_batch_bar_ pine_adapter.hpp:2854; the kernel's comment-only residue was deleted by R5 lanes E6 and E10) | n/a | A | — | S:O8 F:A15 O:A15 | Disagree: S "generic → kernel" (its P2 ReplaceByKey / Fifo). R5-4: adapter. |
| SZ12 | (quirk) POOC reservation-growth market-entry population predicate (select_reservation_growth_sources src/compat/pine/reservation_expansion.cpp:9-34, called from the adapter, select_reservation_growth_sources pine_adapter.cpp:1474) | n/a | A | — | S:O10 | (single-source) R5-4 confirms S's own split: concept → SZ9, predicate A. |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| MG1 | Allowed opening directions (allowed_open_directions pine_adapter.cpp:3043) | yes — allowed_open_directions native_run_spec.hpp:665 → MatchRejectReason::OpeningDirection native_execution_consumer.cpp:3903 | K | — | F:D4 O:D7 S:M1 | the one risk rule the adapter delegates (O) |
| MG2 | Max absolute position size | yes — max_abs_units native_run_spec.hpp:663 tests the resulting book native_execution_consumer.cpp:3910 | K+A | — | F:D5 O:D5 S:M1 S:M5 | Disagree: O, S:M5 partial. R5-1: the generic cap is native; the adapter's live-position >= held variant (max_position_size pine_adapter.cpp:17101) is TV. |
| MG3 | Open-lot cap (pyramiding) | yes — max_open_lots native_run_spec.hpp:664 → MaxOpenLots native_execution_consumer.cpp:3915 | K+A | — | F:A5 O:A13 O:D6 S:M1 | Disagree: F, O:A13 partial / O:D6, S yes. R5-1: Pine's per-cycle entry count (accepted_in_cycle pine_adapter.cpp:8130, cap left unset; setting spelling max_open_lots pine_adapter.cpp:2830) is TV. Measured (R5 lane H-MEASURE), and not TradingView's rule: on 15 scenarios of three lab tv tapes (the private test test_pyramiding_count_differential) TradingView checks an entry once, at its first eligible point, against the trades then open; the kernel's max_open_lots books 14 as TradingView does, the adapter's count 13 (it keeps a slot a close or another entry's exit drained; since lane W5B-ENG-MARGIN-RESIDUAL's SB the later market entries of a flat bar are refused at the fill once the book holds the cap). The fifteenth, a resting entry accepted below the cap and filled at it, the kernel's cap refuses; the adapter books it since lane W8A-SIGSTATE-1's R-B, which also stopped the count from counting a resting LIMIT entry (a pending LIMIT entry does not count at a later call, and one the cap admitted fills over it: the private fixture set pyramiding_open_order). A recorded divergence, retained pending a re-lowering ruling. Re-lowering tried by R5 lane H-THIN and not taken: on the kernel's cap four corpus probes moved away from TradingView, for want of that first-eligible-point rule, so the count stays. The lane fixed the reservation divergence H-MEASURE measured beside it (P10): a default strategy.exit(from_entry) under FIFO reserved the whole book (200 in the adapter, 100 on TradingView's tape); it now reserves the entry's own quantity, the oldest lots first (fifo_named_entry pine_adapter.cpp:12145). |
| MG4 | Initial-margin opening gate (adapter answers AdmitWithHostMargin from frozen signal-time equity pine_adapter.cpp:17918 and leaves the kernel gate unset pine_adapter.cpp:17937) | yes — initial_margin_fraction native_run_spec.hpp:666; the requirement resulting_abs_notional * fraction native_execution_consumer.cpp:3933 against marked equity. Conversion instant, R5 follow-up lane E21: both sites of the gate convert at their own point's rate — the candidate at its cursor, the placement gate at the acceptance point where a Sized{SizeTime::AtAcceptance} freezes its units — for the notional, the equity and a percent ticket, and the freeze's equity basis with them. The rate is threaded, never the clock: make_command_context native_execution_consumer.cpp:3779 hands the point's rate to the freeze, to marked_equity_at and to the engine's rate-explicit settlement inspection inspect_native_settlement_scoped_at engine.hpp:839, and the engine's presented clock is never written (tests/test_native_margin_fx_clock.cpp sections 8, 10 and 11) | K | — | F:D1 O:D1 S:M2 | Disagree: F, O partial (they fold per-side in) / S yes. Row split → MG5. Measured (R5 lane H-MEASURE, M11): the two gates part both ways on the same bars — the kernel's declines a 1× long whose entry fee the adapter admits, and admits a gap-up add the adapter refuses against its signal-time equity; ab9714be books the adapter's answer both times (the private test test_adapter_margin_schedule_differential). Since R5 lane TAIL-C an explicit-quantity market buy from flat is costed at its signal close's tick plus the slippage ticks its fill carries, off process_orders_on_close (the private test test_explicit_market_admission_tapes), and an infinite strategy.entry quantity trades the default quantity, as na does (tests/test_nonfinite_entry_qty_tape.cpp). |
| MG5 | Per-side initial margin (margin_pct pine_adapter.cpp:17233, margin_pct pine_adapter.cpp:17673) | no — one std::optional<double> initial_margin_fraction native_run_spec.hpp:666 Closed: NativeMarginModel::initial_long / initial_short native_run_spec.hpp:241-242, opt-in and mutually exclusive with initial_margin_fraction; initial_<side> == 0.0 is the maintenance-only spelling that leaves opening admission to the host at both of its gates (tests/test_native_margin_model.cpp). | K | L4 | F:D1 O:D2 | R5-1 |
| MG6 | Maintenance margin + forced liquidation (submit_margin_call_slice pine_adapter.cpp:18556, submit_margin_call_units pine_adapter.cpp:18589, schedule_margin_call_path pine_adapter.cpp:20838) | no — "no maintenance liquidation" initial_margin_fraction native_run_spec.hpp:666-667; margin is checked only when inspect.would_open native_execution_consumer.cpp:3892 Closed: maintenance_long / maintenance_short with NativeLiquidationCheck::{PathAdverseExtreme, PathAdverseExtremeMark, CalculationOnly}; the kernel solves the level, rests its own Reduce/Flatten bound to the live book, re-prices it with CancelReason::Superseded and books a MarginCallEvent (tests/test_native_margin_model.cpp, tests/test_native_margin_hooks.cpp). | K+A | L4 | F:D2 O:D3 S:M3 | TV parts → MG15 |
| MG7 | Liquidation slice sizing (grid floor, ×4 restore, one-contract fallback source_margin_units pine_adapter.cpp:18215) | no Closed: NativeLiquidationSizing::{RestoreMinimum, ShortfallMultiple, Flatten} with shortfall_multiple and liquidation_min_units, capped at the held quantity and floored onto quantity_grid; resolve_margin_call_units native_host.hpp:1015 gives a host the last word, clamped into (0, held] (tests/test_native_margin_model.cpp, tests/test_native_margin_hooks.cpp). | K+A | L4 | O:D4 F:D2 S:M4 | Disagree: F generic knob ShortfallMultiple / O "×4 is A" / S quirk, yet a 4.0 default inside its generic model. R5-1: policy knob in the kernel, plain default (restore-minimum). Decided by R5 lane F7 (the D2 question): the adapter selects NO kernel knob — project() leaves sizing / shortfall_multiple / liquidation_min_units unset and its resolve_margin_call_units answers every call with TradingView's slice (the restore floored onto the lot grid BEFORE the ×4, floored again, the one-contract band, the dust gate). Measured: on a one-unit grid, 20 long at 100 on 1000 at 50 % reaching 93.9, TradingView books 4 lots and the kernel's own ShortfallMultiple 4 books 5 (tests/test_native_margin_hooks_twin.cpp MG-F3, an executed run, no longer a comment); without a grid the two agree bit for bit (MG-F/H, 4.2105263157894735), but declaring the knob for those runs alone folds NativeMarginModel::sizing into every margin run's identity — an epoch decision F7 does not take. Since R5 lane H-THIN the pre-open admission slice (schedule_preopen_margin_slice) sizes through the same source_margin_units, where it kept its own copy of the ×4 without the band or the dust gates (AUDIT4 §6 G2-12; the private test test_adapter_margin_schedule_differential M10: band and fudge now 1 and 1, the kernel's and ab9714be's). A four-times call on a whole-unit grid whose book's oldest lot is one unit is followed by one more unit at the next check point (whole_unit_follow_up_due pine_adapter.cpp:19745; R5 lane W5B-ENG-MARGIN-RESIDUAL, the private fixture set margin_residual w5b-es-*, w5b-sb-es-*, w5b-f-q1x12). With slippage the one-contract band takes its unit only where that unit restores the book at its own fill: the position's requirement at the liquidation's slipped print, less the equity at the mark, under one unit's margin (source_margin_units pine_adapter.cpp:18215; lane INT28-FIX rule OU, the private fixture set slipped_short). A one-unit call that leaves the book short at its own fill, by more than one unit's margin and two slippage steps with the book re-marked there, is followed by one more unit at the bar's next path point, where that point's own check calls nothing and its print is inside the call's fill (lane INT28-FIX rule OF). Under a percent commission a call of any size is followed so, on a fractional lot grid too: while the book the call left, re-marked at its fill, is short by more than two slippage steps for each unit called and the exit fees of those units and of one more, the next path point inside that fill takes four times the shortfall's lot floor over the fill, or one unit where that floors to none (margin_follow_up_units pine_adapter.cpp:19787, follow_margin_call pine_adapter.cpp:19836; R5 lane TAIL-I, the private fixture set margin_open_print). |
| MG8 | Liquidation price query (margin_liquidation_price pine_strategy_host.cpp:480-496) | no Closed: native_liquidation_price() native_host.hpp:1350 answers the exact solved level, nullopt where none exists; NativeLiquidationLevelBase chooses whether the solve answers for the open entries' fees (tests/test_native_margin_model.cpp, tests/test_native_open_lots.cpp). | K+A | L4 | F:D3 S:R5 | Disagree: F generic / S adapter-only. R5-1: liquidation price is generic; the Pine spelling stays A. Lowered by R5 lane H-THIN: the Pine host's second solve (compute_liquidation_price) is gone; strategy.margin_liquidation_price reads native_liquidation_price(), which the adapter configures with NativeLiquidationLevelBase::RealizedOnly (the Pine formula's closed-money solve, bit for bit at every bar of the lane's 1,021 seeded margin-call runs), and keeps only TradingView's tick spelling: floored for a long, ceiled for a short. A run whose margin calls are switched off declares no margin model, so it reads na. |
| MG9 | FX revaluation of margin (apply_fx_open_margin_slice pine_adapter.cpp:6031, apply_fx_opening_margin_slice pine_adapter.cpp:6043) | no — staged FX curve configure_native_fx_curve native_host.hpp:1222 but no margin re-check. Closed by audit lane N6: NativeMarginCheckKind::FxRoll — a step of the declared curve is a check point of its own, measured at the unchanged price before the first point the new rate converts (tests/test_native_margin_fx_roll.cpp). The adapter refuses the point by kind, for a measured reason since R5 lane F7: TradingView's rollover is a broker-open checkpoint at the open price, now at any positive margin (apply_fx_open_margin_slice, on the one TradingView money / units rule every other checkpoint uses), while the roll point measures at the remaining path's adverse mark and rests the slice there — a carried 1x long on a step bar O 100 L 97 books 0.4116 @ 97 on the adapter's run shape and 0.4036 @ 99, measured a bar early, on a strict calendar, against TradingView's 0.3996 @ 100 (tests/test_native_margin_hooks_twin.cpp MG-FX). Both N6 follow-ups are closed (§4.4). Completed by R5 follow-up lane E3: the conversion INSTANT is now generic too — every check kind converts at its own cursor's rate (account_currency_fx_at(cursor.point.effective_time_ms)) for the requirement, the equity and the level, in batch and stream, so a step landing between a fill's cursor and the clock the engine presents no longer breaches at the fill (tests/test_native_margin_fx_clock.cpp; N6's local clock swap inside fx_roll_margin_check is removed as redundant). Extended by R5 follow-up lane E21 to the opening gate's placement site (MG4), and the rate-explicit equity E3 restated beside the engine's accessor is now that accessor's one implementation, BacktestEngine::marked_equity_at engine.hpp:919 | K | L4 → N6 → E3 → E21 | O:D8 | (single-source) |
| MG10 | Max drawdown halt (update_risk_state pine_adapter.cpp:18095) | no — observation field only drawdown_limit include/pineforge/source/market_admission.hpp:55; composable today from native_marked_equity native_host.hpp:1331 Closed: NativeRiskLimits::max_drawdown native_run_spec.hpp:311, a NativeLossLimit{value, percent} measured against the running peak equity at three points of every script bar, blocking openings for the rest of the run (tests/test_native_risk_limits.cpp). | K | L9 | F:D6 O:E1 S:M6 | R5-9 |
| MG11 | Max consecutive losing days (SourceDayLedger pine_adapter.hpp:1534-1545; gate max_cons_loss_days pine_adapter.cpp:17095) | no — observation only loss_days_limit include/pineforge/source/market_admission.hpp:54 Closed: NativeRiskLimits::max_consecutive_loss_days, settled from each day's realized result at the next day's open (tests/test_native_risk_limits.cpp). | K | L9 | F:D6 O:E2 S:M6 | R5-9 |
| MG12 | Max intraday loss → flatten + block (intraday_loss_breached pine_adapter.cpp:18128, forced flatten submit_intraday_loss_close pine_adapter.cpp:21472) | no — observation only intraday_loss_limit include/pineforge/source/market_admission.hpp:56 Closed: NativeRiskLimits::max_intraday_loss with NativeRiskAction::FlattenAndBlock, under which the kernel issues one Flatten of its own at the breach point (RequestOrigin::KernelRisk) and latches the block before that flatten settles (tests/test_native_risk_limits.cpp). | K | L9 | F:D7 O:E3 S:M6 | R5-9 |
| MG13 | Max filled orders per day (IntradayCap intraday_cap.hpp:84-263, IntradayOrderBudget intraday_order_budget.hpp:27-92; execute_due_cap_close pine_adapter.cpp:21515) | no Closed: NativeRiskLimits::max_fills_per_day counts every applied execution of the day as it settles, whatever issued it (tests/test_native_risk_limits.cpp). | K+A | L9 | F:D8 O:E4 S:M7 | Count cap K; quota transfer, POOC deferral, TV close price A |
| MG14 | Risk-day boundary (chart_day_key pine_adapter.cpp:17992) | no — the calendar exists session_day_ordinal native_calendar.hpp:416-419, there is no risk-day ledger Closed: NativeRiskDay::{SessionDay, CalendarDayInTimezone} keys the risk day, and native_risk_state() native_host.hpp:1359 reads the whole ledger back — block and reason, day, fills counted, loss-day streak, peak equity, day-opening equity (tests/test_native_risk_limits.cpp). | K+A | L9 | O:E5 S:M7 | The chart-day shortcut stays A; the kernel keys on session / calendar day |
| MG15 | (quirk) TV margin call: money rounding source_margin_money pine_adapter.cpp:18160, 4× shortfall 4.0 * minimum pine_adapter.cpp:18249, POOC chronology exception resting_order pine_adapter.cpp:20870 (the commissioned carried short; the competing-entry and rounded-money exceptions retired by R5 lane W5B-ENG-MARGIN-RESIDUAL, the private fixture set margin_residual w5b-pc-*), 1×-long money call submit_tv_money_long_margin_call pine_adapter.cpp:19155, fill pricing (the slice books at its waypoint's mark, submit_tv_money_long_margin_call pine_adapter.cpp:19155) | n/a | A | — | F:D2 F:G4 O:D4 S:M4 | R5-1. Measured (R5 lane H-MEASURE) against the kernel's own points on the same bars and orders, the kernel's hooks answering TradingView's money and slice (the private test test_adapter_margin_schedule_differential): a gap-open breach executes at the open, sized on the open's money, where the kernel's mark check rests at the path's adverse extreme and CalculationOnly executes at the calculation (M8); the pre-open slice parts from the kernel's AfterApplied point, and from ab9714be, on the one-contract band and the restore's +1e-6 floor (M10; the frozen units it also parted on are TradingView's book since R5 lane PAR-MARGIN froze a default-percent stop above 100 % as eight lab tv tapes, the private tapes pm-m10-* of margin_entry_bar, show TradingView does); the entry bar of a leveraged opening, where four lab tv tapes (the private fixture set margin_entry_bar) and the kernel's AfterApplied point book the margin call at that bar's low, is admitted by on_applied since R5 lane PAR-MARGIN on the run shapes ab9714be checked it on (the pre-open slice's own opening keeps that slice's verdict), and the adapter books the tapes' rows (M7; H-MEASURE had recorded it as a divergence); R5 lane PAR-MARGIN-2 extended it to limit openings, process_orders_on_close, calc_on_order_fills and adds to a carried book, and made the kernel's post-fill point reach the low a limit was filled on the way down to, or a short's high on its way up (twenty-eight lab tv tapes, the private tapes pm2-* of margin_entry_bar). Under the magnifier the kernel's IntrabarSample point (R5 lane PAR-MARGIN) is admitted on a leveraged long, plain, under process_orders_on_close or calc_on_order_fills, and on a short outside process_orders_on_close (PAR-MARGIN-2), where TradingView books its call at the first lower-timeframe low that crosses – at its own 2-minute intrabars on a 15-minute chart, which a model reproduces on all 20 magnified tapes (the private fixture set intrabar_margin); six tapes part on the one-minute samples the host feeds, a recorded divergence of the magnifier's sampling. A margin call books the nearest tick of its print, as TradingView's market fills do (12.105 books 12.11; the private fixture set half_tick_rounding, PAR-MARGIN-2). |
| MG16 | (quirk) Historical market-admission review / retention journal (explicit_pair_scope src/compat/pine/market_admission.cpp:34 and the review fold it feeds (original_pair_call src/compat/pine/market_admission.cpp:48); admission_allocation pine_adapter.cpp:4874, record_market_review pine_adapter.cpp:21771) | n/a | A | — | S:R2 | (single-source) |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| CT1 | Calculate at script-bar close | yes — on_native_bar native_host.hpp:913, dispatched by invoke_callback native_execution_consumer.cpp:8586 (call site on_native_recalculate native_execution_consumer.cpp:8614) | K | — | F:E1 O:F1 | — |
| CT2 | Process orders on close (close_execution pine_adapter.cpp:3000) | yes — NativeCloseExecution::AfterCalculation native_run_spec.hpp:35-38; AfterCalculation native_execution_consumer.cpp:9030 | K | — | F:E2 O:F6 S:C1 | — |
| CT3 | Pre-match hook at bar open (on_bar_open pine_adapter.cpp:22867) | yes — on_native_bar_open native_host.hpp:909 runs before the open match native_execution_consumer.cpp:8460; execute_current legal native_host.hpp:1124 | K | — | F:E3 O:F2 | hazard → CT4 |
| CT4 | Partial-bar view / open-bar lookahead guard | no — the open hook receives the complete script bar: invoke_bar_open_callback(engine, bar, point) native_execution_consumer.cpp:8403 with engine.current_bar_ = open_view native_execution_consumer.cpp:8454 (the complete bar unless OpenOnly) Closed: current_partial_bar() native_host.hpp:1093 is the lookahead-free bar so far, through the last path point consumed (a fill inside a segment reads it at the segment's origin), and NativeOpenBarView::OpenOnly native_run_spec.hpp:109 masks on_native_bar_open's bar to H = L = C = open as presentation only — the complete bar is restored before the open match (tests/test_native_calc_timing.cpp). | K | L5 | F:E3 | (single-source) The adapter needs the full bar, so the guard is opt-in. |
| CT5 | Raw input observation before aggregation | yes — on_native_input native_host.hpp:895, NativeInputContext native_host.hpp:793-798 | K | — | O:F3 | (single-source row) |
| CT6 | Calculate on order fills (scheduler PineScheduler::recalculate pine_scheduler_native.cpp:767, coof_recalculation_due pine_scheduler_native.cpp:733, the first-open chain kFirstOpenLoopGuard pine_scheduler_native.cpp:888; begin_coof_recalc pine_adapter.cpp:7114, flush_coof_tail pine_adapter.cpp:7536, end_coof_recalc pine_adapter.cpp:7171) | partial — on_native_applied native_host.hpp:958 fires mid-path after each fill native_execution_consumer.cpp:8307, native_execution_consumer.cpp:8307; commands are legal there commands_allowed native_execution_consumer.cpp:1966; a request born there is eligible on the unconsumed rest of the bar born_on_remaining_path native_execution_consumer.cpp:6857, remaining_path_delivery native_order.cpp:4347-4355. Missing: calculation re-entry, a documented chronology, a bar-so-far view Closed: NativeCalculationTrigger::BarCloseAndFills native_run_spec.hpp:96 drives one OrderFill recalculation per applied execution at that event's own cursor, delivered as on_native_recalculate(..., OrderFill, cause) and bounded by max_recalculations_per_point (tests/test_native_calc_timing.cpp). | K+A | L5 | F:E4 O:F5 S:C2 | Disagree: F partial / O, S no; F, S v1 / O deferrable. R5-5: v1, sub-bar hook + chronology contract, adapter path unchanged. TV parts → CT11. |
| CT7 | Calculate on every tick | partial — on_native_tick native_host.hpp:898 fires per accepted realtime print native_execution_consumer.cpp:8542; no per-tick calculation in batch; the guide pinned "close-only" (docs/pages/native-engine.md, "Batch OHLCV vs ticks vs quiet") Closed: NativeCalculationTrigger::EveryModeledPoint native_run_spec.hpp:97 adds one Tick recalculation at every modeled point of the delivered path and at every observed print, in batch and in a stream alike (tests/test_native_calc_timing.cpp). | K | L5 | F:E5 O:F4 S:C3 | Disagree: F yes / O partial / S no. R5-5: v1. Not an adapter feature either: absent from PineStrategyConfig pine_adapter.hpp:67-81, the runner rejects it (calc_on_every_tick main.cpp:259), the Pine stream route refuses calc_on_order_fills pine_strategy_host.cpp:608. |
| CT8 | Historical intrabar calculation (per lower-TF sub-bar / magnifier sample) | no — with an IntrabarPath the callback still fires once per script-bar calculation (invoke_callback native_execution_consumer.cpp:9308). Closed: EveryModeledPoint recalculates at each intrabar sample, and on_native_sub_bar native_host.hpp:948 fires once after each retained lower-timeframe sub-bar's whole matching path (tests/test_native_calc_timing.cpp). | K | L5 | F:E6 O:F8 S:C4 | — |
| CT9 | Intrabar (magnifier) matching path + path-order policy (volume_weighted_cap pine_adapter.cpp:3120) | yes — IntrabarPath native_run_spec.hpp:362-403, delivery deliver_confirmed_script native_execution_consumer.cpp:8752-9018; NativePathOrder native_run_spec.hpp:335-339 | K+A | — | F:E7 F:G5 O:F7 S:C4 | Disagree: S partial (it wants callback cadence → CT8). TV sampler choice is A: TradingView's own intrabars at its table's timeframe, each owned by the chart bar of its last minute, built from the finer feed (tradingview_magnifier_bars magnifier_intrabars.hpp:46, R5 lane MAG-INTRABAR). |
| CT10 | Batch → warmup → realtime forward execution | yes — stream_begin engine.hpp:1998, stream_push_bar engine.hpp:2004, stream_end engine.hpp:2008, NativeRunPhase native_host.hpp:41, C ABI strategy_stream_begin c_abi.cpp:781 | K+A | — | F:E8 O:J3 S:C5 | Disagree: S partial. R5-1: the lifecycle is native; Pine trade-start suppression and realtime-tail / probe rules (trading_window_active pine_strategy_commands.cpp:15-21, prepare_native_begin pine_strategy_host.cpp:548-702) are A. S's "neutral tail + trade-window policy" is single-source and unassigned (§4 Q8). |
| CT11 | (quirk) COOF TV specifics: waypoint-only refill (coof_next_waypoint pine_adapter.cpp:7385), two-fills-at-open rule, script-state rollback (PineScheduler pine_scheduler_native.cpp:99-150; hooks snapshot_script_state engine.hpp:1522-1523), the first-open guard kFirstOpenLoopGuard pine_scheduler_native.cpp:888, deferral queue of PendingCoofRequest pine_adapter.hpp:2207 | n/a | A | — | F:E4 O:F5 S:C2 | R5-5: the adapter keeps its path |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| HT1 | HTF series of the same symbol (request.security): evaluators registered scheduler_configure_security_evaluators pine_strategy_host.cpp:1874, pumped from the scheduler scheduler_prepare_security_sequence pine_strategy_host.cpp:1888-1948; state lives in BacktestEngine engine.hpp:358-560, engine.hpp:484-533, engine_security.cpp:27 | no sanctioned path — configure_security_evaluators is an empty virtual engine.hpp:1512 whose only caller is pine_strategy_host.cpp:1875 Closed: NativeRunSpec::subscriptions (NativeTimeframeSubscription native_run_spec.hpp:491), or declare_timeframe_subscriptions native_host.hpp:1156 from inside on_native_run_begin, with on_native_timeframe_bar native_host.hpp:905 and native_series_bar(index) native_host.hpp:1139 reading them back; the kernel registers the declared series after that callback returns, so a host's own evaluators survive (tests/test_native_htf_subscriptions.cpp, tests/test_native_htf_subscriptions_stream.cpp). | K | L6 | F:F1 O:G-1 S:H1 | Disagree: S partial (it counts kernel-side storage → HT2); F, S v1 / O deferrable. R5-6: v1. |
| HT2 | Authoritative (exchange) HTF bars substituted into a series (set_native_security_feed engine.hpp:1958, engine_aux_security.cpp:79-133, C ABI strategy_set_native_security_feed c_abi.cpp:1030) | partial (inert) — pre-run staging is accepted refuse_source_mutation native_execution_consumer.cpp:2001 but never consumed: prepare_native_security_feeds is protected engine.hpp:1859, its sole call prepare_native_security_feeds pine_strategy_host.cpp:1918; in-run it latches UnsupportedSource native_execution_consumer.cpp:2019 Closed: a bare host's subscriptions consume authoritative bars: NativeTimeframeSubscription::authoritative_bars native_run_spec.hpp:493 is staged into the feed store by the kernel's own prepare_native_security_feeds native_execution_consumer.cpp:9951 call at begin, so a declared series is the venue's own bars (tests/test_native_htf_subscriptions.cpp). | K | L6 | F:F2 O:G-2 S:H1 | F says "no" on the same facts. "native" in these names means TradingView's native-timeframe bars, not the native host (the feed store's comment never from the chart's intraday prints sha256:3cc5174fa9a60fc0e2ca7870ef1cccd875abaf5d8e11248e633922612c18d9d8 engine_aux_security.cpp:51-61). |
| HT3 | Lower-timeframe arrays (request.security_lower_tf; register_security_lower_tf_eval include/pineforge/source/pine_strategy_host.hpp:847) | no — IntrabarPath::lower_tf drives matching only Closed: on_native_sub_bar native_host.hpp:948 delivers every retained lower_tf sub-bar after its matching path, so a host that put the lower bars in the path reads them bar by bar (tests/test_native_calc_timing.cpp); the series form is the auxiliary feed of HT4 (tests/test_native_auxiliary_feed.cpp). | K | L5 (sub-bar hook); series form optional in L6 | F:F4 O:G-3 S:H2 | Disagree on lane: F via on_native_sub_bar (the host already owns the lower bars it put in the path) / O, S via the series API. v1 = the L5 hook (R5-5), landed; the array form Pine returns stays the adapter's. |
| HT4 | Auxiliary finer feed driving request.security (set_aux_security_feed pine_aux_security.cpp:23-241, feed_aux_security_for_chart_bar pine_aux_security.cpp:262-488; the kernel base returns false set_aux_security_feed engine_consumer.cpp:158-161) | no Closed: NativeRunSpec::auxiliary_feed (NativeAuxiliaryFeed native_run_spec.hpp:524) plus NativeSeriesSource::AuxiliaryFeed build a series from the host's finer bars, routed by time alone, with declare_auxiliary_feed at begin and append_auxiliary_bars on a realtime stream (tests/test_native_auxiliary_feed.cpp, tests/test_native_auxiliary_feed_stream.cpp). | K+A | L6 | F:F3 S:H2 | Disagree: F quirk (TV split-feed harness construct) / S generic. R5-1 + R5-6: aux feed registration is generic; chart-slice mapping and deferred first-bucket publication stay A. Closed (lane INT28-FIX, rule RC): a calc_on_order_fills recalculation reads the calling bar's own slice, as the bar's close calculation does and as TradingView's does after the open fill or a later fill (a lookahead_on site through the slice's first bucket; lab tv tapes (the private fixture set coof_recalc_security): the bar's first recalculation before its close callback feeds it (scheduler_feed_aux_security pine_scheduler_native.cpp:812). The close callback fed it alone (scheduler_feed_aux_security pine_scheduler_native.cpp:674, now only when no recalculation did), after the bar's recalculations had read the previous bar's; a recalculation after that callback feeds nothing. |
| HT5 | Publication mode: lookahead, gaps, session / early-close boundaries (prepare_historical_security_lookahead_projections pine_scheduler.cpp:42-132, calling_bar_complete pine_aux_security.cpp:282-397) | no for a bare host Closed for a bare host: NativeTimeframeSubscription::lookahead and ::gaps are the two delivery rules, and the calendar boundaries — session close, early close by instrument class, the lazy seal of a clipped script interval — are the kernel's own (tests/test_native_htf_subscriptions.cpp). | K+A | L6 | S:H3 F:F1 O:G-1 | Disagree: F "no `lookahead_on`, by construction" / O lookahead = false default, available / S lookahead_on field, status partial. R5-6: user question (§4 U1); the exact TV projection stays A. |
| HT6 | Self-aggregation recipe (interim) | yes — TimeframeAggregator is public and engine-free timeframe.hpp:288-521, fed from on_native_input native_host.hpp:895; the Pine scheduler asks the kernel's script_bucket_completions native_execution_consumer.cpp:9449, which feeds the same aggregator through ScriptBucketCursor native_execution_consumer.cpp:14117, how its input span buckets (R5 lane B-ADAPTER) | K | L0 (document) | O:G-4 F:F1 | R5-6 |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| FP1 | Trigger geometry on the modeled path; fill at the level or at the gap price; market-if-touched | yes — native_matching native_execution_consumer.cpp:6971, first_region_entry native_matching.hpp:229-257; kind NativeCandidatePriceKind native_execution_consumer.cpp:7597; Limit::fill_through native_order.hpp:256-261 enforced where the matched price is limit-protected (protect_limit native_execution_consumer.cpp:7593) and re-checked after host terms (InvalidTerms native_execution_consumer.cpp:6184) | K | — | F:G3 O:H1 O:H2 O:H3 | — |
| FP2 | Slippage in ticks, limit-clamped | yes — slippage_ticks native_run_spec.hpp:652 applied once native_execution_consumer.cpp:5812, clamped by protect_limit native_execution_consumer.cpp:7593 | K+A | — | F:G1 O:H7 S:F2 | TV's round-then-slip order (source_bar_fill pine_adapter.cpp:15745) is A |
| FP3 | Commission: percent / cash per unit / cash per execution (fee_kind_for pine_adapter.cpp:754) | yes — NativeFeeKind native_run_spec.hpp:19-23; quote_execution_commissions engine_execution.cpp:1690 | K | — | F:G2 O:H8 S:F1 | — |
| FP4 | Booked fill quantized to the instrument tick, nearest or directional (nearest_tick pine_adapter.cpp:359, source_bar_fill pine_adapter.cpp:15745, directional_tick pine_adapter.cpp:412) | no — price_tick is only a slippage multiplier native_execution_consumer.cpp:7575; bar_fill_price engine.hpp:961 is reachable only through NativeCurrentPriceRule::NearestTick native_execution_consumer.cpp:7751; the reference helpers were dead (since deleted) Closed: NativeRunSpec::price_grid (NativePriceGrid native_run_spec.hpp:118) with grid_rounding (NativeGridRounding native_run_spec.hpp:128) rounds the fill basis onto price_tick before slippage, HalfUp or Directional (tests/test_native_sizing_price_rule.cpp quantizes the fill basis under HalfUp; tests/test_native_acid_composite.cpp books the Directional fills on the 0.25 ladder; the private test test_native_price_grid re-ran the pair under the overlay). | K | L8 | O:H4 O:H6 F:G4 S:F3 | Disagree: O kernel / F, S "stays adapter, no promotion". R5-3: own kernel lane. |
| FP5 | Trigger tested against the tick-quantized bar | no — the kernel tests raw prices against raw levels Closed: NativePriceGrid::QuantizeFillsAndTriggers additionally tests and re-validates every activation against the tick-quantized path, one rule for every trigger kind (tests/test_native_trail_best_seed.cpp puts an off-ladder trail seed on the ladder; tests/test_native_acid_composite.cpp runs a trail's best and its exit level on it; the private test test_native_price_grid re-ran the pair under the overlay). | K+A | L8 | O:H5 F:G4 S:F3 | R5-3: the optional quantized trigger test is K; TV's exact half-tick rule (source_trigger_threshold pine_adapter.cpp:469) is A on top. |
| PG | The Pine adapter on the kernel price grid: raw levels submitted, project() declaring QuantizeFillsAndTriggers + HalfUp, source_trigger_threshold deleted (lane R7; re-tested after L8b by gap lane N13) | measured-infeasible for the Pine adapter — B1 (the sub-tick cursor-print abort) is closed by L8b's ActivationGrid: 0 aborted runs under the trial (R7: 9 zero-offset-trail tapes + 6 POOC panels; now test_zero_offset_trail_rides_l4c 449/450, test_pooc_short_close_tick_l4d 183/183). B2 (TradingView quantizes per order kind, the grid is one run-wide rule) moved 30 pinned checks in 4 units when N13 last ran the trial, before lanes E14 and E16, with no adapter-side remedy: test_coof_market_limit_recross_l4c 24, test_stop_tick_rounding_l4d 3, test_adapter_grid_relower 2, test_zero_offset_trail_rides_l4c 1; 12 more checks in 3 units have an adapter-side cause (test_trail_fill_snap_l4c 2, test_native_margin_hooks 2, test_adapter_brackets_relower 8 book digests). Corpus: 5/312 probes differ, in the engine-only entry-incarnation column only (every probe runs a 0.01 tick on an on-grid feed, so it cannot arbitrate) | A | waived (R7, N13) | — | Waiver. No class of triggers is byte-identical on its own: the grid is a run-wide switch and a per-kind mask would spell TradingView's inconsistency into the kernel (not generic, R5-3). The adapter stays on NativePriceGrid::None and keeps source_trigger_threshold and its call sites; native-engine.md "The Pine adapter's grid re-lowering is waived". Native-only by ruling (audit lane P6): §3.6.2, with examples/native/native_price_grid_strategy.cpp and its C twin as the feature's hosts. |
| FP6 | Account-currency FX | partial — scalar account_fx + immutable NativeFxCurve native_host.hpp:1222 in batch; the stream then refused a non-empty curve (today only tick-driven input is refused under one, refuse_fx_curve_tick_input native_execution_consumer.cpp:10391). Closed by audit lane N6: the declared curve is the run's immutable FX epoch and a confirmed-bar stream runs under it exactly as a batch does (stream twin in tests/test_native_margin_fx_roll.cpp); no realtime rate ingestion and no tick-driven input under a curve are stated as permanent limits with their reasons (native-engine.md, "Stream FX"). Re-examined by R5 follow-up lane E3 and KEPT: the tick refusal was never only about the margin check — it guards the SCRIPT-VISIBLE conversions (open_trade_profit(), marked_equity(), the equity curve), which read active_account_currency_fx(), i.e. the presented clock the observation hook has not yet moved (re-measured: a step at T+90s left the T+100s and T+110s prints converting at the pre-step rate). E3's cursor rule reaches the margin model only, so the refusal stands and is pinned in tests/test_native_margin_fx_clock.cpp | K | unassigned → N6 (E3: refusal kept) | F:D9 S:F4 | Disagree: F out of scope ("O7 clock") / S its P10, a stream-safe FX epoch. No R5 lane → §4 U4 (answered by N6: S's "same curve in batch and stream", without a new clock or callback). |
| FP7 | (quirk) TV tick conventions: half-tick threshold source_bar_fill_tick pine_adapter.cpp:380 (used source_trigger_threshold pine_adapter.cpp:469, exit_limit_trigger pine_adapter.cpp:14911), source_bar_fill_tick pine_adapter.cpp:380, raw-vs-booked spelling source_same_point pine_adapter.cpp:398, margin-call fill pricing source_margin_fill_price pine_adapter.cpp:18384 | n/a — already isolated behind the terms seam (adapter resolve_terms pine_adapter.cpp:15421) | A | — | F:G4 O:H5 S:F3 | R5-1, R5-3 |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| RP1 | Per-lot MFE / MAE, kernel-sampled or host-owned | yes — sampler apply_excursion native_execution_consumer.cpp:3562, closing-row fold max_runup engine_orders.cpp:158-172; owns_lot_excursions native_host.hpp:1043-1047 | K+A | — | F:H1 O:I1 O:I2 S:X1 | Disagree: S partial + its P8 excursion-mode enum (single-source, not adopted, Appendix A.9). Lowered by R5 lane H-THIN: the Pine host keeps the kernel's owns_lot_excursions() default, so the sampler is the Pine excursion; the host model, its entry-bar masks and its caches are deleted, and the source layer's fold no longer carries them. Measured against TradingView first (R5 lane H-MEASURE, tests/test_e19_excursion_tape.cpp): the host-owned model was ab9714be's, not TradingView's — where it and the kernel sampler differ the kernel's number is TradingView's (entry-bar over-mask, exit-bar over-fold after an at-open exit, a calc_on_order_fills scratch TradingView reports as 0 / 0); both miss a bar's open print ahead of a same-bar stop fill. On the corpus the two models differ on 341 of 431 390 trades, and of the 326 of those a TradingView export identifies, the kernel's number is TradingView's on 315 and the host's on none. The lowering moved the cells of 335 trades in 19 corpus probes: 324 now equal TradingView's, 11 moved closer, none farther; the all-in residual's own entry-bar call is the tape of the private fixture set e19_allin_trim. |
| RP2 | Closed-trade list, trade statistics, report struct | yes — fill_report engine_report.cpp:72-151, get_trade engine.hpp:2042, compute_trade_stats engine_metrics.cpp:208 | K | — | F:H2 O:I3 S:X2 | — |
| RP3 | Equity curve | no — update_equity_extremes engine.hpp:1552 / record_equity_point engine.hpp:1570 are protected and were called only by the adapter Closed: NativeReportPolicy::KernelRecorded native_run_spec.hpp:63 has the consumer fold the extremes and append one point per script calculation, labelled with the script interval's open; KernelRecordedAtHostMarks records the same series at the host's own marks (tests/test_native_report_truth.cpp). | K | L2 | F:H3 O:I4 S:X2 | S's "partial" folds RP2 in |
| RP4 | Equity metrics (drawdown, run-up, Sharpe / Sortino, CAGR, time in market) | no — compute_equity_stats exits early on an empty curve engine_metrics.cpp:449; fill_metrics_section engine_report.cpp:393 Closed: with a recorded curve the drawdown / run-up walk is finite and every equity metric is computed over a real series; report_open_position_at_end adds the still-open position as a mark-to-market row (tests/test_native_report_truth.cpp). | K | L2 | F:H3 O:I5 | silent degenerate metrics, no error (O) |
| RP5 | Range-end row for a position still open at the end (scheduler_record_range_end pine_strategy_host.cpp:2254) | yes, shared — one generic producer NativeExecutionConsumer::append_open_position_report_rows, called by the kernel's run-end record_open_position_report_rows (KernelRecorded + report_open_position_at_end) and by the adapter at TradingView's mark; report_trade_count() engine.hpp:2063 includes the rows | K + A report shape | L2, Q6 | F:H4 O:I6 S:X2 | Disagree: S says kernel range-end rows exist (storage only). The ROWS are now the kernel's (§3.7 part 1); TV report shape — the equity re-mark, the extreme re-fold, the same-bar re-sort, three marks — stays A, measured in §3.7 part 2. The short-seed swaps (project_short_seed_report_rows pine_strategy_host.cpp:1795) stay A as well: a projection of two rows' entry_incarnation onto ab9714be's numbering, which TradingView does not export; measured against the kernel's own lot identities by tests/test_short_seed_report_swap.cpp (R5 lane H-MEASURE), and pinned by test_native_oracle_short_seed_percent_full_l2, whose two incarnation checks fail when the swap is skipped. The swap runs only when a later close of the seed id closes a remnant; a flat collision keeps the kernel's numbering. |
| RP6 | Per-trade accessors | partial — get_trade is public; the closed_trade_* family is protected engine.hpp:1606 Closed: closed_trade_count() / closed_trade(i) and report_trade_count() / get_report_trade(i) are public, and closed_trade_close_cause(i) engine.hpp:2094 answers the typed execution::CloseCause the closer recorded (tests/test_native_report_truth.cpp). | K | L2 | F:H6 | (single-source) |
| RP7 | Live order-action stream (runner ledger / webhook) | yes — stream_observe_actions_ engine_execution.cpp:1390, enabled stream_observe_actions_ native_execution_consumer.cpp:11139, C ABI strategy_stream_order_actions_len c_abi.cpp:820 | K | — | F:H5 | (single-source) |
| RP8 | Continuation hash | yes — native_continuation_hash native_host.hpp:1406, native_execution_consumer.cpp:12734; the spec is folded by hash_spec native_execution_consumer.cpp:240-262 | K | — | F:I1 O:J2 S:X3 | — |
| RP9 | Broker-state hash: scalar + per-bar recording | partial — scalar broker_state_hash engine_state_hash.cpp:22-118, C ABI strategy_broker_state_hash c_abi.cpp:668; per-bar rows are appended only by the source scheduler scheduler_record_broker_hash pine_strategy_host.cpp:3247 Closed: under KernelRecorded with set_broker_state_hash_recording(true) the consumer appends one broker_state_hash() row after each report point, so broker_state_hash_len == equity_curve_len == script_bars_processed on a completed run in batch and across a stream's warmup and realtime legs. A Failed or Aborted run can expose a shorter prefix. A row is the run's continuation identity at that bar, per driving mode — the broker half alone is mode-invariant (tests/test_native_report_truth.cpp). | K | L2 | F:I2 O:J1 S:X3 | O's "yes" covers the scalar only. §3.9 records the old quadratic adapter fold and closed-row kernel walk as history; V19-E and V19-A replaced them with running digests. |
| RP10 | Fold the host's own durable state into the hash | partial — the protected virtual hash_source_extension engine.hpp:541 is overridable but source-named; the kernel default folds "source:none" engine_state_hash.cpp:19-21 Closed: hash_host_extension(BrokerStateHashSink&) engine.hpp:535 is the generic seam — protected virtual on BacktestEngine, called once per hash, last — and hash_source_extension engine.hpp:541 is its deprecated spelling, which the generic default forwards to (tests/test_native_host_hash_extension.cpp). | K | L2 (the virtual rides the shared bump) | F:I3 O:J1 S:X3 | — |
| ID | Feature (adapter mechanism) | Native today | Class | Lane | Sources | Notes |
|---|---|---|---|---|---|---|
| OT1 | Key / value run parameters (set_input engine.hpp:2130, refused in-run by guard_native_mutation engine.hpp:2131) | partial — staging allowed pre-run, getters protected | K (low) | — | F:J1 | (single-source) Nothing for v1: native hosts take constructor parameters. |
| OT2 | Symbol info, sessions, calendars, timezones (timezone pine_adapter.cpp:2921, chart_timezone pine_adapter.cpp:2964) | yes — spec fields ticker native_run_spec.hpp:636-646; parse_session native_calendar.hpp:180-467; intervals on every decision context input_interval market_driver.hpp:123-124 | K | — | F:J2 O:G-5 | — |
| OT3 | Indicators, series, math, matrix, map, string utils | yes — ta.hpp includes only na/series/window_sum window_sum ta.hpp:2-8 | K | L11 (naming only) | F:J3 | (single-source row) TV-calibrated numerics → §2.ii b |
| OT4 | Cooperative abort | yes — request_abort engine.hpp:2434, NativeAbortReporting native_run_spec.hpp:41-44 | K | — | F:J4 | (single-source) |
| OT5 | C-level run configuration, stream, FX curve, contract probe | yes — strategy_configure_native_v1 c_abi.cpp:1082, FX curve strategy_configure_native_fx_curve_v1 c_abi.cpp:1153, probe strategy_execution_contract c_abi.cpp:1064, stream strategy_stream_begin c_abi.cpp:781 Extended: pf_native_run_spec_ext_v1's policy tail now carries intrabar, path_order, abort_reporting and the slot-label / feed-tolerance policies (PF_NATIVE_SPEC_EXT_INTRABAR, PF_NATIVE_SPEC_EXT_FEED_POLICY), so nothing of NativeRunSpec but identity is unreachable from C (tests/test_native_c_api.c). | K | — | O:J4 | The v1 C spec pf_native_run_spec_v1 pineforge.h:500 omitted intrabar, path_order and abort_reporting; they now travel in the extension's policy tail, pf_native_abort_reporting_e native_c_api.h:917 among them (F §2.iii) |
| OT6 | C-level order submission + strategy callbacks | no — none of the original runtime PF_API symbols; then listed as a refusal on native-engine.md Closed: strategy_native_host_create_v1 takes a C callback table and strategy_native_submit_v1 / _replace_v1 / _cancel_v1 / _cancel_all_v1 / _cancel_where_v1 / _execute_current_v1 command the same kernel under the same legality rule; the 41 strategy_native_* declarations plus two strategy_configure_native_ext_* declarations are a second, disjoint inventory beside the 94 runtime exports (and up to twenty generated per-strategy exports) (tests/test_native_c_api.c, tests/test_native_c_api_frozen_header.cpp). | K | L13 | F:J6 O:J5 | R5-7: post-v1. S covers it in its §2.3 without a row. |
| OT7 | (quirk) Pine language state: series, barstate / session flags, position-view freezing (PineLanguageState pine_language_state.hpp:12-68; publish_series pine_scheduler_native.cpp:367-402, chart_index pine_scheduler_native.cpp:671) | n/a | A | — | S:R1 | (single-source) Closed (E25, lane E20's finding 2; E26, E25's findings 1 and 3): session.islastbar reads one script bar ahead on every driving path: the chart timeframe's next retained bar, an aggregated chart's next bucket (a scheduler lookahead, deleted when the rule moved into the kernel), the calendar for the live tail's bar and for every bar of a stream, warmup included — a stream continues past its replay, so only a batch run's final bar reads "last" from having no successor. Since R4 slice C an aggregated or realtime bar read every in-session bar as last. What TradingView does: it ends a session at the session DAY — the flag belongs to the last chart bar whose successor belongs to another session day, in session or not, and session.isfirstbar is its dual, the bar after a session's last one. The ordinal is internal::session_trading_day_index timeframe.hpp:145, the exchange-timezone day rolling at the symbol's day stamp (09:30 ET on NYSE RTH, midnight on 24x7, 17:00 ET on a 1700-1700 forex session), not local midnight; the unmerged form, since a fused holiday daily bar still holds two intraday sessions. The three lab tv tapes (the private fixture set session_islastbar) give that rule bar for bar with no disagreement, and the engine now holds 255/255 NYSE:F last bars, 255/255 first bars and 370/370 ETH last bars on the chart-timeframe AND the aggregated path (it held 1, 0 and 1 before). Early closes come with it: the three NYSE:F half days flag 12:45 ET without a holiday calendar, because the next bar is the next session day's. Generic since R5 lane F5: the kernel computes that reading for every host — NativeDecisionContext::in_session market_driver.hpp:161 and the facts after it (opens_session_day, closes_session_day, closes_session_day_open_ended), in C the session bytes of pf_native_decision_v1 native_c_api.h:1491 — from the run's own calendar and input (present_session_day native_execution_consumer.cpp:8666), so a bare host reproduces the tapes flag for flag (the private test test_native_session_day_facts, kernel-only). The adapter computes no regular-day rule of its own: scheduler_update_session_state pine_strategy_host.cpp:2639 selects the kernel's facts into the flags, and only widens the plain session.isfirstbar / islastbar pair to the chart's day on an extended-hours chart (R5 lane K-SESSION-WINDOWS F2: TradingView's isfirstbar / islastbar span the pre- and post-market bars there while the _regular pair keeps the regular day, tests/test_session_extended_day_tape.cpp), which are PineStrategyHost members now (session_ismarket_ pine_strategy_host.hpp:1208; BacktestEngine holds no Pine session state), and only its live-probe tail reads the open-ended close at its batch's final bar (the private test test_session_day_facts_adapter). Selecting the kernel also ended a calc_on_order_fills defect of the adapter's lookahead, which read two bars ahead on a bar a fill recalculation had already published (a fill on a day's second-to-last bar flagged that bar last and the day's real last bar first). Ruled, not open: a bar with nothing held after it — a stream's bar, a live-tail bar — steps the calendar one width on, so it cannot see an early close the session string does not declare (ADR-0001, "Session-day facts at a bar with nothing held after it"). Closed (F1): a calc_on_order_fills recalculation reads the flags of the bar it runs on, not the previous bar's (the kernel presents the current bar's facts on that recalculation), and the close callback after it no longer looks one retained bar too far ahead (§3.8, AG3). |
| OT8 | (quirk) Live-tail / probe-suppress harness flags (realtime_tail_ pine_strategy_host.cpp:613) | n/a — contained since N14 (the flags were BacktestEngine members until then; now source::PineStrategyHost state behind two kernel virtual seams, §2.ii q) | A | N14 | F:J7 S:C5 | parity-campaign tooling |
include/pineforge/source/** and include/pineforge/compat/pine/** included, and src/compat/pine/market_admission.cpp sat in the main list for historical reasons (added in an earlier commit, before the source-layer list existed — F). Since L1: the source layer is its own list (PINEFORGE_SOURCE_LAYER_SOURCES CMakeLists.txt:91, market_admission.cpp at :94), appended to pineforge only when PINEFORGE_BUILD_SOURCE_LAYER is ON (add_library(pineforge STATIC CMakeLists.txt:180-183), beside the kernel-only archive (pineforge_kernel CMakeLists.txt:172), whose header install excludes source and compat (PATTERN CMakeLists.txt:487-489).source/ or compat/pine/ (F, O). The only code coupling is the forward-declared opaque source::StrategyOverrides* (§2.ii a), which needs no definition.libpineforge.a changes. Harmless for static linking, but check PINEFORGE_REQUIRE_ABI_RECEIPTS (CMakeLists.txt:65); what the receipts pin is unverified (F).| Aspect | F | O | S | Merged (L1) |
|---|---|---|---|---|
| Option | PINEFORGE_BUILD_SOURCE_LAYER, default ON | same | PINEFORGE_BUILD_KERNEL_ONLY | PINEFORGE_BUILD_SOURCE_LAYER (default ON; OFF = kernel only): 2 of 3, and it names the optional part |
| Targets | object libs pineforge_kernel_objs + pineforge_source_objs; pineforge_kernel STATIC, alias PineForge::kernel; pineforge STATIC = both | pineforge_kernel OBJECT; pineforge STATIC = kernel objects + conditional source list | pineforge_kernel + pineforge as the compatibility aggregate | F's shape. pineforge keeps its name, alias and install rules, so codegen-built strategies and find_package(PineForge) consumers do not move |
| What moves | compat/pine/market_admission.cpp → source list | same + delete dead admission_retention | same ("a concrete boundary bug") | Done. The unit is in PINEFORGE_SOURCE_LAYER_SOURCES CMakeLists.txt:91 with the other five compat/pine/ units, and admission_retention is deleted — the name is nowhere in src/ or include/. Its callers were the adapter only and tests (both retired with the legacy loop in R4 slice C, 73817c1d), so the move was link-neutral |
| Installed headers | OFF skips include/pineforge/source, include/pineforge/compat | install conditional on the option | installed closure with source + compat headers physically removed | Conditional install; S's closure test is the acceptance |
| CI / gate | link the two native examples + the 20 source-free native tests against pineforge_kernel only; nm assertion in the independence checker (it already forbids pineforge::source / compat::pine symbols check_native_include_independence.py:72-78) | new CI lane: configure with the source layer OFF, build, run the checker against that prefix | clean kernel-only compile; nm shows no source / compat symbol; keep the textual ban (FORBIDDEN_INCLUDE test_native_source_guard.py:43) as a fast preflight | All three, one CI lane | | Size | M | M | L | M (R5-11) |
| # | Item | Where / what it is used for | Class | Plan | Src |
|---|---|---|---|---|---|
| a | source::StrategyOverrides opaque pointer | Forward declarations in the kernel headers (since removed: no kernel header names the adapter type now); parameter of run_rich (execution_consumer.hpp:46-55, native_execution_consumer.hpp:148-157) and of the rich BacktestEngine::run overload (engine.hpp:2027, engine_consumer.cpp:81-93). The kernel only forwards it as NativeBeginArgs::overrides_opaque native_host.hpp:778; the source host casts it back overrides_opaque pine_strategy_host.cpp:620 | NEUTRAL in behaviour; a consumer-vtable signature change, so it rides the shared engine_script_run bump | const void* in the kernel signatures (F host_begin_extras, O overrides_opaque), typed convenience overload in source/pine_strategy_host.hpp; then drop the checker's single whitelist entry test_native_example_batch check_native_include_independence.py:42-46. S's alternative (NativeRunOverrides, or removing the rich overload) not adopted. Unverified: generated code calls the typed overload (codegen repo is not in this tree — F). Status (L11, 3f4fff5b): done — run_rich and the rich BacktestEngine::run take const void* overrides, the rich begin carries them as overrides_opaque, and the independence checker whitelists nothing (§2.vi row 6) | F O S |
| b | pine_float_compare.hpp | 1e-10 absolute equality band, used by kernel TA only: float_band_le ta_misc.cpp:30 (percentrank), float_band_gt ta_oscillators.cpp:462-463 (MFI), plus_dm ta_volatility_trend.cpp:337-340 (DMI); no execution TU includes it (O) | NEUTRAL (rename only) | Rename to a neutral header (F float_band_compare.hpp, O ta_compare_band.hpp), keep pine_float_* inline aliases in a source-layer header for generated code. Numerics must not move. S's behavioural ComparePolicy split is not neutral → not adopted (§4 U6). Status (L11 + N14): done — ta_compare_band.hpp is the kernel header; the alias shim is include/pineforge/source/pine_float_compare.hpp (N14 moved it off the kernel include root; it has no in-tree includer, generated code emits its own comparator) | F O S |
| c | NativeSlotLabelPolicy::LegacyTolerant, NativeLegacyTolerance (now NativeSlotLabelPolicy::FeedTolerant, NativeFeedTolerance) | NativeSlotLabelPolicy native_run_spec.hpp:324-353; read by legacy_tolerant_slot_labels market_driver.cpp:55-56 and native_feed_tolerance_enabled market_driver.cpp:43-49, and by legacy_tolerant_slot_labels native_execution_consumer.hpp:1122 (outside a run it answers the spec's spec_tolerant_slot_labels native_execution_consumer.cpp:2501; the reads are legacy_tolerant_slot_labels native_execution_consumer.cpp:2518, :10525); set only by the adapter slot_label_policy pine_adapter.cpp:2990. A feed-tolerance policy (raw strictly-increasing labels, zero / NaN-volume legacy bars), not TV semantics (O) | NEUTRAL | Rename with deprecated aliases (F RawIncreasing + NativeInputTolerance::{StructuralBars, WarmupNonNegativeOHLC}; O NativeLabelPolicy::ProviderLabels). Enumerator values stay 0/1 and 1/2: they are hashed slot_label_policy native_execution_consumer.cpp:277. Status: renamed as FeedTolerant / NativeFeedTolerance (the enumerator values unchanged), and the C transport carries them now, in the extension's feed-policy block (PF_NATIVE_SPEC_EXT_FEED_POLICY native_c_api.h:751) | F O S |
| d | Orphaned comments | The KI-62 block of engine_orders.cpp (fresh: comment-only, the bodies are deleted; S read it as live code) and a comment of engine.hpp; wider deleted-declaration blocks in engine.hpp; the money-rule essay in engine.hpp (the code is source_money_round pine_adapter.cpp:643) | NEUTRAL | Delete, or move to docs/ next to the adapter code that implements them. Status: done — R5 lanes E6 and E10 deleted the detached comment residue (a census of 0 over the kernel's compile closure) | F O |
| e | Dead helpers in the public kernel header | Done. All four are gone from engine.hpp: round_to_mintick_directional and apply_slippage were the pre-R5 spelling of the directional snap and the slippage step and the kernel now fills on native_matching::grid_round_directional / native_matching::apply_slippage src/native_matching.hpp:259 (the engine.hpp comment that named them went with R5 lane E10's residue sweep); apply_limit_fill and reserve_percent_commission are deleted outright, and only the adapter still spells the first two names, in its own comments. round_to_mintick engine.hpp:943 and bar_fill_price engine.hpp:961 stay, live through the call bar_fill_price native_execution_consumer.cpp:7752 | NEUTRAL | Reuse as the reference arithmetic of the price-grid and sizing lanes, delete the rest | O (single-source) |
| f | Dead legacy stream path | Done. stream_feed_input_bar and dispatch_source_stream_script_bar are both gone — neither name is in src/ or include/ — and the virtual left with the epoch that carried it. stream_state_hash engine.hpp:2017 stays, and the declaration/definition pairing is still enforced by scripts/test_native_source_guard.py | NEUTRAL (removing the virtual changes the vtable → shared bump) | Delete both | O; F asked (its Q6) |
| g | Legacy path resolver | Done, and then deleted. src/engine_path_resolve.cpp is 118 lines with five functions: bar_path_uses_high_first, both first_touch_position overloads, fill_bar_path_points_ordered, and BacktestEngine::declare_opened_lot_entry_bar_mask. Ten test files still mention resolve_exit_path_fill in historical comments; the resolver body and test-only oracle are gone. | NEUTRAL (pure move; verify with the link gate) | Keep the generic path helpers in the kernel TU; the old source resolver is not a live counterpart | F (single-source) | | | h | pine_*-named generic API | timeframe_time session_time.hpp:35-211 (live, e.g. session_in_market, which generated code reaches through pine_session_ismarket session_time.hpp:263), str_utils.hpp, deterministic_random math.hpp:9, tz_util timezone.hpp:8-30; the guard bans pine_* only inside IDENTIFIER_ROOTS (test_native_source_guard.py:34, test_native_source_guard.py:34-42) | NEUTRAL, M | Neutral aliases; pine_* kept as deprecated inlines for codegen. Large blast radius in generated code. Status (L11 + N14): done — session_time / str_utils / math / timezone in L11; N14 applied the same treatment to the two public matrix types: PineMatrix → NumericMatrix, PineGenericMatrix<T> → GenericMatrix<T>, old spellings kept as exact aliases (no version script pins either name) | O (single-source) | | i | pf_pending_order_v1_t Pine-named fields | tv_carry_qty pending_order_mirror.hpp:73, pine_birth_reach pending_order_mirror.hpp:135, pine_exit_activation_owner_cycle_at_birth pending_order_mirror.hpp:147-156, pine_frozen_market_instruction_kind pending_order_mirror.hpp:164-169; enumerated by PF_PO_FIELD pending_order_mirror.cpp:144-178; written only by pine_birth_reach pine_adapter.cpp:27800 | no change | Freeze (ABI). The neutral view arrives beside it (L7 / L13) | O (single-source) | | j | Pine-shaped lot flags | Mostly done. market_pyramid_add moved to the adapter side table the row asked for — market_pyramid_adds_ src/source/pine_state_hash.cpp:771, whose own comment records that it was PyramidEntry::market_pyramid_add — and ordinary_market_open, pooc_terminal_market_entry, ordinary_stop_open, bracket_slot_shadowed and entry_path_position are all deleted. What is left on the lot is the intrabar-fill excursion mask, skip_entry_bar_high / skip_entry_bar_low engine.hpp:161-162, hashed (skip_entry_bar_high engine_state_hash.cpp:80) and, since R5 lane E6, written by no host at all: a host that owns lot excursions declares where its fill sat (declare_opened_lot_entry_bar_mask engine_path_resolve.cpp:95; the Pine host did until R5 lane H-THIN left its excursions to the kernel's sampler) and the kernel derives the pair | HASH-VISIBLE | Move the live flag to an adapter side table keyed by entry_incarnation (the adapter already keeps PlacementTable pine_adapter.hpp:576-939) and fold it through hash_source_extension; delete the dead ones; update the hash-coverage waivers. Whether a kernel TU beyond the hash reads them is unverified (O) | F O S | | k | margin_call_enabled_ | Done. The generic kernel model is enabled by NativeRunSpec::margin native_run_spec.hpp:672. TradingView-specific toggling remains source::PineStrategyHost::set_margin_call_enabled pine_strategy_host.hpp:517; the frozen C symbol remains source-owned. | HASH-VISIBLE (R5-8) | Keep the source setter in the adapter; the kernel toggle is the presence of spec.margin | F O | | l | Kernel decodes adapter strings | Done. closed_trade_close_cause engine_trade_accessors.cpp:166 branches on no string at all: it reads Trade::open_at_end, then the typed execution::CloseCause the closer recorded on the row, then exit_from_bracket. A kernel-originated liquidation or risk flatten states its own cause through the settling fill, so a bare host reads 3 / 4 where the retired string derivation answered 1. The public C contract it implements is strategy_closed_trade_close_cause pineforge.h:1428 | HASH-VISIBLE (adds durable state) | Kernel enum class CloseCause recorded on the Trade at settlement (the kernel sets MarginCall when L4 liquidates; a source virtual covers adapter causes). C numbers unchanged. After L4 | O (single-source) | | m | request.security machinery | register_security_eval engine_security.cpp:27, set_native_security_feed engine_aux_security.cpp:77-131, SecurityEvalState engine.hpp:1158-1362 carry Pine semantics (lookahead, gaps, Heikin-Ashi, KI-55 range start) and are driven only by the source layer | HASH-VISIBLE / measured path | F planned to move both TUs and the state into the source host after native HTF. R5-6 narrows this: L6 reuses the feed store and routing, so those stay kernel; only the Pine-only evaluator semantics remain candidates. Do last. Status (lane L12b): done — the evaluator semantics are source::PineSecurityEvalState + src/source/pine_security_eval.cpp; the kernel keeps the registry, aggregator, feed store and one generic step | F S | | n | S-only proposals, not adopted | A generic FillModel interface with a TV model installed by the adapter (for the TradingView money / tick / path comment blocks in engine.hpp and the dead helpers of item e); "emit generic FIFO facts and let an adapter policy produce the KI-62 scratch" | — | Superseded by R5-3 (price-grid lane) and R5-1 (TV fill specifics stay behind resolve_execution_terms); the dead-helper part is item e; there is no scratch code left to re-lower (item d) | S | | o | pineforge::admission (market-admission journal) — N14 addition | was include/pineforge/market_admission.hpp (275 lines, installed by a kernel-only build) + src/market_admission.cpp (kernel TU) until N14 moved both under source/ (include/pineforge/source/market_admission.hpp, src/source/market_admission.cpp): the observation journal of TradingView admission reviews; every Configuration field is a strategy() declaration parameter. No kernel TU consumed it (engine.hpp included it and used nothing; the hash helper in src/broker_state_hash_internal.hpp had one caller, src/source/pine_state_hash.cpp) | NEUTRAL (pure move; the fold is unchanged, so no hash domain moves) | Status (N14): done — moved whole to include/pineforge/source/market_admission.hpp + src/source/market_admission.cpp (PINEFORGE_SOURCE_LAYER_SOURCES); hash_admission_field moved verbatim into the source hash TU; check_market_admission_schema.py re-pointed; MarketAdmissionDraft / MarketAdmissionJournal spellings and the market_admission_v2 inline namespace unchanged. ADR-0001 no longer calls it a generic stem | audit (Opus N14) | | p | TradingView trail tick arithmetic — N14 addition | src/engine_internal.hpp: kTrailPointsCeilEps (5e-5 tolerant ceil of trail_points, kept as the fallback without a usable mintick; the live tolerance is kTrailPointsCeilMintickScale × mintick ticks), trail_points_to_ticks, trail_offset_to_ticks, snap_trail_level_to_tick_grid; every value a lab tv calibration; readers only in src/source/ and src/compat/pine/ (the kernel's trail is TrailTicks, L7) | NEUTRAL (bodies byte-identical) | Status (N14): done — moved verbatim to include/pineforge/compat/pine/trail_ticks.hpp (pineforge::compat::pine); nine call sites re-qualified; test_trail_fill_snap_l4c includes the compat header (CHECK texts unchanged, twin parity OK) and is therefore a source-layer row | audit (Opus N14) | | q | OT8 live-tail / probe-suppress overrides — N14 addition | BacktestEngine::realtime_tail_, realtime_tail_horizon_bars_, probe_suppress_tail_logic_ with public kernel setters and the horizon walk apply_realtime_tail_horizon (engine_run.cpp); every reader in src/source/; two frozen PF_API setters in c_abi.cpp | HASH-WAIVED (configuration before and after; no domain moves) | Status (N14): done — as 2.ii k: state, setters, readers and the walk move verbatim to source::PineStrategyHost; the C exports stay in c_abi.cpp and reach the host through two kernel virtual seams (virtual bool set_realtime_tail, virtual bool set_probe_suppress_tail_logic) whose kernel defaults are accepted-and-inert (answer false, no error: the L1 pre-begin ingress contract test_native_example_batch pins on a native module); v16→v18 relocation manifest rows added, epoch v18 still open then (the host epoch has advanced since), no bump; kernel-only pin test_native_tail_override_seam | audit (Opus N14) | | r | Lower-timeframe merge-flag rule — N14 addition | internal::ensure_supported_lower_tf_emulation_flags (engine_lower_tf.cpp) threw "request.security lower TF emulation only supports lookahead=barmerge.lookahead_off and gaps=barmerge.gaps_off" from a kernel TU — the one executable Pine/barmerge string in libpineforge_kernel.a | NEUTRAL (message and refusal unchanged) | Status (N14): done — the predicate is a file-local helper in src/source/pine_security_eval.cpp; the kernel TU keeps the generic primitives (fixed intraday TF parser, input : requested ratio, evenly sampled sub-bars), which have kernel-only tests (test_lower_tf_parse_extra, test_lower_tf_seconds_suffix) and source-only production callers — retained as a generic capability | audit (Opus N14, Codex N4) | | s | Authoritative-feed period partition (engine_aux_security.cpp) — N14 ruling | "W"/"M" from the installed daily feed; feed stamps as the period partition; trade date = session-day of the period's last chart bar (§2.iv item 6, §4 E8). A bare host that installs authoritative_bars inherits these | — (documented contract, no gate) | Ruled (N14): retained as the generic contract. The three rules follow from "a feed is the venue's own bars of one timeframe" and from nothing platform-specific; the policy knob is the feed itself (install none → plain aggregation of the input; the doc says so, native-engine.md "Authoritative bars"). A separate partition-policy field would be hash-visible spec surface for a choice the host already makes by installing or not installing the feed. The TradingView pins stay as the calibration evidence. Listed in ADR-0001's residual table | audit (Opus R-7, Codex "TV-shaped auxiliary-feed policy") | | t | Early-close completion keyed by instrument class (engine_security.cpp session_template_knows_early_close) — N14 ruling | Branches on SymInfo::type ∈ {forex, cfd, crypto} (continuous-session OTC classes never complete a calendar period at an early close; the OANDA pins in timeframe.hpp) | — (documented contract) | Ruled (N14): retained. SymInfo::type's vocabulary is fixed by the frozen C ABI (strategy_set_syminfo_type), so classifying by it is the kernel's own market-structure vocabulary, not a source-language one; the adapter's routed sites (R3b) and the bare host's subscriptions share the rule, which keeps the corpus byte-identical by construction. Stated on the function and in ADR-0001's residual table | audit (Opus R-8, design §4 E8) |
History: the plan L13 worked from. The surface shipped as <pineforge/native_c_api.h>, R5 lane F4 routed every policy hook, and what the 1.0 C surface still does not expose is the 1.0 C boundary table of docs/pages/native-engine.md. "Today" below is the tree L13 read.
Today a C host can configure (strategy_configure_native_v1 c_abi.cpp:1025-1103), stream (strategy_stream_* c_abi.cpp:743-864) and read results, but strategy logic is C++-only and the batch entry points (strategy_create, run_backtest*) are per-strategy symbols a native author hand-writes. Design retained from F (most complete), with O's event polling and S's hardening rules:
| Point | Decision | Src |
|---|---|---|
| Implementation | One new kernel TU (F src/native_c_host.cpp, O c_native_host.cpp, S native_c_api.cpp): class CCallbackHost final : public NativeStrategyHost forwarding each virtual to the table. Streaming reuses the existing strategy_stream_* symbols unchanged (they take any BacktestEngine*) | F O S |
| Errors | A non-zero callback return maps to NativeFailureCode::CallbackException; C callbacks must not unwind. The C++ path already latches exceptions as CallbackException native_execution_consumer.cpp:8621 | F O |
| Legality | The existing rule (commands_allowed native_execution_consumer.cpp:1966): inside a callback, or between realtime inputs | F |
| Hardening | Tagged, size-prefixed PODs; struct_size + version; unknown-tag refusal; never cast the C request to a C++ variant; a C fixture compiles against the frozen previous header. S puts the API in a separate header include/pineforge/native_c_api.h | S |
| SOP | Every runtime symbol goes into src/c_abi.cpp, its public header (pineforge.h or an additive runtime header), EXPECTED_RUNTIME check_c_abi_runtime.py:29 and both counts (EXPECTED_PUBLIC_DECLARATIONS check_c_abi_runtime.py:143, EXPECTED_RUNTIME_IMPLEMENTATIONS check_c_abi_runtime.py:144), the README table and consuming Python harnesses, or the source-guard-c-abi stage fails, in preflight and every ci_verify profile (CLAUDE.md) | F O S |
| Not in v1 of the C API | resolve_execution_terms / validate_execution_precommit are the adapter's seam; a C host that needs sizing uses Sized (F). O offers an optional resolve_terms callback: deferred. History: R5 lane F4 routed both (on_execution_terms, on_precommit); the terms hook's units answer outside a host-sized close is still C++-only (the 1.0 C boundary table) | F O |
| Open | Naming: F and O use pf_native_*; S uses strategy_native_*, which matches the existing runtime symbols. The v1 C spec pf_native_run_spec_v1 pineforge.h:500 omitted intrabar, path_order and abort_reporting; the extension's policy tail carries all three now (pf_native_abort_reporting_e native_c_api.h:917) | F S |
Depends on L7 (working view), L3 (so Sized is in v1 of the C request) and the epoch batch.
| F (its §2.iv A) | O (its G10) | S (its P7) | Chosen | |
|---|---|---|---|---|
| API shape | spec subscriptions + push callback on_native_timeframe_bar | register_native_series + pull native_series_bar(id) / native_series_slot_is_new(id) | spec security_requests + configure_native_security_feed + native_security(id) view | F's subscription in the spec + push callback, plus O's pull accessor |
| Engine reuse | none: generalize the consumer's one script bucket (ScriptBucket native_execution_consumer.hpp:840-855, contribute_input native_execution_consumer.cpp:9637, seal_script native_execution_consumer.cpp:9547) to N; rejects exposing the request.security path as Pine-shaped end to end | promote the existing evaluator + feed machinery; edits engine_security.cpp, engine_aux_security.cpp, engine_lower_tf.cpp | same TUs + native_calendar.cpp | The existing feed machinery, called, not edited; engine_security.cpp untouched |
| Pump | consumer seal logic | accepted-input path (invoke_input_callback native_execution_consumer.cpp:8492), only for natively registered series | copy at Ready, evaluators created before the first input | O's registration-gated pump + S's copy-at-Ready |
| Authoritative bars | authoritative_bars on the subscription | Source::ExchangeFeed through set_native_security_feed | configure_native_security_feed(id, bars, tf) | F's field, installed through the existing store |
| Lookahead | none, by construction | lookahead = false default, available | lookahead_on = false, gaps_on = true | open then → §4 U1; landed as lookahead and gaps, both default false (HT5) |
std::vector<NativeTimeframeSubscription> subscriptions (Appendix A.5), hashed only when non-empty (digest like native_intrabar_path_digest).subscriptions is non-empty (zero work for the adapter): register one evaluator state per subscription through the existing register_security_eval (engine.hpp:1485-1486, fresh); install authoritative_bars into the existing store (set_native_security_feed engine.hpp:1958, engine_aux_security.cpp:79-133); call prepare_native_security_feeds (engine.hpp:1859, defined BacktestEngine::prepare_native_security_feeds engine_aux_security.cpp:136, fresh) — the "made native-callable" step: reachable from the native consumer, still refused from host code in-run; pump feed_security_eval_state (engine.hpp:1502, fresh) from the accepted-input path; deliver each completed bucket through on_native_timeframe_bar.engine_security.cpp, the Pine scheduler sequence pine_strategy_host.cpp:1566-1626 and its call order. The per-run evaluator init is today a source-layer member (PineStrategyHost::init_security_eval_states_for_run pine_scheduler.cpp:9-39, declared init_security_eval_states_for_run pine_strategy_host.hpp:971, fresh), so L6 adds a kernel-side twin instead of moving it.LazyComplete bucket, the next period's first input bar — immediately before that input's script calculation, never earlier — and never before an earlier script interval's lazily sealed calculation has run: the pump is ordered against the script interval, seal(k) → deliver(i+1) → calc(i+1) (L6d).on_native_input + TimeframeAggregator (HT6). Doc notes (O): set_native_security_feed is public but inert without evaluators and throws in-run (guard_native_mutation engine_aux_security.cpp:83, which latches UnsupportedSource native_execution_consumer.cpp:2019); set_aux_security_feed returns false in the kernel engine_consumer.cpp:158-160.set_native_security_feed sha256:eca3dbea0baa461982ba89f13d0a5c43277854069fb6eb2dd2f5c5471313204d engine_aux_security.cpp:51-83): the feed machinery is TV-calibrated. "W" / "M" buckets are built from the installed native daily bars, and the native stamps define the period partition (a holiday session folds into the next trade date's bar). A bare host that supplies authoritative_bars inherits those rules. Document them as the contract or gate them behind a partition policy — decide in the lane, not silently (§4 E8).NativeRunSpec::auxiliary_feed (NativeAuxiliaryFeed{tf, bars}, strictly finer than input_tf, opt-in and hashed only when present) plus NativeTimeframeSubscription::source = NativeSeriesSource::AuxiliaryFeed build a series from the host's finer bars through the existing evaluator step (register_security_eval against the feed's timeframe, feed_security_eval_state per feed bar — called, not edited); routing is by time alone (a feed bar rides on the first accepted input whose period it opened before), with declare_auxiliary_feed at begin, append_auxiliary_bars on a realtime stream and a C spelling (PF_NATIVE_SPEC_EXT_AUXILIARY_FEED tail, strategy_native_append_auxiliary_bars_v1). No epoch moved: native_run_spec_v3 and engine_script_run_v18 were open then (no release tag contained them, no frozen provider fixture of either existed), exactly as L6c's gaps and begin-time hook were added. BacktestEngine::set_aux_security_feed stays the source host's door and still answers a silent false on a bare host (the pre-begin setter pins, tests/test_native_example_batch.cpp among them, hold it to that). Retained, why: the adapter does not re-lower its auxiliary sites, on three measurements — 0 of 312 corpus probes install an auxiliary feed, so corpus byte-identity cannot witness the move; TradingView's chart slice leaves pre-range feed coverage inert where the kernel folds it by time (two hourly buckets against three over one feed, tests/test_native_auxiliary_feed_twin.cpp row B); and the adapter evaluates a chart bar's slice at its calculation, after that bar's matching pass, where the kernel delivers before it (position 1 against 0 at the same bucket, row C), which no predicate over an opaque generated evaluate_security can prove neutral. The congruent shape is the same series on both (row A) and can move once a population exercises it. Full contract: docs/pages/native-engine.md, "The auxiliary finer feed".Status (L10): done. examples/native/ holds eighteen hosts, sixteen C++ and two C, built by PINEFORGE_BUILD_EXAMPLES; include/pineforge/native_module.hpp defines PINEFORGE_EXPORT_NATIVE_STRATEGY(Class). The bullets below are the plan L10 worked from.
runner/examples/native_market_strategy.cpp and native_selected_strategy.cpp to a top-level examples/native/, built by PINEFORGE_BUILD_EXAMPLES (then "none yet" and guarding nothing CMakeLists.txt:37; the examples build only under PINEFORGE_BUILD_LIVE_RUNNER CMakeLists.txt:520-522, with native_market_example runner/CMakeLists.txt:54). Link the kernel target (S), or PineForge::pineforge during migration (O) — no SQLite / curl / OpenSSL (the optional executable's find_package calls, runner/CMakeLists.txt:2-4).main() that runs batch + stream on embedded bars (the guide already contains that main native-engine.md:3284-3351) and (2) the same MODULE targets the runner tests load; keep the C-ABI shims so the live runner can still dlopen them (F, O, S).native_market_example runner/CMakeLists.txt:54, NATIVE_EXAMPLES check_native_include_independence.py:49, the tests at test_native_example_batch runner/CMakeLists.txt:158.include/pineforge/native_module.hpp with PINEFORGE_EXPORT_NATIVE_STRATEGY(Class) to replace the ~70 hand-written extern "C" lines per example (F, single-source).native-engine.md fixesStatus: applied. Every row below has been carried into docs/adr/0001-kernel-adapter-boundary.md and docs/pages/native-engine.md, and both pages are now held by scripts/check_doc_anchors.py (every path:line names its symbol) and scripts/check_doc_lint.py (no roadmap label, no unverified negative claim, no stale epoch). Several rows were overtaken by the code while they waited: rows 2, 3, 4, 8 and 9 describe a tree that no longer exists, and the ADR now says so in its own words. The rows are kept as the audit's record of what each page asserted, not as a work list.
Union of F §2.vi (9), O §2.vi (5 + 1) and S §2.6 (7), deduplicated. S had no ADR file in its checkout and wrote "what an ADR in this tree must say"; rows 11-12 come from that.
| # | ADR claim | Correct statement | Evidence | Src |
|---|---|---|---|---|
| 1 | Orders are "Market / Limit / Stop" (ADR L72-75) | Also StopLimit and Trail; five intents, five owner kinds, groups, PointBudget | Trigger native_order.hpp:313, OrderIntent native_order.hpp:246, Owner native_order.hpp:519, Group native_order.hpp:528, PointBudget native_order.hpp:396 | F |
| 2 | "The kernel matches and fills … with sizing, slippage, fees, margin and settlement" (ADR L74-75) | "with slippage, fees, opening admission and settlement; sizing is resolved by the host". NativeRunSpec owns fees, FX, close timing, direction / caps and intrabar paths; it does not own percent / cash sizing, maintenance margin, calc-on-fill, every-tick calculation or bare-host security registration | at 73817c1 (overtaken; ADR-0001, "Current state vs. target") | O S |
| 3 | "Read results via `metrics.hpp`" (ADR L76-80) | Trade stats yes. Equity curve and equity metrics are empty for a bare host, silently, and an open final position is missing from the report | at 73817c1 (overtaken; ADR-0001, "Current state vs. target") | F O |
| 4 | HTF "comes from the kernel feed `set_native_security_feed` / `prepare_native_security_feeds`" (ADR L84-89) | Not usable by a bare host: a partial boundary, not an absent kernel. prepare_native_security_feeds is protected with one caller; configure_security_evaluators has one caller; the public setter is inert pre-run and refused in-run | prepare_native_security_feeds engine.hpp:1859, configure_security_evaluators engine.hpp:1512, set_native_security_feed engine.hpp:1958, guard_native_mutation engine_aux_security.cpp:83 | F O S |
| 5 | "`engine_aux_security.cpp` includes `source/pine_strategy_host.hpp`" (ADR L129-132) | False at 73817c1: its includes are engine_internal.hpp, ta.hpp and std headers (fresh re-read; the run-failure codes added run_failure.hpp, a kernel header). No kernel TU includes a source header | sha256:560a7f68b243d921382a69ed0390c398f28370ac066b84f30b5f96cbb385fb60 engine_aux_security.cpp:6-14 | F |
| 6 | "Kernel TUs reference adapter types (`source::StrategyOverrides` …)" (ADR L129-131) | True only as a forward-declared opaque pointer the kernel never dereferences; the independence checker whitelists exactly that symbol | at 73817c1; overtaken by 3f4fff5b (§2.ii a), after which the rich begin carries overrides_opaque native_host.hpp:778 and the independence checker whitelists nothing | F O |
| 7 | engine.hpp hosts "the ten-significant-digit money rule, KI-62, the `strategy.close` batching + entry-id ledger, a TradingView margin-call toggle" (ADR L131-134) | Two of four survive as code. Money rule: a comment block; the arithmetic is in the adapter. KI-62: orphaned comments + one live, hashed lot flag. Close batching + id ledger: gone from the kernel (the ledger is in the adapter; the old names survive only in comments). Live: margin_call_enabled_, the KI-62 flag and — unmentioned by the ADR — string-sentinel decoding of adapter comments | the money-rule block was lines 927-961 of engine.hpp and R5 lane E6 deleted it (lines 73-180 were and are live code), the arithmetic source_money_round pine_adapter.cpp:643 and its twin tv_money_round pine_policy_support.hpp:9-15; skip_entry_bar_high engine.hpp:161, skip_entry_bar_low engine_state_hash.cpp:80, declare_opened_lot_entry_bar_mask engine_path_resolve.cpp:95; the ledger close_logical_units_ pine_adapter.hpp:2839; closed_trade_close_cause engine_trade_accessors.cpp:166. Overtaken: the margin-call toggle and the string decoding are both gone — see ADR-0001, "Current state vs. target" | F O |
| 8 | "No kernel-level C API. `c_abi.cpp` exports only version/descriptor symbols" (ADR L136-138) | 94 runtime PF_API symbols: native configure, the 12-symbol stream family, pending-order views, broker-state hash, security feeds, FX curve, reports, scratch-table selection, path/fd report sinks and the report-only T-summary query. The 94 compiled-strategy runtime symbols in c_abi.cpp are distinct from the native C host API in native_c_host.cpp: the latter exposes submit, replace, cancel and a callback table to a C host. | strategy_closed_trade_entry_incarnation, strategy_initial_capital, strategy_configure_native_v1, strategy_stream_begin, strategy_stream_fill_report, strategy_pending_orders_len, strategy_position_cycle_seq, strategy_set_broker_state_hash_recording, strategy_broker_state_hash, strategy_set_aux_security_feed, strategy_set_native_security_feed, strategy_configure_native_fx_curve_v1, strategy_set_scratch_tables_v1, strategy_report_sink_receipt_v1, strategy_set_report_sink_fd_v1, strategy_report_sink_summary_v1, pf_report_scratch_arena_stats in src/c_abi.cpp; EXPECTED_RUNTIME in scripts/check_c_abi_runtime.py | F O S |
| 9 | "No examples. `PINEFORGE_BUILD_EXAMPLES` is 'none yet'" (ADR L139) | The option is inert, but two Pine-free NativeStrategyHost examples exist, are compiled by the independence checker and tested through the live runner, only under PINEFORGE_BUILD_LIVE_RUNNER. Wording: "no top-level examples target" | at 73817c1 (overtaken; ADR-0001, "Current state vs. target") | F O S |
| 10 | Follow-ups (1)-(4) (ADR L168-170) | Add native reporting, sizing, margin, HTF — and, per R5, calc-timing, price grid and order ergonomics. Without them "usable without Pine" is a build-system claim, not a feature claim | §3.5 | F |
| 11 | Build layout | One pineforge library always appends the source list; a kernel-only target does not exist yet; compat/pine/market_admission.cpp sits in the main list, a boundary bug. Overtaken: PINEFORGE_KERNEL_SOURCES CMakeLists.txt:118 and add_library CMakeLists.txt:169 build PineForge::kernel, and the compat unit is in PINEFORGE_SOURCE_LAYER_SOURCES CMakeLists.txt:91 | add_library CMakeLists.txt:169 | S (F, O agree in §2.i) |
| 12 | Host constructors | NativeStrategyHost is a zero-argument C++ host with the callbacks and the request API; PineStrategyHost is the class that takes CapAttachment — do not attribute it to the native constructor | NativeStrategyHost native_host.hpp:865, PineStrategyHost pine_strategy_host.hpp:289 | S (single-source) |
docs/pages/native-engine.md fixes, all applied (F unless noted). The page is cited by section rather than by line, because a reference page's line numbers move with every edit and the campaign's own anchor audit found that the first thing to rot:
| Where | Said | Tree says |
|---|---|---|
| the page's opening, and its "close-only" claims | callbacks are close-only | The kernel has a bar-open hook (on_native_bar_open native_host.hpp:909), a tick hook (on_native_tick native_host.hpp:898), a post-fill hook (on_native_applied native_host.hpp:958), a recalculation hook (on_native_recalculate native_host.hpp:933) and a sub-bar hook (on_native_sub_bar native_host.hpp:948). The page's opening now tables all eleven, and on_native_applied is documented as the calculate-on-fill hook with suffix eligibility |
| the request-epoch sentence | native_order_v4 | native_order_v7 native_order.hpp:41 | | the two host-epoch sentences | host epoch v16 | engine_script_run_v19 native_host.hpp:20 | | "What still requires Pine compatibility to build" | BacktestEngine still holds CapAttachment / OrderPriority / IntradayCap | engine.hpp has zero references to them; the cap type is CapAttachment intraday_cap.hpp:19 and the constructor is PineStrategyHost's pine_strategy_host.hpp:289 | | the rich-run paragraph | the rich run overload fails a native host | run_rich native_execution_consumer.cpp:10973 shows no such refusal (still unpinned by a test; the page says so) | | added | — | the self-aggregation recipe beside the subscriptions it is an alternative to; the open-bar lookahead warning with current_partial_bar native_host.hpp:1093; and set_native_security_feed documented as the pre-run door for a series' authoritative bars, refused in-run |
project() (pine_adapter.cpp:2732-3018) never sets the field and never emits the kind, so adapter runs are byte-identical by construction. The four feed-shape fields are folded into hash_spec unconditionally today, including defaults (native_execution_consumer.cpp:252-360), so even a defaulted new field would move every continuation hash; the precedent for a conditional digest is precommit_digest_ (native_execution_consumer.hpp:2096). Each lane adds a test that a default spec hashes to the pre-change constant. Every new durable field is hashed (check_broker_state_hash_coverage.py, fail-closed, waivers in broker_state_hash_waivers.txt). Proof per PR: scripts/run_corpus.sh + scripts/verify_corpus.py locally, then ONE campaign sweep on the final tree, run by the maintainers (fixed population, 4190 probes when this plan was written, zero tolerance on the hard band — native-refactor-progress.md:42-48; a fresh exact-HEAD PASS verdict is required, sha256:59adeba8aabaefa740633d52e9216b49065b28b397b87f22c57ad5e19f2948c2 AGENTS.md:149-150). A lane that cannot be shown neutral by construction does not merge.NativeStrategyHost on the same bars and spec. Trade lists are diffed by identity (entry / exit time, price, qty, pnl, commission), never by trade number; then event, ownership and hash detail (S's D → E order). A difference is allowed only where a named TV-quirk row of §1 is active, and each one is itemized. Because 430 test files drove the adapter and 14 the native host when this plan was written (O; the line snapshot has 486 tests/*.cpp naming source::PineStrategyHost and 128 naming NativeStrategyHost, while the main snapshot has 543 and 110 respectively), every lane also adds native-only tests: a lane is not accepted on the corpus alone, nor on a unit test alone (S).native_order_v5, engine_script_run_v17 and native_run_spec_v2 when this plan was written; on this tree they are native_order_v7 (native_order.hpp:41), engine_script_run_v19 (native_host.hpp:20) and native_run_spec_v3 (native_run_spec.hpp:15): the budget below was spent exactly once, and the v19 value epoch (R5 lane V19-A) took the host epoch one step past it. One bump each per release batch: native_order_v6 = L3 + L7 + L4's origin / event; engine_script_run_v18 = L4's host hooks + L5 + L6 virtuals + L2's hash_host_extension + §2.ii a, f; native_run_spec_v3 = the spec blocks of L2, L4, L5, L6, L8 (L9 if it ships in the batch). A lane that lands before its batch closes stages behind the open epoch, so codegen-built strategies rebuild once per batch. Epoch lanes are not neutral-refactor lanes: the brief lists the header extension explicitly; the frozen C++ ABI fixtures (tests/fixtures/native_cpp_abi/host-*) and the ABI checkers (check_native_cpp_abi.py, check_settlement_cpp_abi.py) move with them; variant_size pins change (OrderIntent native_order.hpp:246, CommandEvent native_order.hpp:1396). Every new test unit compiles first against the frozen previous-epoch header closure (fail-before, CLAUDE.md).CLAUDE.md): ci_preflight.py, then ci_verify.py release; plus check_c_abi_runtime.py and check_native_include_independence.py. No campaign sweep from a worker.| Lane | Concern | Size | v1 | Depends on | Epoch impact | Input lanes F / O / S |
|---|---|---|---|---|---|---|
| L0 | Docs truth pass: native-engine.md fixes + the ADR-0001 correction list (§2.vi); interim HTF recipe; open-bar lookahead warning | S | yes, first | — | none | L0 / — / — |
| L1 | Kernel-only build target; move compat/pine/market_admission.cpp; installed-header closure; CI lane (§2.i) | M | yes | — | none | L1 / L1 / A |
| L2 | Report truth for native hosts: equity curve, equity metrics, range-end row, per-bar hash, host hash extension, per-trade accessors | M | yes | — (∥ L3) | native_run_spec; the hash virtual waits for the shared engine_script_run bump | L2 / L2 / G (part) |
| L3 | Sizing bases (percent-of-equity, cash, fee-net), placement-vs-fill timing, fractional reduce, sibling reservation | M/L | yes | — (∥ L2) | opens native_order_v6 | L3 / L3 / C + B (reservations) | | L4 | Margin model: per-side initial + maintenance, liquidation policy / events / price, host sizing hook, kernel-originated request | L | yes | L2 (equity marking — O); state-hash coverage | all three epochs | L4 / L4 / D (margin part) | | L5 | Calc-timing contract: partial-bar view, open-bar guard, sub-bar hook, calc-on-fill re-entry, every-tick | M/L | yes | runs after L7 in the R5-11 order; ∥ L6 | engine_script_run_v18, native_run_spec | L5 / L7 / E | | L6 | Native HTF / aux feed registration for bare hosts (§2.iv) | L | yes | L1; user decision U1; ∥ L5 | engine_script_run_v18, native_run_spec | L6 / L8 / F | | L7 | Order ergonomics + bracket / trailing completeness: builder, relative anchors, zero / tick offsets, retain-trail replace, cancel-all / by-label, working view | M | yes | L3 (shared native_order epoch) | native_order_v6 | L7 / L9 / B | | L8 | Price grid: quantized fills, optional quantized triggers | M | yes | L3 (sizing uses the grid price — O) | native_run_spec | — (quirk) / L6 / G (as FillModel) | | L9 | Risk limits: strategy.risk.* equivalents, halt semantics, day-boundary basis | M | in scope, post-v1 | L4 (kernel-originated flatten), L2 (drawdown needs equity) | native_run_spec, one event | L9 / L10 / D (risk part) | | L10 | Examples under PINEFORGE_BUILD_EXAMPLES, native_module.hpp, native API reference, dual-run harness (§2.v) | S | yes | L1; one example per landed lane | none | L8 / L12 / J | | L11 | Neutral coupling extraction, small PRs (§2.ii a-i) | M | post-L1, any time (a, d wanted for v1 — F) | L1 (link gate) | a, f ride engine_script_run_v18; the rest none | L11 / L11(a) / I (part) | | L12 | Hash-visible TV extraction (§2.ii j-m); explicit campaign exception | L | post-v1 | L4 (k, l), L6 (m), a planned hash-domain bump (j) | broker-state hash domain | L12 / L11(b) / I | | L13 | C order / callback API (§2.iii) | L | post-v1 | L3, L7, the epoch batch | new PF_API symbols only | L10 / L5 / H |
Order: L0 → L1 → (L2 ∥ L3) → L7 → L8 → L4 → (L5 ∥ L6, one shared epoch bump) → L10 → L11 → L9 → L12 → L13.
main)Two independent claimed-vs-actual audits ran after the L-series, each proposing lanes of its own. These are the ones that landed, with the last commit of each on main — git log --grep "lane <id>" finds the rest of a lane's commits:
| Lane | What it did | Landed |
|---|---|---|
| P2 | scripts/check_kernel_residuals.py: strings/nm over libpineforge_kernel.a held against ADR-0001's residual tables, as a kernel-profile stage and a CTest row in every profile. Ruled the eighty-nine pending-row names the first probe was blind to (coof_*, pooc_*, market_admission_*, tv_carry_qty) by family, and the ambient EMA seeding default by mechanism — ta::EmaSeeding became a named per-instance option | c421b7a9 |
| P2b | Made that gate profile-independent: it reads the linkable surface (nm over the archive, strings over a debug-stripped copy) rather than debug info, and carves out the source-path literals a sanitizer build writes into rodata | 8e099884 |
| P3 | scripts/check_corpus_parity.sh --subset runs the same byte oracle over a subset a pull request can afford (its first 30 probes when P3 landed; scripts/corpus_parity_subset.txt names 54 now). Since the 2026-09-25 merge-gate change, this GitHub Actions run is advisory; the maintainers' full parity verdict posts the required pineforge/parity status | 9b831117 |
| P4 | The C surface's asymmetries: PF_NATIVE_MARGIN_CHECK_FX_ROLL under its own C name, a C host's recorded route to a closed row's exit ticket, and the calculation cadence on the migration page | 98b94672 |
| P5 | The untested capabilities: the market-if-touched geometry of Limit{price, fill_through} kernel-only, closed_trade(i) as the closed row by reference, and a kernel liquidation seen through native_open_lots() under NativeRunSpec::margin | 41ca2ee8 |
| P6 | The standing question "is a spec field the adapter never sets a decision or dead weight?" answered for all ten then (eleven now, with quantity_tolerance; §3.6.3): scripts/check_native_feature_rulings.py and ADR-0001's ruling table, with three native examples added | ceb07e4a |
| P7 | Gate hygiene: RELEASE_MIN_TESTS, example rows that assert the exit code and the summary line through run_example.cmake, and check_adapter_spec_shadowing.py promoted to a ci_verify source guard | 68f590aa |
| P9 | One fill simulation: the trail suites' resolver rows became a matcher-side projection, and the second simulation was deleted — see §2.ii row g | aadfbe7e |
| Q4 | The per-bar broker hash is ruled per driving mode, pinned in both directions; the broker half alone is mode-invariant | 8e558eb9 |
| Q6 | One producer for the range-end report rows, with the report shape measured against it — see §3.7 | b16e11f6 |
| Q9 | The runtime-budget row gates on process CPU time, not wall clock, so its verdict survives a 4x change in host load | 22e6878d |
| Q12 | The CTest row floor counts rows that ran; a skipped row is listed, not counted; and assert() is live in every examples/native target, which ci_verify refuses a configure without | 5a1723c4 |
Lanes the audits proposed that landed on main through R5 lane L14-C (5691220c, 50339d04): Q5 (ADR rule 2 against the kernel's TradingView-calibrated semantics — ADR-0001, "TradingView-calibrated kernel mechanisms") and Q10 (this section's §1 closure markers and scripts/check_design_inventory.py).
| Lane | API proposal (signature level → Appendix) | Neutrality proof (§3.1 a) | Twin test (§3.1 b) |
|---|---|---|---|
| L0 | — | docs only | — |
| L1 | option PINEFORGE_BUILD_SOURCE_LAYER, targets pineforge_kernel + unchanged pineforge (§2.i) | same TUs, same flags; ctest + corpus | kernel-only link of the two examples + the 20 source-free native tests; installed-header closure |
| L2 | NativeReportPolicy { HostRecorded, KernelRecorded } (R1 appended a third value, KernelRecordedAtHostMarks native_run_spec.hpp:64), report_open_position_at_end, hash_host_extension, native_metrics() → A.1 | at L2 the adapter stayed HostRecorded; since R1 it declares KernelRecordedAtHostMarks pine_adapter.cpp:3039, so the kernel records the same series at the adapter's own report marks — still one recorder, no double recording, fill_report bytes unchanged on the corpus | native example: curve length == script bars, drawdown equals a hand walk (F); 50-bar host asserts a non-empty curve, a finite max drawdown and a range-end row (O); same generic strategy through both hosts → equal rows and curve (S) |
| L3 | Sized{side, basis, time, grid_policy, reserve_percent_fee} as the 6th OrderIntent; ScopeFraction{fraction, claim} in ReductionSize → A.2 | at L3 the adapter emitted neither. Since R2 it lowers a default cash / percent opening frozen at the signal onto Sized{CashValue, AtAcceptance, SignalOnTick, ExplicitUnits}, since N11 its placement-time quantity is the kernel's native_sized_units, and since R5 lane F7 a typed quantity (qty_type cash / percent) converts through the same query and a staged FX series keeps Sized wherever the acceptance coordinate converts at the source's rate (tests/test_adapter_sizing_relower.cpp cases 1-4, 8, 11, 12). It still never emits ScopeFraction (case 5) | percent-of-equity probe, zero fee, no grid: exact; with grid / fee: differences explained by SZ10 only (F). Differential: Sized vs a host-resolved HostSized on the same facts → identical ExecutionAppliedEvent (O). Gap, reversal, fee-bearing first lot; two 50 % siblings, partial close, pending parent, replacement (S) |
| L4 | NativeMarginModel, native_liquidation_price(), resolve_margin_call_units, on_native_margin_call, RequestDefinition::origin, MarginCallEvent → A.3 | at L4 the adapter left margin unset. Since R5 project() declares a maintenance-only model (per-side initial_* 0, maintenance_* = margin_long / margin_short, PathAdverseExtremeMark, TradingView's equity basis) and answers its three hooks with TradingView's scheduling, money and slice (MG6, MG7), while every opening is still AdmitWithHostMargin (MG4) | first a plain maintenance breach in both hosts (S); then a margin-call probe reproduced with ShortfallMultiple 4: same bar, same side; quantity deltas itemized against MG15 (F, O) |
| L5 | NativeCalculationTrigger, on_native_recalculate(…, reason, cause), on_native_sub_bar, current_partial_bar(), NativeOpenBarView → A.4 | the adapter keeps BarClose + its own cascade; born_on_remaining_path (native_execution_consumer.cpp:6857) and drain_applied_notifications (native_execution_consumer.cpp:7618) are frozen; accessors are inert | COOF probe re-expressed natively: same fills where the TV waypoint rule is inactive (F); a host that flips on its own fill produces the adapter's cascade in shape (O); callback reason, cursor, newborn eligibility and event ordinals compared (S) |
| L6 | NativeTimeframeSubscription, on_native_timeframe_bar, native_series_bar → A.5, §2.iv | subscriptions empty for the adapter; registration-gated pump; engine_security.cpp and the host's scheduler_prepare_security_sequence (pine_strategy_host.cpp:1898) untouched; corpus diff before / after (O: highest parity risk of any lane) | HTF-filter probe on a 24x7 symbol: exact (F); "D" over a 15m feed equals a TimeframeAggregator baseline bar for bar (O); installed-feed and no-feed cases (S) |
| L7 | native_working_requests(), cancel_all(), cancel_where(label), TriggerAnchor, TrailTicks, zero offset, ReplaceOptions; toolkit submit_bracket, OrderBook → A.6 | new kinds unused by the adapter; anchor defaults to Absolute; the adapter's tick * 0.5 sentinel keeps working | bracket + trailing probe through the builder: exact where TR5 / FP7 are inactive (on-grid levels) (F); the zero-offset trail test asserts the run no longer fails (O); parent-limit + stop sibling, parent rejection, replacement, parent-cycle revival (S) |
| L8 | NativePriceGrid { None, QuantizeFills, QuantizeFillsAndTriggers }, NativeGridRounding → A.7 | None for the adapter, permanently — native-only by ruling, audit lane P6: §3.6.2 | a host with QuantizeFills books on-grid prices; a second test pins that QuantizeFillsAndTriggers still differs from TV's half-tick threshold (FP5) (O) |
| L9 | NativeRiskLimits, NativeRiskEvent, MatchRejectReason::RiskLimit → A.8 | risk unset for the adapter; its ledger (SourceDayLedger pine_adapter.hpp:1534) untouched — retained after measurement, audit lane N12; native-only by ruling, audit lane P6: §3.6.1 | risk probes: same halt bar (F); each limit + the day-boundary basis (O); breach, forced close, cancellation, next-day reset (S) |
| L10 | PINEFORGE_EXPORT_NATIVE_STRATEGY(Class) (§2.v) | build-only | the examples run in ctest; the independence checker compiles the relocated examples |
| L11 | — (renames / moves, §2.ii a-i) | rename / move only; hashed enumerator values pinned; sweep unchanged | — |
| L12 | — (§2.ii j-m) | not neutral: coordinated sweep, waiver updates, hash-domain plan (the "pineforge-broker-state/v17" literal of that wave, since moved to "pineforge-broker-state/v19" engine_state_hash.cpp:41 and pinned to one occurrence by check_broker_state_hash_coverage.py:229) | trades identical, hashes re-baselined once | | L13 | pf_native_* symbols (§2.iii) | additive symbols; check_c_abi_runtime.py exits 0 | a pure-C twin of the market example reproduces the C++ trade rows (O) and the event-history hash (F); C test: submit a market, replace a limit, cancel a child, read the Applied event (S) |
Consistent and kept: price grid after sizing (O); margin after report truth (O); ergonomics after sizing for the shared native_order epoch (F); risk after margin and report (F, O); C API after sizing + ergonomics (F, O); examples and neutral extraction after L1 (F, O); hash-visible extraction after L4 / L6 (F, O); HTF after the build split (S).
Flagged:
| # | Input note | Against R5-11 | Disposition |
|---|---|---|---|
| 1 | S: sizing depends on brackets; brackets are "L, prerequisite for everything" | L3 runs before L7 | rejected by R5-2 |
| 2 | S: report / metrics lane depends on sizing, margin and calc-timing | L2 runs second | S's lane was wider (fill model + excursion + report); F and O see no such dependency |
| 3 | F: L2's virtuals need the host-epoch bump | L2 lands long before the shared bump | L2 uses O's spec-policy form (no virtual); only hash_host_extension waits for the batch (A.1) |
| 4 | R5-11 shares one bump between L5 and L6 | L4, earlier, also adds host virtuals | engine_script_run_v18 opens at L4 and closes after L5 ∥ L6, or L4's hook ships with that batch — supervisor call (§4 Q1) | | 5 | F: the sub-bar hook is optional for v1 | R5-5 / R5-11 put it in v1 | ruling applied | | 6 | O: price grid, calc-on-fill and HTF deferrable past 1.0 | v1 | R5-3, R5-5, R5-6 | | 7 | S: examples after the C API "as applicable" | L10 runs before L13 | the C example joins with L13 | | 8 | O: HTF needs a lookahead decision before it starts | — | U1 blocks the L6 brief, not L0-L4 | | 9 | F: move all request.security machinery to the source host after native HTF | R5-6 keeps the feed store kernel-side | narrowed (§2.ii m) | | 10 | S: margin + risk in one lane, both v1 prerequisites | L4 v1, L9 post-v1 | R5-9 |
| Tier | Lanes | Why |
|---|---|---|
| Day-one blockers (O) | L1, L2, L3, L10 | Without L2 fill_report silently returns an empty curve and degenerate equity metrics and drops an open final position. Without L3 every non-unit order needs a hand-written resolve_execution_terms, and a mistake is a run-terminating TermsUnresolved (native_execution_consumer.cpp:6187). Without L1 "kernel" is a claim, not an artifact. L10 is the minimum buildable proof. |
| Required by the promise (R5-11) | L0, L4, L5, L6, L7, L8 | A kernel that cannot liquidate is not a broker model (O); bracket / trailing corner cases are hit immediately (O); calc-timing and HTF are Pine features the user wants natively (R5-5, R5-6); the price grid is generic and missing (R5-3). |
| Wanted with v1, not blocking | L11 a, d | removes the last source:: name from kernel signatures and the orphaned TV commentary (F) |
| In scope, 1.x | L9 | R5-9; a toolkit stand-in is acceptable meanwhile (F) |
| Post-v1 | L11 rest, L12, L13 | L12 is required before claiming the adapter is a thin optional layer rather than a runtime-dependent sibling (S); L13 only if the promise is widened to C hosts (U2) |
The second independent R5 audit (§6, lane P6) asked for a decision on the two kernel features no adapter run declares, NativeRunSpec::price_grid (L8, L8b) and NativeRunSpec::risk (L9): either close the measured divergence in the kernel so that project() can declare them, or record them as native-only by ruling, with the measurement and with a native example that exercises each. Both are native-only by ruling. Neither divergence can be closed by a generic kernel capability: what separates the adapter from the kernel is, in both cases, a TradingView rule that would have to be spelled into the kernel to disappear (ADR-0001 rule 2). §3.6.1 is lane N12's measurement of the risk rules with the P6 decision appended; §3.6.2 is the price grid's, drawn from lanes R7, L8b and N13; §3.6.3 is the inventory that shows these two are not the only fields project() leaves unset, and that none of the others is undecided either. The normative table is ADR-0001's "Kernel capabilities the
Pine adapter does not declare"; scripts/check_native_feature_rulings.py holds it against the header, the adapter and the examples.
A native-only feature is not dead code. Its consumers are the hosts the kernel exists for: C++ hosts (examples/native/native_price_grid_strategy.cpp, native_risk_limits_strategy.cpp, native_trail_risk_strategy.cpp), C hosts (examples/native/native_price_grid_c.c; PF_NATIVE_SPEC_EXT_RISK in tests/test_native_c_api.c) and the kernel-only tests (the private test test_native_price_grid 1913 checks, tests/test_native_risk_limits.cpp 327 checks), all of which run in the kernel-only CI profile.
risk — the TradingView risk rules against NativeRunSpec::risk (lane N12: measured, retained)The independent R5 audit (§6, lane N12) found that no adapter run declares spec.risk (rg "spec\.risk|NativeRiskLimits" src/source/ = 0 hits) while update_risk_state, SourceDayLedger, submit_intraday_loss_close and the compat/pine intraday cap stay the live path, and that the only artefact was a twin (tests/test_native_risk_limits.cpp twin_of_adapter_risk_halts) that itemized differences without a ruling. This section is the ruling, with the measurement that decides it: every strategy.risk.* rule stays in the adapter; project() keeps risk unset. No kernel or adapter source changed; the measurement lives in tests/test_adapter_risk_relower.cpp (nine paired scenarios, each run through the adapter and through a bare NativeStrategyHost on the same tape, the adapter half harvested from main 683a82f, the kernel half pinned) and in the seeded corpus experiment below.
The structural reason, common to all four rules. Pine's strategy.risk.* calls are script statements: the transpiler emits them inside the per-bar body (corpus 442d497, ta-closedtrades-risk-introspection-01/generated.cpp on_source_bar → set_pine_risk_max_drawdown(20, true); cap-risk-gates-allow-max-intraday-01 set_pine_risk_direction(1) / set_pine_risk_max_position_size(2) / set_pine_risk_max_intraday_filled_orders(3)), so the adapter first sees a limit on script bar 0, from PineStrategyHost::set_pine_risk_* (pine_strategy_host.cpp:1233) — after prepare_native_begin has already projected the spec (pine_strategy_host.cpp:553) and configure_native has folded it into the continuation digest. Measured: an env-gated probe printing the adapter's risk configuration inside project() on the corpus reads max_drawdown=0 cap.active=0 for every risk probe. NativeRunSpec::risk is a begin-time declaration (native_run_spec.hpp:677-684,504, folded at native_execution_consumer.cpp:338-339); a statement-time limit has nowhere to land. Routing any rule needs either the transpiler to hoist constant risk statements into the constructor (out of this repository) or a kernel re-declaration API mid-run (a hash-visible mutation of a digested field: an epoch decision the audit lane does not budget). The witness is the SW check of the test: under every rule the adapter host's kernel ledger stays inert (native_risk_state() has no day, no peak, no block) while the adapter's own rule acts.
Per rule (STEP 1), assuming the values were known at begin time. Adapter line numbers are re-anchored to this tree (the measurement ran on 683a82f); kernel semantics are NativeRiskLimits native_run_spec.hpp:310 and risk_evaluate native_execution_consumer.cpp:4663 (evaluated at the script bar's open, its close calculation and after each applied drain; fills counted at the corresponding adapter checkpoints; the block refuses would_open).
| Pine rule | adapter mechanism | kernel counterpart | class | measured divergence (test scenario) | ruling |
|---|---|---|---|---|---|
strategy.risk.allow_entry_in | risk_.direction; project() already declares spec.allowed_open_directions (pine_adapter.cpp:3043); the forbidden opposite entry is reshaped to CloseOpposite in the adapter's own terms branch | per-opening cap allowed_open_directions | (i)+(ii), already re-lowered | none to measure: the cap is the kernel's, the close-only reshaping is the retained TV policy | done before N12; nothing to move |
strategy.risk.max_position_size | precommit refuses a flat/same-side ENTRY at its fill when the LIVE book already holds ≥ the limit (max_position_size pine_adapter.cpp:17101; the retired legacy rule is git show ab9714be:src/source/pine_risk.cpp, lines 115 and 2531) | max_abs_units: refuses a fill whose RESULTING book would exceed it | (iv) | PS: two-unit entries against 3 — adapter admits the second (live 2 < 3, book 4) and refuses the third; kernel refuses the second (MaxAbsUnits at bars 1, 2), book 2 | retained: a pre-fill-book gate is a TV emulation fact, not a generic cap |
strategy.risk.max_drawdown | update_risk_state(bar.close) once per script bar at the close mark (update_risk_state(bar.close) pine_adapter.cpp:24780, update_risk_state pine_adapter.cpp:18095): peak / running max drawdown at close marks, >= exact, percent of the current peak; the latch risk_.halted gates flat/same-side entries at precommit (risk_.halted pine_adapter.cpp:17094), never an opposite entry | max_drawdown: current drawdown vs percent-of-peak, at all three points; BlockOpenings refuses every opening | (ii) cadence + (iv) scope | MG10-a: bar 1 opens 600 under the entry and closes 100 under — kernel breaches at the open mark and refuses bar 2's add (book 100), adapter admits it (book 200). MG10-b: latched at bar 1's close, the adapter still reverses twice (L→S −600, S→L), the kernel refuses both reversals (RiskLimit at bars 2, 3) and holds. Corpus: ta-closedtrades-risk-introspection-01 (20 % of peak) identical 1502/1502 rows with the kernel seeded — the rule never fires there | retained: the latched-reversal exemption cannot be composed on a kernel block; a close-only cadence knob alone would not close it |
strategy.risk.max_cons_loss_days | SourceDayLedger (pine_adapter.hpp:1534-1545): +1 per losing TRADE on a new chart day, reset to 0 by any winning trade at its fill (consecutive_loss_days pine_adapter.cpp:26429-26431); gated immediately at precommit and latched at the close | max_consecutive_loss_days: a day's NET realized result settles the streak when the next day opens | (iv) | MG11-a (loss; win then loss; loss): adapter latches at day 3's first loss (4 rows), kernel's netted streak never reaches 2 (6 rows, no event). MG11-b (loss then smaller win, twice): adapter resets daily (5 rows), kernel blocks at day 3's open (4 rows, one event). No corpus probe declares the rule | retained: an order-dependent per-trade streak is not a generic day result |
| strategy.risk.max_intraday_loss | day-open equity at the day's first bar (intraday_start_equity pine_adapter.cpp:23107); checked at the bar open (submit_intraday_loss_close(bar.open, context, true) pine_adapter.cpp:23507), along the bar's PATH at its adverse extreme with a resting Stop flatten (schedule_intraday_loss_path pine_adapter.cpp:21504, submit_intraday_loss_close pine_adapter.cpp:21512), and at a closing fill with that fill's own P&L unbooked (preclose_intraday_loss pine_adapter.cpp:25361); epsilon 1e-9·max(1,|limit|); ticket "Close Position (Max intraday Loss)" → CloseCause::RiskLimit; the block refuses ALL placements for the chart day (entry/order/close/close_all/exit, intraday_loss_orders_blocked pine_adapter.cpp:7699, :15189, :10078, :10996, :11527) and cancel_all() withdraws the working book (cancel_all() pine_adapter.cpp:15153) | max_intraday_loss at the three points; FlattenAndBlock closes AsPresented under "__kernel_risk__" / "Risk limit", blocks openings only, leaves working orders | (ii) ticket, day basis + (iv) path check, placement scope, unbooked fill, epsilon | MG12-a: day 2's first bar 100→85→95 — adapter closes at the extreme (85, −1500, cause 4) and refuses the day's later entry, kernel measures 0 at the open and 500 at the close, never breaches, ends with 300 units. MG12-b: breach at a bar open inside the day — same money on both sides (100 @ 88, −1200) under different tickets; the adapter withdraws a resting limit entry, the kernel leaves it working and it fills on day 3. Both sides mark the day's opening equity at its first bar: a gap there is not an intraday loss for either | retained: the path-extreme check is the rule's substance and the kernel has no path evaluation point; a risk ticket field and a PathAdverseExtreme check kind would be generic additions (mirroring NativeMarginModel) but would still leave the placement scope, the unbooked fill and the epsilon | | strategy.risk.max_intraday_filled_orders | compat::pine::IntradayCap + IntradayOrderBudget (243 + 94 lines): charged slots per risk day, quota transfer, POOC deferral, noop-market skip, the close at the fill price or the bar's better extreme (post_dispatch), latch → all placements denied (cap_placement_denied pine_adapter.cpp:18086); ticket "Close Position (Max number of filled orders in one day)" → CloseCause::FillCap; day = session_trading_day_index for a HHMM-HHMM session, else the chart day (chart_day_key pine_adapter.cpp:17992, mday·100+month in the CHART timezone) | max_fills_per_day: applied fills counted as they settle, evaluated at the three points; FlattenAndBlock at the drain's cursor; SessionDay / CalendarDayInTimezone on the SPEC timezone | (i) count + (ii) ticket + (iv) close price, charged-slot budget, placement scope, chart-day key | MG13: the second fill reaches the cap on a bar that closes above its open — adapter closes at that bar's HIGH (103) under its ticket, kernel at the close (101) under its own. MG14: chart timezone America/New_York over a UTC symbol — bar 4 (04:00 UTC) is a new chart day for the adapter and the same UTC day for the kernel. Corpus, kernel seeded with the declared cap: cap-risk-gates-allow-max-intraday-01 identical 1464/1464 (the third fill of each day flattens the book, so block and latch coincide); cap-max-intraday-filled-orders-isolate-01 3840 of 3916 rows differ from row 77 (2025-04-08); cap-gatekeeper-intraday-risk-01 312 of 604 differ from row 293 (2025-10-25); composite-bracket-cap-range-pending-stop-01 2370 of 2384 differ from row 17 (2025-04-02 02:45), two extra rows | retained: the count alone is expressible, but the kernel's block would refuse fills the transfer admits and the close price is TV's; a report-only action would give the adapter a count it cannot use (charged slots ≠ applied fills) |
Proposed generic additions (not implemented — no consumer today). A risk ticket (label / comment on NativeRiskLimits, folded only when set, exactly as NativeMarginModel::liquidation_label); a NativeRiskDay timezone override for a host that reports by a wall-clock other than the spec's; a PathAdverseExtreme check kind for the intraday limit mirroring NativeLiquidationCheck; a report-only action. Each is generic on its own merits, none unblocks a rule above while the statement-time delivery stands, and adding them now would be the dead weight the same audit rules against (G2). The order in which they would matter, if the transpiler ever hoists the statements: ticket → day timezone → path check.
Acceptance evidence. tests/test_adapter_risk_relower.cpp: 62 checks, the adapter half reproduced bit for bit against the 683a82f harvest, the kernel half pinned; corpus byte-identity not applicable (no src/ or include/ change); the five corpus risk probes measured as above with the kernel seeded in an env-gated experiment. That experiment is on the tree since R5 lane H-MEASURE: scripts/check_seeded_risk_experiment.sh applies the private patch n12-experiment.patch of the seeded_risk fixture set to a throwaway copy, checks that the unseeded probes still hash to the parity baseline, runs them with the kernel's rule seeded in the adapter's place, and reproduces every count above on H-MEASURE's lane tree (counted on the ten TradingView-recorded columns, oldest row first; the engine's own incarnation column renumbers a few rows earlier). It stays a script, not a CTest row: seeding the kernel's NativeRunSpec::risk for a Pine run needs the source change the patch makes, and the in-process pins remain the five probes' baseline rows and the SW check above.
The P6 decision: native-only. Re-verified on main b8e7976e: the ordering stands (project() at pine_strategy_host.cpp:673, the kernel's configure at :689, the set_pine_risk_* setters (set_pine_risk_direction pine_strategy_host.cpp:1233), and corpus 442d497 corpus/validation/cap-risk-gates-allow-max-intraday-01's generated strategy still emits the three calls inside on_source_bar); rg "spec\.risk|NativeRiskLimits" src/source include/pineforge/source is still empty; test_adapter_risk_relower still reads 62 checks, 0 failures. Closing the divergence instead would take, in order: the transpiler hoisting constant risk statements into the constructor (another repository) or a mid-run re-declaration of a digested spec field (an epoch decision no lane has been granted); then the four generic additions above; and then, per rule, exactly the parts the table calls TradingView's — the pre-fill live-book gate, the latched-reversal exemption, the per-trade streak, the all-placement block with the unbooked fill and the epsilon, the charged-slot budget. Those are not knobs a second broker model would ask for: each exists to reproduce one emulator, which is rule 2's test for "belongs in
the adapter". So the adapter keeps strategy.risk.*, the kernel keeps a generic account-risk block, and the four additions stay unimplemented until a native host asks for one (no consumer today, and adding them would be the dead weight this section exists to rule out).
price_grid / grid_rounding — TradingView's tick rules against the kernel grid (lanes R7, L8b, N13: measured, waived)The ruling predates the measurement. R5-3 made the price grid its own kernel lane and left "TV's exact half-tick rule … in the adapter on top"; E7 (§4.1) states the containment as "`NativePriceGrid::None` for the adapter permanently; never 'unify'"; the L8 row of §3.3 gives its neutrality proof as "`None` for the adapter, permanently". The adapter was never meant to declare the grid. Lane R7 tried the re-lowering anyway, and gap lane N13 repeated the trial after L8b; row PG (§1.7) holds the counts. In summary:
| blocker | what it is | state |
|---|---|---|
| B1 | under QuantizeFillsAndTriggers the matcher reported a cursor print inside the quantized region but short of the raw level, and the core's raw re-validation aborted the run ("native working-request preparation failed": nine zero-offset-trail tapes, six process_orders_on_close panels) | closed generically by L8b: the tick-quantized print is the reached price, and the core re-validates on the same ladder (native_order::ActivationGrid); 0 aborts under the N13 re-run |
| B2 | TradingView's tick quantization is a property of the ORDER KIND — stop and limit legs and a trail's activation (one-shot or trailing: lane E5; the placement close included: lane E9) are tested on the quantized bar, and a one-shot books the tick that bar reaches, its resting half-tick level rounded away from the position (lane E9); the trail stop, the running best, stop-limit entries and the calc_on_order_fills cursors on the raw path — while price_grid is a property of the RUN and the matcher hands one threshold to every trigger | open, and not closable: moved 30 pinned checks in 4 units when lane N13 last ran the re-lowering trial (test_coof_market_limit_recross_l4c 24, test_stop_tick_rounding_l4d 3, test_adapter_grid_relower 2, test_zero_offset_trail_rides_l4c 1) with no adapter-side remedy; 12 more checks in 3 units have an adapter-side cause. The one part of the row that WAS closable is closed (lane E14): where the running best STARTS is not a quantization at all but a level the leg names, so Trail::best_seed carries it generically and the kernel no longer begins a ride half a tick short of the activation. Lane E16 closes a second part, on the other side of the row: the trail stop's ULP — best - offset landing one binary64 ULP off the ladder point a tick count names, which is what made the grid fire test_adapter_grid_relower's TrailUlp row a bar before the raw path did — was never the per-kind rule but a level the kernel spelled wrong on both paths. A stop a whole number of ticks from a best on the run's declared ladder now IS that ladder point, both paths agree on that row, and the trail stop and running best still ride the raw path. The 2 moved checks this row counts in test_adapter_grid_relower are that mechanism; the re-lowering trial itself was not re-run. A carried trailing stop (calc_on_order_fills off) is now REACHED at the first point where the quantized bar reaches it, when that comes before the raw print would, its level and the running best still raw: TradingView's private fixture set callback_lifecycle records carried-trail-14 exits where a 10.485 high prints 10.49 and reaches a 10.485 stop. The kernel's trail is unchanged; the adapter walks the bar at its open and re-prices the trail's distance for that bar only in that case (retune_carried_trails_for_tick_reach, switch CarriedTrailTickReach). A raw reach whose quantized print falls back inside the stop stays the kernel's, as no tape decides it. |
The corpus cannot arbitrate: all 312 probes run a 0.01 tick on an on-grid feed, and under the trial 5 of them differ, in the engine-only entry-incarnation column only.
The offset trail's arm (lane E5, closing lane P9's open question). P9 found the adapter armed a trail WITH a trailing offset at its raw activation while the one-shot's activation was quantized, and no tape pinned the difference. TradingView's own tapes do (private fixture set offset_trail_arm, lab tv, ws-report-v1, rangeProof covered): on NYSE:F 15m with trail_offset 1, lows 9.415 / 13.041 / 12.641 and highs 11.899 / 13.049 / 13.419 reach, only once quantized, the activations one tick past them, and 6 of 6 trades exit on that bar at activation -/+ 1 tick; the one-shot twins exit on the same bars at the activation. On the on-grid ETH feed, levels 0.004 / 0.006 off the fill and trail_points 0.4 never arm on a bar that touches the fill (0 of 32): the quantization is the path's, not the level's. So the arm is B2's per-kind shape again and stays adapter policy — ADR-0001's "Trail and tick conventions" already names the half-tick arm as the adapter's, and the kernel's generic rule for a trail's arm under a declared grid already exists (L8b); selecting it for the arm alone is the per-kind mask above. The adapter now arms the generic Trail at the half-tick threshold its one-shot leg rests at (source_trail_arm_level) and books from a running best that is never short of the activation, because TradingView's starts there. The kernel is unchanged and the corpus byte-identical (on-grid, the two arms admit the same prints); the population sweep over sub-tick feeds is the real gate. One residual was left, of the same kind: the kernel's best started at the arm, half a tick short of the activation, so while the raw extreme stayed inside the activation's tick cell its stop sat up to half a tick further out than TradingView's and a print between the two fired only TradingView's (0 of the 6 tapes). Lane E14 closed it, below.
The one-shot's booked price and the placement print (lane E9, closing E5's findings). Two more lab tv sets (the private fixture set trail_activation_tick_reach, replayed by the private test test_trail_activation_tick_reach) pin the rest of the rule. A one-shot (trail_offset 0) with a sub-tick trail_price — fill 2550.85, level 2550.854 on ETH 15m — rests at the half-tick boundary 2550.855 and TradingView books the tick the quantized path reaches there, 2550.86 (5 of 5; the short twin books fill - 0.01, 4 of 4): the resting level rounded AWAY from the position. The adapter booked the stop-style snap toward it (2550.85), a price its own resting limit forbids, and the kernel's generic limit-or-better check refused it as InvalidTerms — correctly, so the kernel is untouched and the adapter books the reach tick (source_one_shot_reach_tick). And a placement close half a tick short of the activation whose tick IS the activation (NYSE:F 11.295 under 11.30, seven longs and a short) counts as already reached: 8 of 8 trades exit on the next bar, trail_offset 1 and 0 alike, at activation -/+ the offset, so the best of a trail armed at placement starts at the activation too (source_trail_reached_at), which until lane E14 the kernel's own best did not. Both are adapter policy for the reason above and byte-identical on the on-grid corpus (no print lies in a half cell; the corpus sets no sub-tick trail_price); the population sweep over sub-tick feeds is the real gate. TradingView's compiler refuses a trail without trail_offset on its own, so the omitted-offset shape has no tape and follows the same compare.
Where the running best starts (lane E14, closing both lanes' residual). E5's residual and E9's second finding are one sentence: TradingView's running best starts AT the activation, the kernel's started wherever the arm landed — the half-tick threshold on a crossing, the next bar's first print when the placement close armed it. Neither is a quantization, so neither is B2's mask; they are the absence of any way for a leg to say where its ride begins. The kernel gained one, native_order::Trail::best_seed: absent it is the arm print, byte for byte as before, and present it is a floor on the start — the favourable one of the seed and the arm print. It passes rule 2 as amended. Mechanism: a trail that rides from its activation instead of from the next print is a broker shape a second venue asks for, and the level is data the host hands over, as an arm_price is; the kernel neither derives it nor knows what named it. Knob: today's start was not a choice the run had already made another way — there is no second spelling of it — so a request field is admissible rather than a hook. The adapter names the activation, or the favourable one of the activation and the placement print when that print armed the leg, and the kernel's own crossing then IS the price the adapter books: three trail twin suites move their raw_price / path_position / leg-kind rows onto it, with no booked price, bar or quantity changing. The corpus stays byte-identical 312/312 (on-grid: the arming bar's own extreme is folded into the best before any adverse move, so the seed changes nothing there); the sub-tick sweep is the gate. What is NOT redundant afterwards is the sibling stop an explicit-zero trail rests beside its Trail: a Stop is reached by a touch and a zero-distance ride needs a move strictly past the best, and a print landing ON the carried best separates them in the booked price's last bits. Measured (R5 lane H-MEASURE, the private test test_zero_trail_sibling_stop): a bare kernel host given both legs is the adapter bit for bit; given the Trail alone it rides past the open and fills on a later leg of the bar, half a tick to a tick and a half away. On two lab tv tapes (NYSE:F 15m, sixteen trades of that shape) TradingView books the touch at the open 16 of 16: the adapter's bar and price 16 of 16, the Trail alone 0 of 16.
Anchored relative legs: measured (lane F7 recorded it, R5 lane H-MEASURE taped it, lane PAR-ORDERS fixed it). A trailing strategy.exit issued while its entry is still pending was materialized as the kernel's anchored child at the fill (relative_leg_shapes, M17), which names no best_seed: its ride started where the kernel arms it. Six lab tv tapes of that shape (NYSE:F 15m, the exit written on the entry's signal bar; the private fixture set pending_entry_trail, replayed by the private test test_pending_entry_trail_tapes) settle both halves. The arm: on 28 trades whose arming bar's raw extreme stops inside the activation's tick cell (16 market entries, 12 limit entries filling mid-bar), TradingView exits on that bar at the activation ∓ 1 tick 28 of 28 (E5's quantized arm holds for a pending entry). The running best: on 13 trades whose prints after the arm fall between the two stops a best can give, TradingView's best starts AT the activation 13 of 13, as E14 measured for a filled entry; the anchored child's started at the raw arm print, and the adapter exited 1 to 24 bars later on all 13, because the fill point's adoption compare left best_seed out and adopted the unseeded child where exit() would have submitted a seeded leg. Lane PAR-ORDERS makes a trailing offset of a tick or more unanchorable (anchorable_relative_exit): the fill point submits the seeded leg, and the row books TradingView's bar and price on all 41 trades.
The trail stop's own ULP (lane E16, closing E14's finding). E14's tape left one trade of e14-f-long-shallow-next pinned as a recorded divergence: its stop is 11.44 - 5 ticks, whose binary64 value 11.389999999999998792 lies one ULP UNDER the ladder point 11.390000000000000568, so the bar's low of exactly 11.39 did not reach it, TradingView booked the exit and the engine ran to the timeout. Lane R7 had measured the same ULP from the other side — best - offset lands one ULP off its ladder point on about 14% of (best, offset) pairs on a two-decimal feed, which is why test_adapter_grid_relower's TrailUlp row exits a bar later on the raw path than under the grid — and read it as B2's per-kind rule. It is neither. A stop that stands a whole number of ticks from a running best that is itself a ladder point IS the ladder point that many ticks away; the subtraction simply could not name it, because both ends are decimal numbers the host measured on its own ladder and only the ladder index is exact between them. The kernel derives it there now (native_matching::ladder_trail_stop, with the run's declared tick reaching the core as ActivationGrid::ladder_tick so the activation's re-validation cannot refuse a hit the matcher booked), and it passes rule 2 as amended. Mechanism: a stop ticks ticks behind a best is a ladder distance at every venue with a tick ladder, read from the run's own price_tick — data, no platform. Knob: the run had already decided it, by declaring the ladder and spelling the distance on it, so a "raw or ladder" switch would put a hash-visible choice on the surface for an answer already given. Nothing is rounded ONTO the ladder — a sub-tick best, a fractional-tick offset and a run with no declared tick keep the raw subtraction bit for bit, so the trail stop and the running best still ride the raw path and row TR2's "a tick-spelled
trail and its price-spelled equal behave identically bar for bar" still holds (the rule reads the resolved distance, not the spelling) — and it is the level that moves, never the comparison. The kernel-only witness is tests/test_native_trail_stop_ladder.cpp (batch and stream, the sell case and its buy mirror, three off-ladder rows pinned bit for bit); the tape row is e14-f-long-shallow-next 7/7 and TrailUlp exits on TradingView's bar. The corpus is byte-identical 312/312 — measured, not assumed: the on-grid 0.01 feed does print exactly on a ladder stop, but no probe's trade moved.
Why B2 is not closed in the kernel (route (a) rejected). The only kernel change that would let project() declare the grid is a per-order-kind quantization mask: quantize a stop leg but not a trail stop, a trail's activation but not its running best, a limit but not a stop-limit. No broker model asks for that; it is one emulator's inconsistency, and the kernel's own rule (L8b) is the opposite on purpose — one rule for every kind. Rule 2's test applies literally: the mask cannot be justified without the word "TradingView". And what the adapter keeps is not a duplicate of the kernel mechanism that a re-lowering would delete: source_trigger_threshold (pine_adapter.cpp:469), source_level_on_price_grid (:452) and the nearest_tick (pine_adapter.cpp:359) / source_bar_fill_tick (:380) / directional_tick (:412) spellings are a different rule — a half-tick threshold per order kind with the fill booked elsewhere (E7) — which is why re-lowering moves outcomes instead of preserving them.
Re-verified on main b8e7976e. rg "spec\.price_grid|\.price_grid\b" src/source include/pineforge/source src/compat is empty. test_adapter_grid_relower — the permanent witness: its section 4 pins TradingView's per-kind rule on boundary prints as a neutrality differential harvested from the unchanged adapter, and its section 5 pins the measured divergence, the grid firing a trail stop one bar early where best - offset lands one ULP under its ladder point — reads 33878 checks, 0 failures; test_native_price_grid 1913 checks, 0 failures; its adapter twin 9 of 9. The trial itself (raw levels submitted, the grid declared) was temporary in both lanes and is not in the tree; its counts above are N13's, not re-run here.
What landed for the ruling. Until P6 no example exercised the grid (the second audit's §5: "lanes with no example: price grid"), and the C spelling (PF_NATIVE_SPEC_EXT_PRICE_GRID, translated at native_c_host.cpp:3237-3241) had no executed consumer anywhere in the repository — no C test and no C example set the bit. examples/native/native_price_grid_strategy.cpp runs one strategy over one sub-tick tape (a 0.25 ladder under a composite feed) in the four modes and asserts every fill's raw and booked price against hand-computed values; examples/native/native_price_grid_c.c is the same host through the C API and reads the same numbers back from strategy_native_events_v1:
| mode | entry (open 100.10) | target (limit 100.75) | protect (stop 99.50, open 99.40) | breakout (stop 99.75, high 99.65) | its exit (open 99.60) | net |
|---|---|---|---|---|---|---|
None | 100.10 | 100.75 | 99.40 | — | — | −0.05 |
QuantizeFills, HalfUp | 100.00 | 100.75 | 99.50 | — | — | +0.25 |
QuantizeFills, Directional | 100.25 | 100.75 | 99.25 | — | — | −0.50 |
QuantizeFillsAndTriggers, HalfUp | 100.00 | 100.75 | 99.50 | 99.75 | 99.50 | 0.00 |
NativeRunSpec field project() leaves unsetscripts/check_native_feature_rulings.py --list on this tree: 46 fields, 35 assigned by project(), 11 not (43, 33 and 10 when lane P6 wrote this). Each of the eleven has a row in ADR-0001's ruling table, and the check fails when a field has neither an adapter declaration nor a row, when a row's consumers do not exist or never spell the field, when a native-only row has no examples/native/ host or no tests/ unit, or when project() starts assigning a field whose ruling is still in the table.
| field | ruling | decided by |
|---|---|---|
price_grid, grid_rounding | native-only | §3.6.2 |
risk | native-only | §3.6.1 |
max_abs_units | adapter-policy: the pre-fill live-book gate is TradingView's | row MG2; N12's PS measurement (book 4 against 2) |
max_open_lots | adapter-policy: Pine pyramiding is a per-cycle entry count (measured against TradingView: not its rule, row MG3; R5 lane H-THIN's lowering trial moved four corpus probes away) | row MG3; the contract comment in project(); the private test test_pyramiding_count_differential |
initial_margin_fraction | adapter-policy: TradingView's money admission answers AdmitWithHostMargin; the declared margin model is maintenance-only | row MG4; the wave-4 ruling in project() |
report_open_position_at_end | adapter-policy: TradingView's range-end report is report shape, not a mark-to-market row | row RP5 |
open_bar_view | adapter-policy: TradingView's open scheduling and its fill callback read the whole bar | rows CT4, E5 |
subscriptions | adapter-hook: declared through declare_timeframe_subscriptions at begin (lane R3b) | §2.iv |
auxiliary_feed | adapter-policy: the adapter's own auxiliary drive, retained on three measurements (lane N7) | §2.iv |
quantity_tolerance | adapter-policy: TradingView's 1e-10 quantity rules run in the adapter on top of the kernel's exact default (lane K-ULP4) | ADR-0001's row; the quantity-epsilon census of lane H-THIN |
Out of this inventory's scope: request kinds and host members the adapter never emits or calls (Sized, ScopeFraction, native_open_lots(), …). E3 rules Sized; the C header's COVERAGE block rules the host surface.
The second independent R5 audit (§6, lane Q6) found the only first-round duplicate neither gap wave touched: record_open_position_report_rows ran a build_close_trade_with_costs loop with open_at_end = true, gated to NativeReportPolicy::KernelRecorded, which the adapter can never select because project() declares KernelRecordedAtHostMarks; and PineStrategyHost::scheduler_record_range_end ran the same loop again, with a prose reason and no measurement. This section is the ruling, in two parts.
Part 1 — the loop is re-lowered, not measured. The row loop is now one generic producer, NativeExecutionConsumer::append_open_position_report_rows(engine, mark_price,
mark_time_ms, interval_index, account_fx): one open_at_end row per open physical lot through the same non-mutating builder a full close uses, at the account-currency rate the caller names, returning the summed NET row P&L. It is policy-free — the caller decides when to mark, at which rate, what to clear first and what it re-derives from the rows — so the kernel's run-end producer and a host with a different report shape share it instead of each restating it. The adapter's own loop is gone; scheduler_record_range_end calls the producer at TradingView's mark and hands it the rate of that mark's instant (account_currency_fx_at of the curve's last point); since R5 lane B-ADAPTER it no longer presents the mark as the engine's clock so the rows convert there. Ruling: a mark-to-market row per open lot is a generic capability; when to mark and what to re-derive from the mark is not.
Part 2 — the report shape around the loop stays in the adapter, measured. TradingView's range-end report is not a mark-to-market row, so the kernel's run-end producer stays gated out of every Pine run (report_policy = KernelRecordedAtHostMarks, report_open_position_at_end = false). What the adapter keeps is the shape, and every row below is an executed assertion of tests/test_adapter_range_end_relower.cpp — four paired scenarios, each run through a PineStrategyHost and through a bare NativeStrategyHost with KernelRecorded + report_open_position_at_end on the same tape, the adapter half harvested from this lane's base b8e7976e and reproduced bit for bit after the re-lowering.
Common tape: hourly, capital 100000, 100 units a lot, point value 1, fx 1, slippage 0, process_orders_on_close; two lots at 100 and 102 marked at 105, so the gross open profit is 800.
| retained mechanism | adapter site | kernel counterpart | measured divergence (scenario) | ruling |
|---|---|---|---|---|
| the rows themselves | scheduler_record_range_end → the shared producer | record_open_position_report_rows → the same producer | none — this is the re-lowering. RE1/RE2/RE3 assert both halves produce the identical range-end rows: side, units, entry price, mark price, P&L, commission, run-up, draw-down, open_at_end. With no fee: pnl=500 and pnl=300. With 0.1 %: pnl=479.5 comm=20.5 and pnl=279.30000000000001 comm=20.700000000000003 on both sides | re-lowered; nothing left to retain |
| the equity re-mark | last.open_profit = 0; last.equity = initial_capital_ + net_profit_sum_ + range_end_pnl | none — the kernel leaves the curve exactly as the run marked it | RE1 (no fee): the adapter reports the curve's last point as eq=100800 op=0, the kernel as eq=100800 op=800 — same equity, the open profit moved into the realized side. RE2 (0.1 %): eq=100758.8 op=0 against eq=100800 op=800; the adapter's equity is 41.2 lower, exactly the round-trip commission of both lots (20.5 + 20.7), because the re-mark carries the NET row P&L where the run's own mark carried the GROSS open profit | retained: a report that restates the last equity point off net row P&L is TradingView's report shape. Re-marking the curve from the kernel would move a figure every bare host already reads |
| the extreme re-fold | max_equity_/min_equity_ reset to initial_capital_, max_drawdown_/max_runup_ to 0, then fold_equity_extreme over the whole curve | none — the extremes stay as update_equity_extremes sampled them | RE3 (a dip to 90 at bar 1, a peak of 108 at bar 3, 0.1 %): both sides agree on maxeq=101400 mineq=99000 maxdd=2400, and the run-up diverges — adapter maxru=1758.8000000000029, kernel maxru=1800. The re-fold is a no-op except where the re-marked last point differs, so the divergence is again the 41.2 round trip, modulo the binary fold order | retained: it is a consequence of the re-mark above, not a mechanism of its own; it cannot be re-lowered while the re-mark is adapter policy |
| the same-bar exit re-sort | sort_same_bar_exit_trades(trades_, adapter_) — same-bar bracket exits ordered by script command sequence | none — the kernel never reorders trades_ | not exercised by the four scenarios (it reorders CLOSED rows, not range-end rows); it is TradingView command-sequence ordering by construction (git show ab9714be:src/source/pine_fills.cpp, lines 664-670) | retained: script command sequence is a source-language fact |
| the mark cadence | three call sites, each clearing and rebuilding: the terminal sub-bar of the last source bar (scheduler_record_range_end pine_strategy_host.cpp:2254), after every applied execution once terminal (record_applied_range_end, :1147), and the scheduler's terminal-source-bar hook (PineScheduler::applied pine_scheduler_native.cpp:728, before the bar's broker hash) | once, after the last point of the run (run_batch ×3, stream_end) | structural, not a number: the adapter's rows are re-derived at each mark so the report reflects the book as of the last event on the terminal bar, and its re-marked equity is visible to that bar's broker hash. A one-shot run-end producer cannot occupy those three points | retained: three marks on a terminal bar is a TradingView report cadence, not a generic run-end fact |
| the excursion projection gate | excursion_range_end_projection_ suppressed the owner's exit-bar path fold for the projection | none — the generic builder folds what it folds | RE1: identical run-up/draw-down on both halves (ru=500 dd=0, ru=300 dd=0) | retired by R5 lane H-THIN: the Pine host owns no lot excursion since, so no owner fold is left to suppress; RE1 still reads the same magnitudes, now the kernel's |
| the tail / warm-up suppression | if (stream_warmup_mode_ \|\| realtime_tail_ \|\| equity_curve_.empty()) return; | none — the kernel emits at stream_end whatever the run left open | structural: the kernel has no warm-up or realtime-tail notion, and run_corpus's probe-tail suppression (suppress_probe_tail) is a harness fact | retained: a tail is a source-layer replay concept |
Control and witness. RE4 closes the lot at bar 2: neither side emits a range-end row (closed=1 report=1, op=0, extremes equal on both), so every divergence above is attributable to the range end and not to the tape. SW asserts the structural blocker directly — an adapter run's projected spec is KernelRecordedAtHostMarks with report_open_position_at_end == false, i.e. the kernel's run-end producer is unreachable from Pine by construction.
One difference in the literals is not a range-end divergence and shows on the flat control too: the adapter dates a row on the script bar's OPEN and the bare host on its close-execution point, so every @hour label is one bar apart between the halves. The mark itself is the same bar on both.
A dead branch found and reported, not removed. fold_exit_trail_peak_ (engine.hpp:675) is initialised NaN, reset to NaN at run start (fold_exit_trail_peak_ engine_run.cpp:109) and never assigned a finite value anywhere in the tree, so the fold_exit_trail_peak_ checks (engine_execution.cpp:1120, :1135; fold_exit_trail_peak_ engine_report.cpp:1942) and the shared producer's preceding_exit_trail_peak check (engine_orders.cpp:186) are inert. The shared producer keeps the carry so it folds exactly what the kernel's settling path folds; removing the member is an epoch question (it is hashed as fold_exit_trail_peak_ at engine_state_hash.cpp:86) and is left to a lane that budgets one.
Acceptance evidence. tests/test_adapter_range_end_relower.cpp: 12 checks, the adapter half reproduced bit for bit against the b8e7976e harvest; a mutation self-test (marked += row.pnl + 1.0 in the shared producer) fails 3 of the 4 scenarios through the adapter half, which is the executed proof that the adapter now runs the kernel's producer; whole corpus byte-identical at gitlink 442d497.
The final audit (AUDIT3-opus §3.2, §7 F1) reproduced three Pine-visible regressions that R4 slice C (73817c1d) introduced, two of them on a chart whose script timeframe is coarser than its input feed (an aggregated chart). No gate could see them: neither aggregated corpus probe reads a bar index or runs process_orders_on_close, and the two SPY session probes have no baseline rows. Since R5 lane H-MEASURE a CTest row does: tests/test_aggregated_entry_bar_index_tape.cpp replays TradingView's own tapes of a process_orders_on_close script whose every exit reads entry_bar_index / exit_bar_index (lab tv hm-f1e-ebi-pooc-eth, and its magnifier-on twin) on the chart, the plain aggregated and the magnified path, and fails 10 of its 24 checks against the kernel's pre-K-IDX input-slot booking (K-IDX's four booking sites reverted in a scratch build). The oracle is the pre-R4 engine ab9714be, whose plain aggregated run of one script equals its chart-timeframe run field for field, plus TradingView's own tapes (the private fixture set session_islastbar).
Ruling (supervisor, ADR-0001 rule 2): Option A. The kernel owns the public coordinate in script-bar space for every host. NativeCoordinate::interval_index and every lot, trade-row and metric index are sequential SCRIPT-bar indices; NativeCoordinate::input_interval_index keeps the input slot beside them for a host that needs input cadence. The Pine adapter's index re-stamp is removed; its chart-bar sampling remains an explicit source-layer policy. The coordinate and its named input companion are folded by the v19 continuation recipe, so this lane re-pins the affected v19 witnesses once.
| ID | What TradingView and ab9714be do | What R4 slice C did | Owner and fix | Proof |
|---|---|---|---|---|
| AG1 | A trade row counts chart bars: its entry and exit bar are the bar_index the script read there, so bar_index - strategy.opentrades.entry_bar_index(0) is the bars held, and a tape row one chart bar long says "Duration (bars)" 1 | The pre-K-IDX kernel used the first contributing INPUT slot as its public interval index on an aggregated run. That made a two-script-bar trade report input indices and made avg_bars_in_trade count input slots. | Kernel (Option A). NativeExecutionConsumer::ScriptBucket::script_index assigns the sequential script index when a bucket seals. NativeCoordinate::interval_index, PyramidEntry::entry_bar_index, Trade::exit_bar_index, open-lot rows and the metrics calculation all consume that script index. NativeCoordinate::input_interval_index preserves the first input slot. PineStrategyHost::on_native_applied no longer rewrites lots or rows; Pine's cohort sampling still passes its explicit chart source index to the source-layer sampler (its excursion sampling is the kernel's since R5 lane H-THIN). | kc_a310 / kc_a310_c in the K-IDX scratch: batch, stream and both magnified modes report 0/1/2, entry/exit 1/2, avg 2; the private test test_aggregated_path_regressions and the private test test_pooc_fill_stamp_aggregation remain green. TradingView: tests/test_aggregated_entry_bar_index_tape.cpp (R5 lane H-MEASURE), a tape whose exits read the indices, booked field for field on all three paths. |
| AG2 | Every fill of a chart bar is dated at the bar's open: the e25-f-islastbar tape dates all 510 of its process_orders_on_close rows there, and ab9714be's aggregated rows equal its chart rows | The kernel dates a close point (the calculation, the close after it, a modeled bar's close leg) at calculation_time(), the bucket's close, and a fill the host executes at the current point (a calc_on_order_fills first-open chain, a same-bar close-then-entry, the KI-62 cover) at its decision floor, which on the no-path route the bucket's inputs have already carried to the close. The chart timeframe's zero-width interval hides both, since every point of a bar sits at its label | Adapter, at projection time. On a chart whose script bars the kernel gathers from the input (native_aggregates_input_bars(), the kernel's pairing_aggregates of the resolved pairing, since R5 lane H-THIN; the adapter's own literal test parted from it on one-bucket pairings such as 1D on D), on_native_applied dates the lot a fill opened (PyramidEntry::time, hence Trade::entry_time) and the rows it closed (Trade::exit_time) at the chart bar's open: on an aggregated chart every point of a confirmed bar's modeled path (its open, the segments, the calculation, the close after it), where the chart timeframe's own points all sit, and under the magnifier every sub-bar point too (each sub-bar's open, path and ModeledOHLCClose; lane E27 had cut it to the close points, R5 lane PAR-ORDERS widened it, TradingView's dating). A realtime print keeps its own instant, as the chart-timeframe stream keeps it, and so does a stream's quiet carried interval | the audit's pooc probe rows (ab9714be's 19:45Z / 13:30Z on all three paths), row 4, the KI-62 row and the stream row (a chart-timeframe stream and the same stream fed 1m agree on every row, a 10:30:01 print fill and an 11:00 carried fill included); E27's stamp rows flipped. Measured: the chart path unchanged byte for byte, the magnified run moves only its POOC close-fill instants. TradingView itself dates a magnified fill at its chart bar's open too (218 of 218 fills on sixteen lab tv tapes, R5 lane H-MEASURE); the magnified run kept a sub-bar fill's own instant (71 of the 152 instants the private test test_magnified_aggregated_tape replays sat inside their bar) until R5 lane PAR-ORDERS, and the row now asserts every instant exactly |
| AG3 | A calc_on_order_fills recalculation runs the script on the bar the fill is on and reads that bar's session.ismarket / isfirstbar / islastbar; ab9714be set them before any fill of the bar | The scheduler set the flags in the close callback only, so a recalculation published the PREVIOUS bar's: a fill at the next session day's 09:30 read islastbar. On the chart timeframe the close then read its lookahead from a count the recalculation had already advanced, one retained bar too far: a fill on 15:30 flagged 15:30 islastbar and, through E26's dual, 15:45 isfirstbar | Adapter (Pine language state, OT7). scheduler_update_session_state stays the one writer and runs once per script bar, at the bar's first publication: PineScheduler::recalculate asks before the script runs, PineScheduler::bar pine_scheduler_native.cpp:622 only when no recalculation published the bar. Since R5 lane F5 it selects the kernel's session-day facts of that bar, which every callback of the bar carries alike, so the scheduler's lookahead is gone. E26's session-day rule and its isfirstbar dual are unchanged | the audit's coof_session probes and the 15:30 case in the private test test_aggregated_path_regressions; section 7 of the private test test_session_islastbar_aggregation (TradingView's NYSE:F days with calc_on_order_fills, chart, aggregated and magnified: every callback of a bar reads the tape's flags) |
Recorded, not changed here.
calc_on_order_fills extreme sample) now compare script-bar indices with script-bar indices — instrumented, 0 of 5 170 comparisons out of space, against 4 996 with 3 974 wrong verdicts on 91d65ad6 — and K-IDX moved two excursions onto the chart path's values and nothing else. Lane F1's "12 vs 9 rows" is settled by sixteen lab tv tapes of its differential script: TradingView books the script's 12 rows as this engine does on every path, and ab9714be's 9 were its own error (the private test test_magnified_aggregated_tape, the variants the magnified run books exactly). A process_orders_on_close stop entry the placing close already reached is closed too (R5 lane PAR-ORDERS): TradingView fills it at that close – the close's tick, slipped like any stop fill – on every path and with or without calc_on_order_fills, five entries on each -v1 / -v3 / -v5 / -v7 tape, where the resting stop filled a bar late at the next open. The close pass that fills it (flush_pooc_marketable_limit_entry_fills) now takes a pure stop entry that is the bar's lone entry marketable at the close; two such entries keep the next open's both-marketable arbitration, which no tape orders at a close. Since R5 lane PAR-ORDERS-2 the pass (fill_pooc_close_entries) also takes such a stop entry placed while the book holds the other side: TradingView fills it at that close, sized as the reversal it is, and after the bar's own strategy.close of the held side when one fills there (the private test test_pooc_reversing_stop_tapes, chart and magnified; corpus order-deferred-flip-pooc-cross-bar-01: 57 of TradingView's 792 entries at its bar and price before, 734 a bar late and one trade missing; all 792 trades exact after). It tests a pure limit entry on the close's tick too, as it tests a stop, with or without calc_on_order_fills (R5 lane PAR-ORDERS-3, finding 3): TradingView fills a sell limit at 11.76 over a raw 11.755 close at that close, and leaves a buy limit at 11.65 over a raw 11.655 close (tick 11.66) resting whatever the bar's range reached (the private test test_pooc_limit_close_tapes), where the pass compared the raw close and skipped every limit under calc_on_order_fills. Since R5 lane TAIL-C the pass also fills a limit or stop add placed while its own side is held and a limit placed against the held side, and decides the calculation's exits after those fills, the exits naming an entry it just filled included (tests/test_pooc_add_close_tape.cpp). A stop entry the pass fills there can be the same id's entry again, placed at the close of the bar whose bracket limit closed the previous one, with a fresh strategy.exit of the same exit id. When that limit was a retained leg the pre-script drain settled, the adapter reads its terminal receipt only at the next open, and the sibling cancel it runs (cancel_bracket_siblings: the working legs of that exit id, from_entry and origin) took the fresh bracket too, so the re-entry never exited; since R5 lane INT26 it skips a leg of a later strategy.exit call that the kernel accepted after that fill. A leg of the filled leg's own call still goes, however late it was accepted: the drain submits a bracket's legs one at a time, executing each (the private test test_pooc_stop_reentry_bracket_tapes, with a control; the gate sweep's BTCUSDT probe, 820 of TradingView's 821 trades before, 821 after). Under the bar magnifier the same script's short still differs, recorded in the row: the drain leaves that retained limit to the flush below the body, whose book still holds the short, so the re-entry is never placed. That pass also never ran on an aggregated or magnified run: its quiet-bar gate tested the script-bar interval_index where the pass tests the input slot (projection_bar_index), so since K-IDX every such run skipped it, limit entries and exit legs included. The pyramiding-cap bypass is closed (R5 lane PAR-ORDERS). Under process_orders_on_close a pending opposite entry deferred the cap to the close pass even when that pass would not fill it first – a priced entry resting from an earlier bar – and nothing checked a fixed-size add at its fill, so under the magnifier with calc_on_order_fills every fill's recalculation added a lot: 86 at once with pyramiding=1. Such an add is now held to the lots open (entry()); the row asserts the cap on all eight variants and three paths, the v3 magnified run booking TradingView's 12 rows and ending flat. A market order a recalculation places on the bar's second extreme is closed too (R5 lane PAR-ORDERS-2, H-MEASURE Finding 6d). When the matcher's fill of a resting request at its own level ends the leg to the bar's second extreme, the calc_on_order_fills recalculation's market entry and the exits it places are live from that extreme, as TradingView books them (row 5 of hm-chart-diff-v2 / -v3 / -v7, the pa2-i2-exit-w2-* tapes of the private test test_second_extreme_order_tapes): the entry used to advance to the close point, which admits no cascade order, and so to the next open. A fill the adapter itself forced onto that extreme – an order an earlier recalculation of the bar placed, gap-filled there (coof_fill_forced_) – starts the next leg instead, and its recalculation's market orders still advance, as TradingView books them too (the pa2-i2-point-w2-* tapes; corpus bracket-rivet-calc-on-fill-01, trades 350 and 516, byte-identical). A strategy.close the same recalculation calls fills at that extreme too, as TradingView books it (pa2-i4-close-w2-*), where it used to wait for the next open, or under process_orders_on_close for the bar's close. An exit a recalculation places on a fill AT an extreme reads that fill (R5 lane PAR-ORDERS-3, findings 1 and 2). The matcher's fill at a resting request's own level on the extreme ends its leg, so none of that leg is left in flight: the exit's limit is live at its own level on the next leg, as TradingView books it (pa2-i2-level-w1-*), where exit() forced it onto the next extreme. A fill forced onto the extreme starts the next leg, read from the extreme itself even when its booked tick is off-grid (pa2-i2-point-w2-long*: 11.425 booked 11.43), where that tick read as a fill short of the low. The same holds on a bar's first extreme (R5 lane PAR-ORDERS-3, finding 4). After the matcher's fill at a resting request's own level ON a bar's first extreme, the recalculation's orders act from that extreme: a market entry, strategy.close, close_all and a market strategy.order fill AT it, an exit stop already through fills AT it, and a stop entry between the extremes fills at its own level (pa3-f4-level-w1-*, pa3-f4-stop-w1-mkt-*, pa3-f4-xlimit-w1-reentry{,-tp}-*), where ab9714be advanced every one of them to the second extreme or the next open (coof_next_waypoint); the frozen rows that pinned it moved with their notes (test_calc_on_order_fills and its twin, oracle row R1). A forced fill there still starts the next leg (pa3-f4-point-w1-mkt-*), and so does the gap fill of an exit limit that a recalculation of the open's refill placed already through: exit() arms it at the first extreme, a StopLimit whose stop satisfies its level, and begin_coof_recalc counts its fill there as forced even with that level exactly on the extreme, so the market re-entry its recalculation places fills at the second extreme, as TradingView books it (int27-open-x{limit,limit-at,bracket}-w1-reentry-*). Booked at the first extreme, the finding as first integrated moved the INT27 gate sweep's probe on four lanes (XAUUSD, EURUSD and BTCUSDT 15m, and the community-script ETHUSDT.P lane); the fix returns all four to their baseline trades. The same reading lets a stop born on the second extreme and reached on the leg to the close fill at its own level (pa2-i2-exit-w2-* rows 5 and 7 / 1), where it was forced onto the close's tick. After a fill the adapter forced onto a leg's end (R5 lane TAIL-C). A limit or stop entry the recalculation of a fill forced onto the bar's second extreme places is live only from the next bar's open, executable at that fill or not – 1013 refills on the tailc-w2r-* tapes, none on the fill's bar (tests/test_coof_w2_refill_tapes.cpp). After a fill forced onto the first extreme it fills at the second extreme's print when executable there and otherwise rests at its own level from that extreme, as does any entry executable at its birth but not at the next extreme: a limit as a StopLimit armed at the extreme, a stop as a trail pinned there, the plain stop again at the next open. An exit stop the recalculation of a later fill at the open places already through is live from the first extreme, filled there when still through and otherwise at its own level from there (tests/test_coof_open_newborn_exit_tape.cpp). With the bar magnifier the entry is live from TradingView's next intrabar fill point (PineScheduler::next_input_waypoint), and from the next bar's open after a fill at the chart bar's close print (tests/test_magnified_coof_refill_tape.cpp).tests/test_native_point_checks.cpp; no pre-release epoch outside v19 is changed.Per-bar recording (set_broker_state_hash_recording(true), §1.8 RP9) appends one broker_state_hash() per script bar, and every row is a whole fold: the kernel's broker state, then the host's extension. Lane E24 made the continuation's own logs fold by their tail and left a STOP bucket; both final audits (Opus F9, Codex A3-8) carried it forward. Each cost it holds is measured here on fd785928 (process CPU, best of 3 to 5, host load 7 to 26) and ruled. None of them touches a run with recording off: that path latches the scalar once per run, and its values are what they were (R5 lane PERF-P1 made that latch a view folded on first read, and R5 lane V19-A made it eager again, row C).
| # | cost | measured on fd785928 | ruling |
|---|---|---|---|
| A | the Pine adapter's recording fold: O(retained history) per row | ×3.92 to ×4.02 per doubling — 9.26 s at 800 bars of order-and-cancel, 19.5 s at 2,688 bars of re-issue — against at most 0.015 s with recording off | open: blocked on storage and a write barrier outside lane F9's files; no epoch needed; value witness pinned. Resolved by R5 lane V19-E (pineforge-source-adapter/v4): retired rows are erased and the logs fold as running digests, so a row costs what is live; completed by R5 lane V19-FIX for the one shape V19-E still held, an exit cancelled and re-placed every bar at one level |
| B — historical | the pre-v19 kernel recorder's closed-row walk: O(closed rows) per row | ×2.61 rising to ×3.64 per doubling with trades (0.80 s at 24,000 bars and 1,200 closed rows) against ×1.94 to ×2.08 without | historical ruling, superseded by the v19 value epoch (R5 lane V19-A): each closed row now folds once, into a running digest |
| C | the terminal continuation capture (E24 STOP1) | 0.048 to 0.052 s, 23 to 24 % of the 43,008-bar gated replay | revised by R5 lane PERF-P1 (supervisor ruling): latched as a view, folded on first read; no value moves. Eager again since R5 lane V19-A: the v19 fold is the live state alone, so a view saves nothing |
| D | the kernel's cohort rosters on a Pine host: O(openings) per row (V19-A's finding) | ×2.45, ×2.80, ×3.06 per doubling from 4,000 to 32,000 bars of a flip recorded per bar on 91d65ad6 (0.206 s to 4.33 s), against ×2.02 with recording off | resolved by R5 lane V19-FIX: the adapter takes an origin that can no longer be bound off its kernel roster, so the continuation folds the openings that still work or hold a lot |
A. The adapter's recording fold. Until R5 lane V19-E (below), PineExecutionAdapter::hash_state pine_state_hash.cpp:315 folded, at every row, every placement snapshot the run had accepted (a sorted walk of every key, since replaced by the walk of the retained rows, placement_ pine_state_hash.cpp:382) — one per accepted request, cleared only by reset_for_run pine_adapter.cpp:2572 — and reflected the whole admission journal (since replaced by a running digest of its events, admission_journal pine_state_hash.cpp:837). PineScheduler::hash_state pine_state_hash.cpp:921 re-folded the consumed source prefix too (consumed pine_state_hash.cpp:925-926). Measured with recording on and off, in two shapes:
strategy_order accepted and cancelled per bar): 200 / 400 / 800 bars = 0.588 / 2.305 / 9.262 s (×3.92, ×4.02), against 0.0014 / 0.0012 / 0.0023 s off;ReissueReplay test_l4g_runtime_budget.cpp:73, an exit re-issued every bar) on its 672-bar tape repeated 1, 2 and 4 times: 1.247 / 4.929 / 19.497 s (×3.95, ×3.96), against 0.0044 / 0.0078 / 0.0151 s off. At the gated 43,008 bars that is about 80 minutes, against 0.21 s.A sample call tree of each run splits the adapter fold: the placement snapshots are 55 % of it on the order-and-cancel shape and 97 % on the re-issue shape, the journal reflection the other 45 % and 1 %, the scheduler prefix under 2 %. Most of a snapshot's bytes are field paths the reflection builds as strings, one per field (Reflect::field market_admission.cpp:658-661).
Why it is not fixed in this lane. The fold can be made incremental without moving a single value, but not from inside lane F9's files:
BrokerStateHashSink engine.hpp:477), and FNV-1a over a fixed byte segment S is an affine map with a low-byte-indexed offset, T_S(h) = p^|S|·h + C_S[h mod 256] (mod 2^64); two such maps compose in 256 steps. A row folded once can be applied in O(1) to whatever state the fold arrives with, byte for byte. Lane F9's probe checked the identity 30,000 times against the sink's own byte chain, with no mismatch.PlacementTable pine_adapter.hpp:595, admission::Journal market_admission.hpp:218 or PineScheduler has a member it could keep one in. Adding one is a header change in include/pineforge/source/, outside this lane.PlacementTable hands out mutable rows — the non-const find pine_adapter.hpp:747, the non-const at pine_adapter.hpp:763 and its iterators — and the adapter rewrites retained rows in place through them, not only rows still working. Lane F9's probe hashed every row callback to callback: the order-and-cancel shape rewrites none, the re-issue shape 1,339 rows each one callback old, an OCA re-price and a trail attach rows two callbacks old, and the row of an entry that filled at bar 1 is rewritten at bar 60, when its first exit is attached (has_full_entry_bracket pine_adapter.cpp:11914). Full-table scans rewrite rows as well (suspend_brackets_for_reversal pine_adapter.cpp:2126, revive_brackets_after_margin pine_adapter.cpp:2372, preserved_by_close_all pine_adapter.cpp:15222), and admission::Journal::retain market_admission.hpp:244 drops events from the middle of the journal. So "a row stops changing once its request is
done" is false, and a cache resting on it would move a value.What the lane that fixes it needs: a per-row revision (or a mutate() accessor) that PlacementTable bumps whenever it hands out a mutable row, with the adapter's read-only lookups and scans moved to the const accessors so that they stop bumping it; a home for the composed transforms beside it; the same for the journal (an append composes, retain and reset rebuild) and for the scheduler's consumed prefix. That is at least include/pineforge/source/pine_adapter.hpp, market_admission.hpp and pine_scheduler.hpp, src/source/pine_adapter.cpp and market_admission.cpp beside pine_state_hash.cpp, plus a scaling row that fails the reproduced ×3.9 to ×4.0 per doubling and passes at ×2.3 or less. The alternative — fold one cached digest per row instead of the row's bytes — needs the same barrier and the same home, saves only the 256-entry tables, and moves every Pine hash: an epoch decision (pineforge-source-adapter/v3 to v4), not a neutral change.
Resolved by R5 lane V19-E, through the epoch. V19-E took neither the barrier nor per-row transforms: it made the retained history go away. At each bar open, after observe_terminal_receipts has consumed every command event up to the high water, PineExecutionAdapter::erase_retired_rows erases the placement row of every request the adapter and the kernel no longer work, unless a root still names it (an opened origin, an open lot, a queued command's snapshot or request, an anchored leg, a short-seed plan, an event above the receipt cursor) or a whole-table scan could still accept it (the pins K1 to K8 in that function's comment). What the later scans read of an erased row stays as a compact record: the per-id sequence minima and the first non-margin ids in PlacementTable, the consumed legs and the settled members of a bracket family (BracketRoster), the sides of a cohort's erased openings (CohortFacts::erased_opening_sides) and the chain origin a row inherits (PlacementSnapshot::chain_origin_sequence). A tombstone build (-DPINEFORGE_PLACEMENT_AUDIT=1) keeps every erased row aside and records any lookup or scan predicate that reaches one: zero hits over the full ctest suite, the equivalence battery and the 100-slot fingerprint sweep with the magnifier off and on. The v4 fold then reads the retained rows in full, in incarnation order, with the table's high water, erased-row count and a running digest of the erased identities; the admission journal's events fold into a running digest (a retain or reset below the folded prefix refolds it); and the scheduler's consumed prefix is immutable within a run, so it folds once per bar. The rewrites item 3 lists stay exact because they only reach rows the table still holds, and those fold in full at every read. Measured by tests/test_adapter_live_state_scaling.cpp on macOS arm64: the re-issue replay with recording on costs ×4.05 for four times the bars (1,000 to 4,000 bars: 0.032 s to 0.131 s) and retains 5 rows, where the base (be372243) cost ×15.64 (2.42 s to 37.86 s) and retained 1,999 and 7,999. Values moved once; trades did not (tests/test_adapter_live_state_equivalence.cpp holds each script's command events, pending orders, working requests, trades, equity and admission fields equal with every row retained and with rows erased).
Corrected at integration (INT21): K3 keeps one re-issue per candidate. K3 first pinned every current-cycle re-issue of a revival candidate at a later bar and another stop. When a trade closes and the script keeps calling strategy.exit every bar for entries that are not open, the legs placed on the cycle's first bar keep their lifecycle target and stay candidates (K1) to the end of the cycle, so every later re-issue was pinned: two rows per exit per bar for the rest of the cycle, each walked and compared by the sweep at every bar open. That is O(bars^2) in the cycle's length: scripts of this shape ran dozens of times slower than on the base, which keeps every row but walks none of them at a bar open. The revival's superseded test asks whether any such re-issue exists, and a re-issue's family, ids, placement bar and stop never change, so K3 now keeps the first one (the lowest incarnation) for each candidate and the rest are erased once nothing else holds them; no trade moves. tests/test_adapter_live_state_scaling.cpp's third workload, a daily chart of that shape, holds it: 12 rows retained and a ×4 cost for four times the bars (4,000 to 16,000), where the uncorrected sweep retained 7,748 and 31,024 rows and cost ×18.6. The tombstone audit counts that scan as the existence test it is: an erased row is a hit only when no retained row answers the test first.
O1W: filled limit retirement and incremental K3. A terminal Applied fill of an origin-free limit without protection or a replacement definition records PlacementSnapshot::filled_exit; a fresh request incarnation clears that fact. K1 releases an origin-free limit-only filled exit whose placed and inherited lifecycle definitions contain no stop, loss or trail and no pending replacement. Suspension cannot give it an executable margin-revival stop, and no opened origin asks its consumed-leg question. Roots and K2/K3 still pin it as evidence for another candidate. In particular, a later same-family/id/from-entry row with different stop bits can still be the first K3 witness for an earlier candidate; distinct ids or identical stop bits cannot satisfy that test. Future calls cannot require the erased row as a candidate: it is already non-revivable, and K3 only looks forward in placement age. Filled stops, trails, bound-origin brackets and inherited replacements keep the existing lifecycle pins.
K3 memoizes its lowest-incarnation witness per candidate and the last scan's placement high water. New candidates read all retained rows; unresolved old candidates read only newly placed rows. Resolved candidates keep their pinned witness. Anchored legs adopted below the high water are explicitly compared against old candidates too; a wholesale row rewrite invalidates the memo. The memo is reconstructible from the table and cleared at run begin. Wholly settled bracket families with no consumed-origin facts leave the active family table, keeping their exact prefix record in an archive. Appending restores that record; bracket cancellation still emits its settled count of same-run non-working cancellations. Ordinary family scans and hash reads visit active families only. The archive's keyed digest is maintained on every archive, restore and erase, under pineforge-settled-bracket-families/v1; the terminal-fill marker and earlier placement erasure are explicit hash value changes within the v19/v4 retirement epoch, like INT21 above. Mutable placements and family tails continue to fold in full. The retired_row_scratch_ waiver covers only the derived memos, not the archive or terminal-fill fact. test_position_tail_retirement compares retaining/erasing runs and exact K3-oracle answers; test_position_tail_retirement_work counts production K3 pairs and hash-row visits over doubled scale-out feeds.
Completed by R5 lane V19-FIX (audit X2, X9), inside the v19 epoch. Two reads still cost what the run had done (macOS arm64, Release, process CPU). K1 held a leg lifecycle of the current position cycle for the margin revival, and V19-D released one only once a re-issue at another stop superseded it, so an exit the script cancelled and re-placed every bar at ONE level kept a row per bar for the whole cycle, each walked by the sweep at every bar open: on 91d65ad6 the audit's probe_recancel_constant cost ×4.90, ×4.79, ×4.65 per doubling from 4,000 to 32,000 bars (11.2 s at 32,000), and strategy.cancel_all() with an exit at position_avg_price * 0.95 ×3.87 to ×4.26 up to 16,000. The same hold let the revival read a cancelled exit as a standing one (OL14), and a gapped stop a declined reversal had parked for the next margin-call slice came back after a cancel too. A script cancel now retires every leg of each exit row it names and drops such a parked stop (retire_cancelled_exits pine_adapter.cpp:2284), the revival skips a withdrawn exit, and K1 holds a withdrawn leg only while its origin is live or opened – the one reader left is origin_leg_consumed, and it asks about such origins alone (erase_retired_rows pine_adapter.cpp:3854). Both shapes now cost ×1.89 to ×2.12 per doubling up to 64,000 bars and retain 3 rows. Row D was the kernel's half: the adapter enrolled every opening in its id's kernel roster (cohort_add) and never took one off, and hash_cohorts folds every member at every read. An origin whose request chain no longer works and that holds no open lot can never be bound again, so at every bar open that follows a command event the adapter takes it off (release_closed_cohort_origins pine_adapter.cpp:4410); a lot opened by a replace successor keeps every older member of its roster while it is open, because its root is not a public fact. The flip then costs ×1.99, ×2.01, ×2.02 per doubling, and its rosters end with one member where they ended with one per opening. On a Linux aarch64 host (GCC 13, one pinned X925 core) the same three probes read ×4.18, ×4.13, ×4.97 / ×4.32, ×4.44, ×5.06 / ×2.39, ×2.68, ×3.02 before and ×1.99 to ×2.02 after. The price is one roster removal per closed opening: the 100 public slots (magnifier on) run +0.54 % more instructions at the median and +1.57 % at most (slots 007 and 030, the latter with 23,164 trades). The dead PineExecutionAdapter::path_order_, written and folded but never read, left the fold in the same step. tests/test_adapter_live_state_scaling.cpp holds all three shapes at a small leg calibrated to at least 0.1 s CPU and four times its bars, below ×5 and within 64 retained rows and 8 roster members; on the base they fail at ×25.6 (the constant level, 16,000 to 64,000 bars without recording), ×27.4 (the cancel_all shape) and ×6.9 with 8,000 roster members (the flip), and three mutants of the product fail it too (K1 holding a withdrawn leg again: ×25.9 and 32,000 rows; no roster removal: ×6.96 and 8,000 members; the removal kept but a walk over every origin the run accepted at every bar: ×12.0).
The witness, pinned now. tests/test_adapter_recording_hash_witness.cpp records every row, a read after every command and the final scalar of six scenarios — submit, replace, cancel, OCA, re-issue with a late bracket, trail — on a fresh host, on the same host run twice (a reset answers the fresh values) and with recording off (the same state at every read): 360 checks, harvested on fd785928. Its projection folds a fixed execution hash, as tests/test_adapter_report_relower.cpp does, so the pins do not depend on the installed tzdata. Three stale-fold mutations fail it at the first point that moves: never re-folding has_full_entry_bracket (first at the re-issue scenario's row 8, the bar its late bracket attaches), freezing the cancel receipts of older rows (first at each cancel or re-price), and a consumed-prefix cache that stops at 16 bars (row 16 in all six scenarios).
B. The kernel recorder's closed rows. broker_state_hash_from_execution_hash engine_state_hash.cpp:44 folds six fields of every closed row (trades_ engine_state_hash.cpp:126-127) at every recorded row, so the recorder costs O(closed rows) per row. The audit's bare host — one round trip per 20 bars, one request accepted and cancelled per bar — from 1,500 to 24,000 bars and 75 to 1,200 closed rows: recording 0.0087 / 0.0228 / 0.0676 / 0.2201 / 0.8014 s (×2.61, ×2.96, ×3.26, ×3.64); the same with no trades 0.0056 to 0.0884 s (×1.94 to ×2.08); trading with recording off 0.0038 to 0.0596 s (×2.00). Recording without trades stays linear, so the growth is the closed-row walk: about 0.71 s of the 0.80 s at 24,000 bars, some 48 folded bytes per closed row per recorded row.
Ruling (ADR-0001 rule 2: a cost ruling for every host, with no TradingView fact in it): the walk stays, and this is its documented cost. An exact running digest is possible by the same algebra as A and would move no value, but it needs two things the kernel does not have. One is storage for the composed transform: a BacktestEngine member changes the layout of the script ABI (then engine_script_run_v18, since advanced), which is an epoch decision, while a member of the heap-owned execution consumer (ExecutionConsumerSlot engine.hpp:1894) is not. The other is a contract that the six folded fields of a booked row are never rewritten. In the tree they are not; the only writer after booking, the Pine host, rewrites close_cause, exit_from_bracket, exit_comment and entry_incarnation only; its writes are close_cause pine_strategy_host.cpp:978, exit_comment :1109, close_cause :1124-1129, exit_from_bracket :1634 and entry_incarnation :1809-1819. But trades_ engine.hpp:676 is a protected member any host can write, so a lane that takes the consumer-side digest has to make that contract explicit. Folding (count, digest) in place of the rows saves the 256-entry table but still needs the storage and the contract, and it moves every broker hash: an epoch decision (then pineforge-broker-state/v18, since advanced).
Revised by the v19 value epoch (R5 lane V19-A). The epoch took that decision: pineforge-broker-state/v19 folds the closed rows as their count and a running digest the heap-owned execution consumer keeps (NativeExecutionConsumer::closed_rows_digest), so no BacktestEngine member moved for it. The contract is explicit: a row is final once the applied notification of the execution that booked it has returned, and a read folds the rows past that mark into its answer without keeping them. The Pine host's amendments of a folded field (the aggregated exit date among them) land inside on_native_applied, but one writer the census above missed changes final rows: the same-bar exit order (sort_same_bar_exit_trades) reorders a bar's trailing bracket exits by command sequence at the next script point, after their notifications have returned. It now names the first row it moved through NativeStrategyHost::native_closed_rows_amended, and the digest folds the rows again from there; rows removed from the end (a test host's trades_.clear()) are found without a name, because the kernel only appends. Debug builds re-fold the digested rows at every run's end and abort on an unnamed change, which is how the reorder was found. Recording now costs linear time: four times the bars cost 3.8 times the CPU with a round trip every ten bars (tests/test_native_state_continuation.cpp, part 6; the base 14.4 times).
C. The terminal continuation capture (E24 STOP1). A Pine run's scalar broker_state_hash() folds the continuation as it stood at the run's last script point, not after the run's teardown, so that it is one value with recording on or off (broker_state_hash_projection pine_strategy_host.cpp:469). capture_script_continuation_hash pine_strategy_host.cpp:865 takes that snapshot at the last batch bar (last_batch pine_strategy_host.cpp:951), and taking it is one full continuation fold per run. Measured on the gated replay — ReissueReplay on 43,008 bars, run() process CPU, best of 5, three rounds — it is 0.2116 / 0.2137 / 0.2141 s with the capture and 0.1632 / 0.1614 / 0.1652 s with it suppressed (a probe build only): 0.048 to 0.052 s, 23 to 24 % of the replay. E24 put it at about a third; the Opus final audit at 22.0 %. Suppressing it is not neutral: the final scalar moves from 235509512418453834 to 14899872126237769279 (at 2,688 bars from 9192851617711936750 to 1972323229210109824), because the fallback folds the continuation after the terminal point.
Decision (F9): the capture stays eager. Deferring it byte-identically needs a captured continuation view — the consumer's state at the terminal script point, kept until someone asks — which is a kernel design of its own; any other deferral moves the scalar and is an epoch question. The capture is one linear fold per run, inside the runtime budget (8.4 to 8.7× against 15× in the final audits), so neither is worth taking now.
Revised by R5 lane PERF-P1 (supervisor ruling, 2026-09-23): the capture is a view. E24's STOP bucket and the decision above were campaign design notes, not owner rulings; the owner's red line is hash values, and a view keeps every one of them by construction. PERF0-P measured the capture at a fifth of every Pine run — about 56 ns per driver point, four points a bar — paid although neither the benchmark, the C report nor the corpus sweep reads the scalar. Lane PERF-P1 (commits d8ca1389 and ba99eca1) latched a view unless a recorded row needed the value at once: continuation_hash()'s own fold run into a recording sink, the bytes the fold would consume, with the command history's and the driver log's digests left as holes at the logs' lengths, folded on the first read. It was exact because both logs only grow within a run. Measured (process CPU, interleaved): the gated replay 0.2618 → 0.2029 s on macOS (7.71–9.08× → 6.58–7.19× of ab9714be, load 36–41) and 0.4054 → 0.3606 s on Linux aarch64 (12.87× → 11.45×); the Pine benchmark 0.84× there, the median of the 14 shared probes and of the 100 public slots, magnifier off and on.
Revised again by R5 lane V19-A: the capture is eager. The v19 continuation (native-consumer/v9) folds the consumer's live state alone — no command history, driver log or account log — so what a view would record is as many words as the fold mixes, one multiply-xorshift each, and deferring them saves nothing a reader does not pay back. capture_script_continuation_hash takes the value at once again, as it did before PERF-P1, and the view, its recording sink and the defer_continuation_views switch are deleted (ADR-0001, "Kernel state the adapter sets"). No value moves: tests/test_native_continuation_view.cpp and tests/test_adapter_continuation_view.cpp keep their scenarios and their pins, which lane V19-A re-pinned once for v19 before the revert, and read them unchanged after it.
The fourth audit's G2 residual (AUDIT4-opus §6 rows G2-01 to G2-39, its item X15, and AUDIT4-codex A4-THIN, A4-PATH-ORDER and A4-DUST) asked that the Pine adapter keep no mechanism that duplicates a kernel capability, that every mechanism it does keep have a row here or in the ADR and a witness, and that the size target be met or re-set by a measured ruling. The lane took R5 lane H-MEASURE's measurements as its inputs.
X15, item by item. "Byte-identical" means the corpus (312 probes), the identity battery with its hash columns, the 100 public slots and the Pine witness all print the same bytes before and after.
| item | the adapter's copy | now | evidence |
|---|---|---|---|
| a | compute_liquidation_price, a second closed-form solve of the margin level | gone: strategy.margin_liquidation_price reads native_liquidation_price() in TradingView's tick spelling (row MG8) | bit-identical at every bar of the 1,021 margin-call runs of a 1,200-configuration seeded sweep; the 179 runs with margin calls switched off declare no margin model and read na (tests/test_margin_call_l4a.cpp) |
| b | open_entry_fees_, the cash-fee ledger the sizing basis restored (since R5 lane PAR-CASHFEE only the money gates' and the margin money's equity restore it, §3.10) | kept, measured: it is not a mirror of the kernel's per-lot open_entry_commission | the two part after a FIFO strategy.exit(from_entry) closes an older lot of another id (the ledger drains the named id's fee, the kernel the lot it closed): 1 of 400 seeded fee runs and three publication-witness configurations move. The lab tv tape hthin-x15b-fifo-fee-basis shows TradingView restores neither (§3.9b finding 1), so neither answer is TradingView's and the ledger stays byte-identical |
| c | CohortFacts::live_units_by_origin, the per-id unit ledger | kept, measured: it is the per-id logical exposure, not the kernel's lots | against the kernel lots booked under each id at every callback of 600 seeded runs it differs in 1,494 of 287,549 checks (1,489 beyond 1e-9): a close named by one id drains another id's older lot, and the ledger debits the named id. P10's reservation reads it as the entry's own quantity |
| d | the pre-open slice's own ×4 | gone: schedule_preopen_margin_slice sizes through source_margin_units (row MG7) | byte-identical; the M10 differential's band and fudge cases now book the shared rule's 1 and 1, the kernel's and ab9714be's |
| e | six copies of the civil-date arithmetic | gone: native_calendar::native_civil_date / native_civil_days (ADR-0001, H-THIN rule 2 rulings) | byte-identical by construction; tests/test_chart_day_key_arithmetic.cpp, tests/test_utc_month_key_arithmetic.cpp |
| f | aggregates_input_bars, read from the two timeframe literals | gone: the kernel's NativeStrategyHost::native_aggregates_input_bars() (ADR-0001, H-THIN rule 2 rulings; row AG2) | byte-identical on the corpus and every witness; tests/test_aggregates_input_bars_literals.cpp counts the two classes where the copy parted from the kernel |
Beside X15 the lane lowered the host-owned excursion model onto the kernel's sampler (row RP1, E19), tried the pyramiding count on max_open_lots and kept it (row MG3, M13), and fixed the one reservation divergence measured beside it (P10). E19 and P10 are the lane's only moves: its one hash step re-pins every witness they move, and the corpus baseline moves on the 19 probes whose excursion cells moved toward TradingView.
AUDIT4 §6, re-derived (MR measured retention, RU ruled unmeasured, UF unfinished, RM removed).
| rows | mechanism | AUDIT4 | now |
|---|---|---|---|
| 01, 02, 05, 07, 08, 11, 14, 16-20, 24-26, 30 | sizing floors and freezing, the FX step, the liquidation slice, margin money, the FX broker-open slice, max_position_size, the risk rules, the price grid, trail conventions, the second bracket path, the range-end shape, the auxiliary drive, calc_on_order_fills, percent-exit reservation, cancel loops | MR | MR (unchanged) |
| 03/04/06, 29 | sizing quotients and the fill-quantity probe, preview aggregation | RM | RM |
| 09, 10, 13 | margin-check scheduling, checkpoints as market executions, AdmitWithHostMargin | RU | MR (H-MEASURE M7, M8, M11) |
| 12 | the pre-open slice and its own ×4 | RU + copy | MR; the copy RM (X15 d) |
| 15 | pyramiding as a per-cycle count | RU | MR (H-MEASURE's tapes; H-THIN's lowering trial, row MG3); P10 fixed |
| 21 | short-seed report swaps | RU + GAP | MR (H-MEASURE's tape) |
| 22 | host-owned excursions | MR (partial) | RM (E19, row RP1) |
| 23 | request.security exclusions | RU + THIN | MR (H-MEASURE, the ADR subscriptions row) |
| 27 | aggregated re-stamp and re-dating | MR | MR; its predicate is the kernel's (X15 f, row AG2) |
| 28 | the dead hashed path_order_ | RM / UF | RM: fac8389a deleted it and its fold |
| 31 | the second liquidation-price solve | UF | RM (X15 a) |
| 32 | the 1e-10 dust sweep | RU + GAP | MR (H-MEASURE's tape; the census below) |
| 33 | the entry-fee ledger | UF | MR (X15 b) |
| 34 | begin-bar validation | RU | RU: the kernel's ingress validation, ruled by E12 |
| 35 | the presented clock and index writes | UF | ruled: Pine presentation, not a kernel cursor copy – bar_index counts script bars with the temporary index of a not-yet-sealed callback, and last_bar_index / last_bar_time freeze at the live tail's horizon, neither of which the kernel's interval_index models |
| 36 | session.ismarket through the legacy predicate | UF | RM on the kernel side (H-MEASURE's six tapes: in_session is TradingView's); the transpiler now asks the session calendar at the bar's open, right on the six tapes, and only a bar that opens inside a break is left, which in_session holds in market as TradingView does (tests/test_session_break_open_bar_tape.cpp) |
| 37 | the per-origin unit ledger | UF | MR (X15 c) |
| 38 | civil-date copies | RU | RM (X15 e) |
| 39 | aggregates_input_bars | UF | RM (X15 f) |
The quantity-epsilon census (A4-DUST). Every quantity comparison with an epsilon in pine_strategy_host.cpp and pine_adapter.cpp, 85 of them at 57 sites, was measured three ways in a scratch copy of the tree that evaluates each in its epsilon form and its exact form: over three seeded witness families (5,500 runs of decimal quantities, dust-sized closes, pyramiding, reversals, POOC, calc-on-fills, margin at 5-100 %, lot grids 1 to 0.001, fees, multi-command bars, paired entries, sticky and trailing exits), over the 610 CTest rows that link the library, and with each deciding comparison made exact on its own. None is removed or moved: the kernel's opt-in quantity_tolerance charges a snapped reduce its request and books every dust lot it closes as a row, where these rules do neither (ADR-0001, its row). Three local copies of the two shared constants were folded into them, byte-identical. The census reads, per site (witness and CTest evaluations; "band" = evaluations where the two forms disagree; runs moved of 5,500 and CTest rows failing with the exact form):
| sites | what the epsilon decides | evaluations | band | exact form |
|---|---|---|---|---|
A1 on_native_applied sweep | a lot of at most 1e-10 is erased, without a row | 1,615,742 | 1,167 | 30 runs; fails test_pine_dust_sweep_paired (tape hm-g232-decimal-dust) |
| A2 flat reset; A3-A5 dust lots in the fee sum, the POOC FIFO view, the KI-62 cover | a dust book or lot is treated as none | 2,015,064 | 17 | no run, no row |
| B1, B2 POOC close flush | a flat book, and a close within 1e-10 of it as a Flatten | 3,110 | 4 | 4 runs |
B3 close whole-id | a close within 1e-10 of the id's basis, or of 100 %, is whole | 190,291 | 4,648 | 365 runs |
B4 resolve_terms scope cover | fill-time units within 1e-10 of the live scope are the scope | 367,513 | 284 | 214 runs |
| B5-B13 exit and reservation classes | full versus partial exits, re-issues, reservation expansion, the ANY-rule percent | 169,586,206 | 1,868 | no run, no row |
C1 source_fifo_prefix_openings, EQ enqueue_pooc_fifo_close | a POOC FIFO close's whole-lot prefix and its batching | 3,978 | 4 | no run, no row |
D1 floor_quantity_grid | the lot floor's +1e-6 of a step | 227,717 | 14,182 | 1,437 runs; fails 14 rows |
D2 quantize_close_units | a full close is the basis snapped to the grid | 383,394 | 11,172 | 349 runs |
D3 quantize_percent_exit_units | the percent-exit floor's +1e-6 of a step | 19,749 | 1,084 | 511 runs; fails 5 rows |
| D4-D9, D11-D15 reservation arithmetic | dust capacity, full families, re-issued brackets | 81,360,970 | 270 | D7: 54 runs; the rest none |
D10 close all-in percent | an all-in close's reversal handling | 36,778 | 1,571 | 3 runs |
| E1, E2 cohort drain | a cohort origin at most 1e-10 is drained | 608,401 | 895 | 16 runs |
M1-M4 source_margin_units | a dust restore, the second floor's +1e-6, the one-contract band, a dust slice | 56,680 | 395 | M2: 74 runs, fails 3 rows (test_tv_money_band_l4b among them); the rest none |
| M5-M12 margin-call carries and revivals | the one-unit call, deferred short checks, resting whole closes | 69,554 | 1,256 | no run, no row; M9 and M10's second test unreached |
| Q1-Q6, O1-O2 paired-market and short-seed identities | own versus gross frozen units, exact quantity identities | 1,099,295 | 0 | none |
Of the 85, 32 decide somewhere and 50 never do; three (B12's explicit-quantity test, M9, M10's explicit-quantity test) are reached by no seeded run and no CTest row. Every site keeps its reason at the line (an ab9714be citation or a TradingView rule); the four whose exact form fails a pinned row are the sweep and the three +1e-6 floors.
Size (AUDIT4's metric: the direct children of src/source, include/pineforge/source, src/compat/pine and include/pineforge/compat/pine, .cpp and .hpp). 29,701 lines at the audit's tree (91d65ad6), 29,920 at this lane's base (0d76a099), 29,059 after it: 861 fewer, below the owner's ruling (at most 29,693, aiming at about 29,350 once the measured duplicates went). The earlier target of 27,008 stays out of reach for the reason H-MEASURE measured: the growth since round 3 is V19 state lifetime and the performance lanes, not duplicated mechanism.
Findings, recorded and not changed here.
floor((pct × E − fee) / close) on the tick-printed quantity grid, where E is the equity with every open lot's pro-rata entry fee charged: the tape hthin-x15b-fifo-fee-basis fits that rule on all six of its sized entries, and the corpus probe order-percent-equity-cash-commission-01 on all 366 of its trades. The engine, before and after this lane, restores the open lots' cash fees and reserves no fee for the new order, so that probe's quantities differ from TradingView's in the fourth decimal. R5 lane PAR-CASHFEE measured the rule on twelve more tapes and fixed it (§3.10).calc_on_order_fills run of the seeded witnesses can stop with the kernel's "native current evaluated allowance mismatch": 117 of the second family's 2,000 runs, every one with calc-on-fills. The base tree stops 111 of those seeds, 108 of them the same runs; the lane's E19 and P10 moves shift which runs reach the failing state, not the failure.H-THIN's finding 1 (§3.9b) as a rule, measured and fixed. TradingView sizes a percent-of-equity DEFAULT quantity under a cash commission from strategy.equity – every open entry fee charged, the cash ones included – and leaves out of that money the fee the order itself pays, on the symbol's quantity grid:
units = floor((pct × E − fee) / (price × pointvalue));units = floor(pct × E / (price × pointvalue + fee)), the money covering the units' notional and their fee together.price is the price the quantity is sized at: the signal close for a market strategy.entry and for strategy.order, the level for a limit or stop entry. A reversing entry sizes its new position the same way from the equity that still carries the reversed position (its open profit and its entry fee share), and its one order pays one fee, split over the closed and the opened units. Before this lane the adapter took the percentage of an equity that restored the open entries' cash fees (percent_commission_live_equity) and reserved nothing for the order, so every such quantity came out too large.
Evidence: thirteen lab tv tapes (the private fixture set cash_fee_sizing, ws-report-v1, rangeProof covered; BINANCE:ETHUSDT.P 15 over 2025-04 and CME_MINI:ES1! 15 over 2025-06). A tape's E is read off the tape itself – the initial capital, the net profit closed before the signal, and each open piece marked at the signal close less its pro-rata entry fee.
| tape | fee | default % | margin | scenarios | trades | sized entries the rule fits |
|---|---|---|---|---|---|---|
pcf-order-p50-m25 | per order 1000 | 50 | 25 | flat, pyramided, partial close, reversals | 15 | 14 / 14 |
pcf-order-p100-m25 | per order 1000 | 100 | 25 | the same | 15 | 14 / 14 |
pcf-order-p200-m25 | per order 1000 | 200 | 25 | flat, reversals | 6 | 6 / 6 |
pcf-order-priced-m25 | per order 1000 | 50 | 25 | limit, stop, strategy.order | 7 | 7 / 7 |
pcf-order-p100-m100 | per order 1000 | 100 | 100 | flat, reversal | 11 | 6 / 6 |
pcf-es-order-p50-m25 | per order 1 000 000 | 50 | 25 | ES1! (point value 50), pyramided, reversal | 7 | 7 / 7 |
hthin-x15b-fifo-fee-basis | per order 1000 | 50 | 100 | FIFO exit of another id (lane H-THIN) | 11 | 6 / 6 |
pcf-contract-p50-m25 | per contract 20 | 50 | 25 | flat, pyramided, partial close, reversals | 15 | 14 / 14 |
pcf-contract-p100-m25 | per contract 20 | 100 | 25 | the same | 15 | 14 / 14 |
pcf-contract-p200-m25 | per contract 20 | 200 | 25 | flat, reversals | 6 | 6 / 6 |
pcf-contract-priced-m25 | per contract 20 | 50 | 25 | limit, stop, strategy.order | 7 | 7 / 7 |
pcf-contract-p100-m100 | per contract 20 | 100 | 100 | flat, reversal | 9 | 6 / 6 |
pcf-es-contract-p50-m25 | per contract 1000 | 50 | 25 | ES1! (point value 50), pyramided, reversal | 7 | 7 / 7 |
That is 60 of 60 entries per order and 54 of 54 per contract, and all 366 trades of the corpus probe order-percent-equity-cash-commission-01 (25 %, 1.5 per order). The restored basis fits only the flat entries; no fee reserve, or a per-contract fee not scaled by the point value, fits none. A priced entry sized at the signal close instead of its level fits none of the eight.
The fix is adapter policy (default_sizing_cash, default_sizing_reserves_cash_fee): the sizing snapshot records strategy.equity beside the restored equity (PineSizingSnapshot::strategy_equity, set by mark_sizing_equity only where the run declares a percent-of-equity default quantity and a positive cash commission, and folded only there), and the money it hands the kernel's CashValue is the percentage of it less the per-order fee, or scaled by unit / (unit + fee) per contract. The money gates and the margin money keep the restored equity; a per-call qty_type (which TradingView's compiler refuses) keeps its conversion.
Replay (tests/test_cash_fee_sizing_tapes.cpp, the tapes' scripts through the Pine adapter on their bars): on the eleven tapes without an entry-bar margin call the adapter books 0 of 111 trades as TradingView does before the fix and 111 of 111 after it (ids, sides, times, prices and quantities exact, commission to 1e-9, net profit to 1e-7 of |profit| + |commission|).
Corpus. order-percent-equity-cash-commission-01 is the corpus's only probe with a percent-of-equity default quantity and a cash commission, and the only one that moves. The corpus harness gives it no quantity grid, so its quantities are the raw quotient; before → after: quantities inside TradingView's 0.0001 cell 0 → 355 of 366 (the other 11 lie 1e-7 to 3e-6 below the cell, where the harness's unfloored quantities left the equity a few cents under TradingView's), largest |Δ quantity| 0.00106 → 0.0000985, 90th percentile |Δ net profit| 0.029 → 0.0023; every one of the 366 quantities and net profits is closer.
Hash step. Only runs that record the new snapshot value move: tests/test_adapter_quiet_bar_pinned.inc 80 values over 24 rows, the six random scenarios with a percent-of-equity default quantity and a cash-per-order fee (Random05, 06, 20, 35, 38, 46), no count; and tests/test_publication_witness_pinned.inc 34 of 156 digests – exactly the Config runs whose configuration sweep passes through such a declaration, by a census of the battery – six of them closing a different number of trades. tests/test_l4b_policy_regressions.cpp pinned the old rule (20 units where the rule gives 9.9 + 9.89).
Findings, recorded and not changed here.
pcf-order-p100-m100: a long filled 0.01 above its signal close loses 0.0008 at the open and 2.2032 more on the bar; a short loses 0.4592 at the bar's high), and every margin-call order pays its own per-order fee. The adapter's margin equity restores a cash entry fee (NativeMarginEquityBasis::MarkedEquityBeforeOpenCommission) and books no such call, so the two tapes part from their second scenario on; the test pins the adapter's rows as a recorded divergence. The quantity sized is TradingView's: the first scenario is TradingView's trade and the second's quantity the sum of TradingView's pieces.price × pointvalue × fx; no tape has an account currency other than the symbol's, so the FX case is unmeasured.| # | Risk | Why it bites | Containment | Src |
|---|---|---|---|---|
| E1 | Spec hashing. hash_spec folded every field unconditionally (native_execution_consumer.cpp:236-258); every block added since folds only when set | Any new field, even defaulted, changes every continuation / broker-state hash, adapter runs included | Fold new blocks only when set, as a tagged extension (precedent precommit_digest_ native_execution_consumer.hpp:2096); test that a default spec hashes to the pre-change constant | F |
| E2 | Margin. The TV margin call is built host-side from kernel primitives (a Stop-triggered Reduce bound to openings, or execute_current, which survives in submit_margin_call_slice pine_adapter.cpp:18556); its chronology lives at schedule_margin_call_path pine_adapter.cpp:20838 and its excursion sample differed by chronology (the Pine host's own excursion model, retired by R5 lane H-THIN: the kernel's sampler books it) | A kernel model under an adapter run would liquidate twice or reorder settlement; a checkpoint that reduces mid-path changes lot state exactly where the adapter's chronology lives | Superseded by R5: the adapter declares spec.margin — maintenance-only, so no opening is decided twice (AdmitWithHostMargin, MG4) — and the kernel owns the mechanism (check points, resting liquidation, receipt) while the adapter's hooks answer TradingView's scheduling, money and slice (ADR-0001 "Margin"). What stays host-side is named there: the chronology exceptions, the pre-open slice, the 1x-long money call and the broker-open FX rollover, whose kernel FxRoll point measures elsewhere (MG9). Corrected by R5 lane F7 | F O S |
| E3 | Sizing. TV sizes from the tick-rounded signal close, ten-significant-digit-rounded percent-of-equity money (source_money_round pine_adapter.cpp:3329, applied when the quantity grid is staged) and fee-reserved cash, frozen at signal, with fill-time exceptions in COOF paths (default_sizing_shape pine_adapter.cpp:3380, the COOF at_fill exception pine_adapter.cpp:8828) | One "generic" percent sizing serving both would break parity or import TV rounding into the kernel; the last ulp differs on lot-stepped instruments | Two paths on purpose: Sized (kernel, plain arithmetic, timing as a first-class field) vs HostSized + the adapter resolver. Never "simplify" the adapter onto the kernel basis. What the two paths share is the conversion: since R5 lanes N11 and F7 the division by price × point value × FX and the percentage fee reserve exist once, in the kernel (native_sized_units), for a default AND a typed (qty_type cash / percent) quantity. The adapter keeps what is measured TradingView: the money basis (signal-time equity, TradingView's margin equity at a typed fill, the hypothetical Flatten's balance for a typed reversal), the freeze, the fill-time exceptions and both lot floors (tests/test_adapter_sizing_relower.cpp cases 8, 9, 11: kernel 0.0392 against the percent floor's 0.0391 on a 0.0001 lot), and a staged FX series keeps Sized wherever the acceptance coordinate converts at the source's rate (case 12) | F O S |
| E4 | Calc on fill. Kernel suffix eligibility is continuous along the segment; TV refills only at O/H/L/C waypoints – on a magnified path at each intrabar's open, twice, and its two extremes, never its close (R5 lane MAG-INTRABAR) (PineScheduler::recalculate pine_scheduler_native.cpp:767, coof_next_waypoint pine_adapter.cpp:7385) | Changing born_on_remaining_path or the notification order shifts adapter fills | Freeze born_on_remaining_path native_execution_consumer.cpp:6857 and drain_applied_notifications (native_execution_consumer.cpp:7618); new behaviour only behind new opt-ins | F |
| E5 | Open-bar lookahead. The adapter reads the full bar in on_bar_open (margin path scheduling uses H/L, pine_adapter.cpp:22867) | The complete bar cannot be masked globally | Opt-in OpenOnly view (A.4); loud doc warning in L0 | F |
| E6 | Double equity recording. The source host already records per bar (history: only its cadence remains, scheduler_mark_report_point pine_strategy_host.cpp:3243) | Kernel recording would duplicate points and shift drawdown | Report policy / ownership (A.1); the adapter declares KernelRecordedAtHostMarks (R1): one recorder, at the adapter's own marks | F O |
| E7 | Price grid. TV does not merely quantize: it tests triggers against a quantized bar while booking the fill on the unrounded level, so the adapter shifts the threshold (source_trigger_threshold pine_adapter.cpp:469) and books elsewhere | A kernel grid that quantizes both sides is correct and not TV; moving rounding into the kernel would change both native results and the adapter | NativePriceGrid::None for the adapter permanently; never "unify". Before L8: a byte-level experiment that the default price spelling on decimal ticks (one-ULP representations) does not move (S) | O S |
| E8 | HTF. The evaluator machinery carries TV publication semantics (lookahead, boundary deferral, early close, syminfo.type branching engine_security.cpp:67-73). The feed store is TV-calibrated too: "W" / "M" from native daily bars, native stamps as the period partition (set_native_security_feed sha256:eca3dbea0baa461982ba89f13d0a5c43277854069fb6eb2dd2f5c5471313204d engine_aux_security.cpp:51-83, fresh). Two aggregators exist: script buckets use native_calendar, request.security uses TimeframeAggregator, whose completion rules are TV-pinned (timeframe.hpp:288-330) | Reuse risks changing the order of the Pine scheduler's pumps; a bare host inherits TV partition rules; native HTF may differ from Pine HTF on early-close sessions | Registration-gated pump; no change to scheduler_feed_security_input pine_strategy_host.cpp:2080; full corpus diff; supplied-bar provenance preserved, never silently replaced by chart aggregation (S); differences on early-close sessions are recorded, not "fixed" (F). The Pine scheduler keeps one more TimeframeAggregator pass, before the run (PineScheduler::run_begin, audit M26): the script-bar count it yields (terminal_source_bar, the probe-tail suppression) and the per-input completion flags it hands on_native_input are needed before the first input arrives, while the kernel seals script intervals only as inputs arrive and offers no pre-run interval query (NativeExecutionConsumer::script_interval_at is consumer-private). Retained by R5 lane F7: replacing it needs that kernel capability | F O S |
| E9 | TA numerics are TV-calibrated (1e-10 band in DMI / MFI / percentrank; conditional-call extremum ring ExtremeRing ta.hpp:134) | Non-Pine users may expect IEEE semantics; changing them breaks parity | Rename only (§2.ii b). For an every-bar caller the ring equals a positional window (the ring comment sha256:fd1f92206558863ad8df33fd3ef3b8cc8b9bd6678a25fc98ca866168c1a81aca ta.hpp:87-88). Document. → U6 | F O S |
| E10 | Dead-but-hashed lot flags (§2.ii j) | Deleting them changes broker_state_hash | Separate, explicitly non-neutral lane (L12) | F O |
| E11 | OCA and ownership. Pine's default-sized OCA and from_entry reservation rules are source policies | Source IDs, callsite tokens or Pine labels leaking into WorkingRequestCore | Opaque keys and receipts only (R5-4) | S |
| E12 | Legacy input tolerance. LegacyTolerant changes bar-structure and slot-label admission (NativeSlotLabelPolicy native_run_spec.hpp:324, NativeFeedTolerance native_run_spec.hpp:345; NativeLegacyTolerance native_run_spec.hpp:357 is the deprecated spelling) | A rename must preserve the Pine route's hash and failure shape | Values pinned (§2.ii c); strict stays the NativeRunSpec default | S |
| # | Risk | Containment | Src |
|---|---|---|---|
| P1 | Epoch bumps move the frozen C++ ABI fixtures and the settlement ABI checker CLAUDE.md names; a neutral-refactor worker may not edit frozen headers | Bump lanes are not neutral-refactor lanes; the brief lists the header extension (§3.1 c) | F O S |
| P2 | Stale fence. CLAUDE.md names "measured sources" engine_strategy_commands.cpp, engine_fills.cpp, engine_risk.cpp, engine_market_admission.cpp; none exists in src/ at 73817c1 (only engine_orders.cpp and engine_run.cpp do) | Done: CLAUDE.md now names the post-R4-C measured sources (src/native_execution_consumer.cpp, src/native_order.cpp, src/engine_execution.cpp, src/engine_orders.cpp, src/engine_run.cpp, src/engine_path_resolve.cpp, src/source/*) | F |
| P3 | Corpus gate state. S's audit found scripts/run_corpus.sh stopping at the corpus strategy compile: committed generated.cpp files no longer match the engine (8/8 drifted from the pinned codegen commit under VERIFY=1 ONLY=validation/analyzer scripts/regen_corpus_cpp.sh) | §3.1 a relies on that script. Re-pin the engine / codegen corpus tuple before L1. Not re-verified in this merge (no builds were run). Resolved since: the corpus was re-pinned (the submodule is at b40aa8e) and the sweep runs on main | S (single-source) |
| P4 | Hash compatibility. The source hash covers hundreds of adapter and scheduler fields (PineExecutionAdapter::hash_state pine_state_hash.cpp:315, PineScheduler::hash_state pine_state_hash.cpp:921); L4 / L9 add hashed state, L12 removes some | A hash mismatch is a diagnostic until the trade diff is known, never a reason to relax parity (S). Open: does the campaign gate compare broker-state hashes across releases, or only trades (O)? → Q3 | O S |
| P5 | Test asymmetry: 430 adapter test files vs 14 native when this plan was written (on this tree 486 tests/*.cpp name source::PineStrategyHost, 128 NativeStrategyHost) | native-only tests in every lane (§3.1 b) | O |
| P6 | Stream vs batch. The stream refuses monthly input, mixed modes, in-session gaps, security feeds and source setters, and tick input under an FX curve (any non-empty curve until audit lane N6) (native-engine.md, "Stream limitations (current)") | L5 and L6 must state which refusals are generic and which are deliberate source limits | S |
| P7 | C ABI variant growth | §2.iii hardening rules | S |
| P8 | Unverified in the inputs: whether execution::LifecycleEffects (execution.hpp:136-139) is ever non-empty from the Pine path, else PlacementSnapshot::legs (pine_adapter.hpp:384) is the only live lifecycle (O); the strategy.risk.allow_entry_in setter chain beyond set_pine_risk_direction pine_strategy_host.cpp:1233 → set_risk_direction pine_adapter.cpp:27186 (O); what the ABI receipts pin (F); the probe corpus itself lives outside this tree (F) | verify inside the lane that touches each | F O |
| # | Question | Recommendation in the inputs | |—|—|—| | Q1 | Does engine_script_run_v18 open at L4 and close after L5 ∥ L6, or does L4's sizing hook ship with that batch (§3.4 row 4)? | — (raised by this merge) | | Q2 | Is a hash-domain bump (needed by §2.ii j) acceptable before the R5 final audit (phase table, native-refactor-progress.md:24, fresh), or must L12 wait? | F asks; none | | Q3 | Does the fixed-population gate (4190 probes then) compare broker-state hashes across releases, or only trades? If hashes, L4 and L12 need a hash-version plan | O asks; none | | Q4 | SizeTime::AtAcceptance needs a current execution point (current_execution_point native_execution_consumer.cpp:12577); between realtime inputs there is none — reject or fall back to AtMatch? | F: reject with InvalidQuantityBasis | | Q5 | Liquidation check granularity: PathAdverseExtreme default; is a cheaper CalculationOnly worth shipping in v1? Pure spec value or injectable policy object (S)? | F: path-accurate default; merged A.3 = spec value + host sizing hook | | Q6 | Does a native series return the current value, a retained series object, or both? It decides C ABI ownership and hash size | S asks; A.5 offers push + current-bar pull | | Q7 | Is a close-reservation key stable across host restarts or only within one RunIdentity? | S: identity rules suggest run scope | | Q8 | S's single-source proposals without a lane: neutral tail / trade-window policy (CT10), excursion-mode enum (RP1), stream-safe FX epoch (FP6 → U4) | not adopted; revisit on demand | | Q9 | Which risk limits belong in the settlement kernel and which in a portfolio / risk service above it? | S asks; L9 keeps the four Pine-equivalent limits |
All six are settled by what landed: U1 (native subscriptions take lookahead and gaps, both default false: L6), U2 (the C API landed on main as <pineforge/native_c_api.h>: L13), U3 (NativeRunSpec::risk: L9), U4 (audit lane N6, below), U5 (open_bar_view defaults to Complete: L5) and U6 (ADR-0001 rules the band a calibrated kernel mechanism with no band-width field, "TradingView-calibrated kernel mechanisms"). The rows are the questions as they were asked.
| # | Question | Positions |
|---|---|---|
| U1 | HTF lookahead default (R5-6). Should native subscriptions offer lookahead at all? | F: none, by construction (a bar is delivered only when complete). O, S: a lookahead flag, default off. Blocks the L6 brief. |
| U2 | Does v1.0.0 promise C hosts, or C++ only? | R5-7 currently says C++ only, C API last. F recommends C in 1.1, once pf_native_request_v1 can carry Sized and the working view; S makes it a prerequisite only if "usable" includes C hosts. |
| U3 | Do risk limits ship in v1? | R5-9: in scope, not a blocker. S wanted them as a prerequisite; F accepts a toolkit stand-in; O defers. |
| U4 | Stream-safe FX (FP6): in the roadmap or not? It has no R5 lane. | F: out of scope here ("O7 clock"). S: its P10, an FX epoch consumed while flat, same curve in batch and stream. |
| — | U4, answered by audit lane N6. In: the curve declared through configure_native_fx_curve is the FX epoch of the run, batch or stream; the account converts at the rate of each driver point's effective time, so the epoch is consumed by the walk itself, flat or not, and a confirmed-bar stream is the batch of the same bars. No NativeFxEpoch type and no on_native_fx_epoch callback were needed. Out, permanently: realtime rate ingestion (a new rate is a new run) and tick-driven input under a curve (its hooks and partial slots read a stale conversion clock — measured). | — |
| — | N6 follow-ups (measured on the adapter; both closed by R5 lane F7). (1) apply_fx_open_margin_slice (pine_adapter.cpp before R5 lane F7) throws on a rate step over a carried position whose margin is not 100 %: a 2× long carried through a 1.0 → 1.5 step ends the whole run with status 1 and an empty report, where the same book without a series, or with a series that never steps, completes. The kernel's FxRoll point is the generic mechanism that case can re-lower onto. (2) default_sizing_intent (pine_adapter.cpp before R5 lane F7) returns nullopt whenever any FX series is staged — even one point that restates the scalar rate — so an FX run silently loses the kernel Sized path R2b landed (measured: 1 kernel Sized request without a series, 0 with a one-point 1.0 series, same 50-unit book). Closed by R5 lane F7. (1) The throw is gone: the carried roll takes TradingView's broker-open checkpoint at any positive margin — its money at the open under the new rate and the side's own maintenance ratio, its lot-floored 4x restore, executed at the open before the step bar's script — and a survivor that is not a 1x long is re-checked over the rest of the bar by the kernel's liquidation. A 2x long or short of 15 at 100 on 1000 across 1 → 1.5 takes 6.666666666666667 at the open and keeps 8.33; at a 25 % side ratio it takes nothing; a constant curve never rolls (tests/test_affordability_fx.cpp F7 rows; G6's leveraged pin and its L0 oracle twin moved with an expectation corrected: note). The kernel's FxRoll point stays refused, measured (MG9). (2) Sized is withdrawn only where the kernel would convert at another rate than the source sized at — a curve step between the sub-bar open and the acceptance coordinate — so a one-point series lowers exactly as no series does, and on a stepped curve only the step's own bar keeps its host-resolved intent (tests/test_adapter_sizing_relower.cpp case 12). | — |
| U5 | Open-bar view default. Should OpenOnly be the default for hosts that are not LegacyTolerant? | F: safer for users, but a behaviour change for the ~20 existing native tests; default Complete proposed. |
| U6 | TA comparison band. Is TradingView's 1e-10 tie-breaking a feature of the TA library, or a default to make configurable? | F, O: rename only; a switch changes indicator outputs and needs its own lane. S: separate policies, hash the policy identity. |
These are the proposals the lanes started from, not the landed API, which docs/pages/native-engine.md documents (native_metrics(), for one, never landed). Common rules: opt-in by spec field or request kind; hash only when present; defaults reproduce the pre-lane behaviour (§3.1). Each block merges the inputs' proposals (F P1-P7, O G1-G13, S P1-P10) and names what was not adopted.
KernelRecorded: the consumer calls the existing update_equity_extremes() (engine.hpp:1552) + record_equity_point(script_open_ms) (engine.hpp:1570) once per script calculation (after invoke_callback native_execution_consumer.cpp:8586, :8586, :8586, :8586) and after the AfterCalculation close; appends the per-bar broker hash when recording is on; synthesizes the open-position row at run end / stream_end with the existing build_close_trade_with_costs (engine_orders.cpp:75).KernelRecordedAtHostMarks (R1, KERNEL-EDGE): the same recording, at the points a C++ host names with NativeStrategyHost::mark_native_report_point instead of once per calculation; the consumer never records on its own initiative under it, so the continuation identity stays where HostRecorded leaves it. The Pine adapter's existing mark_script_report_point route has the same kernel producer. It is what the adapter declares (KernelRecordedAtHostMarks pine_adapter.cpp:3039), with report_open_position_at_end == false.closed_trade_* accessors (RP6).owns_equity_recording() (the A48 ownership pattern of owns_lot_excursions native_host.hpp:1043). Same semantics, but it costs a host-epoch bump, so the spec form was chosen; the virtual was never added, and NativeReportPolicy is what landed (§3.4 row 3).native_execution_consumer.cpp (seal path, seal_script native_execution_consumer.cpp:9547), engine_run.cpp, engine_report.cpp, engine_state_hash.cpp, native_run_spec.{hpp,cpp}.units = cash / (price * point_value * fx), cash = the value or fraction * marked_equity(price), price = the candidate's default_resolved_price. It plugs in where HostSized units resolve today (native_execution_consumer.cpp:735; terms resolution resolve_execution_terms native_execution_consumer.cpp:6158). A host override of resolve_execution_terms keeps the last word. Non-representable → MatchRejectReason::TermsUnresolved (native_order.hpp:965); validation reuses RequestRejectReason::InvalidQuantityBasis (native_order.hpp:915).NetOfSiblings: a fraction-sized reduce claims the bound scope minus the units already claimed by live sibling reduces on it; keys are opaque, never source IDs (S).default_sizing + virtual resolve_native_sizing (duplicates the per-request intent, costs an epoch); S's PercentFreeMargin (needs L4's free-margin definition) and EveryCalculation timing; O's spec-level size_net_of_fees (per-request flag, 2 of 3); O's PercentOfScope basis (expressed as ScopeFraction); S's ReplaceByKey / Fifo modes (callsite semantics → adapter, R5-4).native_order.{hpp,cpp}, native_execution_consumer.cpp, native_run_spec.cpp, the definition hash.Reduce / Flatten with Stop{level} bound to the live openings — the shape the adapter builds host-side today (pine_adapter.cpp:20825). New hashed RequestDefinition::origin { Host, KernelLiquidation, KernelRisk }, new CancelReason::Superseded on re-price, MarginCallEvent joins CommandEvent (17 → 18, native_order.hpp:1396). O's alternative, a checkpoint on the excursion walk (apply_excursion native_execution_consumer.cpp:3562), is not chosen: a reduction mid-path is the entanglement O itself flags (E2).native_fx_curve drives the mark (native_fx_curve.cpp).native_run_spec.{hpp,cpp}, native_execution_consumer.cpp (admit_opening_inspect native_execution_consumer.cpp:3886 per side; scheduling next to invoke_bar_open_callback / drain_after_applied), native_order.*, engine_state_hash.cpp + the coverage checker, c_abi.cpp + pineforge.h (pf_native_run_spec_v2 or an extension struct; CLAUDE.md SOP).Callback chronology contract (the normative part of the lane; F P4.1 + O G8):
on_native_applied per applied event, FIFO (rule already at on_native_applied native-engine.md:1729-1730) → with BarCloseAndFills, one recalculation at the fill cursor (reason OrderFill), driven from the existing notification drain (native_execution_consumer.cpp:8371-8383) under its re-entrancy guard, bounded by max_recalculations_per_point. S would run it before the drain; not chosen, the drain order is frozen (E4).born_on_remaining_path native_execution_consumer.cpp:6857). born_on_remaining_path (native_execution_consumer.cpp:6857) is not touched — the adapter's COOF deferral (pending_coof_requests_, element type PendingCoofRequest pine_adapter.hpp:2207) is calibrated against it.EveryModeledPoint: a calculation at each magnifier sample / observed print, in batch as well as stream (every delivery loop asks the per-point guard recalculate_at_point native_execution_consumer.hpp:1385-1390, whose body is recalculate_at_modeled_point native_execution_consumer.cpp:8217). on_native_tick stays the observation hook (S).current_partial_bar() derives from the driver points already recorded (record_driver native_execution_consumer.cpp:3506); valid in bar-open, applied and tick callbacks.snapshot_coof_script_state pine_scheduler_native.cpp:99, restore_coof_script_state :117, commit_coof_script_state :134) (O).gaps_on / lower_tf_array request fields and strategy_native_security_* C symbols (C surface belongs to L13); O's and S's edits to engine_security.cpp (R5-6).native_run_spec.{hpp,cpp}, native_execution_consumer.{hpp,cpp}, engine.hpp (access only), native_calendar (pairing validation).WaitForApplied / BindOpening and a non-absolute anchor gets its level when the owner's fill arms it (ArmedEvent native_order.hpp:1334-1341 already exists) (O). Fallback with no kernel change: the toolkit materializes relative levels from the host's on_native_applied (F).WaitForApplied, each default byte-identical and folded only when set. NativeArmFirstMatch { AtArmPrint, AfterArmPrint }: whether the armed leg is a candidate at its owner's fill print or, like a request born in the owner's fill callback, only after it. NativeArmScope { OwnerLot, Book }: whether an armed closing leg closes the lot its owner opened or is bound at the arm to the whole position (a BookClose), under which it may be HostSized. Ruling: generic — both are broker models of a contingent child, neither names a source language — and they are what makes an anchored child the very request a host would submit from on_native_applied. The Pine adapter's relative strategy.exit legs use both; R4d's owner-lot spelling left a later same-id add open where the source leg closes the book (rel-pyramid-set-once, tests/test_adapter_brackets_relower.cpp).strategy_native_bracket_* and its TU list (engine_orders.cpp, engine_execution.cpp).native_order.{hpp,cpp} (arming), native_matching.hpp (checked_trail_stop), native_execution_consumer.cpp (prepare_owner_applied path, command surface).QuantizeFills: the kernel rounds resolved_price onto price_tick before slippage (HalfUp) or in the direction that favours the resting order (Directional). QuantizeFillsAndTriggers: the matcher additionally compares the quantized path against the raw level.native_run_spec.cpp (validation), native_execution_consumer.cpp (resolve path native_matching::apply_slippage native_execution_consumer.cpp:7579, execute_current path native_execution_consumer.cpp:7941), native_matching.hpp (threshold form). The dead helpers were the reference arithmetic and are deleted (§2.ii e); the kernel's own step is native_matching::apply_slippage native_matching.hpp:259.MatchRejectReason::RiskLimit for openings (O; F: RiskHalted), an optional kernel-issued flatten through A.3's kernel-originated request (origin = KernelRisk, F), and a NativeRiskEvent in native_events (native_host.hpp:1365) (O) or a callback on_native_risk_event (S — a virtual, so only inside an epoch batch). The day key uses the existing calendar (session_day_ordinal native_calendar.hpp:416), not a chart clock.directions / max_units / max_position_units are existing spec fields (MG1-MG3), not duplicated.native_marked_equity + on_native_applied, which is what the adapter does (update_risk_state pine_adapter.cpp:18095; on_applied pine_adapter.cpp:25046; the opening_margin_checkpoint block pine_adapter.cpp:17927); a toolkit helper is an acceptable stand-in until L9.native_run_spec.{hpp,cpp}, native_execution_consumer.cpp (admit_opening_inspect native_execution_consumer.cpp:3886 and the per-point walk), native_order.hpp, the state hash.| Proposal | Src | Why not now |
|---|---|---|
NativeExcursionMode { PathExtremes, FillOnly, HostOwned } + per-lot sample events | S P8 | The kernel sampler + A48 ownership already cover RP1 (F, O). TradingView's tapes found gaps in the host-owned half, not in the kernel's (RP1, R5 lane H-MEASURE); a mode enum would not close them, and R5 lane H-THIN lowered the Pine host onto the sampler |
NativeFxEpoch + on_native_fx_epoch (stream-safe FX) | S P10 | No R5 lane; F rules it out of scope → U4 |
Generic FillModel interface | S §2.2, lane G | R5-3 + R5-1 (§2.ii n) |
TA ComparePolicy split | S §2.2 | not neutral (§2.ii b) → U6 |
Kernel bracket object + close-reservation modes ReplaceByKey / Fifo | S P1, P2 | R5-2, R5-4 |