Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apps/dispatcher/src/cron-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const readArmedCrons = (): readonly string[] => {
* that explains why. Adding a name here is the deliberate act; forgetting to is
* what this test catches.
*/
const DELIBERATELY_UNARMED = new Set(["release-notes", "product-demo"]);
const DELIBERATELY_UNARMED = new Set(["release-notes", "product-demo", "contextful-measures", "contextful-protocol"]);

describe("cron parity", () => {
it("every armed cron binds at least one registered run", () => {
Expand Down Expand Up @@ -98,4 +98,15 @@ describe("cron parity", () => {
.map((r) => r.name);
expect(armedButListed).toEqual([]);
});

it("arms no contextful schedule", () => {
// contextful uses FlareDispatch for tag releases only.
const armed = readArmedCrons();
const contextful = scheduledRuns().filter((r) => r.name.startsWith("contextful-"));
for (const { crons } of contextful) {
for (const cron of crons) expect(armed).not.toContain(cron);
}
expect(contextful.length).toBeGreaterThan(0);
});

});
8 changes: 6 additions & 2 deletions apps/dispatcher/src/pr-close.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Effect } from "effect";
import { describe, expect, it } from "vitest";
import type { Run, TriggerSpec } from "@fractalboxdev/flare-dispatch-core";
import { contextfulGate } from "@fractalboxdev/flare-dispatch-runs";
import {
CLOSE_MARKER_PREFIX,
Expand All @@ -10,7 +11,6 @@ import {
isCloseGroupPayload,
isCloseMarker,
} from "./pr-close";
import { triggersByEvent } from "./registry";
import { supersededSummary } from "./supersede";

const REPO = "fractalboxdev/contextful";
Expand All @@ -28,7 +28,11 @@ const closedPayload = (over: { merged?: boolean; headRepo?: string; action?: str
});

describe("closedGroups", () => {
const matches = triggersByEvent("pull_request");
// The gate's own triggers: the registry carries it without them, so no
// pull request starts or closes one there.
const matches = (contextfulGate.triggers ?? [])
.filter((trigger) => trigger.event === "pull_request")
.map((trigger) => ({ run: contextfulGate as Run<unknown, unknown>, trigger: trigger as TriggerSpec<unknown> }));

it("names the gate's group, ranked one millisecond after the close, for a merged or unmerged close", () => {
expect(matches.some(({ run }) => run.name === contextfulGate.name)).toBe(true);
Expand Down
30 changes: 30 additions & 0 deletions apps/dispatcher/src/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { lookupRun, triggersByEvent } from "./registry";

const CONTEXTFUL_REPO = "fractalboxdev/contextful";

describe("contextful runs", () => {
it("only a tag release starts a contextful run from a webhook", () => {
const events = ["pull_request", "push", "pull_request_target", "check_suite", "workflow_dispatch"];
const started = events.flatMap((event) =>
triggersByEvent(event)
.filter((m) => m.run.name.startsWith("contextful-"))
.map((m) => m.run.name),
);
expect([...new Set(started)]).toEqual(["contextful-release"]);
});

it("keeps the gate dispatchable by name", () => {
expect(lookupRun("contextful-gate")).toBeDefined();
expect(lookupRun("contextful-gate")?.triggers).toEqual([]);
});

it("starts a release on a v-tag push", () => {
const sha = "a".repeat(40);
const payload = { repository: { full_name: CONTEXTFUL_REPO }, ref: "refs/tags/v1.2.3", after: sha };
const fired = triggersByEvent("push").filter(
(m) => m.run.name === "contextful-release" && m.trigger.gate?.({ payload } as never),
);
expect(fired).toHaveLength(1);
});
});
11 changes: 9 additions & 2 deletions apps/dispatcher/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ import {
workerDeploy,
} from "@fractalboxdev/flare-dispatch-runs";

/**
* A run registered without its webhook triggers: no GitHub event starts it,
* and it stays dispatchable by name. contextful uses FlareDispatch for tag
* releases only, so its pull-request gate and cache warm fire on no push.
*/
const dispatchOnly = <I, O>(run: Run<I, O>): Run<I, O> => ({ ...run, triggers: [] });

/** name → Run. The single seam new runs are registered through. */
const RUN_REGISTRY: Record<string, Run<unknown, unknown>> = {
[offloadTest.name]: offloadTest as Run<unknown, unknown>,
Expand Down Expand Up @@ -75,8 +82,8 @@ const RUN_REGISTRY: Record<string, Run<unknown, unknown>> = {
[contextfulReleaseFormula.name]: contextfulReleaseFormula as Run<unknown, unknown>,
[oxlint.name]: oxlint as Run<unknown, unknown>,
[check.name]: check as Run<unknown, unknown>,
[contextfulGate.name]: contextfulGate as Run<unknown, unknown>,
[contextfulCacheWarm.name]: contextfulCacheWarm as Run<unknown, unknown>,
[contextfulGate.name]: dispatchOnly(contextfulGate) as Run<unknown, unknown>,
[contextfulCacheWarm.name]: dispatchOnly(contextfulCacheWarm) as Run<unknown, unknown>,
[vitestShard.name]: vitestShard as Run<unknown, unknown>,
[emailOtpLogin.name]: emailOtpLogin as Run<unknown, unknown>,
[finopsAudit.name]: finopsAudit as Run<unknown, unknown>,
Expand Down
14 changes: 3 additions & 11 deletions apps/dispatcher/src/routes/webhook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,23 +551,15 @@ describe("POST /v1/webhooks/github — pull request closed", () => {
},
});

it("starts one group close per superseding run and dispatches no run", async () => {
it("starts no group close and dispatches no run, since no registered run supersedes on a pull request", async () => {
for (const merged of [true, false]) {
const { env, workflow } = fixture();
const res = await handleRequest(await webhookRequest(closed(merged), { event: "pull_request" }), env);
expect(res.status).toBe(202);
const body = (await res.json()) as { dispatched: unknown[]; closed?: unknown[] };
expect(body.dispatched).toEqual([]);
expect(workflow.calls).toHaveLength(1);
expect(workflow.calls[0]?.params).toEqual({
closeGroup: {
group: `contextful-gate:${REPO}#42`,
rank: Date.parse("2026-10-19T10:00:05Z") + 1,
repo: REPO,
installation_id: 77,
},
});
expect(body.closed).toEqual([{ group: `contextful-gate:${REPO}#42`, instanceId: workflow.calls[0]?.id }]);
expect(workflow.calls).toHaveLength(0);
expect(body.closed ?? []).toEqual([]);
}
});
});
Expand Down
5 changes: 3 additions & 2 deletions wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
"45 5 * * *", // org-spec-audit — daily, after spec-drift-pr
"0 6 * * *", // ci-triage-pr — daily
"0 7 * * 1", // finops-audit — Mondays
"17 2 * * *", // contextful-measures — daily
"17 3 * * SUN", // contextful-protocol — Sunday at 03:17 UTC
// contextful-measures ("17 2 * * *") and contextful-protocol
// ("17 3 * * SUN") are unarmed: contextful uses FlareDispatch for tag
// releases only.
// The verdict reconcile sweep, not a run: re-sends the conclusion of
// every finished execution GitHub never received (DELIVERY_SWEEP_CRON,
// apps/dispatcher/src/delivery-sweep.ts).
Expand Down
Loading