Repository navigation
R5 wave D: fifteen follow-up engine lanes E1–E15 integrated (INT13) - #273
Merged
Merged
Conversation
…th a placeholder (R5 follow-up lane E1, item 1)
`Trail{offset, std::nullopt}` under a `FromOwnerFill` anchor was refused with
`RequestRejectReason::InvalidTrigger`, so the anchored trail was the one
trigger kind whose placeholder had to be typed out: `Limit::price` and
`Stop::price` default to the `0.0` the anchored form wants, while
`std::optional<double>`'s own default is `nullopt`. Lane L14-B hit exactly
that assembling the six-feature host from the page alone, and the leg was
dropped while the rest of the strategy ran.
The ruling is that an absent arm is the anchored spelling, not an error, and
the tree already says so twice:
1. `install_anchored_level()` assigns `trail->arm_price = level`
unconditionally at the arm. It never reads the prior value, so the
placeholder is write-only ceremony.
2. An anchored request must be owned by `WaitForApplied`
(`InvalidOwner` otherwise), so its authority is `Wait` until the arm.
`eligibility_facts()` sets `facts.waiting` from that and the consumer's
candidate scan skips a waiting request outright, so the pre-arm
`arm_price` is unreachable by the matcher under either visibility.
So `validate_levels` now accepts an anchored trail with no `arm_price`. A
WRITTEN non-placeholder level stays refused for the reason a written
`Limit`/`Stop` price under an anchor is refused -- the arm would silently
overwrite it -- and `0.0` stays legal so every host already spelling it keeps
its meaning. No leg kind has an arm the anchor cannot resolve, so the refusal
that remains is a contradiction rather than an omission and needs no new
typed `SubmitError`: the C surface is untouched.
This is a generic capability, not a Pine one. The kernel learns that a
deferred field's absence is its own spelling; nothing here knows what a
source language calls a trailing exit.
The witness runs both spellings side by side, in batch and on a stream, and
pins that they arm at the same level and produce the same applied events and
the same trade. It also pins what does NOT converge: `hash_trigger()` folds
`arm_price.has_value()` and `continuation_hash()` folds the command history,
whose acceptance row keeps the definition as submitted -- so the spelling is
a durable identity choice with no behavioural consequence. That is why the
Pine adapter's placeholder (`relative_leg_shapes`) stays exactly where it is:
swapping it would move the continuation identity of every corpus run that
anchors a trail and buy nothing.
Floors: kernel 189 -> 190, release 554 -> 555 (one kernel-only TU).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit a04d31a15200a0609bdf18ffc8f83708d5a7e60b)
…ens to take (R5 follow-up lane E1, item 2) native-engine.md said the anchored field "carries the placeholder 0.0 until then", which reads as a value to write and is what sent lane L14-B's assembled host into an InvalidTrigger. The rule is the opposite: you do not write the field at all, and each kind spells absence its own way -- the 0.0 `Limit::price` and `Stop::price` already default to, and either an absent `std::optional` or that same 0.0 for a `Trail`'s `arm_price`. Writing any other level is the error, because the arm overwrites it. pine-to-native.md carries the same correction inside its existing bracket row only, since lane L14-B is rewriting that page in parallel and the integrator has to be able to carry this as one row. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit d0fec0adc5c3ce78e3ae4dc1c9de27e29fed3b23)
…(R5 gap lane E2)
A host that leaves `report_policy` at its `HostRecorded` default read
`max_drawdown_`, `max_runup_` and `max_contracts_held_all_/long_/short_`
back as zero for the whole run. Nothing was wrong with the fold: it was
simply never reached. `update_equity_extremes()` had exactly one caller,
`record_report_point`, and both ways into that helper were gated on a
report policy — `record_script_report_point` on `KernelRecorded` and
`mark_script_report_point` on `KernelRecordedAtHostMarks` — so the
kernel's own per-calculation folding happened only for a host that had
also asked the kernel to record its equity curve. Lane L14-B found it
from the outside, running one tape under both policies while writing the
open-lots example.
Ruling. Drawdown, run-up and the position peak are report truth of the
RUN: what it drew down, what it ran up, the most it ever held. They are
not a property of whoever owns the equity series, so the kernel folds
them whatever the report policy says. Recording the CURVE is the part
that stays policy-scoped — a `HostRecorded` host owns its series and
still gets no point from the kernel.
Where the fold happens is the policy's mark cadence, and there are only
two. When the kernel owns the cadence, the fold is at every script
calculation, which is what `HostRecorded` and `KernelRecorded` now
share; `KernelRecorded` additionally appends the point and, behind its
own switch, the broker-state hash row. `KernelRecordedAtHostMarks` is
the one policy that has handed the cadence to the host, and its fold
travels with it (`mark_script_report_point`), untouched: folding at the
kernel's calculation as well would count an instant the marking host
never reports — a different run truth, not a truer one. The Pine
adapter declares that policy, so not one of its numbers moves, by
construction rather than by tolerance.
One consequence is deliberate. The extremes are folded into the
execution continuation (`engine_state_hash.cpp`), so a `HostRecorded`
run's `native_continuation_hash()` / `broker_state_hash()` now moves
with them instead of folding three frozen zeros. A resume identity that
could not tell a run at its peak from a run at its trough was hiding
state; it no longer does.
Witness (`tests/test_native_report_truth.cpp`, kernel-only): one tape
through both policies, the extremes sampled at the top of every
calculation and read again at the end. They agree at every calculation
and at the end, they equal an independent walk of the curve the
`KernelRecorded` half recorded — which is what keeps
`compute_equity_stats` ("MUST mirror update_equity_extremes") one
statement under both — and the `HostRecorded` half still reports
`equity_curve_len == 0`. Before the change the `HostRecorded` column was
all zeros and the run diverged at calculation 12.
`scripts/check_broker_state_hash_coverage.py` pins the recorder's rows.
expectation corrected:
`if (!spec || spec->report_policy != NativeReportPolicy::KernelRecorded) return;`
-> `if (!spec) return;` plus a separate
`if (spec->report_policy != NativeReportPolicy::KernelRecorded) return;`
below the now policy-independent `engine.update_equity_extremes();`,
because one combined guard cannot express two scopes. The fold itself is
added as a pinned row, so re-gating it fails that gate closed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 9b5a8f127c4f077d56acfceefa35a9a37ffab50c)
docs/pages/native-engine.md's "Reporting for native hosts" said the curve is host-owned by default and left the reader to conclude that everything downstream of it is too. Two things hang off that default and they do not share a scope: the `pf_equity_stats_t` figures are a walk over the recorded curve and do degenerate without one, while `max_drawdown_`, `max_runup_` and `max_contracts_held_all_/_long_/_short_` are scalars the kernel folds at every script calculation under every policy. The section now names the split and says where each is read back; the `KernelRecorded` paragraph describes what it adds (the point) rather than re-claiming the fold, and points at the mirror the two halves share. docs/pages/pine-to-native.md gains the two rows a migrating script looks for — `strategy.max_drawdown` / `strategy.max_runup` and `strategy.max_contracts_held_*` — with the same distinction: the scalars need no report policy, the `pf_equity_stats_t` pair needs a curve. Lane L14-B is rewriting this page in parallel and states the pre-E2 rule (zero under `HostRecorded`); at integration its sentence is the one to drop. The engine.hpp NOTE above `fold_equity_extreme` is rewritten in place at its original eight lines, so the ~40 `engine.hpp:NNNN` citations that docs/design, docs/adr and docs/pine-adapter-kernel-notes.md carry past it stay valid. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 759c6e66444d3c9d0b70a643c5f43596722c5b48)
…up lane E3, item 1)
Ruling: the account-currency rate one margin check point converts at is the
rate the run's declared curve has in force at THAT POINT'S cursor — the
curve's own step semantics, `account_currency_fx_at(cursor.point
.effective_time_ms)` — for every check kind (BarOpen, AfterApplied,
Calculation, FxRoll) and in batch and stream alike. This is generic: it names
a declared FX curve and a check point, no script language and no broker.
The margin model was the last thing in the kernel still converting at the
PRESENTED bar clock. That clock is a different instant whenever the walk has
moved past the point being checked: an applied fill drained at its script
bar's calculation is presented that bar's close coordinate
(`invoke_applied_callback` sets `max(cursor, decision_floor())`) while its
cursor still stands at the opening print it filled on. A curve step landing
between the two made the entry fill itself breach — lane P4 hit exactly that
writing the C fx-roll scenario ("required 1250 at cursor time 300000, where
the curve's own step was at 600000") and moved its step one bar later to dodge
it. Every OTHER cursor-converting site in the kernel already threads the rate
from the coordinate: a `Sized` request freezes its units at
`account_currency_fx_at(point->decision.coordinate.effective_time_ms)`, an
execution term records `active_fx` at `cursor.point.effective_time_ms`, and
`consume_matched_request` states the rule outright ("All resolver, inspection,
admission and financial reads see the execution coordinate's FX activation
instant"). The margin model now joins it.
Mechanism — the rate is threaded, never the clock. `margin_check_fx(engine,
cursor)` answers one point's rate; `margin_call_units` takes it for the
requirement and hands it to `margin_equity`, `maintain_margin_liquidation`
solves the level with it, and the margin-call receipt states the equity,
requirement and level of the book it left behind at the rate where the fill
that booked it landed. `marked_equity_at` is `BacktestEngine::marked_equity`
term for term at an explicit rate, because both terms of the test are
FX-bearing: reading the later clock invented equity as well as requirement.
No host callback sees a clock it did not see before, and no new durable state
exists, so no continuation identity and no epoch decision move.
N6's local clock swap inside `fx_roll_margin_check` is removed: with the
generic rule in place the roll's own instant IS the instant it is measured at,
so the swap was exactly redundant — `test_native_margin_fx_roll` (844 checks)
and `test_native_fx_activation` (180 checks) are unchanged and green.
N6's FxRoll DEFINITION is kept verbatim — the first driver point whose rate is
not its predecessor's in the driver log — because it was already stated in
cursor time; it is now the same rule the checks it precedes are taken under,
instead of a special case beside them.
`native_liquidation_price()` keeps the presented clock: it is a host query
with no cursor of its own, which is what "where is my level now" means.
Adapter: `src/source` declares no kernel FX curve of its own, but generated
Pine calls `strategy_set_account_currency_fx_series`, which stages one
(`stage_account_currency_fx_series`), and the adapter declares a
maintenance-only kernel margin model whenever the margin call is enabled — so
the two do meet. Its outcomes still cannot move: its model is
`PathAdverseExtremeMark`, which never solves `liquidation_level`;
`resolve_margin_requirement` replaces both of the kernel's numbers with
`source_margin_money(view.mark, view.cursor.point.open_ms)`, already keyed on
the cursor; and `resolve_margin_call_units` always answers, so the kernel's
restore is discarded. The only kernel number left is `unit_margin`, read as a
finite-positive gate whose truth a validated positive rate cannot change.
Witness `tests/test_native_margin_fx_clock.cpp` (607 checks), kernel-only —
P4's geometry with the step AT the entry bar's presented clock, batch and
stream, both terms of the test, every kind on its own cursor, the receipt, and
a no-curve run left untouched. Fail-before on this tree: 10 failures, the
applied check measuring `required 1875` where the cursor's rate owes 750 and
calling a margin at T+1m that the account never owed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 1e64e19c77039ed5152fc6a9ac544aeae4302e63)
…ed it (R5 follow-up lane E3, item 2) Lane P4 wrote `FX_ROLL_STEP_MS 900000` and said so in its report: its first version put the step at 600000 and watched the ENTRY fill breach at `required 1250` with its cursor at 300000, so it moved the step one bar later to get the scenario's own acceptance geometry back. That was a kernel defect being dodged by a test literal, not a geometry choice. expectation corrected: FX_ROLL_STEP_MS 900000 -> 600000, because 600000 is bar 1's close coordinate — the clock the engine presents while the entry fill's applied check is drained — and with item 1 in place the margin model converts at that check's own cursor (300000, rate 1.0, required 500 against equity 1000) instead. The entry is quiet, and the roll at 600000 is what breaches, which is what the scenario's hand arithmetic describes. Every existing C row is unchanged and still passes: one FxRoll offered, its cursor at the step, `equity 1000 / required 1250`, one margin call of 2 units, one closed row under the declared ticket at entry price 100. One row is added — `call_time_ms`, read from `pf_native_event_v1::effective_time_ms` — to pin WHICH point called, which is the whole difference the two geometries expose. Fail-before with the kernel at 0a47cbf and this file as committed: 3 failures, "the roll named another point (got 0, want 1)", "the roll's two numbers are not the new rate at the unchanged price (got 0, want 1)" and "the margin call was not made at the roll's own instant". No C surface change: no symbol, struct, enumerator or layout moves. `scripts/check_native_c_api_surface.py` and `scripts/check_c_abi_runtime.py` both exit 0, and `test_native_c_api_frozen_header` still builds and passes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 312947cce3762938caf483d1d97ce9a38cac3547)
…efusal is not about it (R5 follow-up lane E3, item 3) `native-engine.md`, "Margin and liquidation": the `equity(P)` / `required(P)` block named an `fx` and never said which one. It now says: the rate of the check point being taken, `account_currency_fx_at(cursor.point .effective_time_ms)`, on the declared curve's own step semantics, for both terms of the comparison and for the level, at every check kind, in batch and stream. The presented bar clock is named as what it is — a later instant whenever the walk has moved past the point — with the applied-fill case that exposes it, and `native_liquidation_price()` is stated as the one query that answers at the presented instant because it has no cursor of its own. The FX-roll paragraph gains the sentence that makes N6's detection and E3's conversion one rule rather than two: a point's rate is read at its own effective time, so "the rate the next match will use differs from the one the last point used" is already stated in the cursor time the check is taken in. "Stream FX" — the tick refusal's reason is corrected in substance, not loosened. E3 asked whether that refusal existed only because of the stale clock; it does not. The clock it names is `active_account_currency_fx()`, which is what every SCRIPT-VISIBLE conversion reads — `open_trade_profit()`, `marked_equity()`, the equity curve — and the observation hook runs before its print has moved it. Re-measured on this tree with the refusal temporarily lifted: a step at T+90s left the T+100s and T+110s prints converting at the pre-step rate, one whole slot behind. The margin model is now correct on that route; the money the script reads there still is not. The refusal stays, and `tests/test_native_margin_fx_clock.cpp` pins both refusal messages. `native-feature-parity.md`: MG9 records E3 completing the FX margin row's conversion instant, FP6 records the tick refusal as re-examined and kept with the reason it actually guards. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 5bc703d7bb5b1f470739ef42c3f6c0487442b686)
… own accessor (R5 follow-up lane E3, item 4) `NativeExecutionConsumer::marked_equity_at` restates `BacktestEngine::marked_equity` (engine_execution.cpp) term for term at an explicit rate, and item 1's comment claims the two are pinned together. They were not: nothing in the witness compared them. They are now. `ClockHost::resolve_margin_requirement` records `BacktestEngine::marked_equity(view.mark)` — the accessor, converting at the engine's presented clock — and that clock, beside every measured view. Two rows read it: - a run with no declared curve, where the two clocks are the same rate at every instant: every view's equity must be the accessor's number bit for bit (`==`, not a band); - every curve run in "every kind converts at its own cursor": the same identity wherever the presented clock's rate happens to be the cursor's, which is where the rule is a no-op by construction. Mutation check that the pin bites: multiplying the unrealized term in `marked_equity_at` by `1.0000001` — a 1e-7 relative drift, far below any tolerance a `near()` band would catch — fails 11 checks, all of them `host.views[i].equity == host.accessor_equity[i]`. Reverted; the witness is 628 checks, 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit a70b6a2af84c737eee57290d94858833d4caae92)
…, item 1)
Lane L14-C read the next non-comment line after every TradingView-shaped
comment in the kernel's compile closure and found four spans with no code
inside them at all: 312 lines describing functions the R5 lowerings deleted.
A documentation lane may not touch src/, so the text stayed. It is deleted
here, and each span is replaced by one sentence naming the live generic
mechanism and the ADR row.
Verified on this tree first: the three files are byte-identical to r5/L14C's,
so its line numbers apply verbatim. Each span re-read; each named "next
code" confirmed.
src/engine_orders.cpp:200-209 sibling reduction on a group fill
-> native_order::GroupEffect (Cancel / Reduce), resolved by the
execution consumer. Next code was build_close_trade_with_costs.
src/engine_orders.cpp:530-692 the whole "execute_market_entry case
helpers" section -- deferred-flip growth, the opening margin guard,
same-tick sequential fill. Next code was `} // namespace pineforge`.
-> a frozen pending-row field name; NativeMarginModel::initial_long /
initial_short; the consumer's acceptance and incarnation ordinals.
src/engine_run.cpp:148-167 the chart-body warmup scope and the
per-bar dispatch order. Next code was reset_run_state().
-> the ambient ta::EmaSeeding default the source layer raises;
NativeCloseExecution (AfterCalculation / NextEligiblePoint).
include/pineforge/engine.hpp:918-952 ten-significant-digit money
-> the source adapter's own arithmetic; the kernel's money is exact.
include/pineforge/engine.hpp:1215-1379 the sizing freeze, the fill-recalc
sizing moment, the post-liquidation re-freeze, the close-time position
freeze. Next code was net_profit().
-> SizeTime + SizePrice; NativeLiquidationCheck; NativeCloseExecution.
Two boundaries were taken wider than L14-C's table cites, both inside the
same dead region and for the same reason -- what is left would label nothing:
engine_orders.cpp from :530 (the section banner and the first helper's
comment) rather than :556, and engine.hpp from :1215 (the on-tick sizing
census the freeze cites) rather than :1262. Kept, because they document live
declarations: the "Shared close-side" banner and build_close_trade_with_costs'
own doc, round_to_mintick's and bar_fill_price's census comments, the
"Strategy variable accessors" banner, reset_run_state's doc.
Ruling: the kernel documents the code it has. A pointer to the generic
mechanism is documentation; a pinned narrative about a deleted function is a
false statement about this tree, and rule 5's "legacy to extract" is, for
these spans, text and nothing else.
Comment-only, proved by construction: stripping every comment from each file
at both revisions and dropping blank lines leaves the code byte-identical
(3 files, sha256 equal), and `git diff -U0 -- include src` has 0 non-comment
lines.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit a191fda9aa3881e3d2f2351510f1eb9232c2b47a)
…l derives the mask (R5 lane E6, item 2)
`PyramidEntry::skip_entry_bar_high` / `_low` were ADR-0001 rule 5's last live
pair of kernel state written by the source adapter: `pine_strategy_host.cpp`
reached into the kernel's own lots and set two booleans, having first computed
them from two kernel internals it had included for that purpose
(`internal::first_touch_position`, `internal::bar_path_uses_high_first`).
The split this lane makes is the one rule 2 asks for. What the adapter knows
is not generic — whether ITS order was a priced entry, and therefore whether
the fill sits inside the bar or at its closing point — and it keeps that.
What the kernel knows is the geometry: which intrabar leg the modeled path
walks first and where a price is first touched on it. So the owner of the
lot's excursion (RULING A48, `owns_lot_excursions`) now DECLARES the fill
point and the kernel DERIVES the flags:
enum class OpenedLotFillPoint { OnPath, AfterPath }; // engine.hpp
void BacktestEngine::declare_opened_lot_entry_bar_mask(
uint64_t entry_incarnation, const Bar&, OpenedLotFillPoint);
It is the entry-side half of the capability whose read side already crossed
the boundary generically (`ClosedLotExcursionFacts::entry_bar_high_masked`),
and it names no platform: any host that samples its own lots says where its
fill sat, and a host that forces the leg order still does it once, through
`NativeRunSpec::path_order`. The adapter's two writer sites are now two calls;
no host writes a lot's flags.
Ruling: generic mechanism (rule 2a — a second venue is driven through this
surface by answering OnPath / AfterPath), and no knob (rule 2b — the geometry
is a decision the run has already made, in the matcher and in the closing
row). Storage is unchanged BY DESIGN, and that is the residual: the two
booleans are durable, hashed lot state (`src/engine_state_hash.cpp:65`) inside
a POD the settlement ABI freezes, so removing them is an epoch decision and
this lane does not take one. What is removable at the next epoch, and how, is
recorded in ADR-0001.
Witness, kernel-only and source-free (runs in the kernel profile):
tests/test_e6_entry_bar_mask_declaration.cpp — 7 runs over two entry-bar
geometries. The same declaration masks the HIGH on a high-first bar and the
LOW on a low-first one, because the geometry is the bar's; AfterPath masks
both; declaring another incarnation's mask leaves this lot clear; and the
mask drives the skip, the owner's magnitudes and the closed row's differing
by exactly the masked end (mfe 12 -> 0, mae 12 -> 0).
Fail-before against this branch's header closure at a191fda9, verbatim:
tests/test_e6_entry_bar_mask_declaration.cpp:106:47: error: use of
undeclared identifier 'OpenedLotFillPoint'
After: `test_e6_entry_bar_mask_declaration: 161 passed, 0 failed`.
Byte-identical by construction: the derivation is the adapter's former helper
moved unchanged (same predicate, same per-lot fill price, same "a fill the
path never reaches leaves the lot alone" arm), called at the same two sites in
the same order.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 4e048ee3bbbbb8b3c5d6fea8dfc20606433f463e)
…ane E6, item 3)
ADR-0001 on this branch predates lane L14-C's rewrite, so this is the smallest
edit that keeps it true, and it is exactly the two rows the lane moved.
1. Rule 5 ("Boundary rules") now records what it still covers. The comment
residue it covered is deleted -- the four spans L14-C tabulated, cited at
0a47cbf so the reader can find them in history -- and the last kernel
state a host wrote, PyramidEntry::skip_entry_bar_high / _low, has no writer
outside the kernel. What rule 5 still covers is the STORAGE of that pair.
2. A second row in "Kernel state the adapter sets -- the mechanism rulings",
beside ta::ema_na_warmup_flag: the mask pair, its new declaration seam, its
rule-2 argument on both halves, its two pinning suites, and the exact
mechanism that removes it at the next epoch (the owner keys its own tables
by entry_incarnation already, and the kernel's own fallback reader behind
`preceding_exit_path_prefix` has no writer anywhere in the tree, so it is
unreachable in every run).
The row adds four identifiers to the residual gate's first-column vocabulary
(`skip_entry_bar_high`, `skip_entry_bar_low`, `PyramidEntry`,
`ClosedLotExcursionFacts`); none matches a residual pattern, so the 106
vocabulary-matching identifiers the gate enforces are unchanged.
For the integrator carrying this onto r5/L14C, which rewrites the same file:
its section A table (`:468-490`) becomes "deleted by lane E6" and its
consequence bullet (`:595-598`) loses the "312 lines" claim; its section B row
for the pair (`:510`) and its "Current state vs. target" bullet (`:274-277`)
take the new writer. Both of this lane's edits are the same two facts.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit bb64f2ae54e6e0ab667bbba4bd299988e65c0f10)
tests/test_e6_entry_bar_mask_declaration.cpp registers in both gated profiles: it is source-free, so the kernel profile builds it too. expectation corrected: KERNEL_MIN_TESTS 189 -> 190, because the kernel profile now registers 191 rows and runs 190 beside the same skipped test_native_live_websocket. expectation corrected: RELEASE_MIN_TESTS 554 -> 555, because release registers 555 rows and, with --require-receipts, runs all of them. An integrator merging this lane beside another that also adds rows adds both deltas: E6 contributes exactly +1 to each floor. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit b2dda9a6e29316412f3a69c3fb184e74e81eb9e3) # Conflicts: # scripts/ci_verify.py
…nes do not Lane E6 measured 524 detached comment lines left in the kernel's compile closure and reported the number without a tool. A number no one can re-derive is not a measurement, so this is the measurement: within one gap between two code lines the last comment block is attached to the code below it and every earlier block in that gap is detached -- it is separated from the next declaration by a blank line and another block, so nothing in the file says what it is about. One class is excluded, because its position is not a claim about the code under it: a DIRECTIVE block, whose text a tool consumes. A Doxygen group command (@defgroup / @addtogroup / @name and the @{ @} that open and close a group) brackets a run of declarations, so the closer necessarily follows the last of them; the @broker-state / @source-state markers that scripts/check_broker_state_hash_coverage.py reads must sit exactly around the members they enclose. Moving either breaks the tool that reads it, so counting them as residue would make the census ask for a defect. The scope is the kernel's own compile closure, read out of CMakeLists.txt (PINEFORGE_KERNEL_SOURCES) and followed through #include: the kernel is the half that has to stand alone, and the source adapter is judged elsewhere. The classifier is structural, not lexical-by-regex: it tracks block comments, string and character literals and raw strings, so "http://" and R"(/* x */)" are code, and a line carrying any code is code even when it also carries a comment. --self-test pins that behaviour on eighteen fixtures, including the two that a regex gets wrong. It judges position only, never truth. Deciding what a detached block should become -- deleted, moved onto its declaration, or kept -- is reading work. Ruling: generic. The script names no Pine or TradingView concept; it measures C++ comment position in whatever files it is given. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 92f24d61c6b8fe7cefde80e335e16dffd31d37bb)
…rong line is moved (R5 lane E10, item 1) The census this lane commits reads 539 detached comment lines in the kernel's compile closure after lane E6 (engine.hpp 325, engine_orders.cpp 100, engine_run.cpp 34, timeframe.hpp 27, pineforge.h 17, plus ten files under ten lines). After this commit it reads 0. Every block was read; each of the three verdicts was earned. DELETED -- a narrative about a mechanism this tree no longer has. Every name below survives only inside the comment that described it: the input.source resolver, the one-priced-entry-per-bar latch, the entry-request tombstones and the cancelled-incarnation map, both request-matching scratchpads, the dual-entry arbitration pair (dual_entry_path_, last_bar_dual_entry_decision_), the trail-restart bar and its pre-bar extreme, the whole forced-liquidation / margin-call method section (engine.hpp:806-917), the intraday-loss rule, the five market-entry case helpers, the market exit, the three partial-exit helpers, the range-end accounting essay, the exposure-activation note, the run() helpers, the per-script-bar dispatch sequence, run_tf_impl, and the hundred-odd lines of engine.hpp that restated the C surface's own documentation of the live-runtime observers. Where a reader of the file would otherwise be left without a next step, the deletion leaves one sentence naming the live generic mechanism -- in the file's own banner, which is where a file-scope statement belongs and where nothing can orphan it again. MOVED -- still true, on the wrong line. Section banners separated from the declaration they label by a blank line and another block are fused with it (timeframe.hpp, session_time.hpp/.cpp, native_matching.hpp, native_c_api.h, ta.hpp, ta_misc.cpp, ta_moving_averages.cpp, ta_extremes_volume.cpp, c_abi.cpp, pineforge.h). native_calendar.hpp's component note moves onto the namespace it describes. pineforge.h's "STRATEGY .SO EXPORTS" banner moves onto the #ifndef PINEFORGE_NO_STRATEGY_DECLS block it actually introduces, from above the runtime export that had drifted in front of it. E6's own pointer sentences move from the vacuum they sat in onto the thing they bound: the money sentence onto round_to_mintick, the three class-scope ones onto the accessor section, the two file-scope ones into engine_run.cpp's banner. The live-runtime observers get one generic doc: the kernel answers "not available", the host that owns the book overrides, the C header says what each value means. KEPT and CORRECTED -- documents live code, named something dead. Three citations created by the same lowerings: range_end_trades_ cited a deleted engine_orders.cpp function (the rows are the execution consumer's, under NativeRunSpec::report_open_position_at_end); the session predicates cited a setter that is the adapter's; the observers' behaviour is the adapter's, so the kernel states the seam and not the behaviour. Two comment classes are NOT residue and were not touched: Doxygen group commands and the @broker-state region markers, whose position belongs to the tool that reads them. The census excludes them for the same reason. Ruling: generic, and comment-only. The kernel now documents the code it has, in the vocabulary of the mechanism it has -- no TradingView tape, probe or family letter explains a kernel declaration that no longer exists. Proof, per lane E6's method: for each of the fifteen files the blank-normalized comment-stripped bytes are unchanged, so the preprocessor sees the identical translation unit. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 8a5273a9a10c69b5dee1dd4279aeedffda9381f7)
`execution::PhysicalExecutionContext::preceding_exit_path_prefix` was read in
exactly one place -- the non-hook excursion fold of
BacktestEngine::build_close_trade_with_costs -- and written nowhere: not in
src/, tests/, examples/, runner/, tools/, benchmarks/, tutorial/ or the source
adapter. A std::optional<bool> nobody assigns is empty at every call, so the
twenty-one lines behind it never ran in any run this engine has performed.
Lane E6 measured that and recorded it as a finding; this deletes the member
and its reader.
Deletable by the boundary's own test. PhysicalExecutionContext is a plain
stack-bound settlement coordinate in the unversioned pineforge::execution
namespace: no field of it is hashed (engine_state_hash.cpp names none of it),
it is in no frozen layout the settlement or aggregate ABI checkers pin (it
appears in neither check_native_cpp_versions.py's FILES nor either ABI
pairing's caller sources, whose own compiles each see one epoch's headers),
and it is nowhere on the C surface (pineforge.h, native_c_api.h, c_abi.cpp).
Nothing durable, frozen or C-visible changes, so nothing here asks for an
epoch decision. The sibling carry preceding_exit_trail_peak, which does have
writers, is untouched.
The witness, tests/test_e10_dead_path_prefix.cpp (kernel-only):
* the type witness is the proof of unreachability -- after this commit no
translation unit CAN set a path prefix, so a writer anywhere would have
failed to compile, and the whole-tree build is that check. Fail-before
against the header closure at 8a5273a9:
tests/test_e10_dead_path_prefix.cpp:70:15: error: static assertion
failed ...: PhysicalExecutionContext still carries the dead
preceding_exit_path_prefix member
* the behaviour witness answers why it could sit unwritten. The fold
existed for the extreme a priced exit's mid-bar fill leaves behind, on
the theory that per-bar sampling never sees it. It does see it: on a
high-first bar whose high is 8.00 above the lot, a stop exit's closed row
reports favorable 16.00 on two contracts whether the lot opened on an
earlier bar or on the exit's own bar. The branch covered nothing.
* the sibling witness keeps the trail carry's fold pinned beside it.
Six test files spelled the empty prefix as a fourth aggregate initializer and
now spell three fields. No expectation moves: every one of them was `{}`.
Ruling: generic and behaviour-neutral by construction. The corpus run proves
the outcomes byte-identical; the type witness proves the branch could never
have contributed to them.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 3a1bbb67d767366d8c614f6bc2abe67ab56fd5c3)
Row 1 -- "Current state vs. target", the ten-significant-digit money rule (line 206 at 3a1bbb67). It cited `engine.hpp:73-180`, which is and was live code: ClosedLotExcursionFacts, PyramidEntry and Trade with their own docs. The money residue was `engine.hpp:918-952`, 35 lines between round_to_mintick and bar_fill_price, and lane E6 deleted it. Lane L14-C's residue table carries the same wrong span in its row 8 and in the same bullet on the docs branch (r5/L14C and r5/int12-docs, `docs/adr/0001-kernel-adapter-boundary.md:265` and the table row at `:483` / `:525`, plus `docs/pine-adapter-kernel-notes.md:16`); this repository's copy is corrected here and the integrator re-applies the same correction to the rewritten table. Row 2 -- the entry-bar mask pair, in "Kernel state the adapter sets". Lane E6 wrote that the kernel's own fallback reader of the pair has no writer and is therefore unreachable in every run. That is still why the pair needs no kernel replacement at the next epoch, but the fallback itself is now deleted (item 2 of this lane), so the row says so and names the witness. Neither row changes a ruling: the pair is still retained, still hashed, still removable only at an epoch. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit af49badeea39d8bde03780e4254dbb6630640501)
tests/test_e10_dead_path_prefix.cpp is source-free -- it binds
pineforge/native_host.hpp and nothing under pineforge/source -- so it
registers in the kernel profile as well as in release.
expectation corrected: KERNEL_MIN_TESTS 190 -> 191, because 192 rows now
register in the kernel profile and 191 run beside the same skipped
test_native_live_websocket.
expectation corrected: RELEASE_MIN_TESTS 555 -> 556, because the same row
registers in release, where nothing skips.
Both floors were exactly tight at this lane's base (E6's tip), so an
integrator merging this lane beside another row-adding lane adds both deltas.
Also here, because the full build is what found it: the SEVENTH aggregate
initializer of a PhysicalExecutionContext that spelled the empty path prefix,
tests/test_source_layer_native_defaults.cpp:34, whose multi-line form the
item-2 sweep missed. Item 2's message says six files; it is seven. No
expectation moves -- the dropped initializer was `{}` there as everywhere --
and the miss is exactly the compile-time proof that commit relies on doing
its job: a translation unit that still named the member failed to build.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit fcfb89a2ed6cc22a44fd32865ba789815e278b66)
Item 1's new doc for the live-runtime observation seam listed the C spellings a reader should follow, and one of them -- `strategy_probe_fill_qty` -- is not a symbol this project has. The C surface spells it `strategy_pending_order_fill_qty` (include/pineforge/pineforge.h:1015), beside the five names the same sentence gets right. Found by reading pineforge.h rather than by trusting the C++ member name the deleted residue had used. A pointer sentence whose symbol does not exist is the same defect the lane deletes, one indirection further out. Comment-only: the blank-normalized comment-stripped bytes of engine.hpp are unchanged by this commit. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 704c0ca3d352b9c02661501dfd5b4e645f6f07e6)
…t (R5 lane E11, item 1)
Lane E6 cut the entry-side half of RULING A48 as a protected kernel seam,
BacktestEngine::declare_opened_lot_entry_bar_mask(entry_incarnation,
entry_bar, OpenedLotFillPoint), and recorded (exec/E6-final.md, finding 4)
that a C host could own its lots' excursions (on_lot_excursion) but had no
way to declare the mask its closing facts carry. This is that spelling:
typedef enum pf_native_opened_lot_fill_point_e {
PF_NATIVE_OPENED_LOT_FILL_POINT_ON_PATH = 0,
PF_NATIVE_OPENED_LOT_FILL_POINT_AFTER_PATH = 1
} pf_native_opened_lot_fill_point_t;
PF_API int strategy_native_declare_opened_lot_entry_bar_mask_v1(
pf_strategy_t, uint64_t entry_incarnation,
const pf_bar_t* entry_bar, uint32_t fill_point);
The typed word is pinned to OpenedLotFillPoint by static_assert and read
by an exhaustive switch (PF_NATIVE_E_TAG outside it); a NULL bar is
PF_NATIVE_E_ARGUMENT, a handle this API did not create PF_NATIVE_E_HANDLE.
Legality mirrors the one frame the seam is called from: the source adapter
declares from on_native_applied, where a host learns that a fill opened a
lot, and so does the E6 witness. CCallbackHost marks the frame of its C
on_applied call (AppliedFrame: saved and restored, so a nested applied
execution cannot close it, and restored on the CallbackFailure throw), and
in every other frame the call answers PF_NATIVE_E_STATE -- the refusal the
other C calls answer where they are illegal -- and changes nothing. The
mark is host-side transport state like the two snapshot caches: never
durable engine state, never hashed.
Ruling: generic mechanism, no knob. The C spelling carries exactly the C++
member's one fact, where the fill sat; the kernel keeps the geometry, and
nothing names a platform. No epoch: a symbol and an enum are added,
PF_NATIVE_API_VERSION stays 1 and no layout moves. The source adapter is
not touched, so the corpus is byte-identical by construction; this lane's
parity sweep measures it.
C API SOP:
- scripts/check_c_abi_runtime.py: EXPECTED_NATIVE_C_API 32 -> 33.
- scripts/check_native_c_api_surface.py: unchanged, and it must be. Its
census is the public surface of NativeStrategyHost; the seam is a
protected member of the base, so the guard neither requires nor admits
a row. A [C] row naming it fails as "COVERAGE rows naming members that
no longer exist: declare_opened_lot_entry_bar_mask" (measured on a copy
of the header). The COVERAGE block records it in prose beside the
pf_trade_t asymmetry, the one form the guard admits.
- SCOPE, the callback table's legality paragraph and the lot-excursion
facts' doc name the new call.
Witness: the entry-bar mask scenario of tests/test_native_c_api.c, pure C.
One run, two lots -- two units at the close of the high-first bar
(O100 H101 L90 C95) and of the low-first one (O100 H110 L99 C105) that
tests/test_e6_entry_bar_mask_declaration.cpp uses -- under four
declarations (nothing; OnPath then AfterPath; AfterPath then OnPath;
another incarnation), an owner whose magnitudes skip the masked end
exactly as the witness's owner does, and every refusal (before the run,
from on_bar, from the excursion hook, after the run, a NULL bar, tag 2, a
NULL handle). Its eight lots cover the witness's seven rows number for
number: lot A 12/10, 0/10, 0/0, 12/10; lot B 10/12, 0/0, 10/0, 10/12.
Fail-before against this branch's header at the E6 tip, verbatim:
tests/test_native_c_api.c:2170:19: error: call to undeclared function
'strategy_native_declare_opened_lot_entry_bar_mask_v1'; ISO C99 and
later do not support implicit function declarations
After: `test_native_c_api: ok`. Two mutations of a scratch copy of the
host: a spelling that answers OK without reaching the seam fails 12 rows
(both declaring cases); one that drops the frame rule fails 36 (all four).
Found, not changed (kernel scope): the seam derives the leg order from the
open-proximity rule even when the run forces NativeRunSpec::path_order --
the thread-local override is installed only while the native driver
samples a path, never around a callback -- so the C doc states the rule
the seam applies rather than the forced order.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit a3a75cd8f270e2d4b5a93f2b37c130fd800ab617)
…5 lane E11, item 2)
README's C ABI table gains exactly one row,
strategy_native_declare_opened_lot_entry_bar_mask_v1; nothing else in the
README moves.
docs/pages/native-engine.md, minimal:
- "Driving the kernel from C" counts 33 additive PF_API symbols, not 32
(scripts/check_c_abi_runtime.py pins the same 33).
- The excursion paragraph of the callback-table section gains one
paragraph: the owner of the excursions declares where each lot's
opening fill sat, from on_applied alone, with the typed fill point;
the kernel derives the two flags on_lot_excursion's facts carry back;
being a member of the base, the seam sits outside the COVERAGE census
and the block records it in prose; the C scenario reproduces the C++
witness number for number.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit b1d65c05a767a2f0b02de411fe8a312a64751433)
…es (R5 follow-up C-surface lane E7, item 1)
`pf_native_subscription_v1::lookahead` and `::gaps` were the one pair of
enum-valued words in <pineforge/native_c_api.h> with no `pf_native_*_e`: two
untyped `uint32_t` 0/1 words documented in barmerge terms (lane L14-C,
finding 2). They now hold a `pf_native_lookahead_e` and a `pf_native_gaps_e`
value, named after the two delivery rules `NativeTimeframeSubscription`
declares in native_run_spec.hpp:
PF_NATIVE_LOOKAHEAD_AT_COMPLETION = 0 the bucket rides on the input that
completes it (the default)
PF_NATIVE_LOOKAHEAD_AT_FIRST_INPUT = 1 its final values ride on its first
contributing input
PF_NATIVE_GAPS_HOLD = 0 the last delivered bucket stands
until the next delivery (the default)
PF_NATIVE_GAPS_CLEAR = 1 the series answers PF_NATIVE_ABSENT on
an input that delivers none of it
"Completion", not "last input": a LazyComplete bucket is closed by the next
period's first input, which is not one of its own contributing bars
(NativeTimeframeBarContext, native_host.hpp).
The fields stay `uint32_t`, documented `#pf_native_lookahead_e.` /
`#pf_native_gaps_e.` exactly as every other uint32_t enum word in the header
is, so nothing moves: sizeof 32, lookahead at 4, gaps at 28 -- a C probe
prints the same nine layout numbers against this header and against
0a47cbf's. No epoch: PF_NATIVE_API_VERSION stays 1, the enumerations are
additive and their values are the 0/1 the runtime already accepted. The
COVERAGE row of declare_timeframe_subscriptions names the two words;
check_native_c_api_surface (55 members, 47 spelled, 8 excluded) and
check_c_abi_runtime (no symbol added; neither script counts types) pass
unchanged.
Ruling: generic. The names are the kernel's delivery rules, not a source
language's; the header's three barmerge mentions are gone, and the docs
pages keep the Pine mapping.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit bb6f3e410d751d0aeffce4adc5ac36a9d432ae58)
…+ bools (R5 follow-up C-surface lane E7, item 2) translate_subscriptions read `lookahead` and `gaps` with `> 1u` range checks and `!= 0u` conversions: the one enum-valued pair in native_c_host.cpp that was not read by an exhaustive switch whose default refuses. Each word is now switched onto its enumeration with `default: return PF_NATIVE_E_TAG`, the refusal every other tag uses, at the same point of the row check (after the NULL `tf`, before the authoritative-bar arguments). Every row is therefore accepted or refused exactly as before, with the same status, on both entry points that share this translation (strategy_configure_native_ext_v1's subscription block and strategy_native_declare_subscriptions_v1). static_asserts pin the C++ side: both rules are `bool` in NativeTimeframeSubscription (a rule that grows a third value fails the build before it can reach a C host with no name), the enumerators are false and true in that order (a zero-filled row is the C++ default), both words stay `uint32_t`, and the row's layout is pinned member by member (lookahead 4, gaps 28, size 32 on LP64, pointer-width generic) so the typing can never move it. No kernel, adapter or epoch change: the kernel receives the same two bools for every accepted row. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 7a3323bc810c62a47798fa830be6403873e43ffa)
…ast each (R5 follow-up C-surface lane E7, item 3)
check_subscription_delivery_words() in tests/test_native_c_api.c declares
four rows of one "15" series over thirteen whole buckets of the twin's "5"
feed, one row per pair of values, through strategy_configure_native_ext_v1,
and reads each rule back from C alone:
- WHEN: every bucket of an AT_COMPLETION row rides on its third input, every
bucket of an AT_FIRST_INPUT row on its first (on_timeframe_bar's
delivered_at_ms), each carrying the whole bucket's OHLCV -- for
AT_FIRST_INPUT that is its final values two inputs early;
- WHAT: strategy_native_series_bar_v1 answers on every input once a HOLD
row has delivered, and only on the delivering input of a CLEAR row;
- 13 deliveries per row, 39 calculations.
One past the end of each enumeration is PF_NATIVE_E_TAG on both entry points
that take a row (configure and declare), leaving the handle Unconfigured,
and the same handle then configures and runs. The layout the typing must not
move is pinned from C too: each word one uint32_t, zero the default of both,
and on LP64 the offsets 4/8/16/24/28 and sizeof 32 measured on 0a47cbf.
Fail-before: against 0a47cbf's header the TU does not compile, for exactly
the missing spelling -- 15 diagnostics, all four names, the first
tests/test_native_c_api.c:3299:5: error: use of undeclared identifier
'PF_NATIVE_LOOKAHEAD_AT_COMPLETION'
The runtime rows test preserved behaviour: with the four names shimmed to
0/1 the same TU passes against 0a47cbf's library, and with either word's
two values swapped it fails (13/13 buckets off their input, 24-26 inputs
answering against the gaps word), so the rows tell the values apart.
No new CTest row: the checks run inside test_native_c_api, so neither
KERNEL_MIN_TESTS nor RELEASE_MIN_TESTS moves.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit d2225d8f7a4026945e6503746034af9411d333b7)
…them (R5 follow-up C-surface lane E7, item 4) native-engine.md's C-surface paragraph said a row carries `lookahead` and `gaps` "as 0/1 words" and that any value but 0 or 1 is PF_NATIVE_E_TAG; it now names the two enumerations and their values, keeps the barmerge mapping in parentheses, and says a value outside either enumeration is the tag error. The `_series_bar_v1` sentence names PF_NATIVE_GAPS_CLEAR instead of `gaps = 1`. pine-to-native.md's request.security row gains the C spelling of the two delivery rules. Minimal row text only: both pages are being rewritten by the docs lanes, and the integrator carries these edits. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit cd3327be6a74400718deb4f7ad6e0b9870ce443c)
…d (R5 follow-up lane E5, item 1) Lane P9 left open whether a strategy.exit trail WITH a trailing offset arms (activates) on the raw price path or on the tick-quantized one, and noted that no tape pinned it. These are the tapes that do: nine `lab tv` exports (pineforge-workflow, channel ws-report-v1, rangeProof covered), each directory holding strategy.pine, tv_trades.csv, meta.json and metrics.json unmodified. - NYSE:F 15m, 2025-04-10 .. 2026-04-17: three shorts and three longs whose exit bar has a raw extreme inside the activation's tick cell (lows 9.415 / 13.041 / 12.641, highs 11.899 / 13.049 / 13.419), with trail_offset 1 (e5-f-short-arm-qlow, e5-f-long-arm-qhigh) and the one-shot controls with trail_offset 0 (e5-f-*-oneshot-*). - BINANCE:ETHUSDT.P 15m, 2025-04-20 .. 2026-04-25: 8 longs and 8 shorts whose later bar touches the entry fill exactly, trail_price = fill +/- 0.004 or 0.006 and trail_points 0.4, each with trail_offset 1. bars.inc carries, per entry, the feed bars the probe replays (the NYSE:F registry feed 80f404ae..., the corpus 442d497 derived ETH 15m feed 27b62431...); README.md records the provenance. No test reads them yet. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit d80e411d3e9086478fa3c05f17a6c6594a60b0ac)
… (R5 follow-up lane E5, item 2)
TradingView's tapes (item 1) answer lane P9's open question against the
product. On NYSE:F, with trail_offset 1, all six trades exit on the bar whose
raw extreme stops inside the activation's tick cell and only its quantized
value reaches the activation (9.415 -> 9.41, 13.041 -> 13.04, 12.641 -> 12.64,
11.899 -> 11.90, 13.049 -> 13.05, 13.419 -> 13.42), each at activation -/+ one
tick. The adapter armed the generic Trail at the raw activation and exited one
to four bars later, or at the timeout. The one-shot controls exit on the same
bars at the activation; the ETH tapes show a sub-tick level is never rounded
onto the fill (0 of 32 arm on the touching bar).
Ruling: a trail's activation is quantized per order kind (the arm on the
quantized path, the running best and the trail stop raw), which is design B2's
shape and adapter policy. ADR-0001 "Trail and tick conventions" already names
the half-tick arm as the adapter's, and the kernel's generic rule for a trail
arm under a declared grid exists since L8b; selecting it for the arm alone is
the per-order-kind mask native-feature-parity.md section 3.6.2 rejected. So
the kernel is untouched (no header, no epoch, no C surface) and the adapter
spells the rule:
- source_trail_arm_level: the half-tick boundary where the quantized path
first reaches the activation, the threshold the one-shot leg rests at
(source_trigger_threshold). exit() arms a trailing leg there, and so does
the anchored-leg hook resolve_anchored_level, so an adopted relative child
is still bit-equal to the fill point's request.
- The re-issue identity check compares the attempted arm with the prior
source level's lowering instead of the level itself.
- The booked price reads a running best that is never short of the
activation: TradingView's starts there (13.041 books 13.05, 11.899 books
11.89), while the kernel's starts at the arm.
Witness tests/test_offset_trail_quantized_arm.cpp replays every tape trade and
reads the kernel's record (the Trail's arm is the activation's half-tick
boundary, the TrailArm activation and the close land on TradingView's exit
bar). Against the unchanged adapter: 541 checks, 48 failures (6 NYSE:F exits
late, the arm is the raw activation). With this change: 0 failures.
expectation corrected (test_trail_activation_tick_bar{,_l4c}): the P9 row
asserting the hold of a 9.996 high under a 10.00 activation now asserts the
fill @9.98, with the kernel's own stop at 9.976; its twin-parity rewrite
evidence is re-pinned (twinAssertions 41 -> 45, new digest) and so is
INVENTORY_SHA256 in scripts/check_twin_parity.py, outside the lane's file list
by necessity. Residual, same kind as B2: while the raw extreme stays inside
the activation's tick cell the kernel's stop sits up to half a tick further
out than TradingView's. Corpus 442d497 byte-identical 312/312 (on-grid, both
arms admit the same prints); the population sweep over sub-tick feeds is the
real gate.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 3bd6a36b13f08c9d1d36e6602cdb0ea053e2874b)
…ttled by TradingView's tapes (R5 follow-up lane E5, item 3) native-feature-parity.md: the B2 row names the trailing leg's activation among the quantized kinds, and a paragraph under the section 3.6.2 table closes lane P9's open question: the evidence, why it stays adapter policy (ADR-0001, the rejected per-kind mask), what the adapter now does, corpus byte-identity with the population sweep as the real gate, and the half-tick residual of the running best. native-engine.md: the grid section's per-kind sentence covers the trailing leg, and the P9 section records the settled rule and the corrected row. pine-to-native.md: the trail row states the quantized activation, the adapter's half-tick arm and the kernel's raw compare. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit b796227d579a99c44c67bb45a680e1753b7ade28)
…, byte for byte as exported (R5 follow-up lane E9, item 1) Lane E5 left two questions open (exec/E5-final.md "(f) Findings"): the price a one-shot trail (trail_offset 0) books when its trail_price is sub-tick, and whether a placement close inside the activation's tick cell counts as already reached. These are the tapes that answer them: six `lab tv` exports (pineforge-workflow, channel ws-report-v1, rangeProof covered), each directory holding strategy.pine, tv_trades.csv, meta.json and metrics.json unmodified. - BINANCE:ETHUSDT.P 15m, 2025-04-20 .. 2026-04-25: e5-eth-long-oneshot-p004 (lane E5's one-shot control, trail_price = fill + 0.004: 5 of 8 exit at the tick past the level, fill 2550.85 -> 2550.86) and its short twin e9-eth-short-oneshot-m004 (fill - 0.004: 4 of 8 at fill - 0.01). - NYSE:F 15m, 2025-04-10 .. 2026-04-17: seven longs and one short whose placement close is half a tick short of the activation and quantizes onto it (11.295 -> 11.30 ... ; 14.415 -> 14.41), with trail_offset 1 and 0 (identical tapes): every trade exits on the next bar, at its open. bars.inc carries, per NYSE:F entry, the registry feed bars 80f404ae... the probes ran on; the ETH tapes reuse tests/fixtures/offset_trail_arm/bars.inc. README.md records the provenance and TradingView's refusal of a trail without trail_offset. No test reads them yet. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 52961c8629a2ab3e78a3dd40572f8eb8518e7b30)
… away from the position (R5 follow-up lane E9, item 2) Lane E5's one-shot control (strategy.exit trail_offset 0, trail_price = fill + 0.004) differed from TradingView on 5 of 8 trades, and its short twin (fill - 0.004) on 4 of 8, identically before and after E5. The adapter lowers the one-shot to a generic Limit resting at the half-tick boundary where the tick-quantized path first reaches the level (one_shot_trail_trigger -> source_trigger_threshold: 2550.854 rests at 2550.855), then booked the crossing with the stop-style snap TOWARD the position, directional_tick(activation, is_buy) = 2550.85, in both zero-offset settlement sites of resolve_terms: the unarmed reach of zero_trail_policy_price's path walk and the explicit-zero block that overrides every one-shot filled inside a path. That price lies on the wrong side of the resting limit, so the kernel's generic limit-or-better check (NativeExecutionConsumer:: consume_matched_request, native_execution_consumer.cpp:4426-4443: a non-fill-through sell limit refuses resolved < level) returned MatchRejectedEvent InvalidTerms, terminal; the exit never happened and the trade closed at the timeout. TradingView books 2550.86 and 1791.82. Ruling (adapter): the kernel is right and untouched. A booked price the resting limit forbids is legitimately refused; the defect is the adapter's price. A one-shot trail's activation is reached on the quantized path, so the price it books is the tick that path first reaches there: the resting level rounded AWAY from the position (source_one_shot_reach_tick = directional_tick (resting level, !is_buy), the grid point the half-tick threshold stands for), which the limit admits by construction. On the grid it is the activation itself (k * tick either way), so on-grid outcomes are byte-identical; a generic Trail (a zero-offset trail already reached at placement) keeps the old snap. No header, no epoch, no C surface. Witness tests/test_trail_activation_tick_reach.cpp replays both tapes (tests/fixtures/trail_activation_tick_reach) and reads the kernel's record: the Limit's level is the outermost price whose tick is the reach tick, no match is refused, every TradingView trail exit is filled at that tick on TradingView's bar. Against the unchanged adapter: 235 checks, 36 failures (long 5 of 8 and short 4 of 8 exits late at the timeout, each refused InvalidTerms offered @2550.85 / 1791.83 against 2550.855 / 1791.825). With this change: 262 checks, 0 failures. The full-feed harness (pinned codegen 89645d6 + scripts/run_strategy.py on the corpus ETH feed) goes 3/8 -> 8/8 and 4/8 -> 8/8. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 85f19a09c6ce0a5b26461765f4d46a7993411e7b)
…arried best starts there (R5 follow-up lane E9, item 3)
Lane E5 left one question unpinned: does a placement close that sits inside
the activation's tick cell count as already reached? The tapes of item 1 answer
it. On NYSE:F 15m, seven longs and one short issue strategy.exit(trail_points
n) at the close of the bar whose open filled the entry; that close is half a
tick short of the activation and quantizes onto it (11.295 -> 11.30, 11.575 ->
11.58, 13.055 -> 13.06, 14.135 -> 14.14, 13.705 -> 13.71, 13.385 -> 13.39,
11.565 -> 11.57; short 14.415 -> 14.41), and the next bar never reaches the
activation's half-tick boundary on its raw path. TradingView exits all 8 on
that next bar, at its open, with trail_offset 1 and 0 alike (identical tapes);
with offset 1 at activation -/+ 1 tick, not close -/+ 1 tick. The adapter read
the placement close raw, left the trail dormant (a Trail waiting for its arm,
or a resting one-shot Limit) and exited 1 to 20 bars later: 0 of 16 trades.
Ruling (adapter): the placement print is one more print of the quantized path
the activation is tested on (E5's ruling 1 applied to the placement point),
and a trail armed that way carries a best that starts at the activation (E5's
ruling 2). source_trail_reached_at compares a print with the activation's
half-tick arm (source_trail_arm_level), and replaces the raw compare in exit()
(already_reached, which arms the trail at placement: the generic Trail is
submitted without an arm, the one-shot is not rested) and at the sibling
stop of an explicit-zero trail armed at placement, whose level becomes the
carried best max(close, activation) for a long, min for a short; resolve_terms
reads placement reach the same way (placement_reached_trail_activation,
zero_trail_source_price) and zero_trail_policy_price's carried placement best
never starts short of the activation. On an on-grid feed no print lies in the
half cell, so outcomes stay the raw compare's, save a close on the
activation's own grid point that the raw compare missed by a binary64 ULP.
The kernel is untouched: it already arms a Trail without an arm at its first
print. No header, no epoch, no C surface. The omitted-offset shape follows
the same compare; TradingView refuses it standalone at compile time, so no
tape pins it on its own.
Witness tests/test_trail_activation_tick_reach.cpp section 3 replays the four
placement-cell tapes and reads the kernel's record (no resting one-shot
Limit, a Trail accepted without an arm, a leg closing the trade on the next
bar). Against item 2's adapter: 530 checks, 69 failures (16 trades late, 13
at a wrong price; 8 dormant legs). With this change: 530 checks, 0 failures.
The full-feed harness (pinned codegen 89645d6 + scripts/run_strategy.py on the
registry feed) goes 0/16 -> 16/16; E5's ten probes stay 60/60.
expectation corrected (test_trail_activation_tick_bar{,_l4c}): no fill ->
fill @9.98 on this bar for the carried best 9.996 under the 10.00 activation,
because these tapes pin a placement close inside the activation's tick cell
as reached, with the best at the activation (kernel stop 9.97 from the 9.99
open, the booked price 10.00 - 0.02, as the row's carried-best-AT-10.00
control); the row is renamed test_product_carried_best_arms_on_its_tick. Its
twin-parity rewrite evidence is re-pinned (twinAssertions 45 -> 49, new
digest; the reason names the E9 tapes) and so is INVENTORY_SHA256 in
scripts/check_twin_parity.py, outside the lane's file list by necessity, as E5.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 3d9b1b944df2d666eed47f5c256dc79ac8e8301d)
…R5 gap lane E12, item 3)
`examples/native/native_bracket_strategy.cpp` keeps one keyed resting bid in a
`tk::OrderBook<std::string>` and drives it through every action a host can
meet — submitted fresh, replaced in place, a replacement the kernel refuses
(`Reduce{ExplicitUnits{0.0}}` → `InvalidQuantity`), then withdrawn — reading
the outcome each time and never the bare handle. It asserts the two facts the
handle cannot carry: a re-pricing issued no submit (the order the host named
kept its place), and a refused replacement issued no submit either and left
that order working and still bound to the key. It also asserts that an unknown
key is the book's own silence: `known == false`, nothing commanded.
The bid rests at 90/91 under a market around 100 and is cancelled on the last
bar, so it never fills, never joins the bracket's group and moves no trade:
the row's assertion, `closed trades: [1-9]`, is unchanged, as is every line
the example printed before ("working before the fill: 1", "working after the
fill: 2", both arms, one long trade). Its own new lines are printed after the
asserts that produced them.
docs/pages/native-engine.md gains the `OrderBookAction` table beside the
existing `BracketLegState` one and states the ruling in the same words: the
kernel's `ReplaceResult`, `SubmitResult` and `CancelResult` are the truth and
are carried verbatim, `action` is the toolkit's only added word, and the two
legacy spellings are projections — `submit_or_replace(...)` is
`submit_or_replace_outcome(...).handle()` and `cancel(key)` is
`cancel_outcome(key).status()`, exactly. docs/pages/pine-to-native.md's
cancel-by-id row gains one clause for the same, and its stale
`native_toolkit.hpp:124` anchor for `OrderBook` is corrected to :309.
No C change. The toolkit has no C spelling (it is header-only composition
over the public request surface and emits no library symbol), and the C
replace already answers a typed status: `strategy_native_replace_v1` returns
`PF_NATIVE_OK` / `PF_NATIVE_E_REJECTED` / `PF_NATIVE_E_NOT_WORKING` /
`PF_NATIVE_E_INVALID_TARGET` / `PF_NATIVE_E_STATE`, which is `ReplaceStatus`
plus the command-legality code, with the successor handle written out.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 129c0be192b6bb7f8a77acb80dc99a61f076232c)
…ollow-up lane E13, item 1) `pf_native_request_v1` has always said whether a TRAIL carries an arm price (`trail_has_arm_price`), but the working-book readout `pf_native_working_v1` carried only `p1`/`p2`: `working_trigger_tag()` (called by `fill_working()`) wrote `p2 = *arm_price` when the optional held a value and left it 0 otherwise, so a trail with no arm and a trail armed at 0.0 came back as the same row. Since lane E1 an anchored trail's absent arm is a legal spelling of its own, which made that observable: the two waiting legs of the new C scenario agree on intent, owner, trigger, trigger state (every Trail starts at TrailWaitArm), remaining kind, p1 and p2 -- nothing but their own labels told them apart. The readout now carries the request's own flag, filled from the live definition's `std::optional<double>`. Before the arm an anchored trail reads back the spelling it was submitted with; the arm installs the level into the definition, and both spellings then read 1 with that level in `p2`. A tick-spelled trail offset needs no mirror: acceptance resolves it into `Trail::offset` in place, so `p1` is already a price distance. Ruling -- growth mechanism, no epoch. The header's hardening rules make `struct_size` exact EXCEPT for a deliberately additive tail, and that path has been taken six times at `PF_NATIVE_API_VERSION` 1 (the request's L7b and L3b tails, the callback table's hook tail, the spec extension's L9, N8 and auxiliary tails): append after the last field, publish the earlier length as the offset of the first appended field, accept both lengths. This is the first READOUT to take it, and a readout adds one obligation an input does not have: the runtime writes, so it must never write past the length its caller sent. `strategy_native_working_get_v1` now accepts `PF_NATIVE_WORKING_V1_BASE_SIZE` or the current sizeof (anything else stays PF_NATIVE_E_STRUCT), fills a full local row and copies exactly the caller's length back. A static_assert proves the base length IS a base-layout caller's sizeof on the runtime's target (the tail starts where `comment` ended and that end carried no padding), and one pins the tail to its two words. The alternative -- reusing the row's `reserved0` word, as `pf_native_subscription_v1::gaps` did -- is refused for a readout: an older runtime writes 0 there, so a newer caller would silently read "no arm"; with the appended tail an older runtime refuses the newer length instead. The STABILITY paragraph's "raises the version constant" describes a revision that refuses earlier callers; an additive tail keeps them, which is why none of the six earlier tails bumped it and this one does not either. Generic capability, not a Pine one: the C transport reads back a C++ optional it already had. No kernel or adapter source is touched, no symbol is added (check_c_abi_runtime.py unchanged), and the COVERAGE row for `native_working_requests` names the field. The HARDENING RULES bullet named one additive struct and three layouts; it now names the four structs that carry tails and the readout's write bound. Test first. Against the unchanged header the new scenario does not compile (first diagnostic: "no member named 'trail_has_arm_price' in 'struct pf_native_working_v1'", 15 errors). With the header grown and the runtime unchanged it runs and fails for the right reasons: "the 0.0-arm trail reads back no arm price", "a base-length working row was refused", "an armed trail reads back no arm price". After the runtime change it passes, including a base-length read whose bytes past PF_NATIVE_WORKING_V1_BASE_SIZE keep the caller's 0xA5 sentinel and a third length refused. No ctest row is added (the scenario runs inside test_native_c_api), so KERNEL_MIN_TESTS and RELEASE_MIN_TESTS do not move. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 5cc28340c4e2e363494973ac61ebd991c0455bcd)
…5 follow-up lane E13, item 2) README's C ABI table row for `strategy_native_position_v1` / `_working_len_v1` / `_working_get_v1` names the readout's appended `trail_has_arm_price` and the base length an earlier caller sends. native-engine.md gets one paragraph beside the request's published lengths: `pf_native_working_v1` is the first readout with an additive tail, what the flag reads before and after an anchored trail's arm, and the obligation a readout has that an input does not -- a caller sending `PF_NATIVE_WORKING_V1_BASE_SIZE` is written exactly that far and never past it. No ADR-0001 row: the ADR has no C-surface growth table. Its ruled tables cover TradingView-named residue in the kernel archive (a struct field is not a symbol or string there), deprecated public spellings (nothing is renamed) and `NativeRunSpec` fields the adapter does not declare (this is a readout field, not a spec field), and the header's own growth rule asks for none. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 2f27bd194073aaf488eb2d523997821a663b48eb)
…landed (R5 follow-up lane E14, item 1)
A trailing stop rides a running best, and until now the kernel had no way to
say where that ride begins. The best was the arm's own print: the arm
threshold's ladder point when a crossing armed the trail, the first print the
trail saw when it was submitted already armed. Both are accidents of WHEN the
arm happened rather than a decision the run made, and a host whose position
already reached a level before the request existed could not say so — it had
to restart the ride at a worse price and watch its stop sit too low.
native_order::Trail::best_seed is that level, appended last so every existing
{offset}, {offset, arm_price} and {offset, arm_price, ticks} initializer keeps
its meaning. Present, it is a floor on the start: at the arm the best becomes
the favourable one of the seed and the arm print, the higher for a sell trail
and the lower for a buy trail. Absent — every request in the tree today — the
arm is byte-for-byte what it was, which the two control rows pin by their
continuation digests, measured on this lane's base before the field existed
(7148617029230866814 and 1059492877796320724).
The ruling, under ADR-0001 rule 2. 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 names — the kernel neither derives it
nor knows what named it, exactly as it does not know what named an arm_price.
Knob: today's start is not a choice the run made another way; there is no
second spelling of it, and keeping the arm threshold as the start is not "the
run already decided" but "nobody decided". So a request field is admissible
rather than a hook. The seed is absolute — an anchor moves the arm threshold,
never the level the ride starts at — it must be finite and positive, and under
a price grid it is put on the ladder exactly like an observed print, so L8b's
invariant that the running best lives on the ladder still holds. The
activation event still reports the print the arm happened at, never the seed,
which the existing reached_price pins require.
The digest folds the seed only when present, under its own "best_seed" tag so
it can never read as an arm price. No epoch: the field is appended inside
native_order_v6 the way WaitForApplied::first_match and FromOwnerFill::rounding
were, and NativeTrailState — the projection the C++ ABI matrix pins — is
untouched.
tests/test_native_trail_best_seed.cpp is the witness, kernel-only, batch and
stream: a sell trail already armed at submission whose next print is 100.60
while the host names 101.00, and a sell trail armed on a crossing of a
half-tick threshold whose bar high stops at 100.999 while the host names
101.00. In both the later shallow bar reaches between the two stops. Plus the
buy mirror, an off-ladder seed under QuantizeFillsAndTriggers, and the
acceptance rows.
Fail-before, compiled against this lane's base 9f0ea24c:
tests/test_native_trail_best_seed.cpp:80:19: error: no member named
'best_seed' in 'pineforge::native_order::Trail'
and the same two bar sequences driven through the unchanged kernel
(exec/E14-probes/fail_before.cpp) book no exit at all:
A control rows=0 exit=0 position=1 continuation=7148617029230866814
B control rows=0 exit=0 position=1 continuation=1059492877796320724
After:
PASS trail best seed: 155 checks, 0 failures
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit c2a48fc2b55286c224386e981d1b35325f0f0c33)
…uest layout (R5 follow-up lane E14, item 2)
pf_native_request_v1 grows one tail: `trail_best_seed` and its
`trail_has_best_seed` flag, the C spelling of native_order::Trail::best_seed.
That makes four published layouts, and the runtime accepts every one of them:
the base the L13 lane shipped, plus L7b's anchored-leg tail, plus L3b's
sizing detail — which now has a name of its own,
PF_NATIVE_REQUEST_V1_SIZING_SIZE, defined as the offset of the first field
appended after it so it stays right on every target — plus this one. A caller
compiled before the seed sends the third length and its trail rides from the
arm print exactly as it did; the tail is not even read.
The hardening rules are met where they apply: the flag is range-checked
(anything but 0 or 1 is PF_NATIVE_E_TAG) and only when the caller carries the
tail, so an older layout is never judged on bytes it does not have; the level
itself is the kernel's own validity rule, answered as PF_NATIVE_E_REJECTED.
Three static_asserts pin that no tail moved.
The COVERAGE list is unchanged — the seed is a request field, not a
NativeStrategyHost member — but the header records the asymmetry a C host
will look for: pf_native_working_v1 keeps two trigger numbers and a trail now
has three. The seed is a submission input consumed once at the arm, and what
a host wants back is the ride, which strategy_native_trail_state_v1 already
answers as best_price and current_level; growing the working POD would cost
every compiled reader a layout for a number the caller wrote.
tests/test_native_c_api.c gains check_trail_best_seed_tail: the same five
bars as the C++ witness, run three ways. Unseeded the lot is still open at the
end; seeded it closes; and sent with PF_NATIVE_REQUEST_V1_SIZING_SIZE while
the seed bytes sit in the struct, it closes nothing — which is the whole
append-only claim. Plus the out-of-range flag and the nonpositive level.
test_native_c_api: ok
test_native_c_api_frozen_header: ok
check_native_c_api_surface: 55 NativeStrategyHost members, 47 with a C
spelling, 8 excluded with a reason
check_c_abi_runtime: exit 0
docs/pages/native-engine.md's request paragraph counts four lengths now, and
names the seed among the trigger's numbers.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 84847ee935b20b4a9a2af9e63b0300b855593d4d)
… lanes' residual (R5 follow-up lane E14, item 3)
Lanes E5 and E9 each left the same residual, and it is one sentence:
TradingView's running best starts AT the activation, the kernel's started
wherever the arm happened to land. Two shapes, both measured:
- armed on a crossing (E5 ruling 4): the kernel's best is the half-tick
threshold the adapter arms at, so its stop sits half a tick further out
than TradingView's. None of E5's six NYSE:F tapes hit the gap.
- armed at placement (E9 finding 2): the arm price is dropped, so the
kernel's best is the NEXT bar's first print. All eight of E9's tapes fell
far enough to hide it; a shallower next bar would diverge.
With item 1's generic Trail::best_seed the leg simply says where the ride
starts. exit() names the source activation, and when the placement print has
already reached it, the favourable one of the two — the same carried best
max(close, activation) for a long, min for a short, that E9's sibling stop
already rests at. The kernel floors its running best there at the arm; nothing
else about the leg changes, and no other adapter site does.
That is not a new TradingView rule in the kernel: the kernel is told a number
and floors a max, exactly as it is told an arm price. Which number is
TradingView's is decided here, where E5's ruling 2 was measured.
Three twin suites move, and every one of them moves the kernel's own facts
TOWARD the price the adapter already booked. No booked exit price, bar or
quantity changes in any row:
expectation corrected (test_trail_activation_tick_bar{,_l4c}): raw_price
9.976 -> 9.98 and path_position 2 + 0.02/0.021 -> 2 + 0.016/0.021 for the
offset-trail arm row, and raw_price 9.97 -> 9.98 for both carried-best rows,
because the kernel's best no longer starts at the arm (9.996 raw) or at the
next open (9.99) but at the activation 10.00 the leg names, so its own
crossing IS the booked 10.00 - 0.02.
expectation corrected (test_trail_fill_snap{,_l4c}): raw_price 9.855 -> 9.85
and 9.97 -> 9.975, because the seeded best is the carried level again — the
very level the deleted exit-path resolver read — instead of the first live
print; the booked 9.85 and 9.97 are unchanged.
expectation corrected (test_trail_open_arm_subtick_offset{,_l4c}):
leg_is_stop -> leg_is_trail for the already-armed zero-distance rows,
because the gapping open 1475.99 is now strictly past the Trail's own
seeded best 1501.03, so the Trail books the print its sibling stop used to.
Price, bar, at_bar_open and level_fill are unchanged.
Their observable-rewrite evidence is re-pinned (three twinAssertionSha256,
reasons extended; the assertion counts are unchanged) and so is
INVENTORY_SHA256 in scripts/check_twin_parity.py, outside the lane's file list
by necessity, as E5 and E9 both were.
The sibling stop STAYS, and it is not redundant. A kernel Stop is reached by a
touch; a zero-distance trail needs a move strictly past its best
(trail_zero_stop_hit), so the two differ exactly when a print lands ON the
carried best. Measured with the stop withheld, on a long carrying best 100.10
into a bar O 100.10 H 100.12 L 100.05:
with the sibling stop: exit 100.09999999999999 raw 100.09999999999999 at_open=1 leg=Stop
with it withheld: exit 100.10000000000001 raw 100.11500000000001 at_open=0 leg=Trail
The booked price differs in its last bits, so removing it would not be
byte-identical. (exec/E14-probes/sibling_stop_touch.cpp.)
check_twin_parity: 155 tests, 8298 base assertions, 6657 literal-matched
twin assertions, 1262 ledgered, 379 pinned observable rewrites, 528
additional twin assertions, OK
corpus_trades_identity: 312 probes at corpus 442d497 — baseline match=312,
moved=0, unrecorded=0, missing=0, schema violations=0
Verified 312 strategies — excellent=311, strong=0, moderate=0, weak=0,
minimal=0, anomaly=1, engine_only=0, missing=0
[corpus-parity] TradingView parity holds at corpus 442d497
The corpus cannot exercise the seed's own effect: its feed is on the 0.01
grid, so the arming bar's own extreme is folded into the best before any
adverse move and the seed changes nothing there. The wave-D sweep over
sub-tick feeds is the real gate.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 4f5cdf3e1f0e84b7bf2e1edd55b6d9189606edcf)
…et the kernel learn it (R5 follow-up lane E14, item 4)
Four pages, one fact each.
native-engine.md gains `Trail::best_seed` beside the other trail spellings —
what absent means (the arm's own print, byte for byte as before), what present
means (a floor on the start, the favourable one of the seed and that print),
that it is absolute, finite and positive and goes on the ladder like an
observed print, that the activation still reports the print the arm happened
at, and that the digest folds it only when set. Its trail section then records
what lane E14 closed: the residual E5 and E9 each left, why the sibling stop
of an explicit-zero trail is NOT redundant afterwards, and that the kernel's
own crossing is now the price the adapter books.
pine-to-native.md's trail row carries the seed in the kernel spelling and one
sentence on who names it.
native-feature-parity.md §3.6.2: the B2 row says which part of it was
closable and is closed — where the running best starts is not a quantization
but a level the leg names — E5's residual paragraph is past tense, E9's names
the kernel half, and a new paragraph records lane E14 against the amended
rule 2 with the corpus and sweep caveats.
ADR-0001 gains one section and one row. Rule 2's hard case is the capability
whose NEED was found by measuring TradingView while the mechanism is a shape
any venue has, and the amended rule asks two things: is it drivable by data,
a spec field or a hook, and would spelling it put a hash-visible choice on the
surface for a decision the run already made. `Trail::best_seed` answers
generic and admissible — a trail that rides from its activation is an ordinary
broker shape, the level is a number the host hands over as it hands over an
arm price, and the old start was nobody's decision but wherever the arm
landed. The section is deliberately its own `##`, outside the residual
vocabulary section, so `check_kernel_residuals.py` cannot read its table as a
stale ruling.
check_kernel_residuals: 109 archive hits (106 identifiers, 3 texts)
against 119 ruled identifiers and 11 ruled texts: 0 findings ... OK
check_native_feature_rulings: 43 NativeRunSpec fields, 33 declared by the
adapter's project(), 10 ruled, exit 0
Merge note: lane L14-C (origin/r5/int12-docs) rewrites ADR-0001 and adds its
own "TradingView-calibrated kernel mechanisms (rule 2's own account)" section.
On a merge, this row belongs in that section's table B and this section goes
away; the wording here is already the amended rule's.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit bca4c902e2755a501ce9af6d337eee28484cebb3)
…nning best told apart (R5 follow-up lane E14, item 5)
Lanes E5 and E9 recorded the same residual and neither could hit it: all
fourteen of their tapes had a next bar that fell far enough for either reading
of the running best to exit. These two do not.
`rank_shallow.py` searched the NYSE:F 15m registry feed (evidence
80f404ae…, `lab evidence get`) for an entry whose placement close reaches the
activation on its tick AND whose next bar is SHALLOW: its own extreme half a
tick to a tick short of the carried best, its other extreme BETWEEN the two
stops, carried - k ticks and restart - k ticks. Eleven such entries, seven
long and four short, `trail_points` n with `trail_offset` 5, exported with
`lab tv` (ws-report-v1, both `rangeProof: covered`) and committed under
tests/fixtures/trail_activation_tick_reach/ beside lane E9's six.
TradingView exits all eleven on the next bar, at the carried stop's reach
tick. Its running best is the carried level and never the leg's first live
print. The four shorts are what pins that level RAW rather than quantized:
their placement closes sit half a tick PAST the activation (13.035 under
13.04, 13.925, 14.075, 13.295), TradingView's stop is that close + 5 ticks,
and the next bar's high reaches it where the activation's own stop never
would. They also pin the booked price's direction: a trail stop is reached
from the ADVERSE side, so a short's books the grid point at or above it
(13.085 -> 13.09) — the other way from the one-shot limit lane E9 pinned.
Through the product (pinned codegen 89645d6 -> strategy.dylib ->
scripts/run_strategy.py on the registry feed), with the adapter seed withheld
and with it:
e14-f-long-shallow-next 0/7 -> 6/7
e14-f-short-shallow-next 0/4 -> 4/4
The seventh is a RECORDED divergence, and it is not this question.
2025-09-10's stop is 11.44 - 5 ticks, whose binary64 value
11.389999999999998792 lies one ULP UNDER the bar's low 11.390000000000000568;
the trail stop is raw by lane E5's ruling, so it is not reached and the trade
runs to the timeout while TradingView books it. That is the tick-product
residual on the trail stop and needs its own lane. The test asserts the row
DIFFERS (`kLongShallowUlpResidual`), so it fails if the engine ever silently
starts matching and the mask is not removed.
tests/test_trail_activation_tick_reach.cpp gains section 4 over the new
probes — the shallow bar's geometry, TradingView's exit, and the accepted
leg's own `Trail::best_seed` — and its replay loop learns the recorded-
divergence mask. Section 3 is gated to the placement-cell tapes, whose
question the new ones do not ask.
PASS trail activation tick reach: 717 checks, 0 failures
PASS offset trail quantized arm: 541 checks, 0 failures
trail_activation_tick_bar: 66 passed, 0 failed
test_trail_fill_snap_l4c: 249 passed, 0 failed
test_trail_open_arm_subtick_offset_l4c: 215 passed, 0 failed
test_zero_offset_trail_rides_l4c: passed=450 failed=0
check_twin_parity: 155 tests, 8298 base assertions, 6657 literal-matched
twin assertions, 1262 ledgered, 379 pinned observable rewrites, 528
additional twin assertions, OK
The adapter comment now records why the carried best is read raw. No logic
moved from item 3.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 0ddc03548ccce202211a41d1ce7fedf2c71bc5ca)
… item 1) BacktestEngine::declare_opened_lot_entry_bar_mask derived a lot's entry-bar mask from internal::bar_path_uses_high_first, whose thread-local override only NativePathOrderScope installs, and only while the consumer materializes an intrabar sample path (native_execution_consumer.cpp). A host calls the seam from on_native_applied, outside that scope, so under a forced NativeRunSpec::path_order the mask was computed for the open-proximity order the run never walked: the low-first bar O100 H110 L99 C105 with an ON_PATH fill at 105 gave (high, low) = (0, 1) under AUTO, HIGH_FIRST and LOW_FIRST alike, where the HIGH_FIRST walk O->H->L->C touches 105 on its first leg, before either end, and masks nothing. Pre-existing: the adapter made the same calls before lane E6. The seam now asks the consumer for the run's order (NativeExecutionConsumer::path_high_first: path_uses_high_first over the spec's path_order, the resolution deliver_confirmed_script walks) and finds the fill's first touch on that same walk (the two-argument first_touch_position). AfterPath is order-free and unchanged. The engine.hpp and native_c_api.h docs of the seam state the order it now applies. RULING (ADR-0001 rule 2): the mask is derived from the same path order the matcher walks. The run already made that decision through path_order, so this is not a knob; it is the existing choice applied consistently. It is generic: nothing here names a platform, and every host (C++, C through E11's strategy_native_declare_opened_lot_entry_bar_mask_v1, or the Pine adapter) gets the one answer. Consequence: a run under a forced order gets different masks, hence different owner excursions, than before. Codegen never declares an order (no path_order in pineforge-codegen-oss); the Pine adapter declares one only when its embedder calls set_path_order / strategy_set_path_order, which in this repository is scripts/run_strategy.py --path-order alone: no runtime_overrides key carries it, neither in run_strategy.py nor in the campaign verifier, and run_corpus.sh never passes the flag. Under AUTO the resolved order is the expression the old read returned in a callback (the override is always 0 there), so every AUTO run, the whole corpus included, is byte-identical by construction; a forced-order probe is judged by the wave-D population sweep. No epoch: no layout, hash field or default moves. Witness: tests/test_e6_entry_bar_mask_declaration.cpp gains forced HIGH_FIRST / LOW_FIRST runs over both bar shapes and both fill points (fail-before: (0,1) under HIGH_FIRST on the low-first bar, (1,0) under LOW_FIRST on the high-first bar); tests/test_native_c_api.c the same numbers through the C spelling, the order declared in the spec extension's feed-policy block. No new ctest row. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit a754745a7cbfa16afa969cdabb7b541e5c5d31ff)
PathOrderScope (src/engine_run.cpp) was defined in that file's anonymous namespace and never instantiated. `git grep PathOrderScope` over the tree finds its own three definition lines and five comments; every other hit is the consumer's live NativePathOrderScope, the version checker's pin of it, or a frozen historical header inside a fixture tarball. Being file-local, no other translation unit could reach it. The class is deleted, and engine_run.cpp's object file is byte-identical before and after (md5 5c5995d65ea985b5356b639d804aaf06, built with the tree's own flags). Five comments described the installation it never made: engine.hpp's path_order_mode_ and set_path_order() docs, engine_internal.hpp's set_path_order_override / path_order_override docs, and the override's own comment and the file header in engine_path_resolve.cpp. They now name the one installer the tree has, NativePathOrderScope around the intrabar sampler, and say that a question a host asks from a callback is answered in the run's order by the consumer (item 1). Comment-only in all three files: each strips (comments out, blank lines dropped) to the same bytes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 85014b43c030aee642703bdd1d518c5d2d3ca094)
check_native_c_api_surface.py censused NativeStrategyHost's public surface
only, so a BacktestEngine member a host calls or overrides from its
callbacks got no C spelling and no stated exclusion with nothing to flag it
(E6's declare_opened_lot_entry_bar_mask was the first protected one called;
lane E11 measured that a [C] row naming it fails the old census).
The census is now opt-in for the base: engine.hpp marks such a member's
declaration with a `// @host-seam` line, and native_c_api.h's COVERAGE block
gains a BASE-CLASS SEAMS list that must name exactly the marked set, each
with a C spelling that resolves against the C headers or a stated reason,
under the host rows' own rules. A marker outside class BacktestEngine, or
one that precedes anything but a member function, fails; a name in both
lists fails as listed twice.
Seeds, from reading how CCallbackHost and PineStrategyHost reach the base:
[C] declare_opened_lot_entry_bar_mask: E11's
strategy_native_declare_opened_lot_entry_bar_mask_v1 (both hosts call
it from on_applied); it replaces E11's prose paragraph.
[--] hash_host_extension: documented for any host (override it, call the
base first to keep its bytes), overridden by PineStrategyHost; the C
callback table has no hash hook (SCOPE said so; the row now holds it).
[--] hash_source_extension: its deprecated spelling, still folded.
CCallbackHost reaches no other protected BacktestEngine member.
PineStrategyHost reaches more (bar_fill_price, current_equity, open_profit,
the request.security machinery, broker_state_hash_projection, the source_*
virtuals): source-layer plumbing that ADR-0001 governs, none documented as
a host seam, so none is marked.
Self-tests: six new must-fail cases (a marked member without a row, a row
without a marker, a seam spelling naming nothing, a seam row among the host
rows, a marker outside the class, a marker on a data member). Against the
previous checker 6 of the 11 fail, the real tree included.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit f35d85966da007294e25e03f3defb3bda677dae1)
…item 4) ADR-0001, "Kernel state the adapter sets": one new ruling row and lane E6's row corrected. The new row records RULING (rule 2): the entry-bar mask is derived from the same path order the matcher walks. The run already made that decision through NativeRunSpec::path_order, so this is not a knob; it is the existing choice applied consistently. It states the pre-E15 mechanism (the seam read the sampler's thread-local override, which is never installed around a host callback, so every declared order got the open-proximity mask) and the consequence: forced-order runs get different masks; codegen declares no order and the adapter declares one only through its embedder's set_path_order (scripts/run_strategy.py --path-order, never passed by run_corpus.sh, carried by no runtime_overrides key), so the corpus is byte-identical by construction and the wave-D population sweep judges any forced-order probe. E6's row claimed path_order steers the mask; it names the mechanism that now does (NativeExecutionConsumer::path_high_first), says the claim held only from E15 on, and names E11's C spelling. docs/pages/native-engine.md: the excursion paragraph says the mask walks the run's leg order and that the seam is a row of the COVERAGE block's new BASE-CLASS SEAMS list; the "what is not exposed" paragraph names that list. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit ad4b11f94257401d1405fbfbcb8896409ceaa3ea)
…1 follow-up) E6's header credited the leg order to bar_path_uses_high_first, which the seam no longer calls since item 1: the order is the run's own. Comment-only, same line count, so the test object is byte-identical (md5 1cad302f2d565f702eedc01839743241 before and after). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 44541b73f54eb131dd6117aa3114868cf3a24fef)
…PU time since A40 rev 7 (R5 tooling lane E4, item 1)
A40 rev 6 made .github/workflows/ci.yml set
PINEFORGE_RUNTIME_BUDGET_CANDIDATE_ONLY=1 on the hosted macOS lane, so its
Release configure registered test_l4g_runtime_budget --candidate-only: one
correctness sample, no ratio. The reason was wall clock (the same tree read
12.8x and 17.2x best-of-five an hour apart on that VM). Lane Q9 (A40 rev 7)
made the gated sample the replay's process CPU time, which leaves out the time
a leg spends descheduled; Q9's open note asked for the lane back.
ci.yml now sets the variable to '0' on every lane, with the comment naming it
as the escape for a runner whose CPU accounting cannot time the replay and
asking for the measured reason before it is used. The mechanism is unchanged:
tests/CMakeLists.txt still reads the variable at configure time (comment
updated; it said the workflow sets it for macOS). docs/ci.md says which lanes
gate, what the escape is, and the line the macOS job must print.
Witness, scripts/test_ci_verify.py RuntimeBudgetLanes: every
PINEFORGE_RUNTIME_BUDGET_CANDIDATE_ONLY assignment in ci.yml is '0', and the
CMake registration still reads the variable (so the escape the comment offers
exists). Before this change:
AssertionError: Lists differ: ["${{ runner.os == 'macOS' && '1' || '0' }}"] != []
After: both pass. On this Mac a Release configure registers the row
--candidate-only with the variable at 1 and with the relative gate at 0.
actionlint 1.7.12 is clean on the three workflows.
Not measured here: the hosted macOS runner's CPU ratio. The PR's
build (macos-26, Release) job must show test_l4g_runtime_budget printing
"runtime budget: candidate=...s ab9714b=...s ratio=...x limit=15.000x
(process cpu time; ...)" and passing, not "candidate correctness sample".
Ruling: CI configuration, a CMake comment, docs and a tooling test. No
kernel, adapter, header or corpus change; no TradingView outcome can move; no
epoch question. tests/CMakeLists.txt is outside the lane's file list: a
comment-only edit, made because the comment described the old workflow.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 7b37d91e46b9545c5b97edf20c03b3209defbee3)
…_verify refuses one where it is not (R5 tooling lane E4, item 2)
Lane Q12 gave every examples/native executable -UNDEBUG and a ci_verify
stage (examples-assert-live) that refuses a compile keeping NDEBUG, but left
out runner/CMakeLists.txt's MODULE builds of two example sources,
native_market_example and native_selected_example ("Out of scope" in Q12's
report). Those modules are what test_native_example_batch and
test_native_example_selected dlopen and drive through the C ABI, so an assert()
in either source was live in its example_* row and a no-op in its module row.
Fail-before, ci_verify's kernel configure at 0a47cbf: both modules compile
with "-O3 -DNDEBUG -std=c++17 -arch arm64 -fPIC -fvisibility=hidden
-ffp-contract=off" (no -UNDEBUG). Mutation: assert(bars_ < 0 && "...") in
on_native_bar of native_market_strategy.cpp and native_selected_strategy.cpp,
false on every bar. Result:
example_native_market_strategy ***Failed (Assertion failed ... line 35)
example_native_selected_strategy ***Failed (Assertion failed ... line 77)
test_native_example_batch Passed
test_native_example_selected Passed
After this change the same mutation fails all four, the module rows with
"Subprocess aborted" and "Assertion failed: (bars_ < 0 && ...), function
on_native_bar"; reverted, all four pass.
runner/CMakeLists.txt appends -UNDEBUG to both module targets, after the
build-type flags, exactly as examples/native/CMakeLists.txt does (flags.make
now ends "-ffp-contract=off -UNDEBUG").
scripts/ci_verify.py: examples-assert-live now reads every compile whose
translation unit lies under examples/native/, whatever its target, instead of
the example_* object directories, and runs in every profile that compiles one:
release and kernel (the examples) and native (the live runner builds the two
modules there). compile_argv/compile_unit/compile_target factor the entry
parsing; OBJECT_DIR replaces EXAMPLE_OBJECT_DIR. On the real kernel compile
database of 0a47cbf the stage flags 2 of 15 targets (the two modules); after
this change 0 of 15.
Tests (scripts/test_ci_verify.py): the scripted configure compiles the two
modules in the profiles that build the live runner; ExamplesAssertLive covers
the native profile, a module compiled without -UNDEBUG (kernel and native), and
fails closed in both profiles. Against the previous ci_verify.py: 7 failures,
1 error. Three pins changed, each with an "expectation corrected" note.
Ruling: CMake runner wiring, CI tooling and docs only. No kernel, adapter,
header, codegen or corpus change, so no TradingView outcome can move and no
epoch is touched. The modules stay release builds (-O3, same visibility and FP
flags); only NDEBUG is undefined.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 016838035c47358f78d479535d87703d49a89f17)
…read (R5 tooling lane E4, item 3) The stage required build/lib/libpineforge.a to be newer than every file under src/, include/ and CMakeLists.txt. In the kernel profile (PINEFORGE_BUILD_SOURCE_LAYER=OFF) that archive is the kernel alone and never compiles src/source/ or src/compat/pine/, so after any edit there a full build rebuilt nothing and the stage failed (lane P7's report, "Two existing issues"). The same held in every profile for a header no archive compiles (e.g. include/pineforge/native_module.hpp, which only examples include). Reproduced on ci_verify's kernel build of 0a47cbf: touch src/source/pine_adapter.cpp include/pineforge/native_module.hpp, full cmake --build (rebuilt only the examples and modules, libpineforge.a mtime unchanged), then the stage function: ['src/source/pine_adapter.cpp', 'include/pineforge/native_module.hpp'] With this change, same tree, same touch: [] for both archives (80 inputs, 35 units, none under src/source or compat/pine). scripts/ci_verify.py: archive_inputs(source, build_dir, target) lists what a target's compiles read: each translation unit compile_commands.json compiles into it, and every header reached through #include that resolves inside the source tree ("quoted": beside the includer, then -iquote/-I/-isystem; <angled>: -I/-isystem, in the compiler's order). stale_archive_sources keeps its signature and derives build dir and target from <build>/lib/lib<target>.a. The stage now checks both archives every profile builds, libpineforge.a and libpineforge_kernel.a (examples and the residual gate read the latter), and fails closed without the archive's compiles in the database. Cross-check on the real kernel build: the scan's set equals the compiler's own depfiles (*.o.d, in-tree entries) exactly for both targets, 80/80, nothing missing either way. CMakeLists.txt is no longer an input: a change there that matters reaches the archive as a changed compile, and one that does not (a comment) rebuilt nothing and failed the stage too. Both trees count: the build tree's generated pineforge/version.h is read by the compiles too, so it is an input wherever the build tree lives (in-tree, as ci_verify's and CI's are, or out of tree). Cross-check against the compiler's own depfiles (*.o.d entries inside the two trees) on three real builds -- ci_verify's kernel build, a CLAUDE.md build-on, ci_verify's release build: identical sets for every archive, 80/80 (kernel) and 111/111 (with the source layer), nothing missing either way. Tests (scripts/test_ci_verify.py StaleBinaries): a synthetic tree pins the reading (kernel-only archive ignores src/source, an unused header and CMakeLists.txt; a unit, a nested header and a "quoted" neighbour are stale; a header generated into an out-of-tree build tree is an input; each archive against its own compiles; fail-closed on no database, no compile of the target, no archive); driver rows pin both archives in all five profiles, a stale kernel archive failing before ctest, and a database without the archive compiles. The scripted configure now emits the archives' compiles in every profile and the scripted build both archives. Against the previous ci_verify.py: 10 failures, 1 error. Ruling: CI tooling and docs only. No kernel, adapter, header or corpus change; no TradingView outcome can move; no epoch question. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 9d3a7cf9ad1a32dde84cfd449480db658853bbb1)
…es their providers (R5 tooling lane E4, item 4) Four CTest rows pair the tree against historical ABI archives: test_script_cpp_abi, test_settlement_cpp_abi, test_aggregate_cpp_versions_runtime, test_l4g_runtime_budget. A tree configured with PINEFORGE_REQUIRE_ABI_RECEIPTS=OFF -- every plain configure, CLAUDE.md's included -- registers them --skip-if-receipt-missing, and until the six ABI providers are prepared in it each exits 77. CTest counts a skipped row as passed, so the run still prints 100% (lane Q12's report, "Recipe gap"). Reproduced at 0a47cbf on a CLAUDE.md build-on (corpus strategies off): 4/4 Test #503: test_l4g_runtime_budget ...............***Skipped 100% tests passed out of 4 (the four gated rows; 540 rows registered) scripts/check_abi_receipt_skips.py, independent of ci_verify.py: reads the rows ctest registered (--show-only=json-v1), names every receipt-gated row that will skip (or, registered --require-receipts, fail) for a missing input receipt, and exits 1 when there is one, printing the recipe. It also names a tree without compile_commands.json, which test_l4g_runtime_budget reads once its receipt exists (Q12's second gotcha: preparing the providers in a plain configure turns that row's skip into a failure). --prepare first prepares the six providers into the tree with the argv ci_verify.py uses: it fetches a missing pinned commit, reuses a matching provider, refuses a mismatched one without deleting it. On that build-on: the check prints the four rows as "skips" (the settlement row missing all six receipts, the runtime-budget row also "then fails, compile database missing") and exits 1. After re-configuring with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, --prepare built the six providers in 98 s and the check exits 0 ("every receipt they read is present: all 4 run"); a second --prepare reuses all six. Recipe: CLAUDE.md's REQUIRED block configures with CMAKE_EXPORT_COMPILE_COMMANDS=ON, prepares the providers before ctest and runs the check after it (must exit 0); docs/ci.md explains the skip contract, the script and the same recipe. Tests: scripts/test_abi_receipt_skips.py (10) drives the script against the real ctest over hand-written rows shaped like the gated ones -- including a run ctest reports as "100% tests passed out of 4" in which three gated rows skipped -- plus the preparation plan with a stubbed runner (order, fetch before a missing object, refusal that keeps the directory). ReceiptRecipe in scripts/test_ci_verify.py holds the recipe's six argv equal to the ones the ci_verify driver invokes. Registered as the CTest row test_abi_receipt_skips (ctest passed as CMAKE_CTEST_COMMAND); before the script existed the test failed with "No module named 'check_abi_receipt_skips'". Floors: the new row registers in every profile (a Python row, source-free), so the integrator adds +1 to RELEASE_MIN_TESTS and +1 to KERNEL_MIN_TESTS; this lane edits no floor. Ruling: tooling, a CTest registration and docs. No kernel, adapter, header or corpus change; no TradingView outcome can move; no epoch question. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 005c3c54f4fd67c7eeb1c296da1373b98dc705af)
…ave D) Two edits that belong to no single lane because each is the intersection of two. 1. `scripts/check_c_abi_runtime.py` — `EXPECTED_NATIVE_C_API` carried `strategy_native_cancel_where_v1` twice. It was harmless (a frozenset literal) and lane E11 left it alone as cosmetic when it raised the list to 33 names; with E11's `strategy_native_declare_opened_lot_entry_bar_mask_v1` added the literal now has 34 entries for a 33-symbol set, which reads as a miscount to the next person who edits it. The duplicate is deleted; the set, `EXPECTED_NATIVE_C_API_DECLARATIONS` and `EXPECTED_NATIVE_C_API_IMPLEMENTATIONS` all say 33 and the checker exits 0. 2. `examples/native/native_open_lots_strategy.cpp` — expectation corrected. The documentation wave's example asserted "HostRecorded folds none of them: that series is the host's" for max_drawdown / max_runup / max_contracts_held_all. Lane E2 ruled the opposite and made it true: the extremes are a property of the RUN, folded at every script calculation under every report policy; what the policy scopes is the recorded equity CURVE and the `pf_equity_stats_t` figures walked out of it. The example now asserts exactly that split — the three numbers are equal under both policies, and only the curve length differs (7 recorded points vs 0) — and its summary line reports both. E2 named this example as the integrator's step in its own findings. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The pages cite the tree by line number and both doc guards are binding since the documentation wave (PR #272). Wave D moves those lines a long way: `include/` loses 411 net lines to lanes E6 and E10 (engine.hpp alone -400), `native_c_api.h` gains E7's two enums, E11's symbol, E13's readout tail and E14's request tail, `native_order.hpp` and `native_execution_consumer.cpp` both grow, and `src/source/pine_adapter.cpp` moves under E5, E9 and E14. On the picked tree 574 of 1553 anchors were bad; none of that is a claim changing, only where the claim lives. Three passes, in this order: 1. `python3 scripts/check_doc_anchors.py --fix` until it converged (358 anchors re-anchored, 216 left that it refuses to guess at). 2. The 216 it refuses are ambiguous only to a symbol search. They are not ambiguous to the diff: every one of them was correct at origin/main, which the wave's own gate proved (bad=0), so each was mapped through `git diff -U0 origin/main..HEAD` for its own file and rewritten to the line the wave moved it to. 159 doc lines. 3. Five anchors survived that, because they were not line drift but claims the wave made false. Each is corrected by reading: - `native-engine.md` cited `record_script_report_point` at the call site lane E2 moved; it now cites the definition. - `native-feature-parity.md` row (e) cited `apply_slippage` "in the comment at engine.hpp:2129" — lane E10 deleted that comment; it now cites `src/native_matching.hpp:240`, where the function is. - `native-feature-parity.md` rows (j) and 7 and ADR-0001's "current state" bullet said the excursion mask pair is "written only by the adapter" at `pine_strategy_host.cpp:471`. Since lane E6 no host writes it: the excursion owner declares where its fill sat and the kernel derives the pair. The three citations now name `declare_opened_lot_entry_bar_mask`. Four citation corrections the lanes asked the integrator for, in files main owns after PR #272: - The ten-significant-digit money residue was `engine.hpp:918-952`, not `:73-180` — measured by lane E6, which deleted it; `:73-180` is and was live code (`ClosedLotExcursionFacts`, `PyramidEntry`, `Trade`). Corrected in all four places that carried the wrong span: ADR-0001's "current state" bullet and its residue-table row 8, `docs/pine-adapter-kernel-notes.md:16`, and `native-feature-parity.md` rows (d) and 7. - ADR-0001 said `<pineforge/native_c_api.h>` implements **32** additive symbols. Lane E11 added the 33rd. - ADR-0001 recorded two rule-2 open questions. The wave closed both: the C delivery words are typed (lane E7's `pf_native_lookahead_e` / `pf_native_gaps_e`), and the excursion mask pair has no host writer (lane E6, with E11's C spelling and E15's declared-order derivation). Both bullets now say so, and say what is left: the pair is still durable hashed lot state, so removing it is still an epoch decision. Also folded in, from the picks themselves rather than from a lane's own file list: lane E14's new `##` ADR section was moved into the documentation wave's "TradingView-calibrated kernel mechanisms" table B (a standalone section beside the residual vocabulary reads as stale to `scripts/check_kernel_residuals.py`), and the header's two asymmetry notes were reconciled — `pf_native_working_v1` did grow a readout tail (E13), and the trail seed is still deliberately not read back (E14). Gates: `check_doc_anchors.py` 1555 anchors, bad=0 on every page; `check_doc_lint.py` 0 offenders; `check_pine_to_native_coverage.py` offered=131 covered=131; `check_design_inventory.py` 0 findings; `check_native_feature_rulings.py`, `check_kernel_residuals.py` and `check_native_c_api_surface.py` all clean. One header edit rides here because it is the same kind of rot, caught by `bash docs/build.sh` rather than by the anchor gate: lanes E7 and E11 wrote their doxygen cross-references against the enum TAG (`#pf_native_lookahead_e`, `#pf_native_gaps_e`, `#pf_native_opened_lot_fill_point_e`), while this header indexes and every other row cites the typedef (`#pf_native_intent_t`, `#pf_native_size_price_t`). Doxygen could not resolve the three, and E11's block left `@param s` undocumented while documenting the other three parameters. Four warnings; with the references pointing at `..._t` and `s` named, `docs/build.sh` reports **0**. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Recomputed on the final tree, under lane Q12's rule: a floor is the number of rows that RAN, never the number registered, and a skipped row is listed rather than counted. No lane's own number is taken -- every lane raised its floor against its own base (0a47cbf, 189/554), while this branch is built on the documentation wave's main (9b50973, 193/558), so the lane values (E1 190/555, E10 191/556, ...) are all relative to a tree that no longer exists. Measured, not summed: python3 scripts/ci_verify.py kernel --build-dir build-ci-kernel --jobs 6 ctest ran 199 rows (floor 193); not counted: 1 skipped (test_native_live_websocket) -> 200 registered python3 scripts/ci_verify.py release --build-dir build-ci-release --jobs 6 ctest ran 566 rows (floor 558) -> 566 registered, none skipped KERNEL_MIN_TESTS 193 -> 199, per lane: +1 E1 test_native_anchored_trail_absent_arm +1 E3 test_native_margin_fx_clock +1 E4 test_abi_receipt_skips (registers in every profile) +1 E6 test_e6_entry_bar_mask_declaration +1 E10 test_e10_dead_path_prefix +1 E14 test_native_trail_best_seed (kernel-only, unlike E5's and E9's) RELEASE_MIN_TESTS 558 -> 566: those six, plus the two TUs that reach the source layer and therefore register in release only: +1 E5 test_offset_trail_quantized_arm +1 E9 test_trail_activation_tick_reach E2, E7, E8, E11, E12, E13 and E15 add no row: every witness they wrote is a new scenario inside a TU that already existed (test_native_report_truth, test_native_c_api, test_native_toolkit_bracket), which is why their lane reports say the integrator adds nothing for them. Cross-checked against a configure of origin/main's own tree with the same options: 558 rows registered there, 566 here, and the diff of the two `ctest -N` name lists is exactly those eight names with nothing removed. The comment that records this arithmetic moves both constants inside `scripts/ci_verify.py` (KERNEL_MIN_TESTS 93 -> 108, RELEASE_MIN_TESTS 116 -> 137), so the six citations of them in `CONTRIBUTING.md` and `docs/pages/contributing-llm.md` are re-anchored here rather than in the documentation commit: they are this commit's own line move. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
| if in_block: | ||
| found = line.find("*/", at) | ||
| if found < 0: | ||
| at = end |
CTest spells its closing summary two ways. CMake 3.28 -- ubuntu-24.04's apt cmake, which the kernel-only CI job runs -- always prints the failed count: "100% tests passed, 0 tests failed out of 4". CMake 4.x drops the clause when nothing failed: "100% tests passed out of 4". The row matched the second literally, so it passed on the local macOS host (cmake 4.4.0) and failed on every Linux runner. expectation corrected: the literal '100% tests passed out of 4' -> ci_verify.ctest_rows() counts, because that string is the CMake >= 4 wording only. ctest_rows is the parser the CI test floor already reads this same line with, and it accepts both wordings; reusing it keeps one reader of the line instead of a second copy of the regex. CTestSummarySpelling pins both wordings as verbatim fixtures of the same four rows -- one captured on cmake 3.28.3, one on cmake 4.4.0 -- so either host covers the reader for both, without a Linux host. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
native_continuation_hash() is not portable. It folds the run's timezone identity, and hash_tz_identity folds the host's zoneinfo ROOT PATH plus the tz resource paths under it: tzdir_canonical() resolves /var/db/timezone/zoneinfo on Darwin -- which realpaths through the installed tzdata release, e.g. /private/var/db/timezone/tz/2026c.1.0/zoneinfo -- and /usr/share/zoneinfo, or $TZDIR, under glibc. So the same kernel over the same bars, booking the same rows at the same prices, answers a different digest per host. Measured: the two controls answer 7148617029230866814 / 1059492877796320724 on Darwin and 3087945503312965943 / 5990633458242713781 on Linux (both aarch64, so it is not the ISA), and on ONE Linux host copying /usr/share/zoneinfo elsewhere and pointing $TZDIR at the copy moves every digest in this unit while every row, exit price and position stays put. expectation corrected: kControlContinuationA/B == the control's digest -> same-host properties of the absent seed, because a continuation digest folds a host path and cannot be a cross-platform literal. The removed values stay in the note above the cases as the lane's recorded pre-field measurement (exec/E14-probes/fail_before.cpp on the base 9f0ea24c), which is what carries "absent hashes exactly as before" for the host that measured it. What the literals were pinning is now pinned as properties, in a new case: the two spellings of absent -- a request that never writes Trail::best_seed and one that writes std::nullopt -- answer one digest; the digest is the run's, not the process's; and a seed the run's own behaviour ignores (1.00, under case A's arm print, so the ride still starts at the print and nothing books) still moves it, which is what makes the seed request state rather than an outcome. The seeded cases now compare against their own control's digest instead of a literal. The printed digest lines stay: they are the cross-platform measurement, and lane E17 needs them. ADR-0001's covering-test cell said the controls carried the pre-field digests; it now says what they carry and why. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
luisleo526
marked this pull request as ready for review
September 22, 2026 02:31
luisleo526
added a commit
that referenced
this pull request
Sep 26, 2026
…blings its leg had (R5 lane INT26, round 2: the gate sweep's leaver) INT26's gate sweep (exp-r5-int26-20260926 against rebaseline e69aa1f7) refused the merge on one leaver, scrapper:data-BINANCE-BTCUSDT/standard/vasudevshenoy-manoj-betrayed-me (BTCUSDT 15m, process_orders_on_close, pyramiding 0; stop entries 'Buy'/'Sell' with strategy.exit stop/limit brackets, a pending entry cancelled when the next bar does not trigger it): 821 of TradingView's 821 trades on main 0d76a09, 820 on ebe6748 (canonical 100 -> 99.9). Bisect (the probe's own feed, spark, one build per pick boundary): a6e6a00 and 2922ade book 821, e1d7a98 -- R5 lane PAR-ORDERS, H-MEASURE Finding 6b, a process_orders_on_close stop entry its placing close already reached fills at that close -- books 820, and so does every later pick. Mechanism, on 2025-08-03 10:00 UTC. The 09:45 close cancels its pending 'Buy', places it again (stop 113689.29) and re-issues 'Buy Exit' (limit 113771.41); the re-issued legs keep their older book slot, so on the 10:00 bar the pre-script drain settles the limit on its parent's touch bar -- after that bar's receipt read. The 10:00 bar closes at its high 113771.47; its body cancels 'Buy' again and places it at that high with a fresh 'Buy Exit' (limit 114004.53), and since e1d7a98 the close pass fills that re-entry at the close, as TradingView does (#274), so the fresh legs are working when 10:15 opens. The first limit's terminal receipt is read only then, and observe_terminal_receipts' sibling cancel withdrew every working leg of the same exit id, from_entry and origin -- the fresh bracket's included, both legs at origin 0. The re-entry ran on to the 15:15 session close and swallowed TradingView's #275 (11:45). Before e1d7a98 the re-entry rested to 10:15, its legs still staged, so the late read found no sibling to take. The pick is right; the sibling cancel was not. Fix, adapter only: cancel_bracket_siblings takes the ordinal the terminal receipt executed at when the observer reads it, and skips a matching leg of a later strategy.exit call (another command_sequence) that the kernel accepted after that ordinal (its Birth::acceptance_ordinal, read through native_working_requests() only when such a leg is among the matches): that leg is the same id's later order. A leg of the filled leg's own call is its sibling however late it was accepted -- the drain submits a bracket's legs one at a time, executing each, so a stop can be accepted after its limit filled -- and goes as before (a review of the first form, which skipped every leg accepted after the fill, found that case). The close passes that call it synchronously pass nothing and are unchanged. No state is added. The probe (spark, generated.cpp of codegen a4259656, the lane feed 6b54c44a): 821 trades, canonical rubric excellent, countAbsDelta 0 (820 / strong before). TradingView's #273-#275 exact: 10:00 113689.29 -> 10:00 113771.41; 10:00 113771.47 -> 11:00 114004.53; 11:45 114005.84 -> 15:15 113844.70. 819 of 821 trades exact to the minute and cent on both legs (main: 771; the two others, #1 and #3, differ on main too). Witness: tests/test_pooc_stop_reentry_bracket_tapes.cpp replays three `lab tv --no-note` tapes of synthetic scripts of the same shape on NYSE:F 15m (tests/fixtures/pooc_stop_reentry_bracket; tv_trades e0447b53 / b7e89ce1 / efbd25e3): three flat signal closes that each cancel and place again a stop entry with its bracket, 10c beyond the close, then 1c (the next bar fills the entry and then the limit and closes flat), then at that close (filled there), long on bars 39-41 and short on bars 44-46; TradingView books the same four rows on the chart and under the magnifier. Every row is compared field by field, with the end book and its working bracket legs. - int26-reentry-bracket-chart: all four rows, flat; the short is the leaver's shape. - int26-reentry-bracket-mag records a divergence the fix does not touch (the same before and after): there the drain leaves the short's retained limit to the flush below the body, whose book still holds the short, so the re-entry is never placed -- rows 1-3 exact, both books flat. - int26-reentry-noexit-chart, the control: the short's re-entry places no strategy.exit (TradingView's row 4 still open at the end); the short bracket's stop, accepted after its limit filled, must still be withdrawn. Fail-before: against ebe6748 2 of 27 checks fail (the chart tape: 3 rows against 4, short 100 against flat); against the first form of the rule 1 of 28 (the control's stop left working); after, 28 of 28. Floors: one source-bound row. RELEASE_MIN_TESTS 702 -> 703, EXCLUDED_REGISTERED_MIN 683/683/692 -> 684/684/693 (and test_ci_verify's expectation, docs/ci.md). KERNEL_MIN_TESTS unchanged. docs/ci.md's sentence "The PR registration floors at INT26 are 683 for Debug and sanitizers and 692 for native" is rewritten with the new counts. Docs: the design page's PAR-ORDERS paragraph records the rule and the magnified divergence; the CHANGELOG 1.0.0 entry's process_orders_on_close item adds it for a user. Every doc anchor carried onto this tree by the exact line map from ebe6748 (135 moved); the three citations of ci_verify.py's RELEASE_MIN_TESTS line (CONTRIBUTING.md, contributing-llm.md twice) set by hand, 390 -> 393. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
luisleo526
added a commit
that referenced
this pull request
Oct 29, 2026
…nnel failure codes, live runner report checkpoints (#637) Engine changes for 1.9.0. ## Members - Closed-trade text reads: compare closed-trade text in place, read sealed rows, and cheapen the read hit path; the owning text read is left to the compiler's inlining. - Build: the shared window-plan library links on macOS. - Run quota contract: host ledger, wire format and typed failure codes, with the ownership manifest naming their release. - Failure codes for the planned supervised report channel. - Live runner: native report checkpoints and the carried report accumulator; the webhook worker gets a dedicated curl-owner thread so delivery progress does not wait for the ledger; documentation corrections after review. - The run-failure registry, header and catalog diff are regenerated by the repository's generator. The ten failure codes added in 1.9.0 are numbered 70 to 79 in name order (the registry count is 80); none of these numbers was released before. - The allocation-bound inventory records the run-quota source sites. ## Sentences rewritten on purpose In docs/pages/run-failure-codes.md, "The C++ registry also contains one unnamed `none` sentinel, giving 70 registry entries." (introduced by 91353de) is replaced by the catalog count paragraph (79 named codes, 80 registry entries). In docs/live-ledger-budget.md, "Optional `extra_payload_bytes` margin supplied by another writer; zero for the current writers." and "The optional extra-payload margin is additive; a writer must still put its SQL changes in the admitted transaction." (both introduced by a991b4e) are rewritten because the margin now has a defined meaning. ## Verification Release, debug, kernel and sanitizer builds with their full test suites, the native live suites, the corpus subset and parity all pass on this exact head. Each member was reviewed independently. Pairs with the code generator change in pineforge-codegen-oss #273, which merges after this one. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
R5 follow-up engine wave D: the fifteen gap lanes E1–E15 that fulfil what the two claimed-vs-actual audits, the docs lanes and the executed walk-throughs found, integrated onto the docs-wave main (9b50973). 53 lane commits + 3 integration commits (unions the lanes could not take, doc re-anchoring and citation fixes, floors from the rows that ran) + 2 test-only fixes for the Linux CI runners (INT13b: the receipt-skip row reads CTest's summary through the shared parser on both CMake wordings; the E14 witness pins its absent seed as same-host properties because
native_continuation_hashfolds the host's zoneinfo path — a finding for the next wave).validate_levelschange), witness rowKernelRecordedAtHostMarksmargin_check_fx,marked_equity_at,liquidation_level)-UNDEBUG, stale-binaries inputs,check_abi_receipt_skips.py --preparesource_trail_arm_level), settled by six NYSE:F TradingView tapesdeclare_opened_lot_entry_bar_maskseampf_native_lookahead_e,pf_native_gaps_e), layouts pinnedBracketLegOutcomewith state, result and reasonsource_one_shot_reach_tick,source_trail_reached_at), eight tapespreceding_exit_path_prefixfold deleted with a type witnessstrategy_native_declare_opened_lot_entry_bar_mask_v1, C API 33 symbols)OrderBook::submit_or_replace_outcome/cancel_outcomesurface the kernel's own results; old spellings are projectionspf_native_working_v1additive tail: a C caller tells an absent trail arm from an arm at 0.0; base-length callers still servedTrail::best_seed(rule-2 ruling row): the running best starts where the host says; C fourth request tail; adapter names each trail's activation; two shallow-next-bar tapesPathOrderScopedeleted;@host-seamcensus of base-class seamsEXPECTED_NATIVE_C_APIduplicate deleted, Doxygen cross-references fixed); doc re-anchoring + citation corrections (money-residue span 918-952); floors from the rows the integrated tree runsEpochs unchanged (engine_script_run_v18, native_run_spec_v3, native_order_v6, PF_NATIVE_API_VERSION 1, PF_ABI_VERSION 4). Corpus pin 442d497 untouched.
Evidence
mainnever starts) — the Linux sanitizers CI job is the gate; kernel residuals 0 findings; rulings guard OK; C surface 55 members / 47 spelled / 8 excluded + 3 base-class seams, 33 C symbols; twin parity OK; doc anchors bad=0, lint 0, coverage 131/131, design inventory 0; Doxygen 0 warnings; parity 312/312 (excellent=311, anomaly=1) + subset 30/30.exp-r5-int13b-wave-d-20260922on this head (6da97ac): executionpineforge-experiment-run-l4q9z, 72/72 cases, 0 failed; 4190 graded, 0 coverage lost; offline band1 +0/−0 band2 +0/−0 (score 0, hard regressions 0); pr_gatepineforge-pr-gate-gv72b→target.not-positive(neutral, accepted at net 0). The same result was measured on 9392273 (exp-r5-int13-wave-d-20260922, executionpnz7p) before the two Linux-CI test fixes (9200167, 6da97ac: tests/scripts only, src/include byte-identical).The corpus cannot exercise E3/E5/E9/E14/E15's paths (on-grid 0.01 feed, no margin regime, AUTO order only); the population sweep above is their gate.
Merge with rebase.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Lg1KP7RXkwcEP2wFxXooy9