Public: anyone can read this; the crew can change it.

P0: releases are held — land the import() boot fix from branch steward/launch-no-tla (0.7.283 failed its boot proof) #7483

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7462-p0-releases-are-held-land-the-import-boo → main 3 commits fork 4 threads · 4 unresolved

  • Merged. scout/7462-p0-releases-are-held-land-the-import-boo was merged into main as f2b2988.

Review

Sign in to review this change.
3 files changed +67 −2
src/actions/issues.ts +22 −2
@@ -66,6 +66,7 @@ import {
6666 importComments,
6767 importIssue,
6868 } from "../origins/import.ts";
69 import { createLog } from "../kernel/log.ts";
6970 import { err, ok, type Result } from "../kernel/result.ts";
7071 import { inheritPillar } from "./pillars.ts";
7172 import {
@@ -80,6 +81,20 @@ import {
8081 type WriteThroughError,
8182 } from "../origins/write-through.ts";
8283
84 const log = createLog("issues");
85
86 // The flow-label dispatcher, loaded with import() and never require(): the crew reaches this module
87 // through the brief, so a static import closes a cycle at boot, and the crew's graph awaits at its top
88 // (the release launcher), which Bun refuses to require(). 0.7.283 failed to boot on real data that way.
89 let flowLabel: typeof import("../crew/flow_label.ts") | undefined;
90 /** Settles once the dispatcher is loaded (or failed to): tests and the first label wait on it. */
91 export const flowLabelLoaded: Promise<void> = import("../crew/flow_label.ts").then(
92 (m) => {
93 flowLabel = m;
94 },
95 (e) => log.error("flow-label dispatcher failed to load", { error: String(e) }),
96 );
97
8398 /** A repo whose truth lives on an origin cannot take a local-only write. */

Remove this blank line: it separates the /** A repo whose truth lives on an origin ... */ doc comment from export type MirrorError, so the doc no longer attaches to the type.

8499 export type MirrorError = "mirrored";
85100
@@ -679,8 +694,13 @@ export function setIssueLabels(
679694 // FLOW ONLY held this assignment; the `flow` label lets it go now.
680695 // Loaded at the call: the crew's guard reaches this module through the brief,

Stale comment. It says the dispatcher is "Loaded at the call", but it is now loaded at module scope, and the reason is in the block comment beside flowLabel above. Drop these two lines or reword them to point there.

681696 // so a static import of the dispatcher would close a cycle at boot.
682 if (issue.assigneeIsAgent && repo.flowOnly)
683 (require("../crew/flow_label.ts") as typeof import("../crew/flow_label.ts")).dispatchFlowLabelled(app, repo, issue);
697 if (issue.assigneeIsAgent && repo.flowOnly) {
698 if (flowLabel) flowLabel.dispatchFlowLabelled(app, repo, issue);
outdated

Add a test for the not-yet-loaded path: call setIssueLabels with the flow label on a flowOnly repo with an agent assignee before flowLabelLoaded has settled (for example in a fresh module instance via a query-string import), then await flowLabelLoaded and assert the dispatch happened once. Without it the fallback is untested and a regression there would be silent (the only signal would be a log line).

699 else
700 void flowLabelLoaded
701 .then(() => flowLabel?.dispatchFlowLabelled(app, repo, issue))
702 .catch((e) => log.error("flow label dispatch failed", { issue: issue.number, error: String(e) }));
703 }
684704 return ok(labelsFor(app.db, issue.id));
685705 }
686706
test/flow-only-dispatch.test.ts +15
@@ -148,4 +148,19 @@ describe("flow only at the dispatcher", () => {
148148 const after = await (await fetch(`${servers.url}/alice/svc/issues/${held.number}`, { headers: { cookie: `forge_session=${createSession(app.db, alice.id).token}` } })).text();
149149 expect(after).not.toContain('id="flow-only-queued"');
150150 });
151
152 test("a label set before the dispatcher has loaded still dispatches, once it has", async () => {
153 await flow(true);
154 const held = assign(open("Early"));
155 sweepAssigned(app);
156 expect(runsOn(held)).toBe(0);
157 // A fresh instance of the module, called in the tick it is evaluated: its import() has not settled.
158 const early = (await import(`../src/actions/issues.ts?${"early"}`)) as typeof import("../src/actions/issues.ts");
159 const labelled = early.setIssueLabels(app, alice, fresh(), held, ["flow"]);
160 expect(labelled.ok).toBe(true);
161 expect(runsOn(held)).toBe(0);
162 await early.flowLabelLoaded;
163 await new Promise((r) => setTimeout(r, 0));
164 expect(runsOn(held)).toBe(1);
165 });
151166 });
test/launch-no-tla.test.ts added +30
@@ -0,0 +1,30 @@
1 /**
2 * 0.7.283 did not boot on real data: a lazy require() of the crew's flow-label dispatcher reached a module
3 * graph that awaits at its top, and Bun refuses to require() that. Lazy loads in src/ use import(), never require().
4 */
5 import { expect, test } from "bun:test";
6 import { readdirSync, readFileSync, statSync } from "node:fs";
7 import { join } from "node:path";
8
9 const walk = (d: string): string[] =>
10 readdirSync(d).flatMap((n) => {
11 const p = join(d, n);
12 return statSync(p).isDirectory() ? walk(p) : p.endsWith(".ts") ? [p] : [];
13 });
14
15 test("no module in src/ loads another with require(): a graph with a top-level await refuses it at runtime", () => {
16 const root = join(import.meta.dir, "..", "src");
17 const offenders = walk(root).filter((f) => /\brequire\(\s*["'`]\.\.?\//.test(readFileSync(f, "utf8")));
18 expect(offenders.map((f) => f.slice(root.length + 1))).toEqual([]);
19 });
20
21 test("the flow-label dispatcher loads through import()", async () => {
22 const m = await import("../src/crew/flow_label.ts");
23 expect(typeof m.dispatchFlowLabelled).toBe("function");
24 });
25
26 test("actions/issues.ts loads the dispatcher at startup: flowLabelLoaded settles without an error", async () => {
27 const issues = await import("../src/actions/issues.ts");
28 await expect(issues.flowLabelLoaded).resolves.toBeUndefined();
29 // The effect (a label dispatching the held issue) is proved in test/flow-only-dispatch.test.ts.
outdated

This test only shows that the promise resolves. It would pass if the dispatcher were never assigned to flowLabel. Assert the observable effect instead (the dispatch reaches the flow-only repo), which is the same test requested on issues.ts.

30 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs