Skip to content

fix(labeler): kill-switch on in-flight runs, durable dead-letter retry, pointer tie-break (held review findings)#2166

Merged
ascorbic merged 4 commits into
feat/plugin-registry-labelling-servicefrom
fix/labeler-sol-held-batch
Jul 21, 2026
Merged

fix(labeler): kill-switch on in-flight runs, durable dead-letter retry, pointer tie-break (held review findings)#2166
ascorbic merged 4 commits into
feat/plugin-registry-labelling-servicefrom
fix/labeler-sol-held-batch

Conversation

@ascorbic

Copy link
Copy Markdown
Collaborator

What does this PR do?

Closes three held findings from the adversarial review of the labeler (RFC #694, umbrella #1909) — parked during the earlier fix PRs because they overlapped files those PRs were rewriting, now addressed off the merged tip. Each is TDD'd and the set passed an independent adversarial review. Targets the feat/plugin-registry-labelling-service integration branch.

1. fix(labeler): enforce the automation kill-switch on in-flight runs (#14). Automation pause was only checked before discovery; an already-dispatched Workflow never re-read automation_state and finalization had no pause predicate, so an admin could pause automation, get a success response, and in-flight runs kept issuing automated labels. Now enforced at two points: the Workflow entry (executeAssessmentInstance re-reads isAutomationPaused after the terminal short-circuit and throws AutomationPausedError before spending AI/network work) and, fail-closed, in the finalization CAS — a guardAutomationNotPaused predicate (EXISTS(automation_state WHERE paused=0)) AND'd onto the same batch as the signing and subject-not-deleted guards, so a mid-flight pause no-ops the whole batch and leaves the run running for the Workflow retry (halt-and-leave-resumable, matching the signing-pause precedent). Unknown/unreadable pause state fails closed (no issuance).

2. fix(labeler): make dead-letter retry durable before marking retried (#9). The dead-letter retry handler committed status='retried' in the mutation batch, then decoded the payload and enqueued in a deferred tail — so a failed or undecodable delivery stranded the row retried: health counts only new, no sweep re-drives retried, and a later operator retry hit 409. The decode + enqueue now happen before commitMutation: an undecodable payload returns 422 (permanent) and an enqueue failure returns 503 (transient), both leaving the row new/actionable; a retried row now implies the job was durably accepted. Verified replay-safe (idempotency/audit rows are written only at commit, the guard's replay branch short-circuits before re-enqueue) and double-retry-safe (the consumer's runKey dedup absorbs a duplicate).

3. fix(labeler): tie-break the current-pointer update on assessment id (#19). The current-assessment pointer upsert allowed overwrite at any equal created_at_epoch_ms, but list/current ordering is (created_at_epoch_ms DESC, id DESC) — so a later lower-id run sharing a millisecond could displace a higher-id current. The upsert guard now requires excluded.epoch > current.epoch OR (equal epoch AND excluded.id >= current.id), matching the canonical ordering (>= on the unique ULID id keeps idempotent same-run replay).

Known follow-up (not introduced here): a pause longer than the Workflow retry window strands the in-flight run running until an operator re-drives it — the fix is correctly fail-closed (no labels issued), and automatic resumption of a long-paused run folds into the existing "reconciliation should re-dispatch stuck runs" hardening item.

Type of change

  • Bug fix
  • Feature (requires a Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (implementation + independent adversarial review), orchestrated by Claude Opus 4.8

Screenshots / test output

labeler: 907 (workerd) + 136 (console) + 39 (calibration) passed
adversary: PASS on all three (kill-switch composes fail-closed with the finalization CAS; dead-letter reorder replay/dedup-safe; tie-break matches canonical ordering)

ascorbic added 3 commits July 21, 2026 07:36
The current-pointer upsert guard accepted any run at an equal
created_at_epoch_ms, but list/current ordering is (created_at_epoch_ms,
id). A later lower-id run could displace a higher-id current sharing a
millisecond. Match the pointer guard to the ordering so a lower-id run
cannot overwrite a higher-id current at an equal epoch.
An admin pausing automation only halted discovery; already-dispatched
Workflow runs never re-read automation_state and finalization had no
pause predicate, so in-flight runs kept issuing automated labels after a
successful pause.

Re-read the kill-switch on Workflow entry (halting before AI/network
work) and fold a fail-closed automation-not-paused predicate into the
finalization CAS. A pause landing mid-flight no-ops the whole batch,
leaving the run running for the Workflow retry to resume after unpause --
matching the signing-pause precedent.
The dead-letter retry committed status='retried' before decoding the
payload and delivering the re-drive to the queue. A failed or undecodable
delivery stranded the row 'retried': health checks (which count only
'new') stopped seeing it and a later operator retry returned 409, with
nothing ever re-driven.

Enqueue the discovery job before committing 'retried', so the status
flips only once the queue accepts the job. An undecodable payload (422)
or a queue failure (503) leaves the row 'new' -- still counted and
retryable. A duplicate enqueue under concurrent double-retry is absorbed
by the consumer's runKey dedup.
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 5c69215

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 6959687 Jul 21 2026, 07:15 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 6959687 Jul 21 2026, 07:15 AM

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jul 21, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 6959687 Jul 21 2026, 07:15 AM

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The approach is sound: these are the right fixes for the three held labeler findings, applied in the idiomatic places (Workflow entry guard + finalization CAS, pre-commit durable enqueue, and pointer upsert tie-break). Tracing the change confirms the kill-switch is fail-closed on both paths, the dead-letter retry now flips status only after the queue accepts the job, and the pointer guard matches the canonical (created_at_epoch_ms DESC, id DESC) ordering while remaining idempotent on same-run replay. The new tests exercise the concrete regression scenarios.

I did not find any logic bugs, regressions, or AGENTS.md violations in the changed code. The only thing that should be cleaned up is a stale interface comment: ConsoleMutationDeps.sendDiscoveryJob still claims the queue operation "runs in the deferred tail," but this PR moved it inline before commitMutation so the contract is misleading for future callers.


Findings

  • [suggestion] apps/labeler/src/console-mutation-api.ts:166

    The docstring for sendDiscoveryJob no longer matches how the retry path uses it. Since runDeadLetterAction now awaits deps.sendDiscoveryJob(job) before committing retried, the queue operation is not deferred off the response path; it is a synchronous durability gate on the request path. Update the comment so callers do not assume they can defer side effects or that the commit has already happened.

    	/** Re-enqueues a dead letter's discovery job on retry (design §6). A queue op
    	 * cannot join the D1 batch, so it is awaited before `retried` is committed; the
    	 * discovery consumer's `runKey` dedup absorbs a duplicate re-drive. */
    

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 21, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 21, 2026
@ascorbic

Copy link
Copy Markdown
Collaborator Author

Fixed the stale sendDiscoveryJob docstring in 69596879: it now states the queue op is awaited before retried is committed (a durability gate on the request path, not deferred), and that retried is a durable claim only once the job is accepted. Dropped the design-section reference per the repo comment policy (no doc/issue refs in comments).

~ 🤖 Claude Fable 5

@ascorbic
ascorbic merged commit fa4d25b into feat/plugin-registry-labelling-service Jul 21, 2026
8 checks passed
@ascorbic
ascorbic deleted the fix/labeler-sol-held-batch branch July 21, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: signed review/needs-rereview Author pushed changes since the last review size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant