Spec¶
The living, caveman-encoded design spec — goals, constraints, interfaces, invariants, open research questions, and the task/bug ledger. This is the authoritative source for anything that contradicts a summary elsewhere on this site; the pages under Reference are prose explanations of what's formalized here.
SPEC — itis-sumo¶
Caveman-encoded (drop articles/filler; → becomes, ! must, ? may/uncertain, ⊥ never). Standalone surrogate-modeling library aggregating prior MetaModeling trials.
LINKS¶
- engine ! PyPI wheel
itis-dakota==6.24.13(Dakota 6.24, T55kd — first 6.24.x shippingwin_amd64wheels (2026-10-06, cp312–314 linux+mac+win) → Windows ! supported platform; crossed the 6.24.8 cp311-drop cliff: py floor 3.11→3.12, mmux/vite parity re-anchored to 1.5.11, see §C py). In-repoitis-dakota/= fork checkout, REFERENCE-ONLY: pkg consumes pre-built wheels, ⊥ builds fork (forkfeat/windows-wheels= source of the 6.24.x win wheels; R5 get-or-create fix ⊥ landed there). The 6.23+interface_cacheregression (R5) is present upstream in 6.24.13 (.at()throw still indakota/src/DakotaInterface.cpp) and is shimmed in itis-sumo (config/funs_create_dakota_conf.py::add_r5_interface_cache_workaround,truth_model_pointer='R5_WA_MODEL'), not waiting on the upstream fix — shim re-verified green on the real 6.24.13 wheel (T55kd) - feeding repos (per-branch/commit inventory + more-to-port →
PORTING.md): ../mmux_vite(GiulianaRenc/mmux_vite): v1 port source = branchjgo/sobol-indices@22685f7→flaskapi/src/mmux_flaskapi/{dakota,data_preprocessor}/; E1 inspiration = branchjgo/sumo-model-export-import@d5150b9→ re-implemented in-repo onfeat/sumo-model-export-import(T12ze-T14qa ✓); Sobol' T31rb estimator tranche =upstream/develop@6eda782d(PR#649: exact arbitrary-d pairs + order masses) →evaluate/funs_evaluate.py+ api types (T51pq ✓)../itis_dakota_projects(ITISFoundation/itis-a-dakota-project) +../itis_dakota_projects_clean(ITISFoundation/itis_dakota_projects): recycledvalidate_dakota_installation/get_dakota_version+validateCLI../mmux_python(ITISFoundation/mmux_gui): ancestor lineage only (⊥ direct port source)- future consumer →
../mmux_vite/flaskapi(swaps in-tree core → this pkg) - research →
DAKOTA-STUBS.md(stubs + JSON→pydantic POC)
VOCAB¶
Canonical nouns for ∀ public API, docs, tests, commit messages. One word per concept, ⊥ synonyms drifting back in.
- sample → one row of tabular data (an observation). ⊥ "job", ⊥ "point", ⊥ "record"
- variable = parameter → one INPUT column
- response = quantity-of-interest (QoI) → one OUTPUT column
- variables vs responses ! stay distinguished — they get different downstream treatment (normalization, sign, sampling, Sobol roles)
- domain → where we may draw/explore a variable (bounds + scale). Property of the design space, auto-inferable from existing samples
- distribution → real-world shape of a variable (a claim about the world). ⊥ inferable from a training design
- surrogate = SuMo → the trained metamodel
- oSPARC's FunctionJob is ⊥ itis-sumo vocabulary; it lives on the consumer side of the boundary
§G¶
Standalone pkg itis-sumo (SuMo = Surrogate Model): aggregate MetaModeling core from mmux/vite + prior trials (mmux_python, itis_dakota_projects) into one wheel-driven library, usable BOTH inside mmux/vite AND headless (notebook/scripts) → build/evaluate/cross-validate surrogates + UQ (Sobol/correlation) + sampling (LHS/grid) + MOGA. First expansion (E1): SuMo model export/import persistence.
Consumer contract (grill 2026-08-18): itis-sumo owns ALL surrogate machinery end-to-end — training-file creation, preprocessing, Dakota config, run dirs, inverse transforms. Consumers (flaskapi OR a direct python user) pass ONLY tabular samples + configuration, and receive typed results in original units.
§C¶
- engine ! itis-dakota wheel
dakota.environment.study(input_string=...); ⊥ subprocessdakotabin, ⊥ vendor surfpack; in-repoitis-dakota/fork ! REFERENCE-ONLY (⊥ build; runtime = PyPI wheel) - v1 config ! NIDR string composers (proven); JSON/pydantic seam later (DAKOTA-STUBS POC, Dakota 6.24 experimental)
- port ! near-verbatim from mmux_flaskapi
dakota/+data_preprocessor/; re-rootmmux_flaskapi.→itis_sumo.; prune known-dead (process_json_file,add_interface_s4l,create_function_sampling_conffile,create_moga_iterative_optimization_conffile); de-webJobVariableSelection/required_completed_jobs(moved from blueprints into pkg) — SUPERSEDED in part by the tabular-only boundary below:required_completed_jobs' Dakota rule stays, the job-shaped models go back to the consumer (T24cm) - recycle itis_dakota_projects:
validate_dakota_installation(),get_dakota_version(),validateCLI, config sanity-guard; ⊥ subprocessDakotaProject/factory/generator/parser/activity-logger - deps ! itis-dakota==6.24.13 (Dakota 6.24, T55kd — 6.24.7→6.24.13 for the first
win_amd64wheels; the 6.23+interface_cacheregression R5 is present upstream but shimmed in itis-sumo, see R5), numpy, pandas, scipy; scikit-learn ⊥ KFold only - py.typed;
src/layout; docs MkDocs + headless notebook; tests standalone ⊥ flask/osparc - versioning single-source in
[project].version; uv/uv_build + pip-compile pins; Conventional Commits - release channel policy: feature branches →
.devN(manual alpha/dev prereleases);develop→aN(TestPyPI);main→ stable versions only (first:0.1.0, T56xx — the earlierbNplan is retired:version-check.ymlalready rejects any prerelease PR targetmain, sobNwas never CI-reachable); real PyPI ships ⊥ automatically — tagv0.1.0→ manualworkflow_dispatch {registry: pypi}(T56xx); version bump ! enforced by a PR-time CI check (target-branch suffix, PEP 440 order, ⊥ reuse/regression); ⊥ bot commit ondevelop/mainthemselves (both branch-protected, no bypass granted) — a bot commit on a PR's OWN head branch, pre-merge, is unaffected by that constraint and is how theaNcounter gets auto-bumped (T43bv); merge-time job ! pushv<version>tag only (tags fall outside 'require PR' branch protection);developmay later renamestaging - real PyPI publish + GitHub Release ! manual
workflow_dispatchonly (for now); CI builds/tests alpha/beta/rc versions; feature.devNversions publish locally viamake publish-testpypi-dev+.envtoken, ⊥ CI/TestPyPI tag cascade - license ! itis-sumo currently private/pending quality review; adopt itis-dakota's license (IT'IS Foundation - All Rights Reserved) until an explicit public/OSS decision is made — MIT retracted as premature given itis-dakota (a required dependency) is itself proprietary
- TestPyPI publish token ! local
.env(gitignored)TESTPYPI_TOKEN; Make injects-t TESTPYPI_TOKEN; ⊥ committed, ⊥ required as a pre-exported shell var - feature-branch TestPyPI local path !
make publish-testpypi-devcomputes/writes next.devN, builds/checks/uploads via local.envtoken; ⊥ commit/tag/CI publication - E1: artifacts keyed server
sumo_model_id(uuid); metadata sidecar{id}.metadata.json; ⊥ user-supplied path/prefix keys (traversal) - py ! 3.12–3.14 (T55kd): crossing the 6.24.8 cp311-drop cliff for the win wheels retired 3.10/3.11;
requires-python = ">=3.12,<3.15". mmux/vite engine parity re-anchored (owner call 2026-10-14): the consumer may stay onitis-dakota1.5.11 (packaging-only diff vs the deleted 1.5.9, T16mo rung 1) while this pkg runs 6.24.x — parity ! process (same 6.20 semantics where the consumer needs them), ⊥ engine-version identity; itis-sumo's py floor ⊥ longer chained to flaskapi's - cross-repo call surface ! narrow, versioned, "deep" entrypoints — one stitched public function per feature (e.g.
analyze_dataset()), ⊥ flaskapi orchestrating several itis-sumo internals itself; version-pinning itis-sumo is fine (same pattern as the itis-dakota engine pin, T16mo) — the coupling risk is a wide call surface, not the pin - data in ! plain tabular (
pd.DataFrameor an itis-sumo dataclass trivially convertible to one); ⊥FunctionJob/oSPARC job concepts anywhere in itis_sumo. Ownership split: consumer converts jobs→table AND filters un-completed samples (warning); itis-sumo rejects insufficient/invalid data (raises; consumer catches + surfaces). Dakota sufficiency rulemax(5, n_variables + 1)stays in itis-sumo - preprocessing ! auto-defaulted (novice/normal users pass nothing); advanced override allowed but expressed in DOMAIN vocabulary only (
scale,direction); ⊥ transform vocabulary in any public signature (sign_switch,normalization="zscore",mapped_name,_hat/_std_hat). Effective config → INSPECTABLE in results, ⊥ SETTABLE in transform terms. MOGAdirectiondefaults"minimize"(optimization-field convention) domain⊥ conflated w/distribution(see VOCAB): domain auto-inferable from samples (observed bounds + detected scale) → Sobol box + MOGA search space; distribution ! stated by the modeller → UQ propagation + correlation. Retires themean ± 3σfallback in_bounds_from_distributions(a conflation artifact — its own docstring admits the two uses differ). LANDED early via T52xx (Sobol box =domains, ⊥ mean±3σ ∧ ⊥ distributions-in-Sobol; the artifact itself was never ported — it lived in mmux flaskapi); handle transformation (T27fr) stays ⊥ this invariant- architecture ! handle-primary is the TARGET (
fit(samples, config)→model.along_axes/grid/sobol/propagate_uq/...,model.save() -> sumo_model_id); the mmux/vite port ships the current ONE-SHOT shape (no frontend change). One-shot entrypoints ! implemented internally as fit-then-query from day one → handle extraction later = re-export, ⊥ second port - run artifacts ! ephemeral on success (⊥ unbounded growth in a web service), PRESERVED on failure w/
run_dir+ captured stderr tail attached to the raised error (B1/B2/Unknown error 250are all Dakota-opacity bugs — the run dir is the only tractable evidence);workspace=opt-in for deliberate debugging. E1 model store (ITIS_SUMO_MODELS_DIR) = separate, always-persistent concern - reproducibility ! fixed default seed
42, overridable; effective seed returned in the result; ⊥ require caller to supply one - errors ! stable taxonomy
SumoError→SumoInputError(→400) /SumoResultError(→422, predictions missing) /SumoEngineError(→500, carries run_dir + stderr tail); ⊥ consumer classifying by string/regex match (retires the MOGAre.searchbranch) - results ! typed dataclasses in ORIGINAL units + ORIGINAL names, JSON-serializable via
dataclasses.asdict()(same convention asDatasetDiagnostics); ⊥ magic suffix keys in the public shape ?should distribution get an auto-generated default the way domain does? User: "probably a good idea but should be explicitly discussed, not a given" → POST-PORT decision (T28gs), ⊥ guess?get_sumo_cv_accuracy_metricsis the ONE workflow bypassing DataPreprocessor entirely (raw var names +process_input_file) — normalize it (changes numbers ⇒ compat decision) or preserve + document??public name/shape of the tabular dataclass vs rawpd.DataFrame
§I¶
- wheel:
dakota.environment.study,study.execute()(⊖dakota.surrogatesuntil wheel bump, R4) - CLI:
itis-sumo validate - python (consumer-facing) →
itis_sumo.api= THE module flaskapi/mmux_vite imports; nothing else. Tabular in, typed results out, taxonomy errors (T22ax) - python (in-package/headless) →
itis_sumo.{core,config,data,sampling,evaluate,preprocess,utils}public funcs;corenow re-exports the E1 model store,evaluatenow re-exportsexport_sumo_model/import_sumo_model(were reachable only viafuns_evaluate, breaking V16qf) - python (forthcoming) →
analyze_datasetnarrow diagnostics entrypoint (T18ry) — its output IS the override payload for the preprocessing defaults AND the auto-domainsource, ⊥ a side feature - artifacts: run_dir
{dakota_stdout.txt,dakota_stderr.txt,*.dat,*.sps/.alg}; models dir{id}.metadata.json(E1)
§V¶
V1pm: DakotaObject.run ! execute wheel in ProcessPoolExecutor; ⊥ subprocess dakota bin
V2qw: run dirs ! explicit paths; process-cwd mutation confined to Dakota worker (os.chdir guard)
V3er: create_manual_uq_samples ! draw ∀ dist via seeded np.random.Generator (random_state=); ⊥ scipy global state
V4ty: core modules ! zero flask/osparc imports (test-enforced)
V5ui: NIDR config ! pure string composers; seam for JSON/pydantic layer later
V6op: sanitize_varnames ! preserve literal - in var names
V7as: data processing ! heal-or-drop malformed rows w/ trace; ⊥ silent wipe
V8df: {output}_std_hat availability ! from surrogate results post-evaluate_sumo, ⊥ raw job outputs
V9gh: Sobol ! scipy-based design + saltelli_2010-form estimator (Saltelli A/B/AB/C + exact joint-pair 2nd-order, V48tr), validated vs Ishigami analytical
V10jk: E1 export ! {id}.metadata.json sidecar w/ verbatim conf block + ordered input descriptors + output descriptor + format + a copy of the real training-data file ({id}.processed_training.dat, kept for reference/debuggability per user request — NOT technically required, archive alone is sufficient for reload, R9)
V11lz: E1 import ! conf block identical to export block bar export↔import swap; import_build_points_file satisfied by staging back the stored real training-data copy (V10); if that copy is missing (legacy model / deleted out-of-band) ⇒ loud warning log + fall back to a header-only placeholder synthesized from the sidecar descriptors (R9 proves this is safe — Dakota never reads row values back), avoiding a crash
V12xc: E1 keying ! server sumo_model_id uuid; ⊥ user-supplied path keys
V13vb: E1 models dir ! single env-overridable source (⊥ branch's dual-strategy split)
V14nm: ⊥ standalone surfpack dep; future standalone-eval → bump itis-dakota (dakota.surrogates)
V15zx (proposed, T17bq): add_surrogate_model training-file header layout ! explicit has_eval_id_column: bool param supplied by caller (whoever wrote/staged the file knows its structure); ⊥ infer from "processed" substring in filename — closes B2's root cause (workaround only renamed the staged file to preserve the substring)
V16qf: itis-sumo's cross-repo-facing API (flaskapi/mmux_vite) ! consumed only via §I's documented top-level entrypoints; ⊥ consumer reaches into internal submodules/functions directly — keeps refactors inside itis-sumo from forcing coordinated cross-repo changes; grows w/ each new feature surfaced (starts w/ analyze_dataset, T18ry)
V17ab: ∀ .py file in repo ! ruff check + ruff format --check clean (CI-enforced via prek on changed files; catches unsorted imports, unformatted code, executable-bit-without-shebang at review time — prevents B3-class regressions)
V18rs: ∀ .github/workflows/** change ! at least one validation job executes the affected workflow path; shared CI workflow changes ⊥ leave the validation matrix entirely skipped
V19cn: public API ! use VOCAB nouns (sample/variable=parameter/response=QoI); ⊥ "job" in any itis_sumo signature, docstring, or result field
V20dm: consumer-facing entrypoints ! accept plain tabular data; ⊥ FunctionJob/JobVariableSelection/oSPARC status strings inside itis_sumo (test-enforced, sibling of V4ty)
V21pf: preprocessing ! auto-defaulted + overridable in domain vocabulary only; ⊥ transform vocabulary reachable from a public signature; effective config inspectable in the result; ∀ value-producing entry point ! accept scale (default linear — ⊥ caller must pass) ∧ ! apply it (V45ls)
V22rs: results ! typed dataclass, original units + original names, dataclasses.asdict()-serializable; ⊥ _hat/_std_hat suffix keys in the public shape
V23er: ∀ error escaping itis_sumo.api ! be a SumoError subclass; ⊥ raw KeyError/IndexError/ValueError crossing the boundary
V24af: run dir ! discarded on success, PRESERVED on failure w/ its path + stderr tail attached to the raised SumoEngineError
V25sd: ∀ stochastic entrypoint ! default seed=42, overridable, effective seed echoed in the result
V26dd: domain and distribution ! remain distinct config objects; ⊥ derive a box from a distribution's mean ± 3σ ∧ ⊥ feed modeller distributions to the sensitivity sampler; Sobol box ∧ MOGA search space ! come from domains — explicit boxes echoed verbatim, otherwise auto-inferred from observed bounds + detected scale; a column constant in the samples ! stays fixed (⊥ a fabricated box around it); an explicitly frozen factor ! be expressed as a fixed value (domain vocabulary; ⊥ a distribution's constant param; ⊥ a variable both boxed ∧ fixed)
V27fq: one-shot entrypoints ! implemented internally as fit-then-query; ⊥ monolithic procedure that would need rewriting to expose a handle
V28tz: CI ! block a PR into develop/main whose pyproject.toml version lacks target branch's required prerelease suffix (aN develop, bN main) or regresses/duplicates PEP 440 ordering vs existing tags
V29yn: tag-on-merge job (develop/main) ! push only a v<version> tag, ⊥ version-bump commit; skip docs-only diff; existing-tag collision ! hard failure, ⊥ force-overwrite
V30wk: itis-sumo LICENSE + pyproject.toml license/classifiers ! match itis-dakota's (all-rights-reserved) until an explicit public/OSS decision is made; ⊥ an OSI classifier claiming otherwise
V31vp: any CI job that installs a built artifact and runs pytest against it ! checkout source first (tests/ isn't shipped in the wheel) — ⊥ silently "pass" via pytest's 0-collected exit code
V32bb: PyPI publish + GitHub Release jobs (publish/release in publish.yml) ! gated behind explicit workflow_dispatch; ⊥ automatic cascade from a tag push. Feature .devN TestPyPI upload ! local Make target + .env token; ⊥ CI publication
V33tz: version check/tag job ! compare candidate version against highest existing tag via PEP 440 ordering; ⊥ emit version sorting lower than prior release channel/version
V34yn: merge-time tag job ! re-check live origin tags; existing v<version> collision → hard failure; ⊥ force-overwrite
V42bv: auto-bump-alpha-version workflow ! push its bump commit only to the PR's own head branch, ⊥ develop/main directly; ! no-op (⊥ commit) when the current version isn't already an aN prerelease or when bumping the alpha counter alone still can't clear an existing tag — those cases stay a human decision, caught by version-check.yml's existing gate (V28tz)
V43zt: pinned itis-dakota version ! re-verified against a FRESH uv lock/uv sync (not just installs served from an already-resolved uv.lock) before relying on it staying installable — an upstream PyPI deletion of a pinned release is invisible until something invalidates the lock (B14zt)
V35wk: workflow_dispatch publish ! validate input tag matches v[0-9]*.[0-9]*.[0-9]* and built artifact version equals tag version before real PyPI upload
V36vp: itis-sumo LICENSE ! own IT'IS copyright/author notice; ⊥ copy itis-dakota attribution or Dakota-source LGPL paragraph
V37bb: .env ! gitignored before token-based publish target lands; token never committed
V38cc: publish-testpypi-dev ! clean worktree → compute/write next .devN → build/check/upload via .env; ⊥ commit/tag/CI publication
V39qf: dev version selection ! max(existing git-tag .devN, published TestPyPI .devN) + 1; uv upload auth ! username __token__ + password token; dev target ! restore original pyproject.toml on exit; ⊥ reuse published version, pass username + token together, or leave .devN in project metadata
V40gz: commit-time hooks (prek tool, run via uvx prek run --all-files; mirrors ../mmux_vite's .pre-commit-config.yaml) ! run ruff check --fix + ruff format + ty check on ∀ commit, ⊥ ruff-only; CI's prek job (V17ab) stays a backstop for anyone who skipped/bypassed the local hook, ⊥ the sole enforcement point
V41hp: ty check ! zero diagnostics repo-wide (blocking in both the local prek hook and CI); version-gated stdlib/typing symbols (e.g. datetime.UTC, typing.Self, tomllib — all 3.11+-only, caught mid-stack once actually run, T42jn) ! resolved against requires-python's floor, not the CI runner's own interpreter — this is strictly cheaper than the 3.12-3.14 test matrix (V18rs) for that bug class, though the matrix stays as the ground-truth runtime check
V19nd: ∀ Dependabot ecosystem entry in .github/dependabot.yml → weekly Monday 03:00 Europe/Zurich schedule ∧ exactly one wildcard dependency group; ⊥ unspecified default schedule or one PR per dependency
V20qx: ∀ ignored Dependabot dependency → ignore reason names active compatibility constraint ∧ references tracked resolution task; itis-dakota stays ignored — engine bumps are a wheel-matrix decision (py floor, platform coverage, R5 shim verification ⊥ §C), the interface-cache resolution path is "shim + verify on real wheel" (B16mo), not an upstream fix to wait for; re-point the ignore reason at the currently active engine task (T55kd)
V44ls: ∀ scale-consuming itis_sumo.api workflow (surrogate fit ∧ UQ/Sobol input sampling ∧ MOGA search domain ∧ CV accuracy metric) → a scale="log" override ! be honoured in that workflow's own computation (log-space surrogate + log-space sampling/search where a distribution or domain is involved, positivity-guarded); ⊥ a scale override accepted but silently ignored outside the surrogate fit
V45ls: ∀ value-producing itis_sumo.api entry point → scale ! be threaded into the computation that yields those values ∧ observable in the result; ⊥ a value the api produced while ignoring the caller's scale; enforced: structural (internal value-producers scale_distribution/resolve_log_scale take scale/flag as a required arg — unwired code breaks at call w/ TypeError, never silent drift) ∧ behavioural (linear↔log flip-test over ∀ entry points (10 table-mode workflows + evaluate_correlations); rank-correlation exempt — monotone-invariant, asserted unchanged)
V46rn: log scale ! compose w/ ∀ uncertainty distribution it is defined on: uniform → log-uniform draw ∧ normal → μ/σ parameterize the ln-space distribution (raw draw = exp(N(μ,σ)), lognormal in original units, positive by construction; the surrogate sees exactly N(μ,σ) in its training space — same "distribution describes what the model sees" contract as log-uniform); ⊥ a sampler path rejecting log⊗normal or drawing it linear; constant ⊥ composes w/ log (rejection stays)
V47st: scale/spec surface input guards ! fail loud at the boundary, never mid-engine: preprocessing override maps ! be exact-cover at ∀ entry points (⊥ unknown-column override silently ignored — session path's rule extends to compute_correlations/generate_lhs_samples/generate_grid_samples); required scale maps ! be indexed by required key (⊥ per-variable .get(..., "linear") silent default); log-uniform bounds ! validated present ∧ ordered at the api boundary (→ SumoInputError, not _run_engine's SumoEngineError); ∀ bounded spec (DomainSpec/DistributionSpec) ! reject minimum ≥ maximum at construction
V48tr: Sobol' 2nd-order pairs ! come from the exact joint-pair estimator S_ij = Var(E[Y|X_i,X_j])/V − S_i − S_j (independent C stream ∧ U^ij/V^ij mixed designs, cost n·(2+d+d(d−1)), EXACT ∀ d); ⊥ the retired B26nc identity that inferred O(d²) pairs from d first/total gaps (underdetermined d≥4: pair sums collapse→0/negative); ∀ estimator quantity ! use pooled-A/B mean removal ∧ pooled variance ∧ centered products → translation-invariant under f→f+c ∧ identical to displayed scipy saltelli_2010 point estimator; order masses M1=ΣᵢSᵢ/M2=Σ_{i<j}S_ij (unordered pair once)/R=1−M1−M2 ⇒ M1+M2+R=1 by construction (⊥ clamp raw estimates; ΣᵢS_Ti ⊥ expected to close); ∀ CIs (first/total/second/masses) ! come from ONE shared-row bootstrap resample recomputing ∀ index+mass per replicate (preserves estimator covariance ∧ closure; ⊥ extra surrogate calls); V̂=0 (all-constant ∨ degenerate surrogate) ⇒ mass fractions undefined ⇒ SobolResult.order_contributions=null (⊥ silent (0,0,0)); ∀ finite index/mass value ! validated (ValueError on non-finite)
V50gm: ∀ worker pool itis-sumo spawns → explicit mp_context, ⊥ the platform default start method (Python 3.14 changed Linux default fork→forkserver — a bare ProcessPoolExecutor child then re-imports fresh ∧ in-process state (test monkeypatches, engine bindings) silently fails to cross, B22wk); the engine runner (dakota_object.future_exec) pins fork where available (POSIX — process isolation for engine containment unchanged, inheritance semantics interpreter-independent), takes spawn EXPLICITLY where fork ⊥ exists (Windows); mock-based runner tests skip iff fork unavailable, ⊥ silently depend on a default
§R¶
R1: export_model/import_model child keywords; formats text_archive(.sps)/binary_archive(.bsps)/algebraic_file(.alg); naming {prefix}.{resp}.{ext} | branch R2
R2: import_model needs conf block identical to export block bar keyword swap; ? whether training VALUES must match — empirical round-trip test | branch R3 — RESOLVED T14qa: values need not match by literal path/identity, only content+structure — a byte-identical copy of the export-time training file at a DIFFERENT path (fresh run_dir, simulated separate session) reproduces export-time predictions exactly (atol 1e-6); real Dakota round-trip, not mocked
R3: .sps archive self-describing vs positional UNVERIFIED ? → sidecar regardless | branch R8
R4: dakota.surrogates ⊖ in wheel 1.5.9; upstream 6.24 adds it — wheel bump over new dep | branch R5-R7
R5: 6.23+ regression — study() ctor → Interface::interface_cache(problem_db) static map (DakotaInterface.hpp:71), populated only via SimulationModel/NestedModel ctor (Interface::get_interface); pure data-fit surrogate confs (import_build_points_file, no interface) never populate → DakotaInterface.cpp:77 .at() throws → IndexError: map::at before execute(). 6.20 ctor used problem_description_db().interface_list() (no cache → no crash). Upstream fix candidate = get-or-create (operator[])
R8: resolves R3's ? — variable-label matching on import happens between the staged points file's header and the Dakota study's OWN declared variables block descriptors (our conf, sourced from the metadata sidecar) — NOT by introspecting the .sps/.bsps archive itself. Verified (dakota/src/SurfpackApproximation.cpp in the itis-dakota fork checkout + live Dakota runs): reordering the points-file header (x2 x1 y1 vs x1 x2 y1) still round-trips correctly — Dakota permutes to match by descriptor, per doc DUPLICATE-import_model ("matched by descriptor... allows order... to change"); supplying wrong (foo bar baz) or incomplete (x1 y1, dropping x2) descriptors fails loudly at Dakota's own import_build_points check ("not a permutation of expected variable labels") rather than silently mis-mapping columns. Confirms the archive is not introspectable from outside Dakota — the metadata sidecar's ordered descriptors remain the sole source of truth for what to declare (V10's sidecar design was necessary, not just defensive)
R9: import_build_points_file's row VALUES are never read back into the reloaded surrogate — SurfpackApproximation::import_model() (dakota/src/SurfpackApproximation.cpp:837-856) does spModel.reset(SurfpackInterface::LoadModel(filename)) straight from the archive; approxData/the points file's rows are never touched by that path. Empirically confirmed: importing with a header-only placeholder (0 data rows), 1 row of out-of-range dummy values (999.0 × N), and 2 rows of zeros ALL reproduce export-time predictions exactly (atol 1e-6) — only the header's descriptor SET matters (R8), not row count or values. → real training data is persisted by default anyway, for reference/debuggability (user preference, not a Dakota requirement); stage_model_for_import stages that stored copy back, falling back to a synthesized header-only placeholder + loud warning only if it's missing, e.g. a model exported before this persistence was added (T17bc)
R10: format support, per Dakota docs (dakota/docs/keywords/DUPLICATE-export_model, DUPLICATE-import_model) + source (SurfpackApproximation::export_model, .cpp:467-516) — only text_archive(.sps)/binary_archive(.bsps) call SurfpackInterface::Save() and are re-loadable via import_model; algebraic_file(.alg)/algebraic_console just dump spModel->asString() (one-way; docs state explicitly "not compatible with Dakota or the surfpack executable") — NOT valid for round-trip, Dakota's NIDR parser rejects them as import_model children outright (fails loud, not silent). Both text_archive and binary_archive verified round-trip exactly (atol 1e-6); no documented/observed precision difference — recommend text_archive (current default, human-inspectable/debuggable) with binary_archive as an opt-in for storage-sensitive cases
§T¶
id|status|task|cites
T1pw|✓|scaffold pyproject.toml ([project], uv, py.typed, pip-compile pins), src/ layout, CLI entry|§C
T2cf|✓|port core/dakota_object.py + core/wiofiles.py|V1,V2
T3qd|✓|port config/ NIDR composers (incl. existing sumo_export/import params)|V5
T4gs|✓|port data/ + sampling/ (lhs, manual-uq, axis/grid, pareto, correlation)|V3,V6,V7
T5as|✓|port evaluate/ (sumo eval, CV+metrics/ttest/convergence, MOGA, Sobol)|V8,V9
T6hy|✓|port preprocess/ DataPreprocessor + integration; move pydantic models in; de-web|V4
T7nv|✓|recycle utils: validate_dakota_installation, get_dakota_version, validate CLI, config guard|§C
T8bx|✓|prune dead paths in ported modules|§C
T9kz|✓|port pure-core tests (test_dakota_, cv-stats, preprocessor, sobol, correlation) + headless smoke|V4
T10le|✓|docs README + MkDocs + headless notebook — README ✓, MkDocs site ✓ (mkdocs.yml, algorithm pages + live V&V report w/ real pytest results, served via mkdocs serve), headless notebook explicitly deferred past alpha|§G
T11rt|✓|verify: standalone pytest green (⊖ flask), clean-venv install, itis-sumo validate, headless surrogate→CV→Sobol|§C
T12ze|✓|E1: core/sumo_model_store.py (models dir single env-overridable source ITIS_SUMO_MODELS_DIR, uuid keying, metadata sidecar) + evaluate/funs_evaluate.py::export_sumo_model/import_sumo_model public API|V10,V12,V13
T13uv|✓|E1: capture verbatim conf block for sidecar (close branch placeholder gap)|V10
T14qa|✓|E1: empirical export→import round-trip test (real, unmocked Dakota run — tests/test_sumo_model_store.py); resolves R2 ?|R2,V11
T15mn|~|wire mmux/vite flaskapi to itis_sumo.api (export/import + all workflows): branch feat/consume-itis-sumo-api (worktree, pushed to origin, no PR yet) already replaces flaskapi's in-tree dakota/+data_preprocessor/ w/ itis_sumo.api across all 7 workflows + drops the direct itis-dakota dep from flaskapi; pinned to itis-sumo 0.1.0.dev2 (TestPyPI); flaskapi suite verified 522 passed/3 skipped 2026-08-19 — remaining: open the PR, retarget the pin at a tagged aN/bN release instead of a .devN build (see parity note added to ../mmux_vite/flaskapi/SPEC.md §C)|§I,R1-R4,T40qz
T16mo|✓|stepwise engine modernization: rung 1 1.5.9→1.5.11 ✓ (Dakota 6.20); rung 2 1.5.11→6.24.7 ✓ (Dakota 6.24 engine bump). Took 6.24.7 — the latest 6.24.x still shipping cp311 wheels — to keep Python 3.11 parity with mmux/vite; 6.24.8+ dropped cp311 (would force a 3.12 migration, deferred). The 6.23+ interface_cache regression (R5) was NOT fixed upstream in 6.24.7, so itis-sumo shims it via add_r5_interface_cache_workaround() + truth_model_pointer='R5_WA_MODEL' in config/funs_create_dakota_conf.py: a no-op fork interface (analysis_drivers='true') on an otherwise-unused single model, so the study() ctor's cache lookup succeeds; the interface is never executed. JSON input seam (R4) deferred per scope. 318-test suite green|R4,R5
T17bq|✓|add_surrogate_model (config/funs_create_dakota_conf.py) ! replaced filename-substring sniffing w/ explicit has_eval_id_column param (infer-or-override idiom); all 5 create_sumo_*_conffile call sites updated (landed via feat/vv-port-tests-and-docs, a6d694e, ahead of this merge)|V15,B2
T17bc|✓|E1: persist real training data on export for reference ({id}.processed_training.dat sidecar copy, user preference over the leaner archive-only design); stage_model_for_import stages it back, falling back to a synthesized header-only placeholder + loud warning log only when that stored copy is missing (legacy model / deleted out-of-band) — fallback verified safe vs Dakota source + fake-points empirical tests (header reorder, wrong/missing descriptors, 0/1/2-row placeholders)|R8,R9,V10,V11
T18ry|.|design+implement analyze_dataset(df, input_cols, output_cols, alpha=0.05, include_detail=False) -> DatasetDiagnostics narrow entrypoint (scale/distribution auto-selection + outlier surfacing, plain dataclasses, JSON-serializable via dataclasses.asdict()) as the sole flaskapi-facing dataset-diagnostics API, replacing any per-function flaskapi orchestration; BLOCKED — building blocks select_variable_scale/auto_select_distributions (+ a new raw-value outlier detector, reusing _tukey_outlier_mask's IQR technique) currently exist only on confidential incubator branch feat/nih-in-silico-example, not develop — needs individual promotion first, same promotion rule as T15mn/E1|§C,V16qf,I
T19kp|.|headless notebook, post-alpha — add runnable notebook counterpart to docs getting-started flow once alpha docs publish is stable|T10le
T20hm|.|CI workflow changes ! activate shared validation jobs and regression-check detector classification|V18rs
T21vk|✓|VOCAB section (above) + docs/reference/glossary.md (nav-registered, strict build green); vocabulary enforced on the api layer by tests/test_api_contract.py::TestPublicSurface. Sweep of the OLDER modules' docstrings still outstanding → folded into T24cm|V19cn
T22ax|✓|itis_sumo/api spine ( errors.py taxonomy, types.py config+results, _session.py fit-then-query + run-dir lifetime, workflows.py public funcs, __init__.py surface): tabular input type, PreprocessingSpec (domain vocabulary), SumoError taxonomy, typed result dataclasses, seed policy, artifact policy|V20dm,V21pf,V22rs,V23er,V24af,V25sd
T23bn|✓|itis_sumo.api: cross_validate() + evaluate_along_axes() — internally fit-then-query; 34 contract tests + 10 real-Dakota end-to-end tests green. Equivalence-vs-flaskapi-glue pinning ⊥ done here (needs the mmux_vite checkout) → moves to the consumer PR|V27fq
T24cm|✓|remove preprocess/models.py::{FunctionJob,JobVariableSelection}; re-express the Dakota sufficiency rule over tabular data; jobs→table adapter moves to flaskapi (this port DELETES itis-sumo code)|V20dm
T25dp|.|itis_sumo.api: remaining 6 workflows (UQ-w-uncertainty incl. the ~120-line erfinv/histogram block, correlation, Sobol, grid, MOGA, cv-accuracy-metrics) + E1 export_model/evaluate_stored_model facade|V22rs,R1-R4
T26eq|.|POST-PORT: extract the fitted-model handle (fit() → methods → save()/load()); carries the fitted preprocessing config ⇒ closes the E1 gap (model store persists archive+metadata+training copy but ⊥ preprocessor config, so a reloaded model cannot inverse-transform to original units)|V27fq,V10jk
T27fr|~|POST-PORT: split domain vs distribution config + consumer migration; absorb the mmux_vite jgo/fullstack-logscale work — backend log-scale now honoured end-to-end (surrogate + along-axes + grid + CV + UQ sampler + CV-metrics + MOGA + Sobol, V44ls); the domain⊥distribution split (V26dd) LANDED via T52xx (domain-box Sobol sampling, domains param, mean±3σ dead); remaining = the mmux_vite frontend consumer migration (Sobol UQ-panel → bounds editor, log-scale controls ∧ persistence ∧ auto-detect)|V26dd,V44ls,T52xx
T28gs|.|POST-PORT ?: decide whether distribution gets an auto-generated default — explicit discussion required, ⊥ silently defaulted|§C ?
T29hw|~|publish.yml tag trigger accepts v-prefixed PEP 440 prereleases ✓; tagging v0.1.0a1 BLOCKED on the one-time PyPI Trusted Publisher config (user action), then clean-venv install + itis-sumo validate + headless smoke|T1pw
T30qa|✓|release/CI workflow refresh: build→TestPyPI automatic on tag push (✓), PyPI+Release gated manual (✓, T35cc); auto-tag-on-branch redesigned to PR-time check + tag-only-at-merge (no bot commit) — see T31xx/T32yy; keep git-cliff release notes, dependency-review + concurrency (✓); skip weekly cron/healthchecks for now|§C,V17rt
T31xx|✓|CI: PR-time check job (feature branch→.devN, target develop→aN, target main→bN) blocking merge if pyproject.toml version missing, regresses, or duplicates PEP 440 order vs existing tags|V28tz,V33tz
T32yy|✓|replace auto-tag.yml's commit-based bump w/ tag-only-at-merge (read already-bumped version from merged commit, re-check live tags, push v<version>, ⊥ commit) — removes need for branch-protection bypass|V29yn,V34yn
T33zz|✓|swap itis-sumo LICENSE + pyproject.toml license/classifiers to match itis-dakota (IT'IS Foundation - All Rights Reserved); drop MIT classifier|V30wk
T34aa|✓|make publish-testpypi-dev ! source TESTPYPI_TOKEN from local .env (gitignored) instead of requiring a pre-exported shell var|§C
T35cc|✓|gate publish/release jobs behind explicit workflow_dispatch; build+verify stay CI-only for release tags; feature .devN uploads move to local Make target|V31vp,V32bb
T36dd|✓|add .env to .gitignore; make publish-testpypi source .env without printing token, then build/check/publish|V37bb
T46ls|x|scale first-class across the WHOLE api surface: generate_lhs_samples ∧ generate_grid_samples ∧ compute_correlations take preprocessing ∧ honour scale; one scale_distribution unit→value helper absorbs the log10-power hand-roll ∧ the duplicate log guards ∧ the _is_log closure; V45ls linear↔log flip-tests over ∀ 10 entry points + 5 gap tests (MOGA log·maximize, log-variable axis x-restoration, log-response UQ spread, Sobol mixed partition, repo-wide ty); rows land in the V&V report Category I|V45ls,V44ls,V21pf,V22rs
T47mt|✓|api.evaluate_correlations: move the #470 MC-through-surrogate correlation workflow (draw → surrogate predict → Pearson+Spearman over the SHARED sample set) out of the flaskapi endpoint into the api — scale-native from birth (engine producer correlate_manual_uq_samples takes input_scales/output_scale as required kwargs); CorrelationResult.seed records the draw; flip matrix extended over ∀ 11 entry points|V45ls,V22rs
T37ef|✓|validate manual publish tag + artifact version before PyPI upload|V35wk
T38dd|✓|make publish-testpypi-dev auto-compute/write .devN, build/check/upload directly to TestPyPI, then restore project version; CI verifies alpha/beta/rc before real PyPI; no dev tag cascade|V38cc,V39qf
T39pk|.|POST-PORT/deepen candidate: itis_sumo.api.workflows repeats the same build-session → translate-config → call → translate-result → wrap-errors skeleton per function; pull the shared shape down into _session.py so each workflows.py function shrinks to signature+one call — behavior-preserving refactor, tests green before/after; natural precursor to T26eq's handle extraction|V16qf,V27fq,T26eq
T40qz|.|cross-repo version-parity: mmux_vite develop ! pin itis-sumo aN, mmux_vite main ! pin itis-sumo bN (mirrors this repo's own channel policy) — noted in ../mmux_vite/flaskapi/SPEC.md §C; the CI check enforcing it lives in mmux_vite (parse pinned itis-sumo version's PEP 440 pre-release tag, fail PR on target-branch mismatch), not this repo — tracked here for visibility since it's the other half of §C's release channel policy|§C,T15mn
T41hp|.|add .pre-commit-config.yaml (ruff+ruff-format via astral-sh/ruff-pre-commit, local ty check hook, pre-commit-hooks file hygiene — mirrors ../mmux_vite) + make prek/make pre-commit targets (uvx prek run --all-files); CI's prek job keeps running the ruff steps directly (⊥ shell out to uvx prek in CI — redundant w/ the per-changed-file ruff invocation it already does)|V40gz
T42jn|.|fix ∀ ~72 pre-existing ty check diagnostics (dict[str,float] narrowing across data/funs_data_processing.py call sites, unresolved-attribute on Optional preprocessor/run_dir/regex-match access, MaskedArray overload in sampling/lhs.py, CaptureToFile.__exit__ override signature in core/wiofiles.py, stale type: ignores, examples/generate_docs_figures.py matplotlib import); flip CI prek job's ty check step from advisory (continue-on-error: true) to blocking once clean|V41hp
T43bv|✓|scripts/bump_alpha_version.py (mirrors dev_version.py's tomllib/regex/--write shape) + .github/workflows/auto-bump-alpha-version.yml: on a PR into develop, auto-bump the pyproject.toml aN counter past every colliding v* tag and push the fix commit to the PR's OWN head branch (⊥ develop/main directly, so V29yn's no-bot-commit constraint doesn't apply); no-op when the current version isn't alpha or a base bump is needed instead — version-check.yml (V28tz) stays the backstop for those and for forked PRs (read-only token there)|V42bv
T44qm|.|POST-T43bv ?: once auto-bump-alpha-version confirmed working end-to-end on a real PR, study consolidating .github/workflows/*.yml (ci.yml, version-check.yml, auto-bump-alpha-version.yml, auto-tag.yml, publish.yml, docs-deploy.yml) — repeated checkout/setup-uv/tag-fetch boilerplate across files; open question whether to merge workflows, factor a composite action, or leave as-is; explicit design discussion required before touching, not a mechanical refactor|T43bv
T45zt|✓|verify ../mmux_vite flaskapi suite (previously 522 passed/3 skipped, T15mn) still passes pinned at itis-sumo 0.1.0a2 + itis-dakota 1.5.11 (T16mo rung 1) — rung 1 is packaging-only/behavior-identical per T16mo, but T15mn's pin predates both the alpha-2 bump and the engine bump, so this closes the loop before retargeting flaskapi's pin off .devN; confirmed via editable overlay (uv run --with-editable), identical 522 passed/3 skipped|T15mn,T16mo
T48kw|x|log ⊗ normal (V46rn): resolve_log_scale ∧ _uq_engine_distributions accept normal (bound checks stay uniform-only; constant rejection stays); create_manual_uq_samples normal branch exp-restores under log_scale (replaces the dead commented-out lognormal branch); Sobol ppf → frozen lognorm(s=σ, scale=e^μ); the 3 rejection tests flip to behaviour tests (draws >0, ln(draws) ~ N(μ,σ), UQ/Sobol/correlation stats move linear↔log); flip matrix asserts a log⊗normal column moves ∀ sampling entry points that take distributions; V&V I4/I9 wording amended|V46rn,V45ls,B19ps
T49dr|x|scale-boundary guards (V47st): compute_correlation_indices indexes input_scales[var] by required key (membership error next to the existing "not found in input samples" check — docstring "REQUIRED, never defaulted" stops overstating); compute_correlations/generate_lhs_samples/generate_grid_samples reject preprocessing overrides for columns not in play (mirrors _session.py exact-cover); _uq_engine_distributions validates log-uniform maximum present ∧ maximum > minimum → SumoInputError not _run_engine's SumoEngineError; MOGA docstring corrected (log objective fits in ln-space, reports* exp-restored original units)|V47st,V45ls,V23er,B19ps
T50vb|x|DomainSpec/DistributionSpec ! reject minimum ≥ maximum at construction (__post_init__, taxonomy-consistent error at first touch); kills the silently-descending inverted log grid axis ∧ turns MOGA's inverted-domain SumoEngineError into an input error; tests incl. inverted log domain|V47st,V23er
T51pq|x|port mmux_vite T31rb (PR#649): exact arbitrary-d 2nd-order Sobol' pair estimator + order masses — _saltelli_abc 3-stream design builder ∧ _saltelli_pair_designs (U/V mixed designs), _sobol_algebra (scipy saltelli_2010 parity pinned by test, translation-invariant), _sobol_joint_bootstrap (shared-row CIs over ∀ index+mass), zero-variance → null masses; replaces the V9gh closed-form identity + the d==1 special case + the runtime scipy.stats.sobol_indices call; public surface gains OrderMasses dataclass ∧ SobolResult.order_contributions; analytic benchmarks additive d=8 / pair-interaction d=5 / Ishigami / pair-quadratic d=10 drive the PRODUCTION helpers with 3·CI-scaled tolerances; B26nc degeneracy regression kept|V48tr,V9gh,V22rs,B20qt
T52xx|x|land V26dd domain⊥distribution split (ahead of the handle transformation, per owner call 2026-09-30 — no defer): evaluate_sobol/SumoSession.sobol take optional domains: Mapping[str, DomainSpec] (unknown names → SumoInputError; boxes auto-inferred from observed bounds + detected scale; columns constant in the samples pinned ∧ echoed in fixed); Saltelli draws become uniform/log-uniform over the box (V44ls) — the distributions parameter, log⊗normal Sobol sampling ∧ the mean±3σ conflation die; engine evaluate_sobol_indices consumes a domain-sampling map ({"minimum","maximum","log_scale"?} | {"value"}); SobolResult: distributions → domains + fixed + effective_config (V21pf); NORMAL flip matrix ⊥ Sobol row (its flip rides DOMAIN boxes, uniform matrix); docs/examples/V&V aligned|V26dd,V44ls,V45ls,V21pf,V47st
T53xx|x|explicit factor pinning for Sobol: evaluate_sobol/SumoSession.sobol accept fixed: Mapping[str, float] (unknown names rejected ∧ boxed∧fixed overlap rejected ∧ finite ∧ log-positive); pins echo in SobolResult.fixed beside auto-inferred constants — the domain-vocabulary freeze a consumer needs to hold a factor without distribution parameters (mmux FE Sobol-panel migration, prelude); test: pinned factor zero indices ∧ surrogate keeps its real spread|V26dd,V47st
T55kd|.|engine itis-dakota 6.24.7→6.24.13 (first win_amd64 wheels, 2026-10-06 → Windows ! supported platform) ∧ py 3.14 enable: requires-python >=3.12,<3.15 (3.10/3.11 retired by the 6.24.8 cp311 drop — cost accepted, owner 2026-10-14; mmux/vite parity re-anchored to 1.5.11, §C py), CI matrix 3.12/3.13/3.14 (+ windows-latest leg), uv.lock re-resolve, docs/README version sweep; B22wk mp_context=fork fix gates the 3.14 leg; verified on REAL wheels before pinning (0.1.0a11 overlay): py3.14.8+6.24.13 analytical 26/26 ∧ unit 428/428 post-fix; py3.13 controls green on BOTH 6.24.13 ∧ 1.5.11 — R5 shim holds, ⊥ upstream fix needed|V50gm,B22wk,T16mo
T56xx|.|first REAL-PyPI release 0.1.0 (name free — prod JSON 404 verified 2026-10-14): cut release/0.1.0 = develop+T55kd ∧ git revert 44093a1 (the single squash commit of the a11 incubator increment — report-façade/diagnostics V49rp/T48np/T49np/T54rp ! ship yet, still incubating on the CONFIDENTIAL NIH branches, owner call 2026-10-14); the revert exists ONLY on this branch: ⊥ ever merge main→develop without an owner call (would carry the revert onto develop); merge origin/main in with -s ours (ancestry only — the 8 main-only commits are the diverged old lineage: test_ci_workflow.py was retired BY DESIGN per B6nr, content ! ours); version 0.1.0 stable; PR→main (version-check: main ! stable ✓, bN scheme retired §C); merge → auto-tag v0.1.0 → publish.yml build+verify (publish job ⊥ tag-push-gated); ONE-TIME manual setup ⊥ automatable: PyPI-side Trusted Publisher for itis-sumo/publish.yml + GitHub pypi environment; then, on merge, auto-tag.yml pushes v0.1.0 AND auto-dispatches publish.yml {registry: pypi} → publish + GitHub Release (git-cliff) ⇒ merging the release PR ! the publish decision itself — the owner gate is the MERGE, ⊥ merge before Trusted-Publisher prereqs + full CI green + owner OK|V20qx,T55kd,B6nr
§B¶
id|date|cause|fix
B10rf|2026-08-19|local TestPyPI publish passed username + token to uv, which rejects that combination; dev counter scanned git tags only, so a previously uploaded .dev1 was selected again|use __token__ + password token and include TestPyPI JSON releases in next-version calculation|V39qf
B1|2026-08-07|itis-dakota 6.23+ Interface::interface_cache() throws IndexError: map::at on interface-less surrogate confs (study() ctor, before execute()) — pure data-fit surrogates never instantiate an interface → static map missing entry (R5)|pin ==1.5.9 (Dakota 6.20, no cache) for flaskapi parity; ladder T16mo; upstream get-or-create fix
B2|2026-08-07|E1 import round-trip: add_surrogate_model (config/funs_create_dakota_conf.py) decided whether the training file has a leading eval_id column purely from whether the substring "processed" appears in the training filename, not an explicit flag — staging the re-import training file as {id}.training.dat (T12ze) desynced Dakota's use_variable_labels column expectation from the actual CSV, surfacing as Cannot reorder variables ... not a permutation of expected variable labels|immediate: sumo_model_store.py::_training_file_name names both the stored and staged file {id}.processed_training.dat, preserving the substring; root-cause: add_surrogate_model heuristic replaced by explicit has_eval_id_column param, T17bq/V15zx (now ✓)
B3|2026-08-18|CI prek job fails on PRs that touch unformatted .py files — ruff I001 (unsorted imports) + EXE002 (executable bit, no shebang) + ruff format violations in funs_evaluate.py, funs_create_dakota_conf.py, test_property_invariants.py, generate_docs_figures.py, test_metamodeling_analytical.py, test_unit_solver.py — pre-commit config dropped but ruff formatting not enforced repo-wide first|fix: ruff check --fix + ruff format all touched files + chmod -x executable bits; preventive: §V17ab invariant — ∀ .py file must pass ruff check + ruff format --check
B4kt|2026-08-18|CI workflow-only changes were absent from detect_changes, so all downstream jobs could be skipped and an invalid astral-sh/setup-uv@v10 reference remained unresolved behind an apparently green PR|classify .github/workflows/** as CI changes, run shared validation matrix, replace nonexistent action tag; V18rs
B6nr|2026-08-18|the workflow regression test hard-coded a Dependabot-managed action version and failed on a valid setup-uv bump; CI already validates the workflow itself|remove tests/test_ci_workflow.py and rely on CI validation
B7pv|2026-08-18|prek passed deleted Python paths from the workflow diff to Ruff, causing E902 after removing a test file|exclude deleted paths with --diff-filter=d before xargs
B8lf|2026-08-18|the develop merge retained executable mode on changed funs_evaluate.py, so prek Ruff raised EXE002 for a Python file without a shebang|clear executable bit; existing V17ab catches changed-file Ruff violations
B9dw|2026-08-18|after rewriting develop history, GitHub push events reported the unreachable pre-rewrite before SHA and detect_changes aborted with bad object|fall back to git diff-tree when the event base commit is unavailable; no code invariant added because this is CI history topology
B10cs|2026-08-18|itis_sumo.api.evaluate_along_axes(at={...}) raised SumoEngineError: 'x1' for a PARTIAL held-value mapping — create_samples_along_axes reads [cut_values[var] for var in input_vars], so an incomplete dict is a KeyError, not a documented default. Never surfaced in flaskapi because its frontend always sends every slider|_map_held_values completes the mapping from the sample means (the same value the no-at path uses) before translating; covered by tests/test_api_workflows.py::TestAlongAxes::test_honours_the_values_the_caller_holds_fixed. ⊥ new §V invariant: V23er already requires taxonomy errors, and the real lesson is that a caller-facing default must be materialised by the API layer rather than assumed by an internal helper
B11xy|2026-08-19|publish.yml's testpypi job ran pytest against the installed wheel w/o a checkout step — tests/ (not shipped in the wheel) was absent, pytest collected 0 items and exited 5 before the job ever reached the TestPyPI publish step (caught via gh run view on the v0.1.0a1 tag push, which failed harmlessly — no upload occurred anywhere)|added actions/checkout before download-artifact (ordered first — checkout's default git clean would otherwise wipe an already-downloaded dist/); V31vp
B12mn|2026-08-19|pyproject.toml requires-python had drifted to >=3.10,<3.14 ("broaden python compatibility matrix") while the pinned engine itis-dakota==1.5.9 ships wheels only for cp310-cp312 (verified via PyPI JSON) — installing on 3.13 would fail dependency resolution, promising support §C never backed|tightened to >=3.10,<3.13 w/ a comment tying the bound to the pinned wheel; re-widen only alongside a T16mo engine bump
B13qf|2026-08-19|3 latent Python-3.10 incompatibilities (datetime.UTC, typing.Self, bare import tomllib — all stdlib/typing 3.11+ additions) shipped undetected because the pre-ci/matrix-and-coverage CI ran a single Python version; surfaced only once the 3.10-3.13 test matrix actually ran each version for the first time|datetime.timezone.utc / sys.version_info-gated typing_extensions.Self fallback / try-tomllib-except-tomli fallback + conditional deps; preventive: V41hp — ty check resolves these against requires-python's floor in one static run, cheaper than a runtime matrix for this bug class
B14zt|2026-08-27|itis-dakota==1.5.9 was deleted from PyPI upstream (release list jumps 1.5.1→1.5.11→6.23.0+, no 1.5.9; that version's PyPI JSON endpoint 404s) — invisible because uv.lock still installs from its cached wheel URLs/hashes, but T43bv's alpha-bump commit changes pyproject.toml's [project] table on every qualifying PR, which invalidates the lock and forces a live re-resolve; that re-resolve then fails outright since the pinned version no longer exists in the index (reproduced locally: identical repo, only version differs, uv@latest sync succeeds unchanged / fails re-resolved)|bumped engine pin to itis-dakota==1.5.11 (T16mo rung 1, already earmarked in a pyproject.toml comment — packaging-only, same Dakota 6.20 engine, adds cp313 wheels); widened requires-python to <3.14; regenerated uv.lock; 318-test local suite + ty/ruff green, cp313 install confirmed; V43zt
B15tg|2026-08-27|the v0.1.0a2 tag (created by auto-tag.yml on the develop merge) exists, but no TestPyPI publish followed — auto-tag's trailing gh workflow run publish.yml did not fire. Root cause: the tag-merged-version step invoked gh from an inline Python script that read GITHUB_TOKEN out of the step environment, but the step's env: block only exported GH_REF_NAME, so gh got no usable token and the dispatch silently did nothing|add GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} to the tag-merged-version step's env: block so the inline gh call inherits a valid token; one-off gh workflow run publish.yml --ref develop --field tag=v0.1.0a2 --field registry=testpypi published a2 manually to recover the missed alpha; V29yn
B16mo|2026-08-28|T16mo rung 2 bumped the engine itis-dakota==1.5.11 (Dakota 6.20) → ==6.24.7 (Dakota 6.24). 6.24.7 still carries the 6.23+ Interface::interface_cache() regression (R5): dakota.environment.study()'s ctor unconditionally calls interface_cache(problem_db) and throws IndexError: map::at ("called with nonexistent study!") for itis-sumo's pure data-fit surrogate confs, which construct no Interface (only import_build_points_file, no interface block) — the cache static map is never populated. All 45 real-Dakota surrogate tests failed on it (training, cross-validation, UQ propagation, MOGA, import/export, evaluation). Upstream fix (get-or-create operator[]) is still pending|shim in itis-sumo, not waiting on upstream: config/funs_create_dakota_conf.py::add_r5_interface_cache_workaround() (appended to every conf via start_dakota_file()) declares an unused single model R5_WA_MODEL wired to a no-op fork interface (analysis_drivers='true') and points each surrogate model at it via truth_model_pointer='R5_WA_MODEL', forcing the Interface to be constructed (populating the cache) during study setup; the dummy model is never referenced by any method/iterator so the interface is never executed. 318-test suite green on 6.24.7. Took 6.24.7 (not 6.24.9) because 6.24.8+ dropped cp311 wheels, which would have forced a Python 3.12 migration of both repos; R5
B8hm|2026-09-11|Dependabot entries used unspecified weekly timing, and itis-dakota was intentionally ignored without a durable policy invariant|V19nd,V20qx
B17sc|2026-09-28|scale="log" on PreprocessingSpec.overrides reached the surrogate fit but was a near-no-op for UQ/MOGA/cv-metrics/Sobol — the logscale port (T27fr) landed the preprocessor transform but not per-workflow log-space sampling/search, so evaluate_uncertainty with a log input returned linear-space statistics (probe: mean 12.057 linear vs 12.058 log). Symptom never seen in flaskapi, whose frontend drove log at the request-payload layer|wire log-space into every scale-consuming path: create_manual_uq_samples log_scale branch (log-uniform, uniform-only + positivity), _uq_engine_distributions flags ∧ validates log vars for UQ + Sobol, evaluate_sobol_indices samples scipy.stats.loguniform, optimize_pareto_front maps a log variable's search domain into ln space ∧ fits log objectives; evaluate_cv_metrics inherits log via cross_validate. Guarded by V44ls ∧ tests across the four workflows|V44ls,T27fr
B18mt|2026-09-29|T25dp's "correlation" port landed only table-mode api.compute_correlations, leaving the #470 MC→surrogate→correlate endpoint workflow (sample distributions ∧ predict ∧ correlate over the SHARED set) inside the flaskapi blueprint — surfaced while re-landing the mmux_vite consumption migration: either compute stays in flaskapi (⊥ §G) or the endpoint silently degrades to table correlation (the superseded branch #537 chose the latter, ignoring distributions/num_samples/seed unnoticed)|api.evaluate_correlations + SumoSession.correlations (log guards via existing _uq_engine_distributions) + engine correlate_manual_uq_samples w/ REQUIRED input_scales/output_scale; flip matrix extended over ∀ 11 entry points|V45ls,V22rs,T47mt
B19ps|2026-09-29|PR#47 review found shipped deliberate semantics wrong: resolve_log_scale + _uq_engine_distributions rejected log⊗normal ("source parity: log-scale sampling is uniform-only; no invented log-normal", from fc93cbe, codified in V&V I4 + 3 tests) — but the log knob must compose with a normal: μ/σ parameterizing ln-space (raw draw = lognormal) is the same "distribution describes what the model sees" contract the log-uniform branch already honors; 4 weak guards surfaced in the same review: input_scales.get(var, "linear") per-variable silent default behind a "REQUIRED, never defaulted" docstring; log-uniform upper bound unchecked at the boundary (missing/inverted maximum → SumoEngineError via _run_engine); compute_correlations/generate_lhs_samples/generate_grid_samples silently ignore preprocessing overrides for unknown columns while the session path rejects them; MOGA docstring claimed the front is reported in log space vs the exp-restored reality|new §V invariants V46rn (log⊗distribution composition) ∧ V47st (boundary input guards); tasks T48kw/T49dr/T50vb|V46rn,V47st,T48kw,T49dr,T50vb
B20qt|2026-09-30|the v1 port shipped mmux_vite's closed-form 2nd-order identity as a "standard approximation for d>3" (V9gh wording) — mmux_vite SPEC §B B26nc proved it UNDERDETERMINED for d≥4 (every pair inferred from only d first/total gaps; pair sums collapse to 0 at d=4 ∧ go negative beyond — typical 4–25-parameter studies produced scientifically invalid interactions), ∧ its raw-product form was not translation-invariant under f→f+c (B28pp: drift ∝ c when mean≫std, e.g. stress in Pa) ∧ differed from the scipy point estimator displayed alongside it|port mmux_vite T31rb (PR#649): exact joint-pair estimator (independent C stream, S_ij=Var(E[Y|X_i,X_j])/V−S_i−S_j, EXACT any d), scipy-saltelli_2010-pinned centered-product algebra, shared-row bootstrap, order masses w/ zero-variance null; new §V invariant V48tr ∧ task T51pq; old file's inlined-duplicate Ishigami math replaced by production-helper benchmarks|V48tr,V9gh,T51pq
B21ru|2026-10-01|SumoSession.grid inverse-mapped the response key but not its COMPANION series: the engine hangs the predicted-std column off the MAPPED response name (y1_std), which passed through get_inverse_mapping() unknown-key-passthrough → GridResult.data carried y1_std for an underscored QoI (mmux's V47pk route test caught it: their FE looks gridData[qoi + "_std"] up, a mapped-name leak camelCases on the wire ∧ the lookup misses → silent plot gaps)|grid loop renames {mapped_response}_* companions to {response}_* (suffix kept) beside the plain response rename; regression test renames the fixture QoI to max_abs_stress ∧ asserts no y1* key ∧ std companions ⊆ {max_abs_stress_std}|V21pf
B22wk|2026-10-14|py3.14 flipped the Linux multiprocessing default start method fork→forkserver: dakota_object.future_exec's bare ProcessPoolExecutor(1) child re-imports modules fresh, so in-process monkeypatches never reach the engine call — 3 test_dakota_object.py tests fail on py3.14 ONLY (same wheel 6.24.13 ∧ control 1.5.11 both green under 3.13 → interpreter, ⊥ engine): real Dakota parse errors surface where mocked diagnostics are asserted (assert 'Some real Dakota parse error before abort' in …dummy conf)|future_exec passes mp_context=multiprocessing.get_context("fork") (V50gm — explicit context, ⊥ platform default; process isolation for engine containment unchanged); the 3 tests are themselves the regression guard — green on the 3.14 matrix leg|V50gm,T55kd
B23rt|2026-10-15|the first windows-latest CI leg failed ALL 16 test modules at collection (TypeError: LoadLibrary() argument 1 must be str, not None): wiofiles' wurlitzer-pattern module-level ctypes.CDLL(None)/PyDLL(None) + stdout/stderr symbol lookups are POSIX/macOS semantics — win32 LoadLibrary rejects None ∧ the UCRT exports the FILEs via __acrt_iob_func, not stdout symbols — the 6.24.13 win wheels existed, but ⊥ anyone had exercised THIS package's import layer on Windows|"win32" branch binds the shared UCRT (ucrtbase.dll): __acrt_iob_func(1/2) for the FILEs + fflush, POSIX path byte-identical; the tests-windows leg (T55kd) is the standing guard — engine-execution capture semantics on win32 stay CI-proven, ⊥ assumed|T55kd