From 8805a1226b734678117977a29ecfcfefc6e53bbd Mon Sep 17 00:00:00 2001 From: luisleo526 <20812494+luisleo526@users.noreply.github.com> Date: Wed, 11 Nov 2026 20:28:54 +0800 Subject: [PATCH] Fail the run when str.match or a calendar lookup meets a memory failure str.match returned the empty string after any exception it met, and the four session-calendar lookups of the native path (the entry session point, the risk day, and the day and ordinal reads of the C callback host) answered "out of session" or "no day" after any exception. A memory failure, or a regular-expression resource limit, therefore let a run finish with a wrong result instead of failing. A std::bad_alloc now reaches the run's out_of_memory failure (status 1). Container length errors and the system errors that mean "out of memory or resources" carry the same code. Every other exception keeps its fallback. The regex library's error_space, error_complexity and error_stack depend only on the pattern and the input, never on memory, so they are not out_of_memory and not retryable: they stop the run with pine_string_error and the new reason regex_resource_limit. One inline function maps the 13 standard error codes to that outcome or to an invalid pattern. An invalid regular expression now stops the run with pine_string_error (reason invalid_regex_pattern), as TradingView does, instead of returning an empty string. Patterns the engine's regular-expression library does not support stop the run too, with the same error; on the Linux build these include lookbehind, named groups, inline flags, comment groups, atomic groups, conditionals, \p{...} classes and {,n} repeats. The message is TradingView's text with the engine's bar prefix, "Error on bar N: ", or "Error before the first bar: " when no bar is being executed; one function, run_error_bar_prefix, builds the prefix from the ambient bar context. The catalog gains the two reasons and their message templates; its diff reads one changed entry, so a release that carries this change is a minor one. The new tests use a counting replacement of the global operator new (tests/fail_alloc_seam.hpp) to refuse each allocation of a run in turn: every allocation must end in the out_of_memory failure or in a closed-trade list (and its totals) identical to the clean run, never in a different completed result, and the sweeps print the indexes at which a run completed. They fail on the base tree at the allocations the old code swallowed. The table of the 13 regex error codes is its own small test: a one-line change of the mapping turns the expected rows red. ADR-0001 gets one row for the kernel's literal of TradingView's invalid-pattern text, which the residuals gate requires for a text that spells a Pine namespace, and the two pinned phrase counts of its unit tests move by one. The allocation-bound manifest lists the two declarations of the new function (ceiling 1413 -> 1415, pinned count 1597 -> 1599) and its site inventory is re-emitted. The lines added to the sources move the lines the documentation cites below them; the anchors that follow them are re-pointed by the number of lines added (digits only, no sentence changes). Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 5 + ci/cr150-source-bounds.yaml | 7 +- ci/cr150-source-sites.json | 31 + docker/run_failure_codes.json | 8 +- docker/run_failure_codes_diff.json | 41 +- docs/adr/0001-kernel-adapter-boundary.md | 21 +- docs/coverage.md | 2 +- docs/design/BARS-BIN-INPUT.md | 10 +- docs/design/arena-bound.md | 6 +- docs/design/cr150-contracts.md | 12 +- docs/design/native-feature-parity.md | 110 ++-- docs/native-refactor-progress.md | 2 +- docs/pages/memory-management.md | 8 +- docs/pages/native-engine.md | 8 +- docs/pages/pine-to-native.md | 12 +- docs/pages/run-failure-codes.md | 2 +- include/pineforge/run_failure.hpp | 7 +- include/pineforge/str_utils.hpp | 5 + scripts/test_check_kernel_residuals.py | 4 +- scripts/test_cr150_source_gate.py | 4 +- src/native_c_host.cpp | 28 +- src/native_execution_consumer.cpp | 23 + src/run_failure.cpp | 7 + src/run_failure_registry.inc | 288 +++++---- src/str_utils.cpp | 25 +- src/str_utils_internal.hpp | 20 + .../calendar-lookup-resource-failure.cmake | 4 + .../cr150.d/str-match-resource-failure.cmake | 6 + tests/fail_alloc_seam.hpp | 156 +++++ .../test_calendar_lookup_resource_failure.cpp | 608 ++++++++++++++++++ tests/test_regex_error_classification.cpp | 42 ++ tests/test_str_match_resource_failure.cpp | 534 +++++++++++++++ tests/test_str_utils.cpp | 20 +- 33 files changed, 1812 insertions(+), 254 deletions(-) create mode 100644 src/str_utils_internal.hpp create mode 100644 tests/cr150.d/calendar-lookup-resource-failure.cmake create mode 100644 tests/cr150.d/str-match-resource-failure.cmake create mode 100644 tests/fail_alloc_seam.hpp create mode 100644 tests/test_calendar_lookup_resource_failure.cpp create mode 100644 tests/test_regex_error_classification.cpp create mode 100644 tests/test_str_match_resource_failure.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index a2d0eb54c..3e36ac01d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ version number follows semantic versioning over the surfaces the ## Unreleased +- `str.match` no longer reports "no match" after a memory failure; the failure now reaches `out_of_memory`. + A pattern over the limits of the engine's regular-expression library stops the run with `pine_string_error` and reason `regex_resource_limit`, which is not retryable. + An invalid regular expression now stops the run with a typed error, as TradingView does; it used to return an empty string. The message is TradingView's text with the engine's bar prefix, `Error on bar N: `, or `Error before the first bar: ` when no bar is being executed. + Patterns the engine's regular-expression library does not support also stop the run now, with the same error. On the Linux build these include lookbehind, named groups, inline flags such as `(?i)`, comment groups, atomic groups, conditionals, `\p{...}` classes and `{,n}` repeats; lookahead, lazy quantifiers, back-references, POSIX classes and `\b` are accepted. +- A memory failure during a session-calendar lookup now fails the run with a typed error instead of reading the bar as out of session. - A percent-of-equity default quantity above 100 percent is now capped at 100 percent of equity when `margin_long` and `margin_short` are both 0 (#805). - With `process_orders_on_close` and the default FIFO close rule, the named `strategy.exit` calls of a reversing `strategy.entry` with an explicit quantity, placed in the same execution after a `strategy.close` of the old position, are now sized from the new entry's own quantity: a percentage exit takes its share of the new entry and an absolute-quantity exit keeps its quantity, instead of being sized from a basis reduced by the old position (#803). - Without `process_orders_on_close`, the named `strategy.exit` calls placed with a reversing `strategy.entry` of explicit quantity now take their percentage from the new entry when its size differs from the position it replaces, and a take-profit given as an absolute quantity now fires; before, the percentage was taken from the position being replaced and an absolute-quantity take-profit never fired (#803). diff --git a/ci/cr150-source-bounds.yaml b/ci/cr150-source-bounds.yaml index 7959e9785..c7d35077e 100644 --- a/ci/cr150-source-bounds.yaml +++ b/ci/cr150-source-bounds.yaml @@ -100,7 +100,10 @@ ceilings: # Three more unreviewed groups: the selected-capacity Source stage's chunk # buffer, its record window and its stored message. None is bounded in the # gate's sense: no fixed input is declared for them. - unreviewed: 1413 + # Two more unreviewed groups: the declarations of the run-error bar prefix + # function, in the installed header and in its source. Each call returns one + # short string and nothing retains it: 1413 -> 1415. + unreviewed: 1415 pending-flat-memory: 184 owners: - {name: "generated-cpp", globs: ["tutorial/**"]} @@ -362,6 +365,7 @@ entries: - {id: "U0230", file: "include/pineforge/order_cancellation.hpp", symbol: "state_", ops: ["assign"], container: "unresolved by the index: state_ = ...", owner: "native-kernel", phase: "unclassified", state: "unreviewed", line: 92} - {id: "U0231", file: "include/pineforge/position_close_obligation.hpp", symbol: "comment", ops: ["decl"], container: "std::string", owner: "native-kernel", phase: "unclassified", state: "unreviewed", line: 15} - {id: "U0232", file: "include/pineforge/run_failure.hpp", symbol: "args", ops: ["assign", "decl", "grow"], container: "std::shared_ptr", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 216} + - {id: "U1800", file: "include/pineforge/run_failure.hpp", symbol: "run_error_bar_prefix", ops: ["decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 252} - {id: "U0233", file: "include/pineforge/run_failure.hpp", symbol: "text", ops: ["assign", "decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 180} - {id: "U0234", file: "include/pineforge/run_failure.hpp", symbol: "value", ops: ["decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 187} - {id: "U0235", file: "include/pineforge/selected_window_plan.hpp", symbol: "input_tf", ops: ["decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 88} @@ -852,6 +856,7 @@ entries: - {id: "U0744", file: "src/run_failure.cpp", symbol: "json", ops: ["alloc", "append", "decl", "grow"], container: "unresolved by the index: make_shared", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 249} - {id: "U0745", file: "src/run_failure.cpp", symbol: "matched", ops: ["decl", "grow"], container: "std::vector>", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 176} - {id: "U0746", file: "src/run_failure.cpp", symbol: "out", ops: ["grow"], container: "unresolved by the index: out.push_back(...)", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 120} + - {id: "U1801", file: "src/run_failure.cpp", symbol: "run_error_bar_prefix", ops: ["decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 408} - {id: "U0747", file: "src/run_failure.cpp", symbol: "std::to_string", ops: ["temp"], container: "unresolved by the index: std::to_string(...)", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 259} - {id: "U0748", file: "src/run_failure.cpp", symbol: "text", ops: ["append", "decl"], container: "std::string", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 318} - {id: "U0749", file: "src/selected_report_view.cpp", symbol: "points", ops: ["alloc"], container: "unresolved by the index: new pf_equity_point_t", owner: "engine-core", phase: "unclassified", state: "unreviewed", line: 54} diff --git a/ci/cr150-source-sites.json b/ci/cr150-source-sites.json index 8f089fda3..7b338efd2 100644 --- a/ci/cr150-source-sites.json +++ b/ci/cr150-source-sites.json @@ -8414,6 +8414,16 @@ "spelling": "std :: shared_ptr < const std :: string >", "ordinal": 1 }, + { + "file": "include/pineforge/run_failure.hpp", + "symbol": "run_error_bar_prefix", + "scope": [ + "namespace pineforge #1" + ], + "cls": "decl", + "spelling": "std :: string", + "ordinal": 1 + }, { "file": "include/pineforge/run_failure.hpp", "symbol": "", @@ -27346,6 +27356,27 @@ "spelling": "json . push_back ( . . . )", "ordinal": 2 }, + { + "file": "src/run_failure.cpp", + "symbol": "run_error_bar_prefix", + "scope": [ + "namespace pineforge #1" + ], + "cls": "decl", + "spelling": "std :: string", + "ordinal": 1 + }, + { + "file": "src/run_failure.cpp", + "symbol": "std::to_string", + "scope": [ + "namespace pineforge #1", + "std :: string run_error_bar_prefix ( ) #1" + ], + "cls": "temp", + "spelling": "std :: to_string ( . . . )", + "ordinal": 1 + }, { "file": "src/run_failure.cpp", "symbol": "text", diff --git a/docker/run_failure_codes.json b/docker/run_failure_codes.json index 71cdb2406..6733ebcab 100644 --- a/docker/run_failure_codes.json +++ b/docker/run_failure_codes.json @@ -835,7 +835,9 @@ "kind": "vocab", "values": [ "substring_out_of_range", - "format_index_overflow" + "format_index_overflow", + "invalid_regex_pattern", + "regex_resource_limit" ] } }, @@ -843,7 +845,9 @@ "description": "A string built-in refused its arguments.", "english": [ "basic_string::substr: __pos (which is {pos}) > this->size() (which is {size})", - "stoul" + "stoul", + "Invalid regex pattern in the 'str.match' function.", + "A regular expression exceeds the limits of the engine's regular-expression library." ], "retryable": false, "since": "1.4.0" diff --git a/docker/run_failure_codes_diff.json b/docker/run_failure_codes_diff.json index e35ada1e3..3111d6bb8 100644 --- a/docker/run_failure_codes_diff.json +++ b/docker/run_failure_codes_diff.json @@ -8,7 +8,7 @@ "to": { "version": null, "unreleased": true, - "catalogSha256": "7e903f977be655901c877a3550008aeee19b7f69a9ecfad986ca6f0d9129c3be" + "catalogSha256": "440a8f9899a8bdbdff8a3e0f7e1c236854d469cbb05f7af542b0d392646330ab" }, "catalogSchema": { "before": "pineforge-run-failure-catalog/v1", @@ -16,7 +16,44 @@ }, "metadataChanges": [], "added": [], - "changed": [], + "changed": [ + { + "code": "pine_string_error", + "collection": "codes", + "fields": [ + { + "path": "args.reason.values", + "beforePresent": true, + "afterPresent": true, + "before": [ + "substring_out_of_range", + "format_index_overflow" + ], + "after": [ + "substring_out_of_range", + "format_index_overflow", + "invalid_regex_pattern", + "regex_resource_limit" + ] + }, + { + "path": "english", + "beforePresent": true, + "afterPresent": true, + "before": [ + "basic_string::substr: __pos (which is {pos}) > this->size() (which is {size})", + "stoul" + ], + "after": [ + "basic_string::substr: __pos (which is {pos}) > this->size() (which is {size})", + "stoul", + "Invalid regex pattern in the 'str.match' function.", + "A regular expression exceeds the limits of the engine's regular-expression library." + ] + } + ] + } + ], "removed": [], "deprecated": [] } diff --git a/docs/adr/0001-kernel-adapter-boundary.md b/docs/adr/0001-kernel-adapter-boundary.md index dc7c6cd62..0701bffe1 100644 --- a/docs/adr/0001-kernel-adapter-boundary.md +++ b/docs/adr/0001-kernel-adapter-boundary.md @@ -78,7 +78,7 @@ summary of it. (`native_host.hpp:913`); also `on_native_input` (`native_host.hpp:895`), `on_native_tick` (`native_host.hpp:898`), `on_native_timeframe_bar` (`native_host.hpp:905`), `on_native_bar_open` — at the modeled opening, before its matching pass - (`native_host.hpp:909`, `on_native_bar_open` `native_execution_consumer.cpp:8637`) — + (`native_host.hpp:909`, `on_native_bar_open` `native_execution_consumer.cpp:8650`) — `on_native_recalculate` for every calculation of the run (`native_host.hpp:933`), `on_native_sub_bar` (`native_host.hpp:948`) and post-fill `on_native_applied` (`native_host.hpp:958`). `on_bar` is `final` (`native_host.hpp:877`). @@ -112,7 +112,7 @@ summary of it. bare host needs no override at all, while `HostSized` hands the units to the host — and an unresolved `HostSized` whose `resolve_execution_terms` (`native_host.hpp:967`) returns no units is a `TermsUnresolved` rejection - (`TermsUnresolved` `native_execution_consumer.cpp:6258`). + (`TermsUnresolved` `native_execution_consumer.cpp:6271`). - **Observing executions.** Implement `on_native_applied` (`native_host.hpp:958`) — notifications drain FIFO after the outer callback returns — and/or poll `native_events(after_ordinal)` (`native_host.hpp:1371`). Do *not* implement @@ -123,7 +123,7 @@ summary of it. | What | Surface | |---|---| | Run state | `native_state()` → `NativeStateView` — kind, phase, `NativeCompletion` BatchComplete / StreamEnded, failure, high water (`NativeCompletion` `native_host.hpp:50`, `NativeStateView` `:282`, `NativeRunPhase` `:41`) | - | Failure model | `NativeFailureCode` (`native_host.hpp:63-77`); `Failed` is latched, nothing resumes. A throwing callback latches `CallbackException` (`CallbackException` `native_execution_consumer.cpp:6231`) | + | Failure model | `NativeFailureCode` (`native_host.hpp:63-77`); `Failed` is latched, nothing resumes. A throwing callback latches `CallbackException` (`CallbackException` `native_execution_consumer.cpp:6244`) | | Cohorts | `cohort_open` (`native_host.hpp:1315`) / `cohort_add` (`:1318`) / `cohort_remove` (`:1321`) — the handle a `BindCohort` owner names | | Mid-callback execution | `current_execution_point` (`native_host.hpp:1107`), `inspect_current_execution` (`native_host.hpp:1119`) / `execute_current` (`native_host.hpp:1124`) | | Margin seams | four, not one: the run spec's own `margin` model (`NativeMarginModel` `native_run_spec.hpp:240`) with its three hooks — `resolve_margin_requirement` (`native_host.hpp:996`), `margin_check_allowed` (`native_host.hpp:1008`), `resolve_margin_call_units` (`native_host.hpp:1015`) — and `validate_execution_precommit` (`native_host.hpp:980`), whose `AdmitWithHostMargin` verdict hands the opening check to the host | @@ -561,6 +561,7 @@ ruled by its words. Each is listed here with its ruling. | `matrix.cols() == matrix.rows()` | the `eigen_assert` text of Eigen's eigenvalue solvers (`Eigen/src/Eigenvalues/RealSchur.h`, `ComplexSchur.h`, `SelfAdjointEigenSolver.h`), which `src/matrix.cpp` instantiates. An `assert` compiles in only when `NDEBUG` is off -- the debug and sanitizers profiles -- so the text is absent under NDEBUG and no release archive carries it | **not residue**: `matrix` is Eigen's parameter, not Pine's `matrix.` namespace, and the kernel does not write the text. It is ruled by its exact words so that the dotted-namespace vocabulary (the documentation consistency checks) keeps reading every other text of a debug archive; the first sanitizers run of that vocabulary found it (a maintainers' remote run). | | `pine_array_error`, `pine_invalid_argument`, `pine_map_capacity_exceeded`, `pine_matrix_error`, `pine_na_reference`, `pine_runtime_limit`, `pine_string_error`, `syminfo_unreadable` | run-failure code names of the catalog `docker/run_failure_codes.json` (schema `pineforge-run-failure-catalog/v1`): the `RunFailureCode` enumerators of the installed `include/pineforge/run_failure_codes.hpp`, and the name table of `src/run_failure_registry.inc`, which the kernel translation unit `src/run_failure.cpp` compiles; both are generated from the catalog (`scripts/check_run_failure_codes.py --write-registry`) | **retained as the public run-failure wire vocabulary (`docker/run_failure_codes.json`)**: the strings `strategy_get_last_error_code` answers. A published code is never renamed, removed or reused: `scripts/check_run_failure_codes.py` holds the catalog against the newest release tag's, and `scripts/gen_run_failure_catalog_diff.py` publishes each release's changes. Each name states the failure in the terms of the script that caused it -- a Pine array, matrix or string built-in, a map capacity limit, a na reference, a Pine runtime limit, an unreadable `syminfo` description -- so a consumer classifies and localizes it without parsing the English. The kernel decides nothing by them: the registry validates a raised code and its arguments, and the getters read them back. | | `Pine native run counter exhausted` | `src/selected_capacity_core.cpp`, `src/selected_capacity_driver.cpp` and `src/selected_capacity_csv_reader.cpp`: the `engine_invariant` text thrown when a 64-bit selected-capacity, driver or CSV byte counter would wrap | **retained**: the text is one of the closed English texts of `engine_invariant` in `docker/run_failure_codes.json`, byte for byte the one `src/source/pine_adapter.cpp` throws when its run counter is exhausted; the selected-capacity sources reuse it for their own counters, so no new English text enters the catalog. A neutral spelling is a failure-catalog change (the text is in the catalog and its generated tables) and an open decision. | +| `Invalid regex pattern in the 'str.match' function.` | `src/str_utils.cpp`: the text `str_match` passes to the `pine_string_error` stop (reason `invalid_regex_pattern`) when the regular-expression library rejects a pattern, syntax it does not support included; the engine puts its bar prefix before it at run time | **retained**: TradingView's own message for an invalid pattern, byte for byte, so that a script's author reads the same words on both platforms and the run stops with them instead of continuing on an empty string; the text is the catalog's closed English text of `pine_string_error` for that reason (`docker/run_failure_codes.json` and its generated tables) and the kernel decides nothing by it. A neutral spelling is a failure-catalog change and an open decision (compare the row of `Pine native run counter exhausted`). | | `request.security`, `request.security_lower_tf`, `matrix.new`, `matrix.get`, `matrix.set`, `matrix.row`, `matrix.col`, `matrix.add_row`, `matrix.add_col`, `matrix.remove_row`, `matrix.remove_col`, `matrix.swap_rows`, `matrix.swap_columns`, `matrix.submatrix`, `matrix.concat`, `matrix.reshape`, `matrix.restore`, `ta.pivot_point_levels`, `max_bars_back`, `nested_heikinashi_request`, `stream_calc_on_order_fills`, `fx_series_calc_on_order_fills`, `syminfo_not_object`, `syminfo_mintick_invalid` | argument values of the same catalog: the closed lists of `other_symbol_request` and `request_timeframe_invalid` (`function`, `api`), `pine_matrix_error` (`function`), `pine_runtime_limit` (`limit`), `request_unsupported` and `symbol_feeds_refused` (`reason`) and `run_mode_unsupported` (`feature`), in the value table of `src/run_failure_registry.inc`; and the argument literals the raise sites spell -- the coded throws of the kernel units `src/matrix.cpp` and `src/ta_misc.cpp` and of the installed `include/pineforge/generic_matrix.hpp` | **retained as the public run-failure wire vocabulary (`docker/run_failure_codes.json`)**: the values `strategy_get_last_error_args` answers, each the built-in, option, limit or reason a failure names, in the terms the catalog publishes. A closed list's value is never renamed or removed, and the registry turns a value outside its list into `engine_invariant`, so neither a script nor a typo mints one; an identifier value (`ta.pivot_point_levels`) is the built-in's own name, spelled at its raise site. | | `pine_no_data_stop`, `pine_other_symbol_stop`, `pine_array_stop`, `pine_collection_stop`, `pine_na_stop`, `pine_limit_stop`, `pine_unsupported_stop`, `pine_string_stop`, `pine_engine_invariant`, `pine_source`, `line.get_price` | the installed header `include/pineforge/run_failure.hpp`: the inline `[[noreturn]]` helpers of the codegen contract `PINEFORGE_HAS_RUN_FAILURE_CODES_V1`, and the argument kind `RunFailureArgKind::pine_source` (the catalog's kind for source text the transpiler emitted as a literal) -- kernel units include the header, but none calls a helper and an enumerator is no symbol; and the `function` literal of the line-price check in `include/pineforge/drawing.hpp`, outside the kernel's compile closure; so no archive symbol | **retained as the public run-failure wire vocabulary (`docker/run_failure_codes.json`)**: the codegen's half of the channel, beside `pine_runtime_error` (the `PineMap` row above). Each helper hardwires one code of the catalog and takes only the literals the transpiler emits, so a generated stop carries its code while the kernel knows no Pine; `pine_source` is a catalog kind and `line.get_price` an argument value, as in the two rows above. A neutral spelling of the helpers is a codegen change, the GAP-15 decision the `PineMap` row records. | @@ -760,17 +761,17 @@ number names its symbol on this tree. | surface | ruling | reason | |---|---|---| | `cancel_all` (`native_host.hpp:1300`) / `cancel_where` answer a count, not a `CancelResult` per request (E12 f7) | **retained** | A bulk cancel is many commands in one call, and each withdrawn request records its own `CancelledEvent` with its own `CancelReason` — dependants of a cancelled owner included — in the history a host already polls (`strategy_native_events_v1`). That is the per-request answer; the count is the call's summary. A result vector would restate the history in a second, allocating shape that C could not take without a caller-sized array. The C spellings answer the same count (`strategy_native_cancel_all_v1` `native_c_api.h:2820`). | -| `cohort_add` (`native_host.hpp:1318`) / `cohort_remove` answer `void` (E12 f7) | **retained; the typed receipt is a named follow-up** | The kernel judges every enrolment into a `CohortReceipt` (Applied, InvalidHandle, UnknownOrigin, TerminalOrigin) and folds it into the continuation (`hash_cohort_receipt` `native_execution_consumer.cpp:937`, since v19 once, at the enrolment, into the running digest the continuation carries), so a replay that diverges there diverges in the hash. Nothing that matches or settles reads it: a roster is a relation the close reads at its match, and a refused enrolment is a member the close does not take. A host that needs the receipt AT the call needs `NativeStrategyHost::cohort_add_result` first, a kernel-header change outside this lane; its C spelling would then be an `_ext_v1` with a receipt word. Until then `strategy_native_cohort_add_v1`'s `PF_NATIVE_OK` means the enrolment was issued. | +| `cohort_add` (`native_host.hpp:1318`) / `cohort_remove` answer `void` (E12 f7) | **retained; the typed receipt is a named follow-up** | The kernel judges every enrolment into a `CohortReceipt` (Applied, InvalidHandle, UnknownOrigin, TerminalOrigin) and folds it into the continuation (`hash_cohort_receipt` `native_execution_consumer.cpp:938`, since v19 once, at the enrolment, into the running digest the continuation carries), so a replay that diverges there diverges in the hash. Nothing that matches or settles reads it: a roster is a relation the close reads at its match, and a refused enrolment is a member the close does not take. A host that needs the receipt AT the call needs `NativeStrategyHost::cohort_add_result` first, a kernel-header change outside this decision; its C spelling would then be an `_ext_v1` with a receipt word. Until then `strategy_native_cohort_add_v1`'s `PF_NATIVE_OK` means the enrolment was issued. | | `native_sized_units` (`native_host.hpp:1350`) / `native_liquidation_price` answer `std::optional` (E12 f7) | **retained** | Observation queries, not commands: each empty is documented cause by cause in its own comment (unconfigured, non-positive money or denominator, a below-one-step quotient; no margin model, no maintenance fraction for the side, a flat book, no finite solution), and every cause is a fact the host can read itself. A typed reason would name what the caller already knows. C answers `PF_NATIVE_ABSENT` and NaN (`strategy_native_liquidation_price_v1` `native_c_api.h:3099`). | | `pf_native_working_v1` without the leg's anchor or owner relation (E13 f4) | **carried** | The C++ host reads them off `NativeWorkingRequest::definition`, so the C readout's relation tail carries them: `anchor` (`native_c_api.h:1730`) through `arm_scope`, the request as the kernel holds it now (an armed leg reads ABSOLUTE with its installed level). | | `hash_source_extension` (`engine.hpp:583`) | **C++-only** | The deprecated spelling of `hash_host_extension`, kept so an existing C++ subclass compiles and folds unchanged. A C host has only ever had the current spelling, `on_hash_extension`. | | `NativeReportPolicy::KernelRecordedAtHostMarks` (`native_run_spec.hpp:64`) | **C++-only** (lane E22's ruling, restated) | Under it the host names each report point from inside its own callbacks, and the C table has no call that marks one: a C host could only declare a series nobody records. `pf_native_report_policy_e` (`native_c_api.h:673`) leaves its value unnamed, and the enum guard holds that exclusion. A C++ host now reaches the same kernel producer through `NativeStrategyHost::mark_native_report_point`; a mark outside a running HostMarks run returns false. | | `strategy_set_path_order` (`pineforge.h:1207`) clamps an out-of-range mode to AUTO | **retained** | A `void` ABI v4 setter cannot answer a refusal. Its values are now documented as `pf_native_path_order_e`'s (`native_c_api.h:916`); a C host that wants an unknown word refused declares `path_order` in `pf_native_run_spec_ext_v1`, which answers `PF_NATIVE_E_TAG`. | | the enumerated `int32` fields of `pf_pending_order_v1_t` (`strategy_pending_order_get` `pineforge.h:1355`) | **no C names in the header; generator-owned** | The mirror is generated from the Pine source host's resting-order record by `scripts/gen_pending_order_mirror.py` (held by its `--check` source guard) and self-described at run time by `strategy_pending_order_layout`. Its enumerated fields are the source layer's order vocabulary, not a kernel enumeration. A C name for them belongs in that generator, which owns the struct, so that it cannot drift from the mirror — never hand-written beside it. | -| `strategy_configure_native_v1` (`c_abi.cpp:1143`) versus `strategy_configure_native_ext_result_v1` (`native_c_host.cpp:4107`) | **retained legacy base plus typed additive route** | A base specification the kernel rejects still returns `-1` and latches `Failed`, preserving its frozen caller contract; a Ready misuse retains that Contract latch. The C boundary refuses arguments, a Running phase and a Completed hash hook without entering kernel configure, so a read cannot reconfigure its host. The extended result call validates before its first configure, writes `pf_native_spec_error_t` / `pf_native_spec_field_t`, and names reused-host session-key and run-number refusals; no old signature or layout moved. | +| `strategy_configure_native_v1` (`c_abi.cpp:1143`) versus `strategy_configure_native_ext_result_v1` (`native_c_host.cpp:4133`) | **retained legacy base plus typed additive route** | A base specification the kernel rejects still returns `-1` and latches `Failed`, preserving its frozen caller contract; a Ready misuse retains that Contract latch. The C boundary refuses arguments, a Running phase and a Completed hash hook without entering kernel configure, so a read cannot reconfigure its host. The extended result call validates before its first configure, writes `pf_native_spec_error_t` / `pf_native_spec_field_t`, and names reused-host session-key and run-number refusals; no old signature or layout moved. | | `strategy_configure_native_fx_curve_v1` (`pineforge.h:551`) | **typed additive route** | `strategy_configure_native_fx_curve_ext_v1` writes the C twin of `NativeFxCurveValidation` and its offending index. The old `-1` route remains unchanged, while a C caller can now distinguish a decreasing timestamp, non-positive rate, allocation failure or wrong phase. | | `on_native_timeframe_bar` (`native_host.hpp:905`) and `NativeTimeframeBarContext::interval` (`native_host.hpp:829`) | **query routed** | The callback signature stays frozen. `strategy_native_timeframe_bar_interval_v1` is valid only during that callback and copies the kernel calendar interval without deriving it from the delivered bar or delivery timestamp. | -| `magnifier_sub_bars_total` / `magnifier_sample_ticks_total` (`engine_report.cpp:380-381`) | **kernel-owned report facts** | The generic intrabar driver already increments `NativeDriverStatistics::sub_bars_processed` / `sample_ticks_processed` for every retained path (`native_execution_consumer.cpp:9380-9389`); the kernel now publishes those same counters for bare hosts. They are report fields, not new durable state: the existing `native_continuation_hash` fold of `NativeDriverStatistics` (`native_execution_consumer.cpp:1132-1138`) sees the same values, so no hash contract moves. | +| `magnifier_sub_bars_total` / `magnifier_sample_ticks_total` (`engine_report.cpp:380-381`) | **kernel-owned report facts** | The generic intrabar driver already increments `NativeDriverStatistics::sub_bars_processed` / `sample_ticks_processed` for every retained path (`native_execution_consumer.cpp:9403-9412`); the kernel now publishes those same counters for bare hosts. They are report fields, not new durable state: the existing `native_continuation_hash` fold of `NativeDriverStatistics` (`native_execution_consumer.cpp:1132-1138`) sees the same values, so no hash contract moves. | | `pf_native_lot_excursion_v1` (`native_c_api.h:1924`) | **additive fee tail** | `entry_commission` carries the C++ `ClosedLotExcursionFacts::entry_commission` in account currency. The callback table publishes a trailing layout marker so F4 callers still receive the base facts length; current callers receive the fee tail without changing `PF_NATIVE_API_VERSION`. | | `strategy_outputs_set_enabled` and the eight `strategy_outputs_*` readers (`pineforge.h`, group `pf_outputs`) | **additive runtime exports** | Recording is off by default, and nothing a run computes reads the record. The event copy is size-prefixed like `strategy_pending_order_get`, and the current `PF_ABI_VERSION` is 5. The writers are C++ only in this version (the BASE-CLASS SEAMS rows of `native_c_api.h`): a C host declares no outputs, so its handle answers every reader as empty. | @@ -829,7 +830,7 @@ The table records, at the tree the doc wave read, what each block described and | `ta::ATR` `include/pineforge/ta.hpp:365` (the `TradingView` comment `sha256:4f6f6939aec2773684b114a13dd002eb58990d6d96c3056ad87361753faf7a0a` :377-384) | average true range over Wilder's `RMA` | Wilder's ATR is the textbook definition; the comment records which convention and the tape that pinned the recompute behaviour | a seeding/averaging field on an indicator whose definition is the convention | `tests/test_ta_rma_warmup.cpp`, `tests/test_recompute.cpp` | | `ta::StdDev` `include/pineforge/ta.hpp:498` (the `TradingView` comment `sha256:7cc1bbcd32dfe673285d51a8ca7080890ac551858099b523480d998f46408666` :488-497) | the population (biased) standard deviation | biased vs sample is a textbook choice, and the kernel states which | a per-instance switch is the *right* answer where two conventions genuinely compete, which is why `ta::EmaSeeding` exists and this one does not need it | `tests/test_ta_indicators_extras.cpp` | | `float_band_eq` `include/pineforge/ta_compare_band.hpp:38` | an absolute 1e-10 float comparison for indicator equality | an epsilon band is needed by any float comparison; the width is the calibration | a band-width field on a comparison the indicators share | `tests/test_dmi_parity.cpp`, `tests/test_ta_osc_edge.cpp` | -| the two delivery rules, `projected_first_index` `src/native_execution_consumer.cpp:10484` (lookahead) and `clear_if_gapped` `:10471` (gaps) | deliver a completed bucket at its **first** contributing bar (lookahead), and **clear** the series on a bar it publishes nothing on (gaps) | both are delivery rules over a bucket the aggregator already closed; neither reads a platform. They are spec fields: `NativeTimeframeSubscription::lookahead` / `::gaps` | already spelled, and both default to the rule that moves no identity | `tests/test_native_htf_subscriptions.cpp` | +| the two delivery rules, `projected_first_index` `src/native_execution_consumer.cpp:10507` (lookahead) and `clear_if_gapped` `:10494` (gaps) | deliver a completed bucket at its **first** contributing bar (lookahead), and **clear** the series on a bar it publishes nothing on (gaps) | both are delivery rules over a bucket the aggregator already closed; neither reads a platform. They are spec fields: `NativeTimeframeSubscription::lookahead` / `::gaps` | already spelled, and both default to the rule that moves no identity | `tests/test_native_htf_subscriptions.cpp` | | `pf_native_subscription_v1::lookahead` `include/pineforge/native_c_api.h:2283`, `::gaps` `include/pineforge/native_c_api.h:2287` | the C spelling of those two rules | since the typed delivery enums both words are typed and named after the kernel's own delivery rules (`pf_native_lookahead_e` `include/pineforge/native_c_api.h:811`, `pf_native_gaps_e` `:822`); the C header no longer names `barmerge` at all, and the migration page maps Pine's words onto them — a *pointer to the reader's vocabulary*, not a justification | — | `tests/test_native_c_api.c` | | the authoritative-feed partition `src/engine_aux_security.cpp` | a feed is the venue's own bars of one timeframe; its stamps are the period partition | already ruled: design §2.ii row s, and the residual table's own row | the policy knob is installing the feed or not | `tests/test_native_htf_subscriptions.cpp`, `tests/test_native_security_feed.cpp` | | `session_template_knows_early_close` `src/engine_security.cpp` | the instrument-class classification | already ruled: design §2.ii row t; `SymInfo::type`'s vocabulary is fixed by the frozen C ABI | — | the private test `test_native_wm_buckets` | @@ -843,9 +844,9 @@ The table records, at the tree the doc wave read, what each block described and | the indicator library -- `include/pineforge/ta.hpp`, `src/ta_moving_averages.cpp`, `src/ta_oscillators.cpp`, `src/ta_extremes_volume.cpp`, `src/ta_volatility_trend.cpp`, `src/ta_misc.cpp`, `include/pineforge/window_sum.hpp`, `include/pineforge/math.hpp` | each indicator implements its Pine `ta.*` / `math.*` definition -- warm-up and na rules, the window call-site ring, the compensated window sum, tie and epsilon rules -- and its comments record the TradingView tape or export that calibrated each number (the `ta::ATR` and standard-deviation rows above are two of them) | an indicator is a function of its inputs, the same for every venue; generated strategies call exactly these classes, and where two textbook conventions genuinely compete the choice is a per-instance option (`ta::EmaSeeding`), never a branch on the platform | a convention switch on every indicator, for a choice the script already made by calling that Pine function | the kernel-only TA suites `tests/test_ta_*.cpp` and the corpus parity gate | | the formatting library -- `include/pineforge/str_utils.hpp`, `src/str_utils.cpp` (`str_tostring`, `str_format_values`, `str_format`) | the number text of Pine's `str.tostring` / `str.format` built-ins: a number's shortest round-trip decimal spelling, scaled and rounded half-up on its digits, the default, `percent`, `volume` (K / M / B / T) and pattern modes, MessageFormat placeholders and quoting, `NaN` and `Infinity` -- the published rendering TradingView's exported tapes pin (the number-format measurement), ported from the codegen formatter (the formatter edge comparison) by the source arithmetic correction; the `mintick` mode, which generated code delegates to it, is kept as it was | a formatting library is a function of its inputs, the same for every venue, as the indicator library is: it spells a number the way the source language's own `str.*` library defines, and no kernel order, fill, settlement, margin or hash code calls it -- of the thirty-five kernel translation units only `src/str_utils.cpp` names these functions | a rendering switch on functions whose definition the calling script chose by calling them | `tests/test_str_number_format.cpp` (the formatter edge comparison's 42-case edge battery, every numeric string of the number-format measurement's TradingView exports, byte-identical output on macOS arm64 and Linux x86-64) and `tests/test_str_utils.cpp` | | the language calendar functions -- `include/pineforge/session_time.hpp`, `src/session_time.cpp` (`timeframe_time`, `local_hour` and its siblings, `session_in_market`, `session_trading_day_open_ms`, ...) | the definitions of Pine's `time()` / `time_close()` / bare-time / `session.is*` built-ins for generated code: session and timezone parsing, the invalid- and 24-hour-session spellings, `time("60")` day keying, `session.is*` on daily charts, each comment naming the tape that pinned it | the inputs are data -- a session string, a timezone, a timeframe -- so another venue is driven by its own session template; nothing branches on a platform, and a native host's calendar is the separate generic `native_calendar` | a rules switch on functions whose definition the generated script chose by calling them | `tests/test_session_time.cpp`, `tests/test_pine_time_day_stamp_grid.cpp` and the corpus parity gate | -| `present_session_day` `src/native_execution_consumer.cpp:8849`, presenting `NativeDecisionContext::in_session` `include/pineforge/market_driver.hpp:161` and the three facts after it | a script bar's session-day facts: in session on the run's calendar, read at the bar's label, or at its interval's first eligible instant when that is later (a bar opening in a declared break), never at an instant before the label; it opens / closes its session day when the bar before / after it is out of session or on another session day, that bar being the one the run holds (batch input, stream warmup) and otherwise the calendar's previous or next eligible input slot across declared breaks; the run's first bar opens its day, a batch's final bar closes it, a D/W/M bar holds whole days | the session, the timezone and the session day are the run's own calendar (`spec.session`, `spec.timezone`, `native_calendar::session_day_ordinal`), the neighbours are the run's own input, and a second venue drives it through that data alone. The reading is TradingView's session DAY, measured on its tapes by the session-day comparison (the private fixture set `session_islastbar`: 255/255 NYSE:F last bars, 255/255 first, 370/370 ETH last), which a bare host now reproduces from the kernel alone. Reading a bar at its own label is generic too: an instant before the label is not the bar's, whatever grid the calendar keys its interval to (the session-window calculation: NASDAQ:AAPL with extended hours opens its bars on the hour, and the 16:00 bar, which the 09:30-anchored grid files under 15:30, read in session and closed the regular day in place of the 15:00 bar; TSE:7203's and CBOT:ZC1!'s 60-minute bars that open inside a break are in session on TradingView's tapes, which reading at the label alone would lose) | a switch over the run-end convention or the neighbour rule would put a hash-visible choice on the surface for what the run's own input already decides. The one host that reads its batch end differently — a live probe recomputing a batch whose last input is still forming — reads a second fact, `closes_session_day_open_ended`, not a switch; the facts are presentation and fold into no digest | the private test `test_native_session_day_facts` (kernel-only: the tapes, the audit's Tokyo `2230-0500` session, streams, a fill recalculation), `tests/test_native_session_day_bar_open.cpp` (kernel-only: TradingView's extended-hours and break-open tapes, the private fixture set `session_windows`), the C scenario of `tests/test_native_c_api.c`, the private test `test_session_day_facts_adapter` (the adapter selects them flag for flag), `tests/test_native_session_day_utc.cpp` (kernel-only: on a UTC calendar the facts read an in-session instant without resolving its interval, and match the zone path at every decision point), and `tests/test_native_session_day_zoned.cpp` (kernel-only: on a certified cycle of an exchange zone, which `native_calendar::cycle_certificate` decides and the witness checks at real transitions, the facts read an instant off its session day, and match the zone's POSIX twin at every decision point) | +| `present_session_day` `src/native_execution_consumer.cpp:8862`, presenting `NativeDecisionContext::in_session` `include/pineforge/market_driver.hpp:161` and the three facts after it | a script bar's session-day facts: in session on the run's calendar, read at the bar's label, or at its interval's first eligible instant when that is later (a bar opening in a declared break), never at an instant before the label; it opens / closes its session day when the bar before / after it is out of session or on another session day, that bar being the one the run holds (batch input, stream warmup) and otherwise the calendar's previous or next eligible input slot across declared breaks; the run's first bar opens its day, a batch's final bar closes it, a D/W/M bar holds whole days | the session, the timezone and the session day are the run's own calendar (`spec.session`, `spec.timezone`, `native_calendar::session_day_ordinal`), the neighbours are the run's own input, and a second venue drives it through that data alone. The reading is TradingView's session DAY, measured on its tapes by the session-day comparison (the private fixture set `session_islastbar`: 255/255 NYSE:F last bars, 255/255 first, 370/370 ETH last), which a bare host now reproduces from the kernel alone. Reading a bar at its own label is generic too: an instant before the label is not the bar's, whatever grid the calendar keys its interval to (the session-window calculation: NASDAQ:AAPL with extended hours opens its bars on the hour, and the 16:00 bar, which the 09:30-anchored grid files under 15:30, read in session and closed the regular day in place of the 15:00 bar; TSE:7203's and CBOT:ZC1!'s 60-minute bars that open inside a break are in session on TradingView's tapes, which reading at the label alone would lose) | a switch over the run-end convention or the neighbour rule would put a hash-visible choice on the surface for what the run's own input already decides. The one host that reads its batch end differently — a live probe recomputing a batch whose last input is still forming — reads a second fact, `closes_session_day_open_ended`, not a switch; the facts are presentation and fold into no digest | the private test `test_native_session_day_facts` (kernel-only: the tapes, the audit's Tokyo `2230-0500` session, streams, a fill recalculation), `tests/test_native_session_day_bar_open.cpp` (kernel-only: TradingView's extended-hours and break-open tapes, the private fixture set `session_windows`), the C scenario of `tests/test_native_c_api.c`, the private test `test_session_day_facts_adapter` (the adapter selects them flag for flag), `tests/test_native_session_day_utc.cpp` (kernel-only: on a UTC calendar the facts read an in-session instant without resolving its interval, and match the zone path at every decision point), and `tests/test_native_session_day_zoned.cpp` (kernel-only: on a certified cycle of an exchange zone, which `native_calendar::cycle_certificate` decides and the witness checks at real transitions, the facts read an instant off its session day, and match the zone's POSIX twin at every decision point) | | `NativeExecutionConsumer::script_bucket_completions` `src/native_execution_consumer.hpp:554` | before a run, how an input span aggregates into script bars: which input bars complete one, and which complete one on a period boundary, under the run's own timeframes, zone and session (the execution-rate and clock correction, item 4) | any host that aggregates a finer feed asks it before its first input; the answer is the run's own `TimeframeAggregator` over the span and spec fields -- the real-end / chart-close completion and the session-day split this table already rules generic -- and nothing branches on a platform | none: a const query with no spec field, nothing hashed and no state moved; the host decides what the answer means for it (the Pine scheduler reads it instead of running its own preview aggregator) | `tests/test_native_script_bucket_completions.cpp` (kernel-only: the Pine scheduler's preview on the K1 zones, across DST, windowed sessions and 1D/1W/1M) | -| `PhysicalExecutionContext::account_fx` `include/pineforge/execution.hpp:92`, read by `build_close_trade_with_costs` `src/engine_orders.cpp:87-107` and handed by `append_open_position_report_rows` `src/native_execution_consumer.cpp:9608-9620` | a closing or mark-to-market row converts at the account-currency rate its execution names -- a mark at a report point's instant, a fill at its own cursor -- and at the presented clock's rate when it names none, as before (the execution-rate and clock correction, item 3) | the rate is data a host of any venue supplies with the row it asks for; nothing branches on a platform, and the row's currency is the basis its provider's magnitudes are recorded in | none: no spec field; a caller names the rate per call, and a call naming none converts exactly as before | `tests/test_adapter_range_end_fx.cpp` (a stepped FX curve in eight cases around the range end, plain and aggregated charts) | +| `PhysicalExecutionContext::account_fx` `include/pineforge/execution.hpp:92`, read by `build_close_trade_with_costs` `src/engine_orders.cpp:87-107` and handed by `append_open_position_report_rows` `src/native_execution_consumer.cpp:9631-9643` | a closing or mark-to-market row converts at the account-currency rate its execution names -- a mark at a report point's instant, a fill at its own cursor -- and at the presented clock's rate when it names none, as before (the execution-rate and clock correction, item 3) | the rate is data a host of any venue supplies with the row it asks for; nothing branches on a platform, and the row's currency is the basis its provider's magnitudes are recorded in | none: no spec field; a caller names the rate per call, and a call naming none converts exactly as before | `tests/test_adapter_range_end_fx.cpp` (a stepped FX curve in eight cases around the range end, plain and aggregated charts) | | `NativeExecutionConsumer::execute_current` `src/native_execution_consumer.hpp:292` | a current execution converts at its cursor's rate, threaded through its terms facts, inspection, preview and settlement contexts, and neither writes nor restores the host's presented clock, so its own hooks and the host after it see the frame's clock; its preview `inspect_current_execution` still converts at the presented clock (the execution-rate and clock correction, item 5) | an execution converting at its own instant's rate is the same for every host and venue, and the host's presented clock stays its own | none: the rate is the one the run already chose (the cursor's instant), carried as data | `tests/test_native_current_execution_rate.cpp` (kernel-only) | | `NativeExecutionConsumer::validate_current_execution` `src/native_execution_consumer.hpp:800` | a current execution refuses a request bound to a host roster (`BindCohort`) as `UnsupportedRequest`, before its point is taken and whatever the roster holds: it admits exactly the parent requests the evaluation's current shape executes (`current_shape` `src/native_order.cpp:765`: `Independent`, `BindOpening`, `BindOpenings`). Since R4 slice C it had admitted `BindCohort` as well, which the evaluation then left unevaluated, so a roster holding a live lot failed the run with "native current evaluated allowance mismatch" and an empty one answered `UnreadyOwner`; its read-only preview (`inspect_current_execution`) settled what the execution could not (the recalculation fill-order correction) | one legality rule, the evaluation's own, decides a command before any point is taken, the same for every host and for the C surface (`strategy_native_execute_current_v1` answers through this validation); a roster read at the match is not a membership fixed at the command, which is what the documented contract names (`docs/pages/native-engine.md` "Selected exposure and current execution") | none: no spec field or hook. A refused request stays queued and fills at the next match like any market close; a host that wants a cohort's lots closed at once fixes its roster first (`BindOpenings`), as the Pine adapter's immediate closes do | `tests/test_native_current_cohort_refusal.cpp` (kernel-only: a live and an empty roster refused, then filled as queued closes; a fixed roster executing at once) , `tests/test_native_cohort_sliced_close.cpp` (its preview rows: the roster close's preview is refused, the same selection's settles) and `tests/test_adapter_live_state_equivalence.cpp` (reversals seed 3246599, which the failure used to end) | | the chart symbol's daily partition -- `prepare_chart_day_partition` `src/engine_aux_security.cpp:359`, kept in `chart_day_partition_` `include/pineforge/engine.hpp:1513` and presented to the language runtime through `RuntimeAmbient::day_partition` `src/runtime_ambient.hpp:39` (the TradingView comment above `NativeDayPartition` in `include/pineforge/timeframe.hpp`) | on an intraday chart whose run installed its own symbol's `D` feed, the chart-level D period follows that feed's stamps: the exchange's trade-date bars, a holiday-merged day included. It governs `time("D")`, `timeframe.change("1D")`, `ta.change(time("D"))`, the default-anchored `ta.vwap` and `crosses_boundary(DAY)`. Without the feed, on a 1D chart or on another symbol's clock every rule is the nominal session day | the stamps are the venue's own data, as for the authoritative-feed partition above: another venue is driven by installing its own daily bars, and nothing branches on a platform | the policy knob is installing the feed or not, so a "which daily partition" field would restate it. Only the Pine host builds it (`prepare_chart_day_partition` `pine_strategy_host.cpp:2066`, `:2109`) and presents it for each bar it publishes (`AmbientDayPartitionScope` `pine_strategy_host.cpp:3011`). A bare host never calls the builder, so its calendar stays nominal | the private test `test_o_close_pct_day_anchor` (the partition is built from the D feed, and none without it) and `tests/test_chart_day_memo.cpp`; the tape is `lab tv` `o-cme-dayanchor-full`, recorded at the site | @@ -947,7 +948,7 @@ questions: the mechanism, why it is generic, and why no knob is needed. | Kernel capability | Generic mechanism | Why no platform knob is added | Executed witness | |---|---|---|---| -| A daily bar the installed daily feed dates: `NativeExecutionConsumer::prepare_day_labels` `src/native_execution_consumer.hpp:1209`, called at the end of `begin_ready`, once every feed is installed (`prepare_day_labels` `src/native_execution_consumer.cpp:3324`) and read where a script interval resolves (`script_interval_resolved` `src/native_execution_consumer.cpp:2576`); a lookup by a moved label is its bar's own (`day_label_origin` `src/native_execution_consumer.cpp:2679`) | (a) The venue's own daily bars date its days. When an intraday input aggregates into `"D"` script bars and a daily feed is installed -- the feed store's (`set_native_security_feed`) or a declared series' authoritative bars -- each stamp labels the D bar holding the session instant it covers: the stamp itself inside a session, else the next session's open, the instant the store already keys the feed's bars by. Only the label moves; the bar's span, closes, successor and OHLCV stay the calendar's aggregate, and every lookup by the label -- the bar's open and close, its sub-bars on a magnified run, its execution anchors, its risk day and a C host's session day -- reads the bar's own interval and day, not the day before the stamp. A bar no stamp covers keeps the calendar's label, a bar two stamps cover takes the earlier, and a label never passes an input bar the run holds: a stamp with an input between it and its bar's calendar open labels nothing, so a label never follows its bar's first input nor precedes another bar's. A stamp at the calendar's own open (09:30 ET, 17:00 CT, 00:00 UTC, OANDA:EURUSD's 17:00 ET) changes nothing; a venue that dates its days elsewhere -- OANDA's XAUUSD at 17:00 ET in the break of its 1800-1700 session, NSE's Muhurat day at its 13:45 open -- is driven by installing its own daily bars, and the kernel holds no venue's day-stamp convention | (b) None: the policy knob is the authoritative-feed ruling's, installing the daily feed or not (`engine_aux_security.cpp`'s contract), exactly as W15's late opens are; a "day label" field would restate the stamps the run installed. No input moves without the feed, and a calendar input's labels (the venue's bars themselves) never move. Calibration: TradingView dates every order of OANDA:XAUUSD 1D, backtested with the bar magnifier, by the 17:00 ET stamp -- 22:00 UTC in EST, 21:00 UTC in EDT -- where the 1m aggregate's session opens at 18:00 (`lab tv` `xc-dmag-xau1d`, `xc-dmag-dip-xau1d` and their `-off` twins, the private fixture set `daily_magnifier`) | `tests/test_native_day_labels.cpp` (kernel-only: break stamps across a DST switch, the bar's open reading its close's interval and input slot, a magnified run's sub-bars, a declared series' authoritative bars, stamps at the open, a missing, a doubled and a late stamp, an input in the break before a stamp's bar, NSE's late open, a daily, weekly and intraday script control, a reused host), `tests/test_native_c_api.c` (a C host's session day of a dated bar), `tests/test_daily_magnifier_tapes.cpp` (the four tapes through the Pine adapter: 20/20, 18/18, 31/31, 31/31; without the daily feed every trade an hour late) | +| A daily bar the installed daily feed dates: `NativeExecutionConsumer::prepare_day_labels` `src/native_execution_consumer.hpp:1209`, called at the end of `begin_ready`, once every feed is installed (`prepare_day_labels` `src/native_execution_consumer.cpp:3325`) and read where a script interval resolves (`script_interval_resolved` `src/native_execution_consumer.cpp:2577`); a lookup by a moved label is its bar's own (`day_label_origin` `src/native_execution_consumer.cpp:2680`) | (a) The venue's own daily bars date its days. When an intraday input aggregates into `"D"` script bars and a daily feed is installed -- the feed store's (`set_native_security_feed`) or a declared series' authoritative bars -- each stamp labels the D bar holding the session instant it covers: the stamp itself inside a session, else the next session's open, the instant the store already keys the feed's bars by. Only the label moves; the bar's span, closes, successor and OHLCV stay the calendar's aggregate, and every lookup by the label -- the bar's open and close, its sub-bars on a magnified run, its execution anchors, its risk day and a C host's session day -- reads the bar's own interval and day, not the day before the stamp. A bar no stamp covers keeps the calendar's label, a bar two stamps cover takes the earlier, and a label never passes an input bar the run holds: a stamp with an input between it and its bar's calendar open labels nothing, so a label never follows its bar's first input nor precedes another bar's. A stamp at the calendar's own open (09:30 ET, 17:00 CT, 00:00 UTC, OANDA:EURUSD's 17:00 ET) changes nothing; a venue that dates its days elsewhere -- OANDA's XAUUSD at 17:00 ET in the break of its 1800-1700 session, NSE's Muhurat day at its 13:45 open -- is driven by installing its own daily bars, and the kernel holds no venue's day-stamp convention | (b) None: the policy knob is the authoritative-feed ruling's, installing the daily feed or not (`engine_aux_security.cpp`'s contract), exactly as W15's late opens are; a "day label" field would restate the stamps the run installed. No input moves without the feed, and a calendar input's labels (the venue's bars themselves) never move. Calibration: TradingView dates every order of OANDA:XAUUSD 1D, backtested with the bar magnifier, by the 17:00 ET stamp -- 22:00 UTC in EST, 21:00 UTC in EDT -- where the 1m aggregate's session opens at 18:00 (`lab tv` `xc-dmag-xau1d`, `xc-dmag-dip-xau1d` and their `-off` twins, the private fixture set `daily_magnifier`) | `tests/test_native_day_labels.cpp` (kernel-only: break stamps across a DST switch, the bar's open reading its close's interval and input slot, a magnified run's sub-bars, a declared series' authoritative bars, stamps at the open, a missing, a doubled and a late stamp, an input in the break before a stamp's bar, NSE's late open, a daily, weekly and intraday script control, a reused host), `tests/test_native_c_api.c` (a C host's session day of a dated bar), `tests/test_daily_magnifier_tapes.cpp` (the four tapes through the Pine adapter: 20/20, 18/18, 31/31, 31/31; without the daily feed every trade an hour late) | ## Boundary rules (for contributors) diff --git a/docs/coverage.md b/docs/coverage.md index da742dc5c..301259f99 100644 --- a/docs/coverage.md +++ b/docs/coverage.md @@ -540,7 +540,7 @@ Every other Pine math function is the consumer compiler's responsibility | `pine_str_format` | `(fmt, vector args)` | MessageFormat over text arguments: `{N}` and `{N,number,