Skip to content

feat(wakeups): workspace-wide switch to pause wakeup firing (CHE-1727) [squash] - #254

Merged
congvc-dev merged 28 commits into
mainfrom
agent/x99-claude-sonnet/f2a1a36af736
Oct 17, 2026
Merged

congvc-dev merged 28 commits into
mainfrom
agent/x99-claude-sonnet/f2a1a36af736

Conversation

@congvc-bot-x99

@congvc-bot-x99 congvc-bot-x99 Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Status: Independent verification complete. Cheese's merge is pending. Head 00556be28571da655d289d840623801fd5eb9e3d, base 2b135399996d8072c5a99deb88ef55edb62f49b4. Exact-head CI is green. GitHub reports the PR clean. Design PR #255 is merged (82c8a7700d40c3aa007aac4cc9dc0b63f7442db9). Luna's signed full-pair PASS leaves no open P0/P1. Sol validated the exact-pair OCR receipt against the trusted schema. Coverage: 31 selected files and 10 manually reviewed test exclusions. Reviewer: x99-codex-luna, OpenAI gpt-6-luna, non-author against Sonnet/Anthropic. Signed verdict: CHE-1727 comment 01a1497e-35c0-747b-8d83-abda9d1a9efe. P2/LOW findings remain disclosed and non-blocking. Author-local results below remain author-reported; neither Luna nor Sol reran local or database tests. The 12-capture UI pack is reused from 3d1bb0d32dc62c60a6b5f5ee921fc40e65df0231, not recaptured. UI source is unchanged.

Conditions:

  • Only Cheese merges. No deploy in scope.
  • Overdue one-shot wakeups fire once after resume. Explicit human triggers stay allowed while paused.
  • The design gate is met: Cheese merged design PR design(wakeups-pause): rev-1 directions for the workspace wakeup pause #255 and approved design/wakeups-pause/rev-3/A.html. Exact-pair independent verification and OCR are complete. Readiness does not authorize an agent merge.

Change:
A workspace switch wakeups_paused in workspace.settings stops wakeup firing. No new table.

flowchart LR
  Settings[Admin settings] --> Lock[Workspace lock and pause state]
  Lock --> Dispatch[Dispatch and daemon claim]
  Dispatch --> Gate{Paused?}
  Gate -->|Yes| Hold[No new non-manual firing or join]
  Gate -->|No| Filter[Drop late paused-era receipts]
  Filter --> Run[Admit eligible wakeup inputs]
Loading
  • Server: a paused workspace fires nothing for event, time, condition or system PR wakeups. Pending inputs are dropped with outcome paused. Recurring schedules skip missed slots. Overdue one-shots and conditions fire once after resume if they still hold. Manual human triggers still run. Other workspaces are unaffected.
  • Resume settles the pause in the transaction that lifts it (R1, R2).
  • P1 fix: a settings write now reads, reconciles and writes under the workspace row lock (LockWorkspaceSettings). A save that read the settings before a pause flip can no longer overwrite it.
  • P2 fix: a write whose read of the stored settings fails now fails with 500. It no longer assumes the settings hold no pause.
  • P2 fix: the pause and resume audit record commits in the same transaction as the flip. A flip that cannot be audited does not happen.
  • P2 fix: the issue header chip reads "Wakeups paused" while the workspace is paused. It no longer shows the next due time.
  • Round-3 P1 fix: regular and system dispatch take the workspace row (FOR KEY SHARE) first, read the pause under it and settle a paused rule inside the transaction that would enqueue. The pause and resume writers take FOR UPDATE on the same row. A pause can no longer commit between the read and the enqueue, and a resume can no longer be followed by a stale paused read that drops a later input. Lock order is workspace, issue, wakeup.
  • R3 guard (unchanged): a non-object settings body returns 400 while the pause is on.
  • API, CLI and settings UI as before: server-stamped wakeups_paused_by and wakeups_paused_at, the workspace_wakeups_paused warning, the admin switch and banners, five locales.

Evidence:

  • Round-2 test-first: tests-only commit 6a2221a301e64bce454cf6fe12da2d28364f63e2. Red CI: https://github.com/cheese-work/multica/actions/runs/38734898762. backend-tests fails on TestStaleSettingsSaveCannotClearAConcurrentPause, TestSettingsWriteFailsClosedWhenTheStoredSettingsCannotBeRead and TestPauseFlipRollsBackWhenItsAuditRecordFails. A views job fails on the header chip test. Each fails on the behavior, not on compilation.
  • Retrospective coverage, not red evidence: the cron tests TestResumeBeforeNextTickSkipsMissedCronSlots and TestPausedWorkspaceSkipsMissedCronSlots already passed on the tests-only commit.
  • Fix commit: 6e4e0316b8dda08c82401b8868d01e5781731c11. Head cbd528cc44a302a1f31fd669fce79ad01dea9423 adds the chip test id and updates the admit sheet.
  • Green CI on the fix commit: https://github.com/cheese-work/multica/actions/runs/38736991925.
  • Green CI on the head cbd528cc44a302a1f31fd669fce79ad01dea9423: https://github.com/cheese-work/multica/actions/runs/38739121171. All 15 non-skipped checks pass.
  • Round-3 test-first: tests-only commit e554f9c23a0bbf14b96897fd7997256e5aced4a9 adds TestPauseCommittingBeforeEnqueueCannotBeOutrunByDispatch, TestPauseCommittingBeforeEnqueueCannotBeOutrunBySystemDispatch and TestResumeBeforeAStalePausedReadDoesNotDropLaterInputs. They park the dispatch at its transaction start with a gate, not with a sleep. Red CI: https://github.com/cheese-work/multica/actions/runs/38758868312. backend-tests fails on exactly those three tests.
  • Round-3 fix commit and head: 3b9f1d36487569ccf20a5f7ea86bd945fdfbefb6. Green CI: https://github.com/cheese-work/multica/actions/runs/38760366702. All 15 non-skipped checks pass.
  • Round 4 (this finalization): merged main twice with merge commits, never rebased. Conflicts: packages/core/package.json (both exports kept) and server/internal/handler/workspace.go (main renamed validateCompactPromptConfig to validateCompactConfig; the rename is taken and the pause-aware saveWorkspace path is kept).
  • Round 4 test-first: tests-only commit 9c56fe97d2e34a891df545b4d9769d27d36f2f40 adds the Saved/Resumed and load-error Retry tests from the approved rev-3 A. Red CI: https://github.com/cheese-work/multica/actions/runs/38856321258. frontend-views-test (2/2) fails on exactly those two tests. The fix is commit fcc7eb374c43282de35f7152ccb1bed933c4ec25.
  • Round 4 CI on its head: https://github.com/cheese-work/multica/actions/runs/38857002198. Every job passed except backend-tests, which failed only on the migration prefix test that main also failed. Fixed upstream by PR fix(migrations): renumber agent_task_queue_context to clear duplicate prefix 627 (CHE-1824) #290.
  • Round 9 (join and claim): tests-only commit 48815262425ae7c569332278f26f32a179eb6a23 adds TestPausedWorkspaceDoesNotJoinWakeupInputsIntoAClaim and TestResumeThenClaimDoesNotJoinALatePausedEraReceipt, plus two controls that pass before and after the fix: TestManualTriggerStillJoinsAClaimWhilePaused and TestEventAfterResumeStillJoinsAClaim. Red CI: https://github.com/cheese-work/multica/actions/runs/39019294055. backend-tests fails on exactly the first two. The fix is commit 00556be28571da655d289d840623801fd5eb9e3d.
  • Round 9 fix: JoinWaitingWakeups takes the workspace row (FOR KEY SHARE) before the wakeup rows and reads the pause under it. reserveForRun joins nothing but a person's manual trigger while paused, sends what the run reserved before the pause back to pending, and drops late paused-era receipts after a resume. Lock order is workspace, then wakeup. The join takes no issue lock.
  • Round 9 consumer dispositions. Every reader or consumer of pending receipts was traced.
    • JoinWaitingWakeups and reserveForRun (the daemon claim path, daemon.go): were a bypass; fixed above. Condition rules only join condition.met, timeout and manual receipts, and that filter is unchanged, so condition hints are not joined and still prompt re-evaluation in dispatch.
    • dispatch and dispatchSystem, with their takenReceipts, deferForActiveRun, reserve, consume and timeout paths: run inside the workspace-locked, pause-gated transaction since round 3. Gated.
    • rebaseBuiltinConfig: only settles firings of carrier runs that already started. Accepted.
    • A run claimed before the pause that starts during it keeps the instructions it was joined with. The pause stops wakeup firing and new joins, not an ordinary run admitted earlier. Accepted and disclosed.
    • Manual triggers: exempt in the join, as in dispatch.
  • Round 8 (receipt invariant): tests-only commit 68dab99ad2703facf7744150747540825a7cb62f adds TestChildDoneHintRecordedWhilePausedEvaluatesOnResume, TestEventInsertedWhilePausedAndCommittedAfterResumeIsDropped and the control TestEventAfterResumeStillFiresWhenItMergesIntoALatePausedReceipt. Red CI: https://github.com/cheese-work/multica/actions/runs/39010584797. backend-tests fails on exactly the first two. The control passes before and after the fix. The fix is commit 54199039a346179ffbc62cd4fa16161cd579b4ed. A later commit regenerates sqlc and the head is 3d1bb0d32dc62c60a6b5f5ee921fc40e65df0231.
  • Round 8 producer dispositions. Every writer of issue_wakeup_receipt was traced, including the effective SQL triggers (read from the live function and trigger definitions, not only the migrations). The SQL function capture_issue_wakeup is the one choke point of seven triggers.
    • comment, comment_reaction, issue_reaction, attachment on insert: the foreign key to the workspace already holds KEY SHARE to the commit, so a resume waits for the producer and sweeps its receipt. I confirmed this by test: a held comment insert blocks the resume.
    • issue update, issue_to_label, agent_task_queue, and update or delete on the tables above: no workspace lock. A receipt of these can commit after the resume swept. Disposition: dispatch drops receipts whose event time is before the resume time. The resume stamps that time from the database clock (wakeups_resumed_at), and it is server-owned.
    • Event time is the later of the insert time and the payload occurred_at, so a real event after the resume that merged into a late paused receipt is kept.
    • Pull request events: workspace KEY SHARE since round 4. Registration snapshot receipts: workspace KEY SHARE already. Time-due, timeout and condition-met receipts: written inside dispatch under its locks. Manual triggers: exempt by design.
    • Sub-issue hints (children.changed) and the hints of any condition rule: no longer dropped by a pause or by the resume sweep. They only prompt an evaluation, so the last sub-issue closing while paused wakes the parent once after the resume if the state still holds. Their evaluation is state-based with the existing fingerprint dedup.
    • No new lock is taken by any producer, so the lock order is unchanged: workspace, issue, wakeup.
  • Round 7 (two P1s): tests-only commit ece9703c2d09b6cab8d2cf1b59f588cc9078341c adds TestPullRequestEventCommittingAfterResumeSweepDoesNotFire, TestPauseSwitchPatchKeepsUnrelatedSettings, and updates the pause-switch views tests to expect a partial patch. Red CI: https://github.com/cheese-work/multica/actions/runs/39005106626. backend-tests fails on exactly the two new Go tests, and frontend-views-test (2/2) fails on the three pause-switch tests. The fix is commit db705ebf115b4a13988ec7fbe3fe0db479f2760f.
  • Round 7 P1 "PR event vs resume": TriggerPullRequestWakeup now takes the workspace row (FOR KEY SHARE) before the issue lock, in the order dispatch and the pause writers use, and holds it to the commit. A resume (FOR UPDATE) either waits for the receipt and sweeps it, or runs first and the event arrives after it. The test parks the event at its receipt insert with a transaction wrapper, then observes the resume either finish or wait on a lock, with no sleep.
  • Round 7 P1 "stale full snapshot": the pause switch is a partial patch, wakeups_paused. The server applies it to the settings it reads under the workspace lock, so a cached snapshot of the whole blob can no longer overwrite a concurrent change. The client sends only the switch.
  • Round 7 database isolation: local database tests ran against the per-environment database multica_multica_395, which make up created for this worktree. It is not the shared multica database, and it lives on the shared PostgreSQL server of X99 next to 131 other databases. It is a dedicated database, not a separate server. No shared workspace or production database was used.
  • Round 7 left the other receipt writers open. Round 8 closed them, as listed above.
  • Round 6 (styling P1): tests-only commit 61eff7bb4ab1544b94cd7c1bc2e97b1d01ae7ca6 asserts the warning tone on the paused banner (settings tab and issue panel) and the success tone on Saved and Resumed. Red CI job: https://github.com/cheese-work/multica/actions/runs/38994864713/job/117285142833. frontend-views-test (2/2) fails on exactly those two tests, on the class assertions. GitHub marks the whole run cancelled because my next push superseded it, but that job had already failed. The fix is commit 92c983278934bc7c6539617188cd72c50c94f321: bg-warning/10 with border-warning/30 for the banner, bg-success/10 with border-success/30 for Saved and Resumed. Text stays on the foreground token and icons are aria-hidden, so contrast and the dark theme hold. Green CI: https://github.com/cheese-work/multica/actions/runs/38995651393.
  • Round 6 did not touch the P2, which stays disclosed and non-blocking: the row in the issue wakeup panel still shows its "Next" time while paused.
  • Round 5 (resync): merged main at 2b135399996d8072c5a99deb88ef55edb62f49b4 with a merge commit, never a rebase, and no conflicts. No code changed in this round, so no new tests were needed. CI on the head: https://github.com/cheese-work/multica/actions/runs/38988181152. This clears the backend-tests failure of the previous round, which came from the duplicate migration prefix 627 on main.
  • Earlier rounds: original red https://github.com/cheese-work/multica/actions/runs/38704389189, corrective red https://github.com/cheese-work/multica/actions/runs/38713578636.
  • Local: handler and service wakeup tests, golangci-lint (0 issues), the full views vitest run and pnpm typecheck pass.
  • Known gap: the row in the issue wakeup panel still shows its "Next" time while paused.

UI review pack — no UI file changed since head 3d1bb0d32dc62c60a6b5f5ee921fc40e65df0231. The pack below was captured on that head and still describes the UI of head 00556be28571da655d289d840623801fd5eb9e3d.

Page Width Before After
settings-wakeups 1440 settings-wakeups-running-1440-before.png settings-wakeups-paused-1440-after.png, settings-wakeups-resumed-1440-after.png, and the two -dark captures
settings-wakeups 390 settings-wakeups-running-390-before.png settings-wakeups-paused-390-after.png, settings-wakeups-resumed-390-after.png
issue-header and wakeup panel 1440 issue-panel-running-1440-before.png issue-panel-paused-1440-after.png
issue-header and wakeup panel 390 issue-panel-running-390-before.png issue-panel-paused-390-after.png

The PNGs are attached to the CHE-1727 comment that carries this pack. The before captures run main at 2b1353999. The after captures run head 3d1bb0d32. The three gaps of the verdict are fixed. The resumed phone capture now shows Resumed (the earlier capture script reused a stale pause state). The paused phone capture now opens the Wakeups panel and shows the banner. The running before capture is no longer blank. Two extra captures, settings-wakeups-paused-1440-after-dark.png and settings-wakeups-resumed-1440-after-dark.png show the dark theme.

Keep / Change / Remove check:

  • Keep: wakeups-child-done-switch present, yes. wakeups-child-done-instruction present, yes.
  • Change: wakeups-child-done-switch shows as overridden while paused, yes. wakeups-pause-status shows Saved and Resumed, yes. wakeups-load-retry offers Retry on a load error, yes. wakeups-header-chip reads Wakeups paused, yes. wakeups-pause-switch and wakeups-paused-banner are new, yes.
  • Removed: none

Writing-lint receipt: retained on CHE-1727 with the merge packet. Form warnings remain advisory. State-adjective and domain-terminology warnings are false positives; other warnings are true positives. Mermaid rendering is NOT-RUN.

Changelog: Admins can pause all wakeups in a workspace from Settings, Wakeups.

Closes CHE-1727

congvc-bot[bot] added 4 commits October 15, 2026 15:15
Tests only. Paused workspaces fire nothing for event, time, condition and
system PR wakeups; dropped events are not replayed; other workspaces and
manual human triggers are unaffected; the server stamps and audits the
switch; create warns while paused; CLI prints the warning; UI switch and
banner.
A workspace.settings.wakeups_paused switch stops dispatch for event, time,
condition and system PR wakeups. Inputs that arrive meanwhile are consumed
with outcome paused and never replayed; recurring schedules skip missed
slots; overdue one-shots and conditions fire once on resume; manual human
triggers still run. The server stamps who and when and writes one activity
record per flip. Creating or enabling a wakeup while paused returns a
workspace_wakeups_paused warning, which the CLI prints to stderr.
…l (CHE-1727)

Admins get a master switch at the top of Settings, Wakeups. While paused the
per-rule switch shows as overridden, and a banner names who paused and when
in the settings tab and the issue wakeup panel. The switch saves through the
existing workspace settings update; the server stamps who and when.
@congvc-bot-x99 congvc-bot-x99 Bot changed the title feat(wakeups): workspace-wide switch to pause wakeup firing feat(wakeups): workspace-wide switch to pause wakeup firing (CHE-1727) [squash] Oct 15, 2026
…ust not clear pause (CHE-1727 R1-R3)

Tests only. Fail against 6864c76: resuming before a scheduler tick replays
events that arrived while paused (R1) and fires recurring slots missed during
the pause (R2); PATCH settings with a non-object body clears the pause with
no resume audit (R3).
congvc-bot[bot] added 13 commits October 15, 2026 16:43
…gs (CHE-1727 R1-R3)

Resume now drops the inputs that arrived and skips the recurring slots that
came due while paused, in the same transaction that lifts the switch, so a
resume before the next dispatch tick cannot replay them. A settings body that
is not an object is rejected, so it cannot clear the pause without a resume
audit record.
…is on (CHE-1727 R3)

An existing contract (wakeup definitions) tolerates a non-object settings
blob, so the guard applies when the stored settings hold the pause, which is
the case where the write would clear it with no resume record.
…der chip, cron (CHE-1727 review round 2)

Tests only. Fail against 69e1e6b: a settings save that read before a pause
flip clears the pause; a write whose stored-settings read fails succeeds and
replaces paused settings; a pause or resume whose audit record fails reports
success and commits; the issue header chip promises a due time while the
workspace is paused. The two cron pause/resume tests are retrospective
coverage and already pass.
…ause-aware chip (CHE-1727 review round 2)

A settings write now reads, reconciles and writes under the workspace row lock,
so a stale save cannot clear a concurrent pause. A read of the stored settings
that fails fails the write. The pause and resume audit record commits with the
flip, so a flip that cannot be audited does not happen. The issue header chip
says wakeups are paused instead of promising a due time.
…ad must not drop later input (CHE-1727 review round 3)

Tests only. Fail against cbd528c: a pause that commits between a dispatch's
flag read and its enqueue transaction still gets a run enqueued, in regular and
system dispatch; and a dispatch holding a stale paused read consumes an input
that arrived after the resume. The interleavings are fixed by gating the
dispatch's transaction start, not by sleeping.
…atch transaction (CHE-1727 review round 3)

Regular and system dispatch now take the workspace row (FOR KEY SHARE) first,
read the pause under it and settle a paused rule inside the same transaction
that would enqueue. The pause and resume writers take FOR UPDATE on the same
row, so a pause cannot commit between the read and the enqueue, and a resume
cannot be followed by a stale paused read that drops a later input. Lock order
is workspace, issue, wakeup.
Conflict: server/pkg/db/queries/wakeup.sql (ListReadyWakeups). main added the
skip_ids and row_limit parameters and ListPendingWakeupInputGroups; the pause
clause is kept before ORDER BY. server/pkg/db/generated/wakeup.sql.go is
regenerated with sqlc.
Conflicts: packages/core/package.json (both sides added an exports entry; both
kept) and server/internal/handler/workspace.go (main renamed
validateCompactPromptConfig to validateCompactConfig for the settings body; the
rename is taken, the pause-aware saveWorkspace path is kept).
…pproved rev-3 A (CHE-1727)

Tests only. Fail against the merged head: no Saved or Resumed state, and the
load error has no Retry.
… A copy (CHE-1727)

Aligns the pause UI with the approved artifact design/wakeups-pause/rev-3/A.html
@ 82c8a77. The admit sheet names the approved
design and the new elements.
congvc-bot[bot] added 9 commits October 17, 2026 07:45
…one from approved rev-3 A (CHE-1727)

Tests only. Fail against 586c20a: the banner and the Saved/Resumed line use
neutral muted gray instead of the warning and success tokens.
…ved/Resumed (CHE-1727)

Matches approved rev-3 A (design/wakeups-pause/rev-3/A.html @
82c8a77). Surfaces use the semantic warning and
success tokens at low alpha with a matching border; text stays on the
foreground token and icons are aria-hidden, so contrast and the dark theme hold.
…7 review round 4)

Tests only. Fail against 92c9832: a pull request event whose receipt commits
after the resume sweep still fires (the workspace is never locked on that path),
and the pause switch has no partial patch, so the client sends a cached full
settings snapshot that can overwrite a concurrent change.
… server-side (CHE-1727 review round 4)

TriggerPullRequestWakeup now takes the workspace row (FOR KEY SHARE) before the
issue lock, in the order dispatch and the pause writers use, and holds it to the
commit: a resume either waits for the receipt and sweeps it, or runs first and
the event arrives after it. The pause switch is a partial patch
(wakeups_paused); the server applies it to the settings it reads under the
workspace lock, so a client's cached snapshot of the whole blob can no longer
overwrite a concurrent change.
… receipt invariant)

Tests only. Fail against db705eb: a sub-issue closing recorded while paused
loses its hint, so the parent is never woken for state that still holds after
the resume; and a receipt a lock-free producer (an issue update) inserted while
paused but committed after the resume sweep still fires. The third test, an event
after the resume that merges into a late paused receipt, passes today and guards
the fix.
…era receipts (CHE-1727 receipt invariant)

Condition hints (sub-issue and other state-check prompts) are no longer dropped
by a pause or its resume sweep: they only prompt an evaluation, so the last
sub-issue closing while paused still wakes the parent once on resume. The resume
stamps its time from the database clock (server-owned, preserved across client
settings saves); dispatch drops receipts whose event time is before it, which is
how a producer that takes no workspace lock (issue, task, label, delete triggers)
and commits after the resume sweep is kept from firing. A later event that merged
into such a receipt is kept: the event time is the latest of the insert and the
payload occurred_at. No new lock is taken on the producers, so lock order is
unchanged.
…-1727 join/claim)

Tests only. Fail against 3d1bb0d: JoinWaitingWakeups reserves pending receipts
for a run being claimed with no pause gate, so a claim while paused, a claim after
the resume of an input from before the pause, and a claim after the resume of a
late paused-era receipt all get the wakeup instruction. Two controls pass today
and guard the fix: a manual trigger still joins while paused, and an event after
the resume joins.
…27 join/claim)

JoinWaitingWakeups now takes the workspace row (FOR KEY SHARE) before the
wakeup rows, reads the pause under it, and reserveForRun joins nothing but a
person's manual trigger while paused (what the run reserved earlier goes back to
pending for the sweep) and drops late paused-era receipts after a resume. Lock
order is workspace, wakeup; the join takes no issue lock.
@congvc-bot-x99
congvc-bot-x99 Bot marked this pull request as ready for review October 17, 2026 11:03
@congvc-bot-x99
congvc-bot-x99 Bot requested a review from congvc-dev October 17, 2026 11:03
@congvc-dev
congvc-dev merged commit 15a54e9 into main Oct 17, 2026
23 checks passed
@congvc-dev
congvc-dev deleted the agent/x99-claude-sonnet/f2a1a36af736 branch October 17, 2026 11:16
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