Skip to content

Replace #132: pack fixtures, fix CI, remove hash pinning - #133

Merged
sujitn merged 5 commits into
mainfrom
fix/pr132-replacement
Oct 20, 2026
Merged

sujitn merged 5 commits into
mainfrom
fix/pr132-replacement

Conversation

@sujitn

@sujitn sujitn commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Replaces #132. It carries the same fixed-income workflow work as #132, rebuilt on main so that 5.9 GB of JSON fixtures never enter Git history. It also fixes the problems that kept #132's CI red, and removes the hash pinning that made every edit to the repository need a matching hash refresh.

What was wrong with #132

  • 5.9 GB of fixtures in Git. Add provider-neutral fixed-income analytics workflows and durable recovery #132 committed 631 regression corpora of 1 MB or more. Seven of them (979 MB) were in Git LFS; the rest (5.2 GB) were plain blobs. Every CI checkout pulled all of it, and LFS bandwidth was spent on every job.
  • Test and Bounded Rust Assurance ran out of runner disk. About 70 include_str! sites embedded 60–180 MB corpora into hundreds of test and example binaries, on top of the checkout. Both jobs died with No space left on device. Most of the 935 assurance steps never ran.
  • Excel Add-in (Windows) had two latent failures.
    • The library parity input curve.fit.dated came from exp_m1. MSVC's libm returns a result one ulp away from glibc's, so the emitted request no longer matched cases.json.
    • The parity steps passed --binary target/excel/examples/X.exe to Python subprocess. Windows CreateProcess rejects forward-slash relative paths. The job never got this far before.
  • The release would have failed to publish. convex-analytics now depends on the new convex-rates and convex-credit crates, but release.yml still published only core, math, curves, bonds and analytics.
  • Every edit needed hash bookkeeping. The assurance tooling pinned the SHA-256 of nearly every protected file in four places (numeric-evidence.json, inventory.json, library-inventory.json, the comparison catalog) and required a change-reviews.json row with fresh hashes for each change. Any fix, even a clippy one, failed CI until those were refreshed.

Commits

  1. Bring over the Add provider-neutral fixed-income analytics workflows and durable recovery #132 workflow tree without its large fixtures. Add provider-neutral fixed-income analytics workflows and durable recovery #132's final tree (feb8e889), unchanged except that the 631 large fixtures are left out.
  2. Pack the large fixtures and fix the CI and release failures.
    • Each fixture of 1 MB or more is committed as <name>.json.xz: 14.2 MB in total, down from 5.91 GB. Git LFS is no longer used.
    • python3 tools/fixtures.py materialize writes the JSON beside each archive (about 80 s); the written files are Git-ignored. pack refreshes an archive after a deliberate regeneration.
    • The 67 include_str! sites that pointed at packed corpora, in 56 test and contract files, now call packed::text(...) from tests/fixtures/packed.rs. Each file is read once per process. Nothing else in those files changes; I verified this token by token.
    • extended.rs uses the two quote literals already in cases.json instead of recomputing them with exp_m1. The Excel job passes Windows-style binary paths.
    • Test, Bounded Rust Assurance, Excel and Java materialize fixtures instead of fetching LFS. The two Linux heavy jobs free unused preinstalled toolchains and build with CARGO_PROFILE_DEV_DEBUG=0: with line tables only, the workspace test build passed 32 GB before running a single test.
    • convex-rates and convex-credit are published before convex-analytics.
    • docs/TEST-DATA.md and README.md describe materialize instead of git lfs pull.
  3. Remove hash pinning and change-review records from assurance.
    • Deletes tools/assurance/change-reviews.json, numeric-evidence.json and tools/source_pins.py, and the checks that read them.
    • Drops sha256 from the inventory.json rows and from the comparison catalog and pack references. library-inventory.json lists inspected_sources as paths; its observation anchors are still checked against the current files.
    • Removes tests/fixtures/packed.json. The archives are the source of truth, and xz's CRC64 rejects a corrupt one. materialize and check also require tests/fixtures/.gitignore to match the archives.
    • Kept:
      • The structural checks: forbidden dependencies, lints, lifecycle purity, required CI steps, inventory paths and exception expiry.
      • The solver-default check (DEFAULT_TOLERANCE 1e-10, DEFAULT_MAX_ITERATIONS 100) and the required comparator and reference files.
      • The --base checks that reject wider tolerances and removed cases, measures or library coverage.
      • The hashes recorded inside fixture data and capture manifests, and the QuantLib wheel download checksum.
  4. Fix the offline comparison and Excel jobs found on the first full CI run.
    • Offline Quantitative Comparison now materializes the packed fixtures. It also frees runner disk and drops debuginfo, like the other jobs that hold the unpacked corpora.
    • The Excel tests checked CX.LIBRARY.RANGE output by rejoining it with LibraryCells.Join, which applies the 2 MiB request bound. Fourteen P&L responses are larger; the native response bound is 16 MiB. The tests now rejoin output with a test helper, ResponseCells.Join, that checks cell sizes only. The request-side round trips are unchanged.
    • LibraryCells.ValidateInput let EncoderFallbackException escape for an unpaired surrogate. It now throws the plain ArgumentException its test expects.
  5. Check the optional graph hedge controls within libm drift. Bounded Rust Assurance failed at step 600 of 945, which Add provider-neutral fixed-income analytics workflows and durable recovery #132's CI never reached.
    • The optional graph hedge reference, qualification and calibrated generators call the platform libm (expm1, erfc, exp). Their regenerated floats differ in the last digits on Linux and on Windows, and both differ from Codex's committed controls, so their byte-exact checks can pass only on the machine that wrote them.
    • Regenerating would cascade: the controls' bytes, and the reference script's own bytes, are recorded as source reads in other fixtures.
    • So the generators and controls are unchanged, and the three CI steps (same names) now run tests/scripts/optional_graph_hedge_controls.py. It regenerates the same six files, requires structure, text (including source reads) and integers to match exactly, and allows floats 1e-11 absolute, or 1e-13 relative above magnitude 100. The largest drift is 1.4e-12 on Linux and 2.8e-12 on Windows; the tightest consumer tolerance in the controls is 1e-10 (cash).
    • The generators' own check modes stay byte-exact, so they still pass only on the platform that wrote the controls.

No financial code, fixture value, expected result, tolerance or solver default changes. The only assertion changes are the Excel output round trips in commit 4 and the three optional-graph-hedge control checks in commit 5. #132's CI never reached dotnet test, so this PR is the first run of those tests.

Verification

Linux (Ubuntu under WSL 2):

  • The full workspace suite (cargo test --locked --all-features --workspace --no-fail-fast -- --test-threads=1, CARGO_PROFILE_DEV_DEBUG=0) gave 3,351 passed, 0 failed, 78 ignored. It ran before the clippy fixes in packed.rs and the contract files' module lines. After those fixes, I re-ran cargo fmt --check, cargo clippy --locked --workspace --all-targets --all-features -- -D warnings (0 warnings) and the fixture-reading tests (monotone_contracts, child_maturity, concurrent_claims, custody_lineage, date_rolls).

  • A regeneration probe found that 344 generator outputs reproduce the fixture bytes exactly.

  • At the final head:

    • tools/fixtures.py check and reconciliation/comparison.py check passed.
    • tools/assurance/check.py passed both with and without --base acc357b8.
    • The unit tests passed: assurance 37, library 64, comparison 26 and swap QuantLib 18.
  • Commit 5: the new checker passes on Linux and Windows. It rejects a 1e-10 change to a price, an edited source-read hash, a changed tolerance, a dropped case and a float turned into an integer. check.py with and without --base, the assurance tests and tools/library/check.py passed. The other 154 Python steps after step 600 that need no build pass on Linux. The 51 Python steps that need a built binary and the 134 cargo steps after it were first run in CI.

  • Commit 4: in a fresh clone with no materialized fixtures, the full Offline Quantitative Comparison job passed: 392 retained QuantLib rows, 973 swap values, 35,820 CDS values and the credit calibration. check.py and the assurance tests passed again with the new workflow.

  • CI on b111510e (run 39447079562): every job passed, including all 946 steps of Bounded Rust Assurance, Excel Add-in (Windows) with dotnet test, Test, Offline Quantitative Comparison, and the Java and QuantLib workflows.

Windows:

  • The Excel job's library_workflow parity (110 cases) and the five parity steps after it passed.
  • The Excel test project builds, and the managed RawJsonChunks… test passes.

Not verified here

🤖 Generated with Claude Code

sujitn and others added 3 commits October 20, 2026 12:02
This is the final tree of codex/production-readiness-pr (#132, head
feb8e88), except for the 631 regression corpora of 1 MB or more under
tests/fixtures/library (5.91 GB, seven of them in Git LFS). The next commit
adds them back as xz archives. The code was produced by Codex and is
carried over unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Commit the 631 corpora as <name>.json.xz (14.2 MB in all) instead of
  5.91 GB of JSON and LFS objects. `python3 tools/fixtures.py materialize`
  writes the JSON beside each archive; the copies are Git-ignored.
- Read those corpora at run time through tests/fixtures/packed.rs instead
  of include_str!, so test and example binaries no longer embed them.
  Shared contract files that nest inside one crate allow
  clippy::duplicate_mod on its module line.
- CI: materialize instead of fetching LFS, free runner disk in the two
  heavy Linux jobs, build them with CARGO_PROFILE_DEV_DEBUG=0, and pass
  Windows-style binary paths in the Excel job.
- extended.rs uses the quote literals already in cases.json instead of
  recomputing them with exp_m1, whose last digit differs on MSVC.
- Release: publish convex-rates and convex-credit before convex-analytics.
- Docs: describe materialize instead of git lfs pull.

No financial code, fixture value, expected result, tolerance or solver
default changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The assurance tooling pinned the SHA-256 of nearly every protected file and
required a review row with fresh hashes for every edit. This removes it:

- Delete tools/assurance/change-reviews.json, numeric-evidence.json and
  tools/source_pins.py, and the checks that read them.
- Drop sha256 from the tools/assurance/inventory.json rows and from the
  comparison catalog and pack artifact references.
- Remove tests/fixtures/packed.json: the .json.xz archives are the source
  of truth, and xz's CRC64 rejects a corrupt one.
- tools/library/library-inventory.json lists inspected_sources as paths;
  observation anchors are still checked against the current files.

Kept: the structural checks (forbidden dependencies, lints, lifecycle
purity, required CI steps, inventory paths, exception expiry), the solver
default check, the required comparator files, and the --base checks that
reject wider tolerances or removed cases, measures and coverage. Hashes
recorded inside fixture data and capture manifests are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026

Copy link
Copy Markdown

Walkthrough

Warning

Review details and warnings were omitted to fit the comment limit.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

44 issues found across 2713 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/convex-ffi/build.rs">

<violation number="1" location="crates/convex-ffi/build.rs:27">
P2: `generate()?` makes optional C-header generation a hard prerequisite for the cdylib and Java build, although the Java binding does not consume this header. Preserve the warning/skip path (or isolate header generation from the library build) so a cbindgen parser failure cannot block otherwise valid FFI tests and artifacts.</violation>
</file>

<file name="crates/convex-wasm/src/conventions.rs">

<violation number="1" location="crates/convex-wasm/src/conventions.rs:190">
P1: This exact lookup makes many valid dropdown selections fail to load defaults, including `Eurozone/GovernmentBond`; restore the market fallback for pairs without specific rules. The UI keeps the previous defaults when this call returns an error, so changing to such a pair can apply stale conventions.</violation>
</file>

<file name="crates/convex-analytics/src/spreads/ispread.rs">

<violation number="1" location="crates/convex-analytics/src/spreads/ispread.rs:140">
P1: `as_decimal_f64()` already returns the decimal rate, so this extra division makes nonzero implied I-spreads 10,000 times too small. Remove the `/ 10_000.0` suffix.</violation>
</file>

<file name="crates/convex/examples/library_curve_methods.rs">

<violation number="1" location="crates/convex/examples/library_curve_methods.rs:11">
P3: This example duplicates the complete consumer runner used by the sibling library examples, so fixes to envelope validation or CLI output must be copied across every binary. Extract the shared runner and pass the contract-specific cases and qualification text into it.</violation>
</file>

<file name="crates/convex-math/src/bounds.rs">

<violation number="1" location="crates/convex-math/src/bounds.rs:34">
P2: `interval` rejects finite endpoints whenever `b - a` overflows, so `bisection` cannot solve valid brackets such as `[-f64::MAX, f64::MAX]` despite using an overflow-safe midpoint. Validate the arithmetic required by each caller instead of requiring a finite raw width.</violation>
</file>

<file name="crates/convex-math/src/interpolation/mod.rs">

<violation number="1" location="crates/convex-math/src/interpolation/mod.rs:37">
P3: `MonotoneConvex::new` accepts zero interval forwards, but this new summary still promises positive forwards. Change the bullet to `Nonnegative forward rates`.</violation>
</file>

<file name="crates/convex-core/src/types/quote_display.rs">

<violation number="1" location="crates/convex-core/src/types/quote_display.rs:24">
P2: This truncates quotes instead of preserving the previous four-place rounding contract. Restore four-place rounding, including carry and banker's-half behavior, before emitting the display string; otherwise prices and yields can be shown one tick below their rounded quote.</violation>
</file>

<file name="crates/convex-bonds/src/instruments/callable_frn.rs">

<violation number="1" location="crates/convex-bonds/src/instruments/callable_frn.rs:40">
P2: This exposes `ParCall` schedules with non-par redemption prices through `contract_terms`; reject non-100 prices or otherwise enforce par pricing before converting the schedule to exercise terms.</violation>
</file>

<file name="crates/convex-ffi/README.md">

<violation number="1" location="crates/convex-ffi/README.md:54">
P2: The C surface list omits the newly exported `convex_library` RPC. Add it to this declaration so C clients can discover the versioned supplied-input API documented by the implementation.</violation>

<violation number="2" location="crates/convex-ffi/README.md:177">
P2: This says that YAS outputs were removed, although `convex_yas` still returns a full `YasResponse`. Say that the obsolete YAS government/benchmark spread fields were removed instead, so callers do not incorrectly delete YAS handling.</violation>
</file>

<file name="crates/convex-analytics/src/risk/convexity/analytical.rs">

<violation number="1" location="crates/convex-analytics/src/risk/convexity/analytical.rs:13">
P2: This delegation regresses valid settlement-time cash flows: `analytical_convexity(&[0.0], &[100.0], ...)` now errors instead of returning zero convexity. Preserve zero-time handling for this numerical-time API, or route it through a validation path that permits zero times.</violation>
</file>

<file name="crates/convex-analytics/src/risk/duration/macaulay.rs">

<violation number="1" location="crates/convex-analytics/src/risk/duration/macaulay.rs:13">
P2: This delegation removes the near-zero price guard, so nearly cancelling signed cash flows produce an extreme unstable duration instead of `CalculationFailed`. Preserve the existing `abs(price) < 1e-10` check before converting `risk.macaulay_duration`.</violation>
</file>

<file name="crates/convex-mcp/src/server.rs">

<violation number="1" location="crates/convex-mcp/src/server.rs:789">
P1: This full-library call runs synchronously on a Tokio worker, so concurrent calibration, hedge, or lifecycle requests can occupy all workers and stall the MCP server. Run the bounded CPU-heavy operation through `spawn_blocking` before returning the tool result.</violation>
</file>

<file name="crates/convex-bonds/src/options/hw1f/mod.rs">

<violation number="1" location="crates/convex-bonds/src/options/hw1f/mod.rs:86">
P2: `b` can return zero for valid subnormal mean-reversion values when `a * time` underflows, instead of the Ho–Lee limit `time`. Treat an underflowed product as the zero-mean-reversion case so bond-option and lattice calculations retain the correct limit.</violation>
</file>

<file name="crates/convex/examples/child_cash_pnl.rs">

<violation number="1" location="crates/convex/examples/child_cash_pnl.rs:1">
P3: The module comment mislabels this example as independently owned invoice cash. The cases exercise acquired-child coupon/principal entitlements, so rename the comment to avoid documenting the wrong financial behavior.</violation>
</file>

<file name="crates/convex-math/src/interpolation/cubic_spline.rs">

<violation number="1" location="crates/convex-math/src/interpolation/cubic_spline.rs:106">
P2: Exact-knot queries can fail for valid finite coordinates when a segment width exceeds `sqrt(f64::MAX)`: the formula creates `NaN` from `0 * (h * h)` before this check. Return the stored knot value for exact knots, or otherwise avoid forming the overflowing squared width.</violation>
</file>

<file name="crates/convex-bonds/src/types/amortization.rs">

<violation number="1" location="crates/convex-bonds/src/types/amortization.rs:142">
P2: Empty non-`Bullet` schedules are accepted and converted into full-face bullet profiles, even though those amortization types require a schedule. Reject empty entries for non-`Bullet` types (the `PassThrough` case is already handled separately).</violation>
</file>

<file name="crates/convex-analytics/src/quote_risk/optional.rs">

<violation number="1" location="crates/convex-analytics/src/quote_risk/optional.rs:28">
P1: This admission can relabel an optional bond's native PV and sensitivity into a caller-supplied currency, and it accepts non-decimal OAS units while passing decimal bumps to the pricer. Require the position and factor currency to match `prepared.currency()` and require `QuoteUnit::DecimalRate` before constructing the native certificates.</violation>

<violation number="2" location="crates/convex-analytics/src/quote_risk/optional.rs:42">
P1: Validate optional position semantics before scaling: accept only original-face `Notional` or unit `TradeCopies`, matching the native PV's reference size. The current ratio-only check lets arbitrary reference quantities produce materially mis-scaled OAS risk.</violation>
</file>

<file name="crates/convex-analytics/src/risk/var/mod.rs">

<violation number="1" location="crates/convex-analytics/src/risk/var/mod.rs:77">
P2: `product` rejects valid parametric inputs when an early factor underflows before a later exposure rescales it. Accumulate the product in a numerically stable order or track its scale before rejecting only a non-representable final result.</violation>
</file>

<file name="crates/convex-analytics/src/risk/duration/key_rate.rs">

<violation number="1" location="crates/convex-analytics/src/risk/duration/key_rate.rs:52">
P3: These new validation errors are rendered as `at most4096key...` and `1..4096key...`, which makes API failures difficult to read. Add spaces around the bounds: `at most 4096 key rates required` and `1..4096 key rate tenors required`.</violation>
</file>

<file name="crates/convex-analytics/src/workflow/optional_hedge.rs">

<violation number="1" location="crates/convex-analytics/src/workflow/optional_hedge.rs:48">
P1: This admission can accept a prepared bond built from different materialized curve nodes when its recipes match the graph, then calculate every hedge column against the prepared curves instead of `graph.baseline().curves`. Also call `prepared.admit_scenario_baseline(&graph.baseline().curves)` before pricing factors.</violation>
</file>

<file name="crates/convex-bonds/src/traits/bond.rs">

<violation number="1" location="crates/convex-bonds/src/traits/bond.rs:237">
P1: This default loses the underlying fixed bond’s schedule context for `CallableBond`, so generic yield, price, and risk analytics can either fail for contextual day counts or use incorrect settlement-to-payment times. Override `yield_cash_flows` on `CallableBond` to delegate to the base bond’s schedule-aware route.</violation>
</file>

<file name="crates/convex-analytics/src/cash_calibration/reprice.rs">

<violation number="1" location="crates/convex-analytics/src/cash_calibration/reprice.rs:64">
P2: `ReferenceZeroSpread` repricing accepts a reference curve with a different origin or time basis, causing discount factors to be evaluated on a different timeline than the issuer basis. Reject references whose `reference_date` and `time_basis` differ from `expected` before pricing.</violation>
</file>

<file name="crates/convex-server/src/configuration_admin.rs">

<violation number="1" location="crates/convex-server/src/configuration_admin.rs:70">
P2: `effective_configuration` serializes the full document and its full assembly, duplicating potentially almost 2 MiB of curve data. Production access middleware caps JSON responses at 4 MiB and converts oversized responses to `CONFIGURATION_UNAVAILABLE`, so valid near-limit configurations cannot be inspected; omit the duplicate payload or give this endpoint an appropriate bounded response budget.</violation>
</file>

<file name="crates/convex-analytics/src/spreads/government_curve.rs">

<violation number="1" location="crates/convex-analytics/src/spreads/government_curve.rs:306">
P2: Validate the requested maturity horizon before interpolation; otherwise `supplied_yield` accepts targets beyond the repository’s 150-year contract span and extrapolates them instead of returning `InvalidInput`.</violation>

<violation number="2" location="crates/convex-analytics/src/spreads/government_curve.rs:315">
P1: `with_extrapolation()` is linear, so out-of-range target maturities do not use the documented flat endpoint yield. Clamp the query to the first/last point before interpolation (or implement explicit flat endpoint handling).</violation>
</file>

<file name="crates/convex-math/src/optimization/mod.rs">

<violation number="1" location="crates/convex-math/src/optimization/mod.rs:27">
P2: This new guard breaks the compatibility function for reversed bounds: the previous implementation converged for `a > b`, but the wrapper now returns `NaN`. Accept reversed endpoints by normalizing them or by removing this order-only rejection.</violation>
</file>

<file name="crates/convex-wasm/src/ingress.rs">

<violation number="1" location="crates/convex-wasm/src/ingress.rs:41">
P2: `snapshot_record` rejects valid plain objects created in another realm because `Object.prototype` identity is realm-specific. Use a realm-independent plain-object check while retaining the own-key snapshot.</violation>

<violation number="2" location="crates/convex-wasm/src/ingress.rs:75">
P2: `decode_curve_points` calls `Array::from` before enforcing the 4096 limit, so large sparse arrays can allocate before rejection and custom iterators can replace the points. Read the original length first, then fetch each numeric index and require an own property.</violation>

<violation number="3" location="crates/convex-wasm/src/ingress.rs:77">
P3: The public curve-point error message is missing spaces. Change it to `Curve point count must be 1..4096`.</violation>
</file>

<file name="crates/convex-bonds/src/options/swaption_hw1f.rs">

<violation number="1" location="crates/convex-bonds/src/options/swaption_hw1f.rs:101">
P2: `try_payer_swaption_hw1f` accepts non-integral `tail` values but rounds them to `n`, pricing payments beyond the requested maturity. Reject non-integral annual tails before constructing `cs`.</violation>

<violation number="2" location="crates/convex-bonds/src/options/swaption_hw1f.rs:146">
P2: The checked pricer rejects valid high-strike swaptions when the Jamshidian root lies outside the hard-coded `[-1, 1]` bracket. Expand the bracket adaptively or validate and document a strike/rate domain that guarantees this interval.</violation>
</file>

<file name="crates/convex/examples/library_host_census.rs">

<violation number="1" location="crates/convex/examples/library_host_census.rs:21">
P2: Apply the shared 16 MiB response limit to the native envelope before comparing it with `execute_json`; otherwise large valid cases make the host-census generation fail instead of retaining the shared transport outcome.</violation>
</file>

<file name="crates/convex/examples/cash_calibration.rs">

<violation number="1" location="crates/convex/examples/cash_calibration.rs:56">
P2: The credit branch never compares `fitted.annualized_parameters` with `credit_case.expected_parameters`, so a calibration-parameter regression can still print success. Check the expected parameter vector as the cash calibration tests do.</violation>

<violation number="2" location="crates/convex/examples/cash_calibration.rs:63">
P2: The credit check discards `basis`, validating only aggregate bond price and cash projection. A wrong promised/recovery PV decomposition can therefore pass despite the fixture’s independent recovery expectations; retain `basis` and compare both PV components.</violation>
</file>

<file name="crates/convex-mcp/README.md">

<violation number="1" location="crates/convex-mcp/README.md:71">
P3: The README's Tools table omits the newly added `library` and `library_schema` MCP tools, so users cannot discover the supplied-input API from the server's tool inventory. Add rows for both tools.</violation>
</file>

<file name="crates/convex-curves/src/lib.rs">

<violation number="1" location="crates/convex-curves/src/lib.rs:234">
P3: This method now documents a nonnegative guarantee but is still named `guarantees_positive_forwards`, which can mislead callers into assuming strictly positive forwards. Rename it to reflect nonnegativity, or add a correctly named API and deprecate the ambiguous name.</violation>
</file>

<file name="crates/convex-math/src/extrapolation/mod.rs">

<violation number="1" location="crates/convex-math/src/extrapolation/mod.rs:62">
P2: `try_extrapolate` accepts targets before `last_t`, so linear extrapolation silently runs backward despite this API being for extending beyond the last point. Reject `t < last_t` here.</violation>
</file>

<file name="crates/convex-ffi/src/build.rs">

<violation number="1" location="crates/convex-ffi/src/build.rs:181">
P2: `business_day_convention` is applied only for fixed-rate bonds, although `CouponSpec` is shared by callable and sinking-fund specs. Forward this field when building those underlying fixed bonds too; otherwise identical JSON terms use different payment schedules by bond type.</violation>
</file>

<file name="crates/convex-analytics/src/pricing.rs">

<violation number="1" location="crates/convex-analytics/src/pricing.rs:66">
P2: The new issue-date validation covers `price_from_mark`, but callable OAS marks bypass it and accept settlements before issuance. Add the same `settlement < bond.issue_date()` check to the OAS branch before pricing.</violation>
</file>

<file name="crates/convex-ports/src/issuer_reference.rs">

<violation number="1" location="crates/convex-ports/src/issuer_reference.rs:24">
P3: `IssuerScopeDefinition::validate` does not validate the scope’s own effective interval or provenance, so direct callers can accept malformed issuer scopes. Validate those fields here, not only in the outer `MasterDefinition` path.</violation>
</file>

<file name="crates/convex-analytics/src/credit_calibration/reprice.rs">

<violation number="1" location="crates/convex-analytics/src/credit_calibration/reprice.rs:30">
P2: `validate_recipe` checks only that each discount dependency exists, so this repricer can use a same-key curve with incompatible metadata. Validate the supplied discount dependency’s full recipe before calling `price`; otherwise residuals may be computed against the wrong discounting definition.</violation>
</file>

<file name="bindings/java/src/main/java/com/convex/ConvexAnalytics.java">

<violation number="1" location="bindings/java/src/main/java/com/convex/ConvexAnalytics.java:96">
P2: The typed Java API still exposes `G_SPREAD`, but this change removes its only benchmark input and adds no `params.government` replacement. G-spread calls therefore fail with the native “actual government benchmarks” error; add a government-benchmark DTO/builder or explicitly remove/guard this typed operation.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

View guided diff | Re-trigger cubic

Comment on lines +190 to +192
let rules = registry
.rules(&key)
.ok_or("No registered convention rules for this market/instrument pair")?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This exact lookup makes many valid dropdown selections fail to load defaults, including Eurozone/GovernmentBond; restore the market fallback for pairs without specific rules. The UI keeps the previous defaults when this call returns an error, so changing to such a pair can apply stale conventions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-wasm/src/conventions.rs, line 190:

<comment>This exact lookup makes many valid dropdown selections fail to load defaults, including `Eurozone/GovernmentBond`; restore the market fallback for pairs without specific rules. The UI keeps the previous defaults when this call returns an error, so changing to such a pair can apply stale conventions.</comment>

<file context>
@@ -184,26 +172,30 @@ pub fn get_convention_options() -> JsValue {
-
-    let defaults = DefaultConventions {
-        day_count: format!("{}", rules.accrual_day_count),
+    let rules = registry
+        .rules(&key)
+        .ok_or("No registered convention rules for this market/instrument pair")?;
</file context>
Suggested change
let rules = registry
.rules(&key)
.ok_or("No registered convention rules for this market/instrument pair")?;
let rules = registry
.rules(&key)
.cloned()
.unwrap_or_else(|| registry.default_rules_for_market(market_enum));

@@ -129,7 +137,7 @@ impl<'a> ISpreadCalculator<'a> {
/// Implied bond yield.
pub fn implied_yield(&self, i_spread: Spread, maturity: Date) -> AnalyticsResult<Yield> {
let swap_rate = self.swap_rate_at_maturity(maturity)?;
let spread_decimal = i_spread.as_decimal().to_f64().unwrap_or(0.0) / 10_000.0;
let spread_decimal = i_spread.as_decimal_f64()? / 10_000.0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: as_decimal_f64() already returns the decimal rate, so this extra division makes nonzero implied I-spreads 10,000 times too small. Remove the / 10_000.0 suffix.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-analytics/src/spreads/ispread.rs, line 140:

<comment>`as_decimal_f64()` already returns the decimal rate, so this extra division makes nonzero implied I-spreads 10,000 times too small. Remove the `/ 10_000.0` suffix.</comment>

<file context>
@@ -129,7 +137,7 @@ impl<'a> ISpreadCalculator<'a> {
     pub fn implied_yield(&self, i_spread: Spread, maturity: Date) -> AnalyticsResult<Yield> {
         let swap_rate = self.swap_rate_at_maturity(maturity)?;
-        let spread_decimal = i_spread.as_decimal().to_f64().unwrap_or(0.0) / 10_000.0;
+        let spread_decimal = i_spread.as_decimal_f64()? / 10_000.0;
 
         let implied = swap_rate + spread_decimal;
</file context>
Suggested change
let spread_decimal = i_spread.as_decimal_f64()? / 10_000.0;
let spread_decimal = i_spread.as_decimal_f64()?;

Parameters(params): Parameters<LibraryToolParams>,
) -> Result<CallToolResult, McpError> {
Ok(CallToolResult::success(vec![rmcp::model::Content::text(
convex::library::execute_json(&params.request_json),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This full-library call runs synchronously on a Tokio worker, so concurrent calibration, hedge, or lifecycle requests can occupy all workers and stall the MCP server. Run the bounded CPU-heavy operation through spawn_blocking before returning the tool result.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-mcp/src/server.rs, line 789:

<comment>This full-library call runs synchronously on a Tokio worker, so concurrent calibration, hedge, or lifecycle requests can occupy all workers and stall the MCP server. Run the bounded CPU-heavy operation through `spawn_blocking` before returning the tool result.</comment>

<file context>
@@ -770,9 +767,35 @@ pub struct ListOutput<T> {
+        Parameters(params): Parameters<LibraryToolParams>,
+    ) -> Result<CallToolResult, McpError> {
+        Ok(CallToolResult::success(vec![rmcp::model::Content::text(
+            convex::library::execute_json(&params.request_json),
+        )]))
+    }
</file context>
Suggested change
convex::library::execute_json(&params.request_json),
tokio::task::spawn_blocking(move || {
convex::library::execute_json(&params.request_json)
})
.await
.map_err(|e| McpError::internal_error(e.to_string(), None))?,

"single-currency model OAS and reference PV currency must agree",
));
}
position.scale()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Validate optional position semantics before scaling: accept only original-face Notional or unit TradeCopies, matching the native PV's reference size. The current ratio-only check lets arbitrary reference quantities produce materially mis-scaled OAS risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-analytics/src/quote_risk/optional.rs, line 42:

<comment>Validate optional position semantics before scaling: accept only original-face `Notional` or unit `TradeCopies`, matching the native PV's reference size. The current ratio-only check lets arbitrary reference quantities produce materially mis-scaled OAS risk.</comment>

<file context>
@@ -0,0 +1,77 @@
+            "single-currency model OAS and reference PV currency must agree",
+        ));
+    }
+    position.scale()?;
+    super::finite::validate_settings(&key, &settings)?;
+    let native = prepared.risk(oas_decimal, RiskAxis::Oas, settings.bump, native_settings)?;
</file context>

policies: q::OptionalRiskPolicies,
) -> WorkflowResult<OptionalGraphHedgeRisk> {
super::hedge::validate_graph_hedge_input(input)?;
q::admit_optional_graph(prepared, graph)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This admission can accept a prepared bond built from different materialized curve nodes when its recipes match the graph, then calculate every hedge column against the prepared curves instead of graph.baseline().curves. Also call prepared.admit_scenario_baseline(&graph.baseline().curves) before pricing factors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-analytics/src/workflow/optional_hedge.rs, line 48:

<comment>This admission can accept a prepared bond built from different materialized curve nodes when its recipes match the graph, then calculate every hedge column against the prepared curves instead of `graph.baseline().curves`. Also call `prepared.admit_scenario_baseline(&graph.baseline().curves)` before pricing factors.</comment>

<file context>
@@ -0,0 +1,113 @@
+    policies: q::OptionalRiskPolicies,
+) -> WorkflowResult<OptionalGraphHedgeRisk> {
+    super::hedge::validate_graph_hedge_input(input)?;
+    q::admit_optional_graph(prepared, graph)?;
+    let position = &input.reference_position;
+    if position.quantity_unit != q::RiskQuantityUnit::Notional
</file context>

@@ -5,7 +5,9 @@

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: These new validation errors are rendered as at most4096key... and 1..4096key..., which makes API failures difficult to read. Add spaces around the bounds: at most 4096 key rates required and 1..4096 key rate tenors required.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-analytics/src/risk/duration/key_rate.rs, line 52:

<comment>These new validation errors are rendered as `at most4096key...` and `1..4096key...`, which makes API failures difficult to read. Add spaces around the bounds: `at most 4096 key rates required` and `1..4096 key rate tenors required`.</comment>

<file context>
@@ -44,16 +46,60 @@ impl KeyRateDurations {
+    pub fn total_duration(&self) -> AnalyticsResult<Duration> {
+        if self.durations.len() > 4096 {
+            return Err(AnalyticsError::InvalidInput(
+                "at most4096key rates required".into(),
+            ));
+        }
</file context>

-v, --verbose Enable verbose logging
-h, --help Print help
-V, --version Print version
```

## Full supplied-input library API

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The README's Tools table omits the newly added library and library_schema MCP tools, so users cannot discover the supplied-input API from the server's tool inventory. Add rows for both tools.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-mcp/README.md, line 71:

<comment>The README's Tools table omits the newly added `library` and `library_schema` MCP tools, so users cannot discover the supplied-input API from the server's tool inventory. Add rows for both tools.</comment>

<file context>
@@ -59,7 +62,15 @@ Options:
   -V, --version        Print version

+## Full supplied-input library API
+
+See the shared API, exact terms and capability limits.
</file context>


</details>

}
let points = Array::from(&value);
if points.length() == 0 || points.length() > 4096 {
return Err("Curve point count must be1..4096".into());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The public curve-point error message is missing spaces. Change it to Curve point count must be 1..4096.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-wasm/src/ingress.rs, line 77:

<comment>The public curve-point error message is missing spaces. Change it to `Curve point count must be 1..4096`.</comment>

<file context>
@@ -0,0 +1,125 @@
+    }
+    let points = Array::from(&value);
+    if points.length() == 0 || points.length() > 4096 {
+        return Err("Curve point count must be1..4096".into());
+    }
+    points
</file context>
Suggested change
return Err("Curve point count must be1..4096".into());
return Err("Curve point count must be 1..4096".into());

}

impl InterpolationMethod {
/// Returns true if this method guarantees positive forward rates.
/// Returns true if the method's correctly represented admissible input

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This method now documents a nonnegative guarantee but is still named guarantees_positive_forwards, which can mislead callers into assuming strictly positive forwards. Rename it to reflect nonnegativity, or add a correctly named API and deprecate the ambiguous name.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-curves/src/lib.rs, line 234:

<comment>This method now documents a nonnegative guarantee but is still named `guarantees_positive_forwards`, which can mislead callers into assuming strictly positive forwards. Rename it to reflect nonnegativity, or add a correctly named API and deprecate the ambiguous name.</comment>

<file context>
@@ -217,10 +224,16 @@ pub enum InterpolationMethod {
 
 impl InterpolationMethod {
-    /// Returns true if this method guarantees positive forward rates.
+    /// Returns true if the method's correctly represented admissible input
+    /// domain guarantees nonnegative forwards. This does not validate an
+    /// arbitrary raw-value representation or certify a market curve.
</file context>

impl IssuerScopeDefinition {
/// Bounded structural and source-time admission; no pricing or recovery estimate.
pub fn validate(&self, recorded_at: Option<UtcTimestamp>) -> Result<(), ReferenceError> {
self.directory

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: IssuerScopeDefinition::validate does not validate the scope’s own effective interval or provenance, so direct callers can accept malformed issuer scopes. Validate those fields here, not only in the outer MasterDefinition path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At crates/convex-ports/src/issuer_reference.rs, line 24:

<comment>`IssuerScopeDefinition::validate` does not validate the scope’s own effective interval or provenance, so direct callers can accept malformed issuer scopes. Validate those fields here, not only in the outer `MasterDefinition` path.</comment>

<file context>
@@ -0,0 +1,54 @@
+impl IssuerScopeDefinition {
+    /// Bounded structural and source-time admission; no pricing or recovery estimate.
+    pub fn validate(&self, recorded_at: Option<UtcTimestamp>) -> Result<(), ReferenceError> {
+        self.directory
+            .validate()
+            .map_err(|_| ReferenceError::InvalidInput("invalid issuer directory"))?;
</file context>

sujitn and others added 2 commits October 20, 2026 14:34
- Offline Quantitative Comparison never materialized the packed fixtures,
  so the swap capture tests could not read calibration-replay.json. It now
  materializes them, and frees runner disk and drops debuginfo like the
  other jobs that hold the unpacked corpora.
- The Excel tests checked CX.LIBRARY.RANGE output by rejoining it with
  LibraryCells.Join, which applies the 2 MiB request bound. Fourteen P&L
  responses are larger (the native response bound is 16 MiB), so the tests
  now rejoin output with ResponseCells.Join, which checks cell sizes only.
- LibraryCells.ValidateInput leaked EncoderFallbackException for an
  unpaired surrogate; it now throws the ArgumentException the add-in and
  its test expect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bounded Rust Assurance failed at "Check independently declared optional
cash model and hedge controls". The optional graph hedge reference,
qualification and calibrated generators call the platform libm (expm1,
erfc, exp), so their regenerated floats differ in the last digits on
Linux and on Windows, and both differ from the committed controls. Their
byte-exact check can pass only on the machine that wrote them.

Regenerating the controls would cascade: their bytes, and the reference
script's own bytes, are recorded as source reads in other fixtures. So
the generators and controls stay unchanged, and the three CI steps now run
tests/scripts/optional_graph_hedge_controls.py. It regenerates the same
six files and requires structure, text (including source reads) and
integers to match exactly. Floats must agree within 1e-11 absolute, or
1e-13 relative above magnitude 100. The largest drift is 1.4e-12 on Linux
and 2.8e-12 on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sujitn
sujitn merged commit 664b2a3 into main Oct 20, 2026
13 checks passed
@sujitn
sujitn deleted the fix/pr132-replacement branch October 20, 2026 20:46
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