diff --git a/CHANGELOG.md b/CHANGELOG.md index 2446603..cb0d1af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,10 +20,90 @@ supported as exact pairs; on the 0.x line they are independent. See the the release commit. A prerelease note describes changes since the preceding prerelease or stable tag; the final stable note consolidates the series. -## Unreleased +## 1.4.0 — Unreleased + +A minor release of the exact engine/codegen pair, prepared from codegen +`bfc4ddce` and engine `b3192bfc`. It consolidates the seven requests landed +since `v1.3.0`: [#183](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/183) +(emission ordering), [#184](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/184) +(coded run stops), [#185](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/185) +(input manifests), [#186](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/186) +(collection history and binding), [#188](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/188) +(CI history), [#189](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/189) +(reference tests), and [#190](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/190) +(emitted-string regressions and edge cases). Recorded-output support from +engine request #356 is excluded. This planned release has not been tagged; +its candidate `VERSION` files still read `1.3.0` until the release workflows +set them. -- Generated C++ no longer depends on memory layout: requested-context history - resets and TA variant position ties preserve their traversal order. Refusal +### Compatibility and migration + +- Codegen 1.4.0 supports only engine `v1.4.0`. Regenerate every strategy's C++ + and relink with that release's generated headers and `libpineforge.a`. + The pair retains C ABI version 4, settings/capability API version 1 and the + `engine_script_run_v19` epoch; equal version numbers for those interfaces + do not make mixed engine/codegen releases supported. + A report's fingerprint records engine/codegen versions, generated C++'s + hash and the paired engine's corrected provenance values. The retained + epoch and hash domain tags do not promise equal fingerprints, state-hash + values or trades across 1.3.0 and 1.4.0. +- Emitted C++ changes, including common run-stop shims and collection history + wrappers. Do not reuse a 1.3.0 strategy library or assume source identity. + The public Python call signatures and override keys stay the same, but + `inputs` values and the Python/Pyodide success envelopes change as described + below. These are not blanket byte-identity promises for emission or envelopes. +- Every manifest row now carries `supported`. When it is `false`, hide the + input, exclude it from sweeps and optimization, and do not send a value: + the strategy uses its compiled default, and the paired engine refuses a + supplied value with `setting_unsupported`. `supported: true` alone does + not validate the value's type, bounds or options, or make a shared key safe. + A supported empty dropdown and an unsupported string input can both have + `options: []`; that list does not determine support. +- Give each input a unique title before upgrading. Calls with the same title + or fallback declaration key still produce separate manifest rows with the + same `title`; their `supported` flags do not detect the collision. Different + `group=` labels do not separate keys. On the paired 1.4.0 engine's normal + checked path, a completed run of such a script has `fingerprint: null` even + without an override. Supplying the shared key fails before execution with + `setting_rejected`, reason `ambiguous_key`. For example, + `Period="7"` returned a success report on 1.3.0, whose legacy setter applied + a shared key to all matching inputs. Legacy-path unresolved provenance rows + may still appear inside a fingerprint; that differs from the whole + fingerprint being null on the checked path. +- The paired engine's run harness now uses the checked settings API. Stored + presets and sweep values must satisfy the compiled input's type, bounds and + options. Two other examples that succeeded on 1.3.0 now fail with + `setting_rejected`: `Color="color.blue"` (`expected_integer`) and + `Choice="15"` for options `[10, 20, 30]` (`invalid_input_option`). A color's + manifest still has `type: "string"` and a Pine spelling such as `"color.red"` + as its default, but the checked native input requires the packed integer; + do not forward that manifest string as a native color value. The checked + API also validates parseable numbers, booleans, enum members and known keys. + This does not mean all invalid settings previously succeeded: + `initial_capital="abc"` and `initial_capital="-1"` already failed on 1.3.0; + they now report `expected_finite_decimal` and `value_below_minimum` reasons. + Legacy C setters retain their existing behavior. +- The paired engine adds `strategy_get_last_error_code` and + `strategy_get_last_error_args` beside the existing English error getter, + under `PINEFORGE_HAS_RUN_FAILURE_CODES_V1`. Its run harness exposes `code` + and typed `args` beside `error` on failures. Check the failure channel even + when the English message is empty; do not accept a stopped run's partial + output as success or parse the English to recover a code. These run failures + are distinct from transpile-time `CompileError` diagnostics. + `setting_rejected` is catalog class `input`; `setting_unsupported` is class + `unsupported`. A deliberate `runtime.error` is `strategy_runtime_error`, + class `strategy`. The installed engine entrypoint returns exit 4 for all + three codes: exit 4 alone does not select a billing or refund decision. +- The paired engine certifies scalar input/override types in fingerprint + provenance; unresolved values remain explicit rather than being treated as + certified native values. This is an engine report change, not another field + added to the transpile-time input manifest. See the paired engine's release + notes for report resolution reasons and counts. + +### Emission and run failures + +- Requested-context history resets and TA variant position ties no longer + use address or set order; they preserve traversal order. Refusal diagnostics for conflicting scalar history and map locals are hash-seed independent. - Generated run stops use stable engine failure codes when the paired engine @@ -41,48 +121,130 @@ supported as exact pairs; on the 0.x line they are independent. See the names; substring arguments retain source order without copying the source text. - Generated run wrappers preserve the inner failure code, and latched setter failures and checked strategy construction retain their failure identity. + +### Python and Pyodide input metadata + - The transpile-time input manifest (`transpile_full()["inputs"]` and the JSON envelope of the Pyodide package) gains `supported`, the checked-settings receipt's flag, on every input, and reads its defaults, choices and bounds - from the descriptor that generates the receipt, so the two agree for every - input of the public corpus and the repository's fixtures. Generated C++, - `strategyParams` and `requests` are unchanged and no key is renamed or - removed, but these values of existing keys change: + from the descriptor that generates the receipt, with the retained limitations + below. No manifest key is renamed or removed, but these values of existing + keys change: - Source inputs (`input.source`, a plain `input(close)`): `default` is the series name, where it was `null`, and `options` lists the nine native sources, where it was absent. - Enum inputs: `options` lists the `Enum.member` choices, where it was - absent. + absent; `default` keeps its `Enum.member` spelling. The compiled receipt + encodes that default as its integer member value. - String inputs: a built-in constant publishes its runtime value (`alert.freq_all` is `"all"`, `currency.USD` is `"USD"`; the manifest held the Pine names), a named string constant its value, and the choices of `input.timeframe` and `input.session` are listed. What the receipt cannot - represent (`size.small`, `position.top_right`, `na`) is `default: ""`, - `options: []` and `supported: false`. - - Typed `int`, `float`, `price`, `time` and `bool` inputs: a signed or + represent (`size.small`, `position.top_right`, `na`) has `supported: false` + and `options: []`; an unavailable default is `""`, but a representable + default survives an unsupported choice. String decoding uses the emitter + escape grammar, keeps literal Unicode and escaped backslashes, and stops + at the first NUL like the receipt's `std::string(const char*)` constructor. + NUL is emitted as a fixed three-digit octal escape even before digits; + this is consistency with current native truncation, not lossless NUL storage. + - Typed `int`, `float`, `price` and `time` inputs: a signed or named-constant `default` is a number, where it was `null`; `min`, `max` and `step` appear for signed and named-constant bounds (`minval=-80` was left - out); an `options=[...]` dropdown lists its numbers. + out); an `options=[...]` dropdown lists its numbers. A literal or inlined + `bool` default publishes a boolean, not a number. - A plain `input(-5)` or `input(-2.5)` is typed `int` or `float` with its - number as `default`, where it was `string` with `null`. + number as `default`, where it was `string` with `null`. The compiled + getter and checked receipt keep their existing float type for these + signed plain inputs; this manifest correction does not change that lowering. Values the receipt computes at run time (`LEN * 2`, `not true`, - `timestamp(year, month, ...)`, a color) stay unpublished, as before. + `timestamp(year, month, ...)`) stay unpublished: the default remains + `None`/JSON `null`, an unfoldable bound is omitted, and the whole `options` + field is omitted when any numeric choice is unfoldable. An `input.color` + retains its Pine spelling as a string default, not the receipt's packed + integer; other nonliteral plain `input()` calls remain `string` with no + published default (the source case above is the exception). A folded + `timestamp("...")` is a publishable literal. These gaps + are explicit in `tests/test_input_metadata_receipt.py::KNOWN_LIMITS`, not a + claim of arbitrary expression evaluation or language soundness. - Documents the timeframe spellings of two surfaces, both stable: a capability receipt records a request's timeframe as the script wrote it (`"D"`), and request discovery and feed keys use the engine's spelling (`"1D"`). + +### Collection lowering and refusals + - Bind keyword arguments of the matrix methods and the checked array methods to their own parameter slot, with the omitted optional parameters taking their defaults in place (`matrix.sort(m, order = order.descending)`, `m.submatrix(to_column = 1)`, `a.fill(7.0, index_to = 1)` no longer drop or shift a keyword), and refuse a keyword that names no parameter, or a parameter given twice, with the existing argument diagnostics. A defaulted `submatrix` evaluates its receiver once, an index-only `add_row` / `add_col` takes any numeric index, and a bound `array.concat` result (`c = a.concat(b)`, `c := array.concat(a, b)`) keeps the unsupported-function refusal. An array history that reaches a user function's or method's parameter keeps its earlier copy lowering and the earlier refusal of a callee that checks it with `na()`; a bound missing-history matrix reports the recorded na-ID runtime error. - Compile matrix/matrix and matrix/scalar `matrix.sum` overloads with matrix-valued results, including helpers and history. Support scalar `matrix.diff`/`matrix.mult` and omitted optional arguments to matrix row/column insertion, submatrix, sort and array fill; unsupported matrix/vector multiplication and bound `array.concat` results report an existing unsupported-function diagnostic instead of failing C++ compilation. - Preserve na array IDs when a history offset has no value, including aliases and `na()`-only bindings; array methods on those IDs report the existing runtime error instead of crashing. Bounds-check legacy collection-element reads and writes to prevent unchecked access. + +The inherited follow-ups remain: keywords on UDT matrix fields, `matrix.new` +and array methods outside the checked table; `matrix.fill` range handling +(both spellings still fill the whole matrix); matrix diff/mult dimension +checks; and nullable history across callable/iteration shapes not covered by +the documented lowering. This release does not claim those cases resolved. + +### Emitted-string edge cases and maintenance + - Return the prefix or ticker of the supplied symbol in `syminfo.prefix(symbol)` and `syminfo.ticker(symbol)` instead of always reading the chart symbol; the - variable forms stay unchanged. + variable forms, no-argument calls and direct chart-ID calls keep their + chart lowering. Supplied-symbol calls split at the first colon; a prefix + is empty without a colon or with no ticker after it, and a ticker without + a colon is the full string. Non-string arguments still compile and yield + an empty string, including the retained string-`na` typing gap. - Refuse malformed library versions with the existing import diagnostic instead of allowing them to raise outside the JSON gate, including non-ASCII decimal digits and ASCII versions exceeding Python's integer-conversion limit. - Reserve generated receipt exports and version macros against script names. - Escape embedded NUL values in generated C++ string literals, and strengthen emitted-string regression tests with explicit emitter reach and round trips. +- CI fetches full branch history for historical reference tests (#188); two + whole-C++ comparisons use the accepted immutable scaffold (#189). These + changes affect verification only, not the compiler or execution semantics. + +The hardening wording remains scoped to the previously documented emitted-string +validation and escaping; these edge-case repairs and regression checks are not +a new arbitrary-source security guarantee. No exploit payloads are included. + +### Known issues with consumers + +The new numeric options expose an exact-type comparison in +`pineforge-hpo` 0.11.0: a JSON integer `20` is refused with +`invalid_input_option` for an `input.float` choice `20.0`, although the +numbers are equal. For HPO 0.11.0, send each selected value with the number +type the original manifest publishes for that option: `20.0` when it lists +`20.0` (explicit JSON `20.0` or a Python float), and integer `20` when it lists +`20`. A float input written with whole-number-literal options such as +`options=[10, 20, 30]` publishes integer options; mixed lists retain mixed +number types. The input's `float` type alone does not choose the workaround. + +This can affect a caller that reads a float-spelled manifest default and +sends it back through JavaScript's ordinary JSON serialization, which writes +a whole-number value as `20`, losing its float spelling. Writing `20.0` in +JavaScript before ordinary serialization does not preserve that distinction; +preserve each option's original number type at the HPO boundary. Upgrade to +HPO 0.11.1 or a later release containing the fix when available. This fix is +separate from the engine/codegen release; HPO 0.11.1 has not been released at +the time of writing. + +### Recorded validation and grades + +The landed #190 candidate recorded 9,254 engine-enabled pytest passes with +12 audited skips, 2,164 focused passes each under GCC and Clang without skips, +314 public corpus compilation passes, and 279 fixtures in each of the JSON +and Pyodide fixture-parity checks. Those +are the request's retained native measurements, not a new native suite run +for this documentation change. The release-pair baseline records +excellent: 7,983; +strong: 6; +graded: 7,989 probes, +with 0 below strong +and 0 engine errors. +These are recorded grading outcomes, not a statement that no internal attempt +ever failed. Baseline `pineforge-parity-baseline-20261007-engine-b3192bfc` +measures engine `b3192bfc` +and codegen `bfc4ddce`; +these quantities render from the prepared release facts, not numeric overrides. ## 1.3.0 — 2026-10-06 diff --git a/README.md b/README.md index 1bcf1ec..e43ab3b 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,14 @@ A pure-Python library that turns a PineScript v6 strategy into a complete C++ source file you can compile against the [`pineforge-engine`](https://github.com/pineforge-4pass/pineforge-engine) runtime. -**Measured 2026-10-06** on main engine `7a1f01c0` with codegen-oss `3e50082f` (baseline `pineforge-parity-baseline-20261006-engine-7a1f01c0`, snapshot `e7921e24`): 7,982 of 7,989 TradingView probes -graded excellent and 7 strong, with 0 below strong; 17 more probes are held out as TradingView-side anomalies. +**Measured 2026-10-07** on main engine `b3192bfc` with codegen-oss `bfc4ddce` (baseline `pineforge-parity-baseline-20261007-engine-b3192bfc`, snapshot `28ba8dbd`): 7,983 of 7,989 TradingView probes +graded excellent and 6 strong, with 0 below strong; 17 more probes are held out as TradingView-side anomalies. A probe is a strategy exported from TradingView with its trade list and replayed trade for trade on the same bars. -Release **1.3.0 grades 7,982 excellent / 7 strong until the next release**, on 7,989 probes (baseline `pineforge-parity-baseline-20261006-engine-7a1f01c0`, 2026-10-06). A main scoreboard advance does not change release results. +Planned release **1.4.0 grades 7,983 excellent / 6 strong**, on 7,989 probes (baseline `pineforge-parity-baseline-20261007-engine-b3192bfc`, 2026-10-07). These are the prepared pair's recorded grading outcomes, not a published tag or a promise about arbitrary scripts. A main scoreboard advance does not change release results. + +Release 1.3.0 recorded 7,982 excellent / 7 strong on 7,989 probes; its historical result remains scoped to that pair. The quantities above render from the public [facts tokens](https://github.com/pineforge-4pass/pineforge-release/blob/main/facts/facts.json). Maintain them with `lab facts render --repo . --facts `; `lab facts check` with the same inputs reports drift. Grades are registry-derived; the authored-script and closed-trade inventory is explicitly sourced to a historical public README for the identical population, not to registry row or slug totals. @@ -40,7 +42,7 @@ for the changes in each release from 1.0.0 on and the release-note policy. ## Releases and this README This README ships with each release as its package description on PyPI (`pineforge-codegen`); releases from 0.7.0 on are also on npm as -`@pineforge/codegen-pyodide`. It describes 1.3.0 (2026-10-06) and what changed -since 0.10.4. The +`@pineforge/codegen-pyodide`. It describes the planned 1.4.0 pair (**Unreleased**) +and what changed since 0.10.4. The candidate is codegen `bfc4ddce` with engine +`b3192bfc`, excluding engine request #356; both candidate `VERSION` files still +read `1.3.0` until the release workflows set them. Historical measurements +below retain the versions on which they were run. The [PyPI release history](https://pypi.org/project/pineforge-codegen/#history) lists every release; the [changelog](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/CHANGELOG.md) @@ -59,6 +64,33 @@ covers 1.0.0 on and links the notes of earlier releases. A source install reports the version in `VERSION`, which the release workflow sets when it tags a release. +### Upgrading from 1.3.0 + +After the 1.4.0 pair is published, regenerate C++ and relink against engine +`v1.4.0`'s generated headers and runtime, even though C ABI version 4 and the +script ABI epoch are retained. Emission and input manifests change; neither +generated C++ nor Python/Pyodide envelopes are promised byte-identical. Every +input row now has `supported`: when false, hide it, exclude it from sweeps and +optimization, and do not send a value. Its compiled default is used; a supplied +value is refused with `setting_unsupported`. A true flag still requires type, +bounds and options validation, and does not detect duplicate keys. Account for +corrected source/enum choices, string values, signed bounds and plain-input types. + +Give each input a unique title; different `group=` labels do not separate +override keys. The paired checked path refuses a shared key and returns a null +fingerprint for a completed run of such a script even without an override. +The [1.4.0 migration notes](CHANGELOG.md#140--unreleased) cover this change, +new refusals of some previously accepted settings, and color's string manifest +versus packed-integer native value. Check stored presets before submitting them. +Users of `pineforge-hpo` 0.11.0 must preserve the manifest's number type for +each option at its input boundary; see the [known issue](CHANGELOG.md#known-issues-with-consumers). + +The paired engine adds coded run failures and typed fingerprint provenance. +Its run harness returns failure `code` and typed `args` beside `error`; an +empty message still means failure. Transpile-time diagnostics remain a separate +Python/JSON surface. Read the [1.4.0 changelog](CHANGELOG.md#140--unreleased) +for migration details and inherited collection/metadata limitations. + ### Upgrading from 0.10.4 0.10.4 (2026-09-06) was the last 0.x release. Its README is at the @@ -90,8 +122,9 @@ It does **not** own execution semantics. Order lifecycle, bracket legs, fill-price and slippage rules, `process_orders_on_close` / `calc_on_order_fills`, margin revival and trail/stop behaviour — everything TradingView parity depends on at run time — live in the engine's source-adapter runtime -([`src/source/`](https://github.com/pineforge-4pass/pineforge-engine/tree/v1.3.0/src/source) -in engine `v1.3.0`), which maps them onto the engine's Pine-agnostic kernel. See +([`src/source/`](https://github.com/pineforge-4pass/pineforge-engine/tree/v1.4.0/src/source) +for the planned engine `v1.4.0` pair), which maps them onto the engine's Pine-agnostic kernel. The tag link becomes available when that release is +published. See the engine's [architecture notes](https://github.com/pineforge-4pass/pineforge-engine#architecture-kernel-vs-parity). --- @@ -186,21 +219,31 @@ It returns `{"cpp": str, "inputs": list[dict], "strategyParams": dict, returns the first three keys; `requests` is new in 1.1.0). `inputs` is the input manifest; its `title` is the actual override key, and an `input.symbol` entry also has `"kind": "symbol"` (since 1.1.0). `diagnostics` contains nonfatal -warnings. Each input also has `supported`, the checked-settings receipt's -flag, so a consumer can hide the inputs the compiled strategy cannot honour -and keep them out of parameter sweeps and optimization before running -anything. Defaults, bounds and choices are the receipt's values where the -receipt holds a literal: string inputs spell a built-in constant by its runtime +warnings. Since 1.4.0, each input also has `supported`, the checked-settings receipt's +flag. Hide inputs with a false flag, exclude them from sweeps and optimization, +and do not send a value: the compiled default is used, and the paired engine +refuses a supplied value with `setting_unsupported`. A true flag is not a full +validation or duplicate-key check. Defaults, bounds and choices are the +receipt's values where it holds a literal: string inputs spell a built-in constant by its runtime value (`alert.freq_all` is `"all"`, not the Pine name), a source or enum input lists its choices, and a typed number input publishes a signed or named-constant default and bound and the numbers of an `options=[...]` dropdown. An unrepresentable string default is `""`, and every unsupported string input has `options: []` (a representable default is kept), so key on `supported`, not on -`options`. A value the receipt computes at run time (arithmetic over -constants, a call, `not true`, a color) is not published: `default` stays -`None` and `min`, `max` and `options` are left out (the -[contract](docs/PUBLIC_CONTRACT.md#input-manifest-and-override-keys) lists -the shapes). The PyPI and Pyodide manifests carry the same fields; no key is +`options`. Plain `input(-5)` and `input(-2.5)` now publish `int` and `float` +manifest types and numeric defaults; their compiled getters and checked +receipts retain the existing float type. A plain `input(close)` publishes +source choices but retains its `string` manifest type. String metadata +follows native C-string construction: an +embedded NUL truncates the published default/choice at the first NUL; it is +not a lossless-string promise. Literal Unicode and escaped backslashes are +retained. Run-time arithmetic, `not true` and calendar-part `timestamp` values +still have no published default or unfoldable bounds; a folded +`timestamp("...")` is a literal. The whole numeric `options` field is omitted +if any choice is unfoldable. Colors retain their Pine spelling and `string` +manifest type, while the checked native setter requires a packed integer. +The [contract](docs/PUBLIC_CONTRACT.md#input-manifest-and-override-keys) lists +these retained limits. The PyPI and Pyodide manifests carry the same fields; no key is renamed or removed, and the changelog lists the values that changed. `requests` lists the other symbols' feeds the script reads (see [List the other symbols a script requests](#list-the-other-symbols-a-script-requests)). @@ -473,6 +516,7 @@ Generated C++ compiles only against the engine it was generated for: | 1.1.0 (PyPI, 2026-10-04) | `v1.1.0` | The pair the `pineforge-release` image `1.1.0` ships. Its C++ defines the checked settings functions that engine `v1.1.0` adds to ``; regenerate and relink. | | 1.2.0 (PyPI, 2026-10-05) | `v1.2.0` | The pair the `pineforge-release` image `1.2.0` ships. Its C++ defines the compiled execution capability functions that engine `v1.2.0` adds to ``; regenerate and relink. | | 1.3.0 (PyPI, 2026-10-06) | `v1.3.0` | The pair the `pineforge-release` image `1.3.0` ships. Its C++ adds the confirmed-bar receipt, which engine `v1.3.0`'s live runner reads, and the order-shapes receipt; engine `v1.3.0`'s `` is `v1.2.0`'s. Regenerate and relink. | +| 1.4.0 (planned, Unreleased) | `v1.4.0` | Prepared from codegen `bfc4ddce` and engine `b3192bfc`. Adds paired coded failures, corrected input manifests and collection lowering. C ABI 4 does not authorize a mixed pair. Regenerate and relink after publication. | | Later `X.Y.Z` releases | `vX.Y.Z` of the same version | See below. | On the 0.x line the engine and codegen versions are independent, and the @@ -498,8 +542,8 @@ Pine as `strategy.pine`, write `strategy.generated.cpp` with the [file example](#transpile-a-file-to-a-cpp) above, then from the same directory: ```bash -# 1.3.0 pairs with engine v1.3.0. For 0.10.4 use --branch v0.13.1. -git clone --branch v1.3.0 https://github.com/pineforge-4pass/pineforge-engine.git +# After publication: 1.4.0 pairs with engine v1.4.0. For 0.10.4 use v0.13.1. +git clone --branch v1.4.0 https://github.com/pineforge-4pass/pineforge-engine.git cd pineforge-engine cp ../strategy.generated.cpp tutorial/macd/generated.cpp # the tutorial's strategy slot bash tutorial/run.sh # needs cmake, a C++17 compiler and python3 @@ -508,7 +552,8 @@ bash tutorial/run.sh # needs cmake, a C++17 compiler and python3 `run.sh` configures CMake once, builds `libpineforge.a` and `tutorial/macd/strategy.so`, and runs `tutorial/run.py`, which loads the `.so`, feeds it the bars and reads back the closed trades. For the quick-start SMA -cross, 1.3.0 with engine `v1.3.0` prints: +cross, the following output was measured on 1.3.0 with engine `v1.3.0`; it +has not been re-measured for this 1.4.0 documentation change: ``` MACD(12,26,9) on BTCUSDT 15m — 672 bars, 2026-04-29 18:15 → 2026-05-06 18:00 UTC @@ -612,7 +657,7 @@ quote. The commercial-license store (coming soon) will take orders online. ## Explicit Pine execution attachment -This section describes 1.3.0 and engine `v1.3.0`. The Pine execution adapter is +This section describes the planned 1.4.0 and engine `v1.4.0` pair. The Pine execution adapter is the engine's full Pine execution runtime (`PineExecutionAdapter` and `PineStrategyHost` in the engine's `src/source/`): order lifecycle, bracket legs, fill-price and slippage rules, POOC / `calc_on_order_fills`, margin @@ -635,10 +680,11 @@ Regenerate old generated C++ before using a new engine for Pine execution. C++ generated before the source-layer cut ([#129](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/129)), 0.10.4's included, derives from `BacktestEngine` and does not compile against -engine `v1.3.0`; old cap-only C++ does not attach the priority rule, and +engine `v1.4.0`; old cap-only C++ does not attach the priority rule, and metadata cannot silently restore it. Rebuild all modules against the -new matching C++ layout (`engine_script_run_v19` in engine `v1.3.0`); old -fingerprint versions are not comparable. The extraction preserves Pine policy +new matching C++ layout (`engine_script_run_v19` in engine `v1.4.0`). Retaining +that epoch does not promise equal fingerprints, state hashes or trades across +releases. The extraction preserves Pine policy under explicit attachment; it does not implement the generic native child-activation scheduler or prove campaign neutrality. Compile-only corpus checks do not run Pine backtests. diff --git a/docs/PUBLIC_CONTRACT.md b/docs/PUBLIC_CONTRACT.md index e86a2d9..be1aacc 100644 --- a/docs/PUBLIC_CONTRACT.md +++ b/docs/PUBLIC_CONTRACT.md @@ -1,5 +1,10 @@ # Public contract for 1.0 +The supported Python/JSON contract began in 1.0.0; the sections below identify +later additions. This revision includes the planned 1.4.0 pair (codegen +`bfc4ddce`, engine `b3192bfc`, **Unreleased**), not an already published tag. +The candidate version files still read `1.3.0` until release dispatch. + ## Optional compiled execution capabilities Availability: **since 1.2.0**, with engine `v1.2.0`'s capability extension and @@ -115,6 +120,52 @@ its override are raw: fold them the same way. To match a receipt's request to a feed, fold the receipt's literal timeframe with that rule (folding twice changes nothing); never map a feed key back to `"D"`. +## Optional coded run failures + +Availability: **planned 1.4.0**, with engine `v1.4.0`'s +`PINEFORGE_HAS_RUN_FAILURE_CODES_V1` feature. The feature is optional in emitted +source; that fallback is not an exception to exact release pairing. + +Generated no-data/other-symbol, collection, UDT, unsupported-request and +checked-string stops carry compile-time provenance through coded helpers. +A script's `runtime.error` always selects `strategy_runtime_error`; neither +its message nor an English message copied from another failure selects a +code. Named `message` arguments are passed, and an omitted message stays empty. +`str.tonumber` evaluates its argument outside the numeric parser's narrow +catches so a run stop propagates; only numeric parsing failures become `na`. +The generated run wrappers retain inner codes. A legacy setter's first failure +latches its English and `setting_rejected` identity for subsequent begins. + +The paired engine provides `strategy_get_last_error_code` and +`strategy_get_last_error_args` beside `strategy_get_last_error`; its run harness +adds `code` and typed `args` beside failure `error`. An empty English message +does not make the run successful or authorize use of its partial results. +Without the feature, generated shims retain the legacy exceptions and English. +This is a runtime failure channel, distinct from the `CompileError` and +`diagnostics` contract of transpilation. Regenerate and relink for the new pair; +C ABI version 4 alone does not authorize reusing a 1.3.0 strategy library. + +The paired 1.4.0 harness uses the checked settings API for generated libraries; +the 1.3.0 harness used the legacy setters. Some requests that previously +returned success now fail before execution: a shared `Period` key set to `"7"` +is `setting_rejected` with reason `ambiguous_key`, textual color `"color.blue"` +is `expected_integer`, and `"15"` outside choices `[10, 20, 30]` is +`invalid_input_option`. The color manifest's `string` type and Pine-spelling +default do not describe the native setter's packed-integer encoding. Validate +presets against the compiled settings, including types, bounds and options; +the checked path also refuses unknown keys and invalid numeric, boolean or +enum values. An input with `supported: false` rejects a supplied value with +`setting_unsupported`; leave it at its compiled default. + +These examples do not imply that every invalid request used to succeed. +`initial_capital="abc"` and `initial_capital="-1"` already failed on 1.3.0; +the paired engine now reports `setting_rejected` with +`expected_finite_decimal` and `value_below_minimum`, respectively. +`setting_rejected` is catalog class `input`; `setting_unsupported` is class +`unsupported`. A deliberate `runtime.error` is `strategy_runtime_error`, +class `strategy`. The installed engine entrypoint returns exit 4 for all +three codes: exit 4 alone does not select a billing or refund decision. + ## Optional generated settings extension From 1.1.0 on, C++ compiled against an engine providing @@ -285,7 +336,8 @@ an `input.symbol` entry also has `kind` equal to `"symbol"`. An optional field is omitted when its argument is absent or cannot be reduced to the supported literal form. -`supported` and the values below are those of the checked-settings receipt (what +From 1.4.0, `supported` and the values below come from the checked-settings +descriptor, subject to the explicit retained limits below (what the compiled strategy's `strategy_get_effective_settings` reports): the manifest is read from the descriptor that generates the receipt, and PyPI's `transpile_full()` and the npm package's Pyodide/glue success envelope carry @@ -295,16 +347,25 @@ them identically. string input whose default or a choice is neither a string literal nor a known built-in constant (`size.small`, `position.top_right`, `na`, a reassigned name), or an `input.enum` whose default or choices are not literal members of - one enum. Hide such inputs, and keep them out of sweep and optimization - spaces, before creating a strategy. Key on `supported` alone: every unsupported + one enum. Hide such inputs, exclude them from sweeps and optimization, and + do not send a value: the strategy uses its compiled default, and the paired + engine rejects a supplied value with `setting_unsupported`. Use `supported` + to determine eligibility rather than `options` emptiness: every unsupported string input has `options: []`, with or without `options=`, and so does a - supported input written `options=[]`. + supported input written `options=[]`. A true flag does not validate a value's + type, bounds or choices, or detect duplicate override keys. - A string input (`input.string`, `.timeframe`, `.session`, `.symbol`, ...) publishes the lowered strings: a built-in constant its runtime value (`alert.freq_all` is `"all"`, `currency.USD` `"USD"`, `format.price` `"price"`, `order.ascending` `"ascending"`, `session.regular` `"regular"`) and a named string constant its value. An unrepresentable default is `""`; a - representable default is kept when only a choice is unsupported. + representable default is kept when only a choice is unsupported. The + descriptor decodes only the emitter's C++ string-literal escape grammar, + preserves literal Unicode and escaped backslashes, and stops at the first + embedded NUL, matching native `std::string(const char*)` construction. The + emitter spells NUL as fixed three-digit octal `\000` (one backslash), including before + digits. This is current manifest/receipt consistency, not lossless storage + of a string containing NUL. - A source input (`input.source(hl2)`, a plain `input(close)`) publishes its series as `default` and the nine native sources (`close`, `high`, `hl2`, `hlc3`, `hlcc4`, `low`, `ohlc4`, `open`, `volume`) as `options`; a plain @@ -312,21 +373,30 @@ them identically. - An enum input publishes its `Enum.member` choices in declaration order as `options` and keeps its `Enum.member` `default`, which the receipt encodes as the member's index (map it through the receipt's `option_values`). -- A typed number or bool input (`input.int`, `.float`, `.price`, `.time`, - `.bool`) publishes `default`, `min`, `max`, `step` and the choices of an - `options=[...]` dropdown (a list of numbers) as numbers where the receipt's - value is a literal: a signed number (`-2.5`), a named constant (`LEN = 14`, +- A typed numeric input (`input.int`, `.float`, `.price`, `.time`) publishes + numeric `default`, `min`, `max`, `step` and the numeric choices of an + `options=[...]` dropdown where the receipt's value is a literal: a signed number (`-2.5`), a named constant (`LEN = 14`, `minval=-RATIO`) or `timestamp("2024-01-02T00:00:00")` of a string literal. A - plain `input(-5)` or `input(-2.5)` is typed `int` or `float` by its literal, as - `input(5)` is; the compiled getter reads it as a double, so the receipt's - type for it is `float`. + plain `input(-5)` or `input(-2.5)` is typed `int` or `float` by its literal; + these signed forms retain a double getter and a `float` receipt type. + A plain `input(5)` uses an int getter and receipt. + An `input.bool` default is a boolean when the + descriptor lowers it to literal `true` or `false`, including an inlined + constant; `not true` remains a run-time expression. - Not published, because the receipt computes it at run time: arithmetic over constants (`LEN * 2`), a call (`math.pow(2, 3)`), `timestamp(year, month, ...)` (it reads the symbol's time zone) and `not true`. Such a `default` is - `None` and such a `min`, `max` or `options` is omitted. An `input.color`'s + `None` and such a `min` or `max` is omitted. The whole numeric `options` + field is omitted when any choice is unfoldable. These gaps are pinned in + `tests/test_input_metadata_receipt.py`'s `KNOWN_LIMITS`; its test view maps + an absent options field to `[]`, which is not the public manifest shape. + An `input.color`'s `default` is its Pine spelling (`"color.red"`) and its `type` `string`, where - the receipt holds the packed integer, and a plain `input(...)` whose default - is not a literal is typed `string` with a `None` default. + the receipt holds the packed integer required by the checked native setter. + Do not submit the manifest's Pine-spelling string as a native color value. + Apart from the source-input case + above, a plain `input(...)` whose default is not a literal is typed + `string` with a `None` default. No key is renamed or removed and the override keys are unchanged; the values above are new or corrected values of existing keys, listed in the changelog. @@ -338,9 +408,16 @@ The `title` is the **actual override key read by the emitted C++**: For example, `length = input.int(14)` has key `"length"`, and `ta.ema(close, input.int(9, "Fast"))` has key `"Fast"`. The manifest includes -both declared and inline global-scope calls. If several calls share a key, -one override sets all of them; the translator emits a warning naming the -colliding inputs. Different `group=` labels do not separate override keys. +both declared and inline global-scope calls, one row per call. If several +calls share a title or fallback key, those rows retain the same `title` and +their per-input `supported` flags do not reflect the collision. The translator +warns about the colliding inputs. The legacy setters apply a shared override +to all matching inputs; the checked setters refuse the key as ambiguous. +On the paired 1.4.0 engine's normal checked path, a supplied shared key fails +with `setting_rejected`, reason `ambiguous_key`; a completed run with no such +override still reports the whole `fingerprint` as `null`. This differs from +legacy-path unresolved provenance rows, which may remain inside a fingerprint. +Give each input a unique title. Different `group=` labels do not separate keys. An explicit title that is not a compile-time string constant raises `CompileError`. diff --git a/docs/pine-cap-activation.md b/docs/pine-cap-activation.md index ed953fd..a151f32 100644 --- a/docs/pine-cap-activation.md +++ b/docs/pine-cap-activation.md @@ -4,7 +4,9 @@ This note came with the explicit cap attachment ([#127](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/127)). It now also covers the execution-adapter attachment that followed ([#128](https://github.com/pineforge-4pass/pineforge-codegen-oss/pull/128)) and -describes codegen 1.3.0 with engine `v1.3.0`. +describes the planned codegen 1.4.0 / engine `v1.4.0` pair. The release +is **Unreleased**: candidate codegen `bfc4ddce` and engine `b3192bfc` still +have `VERSION` equal to `1.3.0`; they are not tagged 1.4.0 artifacts. Generated strategies explicitly select Pine intraday-cap compatibility in their constructor. When the engine defines @@ -20,7 +22,8 @@ The expression is evaluated only where the source executes it, and limit updates preserve the existing quota and pending obligations. Selection does not evaluate a limit or move a conditional statement into the constructor. -Engine `v1.3.0` defines both macros in `pineforge/source/pine_strategy_host.hpp`, +The 1.4.0 engine candidate, like released engine `v1.3.0`, defines both macros +in `pineforge/source/pine_strategy_host.hpp`, the header the generated C++ includes; every engine commit with that header does, since the header and the macros arrived there together (engine #253). So the adapter branch is the one that compiles today. The cap-only branch and the @@ -30,10 +33,12 @@ capability, bare native construction leaves the Pine cap unselected. Matching runtime headers and library are required; this source bridge does not make stale compiled C++ objects compatible across internal ABI versions. -Engine `v1.3.0`, like `v1.0.0`, `v1.0.1`, `v1.1.0` and `v1.2.0`, no longer has -the `script_has_strategy_close_` member that the C++ of codegen 0.10.4 and -earlier assigns, so that C++ does not compile there; regenerate it with codegen -1.3.0. +The 1.4.0 engine candidate, like released `v1.3.0`, `v1.0.0`, `v1.0.1`, +`v1.1.0` and `v1.2.0`, no longer has the `script_has_strategy_close_` member +that the C++ of codegen 0.10.4 and earlier assigns, so that C++ does not +compile there. For the planned release, regenerate with codegen 1.4.0 and +relink with the exact paired engine release; unchanged interface numbers +do not authorize mixing releases. The cap's three compatibility options retain their existing behavior and defaults. They belong to the selected Pine component, not universal native