@humwick/server 0.130.1 → 0.130.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/docs/guides/tools-administration.md +4 -0
- package/docs/guides/tools-coordination.md +1 -0
- package/docs/guides/tools-tasks.md +8 -0
- package/docs/guides/workflows.md +58 -0
- package/docs/openapi.json +414 -1
- package/package.json +8 -17
- package/public/assets/{MessageBubble-BWpoBjl4.js → MessageBubble-BWLqMGKG.js} +2 -2
- package/public/assets/{PlanGraphPanel-C0auck3n.js → PlanGraphPanel-3TlIQpjk.js} +2 -2
- package/public/assets/{TraceView-DBnOx7AQ.js → TraceView-Wimm0Czv.js} +2 -2
- package/public/assets/{VaultDecisionCard-0kp2oNBC.js → VaultDecisionCard-Bau_vNax.js} +2 -2
- package/public/assets/{activity-yLztXEBt.js → activity-CrSurn-r.js} +2 -2
- package/public/assets/{agent-lineage-BbJDR1dW.js → agent-lineage-CknAJKcb.js} +2 -2
- package/public/assets/agent-liveness-CaNwK0Zf.js +2 -0
- package/public/assets/{agent-name-CTZBrSD9.js → agent-name-DKbiIhBo.js} +2 -2
- package/public/assets/agent-profiles-DwF6EQ4R.js +2 -0
- package/public/assets/{agent-project-badge-9TqzUUYE.js → agent-project-badge-Diz4Sjjb.js} +2 -2
- package/public/assets/{agent-tools-D4n40pn8.js → agent-tools-CRhpRQza.js} +2 -2
- package/public/assets/{agent-type-icon-D6FUarOf.js → agent-type-icon-LrUEU3lB.js} +2 -2
- package/public/assets/{agents-BYDslkJc.js → agents-B-v66C-4.js} +2 -2
- package/public/assets/{analytics-B-XSbEMC.js → analytics-BqCb-LKi.js} +2 -2
- package/public/assets/{artifact-viewer-DdWIfV6M.js → artifact-viewer-kf4pGuAR.js} +2 -2
- package/public/assets/{artifacts-oBOqks-2.js → artifacts-D32zyw4Y.js} +2 -2
- package/public/assets/{artifacts-DPN1q4pA.js → artifacts-DJevXcvg.js} +2 -2
- package/public/assets/{automation-4KcRFXEu.js → automation-BpRajx_5.js} +2 -2
- package/public/assets/{backlog-Cw9xPTCF.js → backlog-D_UjTdzX.js} +2 -2
- package/public/assets/{busy-health-CTu32MZt.js → busy-health-N3VEh4ao.js} +2 -2
- package/public/assets/{channels-BiXzg1G9.js → channels-9jopungq.js} +2 -2
- package/public/assets/{chat-Ctk2qwvA.js → chat-CSXrTB3C.js} +2 -2
- package/public/assets/{code-preview-Bm1wWqAp.js → code-preview-p1NnmqK0.js} +2 -2
- package/public/assets/{connectors-C36ax4Vw.js → connectors-CZdqlDNL.js} +2 -2
- package/public/assets/{cosmos-Jj9LK2Wf.js → cosmos-BgdsE4dG.js} +2 -2
- package/public/assets/{crate-agent-H6S7-B9T.js → crate-agent-30aXTKva.js} +2 -2
- package/public/assets/{dependency-graph-Cd1E23wa.js → dependency-graph-B_jvw2GE.js} +2 -2
- package/public/assets/{deploys-Bwfq146q.js → deploys-DoN6bAb5.js} +2 -2
- package/public/assets/{display-CfO7rTuF.js → display-BrKWFGJz.js} +2 -2
- package/public/assets/{files-DhhZI2Ih.js → files-D6XEu8Ng.js} +2 -2
- package/public/assets/{fleet-B_rFEbnL.js → fleet-C7jQWbw2.js} +2 -2
- package/public/assets/{fleet-metrics-BkHP-P3R.js → fleet-metrics-BiDs3MLx.js} +2 -2
- package/public/assets/{formatted-body-Cm1RrzmN.js → formatted-body-CQBwc1fb.js} +3 -3
- package/public/assets/{formatted-body-impl-DQiOuOD1.js → formatted-body-impl-DyG0WsQS.js} +2 -2
- package/public/assets/{host-providers-dialog-CHqwXTov.js → host-providers-dialog-DKaW31dN.js} +2 -2
- package/public/assets/index-CRHep3M9.js +15 -0
- package/public/assets/index-MYAIwYTX.css +2 -0
- package/public/assets/{insights-fH1EL-qZ.js → insights-CzrtkpOE.js} +2 -2
- package/public/assets/{integrations-C5Oix6ng.js → integrations-aVsUmigt.js} +2 -2
- package/public/assets/{issue-ref-chip-ODyondIa.js → issue-ref-chip-BnSlM_95.js} +2 -2
- package/public/assets/{log-viewer-Cvfy6PW7.js → log-viewer-qe7Ee47Z.js} +2 -2
- package/public/assets/{maintenance-D8CDH5wZ.js → maintenance-NfjqFZAk.js} +2 -2
- package/public/assets/{managed-agents-egllbpij.js → managed-agents-BiH0HgaE.js} +2 -2
- package/public/assets/{markdown-preview-CYSFgVgK.js → markdown-preview-BixaT3Xr.js} +3 -3
- package/public/assets/{markdown-preview-impl-BteVIjX4.js → markdown-preview-impl-DNGlWIS3.js} +2 -2
- package/public/assets/{memory-C8WmXsyr.js → memory-DNsCl6UX.js} +2 -2
- package/public/assets/{messages-BzQdqlO6.js → messages-DopU1L1X.js} +2 -2
- package/public/assets/{orchestrators-B1HxzkNw.js → orchestrators-Cq7ckLQT.js} +2 -2
- package/public/assets/{overview-DCUnI4Z7.js → overview-C1DCTzzV.js} +2 -2
- package/public/assets/{pairs-ftCd2XcD.js → pairs-DE_m6rrE.js} +2 -2
- package/public/assets/{profile-icon-B2zH8DMH.js → profile-icon-B3rfh77F.js} +2 -2
- package/public/assets/project-profile-instructions-DQWZE0kA.js +2 -0
- package/public/assets/projects-Br2Bd2h1.js +2 -0
- package/public/assets/{prompt-settings-Bftq9czX.js → prompt-settings-BKvFsvGD.js} +2 -2
- package/public/assets/provider-display-DsuWMvVL.js +2 -0
- package/public/assets/{registry-DWCU5c8q.js → registry-Cw38_6gW.js} +2 -2
- package/public/assets/run-card-CpKqRbhg.js +2 -0
- package/public/assets/scroll-text-BKXS1ahr.js +2 -0
- package/public/assets/{secrets-C7GIUATz.js → secrets-DxI-vzSq.js} +2 -2
- package/public/assets/{service-access-settings-C3aujqTG.js → service-access-settings-D9s_VJbd.js} +2 -2
- package/public/assets/{settings-C6sZ9eBa.js → settings-CKZTVGIx.js} +2 -2
- package/public/assets/{shipping-Bu8p1gEw.js → shipping-B9YtnwtG.js} +2 -2
- package/public/assets/{stats-V9Z-GxUd.js → stats-DyUBKyWI.js} +2 -2
- package/public/assets/{status-dot-CVv0iGHL.js → status-dot-DYqlEUhJ.js} +2 -2
- package/public/assets/{store-BvtFdlGX.js → store-DcClXLH0.js} +3 -3
- package/public/assets/{tasks-CXU75HRF.js → tasks-Bh7Chr4-.js} +2 -2
- package/public/assets/{teams-BPf976zb.js → teams-bowI7qVY.js} +2 -2
- package/public/assets/{terminal-viewer-DE9BOe-8.js → terminal-viewer-BgVLHdsU.js} +3 -3
- package/public/assets/{terminal-viewer-impl-D04pjmGh.js → terminal-viewer-impl-Dz22UPy6.js} +2 -2
- package/public/assets/token-estimate-Co-Jdphh.js +2 -0
- package/public/assets/{use-agent-terminal-B1yR456R.js → use-agent-terminal-DSQQ-iY7.js} +2 -2
- package/public/assets/{use-decisions-D-S4tpDf.js → use-decisions-samMQhQf.js} +2 -2
- package/public/assets/use-project-list-59LheVP7.js +2 -0
- package/public/assets/{use-selectors-CnrAk2YP.js → use-selectors-BZa25xEB.js} +2 -2
- package/public/assets/{user-avatar-nBFIcR4M.js → user-avatar-DHxlvUvV.js} +2 -2
- package/public/assets/versioned-prompt-editor-BmED5DnJ.js +8 -0
- package/public/assets/{work-queue-TCjJxeT-.js → work-queue-CSvpuNLz.js} +2 -2
- package/public/assets/{workflow-marker-Cf3Hf2fN.js → workflow-marker-DAZcj5qt.js} +2 -2
- package/public/assets/{workflows-CXQX20Qj.js → workflows-DdmxRSyg.js} +2 -2
- package/public/assets/{workspaces-qSYzKYMA.js → workspaces-BxeKfVuF.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +31 -29
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/hooks/gate-run-guard.provisioned.mjs +31 -1
- package/runner/src/adapters/claude-effort.ts +2 -4
- package/runner/src/adapters/codex-launch-config.ts +4 -5
- package/runner/src/adapters/codex.ts +1 -1
- package/runner/src/adapters/opencode-provider.ts +7 -22
- package/runner/src/adapters/opencode.ts +4 -4
- package/runner/src/agent-runner/credentials.ts +42 -12
- package/runner/src/agent-runner/inbox.ts +27 -0
- package/runner/src/agent-runner/lifecycle-milestones.ts +0 -2
- package/runner/src/bin/claude.ts +4 -0
- package/runner/src/bin/codex.ts +4 -0
- package/runner/src/bin/opencode.ts +4 -0
- package/runner/src/index.ts +5 -2
- package/runner/src/provider-setup.ts +34 -0
- package/runner/src/providers.ts +2 -1
- package/runner/src/runner-core.ts +18 -23
- package/runner/src/spawn-input.ts +2 -1
- package/runner/src/test-run-dedup.ts +59 -56
- package/scripts/cd-config.ts +8 -0
- package/scripts/gate-run-lease.ts +10 -7
- package/scripts/gate.sh +5 -1
- package/src/branch-land-notice.ts +93 -0
- package/src/branch-landed.ts +7 -76
- package/src/bus.ts +19 -20
- package/src/cd-deploy-notify.ts +30 -2
- package/src/cd-host-lag-notify.ts +7 -6
- package/src/ci/incidents.ts +2 -2
- package/src/ci/monitor.ts +5 -8
- package/src/ci/root-missing.ts +2 -7
- package/src/ci/transition-notify.ts +7 -39
- package/src/cli/cd-pauses.ts +19 -0
- package/src/cli/orchestrator.ts +1 -1
- package/src/cli/state.ts +2 -19
- package/src/cli/task-edit.ts +22 -0
- package/src/cli/task.ts +3 -1
- package/src/cli/workflow.ts +8 -2
- package/src/commands-db.ts +3 -3
- package/src/config-store-write.ts +13 -1
- package/src/config-store.ts +25 -14
- package/src/db/activity-async.ts +6 -1
- package/src/db/activity-write-ops.ts +12 -1
- package/src/db/activity.ts +1 -14
- package/src/db/agent-search.ts +1 -15
- package/src/db/agents-async.ts +52 -5
- package/src/db/agents-write-ops.ts +12 -3
- package/src/db/agents.ts +81 -54
- package/src/db/ci-incidents-async.ts +5 -5
- package/src/db/ci-incidents-write-ops.ts +2 -2
- package/src/db/command-row.ts +10 -2
- package/src/db/commands-list-query.ts +2 -0
- package/src/db/commands-spawn-read.ts +1 -1
- package/src/db/config-store-write-ops.ts +7 -2
- package/src/db/continuation-archives-async.ts +5 -0
- package/src/db/continuation-archives-write-ops.ts +7 -0
- package/src/db/continuations.ts +2 -2
- package/src/db/db-writer-worker.ts +8 -1
- package/src/db/deploy-events-async.ts +1 -0
- package/src/db/deploy-events-read-ops.ts +2 -1
- package/src/db/deploy-events.ts +35 -4
- package/src/db/hosts-registration.ts +10 -2
- package/src/db/hosts.ts +9 -6
- package/src/db/inbox.ts +0 -14
- package/src/db/index.ts +2 -0
- package/src/db/integrations-async.ts +8 -0
- package/src/db/integrations-ops.ts +15 -0
- package/src/db/integrations.ts +15 -21
- package/src/db/issue-lifecycle.ts +3 -4
- package/src/db/issue-refs-async.ts +20 -0
- package/src/db/issue-refs-write-ops.ts +29 -0
- package/src/db/issue-refs.ts +7 -8
- package/src/db/long-running-work.ts +2 -2
- package/src/db/main-connection-writes.ts +23 -2
- package/src/db/maintenance-job-reconcile.ts +61 -0
- package/src/db/maintenance-job-runs-async.ts +14 -0
- package/src/db/maintenance-job-write-ops.ts +9 -0
- package/src/db/mappers.ts +2 -14
- package/src/db/merge-lease.ts +1 -15
- package/src/db/migrations.ts +7 -15
- package/src/db/orchestrators.ts +4 -72
- package/src/db/pairs.ts +1 -13
- package/src/db/project-profile-instructions.ts +50 -0
- package/src/db/prompt-versions.ts +9 -2
- package/src/db/provider-config-history.ts +8 -3
- package/src/db/provider-model-seed.ts +34 -0
- package/src/db/read-op-marshaling-fixtures.ts +5 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/routing-policy.ts +2 -3
- package/src/db/schema.ts +9 -3
- package/src/db/spawn-delegation-state.ts +11 -4
- package/src/db/stats-async.ts +4 -3
- package/src/db/task-entity.ts +23 -2
- package/src/db/task-list.ts +3 -1
- package/src/db/tasks.ts +1 -17
- package/src/db/timer-ingress.ts +7 -1
- package/src/db/token-db-write-ops.ts +17 -0
- package/src/db/token-standing.ts +15 -11
- package/src/db/workflow-contact.ts +1 -1
- package/src/db/workflow-task-read-ops.ts +30 -0
- package/src/db/write-ops.ts +6 -0
- package/src/db/writer-gateway.ts +17 -11
- package/src/dispatch-availability.ts +10 -6
- package/src/ephemeral/{landing-ingress-proof-worker.ts → landing-ingress-proof.test-fixture.ts} +4 -2
- package/src/host/disk-alert.ts +121 -0
- package/src/lifecycle-manager.ts +3 -2
- package/src/maintenance/cd-watcher.ts +3 -3
- package/src/maintenance/critical-job-watchdog.ts +16 -17
- package/src/maintenance/index.ts +1 -0
- package/src/maintenance/jobs.ts +3 -2
- package/src/maintenance/scheduler.ts +84 -128
- package/src/maintenance/stalled-delivery-pager.ts +6 -5
- package/src/maintenance/workspaces.ts +1 -1
- package/src/managed-policy.ts +31 -27
- package/src/mcp/index.ts +5 -2
- package/src/mcp/output-descriptions.ts +8 -8
- package/src/mcp/presentation.ts +4 -4
- package/src/mcp/project-profile-instructions.ts +32 -0
- package/src/mcp/tool-groups.ts +1 -1
- package/src/mcp/tools-spawn.ts +4 -3
- package/src/mcp-agent-action.ts +1 -2
- package/src/mcp-issue-ref-tools.ts +12 -11
- package/src/mcp-maintenance-tools.ts +2 -2
- package/src/mcp-profile-trust-tools.ts +4 -4
- package/src/mcp-provider-catalog.ts +2 -2
- package/src/mcp-task-tools.ts +11 -5
- package/src/mcp-team-template-tools.ts +3 -3
- package/src/mcp-workflow-tools.ts +10 -3
- package/src/notification-types.ts +2 -1
- package/src/operator-alarm.ts +26 -11
- package/src/project-instructions.ts +4 -0
- package/src/project-profile-instruction-text.ts +6 -0
- package/src/project-profile-instructions.ts +20 -0
- package/src/provider-catalog-store.ts +3 -49
- package/src/provider-config-store.ts +12 -11
- package/src/provider-model-seed.ts +1 -18
- package/src/provider-quota-config-store.ts +5 -4
- package/src/provider-secrets.test-fixture.ts +3 -3
- package/src/recipe-runner.ts +2 -2
- package/src/repo-config-source.ts +10 -4
- package/src/routes/agent-profiles.ts +6 -6
- package/src/routes/agents-spawn.ts +1 -1
- package/src/routes/agents.ts +6 -6
- package/src/routes/commands-listing.ts +4 -0
- package/src/routes/commands.ts +2 -2
- package/src/routes/continuation-archives.ts +2 -2
- package/src/routes/index.ts +10 -1
- package/src/routes/integrations.ts +24 -50
- package/src/routes/issue-refs.ts +7 -7
- package/src/routes/orchestrator-bootstrap.ts +1 -1
- package/src/routes/orchestrator-host-registration.ts +2 -2
- package/src/routes/orchestrator-host-utilization.ts +3 -1
- package/src/routes/orchestrator-proxy.ts +2 -2
- package/src/routes/orchestrator.ts +2 -2
- package/src/routes/project-profile-instructions.ts +27 -0
- package/src/routes/project-trust.ts +2 -2
- package/src/routes/provider-config.ts +6 -6
- package/src/routes/provider-quota-config.ts +2 -2
- package/src/routes/settings.ts +5 -5
- package/src/routes/stats.ts +6 -6
- package/src/routes/task-http.ts +15 -1
- package/src/routes/task-issues.ts +11 -26
- package/src/routes/tasks.ts +22 -53
- package/src/routes/team-templates.ts +5 -5
- package/src/routes/teams-config.ts +2 -2
- package/src/routes/tokens.ts +12 -5
- package/src/routes/workflows.ts +20 -5
- package/src/routing-policy-normalize.ts +1 -2
- package/src/routing-step-defaults.ts +0 -2
- package/src/runner-token-mint-limit.ts +63 -0
- package/src/runtime-grant.ts +4 -2
- package/src/runtime-tokens.ts +39 -7
- package/src/security.ts +13 -20
- package/src/server-background.ts +1 -1
- package/src/server.ts +5 -2
- package/src/services/host-utilization.ts +23 -0
- package/src/services/idle-epics.ts +5 -6
- package/src/services/issue-lifecycle.ts +57 -61
- package/src/services/issue-refs.ts +9 -13
- package/src/services/kernel-timers-cutover-chaos-worker.ts +10 -7
- package/src/services/kernel-timers-ingress.ts +1 -1
- package/src/services/landed-ancestry.ts +19 -3
- package/src/services/memory-areas.ts +15 -51
- package/src/services/plan-ci-notice.ts +57 -0
- package/src/services/plan-ci-recovery.ts +2 -55
- package/src/services/plan-wake-digest.ts +1 -0
- package/src/services/profile-trust.ts +4 -3
- package/src/services/project-config-read.ts +70 -0
- package/src/services/project-profile-instructions.ts +54 -0
- package/src/services/provisioning-harness/budget-baseline.json +4 -4
- package/src/services/restart-spawn.ts +61 -49
- package/src/services/self-care-state.ts +2 -1
- package/src/services/self-effort.ts +5 -5
- package/src/services/send-message.ts +5 -1
- package/src/services/spawn-agent.ts +13 -2
- package/src/services/spawn-chain-inspector.ts +6 -13
- package/src/services/spawn-policy.ts +68 -0
- package/src/services/spawn-tree-cleanup.ts +2 -2
- package/src/services/task-dispatch.ts +3 -0
- package/src/services/task-state-reconcile.ts +6 -14
- package/src/services/team-templates.ts +9 -8
- package/src/services/transient-agent-reaper.ts +3 -1
- package/src/services/workflow-auto-start.ts +25 -13
- package/src/services/workflow-ci-main.ts +1 -1
- package/src/services/workflow-deferred-cleanup.ts +4 -2
- package/src/services/workflow-effect-driver.ts +2 -0
- package/src/services/workflow-effects.ts +18 -11
- package/src/services/workflow-land-notice.ts +10 -0
- package/src/services/workflow-pause-text.ts +43 -0
- package/src/services/workflow-reconcile.ts +13 -11
- package/src/services/workflow-run-view.ts +2 -0
- package/src/services/workflow-runs.ts +29 -13
- package/src/services/workflow-supervisor-state.ts +5 -0
- package/src/services/workflow-supervisor.ts +7 -3
- package/src/settings/registry.ts +1 -2
- package/src/spawn-command.ts +4 -4
- package/src/spawn-models.ts +10 -12
- package/src/spawn-placement.ts +1 -1
- package/src/spawn-targets.ts +1 -1
- package/src/startup-context-injection.ts +1 -0
- package/src/state-adoption/cd-cutover.ts +53 -7
- package/src/state-adoption/fleet-cutover.ts +35 -0
- package/src/state-adoption/fleet-plan.ts +36 -0
- package/src/state-adoption/index.ts +2 -2
- package/src/state-adoption/linux-plan.ts +2 -2
- package/src/state-adoption/linux.ts +2 -2
- package/src/state-adoption/macos-plan.ts +2 -2
- package/src/state-adoption/macos.ts +1 -1
- package/src/steward.ts +7 -6
- package/src/task-dispatch-live.ts +1 -1
- package/src/task-dispatcher.ts +1 -1
- package/src/teams-config-store.ts +4 -3
- package/src/token-db-write.ts +10 -0
- package/src/workspace-command-settle.ts +2 -1
- package/src/workspace-steward-wake.ts +2 -2
- package/node_modules/@humwick/kernel/LICENSE +0 -167
- package/node_modules/@humwick/kernel/async-entity-persistence.ts +0 -344
- package/node_modules/@humwick/kernel/async-event-log.ts +0 -107
- package/node_modules/@humwick/kernel/automations.pilot.ts +0 -147
- package/node_modules/@humwick/kernel/config.ts +0 -111
- package/node_modules/@humwick/kernel/contract.ts +0 -572
- package/node_modules/@humwick/kernel/control-authz.ts +0 -281
- package/node_modules/@humwick/kernel/control.ts +0 -645
- package/node_modules/@humwick/kernel/effects.ts +0 -493
- package/node_modules/@humwick/kernel/event-log.ts +0 -319
- package/node_modules/@humwick/kernel/http-transport.ts +0 -384
- package/node_modules/@humwick/kernel/identity-grammar.ts +0 -20
- package/node_modules/@humwick/kernel/identity.ts +0 -248
- package/node_modules/@humwick/kernel/index.ts +0 -28
- package/node_modules/@humwick/kernel/kernel-persistence.ts +0 -181
- package/node_modules/@humwick/kernel/landing-incident.ts +0 -306
- package/node_modules/@humwick/kernel/landing.ts +0 -953
- package/node_modules/@humwick/kernel/manifest-validation.ts +0 -460
- package/node_modules/@humwick/kernel/mcp-transport.ts +0 -109
- package/node_modules/@humwick/kernel/message-authority-chaos-worker.ts +0 -200
- package/node_modules/@humwick/kernel/message-authority.ts +0 -281
- package/node_modules/@humwick/kernel/module-host.ts +0 -722
- package/node_modules/@humwick/kernel/openapi-init.ts +0 -14
- package/node_modules/@humwick/kernel/package.json +0 -39
- package/node_modules/@humwick/kernel/persistence-schema.ts +0 -248
- package/node_modules/@humwick/kernel/persistence.ts +0 -510
- package/node_modules/@humwick/kernel/registry.ts +0 -60
- package/node_modules/@humwick/kernel/sqlite-kernel-persistence.ts +0 -499
- package/node_modules/@humwick/kernel/sqlite-module-host.ts +0 -87
- package/node_modules/@humwick/kernel/timers-chaos-worker.ts +0 -186
- package/node_modules/@humwick/kernel/timers.pilot.ts +0 -1411
- package/node_modules/@humwick/kernel/workflow-activation.ts +0 -25
- package/node_modules/@humwick/kernel/workflow-backstop.ts +0 -40
- package/node_modules/@humwick/kernel/workflow-cancel-tree.ts +0 -20
- package/node_modules/@humwick/kernel/workflow-chaos-worker.ts +0 -213
- package/node_modules/@humwick/kernel/workflow-composition.ts +0 -231
- package/node_modules/@humwick/kernel/workflow-container.ts +0 -72
- package/node_modules/@humwick/kernel/workflow-control.test-fixture.ts +0 -42
- package/node_modules/@humwick/kernel/workflow-control.ts +0 -178
- package/node_modules/@humwick/kernel/workflow-definition.ts +0 -507
- package/node_modules/@humwick/kernel/workflow-dispatch.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-foreach.ts +0 -167
- package/node_modules/@humwick/kernel/workflow-journal-codec.ts +0 -339
- package/node_modules/@humwick/kernel/workflow-journal.ts +0 -922
- package/node_modules/@humwick/kernel/workflow-lane.ts +0 -48
- package/node_modules/@humwick/kernel/workflow-lease-owner.ts +0 -60
- package/node_modules/@humwick/kernel/workflow-recovery.test-fixture.ts +0 -19
- package/node_modules/@humwick/kernel/workflow-runtime.ts +0 -938
- package/node_modules/@humwick/kernel/workflow-script.ts +0 -11
- package/node_modules/@humwick/kernel/workflow-state.ts +0 -299
- package/node_modules/@humwick/kernel/workflow-step-commit.ts +0 -195
- package/node_modules/@humwick/kernel/workflow-transitions.ts +0 -317
- package/node_modules/@humwick/kernel/workflow-values.ts +0 -153
- package/node_modules/@humwick/kernel/workflow.pilot.ts +0 -376
- package/node_modules/@humwick/offload/LICENSE +0 -167
- package/node_modules/@humwick/offload/README.md +0 -57
- package/node_modules/@humwick/offload/dist/async-runner.d.ts +0 -18
- package/node_modules/@humwick/offload/dist/async-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/async-runner.js +0 -158
- package/node_modules/@humwick/offload/dist/async-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.d.ts +0 -25
- package/node_modules/@humwick/offload/dist/envelope.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/envelope.js +0 -80
- package/node_modules/@humwick/offload/dist/envelope.js.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts +0 -10
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js +0 -59
- package/node_modules/@humwick/offload/dist/fixtures/worker-task.js.map +0 -1
- package/node_modules/@humwick/offload/dist/index.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/index.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/index.js +0 -5
- package/node_modules/@humwick/offload/dist/index.js.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts +0 -62
- package/node_modules/@humwick/offload/dist/subprocess-runner.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/subprocess-runner.js +0 -295
- package/node_modules/@humwick/offload/dist/subprocess-runner.js.map +0 -1
- package/node_modules/@humwick/offload/dist/types.d.ts +0 -111
- package/node_modules/@humwick/offload/dist/types.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/types.js +0 -176
- package/node_modules/@humwick/offload/dist/types.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts +0 -23
- package/node_modules/@humwick/offload/dist/worker-pool.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-pool.js +0 -435
- package/node_modules/@humwick/offload/dist/worker-pool.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts +0 -2
- package/node_modules/@humwick/offload/dist/worker-runtime.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker-runtime.js +0 -27
- package/node_modules/@humwick/offload/dist/worker-runtime.js.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.d.ts +0 -5
- package/node_modules/@humwick/offload/dist/worker.d.ts.map +0 -1
- package/node_modules/@humwick/offload/dist/worker.js +0 -1
- package/node_modules/@humwick/offload/dist/worker.js.map +0 -1
- package/node_modules/@humwick/offload/package.json +0 -42
- package/node_modules/@humwick/offload/src/async-runner.ts +0 -170
- package/node_modules/@humwick/offload/src/envelope.ts +0 -92
- package/node_modules/@humwick/offload/src/fixtures/worker-task.ts +0 -49
- package/node_modules/@humwick/offload/src/index.ts +0 -4
- package/node_modules/@humwick/offload/src/subprocess-runner.ts +0 -334
- package/node_modules/@humwick/offload/src/types.ts +0 -235
- package/node_modules/@humwick/offload/src/worker-pool.ts +0 -463
- package/node_modules/@humwick/offload/src/worker-runtime.ts +0 -32
- package/node_modules/@humwick/offload/src/worker.ts +0 -5
- package/public/assets/agent-liveness-DyqxfB-t.js +0 -2
- package/public/assets/agent-profiles-BpI_jTxq.js +0 -2
- package/public/assets/index-B7ZerHLY.css +0 -2
- package/public/assets/index-BU3uWo2t.js +0 -15
- package/public/assets/projects-B_lmcqfO.js +0 -2
- package/public/assets/provider-display-DbLWpP3j.js +0 -2
- package/public/assets/run-card-CO_bguQf.js +0 -2
- package/public/assets/use-project-list-B-T2Bnnn.js +0 -2
- package/public/assets/versioned-prompt-editor-Diu8BIaM.js +0 -8
- package/runner/src/gate-run-lease-probe.ts +0 -38
- package/src/db/claude-catalog-reconcile.ts +0 -27
- package/src/routing-model-aliases.ts +0 -21
|
@@ -8,9 +8,9 @@ import { listEntitiesForIssue, listIssuesForEntity } from "../db/issue-associati
|
|
|
8
8
|
import {
|
|
9
9
|
getSuccessfulIssueLifecycleAction,
|
|
10
10
|
listSuccessfulCloseActionsForCommit,
|
|
11
|
-
upsertIssueLifecycleAction,
|
|
12
11
|
} from "../db/issue-lifecycle";
|
|
13
|
-
import { getIssueRef
|
|
12
|
+
import { getIssueRef } from "../db/issue-refs";
|
|
13
|
+
import { resolveIssueRefByExternalKeyAsync, resolveIssueRefFromStringAsync, updateIssueRefAsync, upsertIssueLifecycleActionAsync } from "../db/issue-refs-async";
|
|
14
14
|
import { associateIssue } from "../db/issue-associations";
|
|
15
15
|
import { resolveIssueTrackerAdapter } from "../issue-tracker";
|
|
16
16
|
import { defaultIssueRepo } from "../config";
|
|
@@ -141,42 +141,53 @@ const ISSUE_REF_AT_START_RE = /^(https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z
|
|
|
141
141
|
const REF_SEPARATOR_RE = /^(?:\s*,\s*|\s+(?:and|&)\s+)/i;
|
|
142
142
|
|
|
143
143
|
// A run of issue refs ("#1, #2 and owner/repo#3") starting at the head of `segment`.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
function collectRefRun(segment: string, fallbackDefaultRepo: string | undefined, into: IssueRef[]): void {
|
|
144
|
+
function refRun(segment: string): string[] {
|
|
145
|
+
const refs: string[] = [];
|
|
147
146
|
let rest = segment;
|
|
148
147
|
while (rest.trim()) {
|
|
149
148
|
rest = rest.trimStart();
|
|
150
149
|
const refMatch = rest.match(ISSUE_REF_AT_START_RE);
|
|
151
150
|
if (!refMatch) break;
|
|
152
|
-
|
|
153
|
-
into.push(resolveIssueRefFromString({ ref: refMatch[1]!, defaultRepo: fallbackDefaultRepo }));
|
|
154
|
-
} catch {
|
|
155
|
-
// Invalid or bare-without-default refs are ignored; the land itself must never fail here.
|
|
156
|
-
}
|
|
151
|
+
refs.push(refMatch[1]!);
|
|
157
152
|
rest = rest.slice(refMatch[0].length);
|
|
158
153
|
const separator = rest.match(REF_SEPARATOR_RE);
|
|
159
154
|
if (!separator) break;
|
|
160
155
|
rest = rest.slice(separator[0].length);
|
|
161
156
|
}
|
|
157
|
+
return refs;
|
|
162
158
|
}
|
|
163
159
|
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
// Bare refs resolve against `fallbackDefaultRepo`; unresolvable ones are skipped so a land never fails here.
|
|
161
|
+
async function resolveRefRuns(segments: Iterable<string>, fallbackDefaultRepo: string | undefined): Promise<IssueRef[]> {
|
|
166
162
|
const refs: IssueRef[] = [];
|
|
167
|
-
for (const
|
|
163
|
+
for (const segment of segments) {
|
|
164
|
+
for (const ref of refRun(segment)) {
|
|
165
|
+
try {
|
|
166
|
+
refs.push(await resolveIssueRefFromStringAsync({ ref, defaultRepo: fallbackDefaultRepo }));
|
|
167
|
+
} catch {
|
|
168
|
+
// Invalid or bare-without-default refs are ignored; the land itself must never fail here.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
168
172
|
return issueRefMap(refs);
|
|
169
173
|
}
|
|
170
174
|
|
|
175
|
+
export function parseClosingIssueRefsAsync(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): Promise<IssueRef[]> {
|
|
176
|
+
return resolveRefRuns(text ? [...text.matchAll(CLOSING_KEYWORD_RE)].map((match) => match[1] ?? "") : [], fallbackDefaultRepo);
|
|
177
|
+
}
|
|
178
|
+
|
|
171
179
|
// Parenthesized backlink groups: the trailing `(#NNN)` convention agents (and a squash merge)
|
|
172
180
|
// stamp on a subject without a closing keyword. Non-issue parens (conventional-commit scopes
|
|
173
181
|
// like `fix(tokens)`, notes like `(see also)`) carry no `#` and fall out.
|
|
174
182
|
const BACKLINK_GROUP_RE = /\(([^()]*#[0-9]+[^()]*)\)/g;
|
|
175
183
|
|
|
176
|
-
export function
|
|
177
|
-
|
|
184
|
+
export function parseBacklinkIssueRefsAsync(text: string | undefined, fallbackDefaultRepo = defaultIssueRepo()): Promise<IssueRef[]> {
|
|
185
|
+
return resolveRefRuns(text ? [...text.matchAll(BACKLINK_GROUP_RE)].map((group) => group[1] ?? "") : [], fallbackDefaultRepo);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function eachSubject(subjects: string[], parse: (text: string) => Promise<IssueRef[]>): Promise<IssueRef[]> {
|
|
178
189
|
const refs: IssueRef[] = [];
|
|
179
|
-
for (const
|
|
190
|
+
for (const subject of subjects) refs.push(...await parse(subject));
|
|
180
191
|
return issueRefMap(refs);
|
|
181
192
|
}
|
|
182
193
|
|
|
@@ -190,13 +201,13 @@ async function resolveIssueRefsForLand(input: LandedIssueLifecycleInput, links:
|
|
|
190
201
|
const associated = issueRefMap(links.flatMap((link) => link.issues.filter((issue) => issue.relation === "delivers").map((issue) => issue.ref)));
|
|
191
202
|
const fallbackDefaultRepo = await landDefaultRepo(input.workspace);
|
|
192
203
|
const subjects = landedSubjectTexts(input);
|
|
193
|
-
const keyworded =
|
|
204
|
+
const keyworded = await eachSubject(subjects, (subject) => parseClosingIssueRefsAsync(subject, fallbackDefaultRepo));
|
|
194
205
|
// A commit declaration is authored at the change boundary, unlike a Task association
|
|
195
206
|
// which can legitimately be historical or span several work items. It is the sole close
|
|
196
207
|
// target when present (#1694).
|
|
197
208
|
if (keyworded.length > 0) return { source: "commit", refs: keyworded, declared: keyworded };
|
|
198
209
|
if (!input.prLand && links.some((link) => link.buildLike)) {
|
|
199
|
-
const backlinks =
|
|
210
|
+
const backlinks = (await eachSubject(subjects, (subject) => parseBacklinkIssueRefsAsync(subject, fallbackDefaultRepo)))
|
|
200
211
|
.filter((ref) => canCloseBacklink(ref, links));
|
|
201
212
|
if (backlinks.length) return { source: "backlink", refs: backlinks, declared: backlinks };
|
|
202
213
|
}
|
|
@@ -348,7 +359,7 @@ async function reconcileClosedIssueRefBeforeSkip(issueRef: IssueRef, opts: Issue
|
|
|
348
359
|
// LOW-4 (#2520 review) — thread the live closeReason through: a completed close observed
|
|
349
360
|
// here was dropped, so the cached ref stayed reason-less and could never count as a
|
|
350
361
|
// completed close. updateIssueRef preserves the cached reason when live carries none.
|
|
351
|
-
issueRef:
|
|
362
|
+
issueRef: await updateIssueRefAsync(issueRef.id, { status, title: live.title, closeReason: live.closeReason, statusSyncedAt: Date.now() }) ?? issueRef,
|
|
352
363
|
liveStatus: live.status,
|
|
353
364
|
};
|
|
354
365
|
} catch (error) {
|
|
@@ -406,7 +417,7 @@ export async function closeIssueRefForLand(
|
|
|
406
417
|
logFailure(opts, `issue lifecycle epic-guard title lookup returned no title for ${latest.id} on ${key}`);
|
|
407
418
|
return { issueRef: latest, action: "skipped", reason: "title-unavailable", landKey: key };
|
|
408
419
|
}
|
|
409
|
-
latest =
|
|
420
|
+
latest = await updateIssueRefAsync(latest.id, { title, statusSyncedAt: Date.now() }) ?? latest;
|
|
410
421
|
} catch (error) {
|
|
411
422
|
logFailure(opts, `issue lifecycle epic-guard title lookup failed for ${latest.id} on ${key}: ${errMessage(error)}`);
|
|
412
423
|
return { issueRef: latest, action: "skipped", reason: "title-unavailable", landKey: key };
|
|
@@ -423,22 +434,25 @@ export async function closeIssueRefForLand(
|
|
|
423
434
|
return { issueRef: latest, action: "skipped", reason: "epic-issue", landKey: key };
|
|
424
435
|
}
|
|
425
436
|
|
|
437
|
+
const closeAction = {
|
|
438
|
+
issueRefId: latest.id,
|
|
439
|
+
action: "close" as const,
|
|
440
|
+
landKey: key,
|
|
441
|
+
workspaceId: input.workspace.id,
|
|
442
|
+
branch: input.workspace.branch,
|
|
443
|
+
baseRef: input.workspace.baseRef ?? "main",
|
|
444
|
+
commitSha: input.mergedSha,
|
|
445
|
+
commitSubject: input.subject,
|
|
446
|
+
agentId: input.workspace.ownerAgentId,
|
|
447
|
+
source,
|
|
448
|
+
};
|
|
426
449
|
let openSubIssues: IssueTrackerIssueSummary[];
|
|
427
450
|
try {
|
|
428
451
|
openSubIssues = await listOpenSubIssuesForLand(latest, opts);
|
|
429
452
|
} catch (error) {
|
|
430
453
|
const message = `sub-issue lookup failed: ${errMessage(error)}`;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
action: "close",
|
|
434
|
-
landKey: key,
|
|
435
|
-
workspaceId: input.workspace.id,
|
|
436
|
-
branch: input.workspace.branch,
|
|
437
|
-
baseRef: input.workspace.baseRef ?? "main",
|
|
438
|
-
commitSha: input.mergedSha,
|
|
439
|
-
commitSubject: input.subject,
|
|
440
|
-
agentId: input.workspace.ownerAgentId,
|
|
441
|
-
source,
|
|
454
|
+
await upsertIssueLifecycleActionAsync({
|
|
455
|
+
...closeAction,
|
|
442
456
|
status: "failed",
|
|
443
457
|
error: message,
|
|
444
458
|
});
|
|
@@ -458,18 +472,9 @@ export async function closeIssueRefForLand(
|
|
|
458
472
|
const comment = closeAuditComment(input);
|
|
459
473
|
try {
|
|
460
474
|
await adapterFor(latest, opts).close(latest, { comment, labels: ["fixed"], closeReason: "completed" });
|
|
461
|
-
const closed =
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
action: "close",
|
|
465
|
-
landKey: key,
|
|
466
|
-
workspaceId: input.workspace.id,
|
|
467
|
-
branch: input.workspace.branch,
|
|
468
|
-
baseRef: input.workspace.baseRef ?? "main",
|
|
469
|
-
commitSha: input.mergedSha,
|
|
470
|
-
commitSubject: input.subject,
|
|
471
|
-
agentId: input.workspace.ownerAgentId,
|
|
472
|
-
source,
|
|
475
|
+
const closed = await updateIssueRefAsync(latest.id, { status: "closed", closeReason: "completed", statusSyncedAt: Date.now() }) ?? latest;
|
|
476
|
+
await upsertIssueLifecycleActionAsync({
|
|
477
|
+
...closeAction,
|
|
473
478
|
comment,
|
|
474
479
|
status: "succeeded",
|
|
475
480
|
});
|
|
@@ -494,17 +499,8 @@ export async function closeIssueRefForLand(
|
|
|
494
499
|
return { issueRef: closed, action: "closed", source, landKey: key };
|
|
495
500
|
} catch (error) {
|
|
496
501
|
const message = errMessage(error);
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
action: "close",
|
|
500
|
-
landKey: key,
|
|
501
|
-
workspaceId: input.workspace.id,
|
|
502
|
-
branch: input.workspace.branch,
|
|
503
|
-
baseRef: input.workspace.baseRef ?? "main",
|
|
504
|
-
commitSha: input.mergedSha,
|
|
505
|
-
commitSubject: input.subject,
|
|
506
|
-
agentId: input.workspace.ownerAgentId,
|
|
507
|
-
source,
|
|
502
|
+
await upsertIssueLifecycleActionAsync({
|
|
503
|
+
...closeAction,
|
|
508
504
|
comment,
|
|
509
505
|
status: "failed",
|
|
510
506
|
error: message,
|
|
@@ -564,15 +560,15 @@ export async function handleLandedIssueLifecycle(input: LandedIssueLifecycleInpu
|
|
|
564
560
|
return { source: resolved.source, closed, reopened, skipped: [], mismatches };
|
|
565
561
|
}
|
|
566
562
|
|
|
567
|
-
function resolveReopenRef(input: ReopenIssueLifecycleInput): IssueRef {
|
|
563
|
+
async function resolveReopenRef(input: ReopenIssueLifecycleInput): Promise<IssueRef> {
|
|
568
564
|
if (input.issueRefId) {
|
|
569
565
|
const issueRef = getIssueRef(input.issueRefId);
|
|
570
566
|
if (!issueRef) throw new Error(`issue ref ${input.issueRefId} not found`);
|
|
571
567
|
return issueRef;
|
|
572
568
|
}
|
|
573
|
-
if (input.ref) return
|
|
569
|
+
if (input.ref) return resolveIssueRefFromStringAsync({ ref: input.ref, defaultRepo: input.defaultRepo ?? input.repo, provider: input.provider, title: input.title });
|
|
574
570
|
if (input.provider && input.repo && input.externalKey) {
|
|
575
|
-
return
|
|
571
|
+
return resolveIssueRefByExternalKeyAsync({
|
|
576
572
|
provider: input.provider,
|
|
577
573
|
repo: input.repo,
|
|
578
574
|
externalKey: input.externalKey,
|
|
@@ -584,14 +580,14 @@ function resolveReopenRef(input: ReopenIssueLifecycleInput): IssueRef {
|
|
|
584
580
|
}
|
|
585
581
|
|
|
586
582
|
export async function reopenIssueLifecycle(input: ReopenIssueLifecycleInput, opts: IssueLifecycleOptions = {}): Promise<ReopenIssueLifecycleResult> {
|
|
587
|
-
const issueRef = resolveReopenRef(input);
|
|
583
|
+
const issueRef = await resolveReopenRef(input);
|
|
588
584
|
const key = manualReopenKey(issueRef);
|
|
589
585
|
if (issueRef.status === "open") return { issueRef, action: "skipped", reason: "already-open", landKey: key };
|
|
590
586
|
const comment = input.comment ?? "Reopened by Humwick issue lifecycle.";
|
|
591
587
|
try {
|
|
592
588
|
await adapterFor(issueRef, opts).reopen(issueRef, { comment });
|
|
593
|
-
const opened =
|
|
594
|
-
|
|
589
|
+
const opened = await updateIssueRefAsync(issueRef.id, { status: "open", statusSyncedAt: Date.now() }) ?? issueRef;
|
|
590
|
+
await upsertIssueLifecycleActionAsync({
|
|
595
591
|
issueRefId: issueRef.id,
|
|
596
592
|
action: "reopen",
|
|
597
593
|
landKey: key,
|
|
@@ -604,7 +600,7 @@ export async function reopenIssueLifecycle(input: ReopenIssueLifecycleInput, opt
|
|
|
604
600
|
return { issueRef: opened, action: "reopened", landKey: key };
|
|
605
601
|
} catch (error) {
|
|
606
602
|
const message = errMessage(error);
|
|
607
|
-
|
|
603
|
+
await upsertIssueLifecycleActionAsync({
|
|
608
604
|
issueRefId: issueRef.id,
|
|
609
605
|
action: "reopen",
|
|
610
606
|
landKey: key,
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
+
import { deleteIssueRefAsync, resolveIssueRefByExternalKeyAsync, resolveIssueRefFromStringAsync, updateIssueRefAsync, upsertIssueRefAsync } from "../db/issue-refs-async";
|
|
1
2
|
import {
|
|
2
|
-
deleteIssueRef,
|
|
3
3
|
getIssueRef,
|
|
4
4
|
listIssueRefs,
|
|
5
|
-
resolveIssueRefByExternalKey,
|
|
6
|
-
resolveIssueRefFromString,
|
|
7
|
-
updateIssueRef,
|
|
8
|
-
upsertIssueRef,
|
|
9
5
|
type ListIssueRefsOptions,
|
|
10
6
|
type ResolveIssueRefByExternalKeyInput,
|
|
11
7
|
type ResolveIssueRefFromStringInput,
|
|
@@ -15,20 +11,20 @@ import {
|
|
|
15
11
|
|
|
16
12
|
export { getIssueRef, listIssueRefs, type ListIssueRefsOptions };
|
|
17
13
|
|
|
18
|
-
export function
|
|
19
|
-
return
|
|
14
|
+
export function upsertIssueRefMappingAsync(input: UpsertIssueRefInput) {
|
|
15
|
+
return upsertIssueRefAsync(input);
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
export function
|
|
23
|
-
return "ref" in input ?
|
|
18
|
+
export function resolveIssueRefMappingAsync(input: ResolveIssueRefByExternalKeyInput | ResolveIssueRefFromStringInput) {
|
|
19
|
+
return "ref" in input ? resolveIssueRefFromStringAsync(input) : resolveIssueRefByExternalKeyAsync(input);
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
export function
|
|
27
|
-
return
|
|
22
|
+
export function patchIssueRefMappingAsync(id: string, input: UpdateIssueRefInput) {
|
|
23
|
+
return updateIssueRefAsync(id, input);
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
export function
|
|
31
|
-
return
|
|
26
|
+
export function deleteIssueRefMappingAsync(id: string) {
|
|
27
|
+
return deleteIssueRefAsync(id);
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
// --- Caller-supplied issue-ref normalization (#2214/#1930) ---------------------------------
|
|
@@ -24,6 +24,8 @@ const releaseFile = process.env.CUTOVER_CHAOS_RELEASE_FILE;
|
|
|
24
24
|
const resultFile = process.env.CUTOVER_CHAOS_RESULT_FILE;
|
|
25
25
|
const owner = process.env.CUTOVER_CHAOS_OWNER ?? `worker-${process.pid}`;
|
|
26
26
|
const leaseMs = Number(process.env.CUTOVER_CHAOS_LEASE_MS ?? "400");
|
|
27
|
+
let now = Number(required("CUTOVER_CHAOS_NOW"));
|
|
28
|
+
const clock = () => now;
|
|
27
29
|
|
|
28
30
|
function required(name: string): string {
|
|
29
31
|
const value = relayEnv(process.env, name);
|
|
@@ -60,7 +62,7 @@ function timerInput(idSuffix = String(process.pid)): CreateScheduledTimerRowInpu
|
|
|
60
62
|
createdByKind: "system",
|
|
61
63
|
createdByScopes: ["message:send"],
|
|
62
64
|
deliveryFrom: "system",
|
|
63
|
-
nextRunAt:
|
|
65
|
+
nextRunAt: now + 100_000,
|
|
64
66
|
scheduleKind: "once",
|
|
65
67
|
scheduleDef: { afterMs: 100_000 },
|
|
66
68
|
body: `cutover-${idSuffix}`,
|
|
@@ -87,6 +89,7 @@ async function main(): Promise<void> {
|
|
|
87
89
|
};
|
|
88
90
|
const c = new TimerMessageCutoverCoordinator({
|
|
89
91
|
ownerId: owner,
|
|
92
|
+
now: clock,
|
|
90
93
|
leaseMs,
|
|
91
94
|
tickMs: 25,
|
|
92
95
|
quiesceTimeoutMs: 5_000,
|
|
@@ -94,7 +97,7 @@ async function main(): Promise<void> {
|
|
|
94
97
|
faults,
|
|
95
98
|
});
|
|
96
99
|
await c.requestQuiesce();
|
|
97
|
-
if (boundary === "after-renewal")
|
|
100
|
+
if (boundary === "after-renewal") now += Math.ceil(leaseMs * 0.75);
|
|
98
101
|
await c.tick();
|
|
99
102
|
await c.tick();
|
|
100
103
|
await c.tick();
|
|
@@ -112,7 +115,7 @@ async function main(): Promise<void> {
|
|
|
112
115
|
const ticks = phaseTicks[boundary];
|
|
113
116
|
if (ticks === undefined) throw new Error(`unknown deadline-watch phase ${boundary}`);
|
|
114
117
|
const c = new TimerMessageCutoverCoordinator({
|
|
115
|
-
ownerId: owner, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
|
|
118
|
+
ownerId: owner, now: clock, leaseMs: 10_000, tickMs: 25, quiesceTimeoutMs: 8_000, worstCaseDrainTransferMs: 10_000,
|
|
116
119
|
});
|
|
117
120
|
await c.requestQuiesce();
|
|
118
121
|
for (let i = 0; i < ticks; i++) await c.tick();
|
|
@@ -124,14 +127,14 @@ async function main(): Promise<void> {
|
|
|
124
127
|
|
|
125
128
|
if (command === "claim-bound") {
|
|
126
129
|
const c = new TimerMessageCutoverCoordinator({
|
|
127
|
-
ownerId: owner, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
|
|
130
|
+
ownerId: owner, now: clock, leaseMs: 1_000, tickMs: 25, quiesceTimeoutMs: 300, worstCaseDrainTransferMs: 1_000,
|
|
128
131
|
});
|
|
129
132
|
await c.requestQuiesce();
|
|
130
133
|
await c.tick();
|
|
131
134
|
await c.tick();
|
|
132
135
|
const drain = await c.tick();
|
|
133
136
|
if (boundary === "held") {
|
|
134
|
-
|
|
137
|
+
now += 340;
|
|
135
138
|
await c.tick();
|
|
136
139
|
await c.tick();
|
|
137
140
|
}
|
|
@@ -141,7 +144,7 @@ async function main(): Promise<void> {
|
|
|
141
144
|
|
|
142
145
|
if (command === "recover") {
|
|
143
146
|
const c = new TimerMessageCutoverCoordinator({
|
|
144
|
-
ownerId: owner, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
|
|
147
|
+
ownerId: owner, now: clock, leaseMs, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
|
|
145
148
|
});
|
|
146
149
|
const outcomes = [];
|
|
147
150
|
for (let i = 0; i < 5; i++) outcomes.push(await c.tick());
|
|
@@ -152,7 +155,7 @@ async function main(): Promise<void> {
|
|
|
152
155
|
if (command === "close-gate") {
|
|
153
156
|
marker();
|
|
154
157
|
const c = new TimerMessageCutoverCoordinator({
|
|
155
|
-
ownerId: owner, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
|
|
158
|
+
ownerId: owner, now: clock, leaseMs: 5_000, tickMs: 25, quiesceTimeoutMs: 5_000, worstCaseDrainTransferMs: 6_000,
|
|
156
159
|
});
|
|
157
160
|
const acquired = await c.requestQuiesce();
|
|
158
161
|
const outcome = acquired ? await c.tick() : null;
|
|
@@ -322,7 +322,7 @@ export class TimerIngressRejectedError extends Error {
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
export function createTimerIngress(options: TimerIngressOptions): TimerIngress {
|
|
325
|
-
installTimerIngressSchema();
|
|
325
|
+
installTimerIngressSchema({ ifNotInstalled: true });
|
|
326
326
|
const apply = options.apply;
|
|
327
327
|
const ownerId = options.ownerId ?? `${process.pid}-${randomUUID()}`;
|
|
328
328
|
const now = options.now ?? Date.now;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { repoDefaultBranch } from "@humwick/sdk/land-gates";
|
|
2
|
+
import { withWorkspaceTaskOwnerRoles } from "../task-owner-notices";
|
|
1
3
|
// #1713 — the coordinator's "landed" signal, derived from GIT ANCESTRY rather than a command's
|
|
2
4
|
// reported status. The observed bug: a worker delivers `complete`, the merge executor stalls, and
|
|
3
5
|
// the coordinator is nonetheless told the work is "complete"/"landed" while `origin/main` is
|
|
@@ -113,7 +115,8 @@ const realVerifier: AncestryVerifier = async (repoRoot, sha, targetRef) => {
|
|
|
113
115
|
// source of truth; a fetch failure/timeout is swallowed — the checks below then run against
|
|
114
116
|
// whatever refs are already local, preserving the prior fail-closed behavior exactly.
|
|
115
117
|
const remoteBranch = ref.startsWith("origin/") ? ref.slice("origin/".length) : ref;
|
|
116
|
-
|
|
118
|
+
const fetched = remoteBranch ? await gitStatus(root, ["fetch", "--quiet", "origin", `+refs/heads/${remoteBranch}:refs/remotes/origin/${remoteBranch}`], GIT_FETCH_TIMEOUT_MS) : null;
|
|
119
|
+
if (ref.startsWith("origin/") && fetched !== 0) return "unresolvable";
|
|
117
120
|
// The sha itself must resolve to a commit in this repo, else ancestry is unknowable (not a
|
|
118
121
|
// negative). `--verify --quiet ^{commit}` distinguishes "sha absent" from "sha present but not
|
|
119
122
|
// on the ref".
|
|
@@ -246,6 +249,18 @@ export async function verifyLandedAncestry(repoRoot: string | undefined, sha: st
|
|
|
246
249
|
}
|
|
247
250
|
}
|
|
248
251
|
|
|
252
|
+
/** Notice proof requires the freshly fetched remote; a local-only main never qualifies (#3761). */
|
|
253
|
+
export async function verifyPublishedLand(repoRoot: string | undefined, sha: string | undefined, baseRef?: string): Promise<LandedAncestryVerdict> {
|
|
254
|
+
if (!repoRoot || !sha?.trim()) return "unresolvable";
|
|
255
|
+
try {
|
|
256
|
+
const base = baseRef ?? await repoDefaultBranch(async args => {
|
|
257
|
+
const result = await gitOutput(repoRoot, args);
|
|
258
|
+
return { ok: result?.exitCode === 0, stdout: result?.stdout ?? "", unavailable: !result };
|
|
259
|
+
});
|
|
260
|
+
return base ? await verifyLandedAncestry(repoRoot, sha, base.startsWith("origin/") ? base : `origin/${base}`) : "unresolvable";
|
|
261
|
+
} catch { return "unresolvable"; }
|
|
262
|
+
}
|
|
263
|
+
|
|
249
264
|
// #2718 — the landing host's own fresh-fetch ancestry check (`landVerifiedSha`) is ref-backed proof
|
|
250
265
|
// that works wherever the repo lives; the hub's check needs a checkout of the same remote on its own
|
|
251
266
|
// disk. Without a matching host verdict, fall back to the hub check (an absent path never confirms).
|
|
@@ -366,6 +381,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
|
|
|
366
381
|
const { subject, body } = landUnconfirmedText(input);
|
|
367
382
|
|
|
368
383
|
const payload = {
|
|
384
|
+
verified: false,
|
|
369
385
|
kind: "workspace.land-unconfirmed",
|
|
370
386
|
notification: "land-unconfirmed",
|
|
371
387
|
landOutcome: kind,
|
|
@@ -397,7 +413,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
|
|
|
397
413
|
metadata: { source: "server", maintenanceJobId: "workspace-land-unconfirmed", workspaceId: workspace.id, mergeCommandId: input.commandId, landOutcome: kind },
|
|
398
414
|
});
|
|
399
415
|
|
|
400
|
-
const recipients =
|
|
416
|
+
const recipients = withWorkspaceTaskOwnerRoles([owner, coordinator, fallback], workspace);
|
|
401
417
|
if (recipients.length === 0) return;
|
|
402
418
|
routeNotification({
|
|
403
419
|
type: "workspace.land_unconfirmed",
|
|
@@ -407,7 +423,7 @@ export function notifyLandUnconfirmed(input: LandUnconfirmedInput): void {
|
|
|
407
423
|
forceDelivery: true,
|
|
408
424
|
message: {
|
|
409
425
|
subject,
|
|
410
|
-
body
|
|
426
|
+
body: `${body} verified: false.`,
|
|
411
427
|
payload,
|
|
412
428
|
idempotencyKey: kind === "merge-failed" ? landFailureEventKey(workspace, input.commandId)
|
|
413
429
|
: `land-unconfirmed:${workspace.id}:${input.commandId ?? "nocmd"}:${kind}`,
|
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
// (relay_project_write_entry, relay_store_memory, the project-entry REST routes) resolves the
|
|
3
3
|
// vocabulary and the stored value here, so the closed vocabulary cannot drift between them.
|
|
4
4
|
|
|
5
|
-
import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea,
|
|
6
|
-
import {
|
|
7
|
-
import { countActivePinnedEntriesInArea, getAgent,
|
|
8
|
-
import { localProjectGit, readHostProjectFiles } from "../project-roles";
|
|
9
|
-
import { hostIsRelayHost } from "../relay-host-locality";
|
|
5
|
+
import { AREAS_FILE, SYSTEM_AREAS, SYSTEM_AREA_DESCRIPTIONS, areasAtCommit, injectsAtStartup, isSystemArea, type AreaDefinition, type AreasConfig } from "@humwick/sdk/areas";
|
|
6
|
+
import { stringValue } from "@humwick/sdk";
|
|
7
|
+
import { countActivePinnedEntriesInArea, getAgent, ValidationError } from "../db";
|
|
10
8
|
import type { Project, ProjectEntry } from "../types";
|
|
11
9
|
import type { AuthContext } from "./auth-context";
|
|
12
10
|
import { callerHost, callerProjectId } from "./memory-identity";
|
|
13
|
-
import {
|
|
11
|
+
import { readProjectConfig, readProjectConfigFirst, type ConfigLookup, type ProjectCheckout } from "./project-config-read";
|
|
14
12
|
|
|
15
13
|
export const MAX_ENTRY_AREAS = 4;
|
|
16
14
|
const PROJECT_WIDE = "project-wide";
|
|
@@ -47,42 +45,15 @@ export function areaCallerFromCtx(ctx: AuthContext): AreaCaller {
|
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
/** A checkout to read areas.json from: a cwd on a host (a hostname or a registered orchestrator). */
|
|
50
|
-
export
|
|
51
|
-
cwd: string;
|
|
52
|
-
host?: string;
|
|
53
|
-
orchestratorId?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type AreasLookup = AreasAtCommit | { status: "unreadable"; error: string };
|
|
48
|
+
export type AreasCheckout = ProjectCheckout;
|
|
57
49
|
|
|
58
|
-
|
|
50
|
+
export type AreasLookup = ConfigLookup<AreasConfig>;
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
if (loggedProblems.has(key)) return;
|
|
62
|
-
if (loggedProblems.size >= 1000) loggedProblems.clear();
|
|
63
|
-
loggedProblems.add(key);
|
|
64
|
-
console.warn(`[memory-areas] ${AREAS_FILE} for ${key}: ${problem}; accepting system areas only`);
|
|
65
|
-
}
|
|
52
|
+
const ACCEPTING_SYSTEM_AREAS = "accepting system areas only";
|
|
66
53
|
|
|
67
|
-
/** #3635 — the areas.json at the checkout's default branch
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
export async function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
|
|
71
|
-
const where = `${checkout.orchestratorId ?? checkout.host ?? "local"}:${checkout.cwd}`;
|
|
72
|
-
try {
|
|
73
|
-
const lookup = !checkout.orchestratorId && hostIsRelayHost(checkout.host)
|
|
74
|
-
? await readAreasAtDefaultBranch(localProjectGit(checkout.cwd))
|
|
75
|
-
: areasAtCommit(await readHostProjectFiles(
|
|
76
|
-
resolveProjectFilesHost({ subject: AREAS_FILE, cwd: checkout.cwd, orchestratorId: checkout.orchestratorId, preferHost: checkout.host }),
|
|
77
|
-
checkout.cwd, { paths: [AREAS_FILE] }, AREAS_FILE,
|
|
78
|
-
));
|
|
79
|
-
if (lookup.status === "invalid") logProblemOnce(`${where}@${lookup.sha}`, `invalid: ${lookup.error}`);
|
|
80
|
-
return lookup;
|
|
81
|
-
} catch (err) {
|
|
82
|
-
const error = errMessage(err);
|
|
83
|
-
logProblemOnce(where, `unreadable: ${error}`);
|
|
84
|
-
return { status: "unreadable", error };
|
|
85
|
-
}
|
|
54
|
+
/** #3635 — the areas.json at the checkout's default branch, through the shared project-config reader. */
|
|
55
|
+
export function readAreasConfig(checkout: AreasCheckout): Promise<AreasLookup> {
|
|
56
|
+
return readProjectConfig(checkout, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
|
|
86
57
|
}
|
|
87
58
|
|
|
88
59
|
function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): string {
|
|
@@ -94,18 +65,11 @@ function invalidDetail(lookup: Extract<AreasLookup, { status: "invalid" }>): str
|
|
|
94
65
|
* answers. Every other cell (base/provider/host) takes system areas only, optionally. */
|
|
95
66
|
export async function resolveAreaVocabulary(anchor: Project, caller: AreaCaller | undefined): Promise<AreaVocabulary> {
|
|
96
67
|
if (anchor.scope !== "project") return { pathAreas: [], required: false, pathAreasMissing: "not-project" };
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
for (const checkout of checkouts) {
|
|
103
|
-
const lookup = await readAreasConfig(checkout);
|
|
104
|
-
if (lookup.status === "found") return { pathAreas: lookup.config.areas, required: true };
|
|
105
|
-
if (lookup.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
|
|
106
|
-
if (lookup.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
|
|
107
|
-
failures.push(lookup.error);
|
|
108
|
-
}
|
|
68
|
+
const preferred = caller?.cwd && caller.projectId === anchor.id ? [{ cwd: caller.cwd, host: caller.host }] : [];
|
|
69
|
+
const { lookup, failures } = await readProjectConfigFirst(anchor.id, preferred, AREAS_FILE, areasAtCommit, ACCEPTING_SYSTEM_AREAS);
|
|
70
|
+
if (lookup?.status === "found") return { pathAreas: lookup.config.areas, required: true };
|
|
71
|
+
if (lookup?.status === "absent") return { pathAreas: [], required: false, pathAreasMissing: "undeclared" };
|
|
72
|
+
if (lookup?.status === "invalid") return { pathAreas: [], required: false, pathAreasMissing: "invalid", pathAreasDetail: invalidDetail(lookup) };
|
|
109
73
|
return { pathAreas: [], required: false, pathAreasMissing: "unreadable", pathAreasDetail: failures.join("; ") || "the project has no checkout to read it from" };
|
|
110
74
|
}
|
|
111
75
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// #3716 — workflow effects read CI episodes without depending on workflow launch or recovery.
|
|
2
|
+
import { ValidationError } from "../db/connection";
|
|
3
|
+
import { computeCiFailureFingerprint } from "../ci/incidents";
|
|
4
|
+
import type { CiShaObservation } from "../db/ci-incidents";
|
|
5
|
+
import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
|
|
6
|
+
import { getGateway } from "../db/gateway";
|
|
7
|
+
import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
|
|
8
|
+
import { planNoticeTarget } from "../db/plan-completion";
|
|
9
|
+
import { notifySystemMessage } from "../notify";
|
|
10
|
+
import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
11
|
+
|
|
12
|
+
/** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
|
|
13
|
+
export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
|
|
14
|
+
const ids: string[] = [];
|
|
15
|
+
for (const { workflow, row } of rows) {
|
|
16
|
+
if (!row) return [];
|
|
17
|
+
const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
|
|
18
|
+
if (!incident) return [];
|
|
19
|
+
if (incident.openedHeadSha !== row.headSha) {
|
|
20
|
+
if (!row.failingJobs?.length) return [];
|
|
21
|
+
const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
|
|
22
|
+
if (fingerprint !== incident.fingerprint) return [];
|
|
23
|
+
}
|
|
24
|
+
ids.push(incident.id);
|
|
25
|
+
}
|
|
26
|
+
return [...new Set(ids)].sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
|
|
30
|
+
export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
|
|
31
|
+
const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
|
|
32
|
+
if (!owner || owner.audit) return undefined;
|
|
33
|
+
let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
|
|
34
|
+
const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
|
|
35
|
+
if (!result && incidentIds.length && typeof payload.landedSha === "string") {
|
|
36
|
+
try {
|
|
37
|
+
block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// A superseded attempt must still send its normal CI notice (#3660).
|
|
40
|
+
if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
|
|
45
|
+
const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
|
|
46
|
+
const to = plan && planNoticeTarget(plan);
|
|
47
|
+
if (!to) return undefined;
|
|
48
|
+
for (const incidentId of block.incidentIds) {
|
|
49
|
+
notifySystemMessage(to, {
|
|
50
|
+
subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
|
|
51
|
+
body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
|
|
52
|
+
idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
|
|
53
|
+
payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
|
|
57
|
+
}
|
|
@@ -1,62 +1,9 @@
|
|
|
1
|
-
// #3660 —
|
|
2
|
-
import {
|
|
3
|
-
import { computeCiFailureFingerprint } from "../ci/incidents";
|
|
4
|
-
import type { CiShaObservation } from "../db/ci-incidents";
|
|
1
|
+
// #3660 — clearance reuses the original run's CI resume path.
|
|
2
|
+
import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
5
3
|
import type { StoredPlanCiBlock } from "../db/plan-ci-blocks";
|
|
6
4
|
import { getGateway } from "../db/gateway";
|
|
7
|
-
import { getPlanGraphProjectionAsync } from "../db/plan-graph-async";
|
|
8
|
-
import { planNoticeTarget } from "../db/plan-completion";
|
|
9
|
-
import { notifySystemMessage } from "../notify";
|
|
10
|
-
import type { WorkflowRunRecord } from "@humwick/kernel/workflow.pilot";
|
|
11
5
|
import { resumeWorkflowRun } from "./workflow-runs";
|
|
12
6
|
|
|
13
|
-
/** Same watch and landed SHA, or the same failing jobs: an unrelated red main must not quiet this failure. */
|
|
14
|
-
export async function correlatedCiIncidents(rows: Array<{ workflow: string; row?: CiShaObservation }>): Promise<string[]> {
|
|
15
|
-
const ids: string[] = [];
|
|
16
|
-
for (const { workflow, row } of rows) {
|
|
17
|
-
if (!row) return [];
|
|
18
|
-
const incident = await getGateway().read("ciIncidents.activeForWatch", { watchKey: row.watchKey });
|
|
19
|
-
if (!incident) return [];
|
|
20
|
-
if (incident.openedHeadSha !== row.headSha) {
|
|
21
|
-
if (!row.failingJobs?.length) return [];
|
|
22
|
-
const fingerprint = computeCiFailureFingerprint({ workflow, failingRuns: [{ id: "sha", status: "completed", jobs: row.failingJobs.map((job) => ({ id: job.name, name: job.name })) }], conclusion: "failure" });
|
|
23
|
-
if (fingerprint !== incident.fingerprint) return [];
|
|
24
|
-
}
|
|
25
|
-
ids.push(incident.id);
|
|
26
|
-
}
|
|
27
|
-
return [...new Set(ids)].sort();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Return a quiet receipt only after both the blocked attempt and the actionable plan notice are durable. */
|
|
31
|
-
export async function planCiNotice(run: WorkflowRunRecord, payload: Record<string, unknown>, result = false): Promise<Record<string, unknown> | undefined> {
|
|
32
|
-
const owner = await getGateway().read("planDispatch.owner", { taskId: run.taskId });
|
|
33
|
-
if (!owner || owner.audit) return undefined;
|
|
34
|
-
let block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
|
|
35
|
-
const incidentIds = Array.isArray(payload.incidentIds) ? payload.incidentIds.filter((id): id is string => typeof id === "string") : [];
|
|
36
|
-
if (!result && incidentIds.length && typeof payload.landedSha === "string") {
|
|
37
|
-
try {
|
|
38
|
-
block = await getGateway().write("planDispatch.ci.block", { planId: owner.planId, taskId: run.taskId, runId: run.id, sha: payload.landedSha, incidentIds, waitKey: run.escalation?.waitKey ?? "" }) as unknown as StoredPlanCiBlock;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
// A superseded attempt must still send its normal CI notice (#3660).
|
|
41
|
-
if (error instanceof ValidationError && error.message === "CI blocker no longer owns its plan slot") return undefined;
|
|
42
|
-
throw error;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (!block || (result && (block.retriedAt !== undefined || block.waitKey !== run.escalation?.waitKey)) || (!result && !incidentIds.length)) return undefined;
|
|
46
|
-
const plan = await getPlanGraphProjectionAsync({ planId: owner.planId });
|
|
47
|
-
const to = plan && planNoticeTarget(plan);
|
|
48
|
-
if (!to) return undefined;
|
|
49
|
-
for (const incidentId of block.incidentIds) {
|
|
50
|
-
notifySystemMessage(to, {
|
|
51
|
-
subject: `Plan ${plan.title ?? plan.id}: CI blocked`,
|
|
52
|
-
body: `Plan ${plan.id}: T${run.taskId}, run ${run.id}, landed SHA ${block.sha}, is blocked by CI incident ${incidentId}. Fix the incident; Relay automatically retries each blocked run's exact landed SHA after clearance. Slots and dependents wait. relay_plan_get shows every blocked Task and run.`,
|
|
53
|
-
idempotencyKey: `plan:${plan.id}:ci:${incidentId}`, replyExpected: false,
|
|
54
|
-
payload: { kind: "plan.ci-blocked", planId: plan.id, incidentId, taskId: run.taskId, runId: run.id, landedSha: block.sha },
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
return { quiet: "plan-ci-episode", planId: owner.planId, incidentIds: block.incidentIds };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
7
|
/** A kernel resume note is the durable receipt if the process dies before marking the plan's row. */
|
|
61
8
|
export async function recoverPlanCiBlock(run: WorkflowRunRecord): Promise<StoredPlanCiBlock | null> {
|
|
62
9
|
const block = await getGateway().read("planDispatch.ci.block", { runId: run.id });
|