Skip to content

refactor(#313): remove PR-mutation executor, review-state veto, and worker respawn subsystem#320

Merged
polymath-orchestrator merged 5 commits into
mainfrom
issue-313/simplification
Jul 14, 2026
Merged

refactor(#313): remove PR-mutation executor, review-state veto, and worker respawn subsystem#320
polymath-orchestrator merged 5 commits into
mainfrom
issue-313/simplification

Conversation

@polymath-orchestrator

@polymath-orchestrator polymath-orchestrator commented Jul 14, 2026

Copy link
Copy Markdown

Implements the operator-approved #313 harmony plan: three accidental subsystems removed, in three commits. Net -2,747 / +332 lines across the branch.

A. Dormant PR-mutation/merge executor (#241 rollback) — cd89d199d, -1,348/+9

POST /api/v1/prs/{id}/merge and /prs/{id}/resolve-comments never worked: no MergeExecutor/ResolveExecutor implementation existed anywhere and the only wiring passed nil executors, so every mutation path answered 501. Removed the whole chain — service/pr action service + LiveMainCIGate + error sentinels, the prs controller and DTOs, api.go/daemon wiring, the two OpenAPI routes and the now-empty prs tag, plus dead frontend route templates and an unreachable mobile mergePR action chain. AO still observes PR state everywhere (service/pr/manager.go ApplyObservation/WritePR untouched); only mutation died.

B. #311 review-state veto (review-authority collapse) — fd5e9baf1, -381/+46

Removed evaluateBlockingReviews/selectHeadPullRequest/flattenReviewPages/VERDICT_REVIEW_STATES from ops/final-review-status-core.mjs and fetchHeadPullRequestReviews + the autonomous blocking-reviews veto from ops/final-review-status.mjs.

Review-authority statement: autonomous merge eligibility is exactly a clean, SHA-current final-review commit status plus the absence of a current-head merge-park (human-only) signal. review-passed remains a derived mirror of final-review; the AO-native /sessions/{id}/reviews API remains visibility-only. Autonomous mode does not consult GitHub review states — a new CLI test pins that the autonomous check makes a single commit-statuses read and never touches /reviews or /pulls. Human mode is untouched.

C. Automatic worker respawn/retry subsystem (#210/#231/#243 rollback) — d5aa978a3, -782/+277

Tracker intake never launches replacement workers any more. A worker that dies with unfinished work emits one terminal worker_died_unfinished escalation carrying the session's terminal failure reason (#318 provenance) and, when one exists, the orphaned open PR as a fact — then waits for an explicit operator restart. Removed: retryDecision + retry caps, orphaned-PR branch adoption, worker_retry_exhausted (type, enrichment, intent fields RetryCount/RetryLimit/AdoptsOpenPR). Kept intact: the duplicate-PR guard and intake dedup (#181/#230 live-driver semantics — a dead worker's open PR still escalates instead of going silent; a live non-worker session cannot silence it).

worker_died_unfinished takes the vacated operator-attention slot (projection type list, attention metadata, DTO kind enum, Slack MENTION_KINDS, icons/tones) and leaves the notifier's informational stream so it is never double-posted. Store-level dedupe (subject+type+body, survives read) keeps the once-per-tick re-emission from spamming — a real-store test pins one row across polls and no resurrection after acknowledgement.

worker_retry_exhausted degrade note (no migration needed)

Existing unread worker_retry_exhausted rows in a live DB simply become invisible: the attention projection reads by an explicit type list, so removing the type filters the rows out at query time; nothing reads or validates them afterwards. The DB CHECK constraints in already-applied migrations still allow the value, so legacy rows keep round-tripping harmlessly. The Slack notifier keeps only its state-migration mapping for the kind, so any legacy needs-response record reconciles away on the next projection poll instead of lingering. Mark-all-read now clears such rows (the type left the exclusion list), which is the desired end state for a retired escalation.

Config-compat decision

#302 added strict JSON decoding (DisallowUnknownFields) on project add/set-config, and stored configs — including the committed spec ops/project-config/agent-orchestrator.json — carry trackerIntake.respawn: {disabled, maxRetries}. Dropping the field would therefore 400 every re-apply of an existing config. Chosen path (the smaller one, matching the existing deprecated labels/excludeLabels pattern): keep respawn as a tolerated-deprecated no-op field — decoded, round-tripped, marked deprecated in the OpenAPI schema, and completely ignored by intake. A domain test pins that a strict decode + Validate of a legacy respawn payload still succeeds. The spec file stays unchanged (so project-config check shows no drift); removing the stanza from spec + live config later is an optional operator cleanup, not required by this change.

Gates

  • npm run ci:backend — exit 0 (go build, go vet, go test -race ./... all ok, golangci-lint 0 issues)
  • npm run test:ops — 464/464 pass
  • Frontend: npm ci install, vitest 717/717 pass, tsc --noEmit clean, build:web ok (schema.ts regenerated)
  • BASE_REF=origin/main npm run format:check — clean
  • API drift: spec regenerated via go generate (specgen drift guard TestBuild_MatchesEmbedded green), schema.ts regenerated via npm run api:ts
  • Dangling-reference greps are clean for MergePR|ResolveComments|resolve-comments|evaluateBlockingReviews|fetchHeadPullRequestReviews and for the retry machinery (retryDecision|workerRetryExhaustedIntent|EffectiveRespawnPolicy|AdoptsOpenPR|RetryCount|RetryLimit|WorkerRetryExhausted); remaining worker_retry_exhausted strings live only in immutable applied migrations, a migration-tolerance test fixture, and the notifier's reconcile-away mapping — all intentional.

Refs #313

🤖 Generated with Claude Code

https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

polymath-orchestrator and others added 3 commits July 14, 2026 09:23
The PR merge/resolve-comments mutation surface added by #241 never had a
MergeExecutor/ResolveExecutor implementation: every mutation path 501'd and
the only wiring passed nil executors. Remove the whole dormant machinery,
keeping the PR observation/persistence path (manager.go ApplyObservation/WritePR)
intact — AO still OBSERVES PR state everywhere; only mutation dies.

Removed:
- service/pr: action_service.go, main_ci_gate.go, errors.go and their tests
  (LiveMainCIGate existed solely to gate the dormant executor; nothing else
  imported it).
- httpd/controllers: prs.go + prs_test.go and the Merge/ResolveComments DTOs
  (MergePRResponse, ResolveCommentsRequest/Response, PRIDParam).
- api.go PRs dep field, prs controller construction and its Register call.
- daemon/pr_wiring.go (buildPRActionService) and its daemon.go call, plus the
  wiring_test.go tests for it.
- OpenAPI: the two /prs/{id}/merge and /prs/{id}/resolve-comments routes, the
  now-empty "prs" tag, and the regenerated spec + frontend schema.ts.
- Dead frontend/mobile callers of the removed endpoints: api-client route
  templates, the mobile mergePR action chain, and stale comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5
#311 (commit e6b1ade) taught the autonomous merge gate to fetch a PR's
GitHub review states and refuse the merge on a current-head CHANGES_REQUESTED.
Per the operator-confirmed #313 harmony plan, the autonomous check returns to
exactly: a clean, SHA-current final-review status plus no current-head
merge-park signal. AO's review authority does not live in GitHub review states.

Removed evaluateBlockingReviews + VERDICT_REVIEW_STATES + selectHeadPullRequest
+ flattenReviewPages from final-review-status-core.mjs, and
fetchHeadPullRequestReviews + the autonomous blocking-reviews veto from
final-review-status.mjs. evaluateAutonomousMergeStatuses (final-review-clean +
no merge-park) is unchanged; human mode is untouched; the review-passed status
mirror, merge-park semantics, and the ao-native reviews API all remain.

Tests pin the restored contract: a CLI integration test asserts autonomous
check passes on a clean final-review with a single commit-statuses gh call and
never reads /reviews or /pulls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5
…ack, #313)

Per the operator-approved #313 plan: remove the automatic worker
respawn/retry subsystem; keep a clear terminal failure notification and
require an explicit operator restart.

Removed:
- observe/trackerintake: the respawn engine — retryDecision and its retry-cap
  logic, orphaned-PR branch adoption (canAdoptOpenPR, spawnCfg.Branch,
  postSpawnIntent/AdoptsOpenPR plumbing), workerRetryExhaustedIntent,
  workerRetryBlockedIntent. The duplicate-PR guard and intake dedup
  (seenIssueIDs, live-driver semantics from #181/#230) stay intact; a live
  non-worker session still cannot silence a dead worker's escalation.
- domain/tracker.go: DefaultWorkerRespawnMaxRetries and the
  WithDefaults/IsEnabled/EffectiveMaxRetries/EffectiveRespawnPolicy machinery
  plus respawn validation. The respawn config FIELD is retained as a
  tolerated-deprecated no-op (accepted, round-tripped, ignored) because #302's
  strict decode on project add/set-config would otherwise 400 every stored
  config that still carries a respawn stanza (including the committed
  ops/project-config spec). Test pins the tolerance.
- ports/notifications.go: RetryCount/RetryLimit/AdoptsOpenPR intent fields
  (TerminalFailureReason stays and now rides worker_died_unfinished).
- domain/notification.go: the worker_retry_exhausted type (const, Valid,
  WithInferredSubject) is gone.

Reworked — worker_died_unfinished is now the terminal death escalation:
- It carries the dead session's terminal failure reason (#318 provenance) and
  names an orphaned open PR as a fact; the body demands an explicit operator
  restart, with no respawn/retry language.
- It takes worker_retry_exhausted's operator-attention slot: projection type
  list, attention item metadata, DTO kind enum, notifier MENTION_KINDS, icon
  and tone maps. It leaves the notifier's INFORMATIONAL stream set so it is
  never double-posted.
- Store dedupe (subject+type+body, survives read) keeps the per-tick
  re-emission from spamming; a new real-store test pins one notification
  across polls and no resurrection after acknowledgement.

Legacy data degrade (no migration needed): existing unread
worker_retry_exhausted rows simply drop out of the type-filtered projection
query and become invisible; the DB CHECK constraints in applied migrations
keep tolerating the value; the Slack notifier keeps only the reconcile-away
migration mapping for legacy needs-response records.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5

Copilot AI 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.

Pull request overview

Removes three dormant/accidental subsystems to simplify AO’s operational surface area: PR mutation endpoints that always returned 501, an autonomous merge veto based on GitHub review state, and the automatic worker respawn/retry machinery—while preserving PR observation and operator-attention escalation.

Changes:

  • Removed the PR mutation API surface (backend routes/services + generated frontend/mobile clients).
  • Simplified autonomous merge eligibility checks to rely only on SHA-current final-review status plus absence of merge-park (no GitHub review-state reads).
  • Removed tracker-intake worker respawn/retry behavior and standardized on a single terminal worker_died_unfinished escalation with durable dedupe.

Reviewed changes

Copilot reviewed 48 out of 49 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/mobile/lib/store.tsx Drops mobile merge action wiring now that PR mutation endpoints are removed.
packages/mobile/lib/api.ts Removes mergePR API call targeting removed backend route.
ops/what-needs-me.mjs Updates icon mapping to use worker_died_unfinished instead of retry exhaustion.
ops/final-review-status.test.mjs Replaces blocking-review tests with a seam test ensuring autonomous mode only reads commit statuses.
ops/final-review-status.mjs Removes review/veto fetch path from autonomous status check logic.
ops/final-review-status-core.mjs Deletes GitHub review-state evaluator/helpers used for autonomous veto.
ops/attention-core.mjs Updates attention-kind icons to match new terminal worker-death escalation.
ops/ao-slack-notifier.test.mjs Aligns notifier behavior/tests with projection-driven worker_died_unfinished attention.
ops/ao-slack-notifier.mjs Removes worker_died_unfinished from informational stream and treats it as projection attention/@mention kind.
frontend/src/renderer/lib/api-client.ts Removes now-invalid PR mutation route templates.
frontend/src/renderer/lib/api-client.test.ts Drops normalization expectation for removed PR mutation endpoint.
frontend/src/renderer/components/PullRequestsPage.tsx Updates UI copy to reflect PR board is read-only and mutation endpoints are gone.
frontend/src/renderer/components/PullRequestsPage.test.tsx Updates test commentary/assertions to match removed PR mutation API.
frontend/src/renderer/components/OperatorAttentionPage.tsx Updates tone mapping for worker terminal escalation kind rename.
frontend/src/api/schema.ts Regenerates API schema removing PR mutation ops and deprecating trackerIntake.respawn as ignored compatibility field.
docs/cli/README.md Removes mention of PR actions that no longer exist.
docs/architecture.md Updates intake/worker lifecycle description to reflect no automatic respawn.
backend/internal/storage/sqlite/store/notification_store.go Restricts “dedupe survives read” behavior to worker_died_unfinished only.
backend/internal/storage/sqlite/store/notification_store_test.go Updates terminal dedupe test cases to remove retry-exhausted type.
backend/internal/storage/sqlite/queries/notifications.sql Updates worker-terminal dedupe query to only match worker_died_unfinished.
backend/internal/storage/sqlite/gen/notifications.sql.go Regenerates sqlc output for updated notifications query.
backend/internal/service/pr/main_ci_gate.go Deletes unused PR main-CI gate (part of removed PR mutation subsystem).
backend/internal/service/pr/main_ci_gate_test.go Deletes tests for removed main-CI gate.
backend/internal/service/pr/errors.go Deletes PR action sentinel errors (subsystem removed).
backend/internal/service/pr/action_service.go Deletes PR action service/contracts (subsystem removed).
backend/internal/service/pr/action_service_test.go Deletes tests for removed PR action service.
backend/internal/service/notification/service_test.go Updates notification service test data to use worker_died_unfinished.
backend/internal/service/attention/service.go Updates operator-attention mapping to treat worker_died_unfinished as the terminal operator-attention notification.
backend/internal/service/attention/attention_parity_test.go Updates parity tests for terminal worker death attention semantics and IDs.
backend/internal/ports/notifications.go Removes retry/respawn enrichment fields from NotificationIntent, keeps terminal failure provenance.
backend/internal/observe/trackerintake/observer.go Removes respawn decision tree; emits one terminal escalation and never spawns replacement workers.
backend/internal/observe/trackerintake/observer_test.go Replaces respawn tests with terminal escalation + dedupe behavior tests.
backend/internal/notify/manager_test.go Updates notification copy/tests to demand explicit operator restart and to name orphaned PRs as facts.
backend/internal/notify/enrich.go Removes retry-exhausted/blocked copy paths; standardizes worker death body text and failure-point suffix.
backend/internal/httpd/controllers/prs.go Deletes PR action controller and routes.
backend/internal/httpd/controllers/prs_test.go Deletes tests for removed PR action routes.
backend/internal/httpd/controllers/notifications_test.go Updates stream notification test fixture to use worker_died_unfinished.
backend/internal/httpd/controllers/dto.go Removes PR action DTOs; updates operator attention kind enum for worker_died_unfinished.
backend/internal/httpd/controllers/attention_test.go Updates attention API tests to expect worker_died_unfinished and exclude informational merged notifications.
backend/internal/httpd/apispec/specgen/build.go Removes prs OpenAPI tag and PR action operations from spec generation.
backend/internal/httpd/apispec/openapi.yaml Regenerates OpenAPI removing PR mutation paths/schemas and marking respawn config deprecated/ignored.
backend/internal/httpd/api.go Removes PR controller wiring from API deps/router registration.
backend/internal/domain/tracker.go Deprecates/ignores trackerIntake.respawn policy while keeping strict decode compatibility.
backend/internal/domain/tracker_test.go Adds strict-decode test ensuring legacy respawn payloads still validate/round-trip.
backend/internal/domain/notification.go Removes worker_retry_exhausted notification type; reclassifies worker_died_unfinished as terminal escalation.
backend/internal/daemon/wiring_test.go Removes PR action service wiring tests.
backend/internal/daemon/pr_wiring.go Deletes PR action service wiring helper.
backend/internal/daemon/daemon.go Removes PR action service from daemon API deps.
backend/internal/cli/project.go Retains CLI config shape for respawn as compatibility-only/deprecated field.
Files not reviewed (1)
  • backend/internal/storage/sqlite/gen/notifications.sql.go: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backend/internal/observe/trackerintake/observer.go Outdated
polymath-orchestrator and others added 2 commits July 14, 2026 10:18
…nd-trip test; PR-guard comment

Review findings on PR #320 (head d5aa978):

1. [MEDIUM] The terminal-death suppression treated ANY historical merged PR as
   issue completion before considering the latest dead worker, so a reopened
   issue — or a later worker dying after an earlier worker's PR merged — had
   its death silenced. dispatchDecision now computes the latest terminated
   worker first, and inspectHandledPRs counts a merged PR as completion only
   when it belongs to that worker or postdates its chronology
   (mergedPRIsCurrent). Tests pin all three shapes: latest worker's own merged
   PR suppresses; an earlier worker's stale merged PR no longer silences a
   later death; a posthumous merge of an earlier PR still suppresses.

2. [LOW] The respawn compat test only strict-decoded into the struct. Added
   TestManager_SetConfigRoundTripsLegacyRespawnStanza: the exact legacy stanza
   strict-decodes (the #302 API write path), saves through Service.SetConfig
   into a real sqlite store, reads back, re-encodes byte-identically, and has
   zero behavioral effect on the effective intake config.

3. (Copilot thread) The Store.ListOpenPRs comment claimed intake skips issues
   with an open PR "even when the owning session row is gone", overstating
   seenIssueIDs, which pins only live-driven PRs. Comment now states the real
   contract: live driver pins; terminated/missing owner routes to the terminal
   death escalation (#230).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5
1. mergedPRIsCurrent requires BOTH timestamps for the chronology comparison:
   a latest worker with no recorded chronology (zero UpdatedAt/CreatedAt) no
   longer lets an earlier merged PR's nonzero timestamp silence its death
   escalation. Ownership by the latest worker still counts independently.
   Unit test pins both sides.

2. The respawn round-trip test compares the re-encoded stanza structurally
   (unmarshal + reflect.DeepEqual against the decoded original fragment)
   instead of strings.Contains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1BedAdnAVFqVQTBoVyyp5
@polymath-orchestrator

Copy link
Copy Markdown
Author

Final review verdict for head afbbd8894af3c54498e43913422f9bc849168c39: clean (finish-lows applied). Independent reviewer family: codex (2 cycles + delta verify: 2 findings fixed, 2 lows polished; 1 reviewer finding was a false positive from the review harness's diff split, verified against the branch). PR-integrated reviewer (Copilot) posted 1 thread — fixed and resolved. Local gates on this head: ci:backend, test:ops (464), format:check PASS; frontend suite intentionally not re-run: the frontend tree is byte-identical to the head that ran green (717/717 + typecheck + build:web). Zero unresolved current-head threads. Net simplification: −2,747/+373 — PR-mutation executor gone, review authority collapsed to the current-head final-review status + merge-park, respawn/retry subsystem gone with terminal-death escalation preserved.

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.

2 participants