ADR-0068: Field order is presentation, stored outside the locked schema
ACCEPTED
Context
ADR-0015 stores AnnotationQueue.schema as a field-name-keyed jsonb map and
locks it once any item has a review, permitting only per-field required to
change. Postgres jsonb does not preserve key order — it re-sorts keys by length
then bytewise — so the order an author enters fields in has never been the order
reviewers see them in. Issue #4276 asks for author-controlled order, explicitly
including on locked queues.
Decision
Field order is stored as AnnotationQueue.field_order, an ArrayField of field
names held outside schema. FieldDefinition is unchanged, so evaluator
output_schema, pydantic_fields and the LLM judge's output model are
unaffected.
A single resolver, AnnotationQueue.ordered_field_names(), is the only way
order is read. It is tolerant: names absent from schema are dropped and schema
keys absent from field_order are appended, so the two cannot disagree in a way
that loses or duplicates a field. The form is strict: a non-empty submitted
field_order must name exactly the schema's fields.
Re-ordering is exempt from the schema lock. The lock protects what was
measured; order is how it is presented, and Annotation.data is keyed by
field name, so re-sequencing cannot invalidate a stored submission. Because
order lives outside schema, _validate_locked_schema_change never sees it and
the exemption needs no carve-out.
Consequences
- An empty or NULL
field_orderfalls back to schema order, so existing queues render unchanged and no backfill migration was needed. - The column is nullable because
AddFielddrops the DB default after migrating, and the previous release's inserts omit the column during a rolling deploy. - Evaluator
output_schemakeeps its jsonb-sorted order. Giving it author control would mean putting a presentation concept into the shared model that defines what is measured, and it has no field-builder UI to drive it. - The items-table summary column's "first three fields" now means the author's first three; previously it was whichever three Postgres sorted first.
- A queue acquires an explicit order only when someone next saves the form.
Alternatives considered
order: intonBaseFieldDefinition— rejected.human_annotationsborrowsFieldDefinitionfromapps.evaluations; adding order there is the borrower mutating the lender's model for a need the lender does not have.schemaas an ordered list of named definitions — rejected. jsonb arrays do preserve order, but the change breaks a stored contract read by aggregation, exports, score writers, concordance and tag rules, and requires migrating every existing row.- A separate compact re-order view — rejected on UI grounds; controls on the existing field cards are sufficient at typical rubric sizes.