@@ -4465,9 +4465,10 @@ export class AutomationEngine implements IAutomationService {
44654465 * stored pass reports that row `failed` with the parse's words.
44664466 *
44674467 * None of it changes `parsed` or `storable`, and nothing here throws for
4468- * it, so a caller that reads no `todos` (the save door, a package
4469- * duplication) behaves as before. `registerFlow` reads none either: it
4470- * runs the same checks itself and throws the first refusal.
4468+ * it. The checks run on the first read of `todos`, so a caller that reads
4469+ * none (the save door, a package duplication, the sync's comparison of a
4470+ * held flow) does no more work than before. `registerFlow` reads none
4471+ * either: it runs the same checks itself and throws the first refusal.
44714472 */
44724473 canonicalizeStoredFlow(name: string, definition: unknown): {
44734474 parsed: FlowParsed;
@@ -4476,19 +4477,37 @@ export class AutomationEngine implements IAutomationService {
44764477 conflicts: ConversionConflictNotice[];
44774478 todos: ConversionTodoNotice[];
44784479 } {
4479- const canonical = this.canonicalizeFlow(name, definition);
4480- // One listing per site: a site a conversion TODO names (the value-slot
4481- // refusal the spec lists under its D3 entry) keeps that TODO, which
4482- // names the retirement; the registration check's line for it is the
4483- // same refusal and is not repeated.
4484- const listed = new Set(canonical.todos.map((todo) => todo.path));
4485- for (const refusal of this.registrationCheckRefusals(name, canonical.parsed)) {
4486- for (const site of refusal.sites) {
4487- if (listed.has(site.path)) continue;
4488- canonical.todos.push(flowRegistrationTodo(name, site));
4480+ const { parsed, storable, notices, conflicts, todos: chainTodos } = this.canonicalizeFlow(name, definition);
4481+ // The registration checks run when `todos` is first READ, once: the
4482+ // stored pass reads it, and the doors that only want `parsed` or
4483+ // `storable` (the save door, a package duplication, the sync's
4484+ // "already held?" comparison) pay nothing for a listing they discard.
4485+ const listRegistrationRefusals = (): ConversionTodoNotice[] => {
4486+ const todos = [...chainTodos];
4487+ // One listing per site: a site a conversion TODO names (the value-slot
4488+ // refusal the spec lists under its D3 entry) keeps that TODO, which
4489+ // names the retirement; the registration check's line for it is the
4490+ // same refusal and is not repeated.
4491+ const listed = new Set(chainTodos.map((todo) => todo.path));
4492+ for (const refusal of this.registrationCheckRefusals(name, parsed)) {
4493+ for (const site of refusal.sites) {
4494+ if (listed.has(site.path)) continue;
4495+ todos.push(flowRegistrationTodo(name, site));
4496+ }
44894497 }
4490- }
4491- return canonical;
4498+ return todos;
4499+ };
4500+ let listedTodos: ConversionTodoNotice[] | undefined;
4501+ return {
4502+ parsed,
4503+ storable,
4504+ notices,
4505+ conflicts,
4506+ get todos(): ConversionTodoNotice[] {
4507+ listedTodos ??= listRegistrationRefusals();
4508+ return listedTodos;
4509+ },
4510+ };
44924511 }
44934512
44944513 /**
0 commit comments