RecastEngine
Copyright 2026 Yueqi Chen, University of Colorado Boulder

This product includes software developed as part of the SciRecast ecosystem,
an open-source, agentic framework for modernizing legacy scientific software.
RecastEngine is its Core Layer.

RecastEngine is licensed under the Apache License, Version 2.0. Two notes
apply, per the ecosystem's governance model:

  1. This repository contains framework and basic functionality only. It carries
     no domain knowledge about any particular scientific model. Domain plugins
     live in their own repositories under their own notices.

  2. Vulnerabilities discovered by this engine's cyber gate are recorded in
     Sec-Track, whose access is restricted to the PI's group to reduce the risk
     of malicious exploitation. No unpatched finding is published here.

Work relayed from elsewhere
---------------------------

Some of this repository was written in another repository and reached this one
through a maintainer. No commit here carries an outside author: both relays
below were rewritten as they moved, so the file names its source and this
section names whose work it was, because a provenance line is not attribution
and both are needed. See CONTRIBUTING.md.

  recast/transform/numpy/, recast/fortran/, recast/verify/ and
     recast/oracle/f2py.py  -- the translation pipeline, from pipeline/ in
     CESM-language-translator. Written by Qinrun Dai, who commits as second5t
     <mouseisnotalwaysmouse@outlook.com>. translate.py became the NumPy
     backend and its rule layers; extract_interface.py, extract_constants.py,
     chunk.py and resolve_use.py became the Fortran frontend; notary.py,
     highprec_verify.py and rwset.py became the three verifiers; gen_wrapper.py
     and the build half of diff_driver.py became the f2py-golden oracle. The
     analysis, the translation rules, the wrapper rules and the verification
     arguments are theirs; what changed on the way in was the decomposition
     into the plugin contract -- what had been one function became a Frontend,
     a Transform, and Verifiers that decide separately -- and the removal of
     the site paths.

  recast/transform/jax/  -- the JAX backend and its _f_* runtime, from
     13_jax_backend/jaxize.py and jax_shim.py in CESM-Agent-Produced-Scripts.
     Written by Qinrun Dai, who commits as second5t
     <mouseisnotalwaysmouse@outlook.com>. The emission rules, the kernel
     eligibility, the lax lowering and the two-tier gate's argument are
     theirs; what changed on the way in was the command-line front end, the
     emitted header, and the formatting.

  transform/numpy/subprograms.py's poison_undefined, the provenance banner
     in tools/*_diff.py, and conformance/test_verifier.py's placement control
     -- three checks, from poison_empty.py, corpus_fingerprint.py and
     substitute_selfcheck.py in the same pipeline/ as the first entry above.
     Written by Qinrun Dai, who commits as second5t
     <mouseisnotalwaysmouse@outlook.com> and from NCAR machines under their
     own name. Not a port: none of those three files was carried, and each
     mechanism here is different -- theirs monkeypatches np.empty at run time
     where this emits the fill, theirs hashes a corpus tree where this records
     a revision, theirs substitutes a module into a live harness where this
     runs a conformance case twice. What is theirs is the finding in each
     case: that determinizing an undefined read makes it reproducible rather
     than visible, that NaN is what makes a float one visible and an
     impossible-index sentinel plus an A/B diff is what makes an integer one
     visible; that a count derived from
     a corpus is unfalsifiable without that corpus's identity beside it; and
     that a harness needs a positive control on itself, out-of-tree, because
     substituting a thing for itself in place exercises nothing.

  fortran/semantics.py's declared-dtype axis of generic dispatch -- from
     issue #4 against CESM-language-translator and the fix in its
     pipeline/translate.py. Found and reported by Qinrun Dai, who commits as
     second5t <mouseisnotalwaysmouse@outlook.com>. What is theirs is the
     finding: that rank and integer-ness are not enough to separate overloads
     that differ by declared type -- a derived type, a real kind, an array's
     element type -- and that the axis has to be built so it can only narrow,
     never flip, or adding it changes resolutions that were already unique.
     Both halves are relayed. The second half is the load-bearing one and is
     why only a *declared* dtype answers here: a dtype inferred for an
     expression that is wrong does not refuse a call, it silently picks a
     different overload. The other half of that issue -- a resolution failure
     swallowed at the call site and falling through to array indexing, which
     emitted an unbound name with a spurious 0-based shift and still scored
     mechanical -- did not apply: all four dispatch sites here already let
     the refusal reach the block.

  Five translation defects -- transform/numpy/{statements,subprograms,
     expressions,constants,names}.py, transform/numpy/runtime.py's intrinsic
     namespaces and loop-exit exceptions, and fortran/interface.py's
     dimension_stmt_shapes -- from issues #8, #9, #10, #11 and #12 against
     CESM-language-translator, and the fixes in its pipeline/translate.py,
     extract_interface.py, extract_constants.py and fortran_shims.py. Found
     and reported by Qinrun Dai, who commits as second5t
     <mouseisnotalwaysmouse@outlook.com>, against BSD Fortran libraries this
     repository does not carry. Not a port: no file was taken and every
     mechanism differs -- theirs binds an intrinsic module to a shim module
     the generated file imports where this puts the namespaces in the runtime
     it already inlines and imports nothing -- which cost a relay defect of its
     own, since a namespace no import line announces is invisible to the
     read/write check downstream until it is told; theirs keys a named EXIT
     off a
     stack the emitter threads through its own recursion where this reads the
     construct name off fparser's reader line and emits a catcher only where
     something crosses a loop; theirs re-parses a refused initializer inside
     the token pass where this checks at the pass's boundary, so no branch of
     it can return a value nothing can evaluate. What is theirs is the
     finding in each case: that a scalar carrying subscripts is
     indistinguishable from a statement function unless the standalone
     DIMENSION form is read; that an integer constant's width has to come
     from its value or the module raises before it runs; that an initializer
     the token pass cannot classify is silently emitted as Fortran; that a
     module the standard provides has no companion to import; and -- the one
     no gate would have found -- that EXIT and CYCLE naming an enclosing
     construct compile to a bare break and continue that leave the wrong
     loop, which is a running program with the wrong answer rather than a
     failure. The sixth of the set, an assumed-size ``x(*)`` read as rank 2,
     was already right here.

  Eight more -- fortran/rwset.py's KIND and caller-buffer rules and
     verify/rwset.py's copy-out target, transform/cuda/emitter.py's
     per-variant state, oracle/f2py.py's array-valued results,
     transform/numpy/statements.py's declared lower bound under sequence
     association and its formatted internal write with the shim in
     transform/numpy/runtime.py, and transform/numpy/modules.py's unbound
     stub imports -- from the fixes for issues #13, #16, #17, #18, #20, #37,
     #38 and #39 against CESM-language-translator, in its pipeline/rwset.py,
     cudaize.py, numbaize.py, gen_wrapper.py, translate.py and
     fortran_shims.py. Those issues were found by this repository's
     differentials and filed from here; the fixes are Qinrun Dai's, who
     commits as second5t <mouseisnotalwaysmouse@outlook.com>. What is theirs
     is each rule's shape: that the KIND exclusion has to be the emitter's
     rule exactly, a ``kind=`` keyword anywhere and the positional the
     conversion reserves for it; that a caller-buffer OUT is a read on the
     source side because the emission passes it in; that the copy-out
     shim's destination is a write; that an array-valued result is spelled
     with its extents and an extent naming no argument is a hidden dummy;
     that a trailing subscript under sequence association shifts by the
     axis's declared bound; that a format decides a write's layout and an
     unimplemented descriptor is refused rather than list-directed; and
     that an import nothing binds to is an error waiting to happen. Two are
     relayed as findings only: #19, a DO with no loop control, was already
     handled here, and #14's naming of a resolved generic in a device
     function was already spelled here the way the CUDA file defines it.
     The format shim, ``_f_fmt_write``, is the one piece of code carried
     across close to verbatim, retyped, and "close" is two substitutions:
     its unparsable-format raise is ``ValueError`` where upstream's is
     ``NotImplementedError``, because that name is how a generated file
     here spells a refused block and a runtime that inlines it would read
     as one; and ``_fmt_one``'s ``w``/``d``/``mn`` are ``width``/
     ``decimals``/``minimum``, because the first is reused as a string and
     an integer and the type checker will not have it. Neither changes a
     byte of output. The bit intrinsics of #15, from the same
     fortran_shims.py, are carried the same way and unchanged.

  recast/fortran/interface.py's host-association pass  -- the rule that an
     internal function's result variable is the function's own and never
     host-associated, from the fix for issue #43 against
     CESM-language-translator, in its pipeline/extract_interface.py. The
     issue was found by this repository's corpus triage and filed from
     here; the fix is Qinrun Dai's, who commits as second5t
     <mouseisnotalwaysmouse@outlook.com>. What is theirs is the observation
     that the result lives in the record's ``result`` and in none of the
     sets the pass subtracted, so a host variable of the same name was
     reported as reached and passed as a trailing actual the Fortran does
     not have. One line here, its own test. The same upstream revision
     closed #40, #41, #42 and #44, none of which is relayed: the engine was
     already on the side the fixes moved the pipeline to, and the
     differentials record the closing.

  recast/scan/, recast/sarif.py and recast/conformance/fake_tool.py  -- the
     cyber gate, from tools/devsecops-local.sh and tests/run.sh in CESM-CC-Test
     (hpc-devsecops). Written by Chien-Wei Huang, who commits under that name
     from Derecho and as Champollion9012 <hunte408@gmail.com>. Not a port: the
     original is shell and none of it was carried. What is theirs is the
     design -- which tools to run and how, SARIF as the common shape, the
     three-way passed / findings / incomplete contract with its exit codes, a
     missing tool counted as a check that did not complete, and the idea that
     the honest place to fake a wrapped tool is PATH rather than the wrapper.
     What is new is the expression of all of that against the plugin contract.

  recast/verify/probe_protocol.py and recast/verify/probe_inject.py  -- the
     probe protocol and its comparison, from gate_sdk/gate.h and
     gate_sdk/scripts/gate_harness.py and stdout_compare.py in ParaCodex
     (Scientific-Computing-Lab/ParaCodex, MIT). Written by Erel Kaplan and
     the ParaCodex authors (Kaplan, Bitan, Ghrayeb, Chen, Yotam, Hasabnis,
     Oren, arXiv:2601.04327). What is theirs is the protocol -- the two line
     forms a program prints, the checksum-identical / statistics-within-
     tolerance / deterministic-across-runs rule in that order, the 1e-2
     default, and the stdout comparison's three readings -- carried here
     with the same regular expressions so a program instrumented for their
     harness is judged the same way. What is new is that it returns a record
     rather than exiting, two suite-specific readings of a program's own
     verdict (NPB's, Rodinia's) the word list got wrong, and the injection:
     their supervisor agent placed the probes in both programs by hand; the
     engine places a candidate's into the reference by anchor matching and
     refuses when the place is not unique.
