Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ supported as exact pairs; on the 0.x line they are independent. See the

The planned number of the next release; it stays **Unreleased** until its tag
exists, and the package version is unchanged. This entry records the diagnostic
additions below and nothing else: the engine pairing, the rest of the content
and the date of 1.5.0 are set when the release is prepared. It changes
diagnostics only; no analyzer or code-generation file differs.
additions below and the report-contract documentation for the paired engine
fix, and nothing else: the engine pairing, the rest of the content and the date
of 1.5.0 are set when the release is prepared. The codegen change is
diagnostics only; the report documentation adds no Python, JSON, diagnostic or
emitted-C++ change of its own, and no analyzer or code-generation file differs.

### Compatibility and migration

Expand Down Expand Up @@ -91,6 +93,25 @@ diagnostics only; no analyzer or code-generation file differs.
this entry, and no diagnostic carries a call-site identifier or a startup
length.

### Documentation

The engine side of the report-ownership text below is a report fix, in
preparation, that engine `v1.4.0` does not carry.

- The [public contract](docs/PUBLIC_CONTRACT.md#a-report-belongs-to-the-call-that-returned-it)
states that a report belongs to the call that returned it. A call the engine
refuses without beginning gets the empty report, never an earlier run's rows;
a run that began and then failed keeps its partial rows; the explicit stream
snapshot keeps the owner's rows; recorded outputs keep their own record of
the last run that began.
- Read the error text, the error code **and** the run status after every run.
A refusal the engine makes before it admits the begin leaves the status at its
earlier value (0 after a completed run), so the status alone misses it. The
README's "Compile & run against the engine" section says the same.
- Pairing is unchanged in kind: regenerate the strategy C++ and relink against
the engine release's headers and `libpineforge.a` on a pair change (README
[Engine pairing](README.md#engine-pairing)).

## 1.4.0 — 2026-10-08

A minor release of the exact engine/codegen pair. Its behavior was validated
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,18 @@ presence of `strategy.close` or `strategy.close_all` in the source. Older C++
has removed; regenerate it before compiling there. Reachable close commands
keep their ordinary runtime lowering.

Check how a run ended before you use its report. Read the error text
(`strategy_get_last_error`), the error code (`strategy_get_last_error_code`)
**and** the run status (`strategy_last_run_status`): the run failed when the
text or the code is non-empty or the status is 1. A refusal the engine makes
before it admits the begin, such as a bar array with a non-finite price, leaves
the status at its earlier value, so a rerun on a handle whose last run completed
can read status 0 beside a non-empty error. An engine without the report gate
(`v1.4.0` and earlier) can also hand a refused rerun on a reused handle the
previous run's rows. The
[public contract](https://github.com/pineforge-4pass/pineforge-codegen-oss/blob/main/docs/PUBLIC_CONTRACT.md#a-report-belongs-to-the-call-that-returned-it)
states the report rule and the engine release that carries it.

Prefer no local build? [pineforge.dev](https://www.pineforge.dev) runs a free
hosted MCP server, with a weekly backtest quota, whose `backtest_pine` tool
transpiles and backtests a strategy for an AI agent. The
Expand Down
81 changes: 80 additions & 1 deletion docs/PUBLIC_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
The supported Python/JSON contract began in 1.0.0; the sections below identify
later additions. This revision includes the 1.4.0 pair (2026-10-08).
Pre-tag validation used codegen `bfc4ddce` and engine `b3192bfc`, whose
version files at that point read `1.3.0`.
version files at that point read `1.3.0`. One section,
[A report belongs to the call that returned it](#a-report-belongs-to-the-call-that-returned-it),
describes a paired engine fix that is not yet released.

Text marked **since 1.5.0** describes diagnostic additions that 1.4.0 and
earlier releases do not have: the `note` severity, the `user_message` template
Expand Down Expand Up @@ -172,6 +174,83 @@ the paired engine now reports `setting_rejected` with
class `strategy`. The installed engine entrypoint returns exit 4 for all
three codes: exit 4 alone does not select a billing or refund decision.

## A report belongs to the call that returned it

Availability: **not yet released.** This is the contract of a report fix in the
paired engine that is in preparation. Engine `v1.4.0` and earlier do not carry
it; this section applies to the first engine release that does. The report fix
itself changes neither the generated C++ nor the transpiler's Python and JSON
contract: the wrappers still run the strategy and then ask the engine for its
report.
<!-- Release lane: when the engine release that carries the report gate is
tagged, replace "not yet released" and the sentence naming `v1.4.0` with "since
X.Y.Z, with engine `vX.Y.Z`" and add the release to the README's Engine pairing
table. -->

A generated `run_backtest` and `run_backtest_full` run the strategy and then
ask the paired engine for its report. The engine hands out the rows of the
attempt that call began. For a call that did not begin (a bar array or run
option it rejects, a handle that is not Ready, a calendar or timezone refusal,
a begin over a live stream) it hands out the empty report instead: every array
`NULL`, every count 0, overwritten and never freed. The report never answers a
refused call with the rows of an earlier run. A refused call on a fresh handle
already read empty; the rule extends that to a handle that has run before.

A refusal records its cause on the failure channel, `strategy_get_last_error`
(text) and `strategy_get_last_error_code` (code), and most refusals also set
`strategy_last_run_status` to 1. Not all do. A call the engine refuses before it
admits the begin writes the text and the code and leaves the run status as it
was: a bar array whose open, high, low or close is not finite (the source host
that a generated strategy derives from validates the array first) and a
repeated stream begin on a stream that is already realtime are two. A rerun on
a handle whose last run completed then reads status 0 together with a non-empty
error, the same status that the settings extension below calls "completed" for
an ordinary batch preparation failure. Read the error text, the error code
**and** the run status after every run. The run failed when the text is
non-empty, the code is non-empty, or the status is 1. The status alone misses
the refusals that leave it at 0, and the report alone does not tell a failed run
from a completed one. The paired engine's run harness applies this rule.

What a refusal does to the handle depends on why the call was refused, and the
report fix changes none of it. A begin on a Completed, Failed or unconfigured
handle, and a rejected bar array or run option, leave the lifecycle, the run
identity and the retained rows as they were. A calendar or timezone failure
inside the begin itself comes after the consumed run-number high-water moved
and fails the lifecycle; a calendar or timezone parse failure at configure time
comes earlier, fails the lifecycle there, and leaves the high-water where it
was. A batch begin over a live stream, or a begin made from a callback, is a
contract failure that latches Failed, so the stream cannot be driven on
afterwards, although its snapshot stays readable. A repeated stream begin on a
stream that is already realtime is the exception: the engine refuses it before
it admits the begin, latches nothing, and the stream stays running.

A run that began owns its rows whatever became of it: a completed report and the
partial rows of a run that began and then failed are handed out as they are, and
the failure is on the same channel. The engine's explicit stream snapshot,
`strategy_stream_fill_report`, asks for the rows' own owner. A live stream's
rows survive a batch call refused over it, which itself reads empty, and the
batch reader stays empty after the snapshot until the next run call. When no
live attempt owns rows (a refusal with nothing running) the snapshot is the
empty report. A run call made from inside a running run's callback reads empty
in the same way, while the outer run, which began, hands out its own rows when
it ends. The snapshot returns 0 when it wrote its output and -1 for a NULL
handle or output and for an exception contained at the C boundary. In that
case the output may hold the arrays built before the exception, so pass a
zero-initialized `pf_report_t` and release it with the library's `report_free`
(the engine's `strategy_native_report_free_v1` for a host that links the kernel
archive on its own) whatever the result.

Recorded outputs (`strategy_outputs_*`), on an engine that provides them, keep
their own record, the last run that began, and their own documentation. The
rule above does not cover them and this section does not change them. Engine
`v1.4.0` does not provide them.

This needs an engine release that carries the report gate: the first release
that contains this change. Against an older engine, a rerun on a reused handle
can return the previous run's rows together with the refusal, so read the
failure channel and the status after every run. Regenerate the strategy C++ and
relink against the new engine as for any pair change.

## Optional generated settings extension

From 1.1.0 on, C++ compiled against an engine providing
Expand Down
Loading