feat(mcp): DEPLOY-job — lifecycle, enqueue-hook, claim-scoping, payload, dispatch, prompt (M17) #62

Merged
janpeter merged 14 commits from feat/M17-deploy-job into main 2026-07-04 14:54:39 +02:00
Owner

Fase 3 van M17 (PBI-124, auto-deploy op scrum4me-server na PR-merge):

  • Lifecycle (update-job-status): checkDeploySkipReason (doc_only_merge|merge_sha_already_deployed), done-sink zonder verify-gate, applyDeployTerminalUpdate — lock → status-flip → bulk-cancel/lift in één transactie; SSE per gecancelde rij ná commit
  • Enqueue-hook: maybeEnqueueDeployJob op het geslaagde ENABLE_AUTO_MERGE-effect (lock → config-read → blokkade-check → create; DB-harde dedup via partial index; triggerPush bij enqueue-falen)
  • Claim: DEPLOY-tak (SYSTEM|MANUAL) + deploy-only worker-scoping — capabilities exact [deploy] claimt uitsluitend DEPLOY (dicht het NULL-capability-lek, opus plan-review hoog)
  • Payload: getFullJobContext-DEPLOY-branch (mode auto|manual, rollbackClaim-gate op ontbrekende deploy_flow)
  • Dispatch: dispatch_job DEPLOY (geen refs, v1 = deploy huidige main) + deploy-dispatch.ts
  • Kind-prompt: src/prompts/deploy/run.md (merge-wacht, guards, flow-trigger, health)

Tests: 902/902, tsc schoon. Interleaving-suite bewijst de advisory-lock in beide volgordes; 4 falsificatie-mutaties gedocumenteerd in de story-log (ST-1459). Spec rev 5 + plan v3: Scrum4Me-repo docs/superpowers/specs/2026-07-03-deploy-job-design.md / docs/plans/M17-deploy-job.md.

Na merge (fase-4-voorwaarde): op de server mcp-stable bijwerken (git pull --ff-only && npm ci) en het runner-image rebuilden met MCP_GIT_REF=main.

🤖 Generated with Claude Code

Fase 3 van M17 (PBI-124, auto-deploy op scrum4me-server na PR-merge): - **Lifecycle** (update-job-status): checkDeploySkipReason (doc_only_merge|merge_sha_already_deployed), done-sink zonder verify-gate, `applyDeployTerminalUpdate` — lock → status-flip → bulk-cancel/lift in één transactie; SSE per gecancelde rij ná commit - **Enqueue-hook**: `maybeEnqueueDeployJob` op het geslaagde ENABLE_AUTO_MERGE-effect (lock → config-read → blokkade-check → create; DB-harde dedup via partial index; triggerPush bij enqueue-falen) - **Claim**: DEPLOY-tak (SYSTEM|MANUAL) + deploy-only worker-scoping — capabilities exact [deploy] claimt uitsluitend DEPLOY (dicht het NULL-capability-lek, opus plan-review hoog) - **Payload**: getFullJobContext-DEPLOY-branch (mode auto|manual, rollbackClaim-gate op ontbrekende deploy_flow) - **Dispatch**: dispatch_job DEPLOY (geen refs, v1 = deploy huidige main) + deploy-dispatch.ts - **Kind-prompt**: src/prompts/deploy/run.md (merge-wacht, guards, flow-trigger, health) Tests: 902/902, tsc schoon. Interleaving-suite bewijst de advisory-lock in beide volgordes; 4 falsificatie-mutaties gedocumenteerd in de story-log (ST-1459). Spec rev 5 + plan v3: Scrum4Me-repo `docs/superpowers/specs/2026-07-03-deploy-job-design.md` / `docs/plans/M17-deploy-job.md`. **Na merge** (fase-4-voorwaarde): op de server `mcp-stable` bijwerken (`git pull --ff-only && npm ci`) en het runner-image rebuilden met MCP_GIT_REF=main. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Bump naar scrum4me-shared a84cc3a (zelfde merge-sha als de web-repo).
Generated schema krijgt DEPLOY + auto_deploy/deploy_flow + resolved_at;
de idea-chat-elementen zaten al in main via PR #60.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- checkDeploySkipReason: DEPLOY-skipped vereist doc_only_merge of
  merge_sha_already_deployed in error (i.p.v. vrije tekst); overige kinds
  ongewijzigd (TASK_IMPLEMENTATION-only, >=10 chars).
- skipWorktreeCleanup=true voor DEPLOY (geen worktree op elk terminal-pad).
- Done-cascade: DEPLOY-branch (DB-only, geen verify-gate/push/auto-PR/
  propagatie) vóór de SPRINT_IMPLEMENTATION-tak.
- applyDeployTerminalUpdate: atomaire product-locked transitie — lock →
  terminale update → (failed) bulk-cancel QUEUED-siblings met
  CANCELLED/'superseded: ...' / (done) resolved_at-lift op oudere FAILED.
  Geïntegreerd op de generieke terminale-update-plek; SSE-notify + extra
  pg_notify per cancelledId ná commit; triggerPush blijft alleen op de job
  zelf.

Drift t.o.v. plan (idea-chat-PR #60 verschoof regelnummers): geen
functionele impact, alleen ankers aangepast op code-context.
Review-mutatie bewees dat de losse per-mock-arrays met lengte-asserts de
lock-vóór-update-volgorde in applyDeployTerminalUpdate niet afdwongen
(update vóór lock verplaatsen hield alle tests groen). Harnas herschreven
naar één gedeeld callOrder-array (patroon web-test actions/deploy.test.ts):
elke tx-mock pusht zijn naam, asserts eisen de exacte volgorde
['lock','update',...]. Mutatie-bewijs: update-vóór-lock ⇒ 4/7 FAIL;
terug ⇒ 7/7 PASS. Geen assert verzwakt — payload-checks blijven staan.
buildDeployOrchestrationKey + maybeEnqueueDeployJob (src/lib/dispatch/deploy-job.ts):
lock-eerst-dan-lezen transactie (pg_advisory_xact_lock op hashtext('deploy'),
hashtext(productId)), product-config-check (auto_deploy + deploy_flow),
blokkade bij onopgeloste FAILED, create zonder requested_*-snapshot
(fase-5-lijn), dedup via de partial-unique-index-catch (P2002/23505).
notifyJobEnqueued ná commit.

Wire in update-job-status.ts: de STORY-mode outcomes-loop (enige live
ENABLE_AUTO_MERGE-succes-site — createPullRequest's legacy
enableAutoMerge=true-pad wordt nergens meer aangeroepen) krijgt een
o.ok-tak die de DEPLOY-job enqueuet; falen daarin logt + triggerPush
i.p.v. de aanroeper te breken (persistent signaal, codex plan-r1 #8).

Nit uit T-1314-review: comment dat checkDeploySkipReason een
PREFIX-match is (staarten zoals ': 77199ba al live' toegestaan).
Fake-lock-harnas (__tests__/deploy-state-interleaving.test.ts): mockt
prisma.$transaction met een echte deferred-based in-memory mutex per
product-key — de mutex geeft pas vrij als de tx-callback resolvet, exact
zoals Postgres een xact-lock vasthoudt tot commit. In-memory
claude_jobs-array + Product-map bedienen findFirst/findMany/create/
update/updateMany.

Bewijst dat de product-lock (hashtext('deploy'), hashtext(productId))
applyDeployTerminalUpdate (failed-branch, T-1314) en maybeEnqueueDeployJob
(T-1316) écht serialiseert, in beide volgordes:
- volgorde A: failed-branch houdt de lock vast → enqueue die wacht ziet
  daarna de FAILED (resolved_at=null) ⇒ 'blocked', geen blok-omzeiling.
- volgorde B: enqueue houdt de lock vast en maakt de QUEUED-rij → de
  wachtende failed-branch cancelt die rij zodra hij de lock krijgt, geen
  wees-job.

Codex plan-r1 #6 / acceptatiecriterium spec §8. 5x achter elkaar gedraaid
zonder flakiness.
Critical (kwaliteitsreview T-1316): het interleaving-harnas claimde de
in-memory mutex in de $transaction-wrapper zelf — de serialisatie kwam uit
het harnas, niet uit de code-under-test (reviewer-mutatie: lock-regel uit
beide productiefuncties gesloopt, tests bleven groen). Fix: de mutex wordt
nu UITSLUITEND geclaimd in de tx.$executeRaw-mock wanneer de
tagged-template-SQL 'pg_advisory_xact_lock' bevat, met de product-key uit
de call; de wrapper geeft alleen een dán verworven lock vrij bij tx-einde.
Test A herschikt (holdBeforeQueries: FAILED-write pas ná de gate) zodat de
blocked-uitkomst alleen via de echte lock ontstaat; test B asserteert
wait-lock/acquired-lock-events van de code-under-test.

Mutatie-bewijs (beide gedraaid en hersteld):
- lock-regel uit maybeEnqueueDeployJob → 2/2 interleaving-tests FALEN
- lock-regel uit applyDeployTerminalUpdate → 2/2 interleaving-tests FALEN

Important: dedup-pad nu getest — create gooit P2002 ⇒ 'dedup', raw
SQLSTATE-23505-boodschap ⇒ 'dedup', ongerelateerde fout ⇒ rethrow.

Minor: push-body in de hook-catch gebruikt err.message i.p.v. String(err).

Interleaving 5x achter elkaar flake-vrij; tsc schoon; npm test 885 groen.
Breidt CLAIMABLE_JOB_KIND_FILTER uit met een DEPLOY-tak (source SYSTEM|MANUAL)
en voegt deployOnly-scoping toe in beide claim-builders (string- en
Prisma.Sql-variant): een worker met capabilities exact ['deploy'] claimt
uitsluitend DEPLOY-jobs met required_capability='deploy' en NOOIT de
NULL-capability-tak (voorkomt dat de deploy-container idea/plan-chat-jobs
oppikt — opus plan-review, hoog). TDD: __tests__/wait-for-job-deploy-claim.test.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Voegt de deploy-agent-claim-payload toe aan getFullJobContext: product-select
uitgebreid met auto_deploy/deploy_flow, nieuwe DEPLOY-branch vóór de
source==='MANUAL'-branch (anders eist die branch een manual-draft en rollbackt
een handmatige DEPLOY-job via "Deploy nu"). deploy_flow ontbreekt ⇒
rollbackClaim + null (mis-config zichtbaar QUEUED). mode='auto'|'manual'
o.b.v. pr_url; prompt_text blijft leeg (runner-owned via getKindPromptText,
PR_REVIEW-patroon). job.head_sha/base_sha/orchestration_key/pr_url/source
waren al scalar via de bestaande include (geen extra select nodig).

Tests: wait-for-job-deploy-context.test.ts dekt auto-mode payload-shape en
bewijst de branch-precedence (MANUAL-source DEPLOY zonder draft rollbackt
niet).
Spec-review vond via gate-mutatie een dekking-gat: verwijdering van de
!job.product.deploy_flow-gate liet alle tests groen (beide fixtures hadden
deploy_flow gevuld). Derde test: deploy_flow null ⇒ getFullJobContext
retourneert null én rollbackClaim's internals worden geraakt — cleanup-lookup
(2e findUnique met kind/product_id/task-select) + QUEUED-reset via
$executeRaw (status='QUEUED', claimed_by_token_id/lease_until NULL, jobId
als bind-value). Mutatie-bewijs: gate tijdelijk verwijderd → test faalt op
toBeNull; gate hersteld → groen.
Task 3.6: mcp-spiegel van deployNowAction — handmatige DEPLOY-enqueue
via dispatch_job. dispatchDeploy (nieuw, src/lib/dispatch/deploy-dispatch.ts)
neemt de product-lock vóór de deploy_flow-read en de active-job-guard
(zelfde tx als de create), gooit DispatchError i.p.v. outcome-unions
(user-facing dispatch-conventie) en slaat MANUAL/'deploy' op zonder
requested_*-snapshot of orchestration_key.

ACTIVE_JOB_STATUSES geëxporteerd uit idea-jobs.ts (was lokaal) zodat
deploy-dispatch.ts hem hergebruikt i.p.v. inline dupliceren.

dispatch-job.ts: DEPLOY in KIND_VALUES, REF_MATRIX.DEPLOY = { required: [] }
(v1 = "deploy huidige main", geen refs — pr_url e.d. worden geweigerd),
switch-case volgens de bestaande auth/return-conventies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Schrijft src/prompts/deploy/run.md (6-stappen: merge-wacht, boekhouding,
doc-only-guard fail-closed, sha-guard via repo_contains_sha, flow-trigger,
health+afronden) en registreert 'm in KIND_TO_PROMPT_PATH. Geen CODEX-variant
(DEPLOY draait uitsluitend runtime CLAUDE). Test dekt non-empty, $PAYLOAD_PATH
en de twee verplichte skipped-redenen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/scrum4me-mcp!62
No description provided.