ADR-192: The eighth writer describes an asset, and stays out of the seventh's field
- Status
-
Accepted
- Date
-
2026-09-16
- Amends
-
ADR-135 (a second writer on
sys_)file_ metadata - Amended
-
2026-09-21 by ADR-194
- Authors
-
Netresearch DTT GmbH
Context
set_ (ADR-135) writes one field of
sys_ and refuses every other argument by name. That refusal is
the right behaviour for a tool whose name states the field it writes, and it
left an assistant asked to caption an asset able to set the alternative text
and nothing else — which it reported accurately, and uselessly. title and
description had no writer at all, and read_ does not even
read description back.
The obvious move is to widen set_ into a general
metadata writer. This record says why that is the wrong one, and what the
eighth writing tool does instead.
Decision
update_ is the eighth writing tool, on exactly the terms of
the previous seven: disabled by default, in the editing group, an explicit
Tool (IDEMPOTENT_ — setting named scalar fields to given
values converges on repeat), a human approval before every call
(ADR-134), a preview at suspend (ADR-136), a
write through the Data under the acting user's permissions
(ADR-083), a read-after-write verification, and a refusal
vocabulary that never confirms a uid exists. It uses the
ADR-146 plan shape, as ADR-180 asked the
writers after it to.
It writes title and description, either or both, on one file's live
default-language metadata record. It does not write alternative.
ADR-194 added copyright where EXT:filemetadata declares
the column.
Two tools, and no field with two writers
Widening set_ was rejected on three grounds, and only
the third is decisive.
The first two are ordinary: it is shipped, tested code, and its name states the one field it writes — widening it would change what it does while keeping a name describing the old behaviour.
The third is the one that would have cost somebody something. A field with two
writers gives an approver two cards that can both claim it, and the refusal
vocabulary would need a second meaning for "this tool does not write that": at
present it means another tool does, which is actionable. So the two tools are
field-disjoint by construction, and each says so on the wire — this one's
description names set_ rather than only declining, so a
model told "not here" is not left to guess where.
The cost is named rather than hidden: an assistant setting every field of a file — the alternative text and the fields of this tool — makes two calls and costs two approvals. That is the same trade ADR-180 made for "page plus first element", for the same reason — one card, one thing.
The first plan() writer that updates
The four plan writers before it all create a record, and
Plans was extracted for them
(ADR-180). This one updates, so that helper does not apply and
the datamap and the read-back are its own.
Nothing is extracted for that. One implementation is not a shape — the same
test ADR-146 applied when it declined to grow
Writes. A second updating plan writer is
the trigger to look again.
What WAS extracted, because it was measured
This record first said the same about the FAL lookup, and the duplication
detector disagreed with a number: 93 lines of the new tool against
set_, 5.9 % new duplicated lines on a 3 % gate. That is
the same instrument, and the same answer, ADR-146 reached for the three writers
it added — two copies made in one sitting are copy-paste, not two decisions — so
Resolves now carries the resolution both metadata writers
perform: the sys_ row, the storage gate, the default-language access
check, and the live default-language sys_ row with the three
pins that decide which row a write lands on.
set_ is retrofitted to it, which ADR-135 and
ADR-146 both declined to do, and that is not a reversal of those records. They
declined to route working code through a trait answering a DIFFERENT question,
for a commonality that was argued rather than measured. Here the two files held
the same query verbatim and one of the copies was days old. Its own functional
test — the file mounts, the workspace-draft row, the neutral refusals — passes
unchanged, which is what makes the retrofit a refactor rather than a rewrite.
What stays per tool is everything the tools differ in: the refusal vocabulary, the fields, the read-back and the preview.
Two behaviours that came out of reading core rather than assuming it
An omitted field is not an empty one. A field the call leaves out is absent from the datamap; an empty string clears the field it names. Collapsing the two would make "set the title" also erase a description an editor wrote by hand, which is the one way a metadata writer destroys work nobody asked it to touch. Three tests fail when that distinction is removed.
The field-level grant is asked BEFORE the write. Core ships
sys_ with 'exclude' => true and description
without one. The Data skips a field the acting user holds no
non_ grant for — silently, with an empty error — and
applies the rest of the datamap. For the one-field writer before it that is a
failure to detect after the fact; here it would be a half-described asset,
because a user granted description and not title would get the
description written and a failure reported. The tool therefore asks the same
question the Data asks, through the same method
(Backend), before
writing anything, and refuses the whole call. The per-field read-back stays as
the backstop for everything that check does not model.
Worth recording about that method: it tests isset
before is, so a user object assembled without group data fails
the grant check whatever its admin flag says. The tool inherits that faithfully
because it calls the same method; a reimplementation would not have.
The title is bounded in bytes. The column is tinytext — 255 bytes,
not 255 characters. A German title of 200 characters is 400 bytes and would be
truncated by the database rather than refused by the tool. Where an
installation declares a TCA max that is narrower, it wins: it is what the
backend form enforces, and a tool accepting more would write what an editor
could not.
Consequences
✓ Eight editorial writes are available where seven were, and the editorial metadata of an asset is writable at all.
✓ No field of sys_ has two writers, so every approval card
answers for exactly one tool.
✕ Describing an asset fully costs two approvals, because the alternative text is another tool's.
✕ read_ still does not return description, so an admin
using the agent cannot read back a field this tool can write. The approval card
shows the before value, which is the channel that matters for a non-admin — the
reader is admin-only and in the structure group — but the asymmetry is real
and is left rather than widened here.
✕ The updating datamap and read-back still exist twice on
sys_, once in each of the two writers that touch it. Only the
lookup is shared; the duplication detector did not object to the rest, and
neither does this record until a third writer makes it a shape.
Revisit when
A second updating plan writer is proposed — then the question
ADR-146 asked about the creating ones applies to these, and
the answer may be an extraction.
Also revisit if a field ever genuinely needs two writers. The disjointness above is a rule this record chose, not one the runtime enforces.