Migrating to 0.14
0.14.0 opens a new compatibility key: it reads pins of 0.14.x only. A project pinned to 0.13.x is refused by name until it is upgraded, and listen-fire upgrade moves its pin once every file checks clean.
One idea runs through the release: a value that may be one of several things says so, and is read as what it can be. A record that may be missing is read with ?.; a record that may be a Memo or a Remark is read for what both have, or narrowed with is. 0.13 let such reads check clean and answered nothing, or failed, at run time.
Contents #
- 1. How the pin moves
- 2. A field read through a value that may be missing is written undefined
- 3. A walk off a union lands one of its members' records
- 4. undefined is a closure
- 5. The loop guard slot is removed
- 6. Runs paused under 0.13
- 7. Affinity: an opportunity list's change undefined names its columns
- 8. For hosts and embedders
- 9. Diagnostic code index
1. How the pin moves #
listen-fire upgradechecks every movement file of the project as a move up from its pin, and applies the fixes marked safe.--checkprints the diff and writes nothing.- The pin (
engine:inlf.yaml) moves to0.14.0only when every file then checks clean. Otherwise it stays, and the command lists what is left to change by hand. - 0.14 changes the meaning of nothing a 0.13 program says. Every change is a refusal, so no warning holds the pin and
--alladds nothing. - Until the pin moves, a 0.14
listen-firerefuses the project by name, saying to validate it under 0.14.0 and upgrade it.
The order that works:
- Delete
core.loopGuardfromlf.yaml(section 5). Until then every command refuses the project,upgradeincluded. - Run
listen-fire upgrade --checkand read what it lists. - Replace each
lazyby hand (section 4). - Narrow each read off a union walk that only some of its records have (section 3).
- Run
listen-fire upgrade. It writes each.through a value that may be missing as?.(section 2), and moves the pin when nothing is left. - Before running the new engine, let the runs paused holding a
lazywalk finish, or cancel them (section 6).
2. A field read through a value that may be missing is written ?. #
A field is never read with . through a value that may be missing, as TypeScript refuses x.F under strict null checks where x may be undefined. 0.13 refused it through a change's row and a map member; 0.14 refuses it wherever the value comes from.
| Before | After |
|---|---|
t = only(at-[:Tables]->), then t.Name | t?.Name |
only(at-[:Tables]->).Name | only(at-[:Tables]->)?.Name |
first(rows).Name, rows[0].Name, d["a"].Name | first(rows)?.Name, rows[0]?.Name, d["a"]?.Name |
if (t.Name != null) { … } | if (t?.Name != null) { … } |
- A value that may be missing is a record a walk may not find (
only(…),first(…), a namedmatch), a call's result, an index read, and, as before, a change's row and a map member. Each is refused withMOV_ABSENT_REQUIRED. x?.Freads the field, and is missing whenxis, sox?.F == nullholds then. It runs exactly asx.Fran in 0.13, so the rewrite is safe andlisten-fire upgradeapplies it.- A test proves the value present, and the reads after it are written with
.:exists(t),t != null, andt?.Name != null;exists(rows[0])orrows[0] != null, which proves every laterrows[0];- a map's member is one entry however it is spelled: a test of
d["a"]provesd.a.
- A walk off a record that may be missing is not written
?.. Walk it inside a test:if (exists(t)) { t-[:History]-> { … } }.
team = connect `@lf/slack` using team_workspace
function `Post Status`(go: <manual-[:Invocation]->>) {
ops = only(team-[ch:Channels WHERE ch.Name == "ops"]->)
if (!exists(ops)) { fail("no #ops channel") }
write ops-[:Messages]-> { Message: "Status from ${go.Text}: posted to ${ops.Name}" }
}
3. A walk off a union lands one of its members' records #
Where the members of a union land the same edge on different record types (a Person's Notes on a Memo, a Company's on a Remark), crm-[:Subjects]->-[n:Notes]-> lands a Memo or a Remark, as TypeScript's Memo | Remark. A read, a walk or a write off it is held to what every one of them has.
| Before | After |
|---|---|
crm-[:Subjects]->-[n:Notes]-> { … n.Mood … } | crm-[:Subjects]->-[n:Notes]-> { if (n is <crm-[:Memo]->>) { … n.Mood … } } |
n.Mood, then n.Tone | if (n is <crm-[:Memo]->>) { … n.Mood … } else { … n.Tone … } |
- A field every member's record has (
n.Text) is read as it was. - A field only some of them have is refused (
MOV_UNION_MEMBER_ONLY), naming theistest to write. A field none of them has isMOV_UNKNOWN_PROPERTY. 0.13 typed nothing past such a hop, so both checked clean and failed at run time. isnarrows the value: inside the arm it is the member named, and in theelseand every later arm, the rest. Below an arm that always returns or fails, the value is the rest too.- An
isnaming a type the value can never be is warned (MOV_IS_NO_OVERLAP): the arm never runs. listen-fire upgraderewrites none of it: only the author knows what a Remark should give where a Memo'sMoodwas read.
4. lazy is a closure #
A closure already says "run this when it is called", so lazy is retired. x = lazy …, return lazy … and a node entry files: lazy … are each refused where they are written (MOV_LAZY_RETIRED).
| Before | After |
|---|---|
people = lazy crm-[:People]->, read as count(people) | people = () => crm-[:People]->, read as count(people()) |
return lazy crm-[:People]-> | return crm-[:People]-> |
a node entry files: lazy d-[:Files]-> | files = () => d-[:Files]-> beside the node, called where the files are read; or files: d-[:Files]-> to keep what the walk lands on when the node is built |
listen-fire upgraderewrites none of it, and leaves the pin where it is until each is replaced. The refusal names the closure to write.- Where a lazy name was bound whole to another name, or passed to a function, decide which you meant: the walk's records when the name was bound (pass
people()), or a walk again at each use (pass the closure,people).
5. The loop guard slot is removed #
- An
lf.yamlnamingcore.loopGuard, and a core module whoself-external.yamlsaysslot: loopGuard, are refused by name by every command: "the loop guard slot is retired: remove it; the loop breaker (lf.yaml's loopWindow and loopDepth) replaces it". - Delete
core.loopGuardfromlf.yaml, and any guard module from the externals folder. Nothing else changes: 0.13 already ran no guard, and the loop breaker caps every chain of writes and arrivals.
6. Runs paused under 0.13 #
- A paused run's state moves to version 4, which holds no kept walk.
- A run paused under 0.13 while holding a
lazywalk is refused on resume, by name ("this run paused holding alazywalk"). Let such runs finish, or cancel them, before upgrading the engine. - Every other run paused under 0.13 resumes.
7. Affinity: an opportunity list's change Field names its columns #
- A listen on
`List Entry — <opportunity list>`'sChangesnaming aFieldthat is not one of the list's columns is refused (MOV_ENUM_UNKNOWN_VALUE), as on every other list. 0.13 typed it as text, so a misspelled column checked clean and never ran. - Name one of the list's columns, as the refusal lists them.
listen-fire upgraderewrites nothing. - The
@lf/affinityrelease that does this needs the 0.14 engine.
8. For hosts and embedders #
triggerEventOfhas left the engine package'spublic/inbound. Build a run's event witharrivalTriggerOf.- The loop guard slot has left
CORE_SLOTS(engine-contracts), and module-kit'sLoopGuardwith it. - A lowered field read written with
?.carriesoptional: true(omitted when false). The engine evaluates it as a plain read. - A paused run's state is version 4 (section 6). The wasm artifacts are rebuilt.
- A host's checker adopts 0.14's refusals (sections 2, 3, 4 and 7) with the 0.14 key, never under a 0.13 pin.
9. Diagnostic code index #
| Code | Severity under 0.14 | Section |
|---|---|---|
MOV_ABSENT_REQUIRED (a field read through a value that may be missing) | error, safe fix to ?., applied by listen-fire upgrade | 2 |
MOV_UNION_MEMBER_ONLY | error, by hand: narrow the value with is | 3 |
MOV_UNKNOWN_PROPERTY (off a union walk's landing) | error, by hand: read a field its records have | 3 |
MOV_IS_NO_OVERLAP | warning: the is test is always false | 3 |
MOV_LAZY_RETIRED | error, by hand: write a closure | 4 |
MOV_ENUM_UNKNOWN_VALUE (an Affinity opportunity list's Field) | error, by hand: name one of the list's columns | 7 |