Skip to content

feat: archived_file output binding for composite model runs - #404

Merged
wmobley merged 6 commits into
developfrom
feat/output-file-binding
Nov 7, 2026
Merged

wmobley merged 6 commits into
developfrom
feat/output-file-binding

Conversation

@wmobley

@wmobley wmobley commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Composite model runs (Ensemble Manager -> SVO adapter -> Tapis job) picked the model output file by the catalog output's label. With three GeoTIFFs in the WERC archive that failed MODEL_OUTPUT_AMBIGUOUS on the first OPERA job to finish on ls6 (ue_3a5901b2, 2026-11-06). A second bug on the same path: a configuration's outputs were never ordered by position, so the default "first output" was database row order (run ue_63132569 got "Reference anchor JSON").

This is the output-side mirror of the app-input binding (#390-era, app_input_name). Design: docs/design/2026-11-07-output-file-binding.md (approved rev 2; architect, skeptic and QA reviews recorded under Decisions).

  • Catalog: nullable modelcatalog_configuration_output.archived_file (migration 1771300000038): a file name, an fnmatch glob, or {param:<parameter id>}. Exposed as hasOutput[].archivedFile (junction column on the three resource types, field maps, OpenAPI); user insert/select and anonymous select, like app_input_name.
  • Ensemble Manager: inputs and outputs sorted by position (ties by id) when the Model is built. buildCompositeWorkflowModelTask resolves the binding ({param:…} takes the seed execution's parameter value, else the default; 422 MODEL_OUTPUT_FILE_UNRESOLVED when it cannot) and the composite submit sends model_task.output_file. output_uri unchanged.
  • SVO adapter: /workflows/submit validates model_task.output_file (422 MODEL_OUTPUT_FILE_INVALID: archive-root whitelist, no tapisjob*, no unresolved reference). The model task pages the archive listing (limit=1000, until an empty page, at most 5 pages, truncated recorded), drops directories and tapisjob*, and selects the bound file literally or by glob without falling back to the label: zero matches is the new MODEL_OUTPUT_MISSING (same-extension candidates, re-register hint), several is MODEL_OUTPUT_AMBIGUOUS. Unbound outputs keep the name/extension rule. Manifest records output_file and selected_by.

No format-guessing fallback and no subdirectory bindings (both rejected in review as silent-wrong-file risks).

Rollout

  1. Merge -> dev deploy applies the migration.
  2. register_subside_to_mint.py --only-output-binding (modflow-executables PR) as the end user, dry run first.
  3. Resubmit the WERC Waco scenario from the SUBSIDE UI in dynamo mode; expect opera_disp_s1_cumulative.tif selected by binding.

Until the ui-react follow-up carries the column on re-insert, editing a configuration's outputs in the MINT UI nulls its bindings (re-run step 2).

Tests

  • graphql_engine: pytest tests 39 passed (+1 pre-existing failure, test_hydraulic_head_inference_metadata::test_public_inference_relationships_are_readable_anonymously, fails on develop too)
  • model-catalog-api: npm test 149 passed
  • mint-ensemble-manager: tsc --noEmit clean; jest 197 passed in classes/tapis, classes/graphql, unifiedExecutionService.test.ts; eslint error counts unchanged on touched files
  • svo-adapter-service: pytest 519 passed (+4 pre-existing test_demo_api.py failures, fail on develop too); golden regenerated

🤖 Generated with Claude Code

Nullable modelcatalog_configuration_output.archived_file (migration
1771300000038) names the archived file a declared output is in that
configuration: a file name, an fnmatch glob, or a {param:<parameter id>}
reference. Exposed as hasOutput[].archivedFile through the catalog API
(junction column, field maps, OpenAPI) and readable by the anonymous role
like app_input_name.
/workflows/submit accepts model_task.output_file (422
MODEL_OUTPUT_FILE_INVALID on a value outside the archive-root whitelist,
a tapisjob* name or an unresolved {param:} reference). The model task
pages the job archive listing, drops directories and tapisjob* files, and
selects the bound file literally or by glob without falling back to the
label: zero matches is the new MODEL_OUTPUT_MISSING (with same-extension
candidates and a re-register hint), several is MODEL_OUTPUT_AMBIGUOUS.
Unbound outputs keep the name/extension rule. The result manifest records
output_file and selected_by.
…e bridge; order outputs by position

buildCompositeWorkflowModelTask resolves the catalog output's archived_file
({param:<id>} references take the seed execution's parameter value, else the
parameter default; 422 MODEL_OUTPUT_FILE_UNRESOLVED when that fails the
archive-root rules) and the composite submit sends it as
model_task.output_file. A configuration's inputs and outputs are sorted by
position (ties by id) when the Model is built, so the default output of a
run is the first declared one rather than database row order.
A {param:<id>} reference must be the whole value (the EM rejects a malformed
or embedded one as MODEL_OUTPUT_FILE_UNRESOLVED). The model task pages the
archive listing until an empty page rather than a short one, since Tapis may
cap limit below the requested size; truncated now means five non-empty pages.
MODEL_OUTPUT_MISSING lists same-extension candidates for literal bindings
only. Test harnesses answer skip>0 with an empty page; golden regenerated.
@vercel

vercel Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
monorepo Ready Ready Preview Nov 7, 2026 3:06pm UTC

@wmobley
wmobley merged commit 384ebd4 into develop Nov 7, 2026
43 checks passed

This branch was successfully deployed

1 active deployment
Preview — 12aca7e9 Deployed Nov 7, 2026 by vercel[bot]
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