Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c4f3968
Re-pin the validation corpus to f989e36: special-validation builds, o…
luisleo526 Sep 25, 2026
4552eb8
tests: harden timing ratio legs against runner noise
luisleo526 Sep 25, 2026
c6e0bb7
C-SURFACE-1: preserve published C callback layouts
luisleo526 Sep 25, 2026
409ebad
fix(v19 KERNEL-EDGE): repair FX and calendar edges, expose host marks
luisleo526 Sep 25, 2026
86780e4
Kernel: no ordinary quantity request stops a run -- the typed Unrepre…
luisleo526 Sep 25, 2026
2d4affc
Docs: what a host gets from a quantity -- the typed refusal, the grid…
luisleo526 Sep 25, 2026
cbf693c
Kernel: a fill the request core cannot take off its units is the same…
luisleo526 Sep 25, 2026
a6e34ac
Kernel: the grid holds a boundary Reduce to its boundary at the candi…
luisleo526 Sep 25, 2026
c30ef7e
Kernel: the grid's candidate tests what it settles; a whole-scope fra…
luisleo526 Sep 25, 2026
b945385
Docs: the one quantity case that still stops a run -- an OCA-Reduce s…
luisleo526 Sep 25, 2026
42fec56
INT25 merge: C-SURFACE-1's failure-discriminator row fails its run th…
luisleo526 Sep 25, 2026
fac8389
v19 hash step (R5 lane V19-FIX): a cancelled exit stays cancelled, ca…
luisleo526 Sep 25, 2026
07fc918
Docs: a cancelled exit stays cancelled, and cancelling and recording …
luisleo526 Sep 25, 2026
709526b
K-IDX: book aggregated coordinates in script bars
luisleo526 Sep 25, 2026
3ed0276
K-IDX: preserve Pine input projections and re-pin v19 witnesses
luisleo526 Sep 25, 2026
ae8a074
docs: make native migration examples and comments executable truth
luisleo526 Sep 25, 2026
4c02933
docs: finish native contract truth and report caveats
luisleo526 Sep 25, 2026
9ccc4e5
INT25 (i): current_partial_bar() says what a bare host gets -- the ba…
luisleo526 Sep 25, 2026
b4b9a36
INT25 (iii): promote-baseline admits a squash merge by tree equality,…
luisleo526 Sep 25, 2026
cb6fce2
INT25 (ii): CI fits CI-LITE's rule for wave G -- slow rows re-measure…
luisleo526 Sep 25, 2026
18ec002
Kernel: an OCA-Reduce deduction binary64 cannot take off its recipien…
luisleo526 Sep 25, 2026
3e21571
Tests, docs: the absorption's C reach stated, each deduction taken on…
luisleo526 Sep 25, 2026
16496ae
INT25 merge: two row-8 citations C-SURFACE-1's page kept over its own…
luisleo526 Sep 25, 2026
2a75925
INT25 v19 hash re-pin
luisleo526 Sep 25, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ jobs:
# PINEFORGE_ENABLE_SANITIZERS).
sanitizers:
runs-on: ubuntu-24.04
timeout-minutes: 90
# A full run (push to main, manual dispatch) gives its CTest stage an hour
# after a build of about 30 minutes; the PR set keeps its 30-minute stage.
timeout-minutes: 120
env:
ASAN_OPTIONS: detect_leaks=1:halt_on_error=1:abort_on_error=1
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
Expand Down
76 changes: 51 additions & 25 deletions .github/workflows/promote-baseline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
#
# On a merged PR it advances the campaign baseline one axis to the merged
# composite, reusing the snapshot from the pr-gate run that already cleared
# it. It promotes ONLY when the merge is exact-head (the gated commit's tree is
# now on the base branch), both maintainer verification statuses are green on
# that commit, and a PASS verdict binds
# the full (engine, codegen) pair. Otherwise it exits green without promoting.
# it. It promotes ONLY when the merge is exact-tree (the merge commit on the
# base branch carries the gated PR head's tree, and the base has not moved past
# it), both maintainer verification statuses are green on the PR head, and a
# PASS verdict binds the full (engine, codegen) pair. Otherwise it exits green
# without promoting. PRs are squash-merged, so the PR head is never an ancestor
# of the base branch: the merge commit is the event's merge_commit_sha, and the
# guard compares trees rather than asking for ancestry.
#
# The gate logic is exact-head + pineforge/verify + pineforge/parity +
# The gate logic is exact-tree + pineforge/verify + pineforge/parity +
# baseline-promote.mjs. The plane is now GCP: the
# promotion reads the active baseline and the gate ledger from the Postgres
# registry through a cloud-sql-proxy, streams the pinned documents from the
Expand Down Expand Up @@ -55,12 +58,16 @@ on:
types: [closed]
# Manual re-fire for a merge whose pull_request event already passed (e.g. a
# PR merged before this workflow was installed, or a rebase-merge whose PR
# head was orphaned): pass the exact commit now on the base branch.
# head was orphaned): pass the exact commit now on the base branch and the
# PR head the maintainers verified.
workflow_dispatch:
inputs:
merge_commit:
description: "40-hex commit now on the base branch to promote"
required: true
pr_head:
description: "40-hex PR head the verification statuses were posted on (the merge commit itself for an exact-head merge)"
required: true
pr_number:
description: "the PR number"
required: true
Expand Down Expand Up @@ -97,26 +104,40 @@ jobs:
with:
fetch-depth: 0

# Exact-head is the safety property: the snapshot describes the gated
# commit's tree, so we promote only if that tree is what is now on the
# base branch. A squash/rebase that rewrote the tree, or a base that
# moved on, is skipped — those must be re-gated.
- name: Verify exact-head
# Exact-tree is the safety property: the snapshot describes the gated PR
# head's tree, so we promote only if the merge commit is on the base
# branch, carries exactly that tree, and is still the base branch's
# tree. A squash merge mints a new commit whose tree is the PR head's; a
# merge that rewrote the tree, or a base that moved on, is skipped —
# those must be re-gated.
- name: Verify exact-tree merge
id: exacthead
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.merge_commit }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.event.inputs.merge_commit }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head }}
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
run: |
set -euo pipefail
git fetch --quiet origin "$BASE_REF"
if ! git merge-base --is-ancestor "$HEAD_SHA" "origin/$BASE_REF"; then
echo "::notice::head $HEAD_SHA is not on origin/$BASE_REF — not an exact-head merge; skipping"
if ! git merge-base --is-ancestor "$MERGE_SHA" "origin/$BASE_REF"; then
echo "::notice::merge commit $MERGE_SHA is not on origin/$BASE_REF; skipping"
echo "ok=false" >> "$GITHUB_OUTPUT"; exit 0
fi
base_tree=$(git rev-parse "origin/$BASE_REF^{tree}")
# A squashed PR head is on no branch; fetch it by its sha.
if ! git cat-file -e "$HEAD_SHA^{commit}" 2>/dev/null \
&& ! git fetch --quiet --no-tags origin "$HEAD_SHA"; then
echo "::notice::PR head $HEAD_SHA cannot be fetched; skipping"
echo "ok=false" >> "$GITHUB_OUTPUT"; exit 0
fi
merge_tree=$(git rev-parse "$MERGE_SHA^{tree}")
head_tree=$(git rev-parse "$HEAD_SHA^{tree}")
if [ "$base_tree" != "$head_tree" ]; then
echo "::notice::base tree != head tree — the base diverged from the gated tree; re-gate. skipping"
if [ "$merge_tree" != "$head_tree" ]; then
echo "::notice::merge tree != PR head tree — the merge rewrote the gated tree; re-gate. skipping"
echo "ok=false" >> "$GITHUB_OUTPUT"; exit 0
fi
base_tree=$(git rev-parse "origin/$BASE_REF^{tree}")
if [ "$base_tree" != "$merge_tree" ]; then
echo "::notice::base tree != merge tree — the base moved on from the merged tree; re-gate. skipping"
echo "ok=false" >> "$GITHUB_OUTPUT"; exit 0
fi
echo "ok=true" >> "$GITHUB_OUTPUT"
Expand All @@ -126,7 +147,7 @@ jobs:
if: steps.exacthead.outputs.ok == 'true'
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.merge_commit }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head }}
run: |
set -euo pipefail
# GitHub lists newest statuses first. Keep the first occurrence of
Expand Down Expand Up @@ -216,7 +237,8 @@ jobs:
# No metadata server on a GitHub runner; mint GCS tokens from the
# federated gcloud credential explicitly.
PINEFORGE_GCP_TOKEN_CMD: gcloud auth print-access-token
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.merge_commit }}
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.event.inputs.merge_commit }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
PR_URL: ${{ github.event.pull_request.html_url || github.event.inputs.pr_url }}
MERGED_AT: ${{ github.event.pull_request.merged_at || github.event.inputs.merged_at }}
Expand All @@ -226,18 +248,22 @@ jobs:
set +e
node .workflow/campaign/src/baseline-promote.mjs \
--repo "${{ steps.axis.outputs.repo }}" \
--merge-commit "$HEAD_SHA" --head-sha "$HEAD_SHA" --ci-head "$HEAD_SHA" --ci-green \
--merge-commit "$MERGE_SHA" --head-sha "$HEAD_SHA" --ci-head "$HEAD_SHA" --ci-green \
--pr-number "$PR_NUMBER" --pr-url "$PR_URL" --merged-at-ms "$ms"
code=$?
set -e
# Exit 2 is `refuse`: no PASS verdict binds this merge as a single-axis
# advance -- a cross-cutting engine+codegen change (gated as a composite
# the single axis cannot complete), an ungated merge, or a non-exact-head
# merge. Defer green, do not fail CI; a cross-cutting pair is promoted by
# hand with `lab promote --composite` once both repos merge (see the
# baseline-promote skill). Any other non-zero is a real error.
# the single axis cannot complete), an ungated merge, or a merge commit
# the campaign tooling does not accept for the gated head (the squash
# commit is passed as --merge-commit and the verified PR head as
# --head-sha / --ci-head; the tree equality above is this workflow's
# proof that they carry one tree). Defer green, do not fail CI; a
# cross-cutting pair is promoted by hand with `lab promote --composite`
# once both repos merge (see the baseline-promote skill). Any other
# non-zero is a real error.
if [ "$code" = "2" ]; then
echo "::notice::baseline not promoted (deferred): no PASS verdict binds this merge as a single-axis advance. If this was a cross-cutting engine+codegen change, run the composite promote by hand once both repos are merged. Nothing was changed."
echo "::notice::baseline not promoted (deferred): the campaign tool refused this merge (its reason is printed above) -- no PASS verdict binds it as a single-axis advance, or it is a squash merge commit the tool does not accept for the verified PR head. If this was a cross-cutting engine+codegen change, run the composite promote by hand once both repos are merged. Nothing was changed."
exit 0
fi
exit "$code"
9 changes: 6 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ dispatch run the full CI profiles. A campaign PASS verdict still binds the
exact engine and codegen HEADs for baseline promotion.

Merged single-axis PRs advance the campaign baseline automatically through
`.github/workflows/promote-baseline.yml` only when the exact-head guard, both
required statuses on that head, and the campaign verdict pass. A squash or
rebase that rewrites the SHA defers promotion and needs new verification.
`.github/workflows/promote-baseline.yml` only when the exact-tree guard (the
merge commit on `main` carries the verified PR head's tree, and `main` has not
moved past it), both required statuses on the PR head, and the campaign verdict
pass. PRs are squash-merged, so the workflow hands the campaign tool the squash
commit and the verified PR head; while that tool requires the merge commit to be
the gated head itself, a squash defers promotion and needs new verification.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ What each gate refuses:
### The floors

`ci_verify.py` counts the CTest rows that actually **ran** and fails below a
floor — `KERNEL_MIN_TESTS` ci_verify.py:229 and `RELEASE_MIN_TESTS`
ci_verify.py:332.
floor — `KERNEL_MIN_TESTS` ci_verify.py:242 and `RELEASE_MIN_TESTS`
ci_verify.py:350.
A deleted or silently skipped row is a failure, not a quieter run. If your
change adds rows, raise the floor in the same commit and say by how much; if it
legitimately removes one, lower it deliberately and say why. `--min-tests`
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ report. Each is a CTest row: `ctest --test-dir build -R example_`.
Hand the runtime a callback table and drive the kernel from any language with
a C FFI — no C++ in your own code. The complete file is
[`examples/native/hello_kernel_c.c`](examples/native/hello_kernel_c.c); the
43 `strategy_native_*` functions are declared in
41 `strategy_native_*` functions (plus two `strategy_configure_native_ext_*`
functions) are declared in
[`include/pineforge/native_c_api.h`](include/pineforge/native_c_api.h) and
summarised in [Driving the kernel from C](#driving-the-kernel-from-c) below.

Expand Down Expand Up @@ -449,12 +450,12 @@ inventories are pinned by `scripts/check_c_abi_runtime.py`:
| `strategy_set_account_currency_fx_series` | Effective-time quote-to-account FX |
| `strategy_get_last_error` | The latest runtime error |
| `pf_version_get` / `pf_version_string` / `pf_abi_version` | Runtime version, version string, struct-layout version (`PF_ABI_VERSION == 4`) |
| `strategy_execution_contract` / `strategy_configure_native_v1` / `strategy_configure_native_fx_curve_v1` / `_fx_curve_ext_v1` | Query Legacy vs NativeMarketV1; apply the versioned native run specification (a refused one returns `-1` and leaves the handle Failed); stage or clear an immutable native FX curve, `_fx_curve_ext_v1` also writing the typed refusal (`pf_native_fx_curve_error_e`) and the offending point's index |
| `strategy_execution_contract` / `strategy_configure_native_v1` / `strategy_configure_native_fx_curve_v1` / `_fx_curve_ext_v1` | Query Legacy vs NativeMarketV1; apply the versioned native run specification (an invalid or refused base call returns `-1` and leaves the handle Failed; a cooperative `Aborted` handle may be reused with the same key and a higher run number); stage or clear an immutable native FX curve, `_fx_curve_ext_v1` also writing the typed refusal (`pf_native_fx_curve_error_e`) and the offending point's index |
| `strategy_request_abort` / `strategy_last_run_status` | Cooperative abort of a run in progress; `0`=completed, `1`=aborted |
| `strategy_set_realtime_tail` | Live-runtime surface (ABI v4): the array's last bar is a still-forming tail — `barstate.islast=false`, `last_bar_index`/`last_bar_time` frozen at the horizon bar, no range-end row |
| `strategy_set_probe_suppress_tail_logic` | ABI v4: the last bar runs only the broker's pre-`on_bar` steps (pending-order settlement, intraday-cap/loss checks) and returns — no `on_bar`, no margin-call / POOC second pass / bracket-reissue processing (the range-end row is `strategy_set_realtime_tail`'s to skip; the flags are independent) |
| `strategy_set_path_order` / `strategy_last_bar_dual_entry_path` | ABI v4: force the intrabar O→H/L→C leg order (`AUTO`/`HIGH_FIRST`/`LOW_FIRST`) for path-dependent fill probing; read which side won a same-bar dual-entry-stop arbitration |
| `strategy_set_broker_state_hash_recording` / `strategy_broker_state_hash` | ABI v4: toggle a 64-bit broker-state hash appended per script bar to `pf_report_t::broker_state_hash`; read the final state's hash |
| `strategy_set_broker_state_hash_recording` / `strategy_broker_state_hash` | ABI v4: toggle a 64-bit broker-state hash appended at report points to `pf_report_t::broker_state_hash` (a bare host uses `KernelRecorded`); read the final state's hash |
| `strategy_pending_orders_len` / `strategy_pending_order_get` / `strategy_pending_order_layout` | ABI v4: the resting pending-order book after the most recent run — count, a POD snapshot per order (`pf_pending_order_v1_t`), and the snapshot's self-describing field layout |
| `strategy_pending_order_fill_qty` / `_level_resolved` / `_effective_levels` / `strategy_trail_best_price` | ABI v4: engine-computed values for a resting order — the quantity it would open if filled at a given price, whether its relative offsets resolve yet, its resolved stop/limit/trail-activation levels, and the live position's trail extreme |
| `strategy_position_avg_price` / `strategy_position_cycle_seq` / `strategy_position_size` | ABI v4: the live position's volume-weighted average entry price, its cycle id, and its script-facing signed size |
Expand All @@ -481,7 +482,7 @@ strategy. They are additive; no symbol, struct or behaviour above changes, and
| `strategy_native_events_v1` / `_state_v1` | Poll the recorded event history by ordinal; read the lifecycle and its typed failure |
| `strategy_native_acknowledge_events_v1` / `strategy_native_event_window_v1` | Under the `WINDOW` event retention: say which events the host has read, so the kernel drops those command events at the next script-bar boundary; read the oldest ordinal a poll can still return |
| `strategy_native_timeframe_bar_interval_v1` | From inside `on_timeframe_bar`, the delivered bucket's own calendar interval (`pf_native_timeframe_interval_v1`: the C++ `NativeTimeframeBarContext::interval`); `PF_NATIVE_E_STATE` anywhere else |
| `strategy_native_partial_bar_v1` / `_series_bar_v1` / `_trail_state_v1` / `_liquidation_price_v1` | The four optional reads — the bar so far at the cursor, a declared higher-timeframe series' latest bucket, a live trail's projection, the solved liquidation level. Each answers `PF_NATIVE_ABSENT` where the C++ `std::optional` is empty |
| `strategy_native_partial_bar_v1` / `_series_bar_v1` / `_trail_state_v1` / `_liquidation_price_v1` | The four optional reads — the bar so far through the last path point consumed, a declared higher-timeframe series' latest bucket, a live trail's projection, the solved liquidation level. Each answers `PF_NATIVE_ABSENT` where the C++ `std::optional` is empty |
| `strategy_native_risk_state_v1` / `_marked_equity_v1` / `_recalculations_v1` / `_continuation_hash_v1` | The generic risk ledger, marked equity at a mark, the driven/suppressed recalculation counters, and the run's continuation identity |
| `strategy_native_margin_call_v1` | One margin call's whole economics by its event ordinal (`pf_native_margin_call_v1`: mark, units, the position before and after, the surviving book's equity and requirement, the re-solved liquidation price) |
| `strategy_native_sized_units_v1` | The units a `PF_NATIVE_INTENT_SIZED` request resolves to under the run's spec — the kernel's own sizing function, as a pure query before submitting |
Expand All @@ -502,12 +503,13 @@ declare all fail CI.

Every struct is tagged and size-prefixed (`struct_size`, `version`); an unknown
size, version or enumerator is refused with a documented negative status and
mutates nothing. `pf_native_run_spec_ext_v1` has five published lengths — the
mutates nothing. `pf_native_run_spec_ext_v1` has six published lengths — the
layout the lane first shipped (`PF_NATIVE_RUN_SPEC_EXT_V1_BASE_SIZE`), the same
struct with L9's appended risk tail (`PF_NATIVE_RUN_SPEC_EXT_V1_RISK_SIZE`), that
plus N8's intrabar / policy tail (`PF_NATIVE_RUN_SPEC_EXT_V1_POLICY_SIZE`), the
auxiliary-feed tail (`PF_NATIVE_RUN_SPEC_EXT_V1_AUXILIARY_SIZE`), the event-retention
tail, and the current layout; `pf_native_callbacks_v1`
tail (`PF_NATIVE_RUN_SPEC_EXT_V1_RETENTION_SIZE`), and the current layout with
K-ULP4's quantity-tolerance tail; `pf_native_callbacks_v1`
has four — the layout the lane first shipped (`PF_NATIVE_CALLBACKS_V1_BASE_SIZE`),
that plus its six-hook tail (`PF_NATIVE_CALLBACKS_V1_HOOKS_SIZE`), that plus the
policy-hook tail (`PF_NATIVE_CALLBACKS_V1_POLICY_SIZE`), and the current one,
Expand All @@ -522,7 +524,7 @@ needs no new symbol: the `strategy_stream_*` family takes these handles
unchanged. Worked example: [`examples/native/hello_kernel_c.c`](examples/native/hello_kernel_c.c);
reference: [`docs/pages/native-engine.md`](docs/pages/native-engine.md).

POD types `pf_bar_t`, `pf_trade_tick_t`, `pf_trade_t`, `pf_report_t`, `pf_security_diag_t`, `pf_trace_entry_t`, `pf_version_t`, `pf_trade_stats_t`, `pf_equity_stats_t`, `pf_metrics_t`, `pf_equity_point_t`, `pf_pending_order_v1_t`, `pf_field_desc_t` and the `pf_magnifier_distribution_t` enum complete the surface. ABI v2 added computed trading metrics and a per-bar equity curve; ABI v3 added `pf_trade_t::open_at_end`, TradingView's range-end close of a position still open after the last bar; ABI v4 added the live-runtime accessors above plus `pf_report_t::broker_state_hash` / `broker_state_hash_len` (a per-script-bar broker-state hash array, appended after `equity_curve_len`, NULL/0-length unless `strategy_set_broker_state_hash_recording` is on) and the `pf_pending_order_v1_t` generated POD mirror of the engine's resting-order record. Check `pf_abi_version()` before running: the report struct is caller-allocated.
POD types `pf_bar_t`, `pf_trade_tick_t`, `pf_trade_t`, `pf_report_t`, `pf_security_diag_t`, `pf_trace_entry_t`, `pf_version_t`, `pf_trade_stats_t`, `pf_equity_stats_t`, `pf_metrics_t`, `pf_equity_point_t`, `pf_pending_order_v1_t`, `pf_field_desc_t` and the `pf_magnifier_distribution_t` enum complete the surface. ABI v2 added computed trading metrics and a per-bar equity curve; ABI v3 added `pf_trade_t::open_at_end`, TradingView's range-end close of a position still open after the last bar; ABI v4 added the live-runtime accessors above plus `pf_report_t::broker_state_hash` / `broker_state_hash_len` (a per-report-point broker-state hash array, appended after `equity_curve_len`, NULL/0-length unless recording is on and a report point exists) and the `pf_pending_order_v1_t` generated POD mirror of the engine's resting-order record. Check `pf_abi_version()` before running: the report struct is caller-allocated.

Full flag semantics, string lifetimes and the three L0 evidence lanes behind the ABI v4 live surface: [`docs/pages/live-surface.md`](docs/pages/live-surface.md).

Expand Down
Loading
Loading