Skip to content

Immutable is enforced only on the REST update path, and unlike its neighbour ReadOnly its doc comment does not say so #150

Description

@jryannel

Small one, found while deciding where the immutability of an order line lives. The two neighbouring doc comments in schema/field.go disagree about their own scope:

// ReadOnly makes the column unwritable through REST.
func (f *Field) ReadOnly() *Field

// Immutable allows the column to be set at create time only.
func (f *Field) Immutable() *Field

ReadOnly names its boundary. Immutable reads as a property of the column.

It is not one. Grepping the enforcement, it is REST-only and in two places:

  • rest/item.go:357,380 — the update path refuses it
  • codegen/rest.go:123 — it is omitted from the generated …Patch body

sqlb.UpdateRows[T]().SetX(…) from application code writes it, as does a hook, as does an action's write-back. Which is correct — the engine deliberately does not police application writes — but "settable at create time only" is what the sentence says, and the sentence is what someone declaring a financial record reads.

The generated prose inherits the ambiguity and sharpens it, because it drops the qualifier entirely:

  • codegen/tsclient.go:373"Immutable columns are absent: they are settable once, at create."
  • codegen/gocli.go:704"Immutable columns have no flag here: they are settable once at create."

In a client, "settable once, at create" is true and unambiguous — a client has only the REST door. In the schema DSL it is a claim about the column, and the column has two doors.

Why it is worth a line rather than nothing

The repo's own argument against half-closed doors is in docs/concepts/domain-logic.md, and it is the reason I looked:

the problem with it is not that it is more code. It is that the generated door stays open beside it

Immutable is a rule stated on the model, which is the shape that document holds up as the one that closes both doors — and it closes one. That is a fine design; the surprise is that it presents as the other kind. For an immutable financial record the answer turns out to be a trigger regardless, and I would have got there faster from a doc comment that told me Immutable was not going to carry it.

What would fix it

One word, matching its neighbour:

// Immutable makes the column writable through REST at create time only.

Optionally a sentence pointing at where the guarantee lives if you need one — a BEFORE UPDATE trigger — in the same spirit as the four places a rule can live section, which already makes the constraint-versus-convention distinction and is the right frame for this.

No behaviour change wanted. ReadOnly got the qualifier and Immutable did not, and that is the whole of it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions