Repository navigation
feat(dispatch): opt-in dispatch, counted supersessions and a two-tier contextful gate - #234
Conversation
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.
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.
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.
There was a problem hiding this comment.
AI code review — 💬 Comment
Risk tier: full · 0 critical · 1 warnings · 0 suggestions
Reviewers: security 0 · performance
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.
# Conflicts: # runs/contextful-gate.ts
There was a problem hiding this comment.
AI code review — 💬 Comment
Risk tier: full · 0 critical · 1 warnings · 0 suggestions
Reviewers: security 1 · performance
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.
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.skippedas{ run, reason: "not-configured" }.checkdeclarescheck.command:<repo>(labelled:…:<repo>:<label>, then…:<repo>),offload-testdeclaresoffload-test.command:<repo>,offload-test.commandandoffload-test.stages:<repo>, andworker-deploydeclaresworker-deploy.command:<repo>[:<label>]. Each returns[]when the dispatch passescommand.optInthat throws, logs and dispatches; a deploy withoutCONFIG_KVdispatches without a read. A failed read costs one no-op run and never drops a configured repo's run./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
{ event: "supersede.cancelled", group, root, supersededBy, children, admittedSlots, checkRuns }: the children it ended, therun_admissionsrows in stateadmittedit freed, and the check-runs it concludedcancelled.RunSupersedeStore.cancelresolves to{ admittedSlots }, counted in the same D1 batch that deletes the rows.run_supersessionsrow turnscancelledinstead of being deleted. It keepssuperseded_byuntil the existing 48-hour GC, and no later registration supersedes it again or reads it as live.run_supersessionsand labels acancelledrow "superseded by<id>", or "pull request closed" for a close marker./v1/dashboard.jsonrows carrysupersededBy.Two-tier gate (
contextful-gate1.3.0)CONFIG_KVgate.lightLabels:<repo>lists the light parts, as a comma-separated or JSON list. Every other part is heavy.pushtrigger on the default branch starts a heavy-tier gate once per head (contextful-gate:push:<after>), withbeforeas the base. ItsoptInnamesgate.lightLabels:<repo>, so an unconfigured repo dispatches nothing; a body that still sees the key unset endsRunSkippedbefore checkout.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.deferred; with the split off the field is absent.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)