Skip to content

feat: coverage fixes + sha256 worker-verify (0.7.3)#15

Merged
luisleo526 merged 5 commits into
mainfrom
feat/coverage-0.7.3
Jun 17, 2026
Merged

feat: coverage fixes + sha256 worker-verify (0.7.3)#15
luisleo526 merged 5 commits into
mainfrom
feat/coverage-0.7.3

Conversation

@luisleo526

Copy link
Copy Markdown
Contributor

Deep-research coverage/trust fixes (gate-blocked release):

  • Divergent vars → ERROR (selective): last_bar_index (aliased to current bar) + time_close (aliased to bar open) now REJECT instead of silently-wrong-WARNING; bar_index/timenow stay WARNING. (+fixed a bug where time_close("D") function-call was wrongly flagged as the divergent var.)
  • max_bars_back WIRED: removed from NOT_YET; both strategy(max_bars_back=N) + max_bars_back(var,N) now size the engine ring-buffer Series<T>{N}. Byte-identical output when the directive is absent (proven across the full corpus); emitted C++ compiles against the engine headers. Faithful to Pine (caps history at N).
  • syminfo silent-gap warnings now fire on ALL reads of the 26 na-fields (was conditionals-only).
  • sha256 worker-verify: the shipped worker verifies the codegen archive's sha256 against the manifest when present (defensive; app writes it next).

Verify

1008 pytest green; gate PARITY OK over 278 (ok=261/err=17), verdicts asserted; determinism test green; emitted C++ g++-compiles. Parity tests updated (behavior changed, coverage preserved); 3 gate-corpus fixtures added.

Note: app must mirror the divergent-var ERROR + max_bars_back in the TS analyzer (next wave) to keep conformance.

🤖 Generated with Claude Code

luisleo526 and others added 5 commits June 18, 2026 01:17
…wrong mis-aliases)

last_bar_index is aliased to the CURRENT bar index and time_close (bare var)
to the bar OPEN timestamp in PineForge codegen. Both return a plausible but
wrong value that flows into trade logic, so a backtest is silently wrong. Add
DIVERGENT_VARS_ERROR = {last_bar_index, time_close} and emit Level.ERROR for
that subset at both emit sites (bare name + member chain); bar_index and timenow
stay WARNING. Sharpen the hints ("...is aliased to <X>; backtest would be
silently wrong — rejected") and the module docstring.

Disambiguate the bare divergent VARIABLE from a same-named FUNCTION call: the
generic child-walk would otherwise flag the callee of the session-aware
time_close(...) function. Track callee node ids and skip them.

Tests: split the divergent parametrize into warn-only vs error subsets; add
last_bar_index/time_close ERROR cases, bar_index/timenow still-WARNING cases,
and time_close(...) function-call not-flagged regression. Gate corpus: add
err/divergent_last_bar_index.pine + err/divergent_time_close.pine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… too

The 26 _SYMINFO_SILENT_GAP_FIELDS warned only inside an if/ternary condition, so
a field read directly in a plain expression (e.g. `x = syminfo.pricescale * 2`)
slipped out as na with NO signal. Broaden the gate to fire on EVERY read of
those fields; keep it a WARNING (never ERROR). The conditional phrasing
("condition will always be false") is kept where it applies; plain reads get
"any expression using it will be na".

Tests: add plain-expression parametrized coverage for all na-accept fields and
the exact arithmetic shape from the report; flip the old
test_syminfo_sector_non_conditional_no_warn (which encoded the bug) to assert
the plain read now warns and still does not error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engine's Series<T> history store is a fixed-capacity ring buffer with an
explicit Series<T>(int max_len = 500) ctor (include/pineforge/series.hpp);
reads past the retained depth return na. max_bars_back was therefore in
NOT_YET_FUNC ("silently dropped") even though the engine has the exact hook.

Wire it: CodeGen._compute_max_bars_back_cap scans the AST for both directive
forms — the strategy(..., max_bars_back=N) kwarg and the bare max_bars_back(var,
N) function — and takes the MAX literal N. Every Series<T> member declaration
now emits that capacity as a ctor arg (Series<T> name{N};). Applying the max to
all series is a safe superset of Pine's per-var semantics: it never retains LESS
than Pine, so any history access that succeeds in Pine succeeds here. Absent the
directive the cap is None and declarations keep the bare form, so directive-free
output is byte-identical to before (golden + determinism tests unchanged).

support_checker: drop max_bars_back from NOT_YET_FUNC (no longer rejected).
visit_call: the bare max_bars_back(...) call is a directive, not a value, so it
emits a no-op (the buffer sizing is done in the prepass) instead of tripping the
unknown-function guard.

Tests: flip test_max_bars_back_rejected -> _accepted; add official-surface
coverage for kwarg/function/max/default-unchanged sizing. Gate corpus: add
ok/validation__max-bars-back-function-call-deep-history-01.pine to anchor the
function form (the 3 existing fixtures only used the kwarg). Engine support
verified directly against series.hpp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The max_bars_back wiring comment claimed the {N} cap is applied to
"every Series declaration". That is inaccurate: the security-helper map
(_security_helper_series_, the std::unordered_map<string, Series<double>>
~line 971) does NOT pick up the cap — its entries are default-constructed
on first operator[] access and therefore always use the engine default
500. Reword to say the cap applies to the directly-declared Series<T>
members, and document the security-helper map as a known limitation.
Comment-only; no change to generated C++ output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shipped Pyodide transpile worker fetched manifest.json -> archive ->
unpackArchive + runPython with no integrity check. Add a defensive,
verify-if-present sha256 check: after fetching the archive bytes and
BEFORE unpackArchive, if manifest.sha256 is present, compute the SHA-256
of the bytes via the worker's WebCrypto (crypto.subtle.digest("SHA-256",
buf), hex-encoded) and compare. On mismatch, post an init-error
("codegen archive sha256 mismatch — expected <x> got <y>") and return
without unpacking or running. When manifest.sha256 is absent the check is
skipped, so older manifests that predate the field keep working
(forward/backward compatible) — the app writes the field in a later wave.

The hex encoding matches Node's createHash("sha256").digest("hex") (and
thus the gate's release.json sha256), so a manifest sha256 derived from
release.json will verify. Edits scripts/worker-template.mjs (the
__GLUE__-templated source); the generated npm/transpile.worker.mjs is a
gitignored build artifact regenerated by build-npm-package.mjs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@luisleo526 luisleo526 merged commit 085fb58 into main Jun 17, 2026
@luisleo526 luisleo526 deleted the feat/coverage-0.7.3 branch June 17, 2026 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant