Skip to content

Editing an existing model input fails: the junction upsert names a column Hasura does not expose #91

Closed

Description

@mosoriob

Question

Editing any existing model input fails at save. This blocks the §8 acceptance criterion for the edit case. Found on #76, walking the signed-in form against TACC's real catalog.

Adding a new input works — variable, standard variable and unit all land in one submission. Editing an existing one does not.

Fault 1 — the save hard-fails (blocker)

The form shows:

expected one of the values ['_PLACEHOLDER'] for type
'modelcatalog_configuration_input_update_column', but found 'is_optional'

InsertConfigurationInputJunction (ui-react/src/graphql/mutations/model-catalog.graphql:406)
upserts the junction row with on_conflict.update_columns: [is_optional]. This repo's own
metadata gives role user only insert / select / delete on
modelcatalog_configuration_input — no update_permissions
(graphql_engine/metadata/tables.yaml:3088). With no updatable column, Hasura generates the
update-column enum with _PLACEHOLDER alone, so the document fails validation.

Not TACC-specific. It comes from the metadata in this repo, so it reproduces on every MINT
deployment, ISI and the dev cluster included.

The sibling mutation already does it right: InsertConfigurationOutputJunction uses
update_columns: [], which is valid with no update permission. The input mutation is the only
one in the file that names a column.

ConfigurationForm.tsx:283 calls insertInputJunction for every row in toUpdate, whether
or not is_optional changed. diffInputRows puts a row in toUpdate on any change, so
editing a label, a format, a description — or the standard variable and unit, which is the SOW
workflow — is enough to fail.

It also half-applies. updateDatasetSpec and updateVarPresentation commit before the
junction upsert throws. I changed one input's format to csv; the save reported an error and
has_format = "csv" was already in the database.

Two candidate fixes:

  • update_columns: [] plus a delete-then-insert when is_optional changed — the pattern the
    output path already uses; client-only, no metadata change; or
  • add update_permissions for is_optional to graphql_engine — needs a metadata apply on
    every deployment, TACC included.

Fault 2 — a successful save renders stale (minor)

After the successful add, the detail view showed INPUTS (2). The new input was absent until a
manual reload. It was in the database the whole time.

No mutation declares refetchQueries or writes to the cache, and onSaved only calls
setIsEditing(false) (ModelsBrowsePage.tsx:175). The detail view then renders the
GetConfiguration cache entry from page load. A GetConfiguration refetch did go out mid-save
and returned the pre-insert state, so the stale entry is what sticks.

A user configures an input, saves, and it looks as though nothing happened.

Verification

In a real Chrome against https://graphql.mint.tacc.utexas.edu/v1/graphql, signed in as
mosorio@portals via Tapis client mint-localhost-3000. Test rows were written to TACC's
catalog and removed again; the catalog is back to its prior state.

Activity

  1. added
    wayfinder:taskWayfinder ticket: manual work that unblocks a decision
    on Aug 8, 2026
  2. self-assigned this
    on Aug 8, 2026
  3. mosoriob commented on Aug 9, 2026

    @mosoriob
    ContributorAuthor

    Both faults fixed and verified live against TACC. Code in PR #93.

    Fault 1 — the blocker

    Confirmed under a real Tapis token, mosorio@portals, and it wrote nothing. Validation runs
    before execution, so sending each document with a bogus id settles it:

    document result
    old, on_conflict.update_columns: [is_optional] validation-failed — the exact _PLACEHOLDER error
    new, delete_by_pk + insert_one constraint-violation (foreign key, from my fake id)

    The new document passes validation and reaches execution. That is the whole claim, measured
    rather than argued — and it is a genuine red/green against the user-role schema.

    The fix is client-only. SetConfigurationInputOptional does delete_..._by_pk +
    insert_..._one in one document, so both root fields run in a single Hasura transaction and
    the row is never left missing. No metadata change, so nothing to apply to TACC's Hasura. It is
    now called only when is_optional actually changed — the old code fired it for every row in
    toUpdate, which is why editing a label was enough to fail.

    Why nothing caught this. src/graphql/generated/schema.graphql is the admin schema, so
    codegen and every offline test validate against a schema where is_optional is updatable. The
    app runs as user, whose schema is narrower. A green build and 760 green tests could not see it.

    Fault 2 — the stale render

    Fixed. The form refetches GetConfiguration before onSaved; ConfigurationDetail reads the
    same cache entry. A failed refresh is swallowed — it must not report a successful save as failed.

    End-to-end walkthrough

    On localhost:3000 against TACC's live catalog, on configuration simpleModelAnnotated. I added
    my own throwaway input rather than touch the client's existing input_file.

    1. Add wf91-probe → saved. Appeared without a manual reload, heading went to Inputs (2). Fault 2 fixed.
    2. Edit its label → saved, no error. Operations sent: UpdateConfiguration, UpdateDatasetSpecification, GetConfiguration — no junction mutation, because is_optional did not change. This is the save that used to hard-fail.
    3. Toggle Optional → SetConfigurationInputOptional ran with no errors; the row read Optional immediately, and is_optional: true was confirmed in the database.
    4. Cleanup — junction row and dataset specification deleted. simpleModelAnnotated is back to one input (input_file, unknown, Required) and three parameters: its exact prior state.

    Not fixed here

    The half-apply stays. updateDatasetSpec and updateVarPresentation still commit before
    later steps, so a save that fails midway leaves partial writes. No step in this ticket's path can
    fail that way any more, but the save sequence is still many separate mutations rather than one
    transaction. Worth its own ticket if TACC hits it; not a blocker for the flip.

  4. added a commit that references this issue on Aug 9, 2026
    d9f12f9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

wayfinder:taskWayfinder ticket: manual work that unblocks a decision

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions