Skip to content

Wayfinder: close out the SUBSIDE UI SOW — TACC signs off on ui-react at mint.tacc.utexas.edu #79

Open
30 of 42 issues completed

Description

@mosoriob

Destination

TACC uses ui-react at mint.tacc.utexas.edu in place of the Lit UI, and the client
manager signs off that the SOW's acceptance criterion (§8 — a model input's variable,
standard variable and unit configured in one form submission) is met.

That sign-off closes out the SOW. The map ends there.

Notes

Domain. MINT / SUBSIDE model catalog. See ADR-0002
for the React migration and ADR-0001
for the Hasura backend it sits on. Both have a Traps section — read it before debugging
anything that looks like a permissions or cache bug.

This map carries execution, not just decisions. The remaining work is deploy-and-verify,
so tickets do the thing rather than only decide it.

Contract. The SUBSIDE model-catalog UI SOW with UT Austin / TACC. Its period of
performance has expired (confirmed not to be a blocker) and the engagement is settled
financially, so the only remaining obligation is delivery and sign-off.

Contract number, terms, amounts, billing status and client contacts are deliberately not
recorded here
— this repo is public. They live in the private metalearn wiki. Keep them
out of this map and its tickets.

Standing preferences for this effort

  • Deploy target is TACC's own small k8s cluster, via the MINT Helm chart. Max has kubectl access.
  • ui-react replaces Lit at mint.tacc.utexas.edu (decided; T1 de-risks it).
  • No UAT walkthrough document — TACC gets the URL (decided).
  • Skills: /grilling and /domain-modeling on decision tickets.

State at charting (2026-08-08). Last code commit 2026-07-20; ~3 weeks idle. ui-react is
containerized and published, 21 routes ported, and has run only on Vercel previews and
localhost. OAuth2 has been exercised on previews and works — but previews use a dedicated
OAuth path for preview origins, so the round-trip at mint.tacc.utexas.edu is still unproven.
The app has never been driven against TACC's own Hasura and CKAN.

Decisions so far

  • Confirm SUBSIDE can live without the un-ported Lit screens —
    none are needed; the flip is free. Most are already dead or stubbed in Lit, Analysis and
    Emulators have no nav link, thread Summary turned out to be ported, and thread Visualize was
    never functional in Lit either. Also settled the rollout: no parallel deploy (no users on
    Lit at TACC), Vercel is the test surface, and Lit stays deployed-but-unrouted at the upgrade
    as the only undo, removed after sign-off.

  • Tag helm-charts 9.0.0-beta.3 and bump the submodule pointer off beta.2 —
    the chart is reachable again. 9.0.0-beta.3 tagged upstream on 3d3aca8, submodule bumped
    in #82, merged to develop as 200a0d7.
    Both confirmations held: ui_react.enabled: false by default, and the legacy ui
    component is untouched (values.yaml is +52/-0, no ui* template in the diff, _helpers.tpl
    append-only). Three facts fell out — ui_react needs an OAuth2 client distinct from
    Lit's (one callback URL per client, so reusing Lit's breaks the undo — sharpened
    #73); its image tag is a monorepo SHA
    the bump automation will silently skip, so it is hand-set; and it takes its own host
    rather than a path prefix, making the flip a host reassignment.

  • Verify CKAN from a browser origin against ckan.tacc.utexas.edu — is CORS actually broken? —
    fine in production, broken on Vercel. CKAN echoes Access-Control-Allow-Origin only for
    *.tacc.utexas.edu (not the apex), localhost and 127.0.0.1; every other CORS header is
    sent to everyone, so it looks enabled when it is not. mint.tacc.utexas.edu is allowlisted,
    so production needs nothing from TACC and nothing from us — which killed
    #74 (the nginx proxy only serves the
    origin that already works, and never touches a preview), closed unopened. *.vercel.app got
    no header — since fixed: Max added the two real Vercel origins to CKAN's nginx map as
    anchored regexes and dropped the pre-existing *.github.io wildcard, verified live on every
    action the app calls plus the preflight. So
    #76 runs wholly on the Vercel preview as
    originally written, /datasets/* included, on the branch alias
    monorepo-git-develop-mosoriobs-projects.vercel.app — per-deployment preview URLs are refused
    and cannot be allowlisted, since the hash changes every push. No public-suffix wildcard is left
    in the allowlist, which closes the credentialed-access concern the first fix opened.

  • Commit .env.example and local-setup notes so ui-react runs for someone who is not Max —
    a .env file cannot configure the dev server. That was the finding, and it made the
    ticket's premise wrong. index.html always loads public/env-config.js, so the VITE_*
    fallbacks never run. The real fault was one line: HASURA_ENDPOINT pointed inside the
    cluster, at graphql.mint.local. It now points to TACC's public Hasura. A clone plus
    npm install plus npm run dev now shows real model catalog data, read-only, with no
    cluster access. I verified this live from a localhost:3000 origin. The ticket also added
    .env.example, npm run config:local and a README section, and deleted
    .env.development. It deleted MODEL_CATALOG_API, which no code used. That left
    #75 with a fixed list of 12 keys.
    ENSEMBLE_MANAGER_API must be set, or thread execution stops without an error. The code is
    in PR #83, merged to develop as
    45a6d7e
    .

  • Check whether the OAuth2 redirect URI for mint.tacc.utexas.edu is already registered —
    already registered; nothing to do at the IdP. The premise was wrong: Lit uses
    /oauth2/callback too (${window.location.origin}/oauth2/callback, hardcoded), so both apps
    want the identical redirect URI. The IdP is Tapis, tenant portals — not Keycloak — and
    client mint has callback_url exactly https://mint.tacc.utexas.edu/oauth2/callback.
    Verified live, with negative controls, against GET /v3/oauth2/authorize, which validates
    redirect_uri before login: the match is exact on scheme, host and path (http:// is
    rejected), and no mint-*react* client exists. Tapis really is one-callback-per-client, but
    the Tag helm-charts 9.0.0-beta.3 and bump the submodule pointer off beta.2 #70 inference that ui_react needs its own client does not follow — the two apps want
    the same URI and never serve the origin at once (Lit is unrouted). Decision: reuse mint,
    so there is no IdP change at flip or rollback. The chart's own text argues the opposite and
    is wrong for this rollout; it only hard-fails on an empty client_id, so "mint" renders —
    flagged on #75. Two constraints landed on
    #77: the host must be exactly
    https://mint.tacc.utexas.edu, and Lit must be unrouted, never re-hosted — parked at a
    second hostname it cannot log in, making it a rollback to a broken app. Also rules out a
    staging host for ui-react before the flip.

  • Settle the window.MINT_CONFIG values for the TACC deployment —
    two values, not twelve. The deployed Lit app publishes TACC's real config at
    mint.tacc.utexas.edu/config.js, so no value had to be guessed. Ten of the twelve keys need
    no statement: they derive from the component ingresses or inherit shared chart values TACC
    already overrides (google.maps.key, welcome_message). Only client_id: "mint" and
    data_catalog_api: "https://ckan.tacc.utexas.edu" must be set. Derivation is proven safe
    for Hasura and the Ensemble Manager rather than assumed — the Lit template and the ui_react
    helper share the same ingress.tls idiom, and Lit's live output shows https:// for both, so
    TACC declares TLS and there is no mixed-content trap. CKAN is the exception: ui_react derives
    it from components.data_catalog, which TACC does not use (it uses external_services.ckan.url,
    read by the Lit template only), so the key is omitted and the app falls back to a compiled
    default that is right by coincidence — hence the override. Verified live from the production
    origin: Hasura and the Ensemble Manager both send Access-Control-Allow-Origin: *, the
    Ensemble Manager's /v1 routes exist, and CKAN sets no X-Frame-Options, so the datasets
    iframe will render. Two facts landed on
    #76 — ui-react needs no CKAN API key
    (Lit's DATA_CATALOG_KEY has no counterpart; all reads are anonymous GETs), and
    ENSEMBLE_MANAGER_API is now set, so thread execution is in scope for the smoke test. Three
    chart cleanups landed on #77: the dead
    MODEL_CATALOG_API key, the wrong "must be a distinct client" text, and teaching
    ui-react-config.yaml to read external_services.ckan.url. None block the flip.

  • Ensemble Manager calls send no Authorization header — localStorage key mismatch —
    fixed in PR #87, merged to develop as
    4f7debf
    .
    Both REST calls read the token by string literal from a key nothing writes, and spread the
    header conditionally, so every call went out anonymous with no error. The symptom is now known
    rather than guessed, from the Ensemble Manager source: against a tapis engine, which is
    what TACC runs, submit returns 500 ("Unauthorized" escapes as a plain Error) and logs
    return 401 — both read as server faults, not a missing credential. The probe could not
    settle this because fetchLog looks the execution up before it reads the header, so an
    unknown id 404s either way. Both calls moved into src/lib/ensemble-manager.ts so the header
    is built once; the tests assert the outgoing header, not the storage key, and were checked
    to fail against the old code. The fix exposed a second defect and did not resolve it:
    #88 — ui-react posts to
    /executionEngines/localex, and the only such route is /executionEngines/tapis, so run
    submission 404s before auth matters. Submission is still broken at TACC; the log call is
    fully fixed.

  • ui-react submits runs to /executionEngines/localex, a route that does not exist —
    two faults, not one. The engine name was wrong for TACC, and the route was wrong for every
    engine except Tapis: server.ts mounts /executionsLocal, /executions and
    /executionEngines as three separate routers, and the last registers /tapis only. So a
    config key alone would not have fixed it. The engine is now the config key
    EXECUTION_ENGINE, and executionEnginePath maps each backend to its real route — the same
    map Lit makes in mint-runs.ts:731 — so the key is honest for all three engines. The
    default is localex
    , matching both the chart default for the Ensemble Manager and the Lit
    fallback, so one unset value describes the same deployment in three places. The results step
    needed nothing: MintResults reads the engine off the execution row, not from config, and its
    localex/wings rewrite is dead because MintThread never passes mintConfig. Code is in
    PR #89, merged to develop as 1d1ada9.
    One constraint landed on #77: the chart must
    emit EXECUTION_ENGINE: tapis for TACC, derived the way ui-config.yaml derives Lit's — a
    prerequisite for run submission, not a cleanup. Both halves confirmed live on
    #76:
    TACC's Ensemble Manager publishes
    exactly the three separate mounts predicted here, and its Lit config sets
    REACT_APP_EXECUTION_ENGINE = "tapis".

  • Smoke-test ui-react against TACC's real Hasura and CKAN —
    the §8 acceptance criterion passes, for a new input. Signed in as mosorio@portals, a new
    model input took label + standard variable (soil__porosity) + unit (m3 m-3) in one
    submission against TACC's live catalog. The form is genuinely flattened — one control for
    standard variable and unit, which offers the units already used with the variable chosen. The
    cmdk trap ADR-0002 warns about could not apply: this picker is plain buttons, not cmdk. Verified
    anyway by hit test and a real mouse click. Run on localhost:3000, not the Vercel preview —
    the preview is behind Vercel SSO, and localhost is CKAN-allowlisted with a working OAuth
    client, so it tests strictly more. Editing an existing input fails, and that is
    #91, now blocking
    #77: the junction upsert names a column this
    repo's own metadata never exposes, so it is not TACC-specific, and it half-applies. A second,
    minor fault rides along — a successful save renders stale until a manual reload. Thread
    execution was carved out as #92 rather than
    fired at the client's cluster unasked. /modeling/* renders against real data; note the region
    filter defaults to California, where TACC has nothing — it reads as an empty deployment until
    you switch to Texas. All test rows were removed; TACC's catalog is back to its prior state.

  • Editing an existing model input fails: the junction upsert names a column Hasura does not expose —
    fixed and verified live; the write surface is a graphql_engine question, not a TACC one.
    Client-only, so nothing to apply to TACC's Hasura. SetConfigurationInputOptional replaces the
    upsert with delete_by_pk + insert_one in one document — Hasura runs a mutation's root
    fields in a single transaction, so the row is never left missing — and it now fires only when
    is_optional changed
    , instead of for every row in toUpdate. Proven under a real Tapis token
    with a zero-write probe: validation precedes execution, so the old document returns
    validation-failed (the exact _PLACEHOLDER error) and the new one reaches execution. Why
    nothing caught it:
    generated/schema.graphql is the admin schema, where the column is
    updatable — codegen and all 760 offline tests validate against a schema the app never runs as.
    Treat any role-sensitive write as unverified until it runs under a real token. Fault 2 fixed too:
    the form refetches GetConfiguration before onSaved, so a save no longer renders stale.
    End-to-end on simpleModelAnnotated — add, edit label, toggle Optional — all clean, and the
    catalog was returned to its exact prior state. Code in
    PR #93. One thing deliberately not fixed:
    the save is still many mutations, not one transaction, so a mid-sequence failure still
    half-applies. No step on this path can fail that way now; ticket it if TACC hits it.

  • Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field —
    fixed and verified live: 3 of 40 annotated variables → 40 of 40. Client-only; nothing to
    change at TACC. findDatasets no longer sends a q at all — packagesMatchingVariables keeps
    the packages whose resources carry the variable, as Lit does. The ticket's premise was
    re-measured first and held. Three defects the ticket did not name, each real: resource
    narrowing was mandatory, not cosmetic — a dataset matches because some of its resources carry
    the variable, and one TACC package holds 35 resources, 1 of which does, so binding all of
    them handed the model input files it cannot read; datatype was read off the first resource,
    which MintDatasets then filters the list on; and rows=1000 was a silent cap, now paged.
    Two existing fixtures encoded the bug — they carried no annotation and passed only because
    the old code matched prose. Code in PR #95,
    CI green. Submit one thread run end to end against TACC's Ensemble Manager #92 is unblocked, with one constraint landed on it: ext_bbox drops any package
    with no spatial extent, and 11 of TACC's 33 annotated packages have none — Texas keeps 16 of
    33, the California default keeps 0. Lit behaves the same, so it is not a regression, but a
    region-scoped thread cannot see a third of the annotated catalog — now
    #97
    . The same root cause survives
    on the browse surface as #96, deliberately
    not fixed here.

  • Missing dataset metadata must not mean an invisible dataset (decided 2026-08-09, no ticket
    of its own to close — it scopes #97). The
    four constraints on the Datasets step disagreed about what unknown means: the standard
    variable drops it (right), the datatype offers a "Show N" link, dates keep it, and the
    region dropped it silently — because ext_bbox filters on having a location, not on
    location. Region was also the only constraint on the server. The rule is now two rules:
    unknown extent is shown and badged, as dates already do; known-but-outside is hidden behind the
    counted link the datatype filter already has. Chosen over a single show-everything toggle,
    which cannot say why a row was hidden, and over silently widening the results — the exact
    failure Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field #94 had just demonstrated. Cheap only because Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field #94 made findDatasets read the whole
    catalog, so dropping ext_bbox costs no request. One trap found while scoping it:
    packageSpatialCoverage reads coordinates[0] and handles a bare Polygon only, while TACC also
    holds FeatureCollection ×5, Feature, Point and MultiPolygon — moving the filter
    client-side without a real bbox extractor would drop those 8 without a word. Datasets step: show the datasets the region filter hides, instead of dropping them #97 blocks
    #77
    : TACC must meet a correct Datasets
    step, not an empty one. One thing Datasets step: show the datasets the region filter hides, instead of dropping them #97 does not yet say, seen live on Submit one thread run end to end against TACC's Ensemble Manager #92: Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field #94 made
    findDatasets read the whole catalog, so the region now narrows the Datasets step not at
    all
    — an Alaska package (Bethel Elevation) was offered under a Texas framing. That is the
    opposite failure to the one Datasets step: show the datasets the region filter hides, instead of dropping them #97 was written for, and the same rule has to settle both.

  • Dataset search by variable name searches CKAN free text, not mint_standard_variables —
    fixed and verified live: groundwater 48 → 12, corpus_nlp 0 → 17. Client-only; nothing to
    change at TACC. The measured premise held exactly. Substring-match the annotation, prose
    dropped
    — chosen over keep-prose-and-badge, which leaves the count wrong and cannot fix
    corpus_nlp at all (Solr splits the name on _ before it matches), and over a typeahead on
    Hasura's 668 standard variables, where only a small fraction are carried by any TACC dataset, so
    a user picks a legitimate variable and gets a confident 0. Substring matching only ever offers
    what exists, and it agrees with the Datasets step: show the datasets the region filter hides, instead of dropping them #97 rule: filtering by standard variable correctly drops a
    dataset that carries no annotation. A second, independent fault rode along and hit both modes:
    searchDatasets took CKAN's rows=100 default while TACC holds 215 packages, so the empty term
    listed 100 of 215 and said nothing — now searchAllPackages, which Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field #94 had already taught to
    page. buildSearchQuery lost its variables branch entirely; it only ever built a query Solr
    cannot answer, and this was the second surface it broke. One ticket framing was wrong: this was
    never a regression.
    Lit's own /datasets/search is dead at TACC — it POSTs to
    data_catalog_api + "/find_datasets" (ui/src/screens/datasets/actions.ts:377), the legacy MINT
    Data Catalog route, never routed through DataCatalogAdapter, so data_catalog_type = "CKAN"
    does not redirect it; TACC answers 405 for both modes. So "match Lit" was not available as a
    constraint. Code in PR #100, full suite green
    at 813 tests; the 22 new assertions were checked to fail against the old code, on a path that had
    no test file at all.

  • Thread wizard shows 18 of 175 model configurations: the model tree query accepts only sdm#Model —
    no type belongs out of the model tree; all three queries drop the predicate. Verified live in
    the signed-in app: the Models step now reports 175 where it reported 18, and HAND — TACC's
    largest family, 44 configurations — goes 0 → 44. The ticket's premise was wrong. type
    classifies a model; it does not say whether a row is one. The ETL only migrated
    ?id a sdm:Model and stored the most specific subtype, so every row is a model by construction,
    and the 8 sd#Software rows carry the ontology superclass — MODFLOW ×5, ParFlow, an InSAR
    workflow, a particle filter. So _neq sd#Software, the ticket's own candidate, would have hidden
    real models. An allowlist is worse and this was measured, not argued: model-catalog-api
    keeps one and it is already stale, serving 44 of 55 — #102,
    ruled out of scope below. All three sub-questions answered the same way: GetModelFamilies too
    (registration could not offer any subtype family, so no version could be registered under HAND),
    and RegionModels too (it shares the query, so a different rule would only make them disagree).
    Registration still writes sdm#Model for a new family, which is correct — read and write are
    different questions. Code in PR #101, 802
    tests green, the 9 new assertions checked to fail against the old code; graphql.ts regenerated
    by codegen from the committed admin-schema snapshot rather than hand-edited, which is worth
    remembering — codegen runs offline against generated/schema.graphql, no admin secret needed.
    Submit one thread run end to end against TACC's Ensemble Manager #92 is unblocked. The fix also exposed the next wall in the same step, now
    #103: the Variables step offers 668
    indicators where 147 can find a model.

  • Thread execution blocks the flip; ui-react must run a thread before TACC gets the URL
    (decided 2026-08-09 by Max, no ticket of its own to close — it scopes
    #104). The SOW's §8 criterion is already
    met (#76), so Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 is not required by the
    contract's letter. It is required anyway: Lit runs threads at TACC today, and flipping the host
    to an app that cannot would hand the client a regression, whatever §8 says. Chosen over shipping
    on §8 alone, and over asking the client — asking would invite a "no" to a question we would not
    act on. Consequence: Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 is now the largest item left on the map, and unlike Editing an existing model input fails: the junction upsert names a column Hasura does not expose #91/Thread datasets: ui-react searches CKAN free text instead of the mint_standard_variables field #94/Dataset search by variable name searches CKAN free text, not mint_standard_variables #96/Thread wizard shows 18 of 175 model configurations: the model tree query accepts only sdm#Model #98
    it is a data path to build, not a query to correct. Sequence it before
    #97,
    #99 and
    #103, which are all smaller.

  • Thread wizard dead-ends at Parameters: threadExecutionData is never loaded —
    the pipeline now loads from Hasura, and the ticket's premise was half the fault.
    threadExecutionData was useState(null) that nothing loaded, as written — but
    DatasetsStep persisted nothing either, calling UpdateThreadData with empty data
    and modelIO against a mutation that deletes every binding first. So Continue destroyed
    bindings, and Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104's own measurement that the assignment survived a reload is retracted.
    That mattered: loading the pipeline alone would have changed nothing, because there was
    never a binding to read back. GetThreadExecution + threadExecutionFromGQL are ported
    from Lit's get.graphql and graphql_adapter.ts; Datasets and Parameters now write;
    executions load and the summary polls while runs are in flight. Three defects found in
    the wiring, all where the React port had drifted from Lit:
    resource ids were a djb2 hash
    where Lit writes MD5 of the URL — and since this ticket is what starts writing those rows,
    shipping it would have duplicated every file TACC already had; run_progress is a
    fraction, drawn as a percentage, so a finished run rendered as a 1% sliver; and total_runs
    dropped the input-resource factor, where TACC's own summaries keep it. Verified live three
    ways — anonymously for the whole thread_models subtree and the executions query, under a
    real Tapis token for thread.thread_data and dataslice.dataset (both user-only), and a
    zero-write probe of the parameters mutation that reached constraint-violation rather than
    validation-failed. The running app renders Runs ✓ Complete from TACC where every step
    read Pending before. Code in PR #105,
    852 tests green. generated/schema.graphql is the admin schema — the role trap Editing an existing model input fails: the junction upsert names a column Hasura does not expose #91
    found for writes applies to reads too
    : anonymous sees neither thread_data, nor
    thread.events/permissions, nor the dataset table. Submit one thread run end to end against TACC's Ensemble Manager #92 is unblocked, and carries
    the one thing Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 could not prove: the two write round-trips through the UI.

  • Submit one thread run end to end against TACC's Ensemble Manager —
    a run went from ui-react to TACC's cluster and finished. Model SUBSIDE Allocation Specification, POST /v1/executionEngines/tapis → 202, two real Tapis jobs, one of
    which ran to completion and wrote a full output set. That is the first ui-react run to
    produce output at TACC, and it retires the "unproven path" risk this ticket existed for.
    Of the four checks, two pass and two fail. Submit returns 2xx, and the Runs step polls
    and tracks status live with no 401 — so Ensemble Manager calls send no Authorization header — localStorage key mismatch #85 and ui-react submits runs to /executionEngines/localex, a route that does not exist #88 both hold under a real submission.
    The execution row says execution_engine: localex
    (#109): a hardcoded literal in
    ExecutionCreation, harmless today because the server reads prefs.execution_engine
    instead, wrong for every consumer of the column. And the Results step never shows any
    output
    (#110) — two independent
    causes, which matters because fixing either alone changes nothing: MintResults guards on
    onPublishResults, a prop MintThread never passes, so Fetch results makes no request at
    all
    ; and the route it should call returns 500 wrapping a 403 from Tapis or CKAN. Results step never shows any output: the Fetch results button is not wired, and the publish route returns 500 #110
    blocks Flip mint.tacc.utexas.edu to ui-react #77. The wall-per-fix pattern has finally run out of walls in this half — the
    wizard now walks Framing → Results without a new blocker appearing, and the two failures
    are at the far end, not one step in. Two workarounds confirmed as sound: Models step Continue does nothing once a thread has execution data: delete_thread_model is refused, and the error is swallowed #107 only bites
    a thread that already holds bindings, so a fresh thread clears the Models step, and Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 is
    steered around by leaving the indicator empty. Both run failures were data, not code —
    an .m4a fed to a JSON-reading model, then a CKAN resource holding invalid JSON (a raw
    newline at byte 25). Both were diagnosed from the Ensemble Manager log endpoint, which
    works: read the log before blaming the client.

  • Datasets step: show the datasets the region filter hides, instead of dropping them —
    two faults, opposite in direction, and the ticket named only one. ext_bbox hid the 11 of
    33 annotated packages with no extent, as measured — but MintThread also never passed the
    region to DatasetsStep at all
    , so what did arrive was not narrowed either. One rule had to
    settle both, and does: unknown extent is shown and badged ! no location; known-but-outside is
    hidden behind a counted link. findDatasets now drops nothing for the region — it labels each
    dataset region_match: inside | outside | unknown and the UI decides. Verified live at TACC:
    Texas 29 offered (18 in-region + 11 badged) and 4 behind the link, where ext_bbox served
    16
    ; California — the default framing — 11 badged rows where it showed 0, which retires the
    "reads as an empty deployment" note. ext_bbox is not a server-side version of the same
    test:
    ckanext-spatial indexes bare geometries only, so it also drops a Feature or
    FeatureCollection, and TACC has two of those squarely inside Texas — hence 16 → 18, not
    16 → 16. The extractor replaced two partial readers, not one: the region map's
    calculateBoundingBox missed Feature.geometry and FeatureCollection.features too, so
    lib/geo/bbox.ts now serves both surfaces and the region picker gained the coverage for free.
    GetThread selects region { geometries }, confirmed readable under anonymous. Code in
    PR #113, 883 tests green; the
    Feature/FeatureCollection/Point assertions were checked to fail against both old readers.
    Two traps left standing, neither blocking: a bounding box is not the region — Alaska crosses
    the antimeridian, so its box spans nearly the whole globe and almost nothing reads as outside it
    (Lit is the same; it does not bite Texas) — and the browse surfaces still carry the old fault,
    now #114.

  • Deleting a problem statement, task or thread leaves the row behind and reports success —
    the permission was unsatisfiable by construction, so this is the first fix on the map that
    needs a schema migration.
    The user role may delete one of these rows only while it still
    carries a CREATE provenance event; the provenance FKs were ON DELETE RESTRICT. Delete the
    provenance first and the row loses its own delete permission (0 rows, reported as success);
    delete the row first and Postgres refuses it. Both orders were run against a live database
    and neither works
    , which kills the ticket's own first option and rules out any client-only
    fix. The ticket named one failed root field; all eleven failed — every child on the tree is
    gated through the same provenance, so only the provenance and permission rows were ever really
    deleted, which is exactly the state Submit one thread run end to end against TACC's Ensemble Manager #92's orphan was found in. Six FKs become ON DELETE CASCADE in graphql_engine PR #14 —
    metadata unchanged, structural FKs untouched — and the client deletes bottom-up, never
    touching provenance, in PR #115 with
    assertDeleted to stop a zero-row delete rendering as success. A second defect had to be fixed
    with it: the port dropped Lit's dataslice/thread_data deletes, harmless while the
    mutation was a no-op but a hard FK error once Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 started writing those rows. Verified on the
    migrated dev cluster under Hasura's own compiled predicates, in a rolled-back transaction; the
    Submit one thread run end to end against TACC's Ensemble Manager #92 orphan is cleared. Two things outlive this ticket. Lit's delete mutations are
    byte-identical, so delete is broken at TACC today — which is why Deleting a problem statement, task or thread leaves the row behind and reports success #99 no longer blocks
    #77
    (decided by Max, 2026-08-09, on the
    same rule Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 set: a defect blocks the flip only if moving off Lit is a regression). And the
    chart's Hasura migration job is a post-install hook only, so helm upgrade never applies
    migrations — TACC needs the six-FK change run by hand, after the flip, preferably as a surgical
    run_sql rather than hasura migrate apply, whose state at TACC is unverified.

  • The indicator is a standard variable, and the Variables step does not block the flip
    (decided 2026-08-10 by Max, no ticket of its own to close — it scopes
    #106 and
    #103). Two calls, and the second one moves
    the critical path. Storage: thread.response_variable_id will hold a standard-variable URI.
    The four FKs on thread/task repoint from public.variable to
    modelcatalog_standard_variable, in a graphql_engine migration on the Deleting a problem statement, task or thread leaves the row behind and reports success #99 pattern — FKs change,
    metadata does not, because the relationships are foreign_key_constraint_on and follow the
    constraint. Chosen over writing the label, which covers 129 of the 147 producible output
    variables today and only decays: etl/load.py writes modelcatalog_* tables only, so
    public.variable has had no writer since the DYNAMO migration — treat it as frozen and build
    no list from it. Data cost at TACC is one row; the unmeasurable risk is ISI's, so the
    migration must map by label and null what does not map, never drop rows. Scope: Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 and Variables step offers 668 indicators; only 147 can find a model #103
    both stop blocking #77, on the same rule Deleting a problem statement, task or thread leaves the row behind and reports success #99
    set — Lit's Variables step is dead at TACC too, so moving off Lit loses nothing.
    ui/src/screens/variables/actions.ts:31 hardcodes is_indicator: false on every entry and
    renderResponseVariables filters on exactly that flag, so Lit's select renders zero options;
    live, 1 of 165 threads and 0 of 55 tasks carry a response variable and none carry a
    driving variable. ui-react's step is skippable, so it traps a user who picks but does not stop
    one who does not. Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 now blocks Variables step offers 668 indicators; only 147 can find a model #103, and Flip mint.tacc.utexas.edu to ui-react #77 is down to two blockers,
    #107 and
    #110
    — two now one, Results step never shows any output: the Fetch results button is not wired, and the publish route returns 500 #110; Models step Continue does nothing once a thread has execution data: delete_thread_model is refused, and the error is swallowed #107 is
    fixed, see below. One consequence to file when the
    migration lands: the Ensemble Manager's POST /v1/problemStatements/*/subtasks validates these
    ids against variable, so it will reject every URI — pre-existing, and ui-react does not use that
    route. Two corrections from building it (#106,
    below): metadata does change — the four array relationships on variable are built on the
    same constraints and go inconsistent — and the consequence for the Ensemble Manager is now filed,
    as #137.

  • Apply the modeling provenance cascade migration to TACC's Hasura —
    delete works at TACC, and the migration alone was never enough. The schema change was applied
    on 2026-08-10 (six FKs read confdeltype = c, no drift, one 100-day idle in transaction session
    had to be terminated first — Set idle_in_transaction_session_timeout on TACC's Postgres #119, runbook docs: runbook for the TACC cascade migration #118). Step 4, the round trip under a real Tapis token,
    is now done: a throwaway problem statement created, deleted and gone after a reload, with
    delete_thread and delete_task at affected_rows 1 and delete_problem_statement_by_pk
    returning a row
    — the value Deleting a problem statement, task or thread leaves the row behind and reports success #99 could never get. The successful delete is itself the proof the
    FKs are CASCADE
    , since under the old RESTRICT it raises a foreign-key violation; that is the
    migration verified from the app, not from confdeltype. Confirmed as anonymous afterwards,
    not just in the app — an orphan is invisible to its owner and visible to anonymous, which is how
    Deleting a problem statement, task or thread leaves the row behind and reports success #99 hid. Run at localhost:3000 against TACC's production Hasura, not at the host, and the
    literal test was not available:
    mint.tacc.utexas.edu still serves Lit, whose delete removes the
    CREATE provenance as root field 6 before the row delete in the same transaction, so it fails for
    a reason unrelated to the migration — a false negative. Origin is not part of this round trip (same
    endpoint, same database, same user role); what localhost cannot prove is the deployment, which is
    Flip mint.tacc.utexas.edu to ui-react #77's job, and a re-confirm-after-flip constraint is landed there. Two things outlive this
    ticket.
    The migration alone does not fix delete — the client fix fix(ui-react): delete a problem statement, task or thread bottom-up (#99) #115 is required, and Lit
    stays broken, so a rollback to Lit is a rollback to silent orphaning. And a stale dev server from
    another worktree held port 3000, so two runs exercised day-old code that looked right and produced
    a confident wrong diagnosis; the request body gave it away, not the UI. Start dev servers with
    --strictPort, and read the request body before believing a live verification.
    That accident was
    a clean negative control — the old client against the migrated database reproduced Deleting a problem statement, task or thread leaves the row behind and reports success #99 exactly,
    HTTP 200 with no errors key — and it left two orphan rows at TACC that no client can delete,
    now #120.

  • Models step Continue does nothing once a thread has execution data —
    fixed and verified live; the step now saves by diff, and Lit's answer was the wrong one to copy.
    Both faults held as written: the mutation deleted every thread_model row for the thread against
    four ON DELETE RESTRICT children, and ModelsStep had no catch, so the refusal was silent.
    diffThreadModels keeps a still-selected row and its id, deletes only deselected rows with
    their four children, and inserts only what is new — so an unchanged Continue writes nothing at
    all
    . Keeping the id is the whole point: the children hang off thread_model.id, so no
    delete-and-reinsert can preserve them. Lit clears the wall by deleting every child row for the
    thread on every save, which destroys the Datasets and Parameters work each time — not copied.
    Verified on thread x4gbmq00msmbp9ib under a real Tapis token, reading request bodies: unchanged
    Continue sent zero GraphQL requests and advanced the step; adding a model sent removedIds: []
    with all five deletes at affected_rows 0; removing it sent models: [] and delete_thread_model
    at affected_rows 1; and the kept row's id, data binding and total_runs 2 were untouched. TACC is
    back to its exact prior state. Three things outlive it. insert_<table>(objects: []) is
    accepted under the user role — Thread wizard dead-ends at Parameters: threadExecutionData is never loaded #104 implied it, this measured it. 21 of TACC's 109
    thread_model rows carry no modelcatalog_configuration_id
    , and the step deliberately leaves
    them alone: deleting one would hit the same RESTRICT wall on exactly the legacy threads that predate
    ui-react. And removing a model still discards its runs without warning — Lit does the same for every
    model on every save, so it is not a regression; ticket it if TACC hits it. Code in
    PR #121, 921 tests green, the 4 new assertions
    checked to fail against the old code. Flip mint.tacc.utexas.edu to ui-react #77 is down to one blocker,
    #110.

  • Variables step cannot save any indicator —
    built. The four FKs on thread and task now reference
    modelcatalog_standard_variable; existing values map by label, and a label shared by several
    standard variables is nulled rather than guessed (62 labels are shared, up to 6 ways). Schema in
    graphql_engine#15, client in
    #135. Verified on the dev cluster across all
    four cases, both directions: a URI now saves and the bare legacy name is now the one refused.
    Three corrections ride along. Metadata does change — object relationships follow a
    constraint, array relationships do not, so the four on variable went inconsistent and had to
    be moved by hand; that sharpens the Deleting a problem statement, task or thread leaves the row behind and reports success #99 rule rather than breaking it. The migration would have
    killed the whole thread page, not only the Variables step: GetThread selected
    response_variable { id name } and standard variables have no name. And the stored id is now a
    URI, which four screens printed raw, so all four prefer the label. One trap for any migration of
    this shape: drop the old constraint before rewriting the rows, or the old constraint refuses
    the new ids. Two things outlive it — npm run codegen against the dev cluster produced a diff
    that is exactly this change, adding and removing no type, so the dev cluster is a usable codegen
    source; and the dev cluster is now migrated and ahead of graphql_engine main, so a metadata
    apply from main would break it. TACC is untouched: that is
    #136.

  • Chart never sets EXECUTION_ENGINE for ui_react —
    fixed, and verified as a browser sees it: both UIs now say tapis. The chart derives
    EXECUTION_ENGINE from components.ensemble_manager.config.execution_engine.type, the same value
    ui-config.yaml gives Lit, so the two UIs cannot disagree about the deployment they are in;
    components.ui_react.config.execution_engine overrides it. Chart in
    mint#108, version 9.0.0-beta.6. No client change
    — ui-react submits runs to /executionEngines/localex, a route that does not exist #88 already taught ui-react to honour the value. Checked the way the ticket demands, not by
    helm template:
    helm upgrade on the dev cluster with its own values, then
    curl http://mint.local/env-config.js → "EXECUTION_ENGINE": "tapis", matching
    legacy.mint.local/config.js. Six controls on the render, including that a missing Ensemble
    Manager config omits the key rather than failing and that an empty client_id still fails.
    The "also worth knowing" was real and rode along: DATA_CATALOG_API derived from
    components.data_catalog, which a deployment using external_services.ckan does not enable, so
    the key was omitted and the app fell back to a compiled-in default — correct only by
    coincidence, the same class of gap. It now reads external_services.ckan.url as Lit does. That
    was one of the three chart cleanups Settle the window.__MINT_CONFIG__ values for the TACC deployment #75 landed on #77;
    the other two landed with it, so Flip mint.tacc.utexas.edu to ui-react #77 has no chart cleanups left and its TACC values are down
    to one line, client_id: "mint". Two things outlive it.
    #123 does not bite ui_react — its
    deployment carries a checksum/config pod-template annotation, so a config-map-only change does
    roll the pod, which is how the new file reached the browser; mint-ui and
    mint-ensemble-manager carry none. And the chart had gone three versions without an artifact —
    beta.4 and beta.5 were Chart.yaml bumps never packaged, so the repo's newest installable
    version was still 9.0.0-beta.3. Now released: 9.0.0-beta.6 is packaged, tagged and
    published, verified by rendering from https://mintproject.github.io/mint/ rather than from the
    working tree. Flip mint.tacc.utexas.edu to ui-react #77 needs no checkout deploy — only the monorepo's helm-charts submodule pointer
    bumped to the release commit 7130169, the way Tag helm-charts 9.0.0-beta.3 and bump the submodule pointer off beta.2 #70 did for beta.3.

  • Results step never shows any output —
    a run's outputs now reach the Results step, and there were three causes, not two.
    Lonestar6's maintenance ended 2026-08-11, so the retry this ticket waited on ran on 2026-08-29.
    POST .../subtasks/jx8ibyilmsmg1pvt/outputs returned 200 and wrote a real
    execution_result row — cause 2 is proved by the row, not by the 200. But the step still
    read — No results available —: handlePublishResults refetches GetThreadExecution, the
    pipeline and its counters, while the table renders the execution rows only onFetchRuns
    loads — so the rows the server had just written waited for a manual Reload. That is the
    ticket's own symptom one click later, so it was fixed here, in
    PR #152, merged to develop as
    1ba623b
    , 933 tests green. Verified as a
    first click: the registered row was deleted to restore the exact "before" state, and one
    click rendered the output. Three things outlive it, none blocking Flip mint.tacc.utexas.edu to ui-react #77 — all server-side or
    cosmetic, and the rows live in Hasura so Lit reads the identical ones.
    #153: the registered file is
    tapisjob.out, the job's console log, because getExecutionResultsFromJob probes a fixed list
    of 15 folder names that omits the archive's own data_exports/, where the real
    interview_full_analysis.json sits — and with the right candidate absent, Fuse takes its top
    hit with no score floor, so something is always picked.
    #154: published_runs,
    registered_runs and fetched_run_outputs stay 0, so the step reads Pending beside a
    rendered result, and the client's own comment claiming the server writes them is wrong. And the
    result link is a CKAN /tapis-file/... proxy that answers You must be logged in anonymously
    and serves the file with a CKAN session — measured both ways, not ticketed. The habit worth
    keeping: a 200 from the registration route is not proof it registered
    — it said "Outputs
    registered successfully" while naming the wrong file. Read execution_result, and open the
    resource.

  • Apply the indicator standard-variable FK migration to TACC's Hasura #136 blocks the flip, found while resolving Results step never shows any output: the Fetch results button is not wired, and the publish route returns 500 #110 (wired 2026-08-29). On develop,
    /modeling/thread/:id does not load at TACC at all: Failed to load thread: field 'label' not found in type: 'variable'. Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 taught GetThread to select driving_variable { label } and
    response_variable { label }; TACC's Hasura still resolves both to the legacy variable table,
    which has name. Validation is schema-level, so it fails even for a thread carrying no
    indicator. This is the first defect on the map that takes the whole page down rather than
    one control, and its shape is new: the Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 client change and the graphql_engine migration are
    a matched pair, and develop now carries only half of it against TACC. Every client change
    paired with a migration is now a flip risk until Apply the indicator standard-variable FK migration to TACC's Hasura #136 lands.
    Results step never shows any output: the Fetch results button is not wired, and the publish route returns 500 #110 was finished against a local
    client with those two selections reverted to name — temporary, nothing ships it.

  • How does the MINT Helm chart pass an environment variable to ui_react? —
    It does not, and it cannot be made to from a values file. The chart mounts one ConfigMap
    into ui_react with envFrom, and builds it from ten hardcoded keys
    (charts/mint/templates/ui-react-config.yaml). There is no extraEnv and no range over a
    values map, so an operator cannot add a variable — the chart itself must change. A second
    blocker sits at the other end: ui-react/scripts/generate-env-config.mjs copies only named
    keys through pick(), so it drops an unknown variable even when the pod carries it. A new
    runtime key therefore costs three repositories: ui-react
    (#206), then the chart and dynamo
    (#207, filed on this answer and blocking
    #77). This is
    #150 again, and Chart never sets EXECUTION_ENGINE for ui_react, so a deployed React UI submits runs to the wrong engine #150's own fix is the
    template to copy: EXECUTION_ENGINE reads $cfg.execution_engine under
    components.ui_react.config, with a chart-derived default. Note the chart forwards its two
    optional keys by two different routes — WELCOME_MESSAGE reads top-level
    .Values.welcome_message, shared with the legacy UI. Follow EXECUTION_ENGINE; Remove the legacy ui Helm component from TACC's deployment #81 retires
    the legacy UI. One thing this could not settle: the chart version TACC actually runs.
    Settled the same day, and the "unreadable" finding was wrong. dynamo is cloned locally;
    its org is In-For-Disaster-Analytics, not mintproject, so gh repo view mintproject/dynamo
    fails and an agent searching only the mintproject org calls it unreadable. It is not. TACC
    already pins 9.0.0-beta.10, the latest published chart, so Carry BRANDING from the MINT chart to the pod at TACC #207 needs a new release
    rather than a catch-up bump. Two more facts came with it: ui_react is present in
    values.yaml but enabled: false with no config: key, so Carry BRANDING from the MINT chart to the pod at TACC #207 and
    #81 will edit the same five lines; and the
    checkout sits on an unmerged branch. Two habits worth keeping: a private repo is not
    unreadable until you have looked on disk
    , and a runtime config key is not shipped until you
    have read env-config.js inside the running pod.

  • Do the shipped TACC and UT Austin logos agree with the brand guidelines? —
    Yes, and the provenance is stronger than "compliant": both PNGs are byte-identical to the
    files tacc.utexas.edu serves today.
    The whole strip is a copy of TACC's own
    branding-header component — their live stylesheet sets height:50px, background #222,
    centred, --branding-logo-height:24px, so every value measured in mint-app.ts is TACC's, not
    MINT's invention. No permission is needed: the UT Brand Standards Manual allows wordmark use
    per the guidelines in all digital use without approval, and the
    Outside Entities process does not apply because
    TACC is a UT unit. Two measured near-misses, both cheap and both now on
    #206: the minimum-size rule governs
    shield height, not image height, so 24px renders a 21.7px shield against a 25px floor —
    use 28px; and required clear space is 16.9px against a 15px separator — use 17px. Two things
    that outlive the ticket: the UT file is the Informal signature, which the manual restricts
    to internal use, but TACC ships that same file in its own public header, so the exposure is
    TACC's; and there is no UT SVG — the public vectors are EPS and PDF only, while TACC serves
    its own tacc-formal.svg drawn with fill="currentColor". The habit worth keeping: when a
    brand question looks like a legal one, check what the brand owner's own site actually serves.

  • Carry BRANDING from the MINT chart to the pod at TACC —
    carried, proved in a running pod, and the flip is not the one line Chart never sets EXECUTION_ENGINE for ui_react, so a deployed React UI submits runs to the wrong engine #150 said it was. Both
    halves were already built and merged: the chart forwards BRANDING from
    components.ui_react.config.branding (mint#114, mint#115), published as 9.0.0-beta.11,
    and dynamo sets branding: tacc and moves its pin off beta.10 (dynamo#15, 85149e7).
    What this ticket added is the proof. The dev cluster runs MINT-9.0.0-beta.11 with that same
    values key, and the pod's own
    /usr/share/nginx/html/env-config.js reads "BRANDING": "tacc" — which clears the second
    blocker #204 named, the pick() filter in
    generate-env-config.mjs that drops an unknown key even when the pod carries it. Both logos are
    in the image. The acceptance check could not run at TACC, and that is structural rather than a
    gap:
    it needs a ui_react pod, enabled: false there, and turning it on is
    #77 — so Carry BRANDING from the MINT chart to the pod at TACC #207 blocked Flip mint.tacc.utexas.edu to ui-react #77 while its own
    acceptance needed Flip mint.tacc.utexas.edu to ui-react #77. The circle is broken by closing on the render and landing the in-pod read
    on Flip mint.tacc.utexas.edu to ui-react #77. The render is what made this worth doing. With enabled: true alone, TACC's values
    give AUTH_CLIENT_ID: "mint-local" — the chart default, not TACC's mint — and put ui_react
    on the chart-default host mint.local with no TLS, while Lit keeps
    mint.tacc.utexas.edu and the minttacc secret. So Flip mint.tacc.utexas.edu to ui-react #77's values block is four lines plus
    moving Lit's ingress aside
    , not one. Chart never sets EXECUTION_ENGINE for ui_react, so a deployed React UI submits runs to the wrong engine #150's "down to one line" is now measured and wrong.
    Flip mint.tacc.utexas.edu to ui-react #77 is down to one blocker, #136.
    The habit worth keeping: helm template the client's own values file, not the chart's
    defaults — the gap was in what TACC does not set, not in what the chart emits.
    Confirmed at TACC the same day, and it staged the flip. Max ran helm upgrade on the login
    node (revision 185); it deployed no ui_react pod, as enabled: false predicts, so Carry BRANDING from the MINT chart to the pod at TACC #207's own
    acceptance was unrunnable exactly as argued. What the run did settle is the deploy checkout:
    ~/tacc-values/shared sits on 85149e7, level with dynamo origin/main, with
    MINT-9.0.0-beta.11.tgz vendored and branding: tacc set. So Flip mint.tacc.utexas.edu to ui-react #77 needs no git pull and no
    helm dependency update — only the four-line values edit. One reading trap: helm list prints
    the umbrella chart mint-isi-0.3.0 and never the MINT dependency version; read Chart.lock.

  • Apply the indicator standard-variable FK migration to TACC's Hasura —
    applied 2026-09-12; all four checkpoints pass. The four constraints point at
    modelcatalog_standard_variable, metadata is consistent, thread nzGQkhtRsudfSEHkDGct
    carries TOTAL_WATER_STORAGE, and the Variables step saves an indicator signed in at
    mint.tacc.utexas.edu. This closes Flip mint.tacc.utexas.edu to ui-react #77's last blocker.
    It cost far more than the runbook. Migration 1771200021000_standard_variable_embeddings
    runs CREATE EXTENSION vector, and TACC ran PostgreSQL 10 on postgis/postgis:10-3.2-alpine,
    which carries no pgvector, so every hasura migrate apply failed. TACC now runs
    ghcr.io/mintproject/postgres-pgvector:sha-9d58ec7 — PostgreSQL 16.9, PostGIS 3.5, pgvector
    0.8.6 — restored from a dump, with all 31 migrations applied and 668 standard variables.
    Chart Should helm upgrade apply Hasura migrations and metadata? #117 carries the image and a new
    semantic_search component; mint-semantic-search now runs at TACC for the first time.
    Three facts change Flip mint.tacc.utexas.edu to ui-react #77. The deploy checkout has moved: ~/tacc-values/shared now carries
    chart 9.0.0-beta.13, and this window needed both a git pull and a helm dependency update
    — so the note above, that Flip mint.tacc.utexas.edu to ui-react #77 needs neither, is stale. kubectl scale is refused at TACC
    (patch on deployments.apps but not on deployments/scale, and the bound role mint-role
    does not exist), so use kubectl patch deploy <name> -p '{"spec":{"replicas":N}}'. And
    helm upgrade resets every replica to 1, which restarts services in the middle of a window.
    Two rollback copies stay on disk until a few days of normal use pass:
    ~/tacc-values/tacc-hasura-pg10.dump (md5 0238080b3e7ed462eabaf1f9e4eee163) and
    /mnt/hasura_db.pg10 inside the PVC data-mint-hasura-db-0, 156 MB — the old data directory
    was renamed, not deleted.

  • Flip mint.tacc.utexas.edu to ui-react —
    flipped 2026-09-12, and both things only the real origin could prove now hold. Values in
    dynamo#18: ui_react on with
    client_id: "mint", the mint.tacc.utexas.edu ingress and the minttacc TLS secret, and the
    legacy ui ingress disabled in the same edit. The legacy Deployment and Service stay, so
    Check whether the OAuth2 redirect URI for mint.tacc.utexas.edu is already registered #73's rollback is in place. The OAuth2 round trip is read from the network log, not the
    screen:
    authorize?client_id=mint&redirect_uri=https%3A%2F%2Fmint.tacc.utexas.edu%2Foauth2%2Fcallback
    returns 302, and Tapis validates redirect_uri before it draws the login form, so the 302
    is the match on scheme, host and path; the callback returns 200 from nginx's SPA fallback,
    the app takes the token, and the header reads mosorio. The delete round trip passes as
    anonymous
    — the role Deleting a problem statement, task or thread leaves the row behind and reports success #99 hid from: 39 rows, 40 after the create, 39 after the delete,
    with the child task row gone too, so Apply the modeling provenance cascade migration to TACC's Hasura #116's CASCADE is verified from the app at the real
    origin. The branding chrome renders with both logos served from this host, and the counts are
    TACC's own (55 / 213 / 5,998 / 668). Three things outlive it. The served env-config.js
    is a better check than Carry BRANDING from the MINT chart to the pod at TACC #207's kubectl exec, because it is the file the browser gets — read it
    at the host. A stale token in a browser profile reads as a forced-login regression: the
    first load went straight to Tapis, and an isolated context proved the app does not bounce
    anonymous visitors, who still get the full Explore surface. And the region filter opens on the wrong
    region
    — signed in, the problem statement list showed one item, and that is the first screen
    #80 hands the client, so the handover must
    say "switch to Texas". It defaults to California, where TACC has nothing. Re-measured on
    Hand the client the URL and get sign-off #80 (2026-09-13) and both halves are wrong.
    ListTopRegions orders by name, so the list opens
    on Alaska; and TACC holds 40 problem statements — Texas 26, Alaska 6, Ethiopia 4,
    California 3, Djibouti 1 — counted as anonymous. So the one-item list seen here was the
    signed-in user role's view, not the region's contents. The handover line is unchanged; only
    its reason is. One thing to file: Lit's config.js published
    REACT_APP_DATA_CATALOG_KEY, a CKAN JWT, to every visitor; the flip stops publishing it, so
    rotate the key when #81 retires Lit.
    The route is now clear to the destination — Hand the client the URL and get sign-off #80 is the only step left before sign-off.

  • Variables step offers 668 indicators; only 147 can find a model —
    built; each picker now offers only what it can act on, and two premises were wrong. The
    Indicator picker offers the 147 standard variables a listed configuration produces and the
    Adjustable picker 161, each with a link that widens to the whole catalog. Counts per option
    were rejected: anonymous has no aggregates on these tables, so a count costs the same query and
    still leaves the dead end pickable — the rule Dataset search by variable name searches CKAN free text, not mint_standard_variables #96 already set. The /models facet is the wrong
    list and this was measured, not argued:
    GetOutputVariableOptions offers 148, the extra one
    produced only by pihm_flooding, one of TACC's 89 configurations that hang off no software
    version, so the tree never lists it. The new queries repeat GetModelTreeWithRegions's own leaf
    rule, so the picker and the filter cannot drift. The Adjustable variable filters nothing today
    — driving_variable_id is stored and printed only, and no wizard step reads it — so its list
    comes from the knowledge base's definition of a driver, "input variables or adjustable
    parameters": 154 inputs ∪ 28 adjusted through modelcatalog_parameter_adjusts_variable = 161.
    Neither half alone is right. Question 3's premise was wrong: thread nzGQkhtRsudfSEHkDGct
    holds total_water_storage, which is in the 147 and reaches 1 configuration. A different thread
    dead-ends — test holds 100hr_dead_moisture, which nothing produces — so a stored dead end is
    real and the picker cannot prevent it; the Models step now names the indicator and offers a way
    back, and the picker pins a stored value the scope does not carry. Cost fell rather than rose,
    by reusing neither existing query:
    distinct_on returns one row per presentation, so gzipped
    against TACC the indicator list is 22.2 KB and the driver list 20.8 KB, against 35.8 KB
    for the 668 — and the full catalog now loads only when a user widens. Two faults rode along: the
    filter banner printed the raw standard-variable URI (same class as the four screens Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 fixed),
    and semantic ranking had to be filtered by the scope or it put the dead ends back. Commit
    2206ce0, 995 tests green across 111 files, 12 new assertions each checked to fail against the
    old code. The repo's junction-key-fields guard caught a real cache fault in the first draft —
    both queries selected half a composite key, which Apollo cannot normalize. And npm run codegen
    ran offline against the committed generated/schema.graphql with no admin secret, producing a
    133-line diff that adds nothing but the two operations — the Thread wizard shows 18 of 175 model configurations: the model tree query accepts only sdm#Model #98 note holds.

Not yet specified

Out of scope

  • Porting the un-ported Lit screens — Analysis, Emulators, Messages,
    models-compare/models-calibrate/models-cromo, thread Visualize, and the 3,256-LOC
    model-view. (Thread Summary was on this list in error — it is ported.) Never in the SOW,
    whose scope is the config workflow and the flattened form.
    T1 confirmed SUBSIDE can live without
    them; it does not port them.

  • Retiring ui/ entirely. Downstream of the above, and a separate effort.

  • ISI's mint.isi.edu cutover. Different host, different stakeholders, not this contract.

  • ADR-0001 leftover debt — old model/model_io/model_parameter tables, Fuseki
    references in docker-compose and CI, absent row-level security on modelcatalog_*.

  • Issue Results post-processing: indicator comparison across run ensemble #35 (results post-processing, indicator comparison across a run ensemble) — a
    feature, not migration work.

  • A written UAT walkthrough. Considered and declined; TACC gets the URL.

  • The same model-type fault in model-catalog-api —
    #102: /models returns 44 of TACC's 55
    software from a stale subtype allowlist, and four custom-handlers.ts handlers are cut harder
    still, to 27. Real and measured, surfaced while resolving
    #98. Out of scope because nothing on the path
    to sign-off touches it: ui-react talks to Hasura directly and never calls that API (Commit .env.example and local-setup notes so ui-react runs for someone who is not Max #72 deleted
    the dead MODEL_CATALOG_API key), and the only consumer is the Lit UI, which stays unrouted and
    is being retired. Filed so it is not lost.

  • A ZIP component cannot run on Tapis —
    #108: the Ensemble Manager's Tapis path
    JSON-parses has_component_location, so a model whose component is a localex ZIP (HAND) fails
    with an opaque 500. Surfaced on the first attempt at
    #92 and proved by negative control — the
    same 500, byte for byte, with the input dataset swapped. Out of scope because it is
    pre-existing server behaviour, Lit hits it identically, and nothing on the path to sign-off
    needs a ZIP component: Submit one thread run end to end against TACC's Ensemble Manager #92 finished by choosing a Tapis-backed model instead. It does not
    block Flip mint.tacc.utexas.edu to ui-react #77.

  • The Ensemble Manager rejects standard-variable indicators —
    #137: POST /v1/problemStatements/*/subtasks
    validates response_variables and driving_variables against the legacy variable table, so
    after Variables step cannot save any indicator: it writes a standard-variable URI into a column that references the legacy variable table #106 it refuses every id a client can legally store. Out of scope because ui-react never
    calls that route — it writes threads through Hasura — and the only consumer is the Lit UI, which
    stays unrouted and whose Variables screen renders zero options anyway. The inconsistency predates
    the migration. Filed so it is not lost.

  • Three smaller defects found while resolving Submit one thread run end to end against TACC's Ensemble Manager #92, all filed and none on the route to
    sign-off. #109 — every execution row is
    written execution_engine: localex, including Tapis runs; the server does not read the column,
    so nothing breaks today. #111 — the Runs
    step prints 2 runs (1 input resources × 1 parameters); the count is right and only the printed
    factor is wrong. #112 — every resource with
    an empty URL shares the id d41d8cd98f00b204e9800998ecf8427e, the MD5 of the empty string.
    Every resource with an empty URL shares one id: the MD5 of the empty string #112 is the one to revisit if result registration ever starts writing resource rows.

  • A MINT design system — TACC brand tokens across the whole app. Charted 2026-08-31 and
    cut back to the chrome. ui-react renders the stock shadcn slate palette, and
    tailwind.config.ts:14 wrongly calls those slate values "TACC brand tokens (stubs)".
    Repointing --primary recolours every button, badge and focus ring in the app, and nothing
    on the path to sign-off needs that. Adopting Lit's top nav in place of the sidebar is out for
    the same reason: it is a product decision, not a branding one.
    The chrome half stayed in scope — a branding strip, a footer and a dark header, behind a
    BRANDING runtime key that defaults to none
    (#206). It blocks
    #77
    — built, carried and closed.
    Add the TACC branding strip, the footer and the dark header to ui-react #206 shipped the chrome in ui-react; #207
    carried the key through the chart and dynamo and proved it in a running pod. The flip no longer
    drops the TACC and UT Austin logos that mint.tacc.utexas.edu shows today, which matters because
    #80 hands the client that URL. The rest of
    the design system stays out of scope.

Activity

  1. 13 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    wayfinder:mapWayfinder map: the shared route for an effort

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions