Skip to content

Leverage-ladder gates require achieved leverage (all_of/greater_than conditions), not needs_attention alone - #197

Merged
seathatflowsinourveins merged 3 commits into
mainfrom
claude/leverage-flip-conditions-20260924
Sep 24, 2026
Merged

seathatflowsinourveins merged 3 commits into
mainfrom
claude/leverage-flip-conditions-20260924

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Owner

Closes audit gap from the 2026-09-24 trading wiring audit (native-agent-stack-71).

Audit gap #5 is closed: the leverage-ladder gates no longer flip on needs_attention == 0 alone.

What changed:

  • scripts/trading_gates.py: added two condition types, each with validation. greater_than compares exactly (as Decimal) and accepts a JSON number or a decimal string, because the runner writes Decimal values as strings. It rejects booleans, NaN, Infinity and anything that is not a number. all_of takes a non-empty list of equals, array_contains_id or greater_than conditions. It cannot be nested and cannot contain exists. Every condition pointer must now start with '/'.
  • catalogs/us-equities/gates-20260922.json: each of the leverage-ladder-1x, 2x and 4x rows now uses an all_of with five checks:
    • /needs_attention == 0
    • /leverage/config_max_leverage == "1", "2" or "4"
    • /leverage/next_lower_rung_ceiling == "0.5", "1" or "2"
    • /leverage/peak_achieved_leverage greater than that threshold
    • /leverage/seconds_above_next_lower_rung_ceiling > 0
      Each note now sets out the rung receipt format in advance: {schema_version 1, kind leverage_ladder_rung_receipt, rung, needs_attention, leverage}, where leverage is the run's paper-output.json leverage block copied unchanged. The titles now name the threshold.
  • blueprints/us-equities/adaptive-paper/leverage.py: added ONE_X_MINIMUM_EXPOSURE = 0.5. next_lower_rung_ceiling(1) now returns 0.5 instead of None, so the 1x receipt records time above 0.5x. It still returns None below 1x. The 1x config's entry budget allows up to 0.9x.
  • runner.py is unchanged (same bytes), so the native-fault receipt still matches the runner.py, safety.py and transport.py hashes in source-hashes.json. Its inline comments that say the 1x threshold is None and that no gate reads these fields are now out of date. README-safety.md says so.
  • Updated to match: the three rung config notes, README-safety.md (a new "Gate condition" paragraph), README.md, mover-v3/README.md, and the existing unit tests that expected None at 1x.
  • source-hashes.json and manifests/evidence.json: only the entries for changed registered files were updated, programmatically. No verdict-review-gate trust input was touched.

Limits:

  • These are offline unit tests on synthetic receipts and traces. No paper trial was run and no rung receipt exists.
  • There is no minimum time above the threshold: any positive time passes the checker. How long a rung was actually used is left to the manual review before a dated flip commit.
  • seconds_above is approximated tick by tick.

Tests. New tests:

  • tests/test_trading_gates.py: greater_than on numbers and decimal strings, including booleans, NaN and Infinity; all_of passing only when every member passes; shape errors rejected. New class LeverageLadderFlipConditionTests covers three things. The repository conditions are checked. A clean trial whose peak is at or below the lower cap with no time above it does not satisfy any rung. Each required field is needed on its own: needs_attention 1, peak not above, zero time above, a missing field, a missing leverage block, the old 1x threshold of None, the wrong rung config and a boolean for seconds all fail. GOOD_RECEIPTS and BAD_RECEIPTS now include the three ladder gates.
  • tests/test_adaptive_paper_runner.py: new AchievedLeverageGateTraceTests. It runs per-tick traces through runner._leverage_achievement_step with each rung's threshold, then checks the result with the repository gate rows. Traces that never go above the lower cap fail; traces that go above it pass; a peak seen only on the first tick records no time and fails.
  • tests/test_adaptive_paper_leverage.py: the 1x threshold is 0.5, and every rung's threshold is below its own cap. The existing native integration test now expects next_lower_rung_ceiling "0.5" at 1x.

Results:

  • Pinned paper runtime (Python 3.12.3, nautilus-trader 2.0.0rc5): test_adaptive_paper_runner, test_adaptive_paper_leverage, test_trading_gates, test_adaptive_paper_strategies and test_adaptive_paper_safety ran 293 tests, all OK, 0 skipped. This includes the native 1x/2x achieved-leverage integration tests.
  • Full suite CI-style (uv run -q --no-project --python /usr/bin/python3 --with numpy --with jsonschema --with pyyaml python -m unittest): 4028 tests, OK, 415 skipped. This ran before the final doc-only edit to mover-v3/README.md and evidence.json; the validators were rerun after that edit.
  • After the final edits, system python3 ran the three touched test modules: 197 tests, OK, 34 skipped (native-only).

Validation. All required validators pass (exit 0) on the final tree:

  • python3 scripts/validate.py: passed, 5281 hashed files.
  • python3 scripts/validate_catalogs.py: passed.
  • python3 scripts/landscape.py --root .: passed.
  • python3 scripts/evidence_manifest.py --check: passed.
  • python3 scripts/verdict_review_gate.py --base origin/main --root .: "no verdict rows changed (base 2d11464)".
  • python3 scripts/trading_gates.py --check: passed, no errors and no flip candidates. The three ladder gates show "receipt missing" and stay not_established.

All 48 entries in source-hashes.json match the files on disk (5 refreshed). runner.py, safety.py and transport.py are unchanged, so the hashes bound by the native-fault receipt still hold.

Independent review. Severities: medium, medium, low, low, low, info, info. Fix round b54ad77c64: fixed 2, rejected 0.

🤖 Generated with Claude Code

Scout and others added 2 commits September 24, 2026 12:00
… (audit gap #5)

The leverage-ladder-1x/2x/4x rows flipped on needs_attention == 0 alone, so a
rung could be established by a clean trial whose achieved exposure never
exceeded the next-lower rung's cap.

- scripts/trading_gates.py: add greater_than (exact Decimal comparison of a
  JSON number or decimal string) and all_of (non-nested equals /
  array_contains_id / greater_than members) conditions, with validation.
- catalogs/us-equities/gates-20260922.json: each ladder row's flip_condition
  is now an all_of over a preregistered rung receipt: /needs_attention == 0,
  /leverage/config_max_leverage and /leverage/next_lower_rung_ceiling equal
  to the rung, /leverage/peak_achieved_leverage > threshold and
  /leverage/seconds_above_next_lower_rung_ceiling > 0 (thresholds 0.5, 1, 2).
- leverage.py: next_lower_rung_ceiling(1) returns the documented 0.5x minimum
  exposure (ONE_X_MINIMUM_EXPOSURE) instead of None, so the 1x receipt
  accumulates time above 0.5x. runner.py is unchanged (the native-fault
  receipt's engine-source binding still holds).
- Tests: a trial that never exceeds the lower cap does not satisfy its rung
  (tests/test_trading_gates.py LeverageLadderFlipConditionTests and
  tests/test_adaptive_paper_runner.py AchievedLeverageGateTraceTests).
- Rung config notes, README-safety.md, README.md and mover-v3 README updated;
  source-hashes.json and manifests/evidence.json entries refreshed for the
  changed files only.

Offline unit tests on synthetic receipts and traces only; no paper trial was
run and no rung receipt exists. No minimum duration above the threshold is set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ix mover-v3 step-down text

Fix round for audit gap #5 review findings.

- scripts/trading_gates.py: new source_matches condition (standalone or as an
  all_of member). The receipt names an in-tree file by path and sha256; the
  file's bytes must hash to that value, and each [receipt, source] pointer
  pair must be equal with the same JSON types. Paths that are absolute, contain
  '..' or a backslash, or resolve outside the tree are refused.
- catalogs/us-equities/gates-20260922.json: each leverage-ladder-1x/2x/4x
  all_of now also requires /schema_version == 1, /kind ==
  "leverage_ladder_rung_receipt", /rung, /source/certified_run_status ==
  "passed", and a source_matches binding. The binding ties the receipt's
  /leverage and certified_run_status to the /leverage and /status of the
  committed paper-output.json it names. The preregistered receipt schema in
  the notes gains source.{paper_output_path, paper_output_sha256,
  certified_run_status}. The notes also state a limit: needs_attention is a
  count across runs, is not bound to the hashed file, and is checked at the
  manual qualification.
- mover-v3 README: the step-down list no longer calls
  seconds_above_next_lower_rung_ceiling a breach. It names achieved leverage
  above the ceiling in force as a proposed v3 rule (runner.py has no step-down
  on it today). seconds_above is described as a promotion input.
- README-safety.md: documents the binding and its limits.
- Tests: source_matches shape and behaviour. Every ladder rung rejects a
  receipt whose leverage block or status was edited, whose sha256 is wrong, or
  whose source is missing, outside the tree or absent. A rung also rejects a
  receipt that stitches one run's leverage onto another run's passed status,
  and one whose kind, schema_version or rung is wrong. Trials that never pass
  the lower cap still fail only on the achievement members.
  AchievedLeverageGateTraceTests now builds a hashed synthetic source.
  source-hashes.json and manifests/evidence.json entries are refreshed for
  the changed files only.

Offline unit tests on synthetic receipts and sources only. No paper trial was
run, and no rung receipt exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Resolve manifests/evidence.json by recomputing the hash of the merged
catalogs/us-equities/gates-20260922.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins merged commit cd623c8 into main Sep 24, 2026
25 of 26 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/leverage-flip-conditions-20260924 branch September 24, 2026 17:24
seathatflowsinourveins pushed a commit that referenced this pull request Sep 25, 2026
Main moved 150+ commits, including #186 and #194 (the v3 data files and
calendar extension) and #197 (leverage-ladder gate wording in the v3
README, which merged cleanly). manifests/evidence.json is main's registry
with the stale digests refreshed. The study tree is bound by its own tree
hash and pinned copies, and was never registered there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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