- Status: proposed 2026-10-28. One of its clauses, §2, was ruled on 2026-10-28 by Alessandro Prandini: its rule before this record was written, and its boundary after the record was first proposed. Both are stated here as ruled. The rest is proposed with the record
- Shipped: unreleased
- Date: 2026-10-28
- Opens: nothing in the schema. No
SCHEMA_VERSION, noOUTPUT_VERSION, and no line of_STEPS. This record writes the rule a transforming step will be held to, before the first one exists - Answers: ADR 0055 §2, which says a third kind of step exists, that no rule covers it, and that its rule is owed before its code
- Assumes: ADR 0014 §1 (what may ride a bump), §2 (absent is how not recorded is written; migration stays a rewrite), §5 (the refusal is symmetric); ADR 0055 §2, §6 and §7; ADR 0024 §4 (the calibration case, the first case this rule will meet)
- Touches: nothing in
CLAUDE.md's fixed section - Number: 0056. Swept on 2026-10-28, before a line was written, across
origin/main(fd3f7afe), the three branches on the remote (main,captureandquoted-is-written, whose highest records are 0055, 0023 and 0054), every worktree of this repository and the open pull requests (none). The highest number taken anywhere is 0055
How to read the clauses. As in ADR 0055: derived names the record it follows and is checked against it; ruled is a decision taken before this record and transcribed; new is decided here and is checked against the code it cites.
Context¶
ADR 0055 §2 named three kinds of migration step: one that adds, one that renames, and one that transforms, computing a new shape from the old one. It ruled on the second, said that no rule covers the third, and said the third's rule is owed before its code.
The relatives that exist. Of the 18 steps in _STEPS at fd3f7afe,
seven write a value, and they are the only steps with anything in common with
a transformation:
| step | what it writes | from what |
|---|---|---|
4 -> 5 |
suspended: false on each case |
the version: no case was suspended before the field existed |
5 -> 6 |
aggregate: [] |
the version |
6 -> 7 |
artifacts: {} |
the version |
7 -> 8 |
target_config: {}, judge_config: {} |
the version |
8 -> 9 |
samples, sample_min, sample_max on a sampled verdict |
derived from metadata["scores"], which the document already holds |
16 -> 17 |
a calibration band's assertion_id |
derived from the case's verdicts, and refused when they do not say which check it is |
17 -> 18 |
projected: false |
the version |
All seven add a key and leave every other one where it was. None writes a new
shape over an old one, so none is a transformation. Two of them derive their
value from what the document holds, and one of those two refuses when it
cannot: 16 -> 17 is the nearest precedent this record has.
That refusal was measured on 2026-10-28 on this tree. A schema 16 run whose
calibration case held two verdicts with different identities, neither named as
the band's check, was migrated with digline migrate:
- the run was refused with "case 'calibration-half-right' carries the calibration band of 'llm_rubric', and its verdicts do not say which check that is. … the document was not written as it stands, and binding it to a guess would state a verdict nobody measured";
- the other two documents of the store were migrated, the tally read "2 migrated, 0 already current, 1 refused", and the command exited 64;
- the refused file was left byte-identical on disk.
The first case. The calibration case of ADR 0024 §4 is declared as a band,
low and high around one fixed answer, and stored as such:
results[].calibration carries assertion_id, check, low and high,
measured on the one committed baseline that has a calibration case
(examples/prompt-first). The answer itself is payload and is not in the
document. Changing that band into a relation between answers is planned for
1.0, and that step would be a transformation. Its shape is not decided by this
record.
Decision¶
1. What a transforming step is¶
It reads one or more paths and writes one path of a different shape. It declares the paths it reads and the path it writes, as paths in the schema.
New, as a kind. Derived in its declaration from ADR 0055 §1 and §7: a step finds what it touches by its position in the schema, never by a key's spelling, and declares it as a path. The paths it reads and does not write are removed by the step, and the declaration names them, so nothing leaves the document undeclared.
Its domain is declared too. A transforming step applies at the positions where the thing it transforms exists. A case with no calibration band is not a case whose band is missing: it is outside the step's domain, and the step passes it unchanged. New. The distinction matters for §3 and §6: inside the domain an input that is absent is refused; outside it there is nothing to transform.
2. When the old value does not determine the new one¶
The step does not invent it. It refuses the document with a sentence that names what is missing, and writes no default.
Ruled by Alessandro Prandini on 2026-10-28, in translation: "A default put in by a migration is a silence turned into a value, and it is the defect we are removing from the product: we do not put it into the mechanism that migrates it."
It has a precedent in the code: 16 -> 17 refuses exactly this way, by name,
and the file is left as it was (Context). And it agrees with ADR 0014 §1's
second condition, a field whose honest migrated value is a guess does not
ride.
When a default is a declaration. Ruled by Alessandro Prandini on
2026-10-28, after this record was first proposed. The reason above was wider
than _STEPS, and this is the question that bounds it:
What the value states: was it true of that run? If it was, the default is a declaration. If it was not, the default is forbidden, and the step refuses, naming what is missing.
Why the question is put on the fact. An earlier form of this clause asked could the old version have produced another value? That question reads the schema, and at a change of schema its answer is always no: the field did not exist, so the old version could have produced nothing else. A question whose answer is fixed in advance is not a check. Put on the fact, the question can answer no.
Why a false default is forbidden and not merely tolerated. A default is not read alone. It is compared with a value recorded by a later run, and a false one produces a difference that nobody measured: a change reported where none was established.
The five steps that write a fixed value, read against the question. Three pass, and two are violations, declared.
| step | writes | was it true of that run? |
|---|---|---|
4 -> 5, _add_suspended |
suspended: false |
yes. No case could be set aside before suspension existed, so none was |
5 -> 6, _add_aggregate |
aggregate: [] |
yes. A run written before aggregates existed declared none |
17 -> 18, _add_schema_eighteen |
projected: false |
yes. Nothing before schema 18 could write a projection, so the document is not one |
The declared violations: exactly two, and the count is fixed. Both are released, and the question forbids both. They are named here rather than left among the steps that pass. They are not alike, and the last column is why:
| step | writes | what it states, and why that was not true | where the difference is produced | who can meet it |
|---|---|---|---|---|
6 -> 7, _add_artifacts |
artifacts: {} |
no file under test. The run had a prompt; nothing recorded it | the artifact deltas of compare (artifact_deltas in src/digline/core/compare.py) give new for every file a later run declares, so artifacts_changed is true and the report says a file under test changed since the reference; the same {} excludes a run from the spread of digline log as testing another prompt (_why_not in src/digline/report/log.py) |
nobody. No release ever wrote schema 6: digline 0.1.0, the first on PyPI (2026-08-27), already writes 7, which arrived with a7653237 two hours before the tag. The step acts only on documents written by code older than any release. The violation is true and not reachable |
7 -> 8, _add_configs |
target_config: {}, judge_config: {} |
the target declared nothing. It had a configuration; nothing recorded it | compare, diff and the register read it as not recorded, by config_deltas' second rule. digline log does not: _why_not excludes the run from the spread as asking another system, and sighting reports declared no configuration and sends it on the wire as an absence (both in src/digline/report/log.py) |
every store that holds a run of digline 0.1.x. Those releases write schema 7, and their documents cross this step. The violation is reachable, and the half that can be repaired without changing the type is _why_not |
Noted 2026-10-29, and the table kept as written. The repairable half of
_add_configs is repaired: _why_not reads a side that recorded nothing as
config_deltas' second rule does, so digline log no longer excludes such a
run as asking another system, and excludes it under a reason of its own,
config_unrecorded (ADR 0024 §7.2, amended 2026-10-29). sighting, the other
half, still reports declared no configuration: it wants the type, and it is
not repaired here. The violation stays declared, because the step still writes
{}.
Why _add_artifacts is not repaired. Its violation cannot be met, and
repairing it would be wrong where it can. A document in the field with
artifacts: {} and no digline_version was written at schema 7, 8 or 9,
since digline's own driver records its version from schema 10 on and nothing
released wrote 6; at 7 to 9 artifacts existed, so that run really declared
none. Reading such a {} as not recorded would turn verdicts that are
correct today into unknown. The one other source of a document without a
version is a Run built by hand outside digline's driver, and this record does
not rule on it.
The question decides whether a default is forbidden; reachability decides whether the violation can be met. A table that declares the first without the second sends the work to the wrong case.
Measured on 2026-10-28: a store written by digline 0.20.0 and migrated to
schema 22, with its baseline's artifacts set to the {} that
_add_artifacts leaves and the suite declaring one file, compared with a new
run: "1 file under test changed since the reference.", and compare --json
gives artifacts_changed: true. The readers, line by line, are recorded in
full in the maintainer's notes, in the record of an empty container read two
ways.
A step added later that the question forbids is refused. It does not join this list. This clause does not change the two steps: it names them.
3. Totality¶
Every document the step accepts is transformed, or the step declares which documents it refuses and why. There is no third outcome, and in particular no silent one.
New. 8 -> 9 has the outcome this clause forbids a transformation: a verdict
with no scores gains nothing, in silence. For a step that adds, that is
right, because the absence already means what the step would add (ADR 0014
§2). For a step that transforms, it would leave the old shape under the new
version number, which the reader at that version does not read: the silence
would surface later, as a reader's refusal far from its cause.
So the three outcomes a transforming step may have are: transformed; refused with a sentence the step declares; and, at a position outside its declared domain, untouched (§1). The third is declared, not silent.
4. A transformation does not also rename¶
The path it writes is a path it read. If the shape and the name both change, they are two steps: this one, and a renaming step under ADR 0055.
Derived from ADR 0055 §2, checked against its text rather than copied. That clause says a renaming step "may not change the value's shape or its meaning as well. If both the name and the shape change, they are two steps, and each is held to its own rule." The mirror holds for a step that reads one path and writes it back reshaped. For a step that reads several paths and writes one, it holds when the written path is one of those read: the others are removed, by declaration (§1). If the written path is none of them, the step has named something new, and that is a rename; it splits.
One difference from the mirror: ADR 0055 §2 also forbids a rename to change the value's meaning. This record does not forbid a transformation to. What it forbids is reaching a new value by a default (§2). New.
5. The direction¶
Forward only. Derived: ADR 0014 §5 and ADR 0055 §6. Measured, not
asserted, on 2026-10-28 at fd3f7afe: a copy of a schema 22 store whose four
documents were marked schema_version: 23 and nothing else was read by this
tree. digline list exited 0 and said the runs were ignored and the baseline
could not be read, advising an upgrade; digline compare --run latest exited
64; digline migrate refused all four and exited 64; and every file was left
byte-identical. That is the measurement of ADR 0055 §6 again, on the tree this
record is written on, and src/ and packages/ did not change between the two.
What a transformation adds to it is not measured, because no transformation
exists: whether the old shape can be computed back from the new one. Nothing in
_STEPS goes backwards in any case: upgrade_document refuses a document newer
than its reader, and has no step from a higher version to a lower one. A user
who returns to the version before a transforming step is told to upgrade, and
loses nothing on disk; the runs under */runs/ are gitignored, so there is no
earlier copy of a migrated run to return to.
6. The gate this rule needs¶
Owed before the first transforming step lands, in the same pull request at the latest. New. Proposed form, within the family test ADR 0055 §7 proposes:
- A transforming step declares its nature as transforms, with the paths it reads, the path it writes, the paths it removes, its domain, and the sentences it refuses with.
- The test applies it to documents of its from-version, and each must end in exactly one of the three outcomes of §3:
- inputs present and determining: the declared path holds the new shape, the paths declared removed are gone, and no other path changed;
- an input absent inside the domain: the step refuses, with one of its declared sentences, and the document is left byte-identical;
- inputs present that do not determine the new value: the same refusal (§2);
- outside the domain: the document is identical but for
schema_version. - An outcome that is none of these, or a refusal with a sentence the step did not declare, is red.
Consequences¶
- The first case will meet §2 at once, if it takes the shape the plan for 1.0
describes, two fixed answers ordered with a gap. A stored document holds one
score and a band for a calibration case, and not the answer; nothing in it
measures a second answer. On that shape the old value does not determine the
new one, and the step refuses every document with a calibration case. On the
committed baselines that is one,
examples/prompt-first's. This is a consequence of §2, not a decision about the calibration case. - What the refusal road costs, for whoever decides that shape. If the
calibration case takes a shape the band does not determine, the only
committed baseline refused is
examples/prompt-first's, and it is promoted again from a new run. - A transforming step costs a refused document a decision by a person, as a
_NON_ADDITIVErow does: re-run and re-promote, or keep the version that reads it.
Alternatives considered¶
- Write a default when the old value does not determine the new one. Refused by §2, as ruled.
- Skip what cannot be transformed, and leave it in its old shape. Refused by §3: the reader at the new version would refuse it later, far from the step.
- Treat a transformation as a passenger under ADR 0014 §1. That rule is for a field that arrives. A transformation replaces one, so the question it must answer, what happens to the old shape, is not one §1 asks.
Not decided here¶
- The shape of the calibration case after 1.0. ADR 0024 §4 describes it as
a band, and today's code is that band (
CalibrationBandinsrc/digline/core/calibration.py, refusing anything but0 < low <= high < 1). Its replacement is decided with its own record. - Whether a transformation may be undone. Nothing goes backwards (§5).