Skip to content

feat(dispatch): opt-in dispatch, counted supersessions and a two-tier contextful gate - #234

Merged
debuggingfuture merged 7 commits into
mainfrom
feat/dispatch-opt-in
Oct 22, 2026
Merged

debuggingfuture merged 7 commits into
mainfrom
feat/dispatch-opt-in

Conversation

@debuggingfuture

@debuggingfuture debuggingfuture commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Work that no repo asked for costs nothing, and the work that runs is cheaper to wait on and easier to measure.

Dispatch only opted-in work

A webhook run on a repo that never configured it costs nothing: no Workflow, no check-run, no admission slot, no container.

  • RunSpec.optIn?: ({ input }) => readonly string[] names the CONFIG_KV keys whose presence means the repo opted in. An empty list means the inputs already carry the command, so no read happens.
  • The webhook receiver reads those keys after the trigger gate and before the cooldown (an unconfigured repo never arms a window). With none present it lists the run in skipped as { run, reason: "not-configured" }.
  • check declares check.command:<repo> (labelled: …:<repo>:<label>, then …:<repo>), offload-test declares offload-test.command:<repo>, offload-test.command and offload-test.stages:<repo>, and worker-deploy declares worker-deploy.command:<repo>[:<label>]. Each returns [] when the dispatch passes command.
  • A KV read that throws, or an optIn that throws, logs and dispatches; a deploy without CONFIG_KV dispatches without a read. A failed read costs one no-op run and never drops a configured repo's run.
  • Action mode (/v1/dispatch) and fan-out children do not pass through the receiver, so they never read. The run bodies keep their own opt-in checks.

Counted supersessions

  • Each superseded tree logs one JSON line { event: "supersede.cancelled", group, root, supersededBy, children, admittedSlots, checkRuns }: the children it ended, the run_admissions rows in state admitted it freed, and the check-runs it concluded cancelled.
  • RunSupersedeStore.cancel resolves to { admittedSlots }, counted in the same D1 batch that deletes the rows.
  • A cancelled member's run_supersessions row turns cancelled instead of being deleted. It keeps superseded_by until the existing 48-hour GC, and no later registration supersedes it again or reads it as live.
  • The dashboard execution list joins run_supersessions and labels a cancelled row "superseded by <id>", or "pull request closed" for a close marker. /v1/dashboard.json rows carry supersededBy.

Two-tier gate (contextful-gate 1.3.0)

  • CONFIG_KV gate.lightLabels:<repo> lists the light parts, as a comma-separated or JSON list. Every other part is heavy.
  • A pull-request gate dispatches only the light parts and posts no check for heavy ones. Unset (or blank, empty, malformed), it dispatches every part, as before.
  • A push trigger on the default branch starts a heavy-tier gate once per head (contextful-gate:push:<after>), with before as the base. Its optIn names gate.lightLabels:<repo>, so an unconfigured repo dispatches nothing; a body that still sees the key unset ends RunSkipped before checkout.
  • Heavy-tier gates serialize per repo (contextful-gate:heavy:<repo>, ADR-0004) with the default-branch head as the current revision, so a newer head replaces a waiting older one. They do not read pull-request liveness: the merged pull request is closed by design.
  • The gate's output lists the affected parts it left to the other tier under deferred; with the split off the field is absent.
  • Light parts spawn with the default admission class. The interactive class (limits.admissionClass) lands in feat(spend): spend guard and admission classes — budget, burn ceiling, scaled polls, interactive class #236; once it does, the light-tier spawn passes it through the child input.

Spec: specs/adr/0007-cost-efficient-execution.md § Dispatch only work, specs/adr/0008-cheaper-waiting-and-transport.md § Counted supersessions, specs/adr/0009-fast-trustworthy-verdicts.md § Two-tier gate (#232)

Testing: pnpm lint, pnpm typecheck, pnpm test (2845 passed, 1 skipped)

A run declares optIn: ({ input }) => CONFIG_KV keys. The webhook receiver
reads them before it creates a Workflow; with none present it dispatches
nothing and lists the run in skipped with reason not-configured. check,
offload-test and worker-deploy declare the command keys their bodies read.
A dispatch carrying its own command needs no read, and a failed read
dispatches as before.

Spec: specs/adr/0007-cost-efficient-execution.md (Dispatch only work)
…olveOptIn

resolveOptIn takes the key list as a thunk, so a throwing optIn takes the
same log-and-dispatch branch as a rejected KV read instead of failing the
whole delivery for the sibling triggers.

@flaredispatch-fractalboxdev flaredispatch-fractalboxdev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI code review — ✅ Approve

Risk tier: full · 0 critical · 0 warnings · 0 suggestions

Reviewers: security 0 · performance ⚠️ · code-quality 0 · documentation ⚠️ · release-management ⚠️ · compliance ⚠️ · agents-md 0

No findings.

📋 View full logs & reviewed diff ↗

Each cancelled tree logs one supersede.cancelled line naming its group,
root and superseding execution, with the children, admitted slots and
open check-runs it ended. A cancelled member's run_supersessions row
turns 'cancelled' instead of being deleted, so the dashboard execution
list reads superseded_by and labels the row with what superseded it.
…ts and default-branch pushes

gate.lightLabels:<repo> names the light parts. A pull-request gate runs
only those; a push to the default branch runs the heavy parts once per
head, serialized per repo, and is dispatched only for a repo that lists
light labels. Unset, a pull-request gate runs every part and a push
dispatches nothing.
@debuggingfuture debuggingfuture changed the title feat(dispatch): skip webhook runs a repo never opted in to feat(dispatch): opt-in dispatch, counted supersessions and a two-tier contextful gate Oct 22, 2026
A two-tier gate's output reported every required part under `stages` while it
ran only one tier's share, so a green pull-request gate read as a full verdict.
The output now lists the affected parts the other tier runs under `deferred`;
with the split off the field is absent and the output is unchanged.

@flaredispatch-fractalboxdev flaredispatch-fractalboxdev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI code review — 💬 Comment

Risk tier: full · 0 critical · 1 warnings · 0 suggestions

Reviewers: security 0 · performance ⚠️ · code-quality ⚠️ · documentation 0 · release-management ⚠️ · compliance 1 · agents-md ⚠️

1. ⚠️ Warning — Opt-in control fails open on configuration-store errors

📍 apps/dispatcher/src/opt-in.ts:27-30

A rejected CONFIG_KV read or throwing optIn function returns 'dispatch', allowing workflows, check-runs, admission slots, and potentially containers to be created for repositories that have not opted in. This bypasses the stated opt-in control during an outage. Prefer fail-closed behavior, or gate the fail-open fallback behind an explicit emergency policy with rate limits and alerting.

📋 View full logs & reviewed diff ↗

@debuggingfuture
debuggingfuture merged commit 0b18101 into main Oct 22, 2026
6 checks passed
@debuggingfuture
debuggingfuture deleted the feat/dispatch-opt-in branch October 22, 2026 10:39

@flaredispatch-fractalboxdev flaredispatch-fractalboxdev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI code review — 💬 Comment

Risk tier: full · 0 critical · 1 warnings · 0 suggestions

Reviewers: security 1 · performance ⚠️ · code-quality 0 · documentation 0 · release-management ⚠️ · compliance ⚠️ · agents-md ⚠️

1. ⚠️ Warning — Fail-open opt-in bypasses the configuration boundary

📍 apps/dispatcher/src/opt-in.ts:38-44

Any CONFIG_KV error, including an attacker- or outage-induced read failure, returns 'dispatch' and creates a Workflow for otherwise unconfigured repositories. Since this opt-in is intended to prevent unauthorized/costly execution, a KV outage can turn every matching webhook into container/check-run work. Consider failing closed for configured opt-in runs (or applying an explicit bounded emergency policy) instead of treating all read failures as authorization to dispatch.

📋 View full logs & reviewed diff ↗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant