Repository navigation
R5 wave E: nine follow-up lanes E16–E25 integrated (INT14) - #275
Merged
Merged
Conversation
…ne bit NativeStrategyHost::declare_timeframe_subscriptions and declare_auxiliary_feed returned a bare bool while their own comments enumerate distinct refusals: a call made outside on_native_run_begin, and a list or feed this run's input timeframe would refuse -- the same validation configure_native applies, which answers a typed NativeSetupResult for the identical judgement. A host could not tell "wrong callback" from "invalid list", and never saw the reason the setup path would have given. declare_timeframe_subscriptions_result and declare_auxiliary_feed_result answer that NativeSetupResult. No parallel vocabulary (lane E12 ruling 1): the status is NativeSetupStatus, the refusal is NativeRunSpecValidation -- the validation function's own first error and field -- and the one word added is NativeRunSpecError::WrongPhase, appended so no enumerator value moves, for the call that judged no value at all. That is exactly the shape NativeFxCurveError::WrongPhase already has for configure_native_fx_curve. Both bare bools stay, exact signature and exact answer, as `... .status == NativeSetupStatus::Applied`. Kernel ruling: generic capability only. The kernel gains no Pine or TradingView vocabulary; the adapter's one caller (src/source/pine_strategy_host.cpp:1475) keeps the bool spelling and its outcomes are unchanged by construction. No epoch decision: additive host members and an appended enumerator, no layout, no hash, no C surface, no behaviour. Witness first, against the unchanged header: tests/test_native_htf_subscriptions.cpp:86:30: error: use of undeclared identifier 'declare_timeframe_subscriptions_result'; did you mean 'declare_timeframe_subscriptions'? tests/test_native_htf_subscriptions.cpp:431:71: error: no member named 'WrongPhase' in 'pineforge::NativeRunSpecError' tests/test_native_auxiliary_feed.cpp:553:29: error: use of undeclared identifier 'declare_timeframe_subscriptions_result'; did you mean 'declare_timeframe_subscriptions'? After: native HTF subscriptions 490 checks / 0 failures, was 472; and test_native_auxiliary_feed 624 checks / 0 failures, was 603 -- both counts measured by building this commit's parent's own TUs against the new library, where they also still pass, which is the old spellings' witness. No ctest row added: both witnesses joined their existing TU. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit ee5fda8d06bd8f78b6e7047f8ea4e414b98340c8)
Its comment says the call is "Refused by name, changing nothing and without failing the host" and then names four causes -- bars out of order or not after the feed's last bar, a bar with invalid OHLCV, a bar that opened inside an already-accepted input period, plus the two legality refusals (not realtime, no declared feed). None of those names was carried by a value: the call answered one bit, and the names existed only as last_error() presentation text, which is not a contract a host can branch on. append_auxiliary_bars_result answers NativeAuxiliaryAppendResult: the shared NativeSetupStatus, a NativeAuxiliaryAppendError, and the index of the bar OF THAT CALL it stopped on -- the shape NativeFxCurveSetupResult already has (a status beside an error and the index of the element that failed). Ruling: this is NOT a NativeRunSpecError. Lane E12's ruling 1 forbids a parallel vocabulary, and the reuse test is "if it fits exactly". It does not: appended bars are a live input, not a staged spec value, so no enumerator here is a run-spec field and four of the causes (NotRealtime, NoAuxiliaryFeed, InvalidBarArray, InputPeriodAlreadyAccepted) have no run-spec spelling at all. What IS reused is NativeSetupStatus, and every error word is the one the call's own contract already prints. Two refusals the bool could not even reach are now separate: a host that had ALREADY latched its durable failure (HostFailed) and a call made from inside a callback or an in-flight input (Reentrant), which is itself the Contract failure that latches one. admit_public_stream_input folded both into false. Kernel ruling: generic capability only; no Pine or TradingView vocabulary. No epoch decision: an additive host member and two additive types, no layout, no hash, no C surface, no behaviour. The bare bool stays, exact signature and exact answer, as `... .status == NativeSetupStatus::Applied`. Witness first, against the unchanged header: tests/test_native_auxiliary_feed_stream.cpp:180:35: error: no member named 'append_auxiliary_bars_result' in '(anonymous namespace)::FeedHost'; did you mean 'append_auxiliary_bars'? tests/test_native_auxiliary_feed_stream.cpp:182:32: error: use of undeclared identifier 'NativeAuxiliaryAppendError' After: test_native_auxiliary_feed_stream 815 checks / 0 failures, was 787 -- measured by building this commit's parent's own TU against the new library, where it also still passes. No ctest row added. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 14e9a1ed4ec766ce94980d32078e34daafcc6848)
strategy_native_replace_v1 took only `uint64_t* successor`, where strategy_native_submit_v1 has always taken `uint32_t* reject`. The typed status was there -- PF_NATIVE_OK / E_REJECTED / E_NOT_WORKING / E_INVALID_TARGET / E_STATE is ReplaceStatus plus legality -- but ReplaceResult::reason was dropped, so a C host learned "rejected" without the RequestRejectReason that both the C++ replace and the C submit surface. strategy_native_replace_ext_v1 is that same call with submit's own `reject` parameter. Two rulings decided its shape from the header's own precedents: 1. The reason belongs AT THE CALL, not in a query. submit writes it to an out-parameter; there is no strategy_native_submit_reason_v1 reading "the last one", and cancel has no reason symbol at all because CancelStatus IS the reason (lane E12 ruling 2). A "read the last replace's reason" query would also have to retain that reason on the host, and every durable state added here is hashed -- an epoch decision for a number the caller can be handed synchronously. 2. A second symbol, not a wider signature. The header's STABILITY rule is that within a major version the signatures never change, which is exactly why strategy_configure_native_ext_v1 exists beside strategy_configure_native_v1. strategy_native_replace_v1 keeps its signature and its answers and is now literally this call with `reject` NULL, so no existing caller sees anything move. No POD grew and no enum gained a value, so there is no size prefix to bump and no epoch decision: PF_NATIVE_API_VERSION stays 1 and the frozen v1 header fixture (tests/fixtures/native_c_api/v1) is untouched and still passes. The kernel gains nothing: this is the C layer projecting a ReplaceResult it already had. C API SOP, all of it: the declaration and doxygen in native_c_api.h, the COVERAGE row for `replace` naming both spellings, the implementation in src/native_c_host.cpp, EXPECTED_NATIVE_C_API plus its declaration and implementation counts (33 -> 34) in scripts/check_c_abi_runtime.py, the README symbol row, and the C scenario. No ctypes harness calls replace. Witness first, against the unchanged header: tests/test_native_c_api.c:841:19: error: call to undeclared function 'strategy_native_replace_ext_v1'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] tests/test_native_c_api.c:841:19: note: did you mean 'strategy_native_replace_v1'? After: test_native_c_api ok, test_native_c_api_frozen_header ok. The scenario pins two reasons -- InvalidQuantity, which is 0, from a 0xffffffff sentinel so the write itself is proved, and InvalidTrigger, which is not 0, so the value is the reason and not a zeroing -- that neither refusal retires the request it named, that an accepted replace leaves `reject` untouched and writes the successor, and that the old spelling still answers PF_NATIVE_E_REJECTED for the same request. No ctest row added. python3 scripts/check_c_abi_runtime.py -> exit 0 python3 scripts/check_native_c_api_surface.py -> exit 0 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 7b1dd895b466d92bf233282e8eef8a9c9fd91aca)
`native_continuation_hash()` folded the resolved timezone identity by its PATHS: `zoneinfo_root` and every entry of `resource_paths`, absolute paths of the machine that ran it -- `/usr/share/zoneinfo` or `$TZDIR` under glibc, the realpath of `/var/db/timezone/zoneinfo` on Darwin, which carries the installed tzdata release (`/private/var/db/timezone/tz/2026c.1.0/zoneinfo`). One binary on one host therefore answered two digests for one run with nothing but `$TZDIR` between them, and no two hosts ever agreed: the digest could not be a stream-resume token compared across hosts, a CI baseline or a cross-repo constant, while the run it named booked identical trades on both. Ruling (ADR-0001 rule 2, "a digest names the run's inputs"): a filesystem path is not an input of a run. What the zone contributes is its identity -- the kind of source it is, the definition it resolved to, and the CONTENT of the files the resolver read. `TimezoneIdentityDescriptor::resource_digest` is that content (FNV-1a 64 over the resource count, then each file's byte length and bytes, seeded with the accumulator's own offset basis), computed at identity time where the files are known readable, so nothing downstream opens a file and `continuation_hash()` stays allocation- and IO-free. `hash_tz_identity` folds it in place of the two path members, which stay on the descriptor as diagnostics. The mechanism is generic: the same fold serves any host on any libc, no spec field is added, and an unreadable named resource still refuses the descriptor rather than hashing a hole. Witness first: `tests/test_native_continuation_portable.cpp` (kernel-only). Fail-before on the base e9ad37d -- first diagnostic `error: no member named 'resource_digest' in 'pineforge::native_calendar::TimezoneIdentityDescriptor'`; and with the path fold in place the probe `exec/E23-probes/fail_before.cpp` printed, on one glibc host, continuation=1728220444820265975 under `/usr/share/zoneinfo` against continuation=232630274038263359 under a `$TZDIR` copy of the same bytes (same rows=1, same position). After the change the same binary answers one digest under both roots, a tree whose zone file carries another zone's rules answers a different one, and the descriptor's digest equals a reference FNV written inside the test over the same bytes (the shape `test_native_live_pending_order_mirror_l4d` uses for `id_hash64`). No digest is pinned as a literal: a literal would pin one tzdata release. Cross-host, the same binary source on two hosts with tzdata 2026c: macOS arm64 (root /private/var/db/timezone/tz/2026c.1.0/zoneinfo) UTC resource_digest=7122602514472708710 continuation=12744783684690476045 America/New_York resource_digest=1284284635758775892 continuation=11995699696365935746 Linux aarch64 (root /usr/share/zoneinfo) UTC resource_digest=7122602514472708710 continuation=12744783684690476045 America/New_York resource_digest=1284284635758775892 continuation=11995699696365935746 No epoch is bumped: `native-consumer/v7`, `native-calendar/v1` and `native_calendar_v2` are unchanged, and no frozen fixture pins a continuation value (every test that compares one does so between runs of one process). The recipe change does move every established digest, which is an epoch decision for the user, not for this lane -- reported, not taken. Adapter outcomes are untouched: trades never read the digest, and the whole corpus stays byte-identical at gitlink 442d497 (312/312, excellent=311, anomaly=1). Documentation that described the old fold -- the page's continuation paragraph, the two published header comments, ADR-0001's reproducibility bullet and its E14 row, and ten test comments that justified pinning `native_run_spec_digest` instead -- now says what is true: portable across hosts, still moving with the installed tzdata release. Anchors the +6 line shift in `native_execution_consumer.cpp` moved are re-anchored. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 04f2b09106b9824346bc7bf482c9668973ef406a)
`test_native_calendar` resolves the backward zone links `US/Eastern`, `Japan` and `GB`, and noble split those out of `tzdata` into `tzdata-legacy`; without the package eleven checks of that row fail. GitHub's `ubuntu-24.04` image happens to ship it, which is why CI never said so -- a bare noble host does not. Both places that state the dependency now name it: the three explicit apt lines of `.github/workflows/ci.yml` (build matrix, sanitizers, kernel-only), with the reason beside the first, and CONTRIBUTING's build prerequisites. Measured on the lane's Linux host (Ubuntu 24.04, tzdata 2026c-0ubuntu0.24.04.1) where `tzdata-legacy` is installed and `test_native_calendar` passes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 88c806f46f4756b7f86fff3ca5b49af2cd5fb394)
E23 replaced what `hash_tz_identity` folds: the timezone identity now enters the continuation digest as `TimezoneIdentityDescriptor::resource_digest`, an FNV-1a over the zone files the resolver read, in place of `zoneinfo_root` and `resource_paths`, which named this machine rather than the run. That is a change of recipe, not of inputs, so every established continuation value and every `broker_state_hash` that wraps one moves. The owner ruled (2026-09-22) that the private consumer identity must say so. `kNativeConsumerSemanticVersion` becomes `native-consumer/v8` in `include/pineforge/market_driver.hpp`; the literal pin in `scripts/check_native_cpp_versions.py` and the rejection pair in its self-test `scripts/test_native_cpp_versions.py` follow it, and `docs/pages/abi-stability.md` names v8 in both paragraphs that state the current identity plus the reason for the bump. The marker is itself folded into the digest, so the bump moves every continuation value a second time; nothing in the tree pins one, which is what E23 measured. The broker/stream fingerprint `pineforge-broker-state/v18` stays: it is open and unshipped. `docs/native-refactor-progress.md`'s "Current R4-C epoch baseline" section keeps `native-consumer/v7` beside `engine_script_run_v17`: that section is deliberately historical and carries the `<!-- verified HEAD -->` marker for exactly that. Glue for E23 finding 5: `.github/workflows/native-live.yml` installs `tzdata-legacy` too. It runs `test_native_calendar`, which resolves the backward links noble split out of `tzdata`, and E23 only reached `ci.yml`'s three apt lines and `CONTRIBUTING.md`. Proof: `python3 scripts/check_native_cpp_versions.py` -> ownership verified, rc=0; `python3 scripts/test_native_cpp_versions.py` -> Ran 56 tests, OK. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…, item 1)
Closes E15 finding 1. `PineStrategyHost::closed_lot_excursion` asked
`internal::bar_path_uses_high_first`, which reads the sampler's thread-local
override. Its only installer is `NativePathOrderScope`, which wraps
`sample_price_path` alone and is never installed around a host callback, so
this arithmetic answered AUTO (the open-proximity rule) under every declared
`NativeRunSpec::path_order`. E15 had already moved the kernel's
`BacktestEngine::declare_opened_lot_entry_bar_mask` to the declared order, so
a same-bar entry and exit under a forced order combined a forced-frame mask
with an AUTO-frame path position.
Both folds in that function now resolve the leg order once, through
`NativeExecutionConsumer::path_high_first` — the seam E15 exposed, reached
with the `const_cast<IExecutionConsumer&>(execution_consumer())` spelling this
file already uses for the two receipt high-water readers. Three reads move:
the margin-call prefix sample's walk, the priced fill's own
`first_touch_position` (the two-argument form re-derived the order from the
override, so the fill's coordinate and the extremes it is compared against
were on different walks), and the high/low path positions.
Single source of truth: the consumer's resolution, not the adapter's own
`PineExecutionAdapter::source_path_uses_high_first`. The mask bits this
arithmetic combines (`facts.entry_bar_{high,low}_masked`) are derived by
`declare_opened_lot_entry_bar_mask` from exactly this call, so reading it here
makes the mask and the arithmetic that completes it one resolution of one
field. `source_path_uses_high_first` reads `PineExecutionAdapter::path_order_`,
a second copy that `PineStrategyHost::begin` sets alongside the spec — in step
today, but a mirror of the authority rather than the authority.
Rulings:
- The kernel is untouched and stays TradingView-agnostic: no file outside
`src/source/` changes, and no new capability is added. The consumer method
read here is generic (the run's declared leg order over a bar) and already
existed.
- No C change, no epoch decision: no layout, hash field, default, C symbol or
`PF_NATIVE_API_VERSION` moves.
- Byte-identical under AUTO by construction: `path_uses_high_first(bar, Auto)`
is the same expression `bar_path_uses_high_first` returns when the override
is 0, which is what it always was inside a callback. The corpus declares no
non-AUTO order (E15 ruling 2), so whole-corpus identity holds.
- No existing test pin changes; the witness only adds rows.
Witness `tests/test_e19_excursion_path_order.cpp` — fail-before, on bars whose
forced order is the opposite of the open-proximity verdict:
forced-high-first-long-stopout L @100.00->95.00 mfe=0.000000 mae=5.000000 (want @100.00->95.00 mfe=15.000000 mae=5.000000)
FAIL tests/test_e19_excursion_path_order.cpp:136 near(t.max_runup, fav)
forced-low-first-short-stopout S @100.00->105.00 mfe=0.000000 mae=5.000000 (want @100.00->105.00 mfe=15.000000 mae=5.000000)
FAIL tests/test_e19_excursion_path_order.cpp:136 near(t.max_runup, fav)
auto-long-take-profit L @100.00->112.00 mfe=12.000000 mae=10.000000 (want @100.00->112.00 mfe=12.000000 mae=10.000000)
forced-high-first-margin-prefix fav=0.000000 adv=10.000000 (want fav=15.000000 adv=10.000000)
FAIL tests/test_e19_excursion_path_order.cpp:200 near(owned.favorable, 15.00)
auto-margin-prefix fav=0.000000 adv=10.000000 (want fav=0.000000 adv=10.000000)
test_e19_excursion_path_order: 24 passed, 3 failed
and after:
test_e19_excursion_path_order: 27 passed, 0 failed
The entry and exit prices are identical before and after in every row: the
matcher has always walked the declared order, so only the excursion columns
move. Both AUTO controls already passed before the change, which is the
default's byte-identity at test scope.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit dbdea1d75c8be7f11f54138c0d51bb92cb635cc1)
Closes E15 finding 3, which is real. `strategy_set_path_order`'s doc in
`include/pineforge/pineforge.h` (and its mirror above the definition in
`src/c_abi.cpp`, and §3.3 of `docs/pages/live-surface.md`) still described the
thread-local mechanism the setting had before it became a spec field. Two of
its claims are false against the code:
1. "the leg order every OHLC-path helper (`bar_path_uses_high_first` and
everything built on it) uses". `path_order_mode_` has exactly one reader,
`PineStrategyHost::begin`, which projects it into
`NativeRunSpec::path_order`; `bar_path_uses_high_first`'s thread-local
override is installed only by `NativePathOrderScope`, around the magnifier
sampler, from that same spec field. The list also missed the excursion
columns a source host reports, which lane E19 item 1 just moved onto the
declared order.
2. "Applies to run() only: a stream continued via strategy_stream_begin
dispatches its realtime ticks outside any run() and always sees AUTO." The
spec is projected at begin for a stream too (`stream_begin` reaches the
host's `begin` with `args.is_stream`), and a stream's script bars are
sealed through `seal_script` -> `deliver_confirmed_script`, which resolves
the order from `spec->path_order`. Measured on the `test_live_path_order`
fixture (a 99/101 bracket over an AUTO-low-first touch bar), driving the
touch bar through `stream_push_bar` instead of `run()`:
batch AUTO=99.00 HIGH_FIRST=101.00 LOW_FIRST=99.00
stream AUTO=99.00 HIGH_FIRST=101.00 LOW_FIRST=99.00
The true exemption is narrower and is now what the doc says: an OBSERVED
TICK is one price, not a modeled path, so it has no legs to order. Same
fixture, the same bar drawn by a tick tape that prints the low first:
ticks(low printed first) AUTO=98.50 HIGH_FIRST=98.50 LOW_FIRST=98.50
`include/pineforge/engine.hpp`'s own `set_path_order` doc was already correct
("projects it into NativeRunSpec::path_order at begin ... the native driver
walks the spec field") and is left alone.
Rulings: doc-only. No symbol, signature, behaviour, layout or default
changes, so this is not a C API change and needs no epoch decision; the
`src/c_abi.cpp` hunk is a comment. Both probes above are scratch programs
against the built library, not committed: the committed pin of the batch
column is `tests/test_live_path_order.cpp`, and the stream/tick columns pin
no new behaviour, only the sentence.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit f67a74a8df4b50598a7173f929cb0e21c70a5bbf)
…tem 3) Items 1 and 2 add 15 lines to `src/source/pine_strategy_host.cpp`, 10 to `include/pineforge/pineforge.h` and 4 to `src/c_abi.cpp`, so every documentation anchor below those hunks names the wrong line. This is `scripts/check_doc_anchors.py --fix` (which `scripts/ci_preflight.py` runs, so it is a gate, not hygiene), plus two anchors it declines to move on its own: - `scheduler_record_range_end` pine_strategy_host.cpp:1554 -> :1569. The checker refuses it as ambiguous (the name is on 3 lines: 420, 573, 1569); 1569 is the definition, which is what the row cites. - `(pine_strategy_host.cpp:1252-1284)` -> `:1267-1299`. A range with no symbol attached, so the checker only proves the lines exist. The new range is the same +15 shift, i.e. the same bytes the row pointed at before this lane. Line numbers only: no prose changes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 7fda3d576dce6cd9a8e620369fab2d5fec72d06f)
src/engine_run.cpp's anonymous namespace held three definitions and two
thread-locals, and nothing reaches any of them:
ScopedBirthContext, birth_context_owner, birth_context
a callback-origin guard that is never instantiated
validate_chart_bars
a chart-bar admission scan that is never called; the compiler says so:
"src/engine_run.cpp:55:6: warning: unused function
'validate_chart_bars' [-Wunused-function]"
reject_chart_bar
called only by validate_chart_bars
Being file-local, no other translation unit could reach them. Before this
commit `git grep -n -w -E 'validate_chart_bars|reject_chart_bar|
ScopedBirthContext|birth_context_owner|birth_context' -- . ':!*.tar'
':!corpus'` lists only their own 19 definition lines in this file; after it,
the same grep exits 1. The comment above the scan ("Each public run() calls
this once; run_tf_impl receives validated input") was false twice over:
run() never calls it, and run_tf_impl no longer exists.
The claim that chart bars ARE validated was re-checked before deleting, and
it holds elsewhere: every public entry -- run_simple, run_tf, run_rich and
both stream_begin legs (native_execution_consumer.cpp) -- calls
NativeExecutionConsumer::preflight_bars, which runs preflight_native_inputs
(market_driver.cpp) over the whole array before any state moves. It refuses
every rule the dead scan named: a negative count, a null array, non-finite
OHLC, a low above min(open, close) or a high below max(open, close), a
volume that is neither NaN nor finite >= 0 (the Pine adapter's
BatchStructuralBars tolerance is exactly the dead scan's predicate), a
timestamp that does not strictly increase, and an int64 delta overflow.
tests/test_bulk_preflight_l4d.cpp pins each of them on every public route.
Also deleted: the file's last comment, a section banner ("Full run()
overload with SymInfo, StrategyOverrides, and input injection") over an
empty section -- that overload is engine_consumer.cpp's.
Proof that the deletion is the whole change: the blank-normalized
comment-stripped diff of this file is exactly the anonymous namespace block,
and engine_run.cpp.o built with the tree's own Release flags is
byte-identical before and after (md5 5c5995d65ea985b5356b639d804aaf06): the
dead code emitted nothing. No other file changes.
Doc anchors: the deletion moved three citations of this file, which the
guard (scripts/check_doc_anchors.py) now re-checks. `reset_run_state()` at
`:141` -> `:85` (ADR-0001's residue table) and `fold_exit_trail_peak_`'s
run-start reset `:165` -> `:109` (native-feature-parity.md) are the same
lines, moved. The third, native-feature-parity.md's "live, e.g.
engine_run.cpp:254" example of the session_time API, already pointed into
get_input_int at the base; it now names the live call,
`pineforge::session_in_market` at `:178`.
Ruling: generic, kernel-only, no epoch -- nothing durable, hashed, frozen
or C-visible moved.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit d879d5728f4a00342d6350da0cf6056feed48903)
…R5 lane E20, item 2)
Lane E10 (finding 2) counted the ATTACHED comments that cite a function the
R4/R5 lowerings deleted: execute_market_exit, run_tf_impl, run_magnified_bar,
run_simple_bar_loop, run_aggregation_bar_loop, record_range_end_close_trades
and dispatch_bar. Re-derived on this base with `git grep -n -w` over src/ and
include/, the kernel and adapter-header sites are corrected here; each now
names the live mechanism, read from the tree:
engine.hpp Trade::open_at_end record_range_end_close_trades ->
NativeExecutionConsumer::append_open_position_report_rows, the one
producer of the report-only range-end rows
engine.hpp cached trade metrics execute_market_exit ->
record_close_trade, the only writer of the net/gross sums and counts
engine.hpp magnifier_volume_weighted_, set_magnifier_volume_weighted
run_magnified_bar -> NativeBeginArgs::magnifier_volume_weighted,
projected into the run's IntrabarPath volume_weighted, which
deliver_intrabar_script samples by (sample_price_path_volume_weighted)
engine.hpp set_broker_state_hash_recording
run_simple_bar_loop / run_aggregation_bar_loop (and the equally dead
stream_dispatch_script_bar in the same sentence) ->
record_script_report_point under KernelRecorded; a host that records
or marks its own report points appends its own row there
engine_orders.cpp reset_position_state_to_flat execute_market_exit ->
the native settlement commit (commit_prepared_native_settlement_stage)
and reset_run_state, its two kernel callers
engine_report.cpp fill_trades_section record_range_end_close_trades ->
append_open_position_report_rows
timeframe.cpp feed_ratio_mode run_aggregation_bar_loop -> the
consumer's order: on_native_input and subscription delivery precede
the input's aggregation into the chart bar
pine_strategy_host.hpp calling_open_latches_first dispatch_bar ->
PineScheduler::bar, which feeds the deferred slice once the bar is
published
pine_strategy_host.hpp apply_realtime_tail_horizon run_tf_impl ->
PineScheduler::run_begin's !needs_aggregation
(engine_run.cpp's run_tf_impl citation went with item 1's deletion.)
The remaining hits of the seven names are six src/source/*.cpp comments that
cite them AS HISTORY -- "the legacy run_tf_impl path", "ab9714be
pine_scheduler.cpp:1753 records after dispatch_bar" -- the frozen oracle the
adapter reproduces. A citation pinned to ab9714b, or labelled legacy, claims
nothing about this tree; they stay.
The three documents of the two live-runner tail flags (pineforge.h's
strategy_set_realtime_tail and strategy_set_probe_suppress_tail_logic,
c_abi.cpp's shim, the host's set_probe_suppress_tail_logic) scoped
themselves by the retired loops (dispatch_bar, run_simple_bar_loop,
run_magnified_bar) and listed seven more deleted functions
(flush_same_bar_close, process_margin_call, settle_dormant_bracket_reissues,
evaluate_max_intraday_loss_over_path, update_per_trade_extremes,
invoke_chart_on_bar, _push_source_series). A scope written in dead names
could not be kept, so each block is rewritten to what the live PineScheduler
does, measured with a scratch probe (not committed) over every driving
path, flag off / probe / tail, with and without calc_on_order_fills:
probe tail, script calls on 10 1m bars: run(bars,n) 9, run(1,1) 9,
run(1,1,magnifier) 9 -- the flag is honoured under the magnifier, not
a no-op; 15 1m -> 5m: 2 of 3 (the last COMPLETED script bar); 17 1m ->
5m: 2 of 3, the trailing partial bucket is never calculated at all
probe tail + calc_on_order_fills, entry filling on the last bar: the
close calculation is suppressed but the fill recalculation still runs
the script on the forming bar (10 calls, the last on bar 9)
probe tail, 100%-equity short, last bar high 105: the margin call is
booked ON the forming bar ("Margin call" @105 qty 3.809524 t=2000),
because PineStrategyHost::on_native_bar runs adapter_.on_bar_close
after the scheduler whether or not the script ran
realtime tail, session.islastbar on the last bar: run(bars,n) with 10
bars 1 -> 0 (it IS evaluated there: the timeframe is detected from the
bars), run(1,1) 1 -> 0, aggregation [111] -> [000] (no next script bar
is ever in hand, so the lookahead decides every aggregated bar)
stream_begin with either flag set: refused, "native stream cannot use
historical probe/tail overrides"
Comment-only, proved per file: the blank-normalized comment-stripped bytes
of engine.hpp, pineforge.h, pine_strategy_host.hpp, c_abi.cpp,
engine_orders.cpp, engine_report.cpp and timeframe.cpp are unchanged, and
all 51 objects of libpineforge.a, rebuilt with the tree's own flags, are
byte-identical to the base build.
Doc anchors: the header edits moved line numbers under the pages' symbol
claims. They are re-anchored by the exact line map of each edited file
(difflib over the base and edited copies), which resolves the ones
check_doc_anchors.py --fix reports as ambiguous; symbol-less anchors are
left as --fix leaves them -- several deliberately cite deleted spans (E6's
engine.hpp:918-952). check_doc_anchors: 1555 anchors, 0 bad.
Ruling: comment-only, generic; the kernel's comments name kernel mechanisms
and the adapter's name the adapter's. No epoch.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit acd9eca78f44eb6d485defcccfb097dc6620fbaf)
…R5 lane E20, item 3) Lane E13 (finding 2): native_c_api.h's STABILITY paragraph said "a later revision appends fields and raises the version constant; the size prefix keeps an old caller refused", and tests/fixtures/native_c_api/v1/README.md said the frozen caller fails "the moment a field is appended". Neither has been true since the first deliberately additive tail. At PF_NATIVE_API_VERSION 1 the live header has grown tails on pf_native_request_v1 (PF_NATIVE_REQUEST_V1_BASE_SIZE / _ANCHOR_SIZE / _SIZING_SIZE), pf_native_run_spec_ext_v1 (_BASE_SIZE / _RISK_SIZE / _POLICY_SIZE), pf_native_callbacks_v1 (_BASE_SIZE) and the pf_native_working_v1 readout (_BASE_SIZE), and the runtime accepts every published length -- the frozen caller in tests/test_native_c_api_frozen_header.cpp is exactly an old caller that keeps working. STABILITY now states the rule the header's own HARDENING RULES bullet already carries: an additive tail keeps the version constant, publishes the earlier lengths as *_SIZE constants the runtime accepts, and a readout is written only as far as the caller's length; any other layout change is a new revision that raises the version constant, and only there does the size prefix refuse an old caller. The fixture README says the two files are no longer identical, why the frozen caller still passes, and which changes would make it fail. The frozen fixture header itself is untouched (manifest sha256 f7484fa8... unchanged). Comment-only: native_c_api.h's blank-normalized comment-stripped bytes are unchanged. check_native_c_api_surface and check_c_abi_runtime exit 0. The paragraph grew five lines; 112 page anchors below it are re-anchored by the header's exact line map; check_doc_anchors: 1555 anchors, 0 bad. Ruling: documentation of the existing C contract, no C change, no epoch. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 24ac7d338e4bb9ce108444f7c721b1953f2cdab0)
…E20, item 4)
Lane E13 (finding 3): tests/test_native_c_api_frozen_header.cpp compiled
the frozen v1 header but never read a working row, so the old-length path
of the one readout that grew an additive tail (pf_native_working_v1's
trail_has_arm_price / reserved1, E13) was proved only by a C scenario that
spells the base length through the LIVE header's
PF_NATIVE_WORKING_V1_BASE_SIZE, plus a static_assert.
The frozen caller now does it itself: at its third calculation it rests a
buy limit far below every bar (TRANSACT +1 @ 50.0, label "frozen-rest"), at
the fourth it snapshots the book (strategy_native_working_len_v1) and reads
every row with the struct_size its own header compiled -- the frozen
sizeof, which is the runtime's base length -- into a row followed by a
32-byte 0xA5 guard band, and at the fifth it cancels the limit. It asserts:
no row refused (PF_NATIVE_E_STRUCT), no guard byte touched, and its resting
request read back as what it sent (struct_size, version, intent TRANSACT,
intent_value 1, trigger LIMIT, trigger_state LIMIT_READY, p1 50, owner
INDEPENDENT, label). The limit never fills and is cancelled before the
flatten, so the run's existing pins (one closed trade, events inside the
frozen tag range) are unchanged. The file header's claim that ANY appended
field fails these rows is corrected to the tail rule (item 3).
Fail-before, against two runtimes mutated in /tmp only (native_c_host.cpp
recompiled with build-on's own command, swapped into a copy of
libpineforge.a, linked with this test's object):
M1 (runtime serves only the grown length):
FAIL the current runtime refused the frozen caller's working row (the
readout's base length is no longer served)
FAIL the frozen reader did not find its resting request
FAIL the frozen reader's working row does not describe its resting limit
test_native_c_api_frozen_header: 3 failure(s)
M2 (runtime copies its whole row whatever length was sent):
FAIL the current runtime wrote past the frozen caller's working row
test_native_c_api_frozen_header: 1 failure(s)
Against the real runtime: "test_native_c_api_frozen_header: ok".
Test-only: the frozen fixture header and its manifest are untouched, no
ctest row is added (566 registered before and after), no pin moves, and the
file is not in the twin-parity inventory (check_twin_parity OK).
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 174df121f032820931abfaf2b23e1cb901630cef)
…ontributor (R5 lane E20, item 6)
INT13 (finding 10): native_run_spec.hpp's `lookahead` doc said a
lookahead_off bucket "is delivered when its last contributing input bar is
accepted", native_host.hpp said NativeTimeframeBarContext::delivered_at_ms
is "the bucket's last contributing bar under lookahead_off", and
native-engine.md's delivery list said the same. That holds for a Confirmed
bucket only. A bucket only a later input reveals as complete -- a
session-clipped bucket, a hole over its last slot -- is handed back by the
boundary emission of the next period's FIRST input: the consumer's
input-built pump passes that input's own timestamp as delivered_at_ms with
NativeCompletionKind::LazyComplete (native_execution_consumer.cpp,
deliver_timeframe_bar's call sites), an input that contributes nothing to
the bucket it delivers. native-engine.md's chronology section already said
so ("seal(k) -> deliver(i+1) -> calc(i+1)"); the three sentences now agree
with it, and so does the design page's delivery rule (F).
Are the two headers free to change? Read before touching them: no fixture
pins their live bytes -- tests/fixtures/native_cpp_abi/* and
settlement_cpp_abi/* authenticate HISTORICAL closures (ab9714b, e7cdf05,
f736676, c3ed455, 262a280, e7d023d), and neither file's sha256 or blob id
appears anywhere under tests/fixtures or scripts; check_native_cpp_versions
compares comment-stripped text (clean()) and still passes, as does
check_settlement_cpp_abi's token audit, which reads declarations. So the
headers are corrected, not just the page.
Comment-only: both headers' blank-normalized comment-stripped bytes are
unchanged. The two header paragraphs and the page list grew 2+3+3 lines;
234 page anchors under them are re-anchored by the exact line map;
check_doc_anchors: 1555 anchors, 0 bad; check_doc_lint clean.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit b1612c980a7494313189a3ed874795fe695be72d)
…res (R5 lane E20, item 7) INT13 (finding 11): docs/design/native-feature-parity.md still said, in three places, that "the adapter stays HostRecorded" and declared a two-value NativeReportPolicy. Re-derived from the tree: include/pineforge/native_run_spec.hpp:60-64 declares HostRecorded = 0, KernelRecorded = 1 and KernelRecordedAtHostMarks = 2 (default HostRecorded, :580), and the Pine adapter's project() sets `spec.report_policy = NativeReportPolicy::KernelRecordedAtHostMarks` (pine_adapter.cpp:1553) -- since R5 lane R1 (04330d4, "the kernel owns equity recording; the Pine host only selects the policy and marks its cadence"). The page's own section 3.7 already said so; its lane table, risk table and API appendix did not. 3.3 lane table, L2 row the API column keeps what L2 proposed and names the value R1 appended; the neutrality column says the adapter stayed HostRecorded at L2 and has declared KernelRecordedAtHostMarks since R1 -- still one recorder, no double recording, fill_report bytes unchanged on the corpus risk E6 row containment: the adapter declares KernelRecordedAtHostMarks, one recorder at its own marks appendix A.1 the enum carries all three values, and a bullet states what KernelRecordedAtHostMarks does (the recording at the host's marks, mark_script_report_point; never on the consumer's own initiative, so the continuation identity stays where HostRecorded leaves it) and that the adapter pairs it with report_open_position_at_end == false The two new citations carry their symbol, so the anchor guard checks them (native_run_spec.hpp:63, pine_adapter.cpp:1553 twice). check_doc_anchors: 1558 anchors (1555 + these 3), 0 bad; check_doc_lint clean; check_design_inventory 0 findings. Docs only. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 3bf80ca2afd72e09240d0528dd2238bd9866d7f0)
… E20, item 8)
INT13 (finding 12) / E4 (findings 3, 5): lane E4 gave the runner's two
MODULE builds of examples/native sources (native_market_example,
native_selected_example) -UNDEBUG and made the examples-assert-live stage
check every compile of an examples/native source; two runner modules still
built with Release's NDEBUG -- native_live_example (runner/examples/
strategy.cpp) and native_live_parser_example (runner/examples/
demo_parser.cpp) -- and docs/pages/native-engine.md did not say the stage
covers the runner's modules.
runner/CMakeLists.txt now gives both -UNDEBUG after the build-type flags,
the way E4 did (a target option, emitted after CMAKE_CXX_FLAGS_RELEASE's
-DNDEBUG). The witness is ci_verify.py's own ndebug_defined(), run over a
scratch live-runner configure's compile_commands.json
(-DPINEFORGE_BUILD_LIVE_RUNNER=ON, source layer on):
before native_live_example runner/examples/strategy.cpp -> NDEBUG defined
native_live_parser_example runner/examples/demo_parser.cpp -> NDEBUG defined
runner-example compiles with NDEBUG: native_live_example,
native_live_parser_example (exit 1)
after both -> assert() live; runner-example compiles with NDEBUG: none (exit 0)
Behaviour-neutral, proved rather than argued: neither source holds an
assert(), no header in either include closure includes <cassert> or tests
NDEBUG, and both objects compiled with the exact before and after argv are
byte-identical (strategy.cpp.o c8b57b8defcd909490e38537d11dbb9b,
demo_parser.cpp.o 878c31d954801c660551ed93cff928aa).
The stage itself is unchanged: it reads examples/native sources only, so it
does not check these two, and native-engine.md says exactly that. The page
now also says the stage checks the runner's two examples/native MODULE
compiles wherever the runner is built -- beside the example_* executables
in the kernel profile, alone in the native profile, which builds no
example_* target. check_doc_anchors 1558/0 bad, check_doc_lint clean.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 840df1654deacc2479d80e99858f3b2b194f9a1d)
…s (R5 follow-up lane E16, item 1) The kernel's trailing stop was `best -/+ offset`, and when the offset is spelled in ticks acceptance resolves it to `ticks * NativeRunSpec::price_tick`. Both steps are exact arithmetic on inexact numbers, so the level can land one binary64 ULP off the ladder point the count names: 11.44 - 5 * 0.01 is 11.389999999999998792 against 11.390000000000000568, and a print that IS 11.39 then never reaches a stop the run put five ticks under 11.44. Lane E14 measured it on TradingView's own tape (NYSE:F, e14-f-long-shallow-next's seventh trade: TradingView books that exit, the engine ran to the timeout) and pinned the row as a recorded divergence; lane R7 had measured the same ULP from the other side (test_adapter_grid_relower's TrailUlp scenario, "14% of (best, offset) pairs") and read it as the price grid firing one bar early. Ruling: a KERNEL defect, not a TradingView reading, so the fix is generic and in the kernel. ADR-0001 rule 2 as amended by L14-C. (a) Mechanism: a stop that stands `ticks` ticks behind a running best is a ladder distance at every venue with a tick ladder; the rule reads the run's own declared `price_tick` -- data -- and names no platform. (b) Knob: the run has already decided this. It declared the ladder, and the host spelled the distance on it; a "raw subtraction or ladder point" switch would put a hash-visible choice on the surface for an answer the run already gave, so the calibrated default IS the contract. What it is not: no level is rounded ONTO the ladder. A level already on it is merely spelled without the ULP, so this is not design row B2's per-order-kind quantization mask -- the trail stop and the running best stay on the raw path. A best inside a tick cell, an offset that is not a whole number of ticks, and a run that declares no price tick keep the raw subtraction, bit for bit. And it is the LEVEL that moves, never the comparison. - native_matching::ladder_trail_stop derives the level by index arithmetic through the exactness rule R7 already ships (grid_exact_index: both binary64 spellings of a ladder value are that value), and grid_ladder_price spells the result at the outer of its two spellings on the side the stop is reached from, so a print that IS the point is inside the region under either. checked_trail_stop takes the run's ladder tick, defaulted to none. - The core kept a second spelling of the same rule, and a level it disagreed with the matcher on would refuse an activation the matcher just booked: WorkingRequestCore::trail_level_ok now delegates to the matcher's geometry and receives the run's ladder through ActivationGrid::ladder_tick, appended last and inactive at 0.0 like the other two members. - tests/test_native_trail_stop_ladder.cpp is the kernel-only witness, batch and stream: the sell case is E14's own 11.44 / 5 ticks against a low of exactly 11.39, the buy case its mirror (11.39 + 5 ticks against a high of 11.44), and three rows pin that a sub-tick best, a half-tick offset and a run with no declared tick keep the raw level bit for bit. Fail-before on this lane's base e9ad37d: "sell trail level: actual=11.389999999999998792 expected= 11.390000000000001", rows=0, position=1 (exec/E16-probes/fail-before.log). Two pinned expectations are corrected, each with its note in place: test_trail_activation_tick_reach's kLongShallowUlpResidual (the row asserted it DIFFERS; e14-f-long-shallow-next is now 7/7 at TradingView's bar and price) and test_adapter_grid_relower's TrailUlp row, re-harvested with -DPINEFORGE_R7_HARVEST from this tree -- its exit moves from bar 4 to bar 3 at the same price, and the other four scenarios came back byte-identical. No epoch: no durable state, no hashed field and no C surface changes. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 4973daaafef1afc7b3e5e6607ec325f9c2ed3c14)
…t put it in the kernel (R5 follow-up lane E16, item 2) - native-engine.md: a new paragraph beside Trail::best_seed's, and the price grid's "measured divergence" bullet corrected — the trail stop's ULP was a level the kernel spelled wrong on both paths, not the per-kind quantization it was read as, and the two paths now agree on that row. - native-feature-parity.md §3.6.2: an E16 paragraph closing E14's finding, and the B2 row's state says which part of it that closes and which stays open. The re-lowering trial's own counts are NOT re-measured here; the row says so. - ADR-0001, the amended rule 2's own account (table B): the ruling row, with both halves answered — mechanism (a ladder distance at any venue with a tick ladder, read from the run's declared price_tick) and knob (the run had already decided it by declaring the ladder and spelling the distance on it). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit cc9a4dd2bf23b2c616c1df5620295af3dc465cf8)
…up lane E16, item 3) Item 1 inserted lines in src/native_execution_consumer.cpp (the activation grid's ladder, +4 from line 108; NativeExecutionConsumer::ladder_tick, +5 from 1314; one line at each of three call sites) and item 2 inserted a paragraph in docs/pages/native-engine.md, so every file:line citation below them drifted by the same amount. scripts/check_doc_anchors.py --fix took the unambiguous ones; the rest were shifted by their region's own delta (+4 / +9 / +10 / +12 in the consumer, +20 in native-engine.md) to the same symbol the anchor named. No prose changed: the diff outside item 2's paragraphs is line numbers only. check_doc_anchors: 1555 anchors, every one resolves to its symbol. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 1d8a0246fd469b909a6c733d963ef225421c6073)
…E21, item 1)
Ruling: an opening admission converts at the instant its own point names,
for every FX-bearing term of the test -- the requirement's resulting
notional, the marked equity it is compared with, and the ticket netted from
that equity (a percent fee's ticket is FX-bearing too). This is E3's ruling,
"the conversion instant is part of the margin contract", applied to the
margin model's other half. It is generic: a declared FX curve, an initial
margin and a command point; no script language and no broker.
The opening gate has two sites. The CANDIDATE gate already kept the rule:
consume_matched_request presents the matching cursor's instant before its
inspection and gate. The PLACEMENT gate -- run when a
Sized{SizeTime::AtAcceptance} is accepted, refusing with
RequestRejectReason::PlacementAdmission -- did not. It read the presented
bar clock while the freeze it gates converted its units at the acceptance
point's rate, account_currency_fx_at(point->decision.coordinate
.effective_time_ms). The freeze's own equity basis read the presented clock
as well, so an EquityFraction freeze divided an equity of one instant by the
rate of another; it now takes both at the acceptance point.
The defect's shape: invoke_applied_callback presents max(cursor,
decision_floor()), so an applied callback drained at its script bar's
calculation is presented that bar's close while its frame -- the acceptance
point of any command it submits -- is the opening print the fill landed on.
A curve step between the two made the placement gate measure the book at a
rate the account had not reached: over-refusing on a step up, and on a step
down admitting an opening the acceptance point could not fund.
Mechanism: make_command_context presents the acceptance point's instant for
its pure acceptance block -- the freeze, then the placement inspection and
gate -- and hands the caller's clock back on exit (a scoped restore,
exceptions included). It is not threaded as a rate, as E3 threaded the
margin checks, because two of the three terms are the engine's own
settlement inspection (inspect_native_settlement_stage's notional,
quote_execution_commissions' percent ticket), which convert where the engine
presents. Threading a rate there means re-plumbing the settlement stage every
fill goes through; restating the notional and the fee schedule in the
consumer would add exactly the kind of twin item 2 removes. E3's reason for
threading still holds: nothing inside the block calls out of the kernel
(sized_basis_units, marked, inspect_native_settlement_scoped and
admit_opening_inspect are pure and non-virtual), so no callback ever sees an
instant it did not see before, and the witness pins that the callback's
clock is unchanged once submit returns. It is the presentation
consume_matched_request already makes for the candidate gate.
make_command_context now takes the engine mutably; both callers hold it so.
Adapter: TradingView is not exposed, structurally. The adapter's margin
model declares initial_long/short = 0.0 (the maintenance-only spelling) and
it never sets initial_margin_fraction, so the gate's margin branch never
runs for it. Its core-sized default opening (default_sizing_intent) is a
CashValue basis -- no equity term -- and is withheld whenever the run stages
an FX series, so the one freeze it can reach converts under a single rate at
every instant. Corpus byte-identical by construction; the gate is run anyway.
No epoch: no layout, enumerator, durable state or hash input moves; the
presented instant is transient and restored. No C change. No ADR-0001 row:
rule 2 (a) holds with a generic mechanism, and (b) adds no knob -- the
conversion instant is data the run already has.
Witness: tests/test_native_margin_fx_clock.cpp section 8, kernel-only -- the
step up (scalar and model spellings), the step down, the equity term, the
percent ticket, the freeze's equity basis, a run without a curve, and batch
against stream. Fail-before on e9ad37d: 810 checks, 12 failures -- the
step-up placement refused with PlacementAdmission under both spellings, the
step-down placement accepted and filled, the ticket row refused, and the
freeze sized 6.666666666666667 units where the point's equity funds 5.
After: 826 checks, 0 failures. Mutation: dropping the clock hand-back fails
the two "callback still stands on its clock" checks.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit b0e4dbad26cbfe2b89df491f7afe3ec376636470)
…ty_at (R5 follow-up lane E21, item 2)
E3 left two kernel spellings of the account's marked equity:
BacktestEngine::marked_equity(price), converting at the presented clock, and
NativeExecutionConsumer::marked_equity_at(engine, price, fx), E3's
rate-explicit restatement for the margin model, held together only by a
bit-exact `==` pin and a mutation check. They are now one.
BacktestEngine::marked_equity_at(price, fx) holds the arithmetic (E3's
spelling, moved onto the engine as a protected member beside
marked_equity); marked_equity(price) is
`return marked_equity_at(price, active_account_currency_fx());`; and
margin_equity calls engine.marked_equity_at(mark, fx). The consumer's twin
and its declaration are deleted.
Byte-identical by construction. The delegated expression is the old one
with the rate read once instead of once per lot -- the presented clock
cannot move inside the loop -- and the library builds with
-ffp-contract=off, so every * and - rounds in the same order. The fx
finiteness guard E3's spelling carries cannot fire through the accessor:
every rate the engine holds is validated finite and positive
(NativeRunSpec::account_fx by positive(), set_syminfo_metadata's clamp,
set_account_currency_fx_series and the native curve's validation).
engine.hpp keeps ` double marked_equity(double price) const;` verbatim
-- scripts/test_check_native_c_api_surface.py pins that line -- and declares
the new member below it. It is not a @host-seam, so the C-surface census
does not ask for a C spelling. No layout, vtable, hash input or epoch
moves; no C change.
The pin. E3's `==` rows (sections 3 and 5 of
tests/test_native_margin_fx_clock.cpp) are kept, and they are now true by
construction: both sides are the one function, called with one rate
wherever the two clocks agree. Their mutation check says so. Multiplying
the unrealized term of the (now single) marked_equity_at by 1.0000001 fails
0 of the `==` checks, where it fails 10 on b0e4dbad; the drift is
still caught numerically, by 3 hand-arithmetic checks ("both terms convert
at the cursor" and the freeze row). Because the pin can no longer see a
second spelling, a structural row takes over what it used to witness.
Section 9 reads the kernel's own sources (src/*.cpp, src/*.hpp and
include/pineforge/*.hpp through a CMake glob, comments stripped) and
requires: one out-of-line marked_equity_at definition, on BacktestEngine;
no consumer spelling of it; engine.hpp's one declaration; an accessor whose
whole body is the delegation; and margin_equity routed through
engine.marked_equity_at(mark, fx).
Fail-before on b0e4dbad, the twin still in place: 921 checks, 5 failures --
the engine definition, declaration and delegation absent, the consumer twin
present, margin_equity routed through it -- while every behavioural row
passed, which is exactly the blind spot. After: 921 checks, 0 failures.
Mutation: an accessor that re-spells the arithmetic instead of delegating
fails the structural row alone (1 failure). No existing CHECK changed; the
two pin comments now say why they hold.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit d52a73d2e57569939e27d1c35a3069d2455a6348)
…w-up lane E21) Items 1 and 2 move lines in include/pineforge/engine.hpp (+3 after marked_equity's declaration) and src/native_execution_consumer.cpp (+21 in make_command_context and admit_placement_units' comment, -25 where the consumer's marked-equity twin was), so 194 of the 265 published anchors that cite a file this lane changed pointed a few lines off -- 132 of them far enough for check_doc_anchors.py to report SYMMISS, which fails ci_preflight's source guard; the rest pointed at a neighbouring line without claiming a symbol, which no gate sees. Every one is re-anchored through the exact base->HEAD line map of its file (difflib equal blocks, e9ad37d -> d52a73d2), so each citation lands on the same code line it named before. No cited line was inside a changed hunk (0 unmapped), so no citation needed an editorial choice. Digits only: 137 lines changed in four pages (ADR-0001, native-feature-parity.md, native-engine.md, pine-to-native.md), no prose. check_doc_anchors.py: 1555 anchors, 1555 OK. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit ca442e1efd41f3aec1263252efe70407227ca50c)
…ty is one function (R5 follow-up lane E21)
native-engine.md, "Margin and liquidation": the opening-admission paragraph
named the comparison and never its rate. It now says the comparison's
FX-bearing terms -- the notional, the marked equity and a percent-fee ticket
-- convert at the rate of the gate's own point, as every check point below
it does: the candidate gate's matching cursor, and for the placement gate
the acceptance point a Sized{SizeTime::AtAcceptance} froze its units at,
never the later bar clock a late-drained applied callback is presented; and
that the freeze takes its equity basis at that same point. "Placement-time
admission" says the gate converts at the rate the quantity was frozen at.
native-feature-parity.md: MG4 records the conversion instant for both sites
of the gate, and its stale anchor (native_execution_consumer.cpp:2006-2013,
a reset block, unclaimed so no gate saw it) now cites the gate's
initial-margin branch, :2607-2614. MG9 records the extension to the
placement site and that E3's rate-explicit equity is now the engine
accessor's one implementation, BacktestEngine::marked_equity_at.
Two doc-to-doc anchors (native-feature-parity.md -> native-engine.md
:2682-2749 and :3070-3077) are re-anchored through the page's exact line
map, onto the same `int main()` and `on_native_applied` lines.
check_doc_anchors.py: 1557 anchors, 1557 OK; check_doc_lint.py clean.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 0ec0b64ded34a564daf4e05f70b0d62dab99c7e9)
…p lane E21, item 1 revised, step 1) Supervisor ruling on item 1: the placement gate must not present a different clock to the engine. b0e4dbad's PresentedInstant rewrote current_bar_.timestamp for the length of the acceptance block -- a temporary mutation of core state every bar-clock reader inside that block sees -- and forced make_command_context onto a mutable engine. E3's rule stands for the gate as it does for the checks: the rate is threaded, never the clock. This commit is the engine half of that: the pure settlement inspection, and the commission quote it stages, carry the account-currency rate explicitly. - NativeSettlementStage (private to engine_execution.cpp) gains `fx`, the rate its quoted charges and its inspected notional convert at. Both stage_native_settlement overloads take it as an explicit parameter and store it; quote_execution_commissions takes it (its percent schedule was the quote's one clock read); inspect_native_settlement_stage reads stage.fx instead of the clock. - inspect_with_membership takes the rate. inspect_native_settlement_scoped and inspect_native_settlement_selected hand in active_account_currency_fx(); the new protected BacktestEngine::inspect_native_settlement_scoped_at( action, fill, scope, fx) hands in its caller's. - Every existing staging call (inspection, projection, preview and commit; ten sites, all in engine_execution.cpp) passes active_account_currency_fx() -- the value the quote and the inspection read at that same instant before -- so nothing moves: the same doubles, multiplied in the same order, under -ffp-contract=off. - project_native_settlement_stage is deliberately left on the clock. Every caller stages it at the presented clock's rate, and the closed rows it projects (build_close_trade_with_costs, engine_orders.cpp, a measured source) convert at that clock; reading stage.fx there would mix two rates in one projection of any stage staged elsewhere. No installed layout moves (the stage struct is private to its TU; the changed members are protected and non-virtual), no epoch, no C change. The consumer still presents the acceptance instant in this commit; the next one threads the rate there and deletes that. Witness: tests/test_native_margin_fx_clock.cpp section 10. Percent fee 1 %, LONG 5 held, a 14.5-unit opening inspected from the entry's applied callback, whose clock (T+2m) stands on 3.0 while its point (T+1m) stood on 1.0: at 1.0 notional 1950 and ticket 14.5; at 3.0 notional 5850 and ticket 43.5; inspect_native_settlement_scoped() equal to the rate-explicit inspection at the clock's rate field for field, bit for bit; the clock unchanged across the calls. Fail-before on 0ec0b64d, the TU does not compile: "error: use of undeclared identifier 'inspect_native_settlement_scoped_at'". After: 945 checks, 0 failures. Settlement neighbours unchanged: projection 681, scoped close 2545, resolved execution 369, current execution 1571, terms 1053, open lots 844 checks, 0 failures each. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit bc936d219581c49aa71feca2b8c875790cacf1fb)
…t (R5 follow-up lane E21, item 1 revised, step 2)
Item 1's ruling stands: an opening admission converts at the instant its
own point names, for the notional, the marked equity and a percent ticket,
and the Sized{AtAcceptance} freeze takes its equity basis at that same
point. What changes is the mechanism: the acceptance point's rate is
threaded to every term, and the engine's presented clock is never written.
- make_command_context takes `const BacktestEngine&` again. Its acceptance
block reads fx = account_currency_fx_at(point->decision.coordinate
.effective_time_ms) once and hands it to everything that converts: the
freeze's equity basis (engine.marked_equity_at(price, fx)), the units'
conversion, and admit_placement_units. b0e4dbad's PresentedInstant --
the write of that instant into current_bar_.timestamp and its restore --
is deleted.
- admit_placement_units(engine, sized, units, price, fx) inspects through
step 1's inspect_native_settlement_scoped_at(..., fx), so the resulting
notional and a percent ticket are quoted at the point's rate.
- admit_opening_inspect takes the rate and marks its equity with
engine.marked_equity_at(resolved_price, fx). The candidate gate hands it
margin_check_fx(engine, evaluation.cursor), the rate at the cursor
consume_matched_request presents for the candidate's inspection -- the
number marked_equity() read there before, so the candidate's decisions
are unchanged bit for bit.
Every runtime row of tests/test_native_margin_fx_clock.cpp is kept and
still passes: the rates each term converts at are the ones b0e4dbad's
design produced, only the path to them changed. The "callback still stands
on its clock after submit" pin now holds trivially, since nothing on the
submit path writes the clock at all, and its comment says so. What no
runtime row can see is a write restored before submit returns -- nothing
inside the block calls out -- so section 11 reads the kernel's sources:
make_command_context, admit_placement_units and admit_opening_inspect take
the engine const and never name current_bar_ or const_cast; submit and
replace never name current_bar_; and the engine's inspection path
(inspect_native_settlement_scoped_at, inspect_with_membership, both
stage_native_settlement overloads, finish_native_settlement_stage,
quote_execution_commissions, inspect_native_settlement_stage,
marked_equity_at) is const and reads no clock.
Fail-before, section 11 on bc936d21 (step 1, the consumer still presenting
the instant): 1002 checks, 2 failures -- "make_command_context(: const
engine 0, names the bar clock 1". After: 1002 checks, 0 failures.
Mutations on this tree: restoring the PresentedInstant exactly as b0e4dbad
had it fails section 11 alone (2 failures, every runtime row green);
restoring it through const_cast under the const signature fails it too
(names the bar clock, const_cast). Routing each term back to the clock
fails its runtime rows: the inspection 10 checks (step up, both spellings;
step down; the ticket), the gate's equity 2 (the equity row), the freeze's
equity 2 (the freeze row). Neighbours unchanged: margin model 240, hooks
196, fx roll 844, fx activation 180, sizing bases 685, sizing price rule
194, current execution 1571, terms 1053 checks, 0 failures each.
Adapter exposure is unchanged from b0e4dbad: none, structurally (initial
0.0; CashValue-only core sizing, withheld under an FX series). No epoch,
no C change, no ADR row.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 614aa270a5a5e2b829b41546a941a31539ab4630)
…ed (R5 follow-up lane E21) native-feature-parity.md, MG4: the E21 sentence said make_command_context "presents" the acceptance instant for the freeze and the gate, which is no longer true. It now says the rate is threaded, never the clock: make_command_context hands the point's rate to the freeze, to marked_equity_at and to the engine's rate-explicit settlement inspection, inspect_native_settlement_scoped_at (engine.hpp:640), and the engine's presented clock is never written -- witnessed by sections 8, 10 and 11 of tests/test_native_margin_fx_clock.cpp. The two revision steps moved lines again (engine.hpp +7 for the new declaration and the quote's comment; engine_execution.cpp and native_execution_consumer.cpp around the threaded calls), so 197 of the 266 published anchors that cite a changed file are re-anchored through the exact 0ec0b64d -> 614aa270 line map of their file, digits only. Two had their first line on a rewritten statement (native-feature-parity.md, "admit_opening_inspect native_execution_consumer.cpp:4172-4205", twice): they keep pointing at the same code, admit_placement_units' closing call into admit_opening_inspect through the same last line, now :4164-4198. check_doc_anchors.py: 1558 anchors, 1558 OK; check_doc_lint.py clean. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 08499d0366367acd9cf367a7fdc520f72e578d64)
…l's own rules (R5 follow-up C-surface lane E22, item 1)
Lane E7 typed the subscription row's two delivery words and left nine
enum-valued INPUT words of the two run specifications as untyped uint32_t
words, documented by a kernel type name or not at all (E7 finding 1,
INT13 "still open" 6): pf_native_run_spec_v1's `fee_kind`,
`close_execution` and `allowed_open_directions` (pineforge.h), and
pf_native_run_spec_ext_v1's `report_policy`, `price_grid`,
`grid_rounding`, `calculation`, `open_bar_view` and `margin_sizing`. Each
now has a C enumeration named after the kernel rule it selects, every
value the exact integer of its kernel enumerator:
pf_native_fee_kind_t PERCENT 0, CASH_PER_UNIT 1,
CASH_PER_EXECUTION 2 NativeFeeKind
pf_native_close_execution_t NEXT_ELIGIBLE_POINT 0,
AFTER_CALCULATION 1 NativeCloseExecution
pf_native_open_directions_t NONE 0, LONG 1, SHORT 2,
BOTH 3 NativeOpenDirections
pf_native_report_policy_t HOST_RECORDED 0,
KERNEL_RECORDED 1 NativeReportPolicy
pf_native_price_grid_t NONE 0, QUANTIZE_FILLS 1,
QUANTIZE_FILLS_AND_TRIGGERS 2 NativePriceGrid
pf_native_grid_rounding_t HALF_UP 0, DIRECTIONAL 1 NativeGridRounding
pf_native_calc_trigger_t BAR_CLOSE 0,
BAR_CLOSE_AND_FILLS 1,
EVERY_MODELED_POINT 2 NativeCalculationTrigger
pf_native_open_bar_view_t COMPLETE 0, OPEN_ONLY 1 NativeOpenBarView
pf_native_liquidation_sizing_t RESTORE_MINIMUM 0,
SHORTFALL_MULTIPLE 1,
FLATTEN 2 NativeLiquidationSizing
The words stay uint32_t, exactly as E7 kept `lookahead` / `gaps`: no rule
permits a retype -- the layouts are append-only, the README's stability
guarantee says a field is never retyped, and a C enum's size and
signedness are the compiler's choice (C11 6.7.2.2p4) -- so each typed
enumeration sits beside its field. The six extension fields are
documented `#pf_native_*_t.`, the typedef, which is what this header
indexes (INT13 fixed four tag references). The three base-spec fields
live in pineforge.h, outside this lane, so their enumerations name them
instead. A C probe prints the same 100 layout numbers -- every field
offset and size of both specs and the subscription row, and the three
extension size constants -- against this header and against e9ad37d's.
NativeReportPolicy has a third value the C surface never accepted,
KernelRecordedAtHostMarks (2): its host names each report point from
inside its own callbacks, and the callback table has no call that marks
one, so a C host could only declare a series nobody records. The
enumeration names two values and 2 stays PF_NATIVE_E_TAG, as it always
was; the enumeration's doc says why. allowed_open_directions is the one
word whose zero is not the kernel's default (BOTH), which its doc now
says. The three other kernel spellings of these words' values in the
header (SCOPE, `report_open_position_at_end`, the recalculation note)
name the C values.
No epoch: PF_NATIVE_API_VERSION stays 1, no symbol is added
(EXPECTED_NATIVE_C_API is untouched), and the enumerations are additive
names for integers the runtime already accepted. The configure_native
COVERAGE row names the nine words; check_native_c_api_surface (55
members, 47 spelled, 8 excluded; 3 seams) and check_c_abi_runtime pass
unchanged, and `bash docs/build.sh` reports 0 Doxygen warnings.
Ruling: generic. The names are the kernel's own rules -- fee kinds,
matching eligibility, opening directions, report recording, the price
grid and its rounding, calculation cadence, the open-bar view and
liquidation sizing; no source language is named.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit 6005d1374a7b2b49ba755c38bf585cd862887329)
…umeration, pinned name by name (R5 follow-up C-surface lane E22, item 2) translate_base_spec and apply_spec_ext read the nine words item 1 typed through switches on integer literals (`case 0:` ... `default: return PF_NATIVE_E_TAG`), and no value of theirs was pinned to the kernel enumerator it meant. Now: - static_asserts pin EVERY enumerator of the nine C enumerations to its kernel enumerator (the translation reads a word by its integer, so the pins are what make one integer name one rule on both sides), pin the nine words as uint32_t, and pin where they sit: the six extension words at words 3, 5, 6, 7, 9 and 10 of pf_native_run_spec_ext_v1 (ahead of every pointer, so on every target), the three base-spec words beside the words they always followed. - translate_word<Enum>(word, out) converts the word to its kernel enumeration -- each has a fixed uint32_t underlying type, so the conversion is defined for every word -- and admits it only if c_spelled() does. c_spelled() is an exhaustive switch over the kernel enumeration with NO default: an enumerator the kernel adds is a -Wswitch diagnostic until the C surface spells or refuses it (checked: an overlay NativeFeeKind with a fourth enumerator makes this TU warn "enumeration value 'Tiered' not handled in switch"), and a word naming no enumerator falls out of the switch. Either way the call site refuses with PF_NATIVE_E_TAG, at the same point of the spec as before. NativeReportPolicy::KernelRecordedAtHostMarks, which the literal switch refused implicitly through its default, is now an explicit refusing case with its reason. - all_spelled static_asserts prove the translation accepts every value the header names, and that KernelRecordedAtHostMarks stays refused, so each C enumeration is exactly the set its word accepts. Why the no-default switch is over the kernel enumeration and not the C one: a switch the compiler can check for exhaustiveness needs an enumeration-typed operand, and converting a caller's word to a C enumeration (no fixed underlying type) is undefined behaviour in C++17 for a value outside that enumeration's range ([expr.static.cast]/10) -- exactly the values the switch exists to refuse. The kernel enumerations are fixed on uint32_t, where every word is a value. Behaviour is byte-identical: every word maps to the kernel enumerator it mapped to before (pinned), every other word is PF_NATIVE_E_TAG as before, checked in the same order (optional_mask, fee_kind, close_execution, allowed_open_directions; then report, price grid, calculation, open-bar view, margin sizing). No kernel, adapter or epoch change; check_native_c_api_surface and check_c_abi_runtime pass unchanged, no symbol is added. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit a8f9fb9a6e1fbb33274888a6c60721e5d980bf6c)
…al past each (R5 follow-up C-surface lane E22, item 3)
Ten new checks in tests/test_native_c_api.c, called from
pf_native_c_api_checks, read each typed word's rule back from C alone --
one scenario per word, every value of its enumeration on its own run:
fee_kind a scripted 2-unit round trip (103 -> 106) is charged
(2x103 + 2x106) x 1 % / 4.0 / 2.0 under PERCENT /
CASH_PER_UNIT / CASH_PER_EXECUTION (pf_trade_t::commission)
close_execution an order born in bar 2's close calculation fills in
bar 3 (NEXT_ELIGIBLE_POINT) or bar 2 (AFTER_CALCULATION):
on_applied's decision interval
allowed_open_directions a long and a short opening: which of the two
applies under NONE / LONG / SHORT / BOTH, and that a
refused one is a MATCH_REJECTED event
report_policy equity_curve_len 0 (HOST_RECORDED) or one point per
script bar (KERNEL_RECORDED)
price_grid examples/native/native_price_grid_c.c's sub-tick tape:
raw and booked price of every fill under NONE,
QUANTIZE_FILLS and QUANTIZE_FILLS_AND_TRIGGERS (3, 3, 5
fills; the stop only the quantized path reaches)
grid_rounding the same tape under QUANTIZE_FILLS: nearest tick
(HALF_UP) or the adverse tick (DIRECTIONAL)
calculation on_recalculate's reasons: the close calculation always,
ORDER_FILL above BAR_CLOSE, TICK only for
EVERY_MODELED_POINT, and the driven counter
open_bar_view on_bar_open is handed the whole bar (COMPLETE) or
H = L = C = open with no volume (OPEN_ONLY), 40 of 40
margin_sizing a host-answered breach of exactly one unit's margin
rests a kernel liquidation of 1 unit (RESTORE_MINIMUM),
1 x 2.5 (SHORTFALL_MULTIPLE) or the whole book as a
Flatten request (FLATTEN), read from the working book
plus check_spec_word_layout: each word still one uint32_t, zero-filled
words at their enumeration's zero (allowed_open_directions's zero is NONE,
not the kernel default), and on LP64 the offsets and sizes measured on
e9ad37d (fee_kind 156, close_execution 172, allowed_open_directions 176,
sizeof 216; report_policy 12, price_grid 20, grid_rounding 24,
calculation 28, open_bar_view 36, margin_sizing 40, sizeof 304).
One past the end of every enumeration is PF_NATIVE_E_TAG from
strategy_configure_native_ext_v1 and leaves the handle Unconfigured; the
same handle then configures with the word restored. For report_policy one
past the end is the kernel's KernelRecordedAtHostMarks (2), which has no C
name. The base-spec words run through both entry points that read them
(configure_native_v1 and _ext_v1, same outcomes), and the refusal pins the
asymmetry between them: configure_native_v1 has no boundary refusal, so a
bad word reaches the kernel's validation, answers -1 and leaves a fresh
handle Failed.
Fail-before: against e9ad37d's header the TU does not compile, for
exactly the missing spelling -- 69 diagnostics, every one naming one of
the 33 new names (24 enumerators, 9 typedefs), the first
tests/test_native_c_api.c:4366:18: error: unknown type name
'pf_native_fee_kind_t'
With the 33 names shimmed to the integers the runtime already accepted,
the same TU passes against e9ad37d's library: the rules pinned here are
the base runtime's. Rotating any one word's shimmed values makes that
word's own scenario fail (e.g. "a fee kind charged another rule's
commission" x6, "a liquidation sizing reduced another rule's units" x3),
so every scenario tells its values apart.
No new CTest row: the checks run inside test_native_c_api, so neither
KERNEL_MIN_TESTS nor RELEASE_MIN_TESTS moves. No existing check changed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
(cherry picked from commit beb22e646d9d911dd8a308a4367a589e2450a763)
… the README row (R5 follow-up C-surface lane E22, item 4) docs/pages/native-engine.md's "The run specification" paragraph (Driving the kernel from C) gains one table: each of the nine words, the spec that carries it, its C enumeration and its values with the default first -- allowed_open_directions's default is BOTH while its zero word is NONE, so a zero-filled base spec admits no opening. Three sentences follow: a value outside its enumeration is PF_NATIVE_E_TAG from strategy_configure_native_ext_v1 and the handle stays unconfigured; KernelRecordedAtHostMarks has no C value, and why; and strategy_configure_native_v1 has no boundary check for the three base-spec words, so the kernel's validation judges them and a bad one answers -1 and fails the host. README.md's strategy_configure_native_ext_v1 row names the nine enumerations in one grouped sentence (E7 added no README row; the one row that carries these words is this one). The section's count, "32 further PF_API functions", is corrected to 33: E11's strategy_native_declare_opened_lot_entry_bar_mask_v1 added the table row but not the count, and EXPECTED_NATIVE_C_API holds 33 (unchanged here). `bash docs/build.sh`: 0 Doxygen warnings; check_doc_lint clean. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 16ba9d42b2eb6a2caa423f48937606b2f139e0e4)
…ollow-up C-surface lane E22, item 5) ci_preflight's doc-anchors stage is fail-closed since int12, and items 1 and 2 move lines every page cites: native_c_api.h grew 118 lines above nearly all of its cited declarations (the nine enumerations, the COVERAGE row, the SCOPE line) and native_c_host.cpp's apply_spec_ext moved. At e9ad37d all 1555 anchors resolve; after items 1-4, 113 did not. `python3 scripts/check_doc_anchors.py --fix` re-anchored the 103 it can prove. The 10 it calls ambiguous (a symbol on several lines: struct_size, version, p1, intent_value, price, trail_has_arm_price, intrabar_kind, on_recalculate, strategy_native_) were moved by the exact e9ad37d->HEAD line map of the header (difflib over both versions), so each still cites the declaration it cited. Every changed line differs from its original only in anchor digits (checked line by line with the anchors masked). check_doc_anchors: 1555 anchors, 1555 ok. Files: docs/pages/pine-to-native.md (95 lines), docs/adr/0001 (3), docs/design/native-feature-parity.md (3), docs/pages/contributing-llm.md (1), docs/pages/native-engine.md (2). The first four are outside this lane's file list; they are touched for this mechanical re-anchor alone, because the binding gate fails the preflight otherwise -- the same repair INT13 made for wave D (06d4107). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit d32d0d8270133e81d4020c8d8329fda2a01dafe4)
…ne window (R5 follow-up C-surface lane E22, item 3 follow-up) count_events() and run_grid_tape() read the event history with one strategy_native_events_v1 call into a fixed 256- / 128-row buffer, so a longer history would have been cut silently -- a MATCH_REJECTED or an APPLIED row past the window would read as absent. Both now go through page_events(), which walks the history 32 rows at a time from the last ordinal returned, the paging contract check_event_polling already pins, and counts every row of the asked kind. No expectation changes: the same TU passes against this tree, and with the 33 names shimmed to integers against e9ad37d's library; rotating the open-directions, price-grid or grid-rounding shims still fails their scenarios (22, 9 and 8 failures, as before). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 7c64b62648867668ca217920bc721a809d12777a)
…ication RULING: generic. FNV-1a's chain is unchanged -- XOR with a zero byte is the identity and the multiplications are associative, so a run of k zero bytes is exactly one multiplication by p^k. The scalar folds (u/i/d/b) read the eight object bytes out of the value on a little-endian host and collapse those runs; anywhere else, and for string data, bytes() stays the per-byte reference chain. No digest moves: every continuation hash, broker-state hash and settlement digest answers the byte it answered before, which the suite's existing pins and the 312-probe corpus identity prove. Lane E24 measured where the replay's fold goes: a 43008-bar runtime-budget replay folds 94.1 MB through this chain, and 58.5 MB of that -- 62 % -- are the zero high bytes of ordinals, enum words, millisecond stamps and sizes, and the zero mantissa bytes of round doubles. Collapsing them halves the dependent multiplies (94.1 M -> 46.8 M) and takes the replay from 0.290 s to 0.257 s of process CPU (best of nine, same host, same binary shape). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 21661cc76bc468a8841a797348aa4dbc762f549c)
RULING: generic. The driver log and the account log are append-only for the life of a run -- begin_ready is the only thing that clears them, and it resets their digests and rebinds the run identity in the same breath -- and each fold chains on the one before it, exactly as the command history's digest already did. continuation_hash() therefore folds the rows a digest has not seen yet instead of resetting it and re-deriving the whole log. Same chain, same rows, same order: the digest value is unchanged, which the new witness asserts directly (a run read once per bar answers what a run read once at the end answers). What moves is the SHAPE of the cost. A host that reads the continuation once per bar -- every broker-state-hash recorder, every per-bar checkpoint consumer -- paid O(bars^2): lane E24 measured 1500 bars at 0.514 s and 6000 bars at 8.115 s, a ratio of 15.79 for four times the bars. After the tail fold the same legs are 0.010 s and 0.029 s. tests/test_native_continuation_digest_tail.cpp is the witness: it pins the value identity and bounds the shape at 8x for four times the bars, which separates linear (about 2.9x) from quadratic (about 15.8x) with more than a factor of two of margin either side. It is source-free, so it registers in every profile the tests build: KERNEL_MIN_TESTS 199 -> 200 and RELEASE_MIN_TESTS 566 -> 567. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 0eb55f3a383bc541f7c8999b90e5acd15fcb12aa)
The zero-run fold and the tail fold add lines to src/native_execution_consumer.cpp, and the floor notes add lines to scripts/ci_verify.py, so every documentation anchor below them shifted. The 104 moved anchors are re-anchored to the same symbols by the exact old->new line map of the two diffs; no citation changes what it points at. check_doc_anchors.py: TOTAL 1555 anchors, 0 bad. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 97c9920522841197f5e3893ebed0c5b2b163d7f4)
… lane E25, item 1) Lane E20 found (exec/E20-final.md "(f) Findings" 2) that since R4 slice C an aggregated run reads session.islastbar on every in-session bar. These tapes are TradingView's own answer to which bars carry the flag: three `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. Every probe enters with process_orders_on_close on the flagged bar and closes on the next one, so each entry time is a flagged bar. - e25-f-islastbar, NYSE:F 15m, 2025-04-10 .. 2026-04-17: 255 trades, 15:45 ET on 252 session days and 12:45 ET on the three half days (2025-07-03, 2025-11-28, 2025-12-24). - e25-f-isfirstbar, same chart: session.isfirstbar on 09:30 ET, 255 of 255. - e25-eth-islastbar, BINANCE:ETHUSDT.P 15m, 2025-04-20 .. 2026-04-25: 23:45 UTC on 370 of 370 days. TradingView draws the boundary at the session day. The registry's NYSE:F feeds hold regular hours only (the bar after 15:45 is the next day's 09:30), so a lookahead that asks only whether the next bar is out of session cannot see it; README.md says so. bars.inc carries the replayed days, rows copied from the feeds the tapes ran on: registry lane f-15's 1m finer feed 50bccb38... and 15m chart feed 80f404ae... (2025-07-02, the 2025-07-03 half day, 2025-07-07), the corpus ETH 1m feed at gitlink 442d497 and the 15m feed derived from it (2025-06-10 23:00 .. 06-11 00:59 UTC). No test reads them yet. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit ac2d39d62b16c9af1f11e6ed59ae576420de6dda)
…the chart-timeframe path does (R5 lane E25, item 2) Lane E20's finding 2: since R4 slice C (73817c1) every in-session bar of an aggregated run (input timeframe finer than the chart's) read session.islastbar = true. scheduler_update_session_state reads "last" when the next script bar's open is out of session, and reads a run's final bar, which has no next one, as last. PineScheduler::bar handed it that open only when tf_ratio == 1, where the next retained bar IS the next chart bar, so an aggregated run looked final on every bar. Measured on e9ad37d (exec/E25-probes/exp_session_base.txt): 1m -> 5m over 15:30 .. 16:14 ET flags 111111000 against the chart path's 000001000, plain and magnified; two regular-hours days 1m -> 5m flag all 156 bars. The contract, three ways: - ab9714b src/source/pine_scheduler.cpp: the chart-timeframe loop (:1714-1737) flags the bar whose next input bar is out of session, the run's final bar included; the aggregated loop (:1865-1869) flags in_session && barstate.islast, the final bar only; the magnified loop (:1839-1845) never flags. - TradingView (item 1's tapes): the last bar of every session day. - The chart-timeframe path today: ab9714b's lookahead, unchanged. Ruling (scheduler, source layer; no kernel file, no epoch, no C change): an aggregated chart looks ahead exactly as the chart-timeframe chart does, to its own next script bar, the chart-grid bucket (session_intraday_bucket_open_ms, the grid TimeframeAggregator labels by) of the first retained input past the current bar's bucket. The chart a script sees does not depend on the timeframe its bars were fed at, so both paths now agree bar for bar, plain or magnified. The legacy aggregated rule is not restored: it flags only the run's final bar, so where out-of-session bars separate two sessions it misses the earlier session's last bar, which the lookahead and TradingView both flag. Nothing is added to the scheduler's state: the next open is derived per call from retained_.bars (whose consumed prefix is hashed), searching one script bar's span of inputs. Daily and longer charts read no lookahead (every bar is its session), so they skip it. Witness: tests/test_session_islastbar_aggregation.cpp, source-bound (release and debug profiles; the kernel-only build drops it). Fail-before, this tree without the scheduler hunk (exec/E25-probes/witness-fail-before-item2.txt): test_one_session_close aggregated 111111000, magnified 111111000; chart 000001000 test_two_sessions aggregated 111111000000111; chart 000001000000001 test_tv_nyse_f aggregated and magnified flag 66 of 66; chart 1 (the final bar) test_tv_eth_24x7 aggregated 11111111; chart 00000001 session_islastbar_aggregation: 30 passed, 13 failed Pass-after: 43 passed, 0 failed. Every chart-timeframe control row passes unchanged on both trees. RECORDED, not fixed: TradingView ends a session at the session DAY, even where the next bar is in session again. On the registry's regular-hours NYSE:F feeds and on a 24x7 feed both paths therefore flag only the run's edges. The RESIDUAL rows pin exactly what both miss (NYSE:F 2025-07-02 15:45 and the 2025-07-03 half day's 12:45; isfirstbar 07-03 and 07-07 09:30; ETH 2025-06-10 23:45 UTC). Closing that moves the chart-timeframe path too, which this lane keeps byte-identical. Corpus: no probe reads a session bar predicate (grep -rl islastbar corpus/validation/*/strategy.pine: 0 files; no generated.cpp names session_isfirstbar_/session_islastbar_/session_ismarket_). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 416ae475936736539cc09e094a8040bfada3b820)
…er to the legacy setter (R5 lane E25, item 3) Lane E20 read scheduler_set_session_bar_state as "the uncalled port of the legacy setter". It was: R4 slice C wrote scheduler_update_session_state with its own copy of the setter's body (the daily-or-higher rule and the three assignments) and left the port with no caller, while engine.hpp's session-predicate comment still named it as the adapter's writer. Decision: call it, do not delete it. It is ab9714b's set_session_bar_state (pine_scheduler.cpp:1661-1675), the one place the daily-or-higher rule lives, and every legacy loop reached the flags through it with its own intraday answer. scheduler_update_session_state keeps the lookahead (next script bar, live-tail calendar) and passes in_session && !next_in_session; the setter writes session_ismarket_, session_isfirstbar_ and session_islastbar_. Deleting it instead would leave the lookahead holding a second copy of the daily rule and engine.hpp citing a symbol that no longer exists. Neutral by construction: the setter's intraday branch assigns exactly in_session && !prev_in_session_ and the value passed in, its daily branch the same two assignments the removed copy made, and prev_in_session_ is still updated after it. Measured: exec/E25-probes' all-paths scratch probe prints byte-identical output before and after this commit; the item-2 witness (43 passed), test_session_predicates_daily_chart_l4d (395 passed), test_live_realtime_tail_l4d and test_streaming_l4d pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 5cc334425066270b2b21d3298de06bc9e1ecca90)
…, not as the run's last bar (R5 lane E25, item 4) The hole item 2 closed for an aggregated chart is also a stream's: a realtime bar has no retained next bar, so scheduler_update_session_state got no next open and, outside the batch live tail, read every in-session realtime bar as the run's final bar. Measured on item 3's tree: warmup 15:30 .. 15:45 ET, realtime ticks 15:50 .. 15:59, 0930-1600 New York, chart 5m fed at 5m and at 1m: islastbar 000111, the realtime 15:50 flagged. ab9714b read a realtime bar on the calendar (src/source/pine_stream.cpp: 458-469, and :417-420 for confirmed stream bars): in session, and the next script bar, one bar width on, out of it; no bar width, never last. That is exactly the rule the batch live tail already reads here, so the tail's branch now also takes a bar the kernel dispatches in its Realtime phase (NativeLifecycleKind::Running, NativeRunPhase::Realtime -- the test scheduler_publish_source_bar already makes for barstate.islast). A retained next bar still wins, and the warmup's final bar keeps the run-end convention the legacy chart-timeframe warmup read too. Ruling: source layer only; the kernel's lifecycle view is read, not changed; no state, no epoch, no C change. This goes one step past the brief's aggregated path because it is the same mechanism on the live runner, where it is TradingView-visible on every bar (a strategy that flattens on session.islastbar flattened every realtime bar). Witness: test_session_islastbar_aggregation section 5. Fail-before, this tree without the host hunk (exec/E25-probes/witness-fail-before-item4.txt): both shapes islastbar 000111, 73 passed, 2 failed. Pass-after: 75 passed, 0 failed; islastbar 000101 on both. The all-paths scratch probe changes on the two stream rows only; every batch row is byte-identical to item 3. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit da985747925edc3b32029bc0e59b117045fe2754)
…er's anchors follow its lines (R5 lane E25, item 5) native-feature-parity.md OT7 (Pine language state: barstate / session flags) records what items 2-4 changed: session.islastbar reads one script bar ahead on every driving path, through the one writer scheduler_set_session_bar_state again, and what stays open: TradingView's session-day boundary, which no path sees on a regular-hours or 24x7 feed (tests/fixtures/session_islastbar). Items 2 and 4 moved lines of pine_scheduler_native.cpp (+28 above PineScheduler::bar, +10 inside it). Thirteen anchors into it are moved through the exact difflib line map from e9ad37d (lane E20's reanchor.py; 13 moved, 0 unmapped): native-engine.md 1, ADR-0001 1 (pine_scheduler_native.cpp:650 -> 688, digits only), native-feature-parity.md 11. check_doc_anchors.py: 1557 anchors, 1557 ok. pine-to-native.md is unchanged: its session row maps session facts to NativeDecisionContext, which no item touched. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> (cherry picked from commit 90686e5b5660b4bcbda2245b13681e35887bfe76)
…ing, E20 finding 1) E20 finding 1 reported that the live Pine scheduler contradicts this page's §3.2 on three points. The owner ruled on 2026-09-22 that the scheduler is right — it passed the sweeps — and the spec is wrong. This is that correction, plus the §3.1 re-check the same ruling asked for. Doc-only; no source file is touched and every doc guard stays green. Re-measured on this tree with E20's own scratch probes (exec/E20-tools/ exp_tail.cpp, exp_tail2.cpp, exp_margin.cpp) and, for §3.1, one new tape that ends at an RTH close so the calendar lookahead and the legacy expression disagree. Every row was also measured against origin/main e9ad37d, built library-only from `git archive`, to attribute it: every §3.2 number is identical there, so none of this is a wave-E behaviour change. The one difference main shows is lane E25's own fix — aggregated `session.islastbar` reads `111111` on main and `000001` here, which is the bug E20 finding 2 reported and E25 closed. §3.2, corrected to what runs: * a margin call or intraday-cap close due against the forming bar is booked ON that bar. The page promised it "surfaces only at settlement" and listed `process_margin_call` among the steps that never run. The two-bar short tape books `Margin call @105.0000 qty=3.809524 t=2000` with the flag on and off alike, with the host called once instead of twice; * under the bar magnifier the probe IS honoured (9 host calls of 10), not the "silent no-op" the page claimed; * under `calc_on_order_fills` it is neither a no-op nor a full suppression: the tail bar's ordinary calculation is suppressed and a fill on the tail bar still recalculates the script (11 calls off, 10 on; the surviving bar-9 call carries `barstate.islast`); * the aggregation path is not UNDEFINED: the last complete bucket is not calculated (3 calls off, 2 on), and the trailing partial bucket is never calculated either way (17x1m -> 5m: buckets at +0/+5/+10 off, +0/+5 on; the +15 bucket, holding two of five inputs, on neither); * `strategy_stream_begin` does not need the flag cleared — it refuses while either override is set, with `native stream cannot use historical probe/tail overrides`. §3.1, the sentence the ruling asked me to re-check: effect 2 is honoured on every dispatch path. All three of that paragraph's claims were false, and false on main too — `run(bars, n)` does evaluate session predicates, effect 2 is not scoped to `run_simple_bar_loop`, and it is not UNDEFINED under aggregation. On an RTH tape ending at the close, all four paths answer `session.islastbar` `000001` with `barstate.islast` `000000`: the tail bar is flagged by the calendar, which is the only thing that could have flagged it. The remaining caveat is not this flag's — a non-24x7 tape still needs the v2 `SessionCalendar`. Proof: `python3 scripts/check_doc_anchors.py` -> 1565/1565, every anchor resolves; `python3 scripts/check_doc_lint.py` -> TOTAL 0. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…ave E)
Three things no lane could land, because each is only true of the merged tree.
**tests/CMakeLists.txt — a shared `endif()`.** E21 and E25 each appended a
`if(TARGET …) target_compile_definitions(…) endif()` block at the same place in
the file. Cherry-picking both let the textual merge fold the two blocks onto
one `endif()`, and CMake refused the tree with "Flow control statements are not
properly nested" at tests/CMakeLists.txt:1482 — a configure error, not a test
failure, so nothing downstream would have caught it. E21's block gets its
`endif()` back; both lanes' definitions are present and `if(`/`endif()` now
balance at 17 each. Every lane's rows survive the union:
`test_native_trail_stop_ladder` (E16), `test_e19_excursion_path_order` (E19),
`test_native_continuation_portable` (E23),
`test_native_continuation_digest_tail` (E24) and
`test_session_islastbar_aggregation` (E25) are all registered — `ctest -N` on
build-on reports 571 rows against main's 566.
**README.md — the C symbol count is the sum.** E22 corrected a count stale
since E11 (32 -> 33); E18 then added `strategy_native_replace_ext_v1`. Neither
lane could see the other, so the merged page says 34, which is what
`EXPECTED_NATIVE_C_API_DECLARATIONS` in `scripts/check_c_abi_runtime.py` already
pins. Both lanes' symbol-table rows merged cleanly and are kept verbatim.
**scripts/reanchor_doc_citations.py — the tool this wave needed twice.** Every
lane that edits a header re-anchors the published pages against its own tree, so
after a merge each citation is right for one lane and wrong for the tree.
`check_doc_anchors.py --fix` repaired 413 of this wave's 509 rotten anchors and
correctly refused the other 96: choosing between ten lines that all say
`invoke_callback` is editorial judgement. It is not judgement when the old line
number is known-good for some tree — then it is a line map. The tool reads a
reference's copy of the cited file, keeps only references that actually place
the claimed symbol inside the cited window, carries the citation through
`difflib.SequenceMatcher`, and **verifies** that the result resolves in the
working tree before writing it: a wrong reference can produce no anchor, never a
wrong one. `--decide` settles the remaining disagreements between references by
the anchor that points most directly at the symbol, and prints the alternatives
it passed over. E18 left a first cut at /tmp/e18/reanchor.py; this is that idea
hardened, with a self-test the brief asked for.
Proof:
python3 scripts/reanchor_doc_citations.py --self-test
reanchor_doc_citations self-test: 2 ambiguous citations carried across a
5-line insertion, 0 stuck ... OK
python3 scripts/check_c_abi_runtime.py -> rc 0
python3 scripts/check_native_c_api_surface.py -> rc 0, 58/47/11
ctest --test-dir build-on -N -> Total Tests: 571
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
…INT14, wave E)
Nine lanes re-anchored `docs/pages/*.md`, `docs/adr/0001-kernel-adapter-boundary.md`
and `docs/design/native-feature-parity.md` against their own trees. Every one of
those hunks conflicted with its siblings, and the integration rule is to keep
both lanes' claims and discard both lanes' digits, then re-anchor once here.
This is that once. Line numbers only — `git diff` on these five pages contains
no prose change; the claim changes rode in with the lanes that made them.
509 anchors were rotten after the picks. `check_doc_anchors.py --fix` proved
413 of them from the tree alone. The remaining 96 name a symbol that occurs on
several lines, which the checker refuses by design; they were carried by
`scripts/reanchor_doc_citations.py` from the lane tips (`origin/main`, `r5/E16`,
`r5/E18` … `r5/E25`), which keeps only references that place the symbol inside
the cited window, maps the line through `difflib`, and verifies the result
before writing it. 65 were unanimous. 31 had several references that all
verified — the same old line maps to slightly different new lines through
different lanes' files — and `--decide` took the reading that points most
directly at the symbol, printing the alternatives. Two are worth naming
because the tie-break, not the majority, chose them:
`native-feature-parity.md:441` `TermsUnresolved` -> `:4439-4441`, whose span
starts exactly on the symbol where the majority reading started three lines
short of it; and `:976`/`:1085` `admit_opening_inspect` -> `:4239-4273`, which
follows E21's revised acceptance block rather than main's.
Result, and the gate this has to end at:
python3 scripts/check_doc_anchors.py
page anchors ok bad nofile noline
CONTRIBUTING.md 6 6 0 0 0
docs/pages/contributing-llm.md 16 16 0 0 0
docs/pages/native-engine.md 107 107 0 0 0
docs/pages/pine-to-native.md 554 554 0 0 0
docs/adr/0001-kernel-adapter-boundary.md 219 219 0 0 0
docs/design/native-feature-parity.md 663 663 0 0 0
TOTAL 1565 1565 0 0 0
every documentation anchor resolves to its symbol
`python3 scripts/check_doc_lint.py` -> TOTAL 0; `bash docs/build.sh` ->
Doxygen warnings: 0.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
No lane's number is taken: every lane measured its floor against `origin/main` e9ad37d alone, so five of them would each have claimed "+1" over the same base and the sum would have been wrong twice over. These are counted on this tree. Kernel, 200 -> 202. Configured exactly as `ci_verify kernel` does (`PINEFORGE_BUILD_SOURCE_LAYER=OFF`, live runner and examples ON, tutorial and corpus OFF), `ctest -N` registers 203 and 202 run; the one row that does not is `test_native_live_websocket`, which still skips on a system libcurl, as it did at 199. The three rows are one per source-free lane: 199 main e9ad37d +1 E16 test_native_trail_stop_ladder +1 E23 test_native_continuation_portable +1 E24 test_native_continuation_digest_tail --- 202 run (203 registered) E19 and E25 add a row each as well, but `test_e19_excursion_path_order` and `test_session_islastbar_aggregation` both reach `pineforge/source/`, so the include-closure filter in tests/CMakeLists.txt drops them here — they are in the release count below. E17 has no commit at all: it ruled the engine not guilty of the slot-181 divergence (UB in the codegen-emitted TU). E18, E20, E21 and E22 add no row: every witness of theirs is a scenario inside a TU that already existed, which is why E18's three suites grew 472 -> 490, 603 -> 624 and 787 -> 815 checks instead. Release, 567 -> 571. `ctest --test-dir build-on` ran 571 of 571 with the six ABI providers prepared, so nothing skipped: 566 main e9ad37d +1 E16 test_native_trail_stop_ladder +1 E19 test_e19_excursion_path_order +1 E23 test_native_continuation_portable +1 E24 test_native_continuation_digest_tail +1 E25 test_session_islastbar_aggregation --- 571 run E24's own commit had already raised the pair to 200/567 in its branch; both numbers are superseded here rather than added to, which is what "no lane's number is taken" means. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
luisleo526
marked this pull request as ready for review
September 22, 2026 11:29
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]>
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 wave E: nine lanes integrated onto main e9ad37d — 39 lane commits + 5 integration commits (the owner-ruled
native-consumer/v7 → v8epoch for the changed continuation-digest recipe; the owner-ruled live-surface §3.2/§3.1 probe-tail doc correction; unions; a once-only doc re-anchoring with the newscripts/reanchor_doc_citations.py; floors from the rows that run).ladder_trail_stop, rule-2 ruling row); E14's pinned ULP divergence now matches TradingView 7/7declare_timeframe_subscriptions_result/declare_auxiliary_feed_result(NativeSetupResult,WrongPhaseappended) /append_auxiliary_bars_result; Cstrategy_native_replace_ext_v1carries the reject reason (C symbols 34)strategy_set_path_orderdoc correctedengine_run.cppnamespace deleted (object byte-identical), 17 dangling citations, STABILITY tail rule, frozen-header working-row read, LazyComplete/report-policy docs, runner examples-UNDEBUGmarked_equity_atsession.islastbaron the aggregation path and stream realtime bars reads one script bar ahead (every in-session bar was flagged since R4-C); three TradingView tapes pinnedFloors: kernel 199 → 202, release 566 → 571. Epochs unchanged except the ruled
native-consumer/v8; PF_NATIVE_API_VERSION 1, PF_ABI_VERSION 4. Corpus pin 442d497 untouched.Evidence
putsnever reachesmain); the Linux sanitizers CI job is the gate.exp-r5-int14-wave-e-20260922on this head (51322ac): executionpineforge-experiment-run-jc8rs, 72/72 cases, 0 failed; candidate2b3fe09d…; offline band1 +0/−0 band2 +0/−0 (score 0, hard regressions 0, coverage lost 0); pr_gatepineforge-pr-gate-sxkjg→target.not-positive(neutral, accepted at net 0).The corpus cannot exercise E16/E21/E25's paths (on-grid feed, margin 0, no session.islastbar probe); the population sweep is their gate.
Merge with rebase.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LifAW9e5J6gFx7gatuxETE