@humwick/server 0.130.2 → 0.130.4
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/bundled/prompts/manifest.json +1 -0
- package/bundled/prompts/task.hydration.md +1 -1
- package/bundled/roles/builder.md +1 -1
- package/docs/guides/sub-coordinators.md +12 -5
- package/docs/guides/tools-coordination.md +1 -0
- package/docs/guides/workflows.md +16 -0
- package/docs/openapi.json +175 -4
- package/package.json +8 -7
- package/public/assets/{MessageBubble-BWLqMGKG.js → MessageBubble-BOkmEvgZ.js} +2 -2
- package/public/assets/{PlanGraphPanel-3TlIQpjk.js → PlanGraphPanel-BkaDJp0r.js} +2 -2
- package/public/assets/{TraceView-Wimm0Czv.js → TraceView-B99Uu2yT.js} +2 -2
- package/public/assets/{VaultDecisionCard-Bau_vNax.js → VaultDecisionCard-DUIfTTFF.js} +2 -2
- package/public/assets/{activity-CrSurn-r.js → activity-ZIX0N1El.js} +2 -2
- package/public/assets/{agent-lineage-CknAJKcb.js → agent-lineage-kq9TPYFw.js} +2 -2
- package/public/assets/agent-liveness-DSQY1Aa_.js +2 -0
- package/public/assets/{agent-name-DKbiIhBo.js → agent-name-C_7F4DFR.js} +2 -2
- package/public/assets/{agent-profiles-DwF6EQ4R.js → agent-profiles-BNOcrGcY.js} +2 -2
- package/public/assets/{agent-project-badge-Diz4Sjjb.js → agent-project-badge-B6yAh70a.js} +2 -2
- package/public/assets/{agent-tools-CRhpRQza.js → agent-tools-CcrhmOzF.js} +2 -2
- package/public/assets/{agent-type-icon-LrUEU3lB.js → agent-type-icon-CXSLrKiq.js} +2 -2
- package/public/assets/{agents-B-v66C-4.js → agents-DIZG97_X.js} +2 -2
- package/public/assets/{analytics-BqCb-LKi.js → analytics-rasmoPjR.js} +2 -2
- package/public/assets/{artifact-viewer-kf4pGuAR.js → artifact-viewer-tBpTrEPb.js} +2 -2
- package/public/assets/{artifacts-DJevXcvg.js → artifacts-BNkrynP1.js} +2 -2
- package/public/assets/{artifacts-D32zyw4Y.js → artifacts-Bjq-ZyBA.js} +2 -2
- package/public/assets/{automation-BpRajx_5.js → automation-Cx422L3L.js} +2 -2
- package/public/assets/{backlog-D_UjTdzX.js → backlog-RuFQ8PUu.js} +2 -2
- package/public/assets/{busy-health-N3VEh4ao.js → busy-health-BvNQ9g5x.js} +2 -2
- package/public/assets/{channels-9jopungq.js → channels-DNbKAS0I.js} +2 -2
- package/public/assets/{chat-CSXrTB3C.js → chat-Bp3Fx73h.js} +3 -3
- package/public/assets/{code-preview-p1NnmqK0.js → code-preview-KIRZA6cz.js} +2 -2
- package/public/assets/{connectors-CZdqlDNL.js → connectors-k7vO4vvc.js} +2 -2
- package/public/assets/{cosmos-BgdsE4dG.js → cosmos-BuVoq1aw.js} +2 -2
- package/public/assets/{crate-agent-30aXTKva.js → crate-agent-irqlt4NH.js} +2 -2
- package/public/assets/{dependency-graph-B_jvw2GE.js → dependency-graph-D_rurNBz.js} +2 -2
- package/public/assets/{deploys-DoN6bAb5.js → deploys-yzZqIpxE.js} +2 -2
- package/public/assets/{display-BrKWFGJz.js → display-BemJG1f_.js} +2 -2
- package/public/assets/{files-D6XEu8Ng.js → files-DXWcrxOz.js} +2 -2
- package/public/assets/{fleet-C7jQWbw2.js → fleet-TEuOCZXE.js} +2 -2
- package/public/assets/{fleet-metrics-BiDs3MLx.js → fleet-metrics-BGoDAFI-.js} +2 -2
- package/public/assets/{formatted-body-impl-DyG0WsQS.js → formatted-body-impl-BB9eq9nN.js} +2 -2
- package/public/assets/{formatted-body-CQBwc1fb.js → formatted-body-qy2nTD_1.js} +3 -3
- package/public/assets/{host-providers-dialog-DKaW31dN.js → host-providers-dialog-BGPHxuwT.js} +2 -2
- package/public/assets/{index-CRHep3M9.js → index-BC5XZc0z.js} +5 -5
- package/public/assets/{index-MYAIwYTX.css → index-D8Rp9pz2.css} +1 -1
- package/public/assets/{insights-CzrtkpOE.js → insights-C-KQG0h-.js} +2 -2
- package/public/assets/{integrations-aVsUmigt.js → integrations-ClPRPyKU.js} +2 -2
- package/public/assets/{issue-ref-chip-BnSlM_95.js → issue-ref-chip-DbH8J9Ak.js} +2 -2
- package/public/assets/{log-viewer-qe7Ee47Z.js → log-viewer-PH4zVn24.js} +2 -2
- package/public/assets/{maintenance-NfjqFZAk.js → maintenance-5lV43uTL.js} +2 -2
- package/public/assets/{managed-agents-BiH0HgaE.js → managed-agents-BdgU35aX.js} +2 -2
- package/public/assets/{markdown-preview-BixaT3Xr.js → markdown-preview-DNT4OUwQ.js} +3 -3
- package/public/assets/{markdown-preview-impl-DNGlWIS3.js → markdown-preview-impl-CnCcPy0A.js} +2 -2
- package/public/assets/{memory-DNsCl6UX.js → memory--GgWH1gH.js} +2 -2
- package/public/assets/{messages-DopU1L1X.js → messages-CzFl_97O.js} +2 -2
- package/public/assets/{orchestrators-Cq7ckLQT.js → orchestrators-CVvJVkzv.js} +2 -2
- package/public/assets/overview-DsrZvE86.js +2 -0
- package/public/assets/{pairs-DE_m6rrE.js → pairs-D2d0d2py.js} +2 -2
- package/public/assets/{profile-icon-B3rfh77F.js → profile-icon-_SMuSbYF.js} +2 -2
- package/public/assets/{project-profile-instructions-DQWZE0kA.js → project-profile-instructions-Apn15y43.js} +2 -2
- package/public/assets/{projects-Br2Bd2h1.js → projects-D3B6kto5.js} +2 -2
- package/public/assets/{prompt-settings-BKvFsvGD.js → prompt-settings-DnPB6Ez6.js} +2 -2
- package/public/assets/provider-display-bWwiuedV.js +2 -0
- package/public/assets/{registry-Cw38_6gW.js → registry-D790cIhN.js} +2 -2
- package/public/assets/run-card-BCro-Odj.js +2 -0
- package/public/assets/{secrets-DxI-vzSq.js → secrets-CVXvfvbU.js} +2 -2
- package/public/assets/{service-access-settings-D9s_VJbd.js → service-access-settings-D3Xxqb9w.js} +2 -2
- package/public/assets/{settings-CKZTVGIx.js → settings-CoUC7Jci.js} +2 -2
- package/public/assets/shipping-D_Lp9n_J.js +2 -0
- package/public/assets/{stats-DyUBKyWI.js → stats-CXiNjB9S.js} +2 -2
- package/public/assets/{status-dot-DYqlEUhJ.js → status-dot-C703YIy1.js} +2 -2
- package/public/assets/store-BgDALGIX.js +9 -0
- package/public/assets/{tasks-Bh7Chr4-.js → tasks-DO0WpJCx.js} +2 -2
- package/public/assets/{teams-bowI7qVY.js → teams-FGkdi_tL.js} +2 -2
- package/public/assets/{terminal-viewer-BgVLHdsU.js → terminal-viewer-Dh8dSC7e.js} +3 -3
- package/public/assets/{terminal-viewer-impl-Dz22UPy6.js → terminal-viewer-impl-B5Zriip3.js} +2 -2
- package/public/assets/{use-agent-terminal-DSQQ-iY7.js → use-agent-terminal-CRUVDvLC.js} +2 -2
- package/public/assets/{use-decisions-samMQhQf.js → use-decisions-CoaqRh4l.js} +2 -2
- package/public/assets/{use-project-list-59LheVP7.js → use-project-list-X-BgT53X.js} +2 -2
- package/public/assets/{use-selectors-BZa25xEB.js → use-selectors-0X_UZkk-.js} +2 -2
- package/public/assets/{user-avatar-DHxlvUvV.js → user-avatar-CkLhVBGu.js} +2 -2
- package/public/assets/{versioned-prompt-editor-BmED5DnJ.js → versioned-prompt-editor-BkTi7eim.js} +2 -2
- package/public/assets/{work-queue-CSvpuNLz.js → work-queue-DDOLEyKo.js} +2 -2
- package/public/assets/{workflow-marker-DAZcj5qt.js → workflow-marker-CzOsB8BM.js} +2 -2
- package/public/assets/{workflows-DdmxRSyg.js → workflows-DJilvmRM.js} +2 -2
- package/public/assets/{workspaces-BxeKfVuF.js → workspaces-CHcfbLR3.js} +2 -2
- package/public/cosmos.html +4 -4
- package/public/index.html +29 -29
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapters/claude-api.ts +1 -1
- package/runner/src/adapters/claude-headless.ts +1 -1
- package/runner/src/adapters/claude-launch-exit.ts +26 -0
- package/runner/src/adapters/claude-session-probe.ts +1 -1
- package/runner/src/adapters/claude-tmux.ts +38 -10
- package/runner/src/adapters/claude.ts +28 -75
- package/runner/src/adapters/provider-quota-harvest.ts +20 -71
- package/runner/src/agent-runner/lifecycle-milestones.ts +2 -2
- package/runner/src/agent-runner/liveness-exchange.ts +104 -0
- package/runner/src/claude-system-prompt.ts +49 -0
- package/runner/src/launch-assembly.ts +14 -10
- package/runner/src/profile-home.ts +1 -1
- package/runner/src/project-instructions.ts +2 -2
- package/runner/src/provider-command.ts +18 -0
- package/runner/src/provider-setup.ts +3 -2
- package/runner/src/runner-core.ts +14 -15
- package/runner/src/runner-helpers.ts +0 -8
- package/runner/src/session-scratch.ts +6 -2
- package/runner/test-support/provisioning-context.ts +4 -2
- package/runner/test-support/runner-harness.ts +2 -1
- package/scripts/steward-policy-cleanup.ts +2 -2
- package/src/agent-authz.ts +2 -2
- package/src/agent-drive-arbiter.ts +3 -8
- package/src/agent-exit-cleanup-async.ts +2 -2
- package/src/agent-exit-cleanup.ts +23 -19
- package/src/agent-lifecycle-events.ts +112 -85
- package/src/agent-ref.ts +3 -2
- package/src/agent-status-events.ts +43 -14
- package/src/auth/login-link.ts +35 -0
- package/src/auth/password.ts +4 -3
- package/src/auth/session-refresh.ts +1 -1
- package/src/auth/session.test-fixture.ts +2 -2
- package/src/auth/session.ts +7 -7
- package/src/automations/crud.ts +8 -81
- package/src/automations/dispatch.ts +33 -58
- package/src/automations/index.ts +1 -1
- package/src/automations/persistence.ts +65 -0
- package/src/automations/precheck.ts +18 -16
- package/src/automations/read-async.ts +11 -0
- package/src/automations/reconcile.ts +30 -39
- package/src/automations/storage.ts +33 -10
- package/src/automations/write-async.ts +33 -0
- package/src/branch-land-notice.ts +3 -3
- package/src/branch-landed.ts +25 -23
- package/src/bus.ts +21 -19
- package/src/cd-deploy-notify.ts +2 -2
- package/src/ci/notifications.ts +4 -4
- package/src/ci/root-missing.ts +3 -3
- package/src/ci/transition-notify.ts +5 -5
- package/src/claude-app/oauth-http.ts +3 -3
- package/src/cli/index.ts +7 -1
- package/src/cli/init.ts +53 -0
- package/src/cli/orchestrator.ts +1 -7
- package/src/cli/setup.ts +6 -4
- package/src/cli/workflow.ts +12 -3
- package/src/commands-db.ts +4 -4
- package/src/commands-write.ts +8 -0
- package/src/compaction-watch.ts +10 -8
- package/src/config-store-write.ts +2 -2
- package/src/config.ts +4 -11
- package/src/connectors.ts +10 -8
- package/src/context-advisory.ts +22 -21
- package/src/db/agent-action-write.ts +36 -0
- package/src/db/agent-control-async.ts +9 -0
- package/src/db/agent-control-write.ts +49 -0
- package/src/db/agent-effort-observe.ts +22 -0
- package/src/db/agent-meta.ts +14 -3
- package/src/db/agents-async.ts +8 -5
- package/src/db/agents.ts +15 -12
- package/src/db/auth-identities.ts +2 -2
- package/src/db/automation-read-ops.ts +11 -0
- package/src/db/automation-write-ops.ts +46 -0
- package/src/db/blackboard.ts +2 -2
- package/src/db/browser-session-write.ts +32 -0
- package/src/db/bus-outbox-write-ops.ts +3 -1
- package/src/db/checkout-refresh-schema.ts +47 -0
- package/src/db/checkout-refresh.ts +5 -5
- package/src/db/command-agent-profile-backfill.ts +39 -0
- package/src/db/command-agent-profiles.ts +5 -1
- package/src/db/command-retention.ts +126 -0
- package/src/db/commands-planner-stats.test-fixture.ts +8 -0
- package/src/db/commands-read-ops.ts +16 -0
- package/src/db/connection.ts +1 -182
- package/src/db/context-carry-write.ts +74 -0
- package/src/db/continuations.ts +4 -4
- package/src/db/db-error-registry.ts +12 -0
- package/src/db/gateway.ts +26 -133
- package/src/db/history-retention-ops.ts +13 -0
- package/src/db/inbox-async.ts +2 -2
- package/src/db/inbox-read-ops.ts +1 -1
- package/src/db/inbox.ts +9 -3
- package/src/db/issue-associations.ts +2 -2
- package/src/db/issue-refs-async.ts +4 -1
- package/src/db/issue-refs-read-ops.ts +6 -0
- package/src/db/issue-refs.ts +8 -8
- package/src/db/kernel-event-log-schema.ts +2 -0
- package/src/db/land-boundaries.ts +51 -5
- package/src/db/land-gate-attestations.ts +2 -2
- package/src/db/landing-receipts.ts +2 -2
- package/src/db/lifecycle-context-write.ts +5 -0
- package/src/db/lifecycle-tail-async.ts +76 -0
- package/src/db/lifecycle-tail-write-ops.ts +62 -0
- package/src/db/lifecycle-write-window.ts +2 -1
- package/src/db/lineage-pair-dedupe-sql.ts +3 -3
- package/src/db/main-connection-mode.ts +11 -0
- package/src/db/main-connection-writes.ts +20 -2
- package/src/db/maintenance-job-runs-async.ts +2 -2
- package/src/db/maintenance-job-write-ops.ts +1 -1
- package/src/db/maintenance-pauses.ts +2 -2
- package/src/db/message-send.ts +4 -0
- package/src/db/messages.ts +31 -25
- package/src/db/migrations.ts +18 -1
- package/src/db/notification-write-ops.ts +19 -0
- package/src/db/operator-answer-write.ts +19 -0
- package/src/db/orchestrators.ts +10 -4
- package/src/db/pairs-async.ts +22 -1
- package/src/db/pairs-write-ops.ts +8 -3
- package/src/db/pairs.ts +41 -48
- package/src/db/plan-dispatch-schema.ts +5 -1
- package/src/db/plan-ready-notices.ts +43 -0
- package/src/db/plan-tools-include-migration.ts +36 -0
- package/src/db/plans-write-ops.ts +2 -0
- package/src/db/presence-buffer.ts +5 -0
- package/src/db/presence-loop.ts +18 -23
- package/src/db/presence-write.ts +0 -6
- package/src/db/project-entries-async.ts +14 -0
- package/src/db/project-entries-read-ops.ts +5 -1
- package/src/db/project-entries-write-ops.ts +8 -0
- package/src/db/project-entries.ts +15 -12
- package/src/db/project-journal.ts +2 -2
- package/src/db/project-trust-approvals.ts +2 -2
- package/src/db/prompt-templates.ts +4 -4
- package/src/db/read-op-marshaling-fixtures.ts +18 -0
- package/src/db/read-ops.ts +4 -0
- package/src/db/retention.ts +13 -2
- package/src/db/review-publish-write.ts +8 -0
- package/src/db/runtime-admin-async.ts +19 -0
- package/src/db/runtime-admin-write-ops.ts +36 -0
- package/src/db/runtime-tail-async.ts +288 -0
- package/src/db/runtime-tail-write-ops.ts +97 -0
- package/src/db/schema.ts +4 -1
- package/src/db/spawn-budget.ts +5 -0
- package/src/db/spawn-delegation.ts +3 -12
- package/src/db/sql-audit-indexes.ts +3 -0
- package/src/db/stats-async.ts +5 -3
- package/src/db/task-entity.ts +5 -4
- package/src/db/team-claim-policy.ts +60 -0
- package/src/db/teams-async.ts +4 -0
- package/src/db/teams-write-ops.ts +4 -0
- package/src/db/terminal-events.ts +15 -0
- package/src/db/timer-cutover-bodies.ts +292 -0
- package/src/db/timer-cutover-core.ts +37 -278
- package/src/db/timer-cutover-state.ts +40 -0
- package/src/db/timer-cutover-write-ops.ts +1 -40
- package/src/db/token-db-write-ops.ts +2 -0
- package/src/db/token-standing-async.ts +12 -0
- package/src/db/token-standing.ts +6 -9
- package/src/db/token-usage-retention.ts +33 -0
- package/src/db/token-usage-write-ops.ts +1 -1
- package/src/db/token-usage.ts +4 -1
- package/src/db/user-invites-async.ts +13 -0
- package/src/db/user-invites.ts +3 -3
- package/src/db/users.ts +14 -6
- package/src/db/workflow-execution.ts +3 -3
- package/src/db/workspaces.ts +8 -5
- package/src/db/write-ops.ts +31 -12
- package/src/db/writer-fault-seam.ts +2 -9
- package/src/db/writer-fault-test-ops.ts +18 -0
- package/src/db/writer-gateway.ts +42 -46
- package/src/dev.ts +12 -16
- package/src/event-loop-lag.ts +4 -2
- package/src/event-loop-yield.ts +2 -2
- package/src/events.ts +10 -8
- package/src/host-provider-store.ts +5 -4
- package/src/hub-metrics-report.ts +5 -1
- package/src/hub-metrics.ts +38 -18
- package/src/init.ts +299 -0
- package/src/injection-sweep-pacing.ts +6 -8
- package/src/invite-link.ts +16 -9
- package/src/land-gate-notifications.ts +11 -10
- package/src/land-gate-queue-progress.ts +3 -3
- package/src/land-settle-dispatch.ts +52 -92
- package/src/lifecycle-context-pressure.ts +11 -10
- package/src/lifecycle-manager.ts +104 -98
- package/src/maintenance/breaker.ts +18 -16
- package/src/maintenance/checkout-refresh.ts +18 -1
- package/src/maintenance/critical-job-watchdog.ts +3 -3
- package/src/maintenance/events.ts +3 -3
- package/src/maintenance/history-retention-job.ts +30 -0
- package/src/maintenance/jobs.ts +29 -67
- package/src/maintenance/long-running-work.ts +3 -1
- package/src/maintenance/offline-agent-prune-job.ts +28 -0
- package/src/maintenance/orphaned-sessions.ts +5 -4
- package/src/maintenance/pauses.ts +4 -10
- package/src/maintenance/plan-ready-notice-job.ts +71 -0
- package/src/maintenance/scheduler.ts +18 -13
- package/src/maintenance/stalled-delivery-pager.ts +4 -4
- package/src/maintenance/teams.ts +19 -30
- package/src/maintenance/workspaces.ts +17 -27
- package/src/managed-policy-escalation.ts +7 -7
- package/src/managed-policy.ts +3 -3
- package/src/managed-provider-gate.ts +5 -4
- package/src/managed-spawn-reservation.ts +5 -4
- package/src/mcp/index.ts +8 -8
- package/src/mcp/output-descriptions.ts +1 -1
- package/src/mcp/presentation.ts +2 -1
- package/src/mcp/runtime-output.ts +6 -1
- package/src/mcp/self-care-visibility.ts +2 -2
- package/src/mcp/spawn-targets-matrix.ts +10 -1
- package/src/mcp/tool-groups.ts +3 -2
- package/src/mcp/tool-roles.ts +81 -0
- package/src/mcp/tools-workspace.ts +1 -1
- package/src/mcp-blackboard-tools.ts +6 -5
- package/src/mcp-decision-tools.ts +7 -7
- package/src/mcp-egress-tools.ts +10 -9
- package/src/mcp-issue-activity-tools.ts +2 -2
- package/src/mcp-issue-association-tools.ts +4 -4
- package/src/mcp-issue-ref-tools.ts +3 -3
- package/src/mcp-merge-tools.ts +5 -5
- package/src/mcp-notification-subscription-tools.ts +14 -15
- package/src/mcp-output-levels.test-fixture.ts +1 -1
- package/src/mcp-profile-trust-tools.ts +1 -1
- package/src/mcp-project-journal-tools.ts +4 -12
- package/src/mcp-project-tools.ts +17 -18
- package/src/mcp-routing-policy-tools.ts +3 -2
- package/src/mcp-team-tools.ts +6 -6
- package/src/mcp-workflow-tools.ts +5 -3
- package/src/memory-area-injection.ts +13 -12
- package/src/memory-boundary-capture.ts +6 -6
- package/src/memory-injection.ts +45 -43
- package/src/merge-pause-maintenance.ts +5 -4
- package/src/notification-router.ts +44 -17
- package/src/notify.ts +5 -5
- package/src/observed-reset-refresh.ts +4 -4
- package/src/operator-alarm.ts +2 -5
- package/src/project-ingest.ts +6 -6
- package/src/project-instructions.ts +8 -1
- package/src/project-trust.ts +12 -11
- package/src/prompt-resolver.ts +3 -3
- package/src/provider-quota-async.ts +5 -7
- package/src/provider-timeline-event.ts +4 -7
- package/src/quota-band-delivery.ts +2 -2
- package/src/recipe-runner.ts +4 -3
- package/src/retention-config-store.ts +4 -3
- package/src/reviewer-pipeline.ts +11 -10
- package/src/routes/_route.ts +4 -4
- package/src/routes/_shared.ts +13 -12
- package/src/routes/agent-effort.ts +5 -5
- package/src/routes/agent-heartbeat-quota.ts +23 -0
- package/src/routes/agent-keep.ts +1 -1
- package/src/routes/agent-reporting.ts +1 -1
- package/src/routes/agent-self-effort.ts +5 -5
- package/src/routes/agent-sessions.ts +24 -24
- package/src/routes/agents-spawn.ts +1 -1
- package/src/routes/agents.ts +17 -119
- package/src/routes/auth.ts +44 -17
- package/src/routes/authority-audit.ts +2 -2
- package/src/routes/automations.ts +6 -5
- package/src/routes/channel-binding-delete.ts +1 -1
- package/src/routes/channel-output.ts +3 -3
- package/src/routes/claude-api-budget.ts +5 -5
- package/src/routes/commands-listing.ts +1 -1
- package/src/routes/commands.ts +29 -27
- package/src/routes/context-resets.ts +2 -2
- package/src/routes/coordinator-teams.ts +1 -1
- package/src/routes/decisions.ts +2 -2
- package/src/routes/host-providers.ts +18 -0
- package/src/routes/host-secret-files.ts +1 -1
- package/src/routes/hosts.ts +11 -11
- package/src/routes/inbox.ts +3 -2
- package/src/routes/index.ts +7 -3
- package/src/routes/integrations.ts +9 -7
- package/src/routes/issue-activity.ts +2 -2
- package/src/routes/issue-associations.ts +4 -4
- package/src/routes/issue-refs.ts +3 -3
- package/src/routes/kernel-timers-operator.ts +1 -1
- package/src/routes/memory.ts +1 -1
- package/src/routes/merges.ts +11 -11
- package/src/routes/messages.ts +23 -17
- package/src/routes/orchestrator-bootstrap.ts +3 -1
- package/src/routes/orchestrator-host-registration.ts +2 -0
- package/src/routes/orchestrator-host-utilization.ts +2 -2
- package/src/routes/orchestrator-proxy.ts +2 -2
- package/src/routes/orchestrator.ts +27 -18
- package/src/routes/pairs.ts +8 -8
- package/src/routes/project-profile-instructions.ts +1 -1
- package/src/routes/project-trust.ts +1 -1
- package/src/routes/projects.ts +16 -16
- package/src/routes/prompt-templates.ts +12 -12
- package/src/routes/provider-quotas.ts +10 -10
- package/src/routes/run-command.ts +1 -1
- package/src/routes/secrets-backup.ts +4 -5
- package/src/routes/secrets-import.ts +3 -4
- package/src/routes/secrets-vault.ts +12 -14
- package/src/routes/settings.ts +1 -1
- package/src/routes/shipping.ts +9 -1
- package/src/routes/spawn-policy.ts +8 -7
- package/src/routes/sse.ts +2 -2
- package/src/routes/team-templates.ts +7 -7
- package/src/routes/teams.ts +10 -10
- package/src/routes/tokens.ts +18 -17
- package/src/routes/users.ts +24 -24
- package/src/routes/viewer-read.ts +3 -3
- package/src/routes/web-tool-calls.ts +4 -4
- package/src/routes/workflows.ts +2 -2
- package/src/routes/workspaces.ts +17 -16
- package/src/routing-policy-push.ts +6 -6
- package/src/runtime-grant.ts +17 -11
- package/src/runtime-install.ts +30 -0
- package/src/runtime-tokens.ts +20 -28
- package/src/secrets-broker/config.ts +1 -1
- package/src/secrets-broker/service.ts +2 -2
- package/src/security.ts +3 -1
- package/src/self-resume-carry.ts +21 -110
- package/src/self-resume-supersede.ts +4 -3
- package/src/self-resume.ts +54 -66
- package/src/server-background.ts +1 -1
- package/src/server.ts +25 -26
- package/src/services/agent-action.ts +12 -31
- package/src/services/agent-effort.ts +7 -32
- package/src/services/agent-hang-watchdog-job.ts +2 -2
- package/src/services/agent-heartbeat.ts +166 -0
- package/src/services/agent-reporting.ts +1 -1
- package/src/services/auth-context.ts +6 -0
- package/src/services/browser-inventory.ts +3 -3
- package/src/services/browser-session-state.ts +3 -2
- package/src/services/browser-session.ts +21 -27
- package/src/services/bus-command-update.ts +9 -9
- package/src/services/checkout-refresh-state.ts +1 -0
- package/src/services/checkout-refresh.test-fixture.ts +3 -2
- package/src/services/command-host.ts +7 -2
- package/src/services/coordinator-digest.ts +3 -2
- package/src/services/dispatch-command.ts +12 -13
- package/src/services/effective-launch-driver.ts +14 -9
- package/src/services/host-changed-paths.test-fixture.ts +5 -6
- package/src/services/host-project-readiness.ts +2 -2
- package/src/services/host-providers.ts +1 -1
- package/src/services/host-utilization.ts +9 -9
- package/src/services/idle-agent-digest.ts +17 -12
- package/src/services/idle-epics.ts +4 -4
- package/src/services/interrupt-agent.ts +12 -9
- package/src/services/issue-activity.ts +3 -3
- package/src/services/issue-associations.ts +15 -18
- package/src/services/issue-lifecycle-comments.ts +3 -2
- package/src/services/issue-lifecycle.ts +14 -13
- package/src/services/issue-refs.ts +4 -5
- package/src/services/kernel-timers-cutover-chaos-worker.ts +1 -1
- package/src/services/kernel-timers-ingress-chaos-worker.ts +6 -1
- package/src/services/kernel-timers-ingress.ts +3 -3
- package/src/services/kernel-timers-message-authority-chaos-worker.ts +3 -10
- package/src/services/kernel-timers-operator-chaos-worker.ts +3 -2
- package/src/services/kernel-timers-outbox.ts +1 -1
- package/src/services/kernel-timers-stage10-operator.ts +1 -1
- package/src/services/kernel-timers.ts +4 -3
- package/src/services/land-gate-placement.ts +11 -2
- package/src/services/land-issue-relations.ts +5 -4
- package/src/services/land-restart-retry.ts +7 -6
- package/src/services/landed-ancestry.ts +6 -5
- package/src/services/legacy-landing-host.ts +4 -1
- package/src/services/legacy-landing-recovery.ts +8 -6
- package/src/services/managed-running.ts +17 -17
- package/src/services/memory-areas.ts +3 -3
- package/src/services/merge-pause.ts +9 -15
- package/src/services/operator-decisions.ts +37 -41
- package/src/services/orchestrator-registration-recovery.ts +6 -2
- package/src/services/orchestrator-upgrade.ts +14 -12
- package/src/services/permission-decision.ts +4 -3
- package/src/services/plan-ci-notice.ts +1 -1
- package/src/services/plan-conformance.ts +4 -4
- package/src/services/plan-dispatch-policy.ts +10 -3
- package/src/services/plan-dispatch.ts +35 -14
- package/src/services/plan-graph.ts +39 -261
- package/src/services/plan-import.ts +1 -1
- package/src/services/plan-live-bindings.ts +8 -37
- package/src/services/plan-task-create.ts +1 -1
- package/src/services/plan-terminal-refs.ts +2 -12
- package/src/services/plan-wake-digest.ts +8 -3
- package/src/services/profile-trust.ts +2 -2
- package/src/services/project.ts +11 -10
- package/src/services/prompt-templates.ts +6 -5
- package/src/services/provider-api-key.ts +73 -0
- package/src/services/provider-hold-nudge.ts +9 -9
- package/src/services/provider-hold-record.ts +13 -12
- package/src/services/provider-state-audit.ts +19 -18
- package/src/services/provisioning-harness/driver.ts +1 -1
- package/src/services/provisioning-harness/fixture-matrix.ts +3 -3
- package/src/services/provisioning-harness/lifecycle.ts +24 -22
- package/src/services/provisioning-harness/prepare.ts +16 -17
- package/src/services/rate-limit-resume.ts +10 -9
- package/src/services/red-main-auto-revert.ts +7 -5
- package/src/services/red-main-controller-deps.ts +20 -3
- package/src/services/red-main-incident.ts +3 -1
- package/src/services/red-main-quarantine.ts +2 -3
- package/src/services/register-agent.ts +22 -22
- package/src/services/restart-project-role.ts +1 -1
- package/src/services/restart-spawn.ts +17 -16
- package/src/services/review-publish.ts +7 -24
- package/src/services/review-ref-reconcile.ts +29 -38
- package/src/services/runtime-dependency-alert.ts +27 -0
- package/src/services/scheduler-command.ts +11 -9
- package/src/services/scheduler.ts +5 -5
- package/src/services/self-effort-grant.ts +5 -25
- package/src/services/send-message.ts +16 -33
- package/src/services/shutdown-agent.ts +17 -14
- package/src/services/spawn-agent.ts +12 -41
- package/src/services/spawn-chain-inspector.ts +1 -1
- package/src/services/spawn-cwd-placement.ts +21 -5
- package/src/services/spawn-issue-activity.ts +8 -6
- package/src/services/spawn-launch.ts +17 -1
- package/src/services/spawn-loss-escalation.ts +1 -1
- package/src/services/spawn-registration-watchdog.ts +2 -2
- package/src/services/spawn-repo-probe.ts +7 -5
- package/src/services/spawn-tree-cleanup.ts +7 -8
- package/src/services/steward-reaper.ts +4 -4
- package/src/services/task-context-hydration.ts +25 -1
- package/src/services/task-delivery.ts +20 -34
- package/src/services/task-entity.ts +40 -203
- package/src/services/task-issue-link.ts +13 -20
- package/src/services/task-issue-weave.ts +12 -11
- package/src/services/task-lifecycle.ts +27 -31
- package/src/services/task-repo-cwd.ts +37 -0
- package/src/services/task-review-hold.ts +4 -17
- package/src/services/task-semantic-events.ts +13 -11
- package/src/services/task-state-reconcile.ts +3 -4
- package/src/services/task-validation.ts +2 -2
- package/src/services/team-templates.ts +5 -5
- package/src/services/team.ts +31 -98
- package/src/services/terminal-provider-exit.ts +6 -5
- package/src/services/timer-message-fire-chaos-worker.ts +7 -3
- package/src/services/timer-message-fire-outbox.ts +1 -1
- package/src/services/transient-agent-reaper.ts +22 -19
- package/src/services/turn-reconcile-audit.ts +9 -9
- package/src/services/user-onboarding.ts +8 -6
- package/src/services/user-profile.ts +6 -9
- package/src/services/work-finality.ts +16 -4
- package/src/services/workflow-auto-start.ts +2 -1
- package/src/services/workflow-caller.ts +61 -4
- package/src/services/workflow-child-proof-worker.ts +1 -1
- package/src/services/workflow-control-proof-worker.ts +1 -1
- package/src/services/workflow-coordinator.ts +8 -2
- package/src/services/workflow-decisions.ts +5 -5
- package/src/services/workflow-deferred-cleanup.ts +1 -1
- package/src/services/workflow-effect-driver.ts +42 -19
- package/src/services/workflow-effects.ts +41 -27
- package/src/services/workflow-pins.ts +7 -4
- package/src/services/workflow-reconcile.ts +1 -0
- package/src/services/workflow-resume-pins.ts +3 -3
- package/src/services/workflow-routing-defaults.ts +18 -0
- package/src/services/workflow-run-view.ts +4 -3
- package/src/services/workflow-run.test-fixture.ts +1 -1
- package/src/services/workflow-runs.ts +34 -22
- package/src/services/workflow-supervisor-state.ts +21 -1
- package/src/services/workflow-supervisor.ts +4 -3
- package/src/services/workspace-recovery.ts +4 -3
- package/src/setup.ts +47 -8
- package/src/spawn-admission.ts +10 -2
- package/src/spawn-placement.ts +27 -9
- package/src/spawn-targets.ts +8 -8
- package/src/sse-agent-status.ts +15 -6
- package/src/sse.ts +49 -24
- package/src/startup-context-injection.ts +61 -57
- package/src/steward-handoff.ts +4 -3
- package/src/steward-policy-cleanup.ts +9 -8
- package/src/steward.ts +2 -2
- package/src/task-dispatch-live.ts +5 -2
- package/src/task-dispatcher.ts +2 -2
- package/src/token-db.ts +2 -2
- package/src/token-usage-async.ts +2 -4
- package/src/user-awaiting-reply.ts +5 -5
- package/src/utils.ts +7 -0
- package/src/workspace-actions.ts +24 -29
- package/src/workspace-auto-merge.ts +14 -12
- package/src/workspace-changed-paths-client.ts +16 -5
- package/src/workspace-cleanup-stale.ts +30 -7
- package/src/workspace-command-settle.ts +24 -16
- package/src/workspace-config.ts +1 -1
- package/src/workspace-conflict-resolver.ts +9 -7
- package/src/workspace-dead-owner-recovery.ts +5 -4
- package/src/workspace-escalation.ts +12 -11
- package/src/workspace-human-escalation.ts +3 -3
- package/src/workspace-land-gate-watchdog.ts +3 -3
- package/src/workspace-land-refusal.ts +20 -0
- package/src/workspace-land-stopped.ts +1 -0
- package/src/workspace-merge.ts +10 -26
- package/src/workspace-nothing-to-land-notice.ts +2 -2
- package/src/workspace-orphans.ts +18 -37
- package/src/workspace-pr-completion.ts +10 -8
- package/src/workspace-rekey.ts +3 -3
- package/src/workspace-steward-failure.ts +2 -2
- package/src/workspace-steward-wake.ts +2 -2
- package/public/assets/agent-liveness-CaNwK0Zf.js +0 -2
- package/public/assets/overview-C1DCTzzV.js +0 -2
- package/public/assets/provider-display-DsuWMvVL.js +0 -2
- package/public/assets/run-card-CpKqRbhg.js +0 -2
- package/public/assets/shipping-B9YtnwtG.js +0 -2
- package/public/assets/store-DcClXLH0.js +0 -9
- package/runner/src/agent-runner/http-liveness.ts +0 -85
- package/src/db/on-loop-write-transaction.ts +0 -34
package/src/db/token-usage.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTokenUsageExpired } from "./token-usage-retention";
|
|
1
2
|
// #2485 — the durable, provider-agnostic token-usage ledger ("the spine" of epic #2481). The three
|
|
2
3
|
// capture seams write per-turn provider usage here through `tokenUsage.record`; the Analytics box
|
|
3
4
|
// reads period/provider aggregates through `tokenUsage.aggregate`.
|
|
@@ -144,7 +145,7 @@ export function getSessionTokenUsageByDedupeKey(dedupeKey: string): SessionToken
|
|
|
144
145
|
* the writer boundary as well as at the HTTP boundary, and never minted from the receipt clock,
|
|
145
146
|
* so a genuine retry deterministically reuses the same key.
|
|
146
147
|
*/
|
|
147
|
-
export function recordSessionTokenUsage(input: SessionTokenUsageInput): { record: SessionTokenUsageRecord; inserted: boolean } {
|
|
148
|
+
export function recordSessionTokenUsage(input: SessionTokenUsageInput): { record: SessionTokenUsageRecord | null; inserted: boolean } {
|
|
148
149
|
const { createdAt } = input;
|
|
149
150
|
if (!Number.isSafeInteger(createdAt) || createdAt <= 0) {
|
|
150
151
|
throw new ValidationError("createdAt required (positive epoch-milliseconds of the provider's observed usage time)");
|
|
@@ -160,6 +161,8 @@ export function recordSessionTokenUsage(input: SessionTokenUsageInput): { record
|
|
|
160
161
|
const prior = getDb().query("SELECT * FROM session_token_usage WHERE dedupe_key = ?").get(dedupeKey) as
|
|
161
162
|
| SessionTokenUsageRow
|
|
162
163
|
| undefined;
|
|
164
|
+
// Transcript capture replays from byte zero after restart; acknowledge folded history without recreating it (#3619).
|
|
165
|
+
if (!prior && isTokenUsageExpired(createdAt)) return { record: null, inserted: false };
|
|
163
166
|
// Bound once, at first attribution: a replacement reuses the row's Task, never the agent's current one.
|
|
164
167
|
const taskId = prior ? (prior.task_id ?? undefined) : deliverableTaskIdForAgent(input.agentId);
|
|
165
168
|
getDb().query(`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getGateway } from "./gateway";
|
|
3
|
+
import type { createUserInvite, acceptUserInvite } from "./user-invites";
|
|
4
|
+
import { invalidateUserCount } from "./users";
|
|
5
|
+
|
|
6
|
+
export function createUserInviteAsync(input: Parameters<typeof createUserInvite>[0]): Promise<ReturnType<typeof createUserInvite>> {
|
|
7
|
+
return getGateway().write("userInvites.create", input, { idempotencyKey: randomUUID() }) as unknown as Promise<ReturnType<typeof createUserInvite>>;
|
|
8
|
+
}
|
|
9
|
+
export async function acceptUserInviteAsync(input: Parameters<typeof acceptUserInvite>[0]): Promise<ReturnType<typeof acceptUserInvite>> {
|
|
10
|
+
const user = await getGateway().write("userInvites.accept", input, { idempotencyKey: randomUUID() }) as unknown as ReturnType<typeof acceptUserInvite>;
|
|
11
|
+
invalidateUserCount();
|
|
12
|
+
return user;
|
|
13
|
+
}
|
package/src/db/user-invites.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// the password identity, so a replayed or raced code can never create a second account.
|
|
5
5
|
import { createHash, randomBytes } from "node:crypto";
|
|
6
6
|
import { ValidationError, getDb, writeTransaction } from "./connection.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { createUserRow } from "./users.ts";
|
|
8
8
|
import { createAuthIdentity } from "./auth-identities.ts";
|
|
9
9
|
import type { User, UserRole } from "../types";
|
|
10
10
|
|
|
@@ -99,8 +99,8 @@ export function acceptUserInvite(input: { code: string; userId: string; handle:
|
|
|
99
99
|
}
|
|
100
100
|
const claimed = getDb().query("UPDATE user_invites SET used_at = ? WHERE id = ? AND used_at IS NULL").run(now, row.id);
|
|
101
101
|
if (claimed.changes !== 1) throw new InviteUnavailableError("invite is invalid, expired or already used");
|
|
102
|
-
const user =
|
|
103
|
-
createAuthIdentity({ userId: user.id, provider: input.provider, providerSubject: input.handle, secretHash: input.secretHash });
|
|
102
|
+
const user = createUserRow({ id: input.userId, handle: input.handle, displayName: input.displayName || row.display_name || input.handle, role: row.role as UserRole }, now);
|
|
103
|
+
createAuthIdentity({ userId: user.id, provider: input.provider, providerSubject: input.handle, secretHash: input.secretHash }, now);
|
|
104
104
|
getDb().query("UPDATE user_invites SET used_by = ? WHERE id = ?").run(user.id, row.id);
|
|
105
105
|
return user;
|
|
106
106
|
});
|
package/src/db/users.ts
CHANGED
|
@@ -91,10 +91,19 @@ export function userCount(now: number = Date.now()): number {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export function createUser(input: CreateUserInput): User {
|
|
94
|
+
const user = createUserRow(input);
|
|
95
|
+
invalidateUserCount();
|
|
96
|
+
return user;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function invalidateUserCount(): void {
|
|
94
100
|
assertMainThreadState("users.ts");
|
|
101
|
+
userCountCache = undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function createUserRow(input: CreateUserInput, now = Date.now()): User {
|
|
95
105
|
if (!input.id) throw new ValidationError("user id is required");
|
|
96
106
|
if (!input.handle) throw new ValidationError("user handle is required");
|
|
97
|
-
const now = Date.now();
|
|
98
107
|
getDb()
|
|
99
108
|
.query(
|
|
100
109
|
`INSERT INTO users (id, handle, display_name, role, status, tenant_id, avatar_artifact_id, created_at, updated_at)
|
|
@@ -111,7 +120,6 @@ export function createUser(input: CreateUserInput): User {
|
|
|
111
120
|
now,
|
|
112
121
|
now,
|
|
113
122
|
);
|
|
114
|
-
userCountCache = undefined;
|
|
115
123
|
const created = getUser(input.id);
|
|
116
124
|
if (!created) throw new ValidationError(`failed to create user ${input.id}`);
|
|
117
125
|
return created;
|
|
@@ -121,7 +129,7 @@ export function updateUserProfile(input: {
|
|
|
121
129
|
id: string;
|
|
122
130
|
displayName?: string;
|
|
123
131
|
avatarArtifactId?: string | null;
|
|
124
|
-
}): User {
|
|
132
|
+
}, submittedAt: number = Date.now()): User {
|
|
125
133
|
const current = getUser(input.id);
|
|
126
134
|
if (!current) throw new ValidationError("user not found");
|
|
127
135
|
const updates: string[] = [];
|
|
@@ -137,17 +145,17 @@ export function updateUserProfile(input: {
|
|
|
137
145
|
}
|
|
138
146
|
if (!updates.length) return current;
|
|
139
147
|
updates.push("updated_at = ?");
|
|
140
|
-
params.push(
|
|
148
|
+
params.push(submittedAt, input.id);
|
|
141
149
|
getDb().query(`UPDATE users SET ${updates.join(", ")} WHERE id = ?`).run(...params);
|
|
142
150
|
return getUser(input.id)!;
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
/** Change a user's role or account status (#3567 admin user list). */
|
|
146
|
-
export function updateUserAccess(input: { id: string; role?: UserRole; status?: UserStatus }): User {
|
|
154
|
+
export function updateUserAccess(input: { id: string; role?: UserRole; status?: UserStatus }, submittedAt: number = Date.now()): User {
|
|
147
155
|
const current = getUser(input.id);
|
|
148
156
|
if (!current) throw new ValidationError("user not found");
|
|
149
157
|
getDb().query("UPDATE users SET role = ?, status = ?, updated_at = ? WHERE id = ?")
|
|
150
|
-
.run(input.role ?? current.role, input.status ?? current.status,
|
|
158
|
+
.run(input.role ?? current.role, input.status ?? current.status, submittedAt, input.id);
|
|
151
159
|
return getUser(input.id)!;
|
|
152
160
|
}
|
|
153
161
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Task ownership
|
|
1
|
+
// Task ownership, coordinator routing and spawn admission share execution facts for runs and item lanes.
|
|
2
2
|
export const WORKFLOW_EXECUTIONS_SQL = `(
|
|
3
|
-
SELECT scope, id, taskId, coordinator, status, reason, awaiting FROM kernel_entity__workflow__run
|
|
3
|
+
SELECT scope, id, taskId, coordinator, callerAgentId, status, reason, awaiting FROM kernel_entity__workflow__run
|
|
4
4
|
UNION ALL
|
|
5
|
-
SELECT item.scope, item.id, owner.taskId, owner.coordinator,
|
|
5
|
+
SELECT item.scope, item.id, owner.taskId, owner.coordinator, owner.callerAgentId,
|
|
6
6
|
json_extract(item.lane, '$.status') AS status, json_extract(item.lane, '$.reason') AS reason, json_extract(item.lane, '$.awaiting') AS awaiting
|
|
7
7
|
FROM kernel_entity__workflow__activation item
|
|
8
8
|
JOIN kernel_entity__workflow__run owner ON owner.scope = item.scope AND owner.id = item.runId
|
package/src/db/workspaces.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { trackRelayEvent } from "../relay-event-drain";
|
|
1
2
|
import { mainThreadRegistry } from "./main-thread-state";
|
|
2
3
|
import { persistWorkspace, type WorkspaceUpsertInput } from "./workspace-persistence";
|
|
3
4
|
import { Database } from "bun:sqlite";
|
|
@@ -438,7 +439,7 @@ export function pruneOrphanedSharedWorkspaces(): string[] {
|
|
|
438
439
|
// owner's branch-state badge" (#236). A hook, not a direct emit, because getDb().ts is
|
|
439
440
|
// the lowest layer — sse.ts imports db, so db can't import sse without a cycle. sse
|
|
440
441
|
// registers a listener at startup that re-emits the owner agent's status over SSE.
|
|
441
|
-
type WorkspaceChangeListener = (workspace: WorkspaceRecord) =>
|
|
442
|
+
type WorkspaceChangeListener = (workspace: WorkspaceRecord) => unknown;
|
|
442
443
|
export const workspaceChangeListeners = mainThreadRegistry("workspaces.ts:workspaceChangeListeners", new Set<WorkspaceChangeListener>());
|
|
443
444
|
export function onWorkspaceChange(listener: WorkspaceChangeListener): void {
|
|
444
445
|
workspaceChangeListeners.add(listener);
|
|
@@ -447,7 +448,8 @@ export function emitWorkspaceChange(workspace: WorkspaceRecord | null | undefine
|
|
|
447
448
|
if (!workspace) return;
|
|
448
449
|
for (const listener of workspaceChangeListeners) {
|
|
449
450
|
try {
|
|
450
|
-
listener(workspace);
|
|
451
|
+
const result = listener(workspace);
|
|
452
|
+
if (result instanceof Promise) trackRelayEvent(result, "workspace.changed");
|
|
451
453
|
} catch {
|
|
452
454
|
// A badge-refresh listener must never break a workspace write.
|
|
453
455
|
}
|
|
@@ -455,15 +457,16 @@ export function emitWorkspaceChange(workspace: WorkspaceRecord | null | undefine
|
|
|
455
457
|
}
|
|
456
458
|
|
|
457
459
|
// #1870 — workspace-create effects stay on main; index.ts registers the issue activity handler.
|
|
458
|
-
type WorkspaceCreateListener = (workspace: WorkspaceRecord) => void
|
|
460
|
+
type WorkspaceCreateListener = (workspace: WorkspaceRecord) => void | Promise<void>;
|
|
459
461
|
const workspaceCreateListeners = mainThreadRegistry("workspaces.ts:workspaceCreateListeners", new Set<WorkspaceCreateListener>());
|
|
460
462
|
export function onWorkspaceCreate(listener: WorkspaceCreateListener): void {
|
|
461
463
|
workspaceCreateListeners.add(listener);
|
|
462
464
|
}
|
|
463
|
-
function emitWorkspaceCreate(workspace: WorkspaceRecord): void {
|
|
465
|
+
export function emitWorkspaceCreate(workspace: WorkspaceRecord): void {
|
|
464
466
|
for (const listener of workspaceCreateListeners) {
|
|
465
467
|
try {
|
|
466
|
-
listener(workspace);
|
|
468
|
+
const result = listener(workspace);
|
|
469
|
+
if (result) trackRelayEvent(result, "workspace.created");
|
|
467
470
|
} catch {
|
|
468
471
|
// A create listener must never break a workspace write.
|
|
469
472
|
}
|
package/src/db/write-ops.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { TIMER_CUTOVER_COORDINATOR_WRITE_OPS } from "./timer-cutover-bodies";
|
|
2
|
+
import { RUNTIME_ADMIN_WRITE_OPS } from "./runtime-admin-write-ops";
|
|
3
|
+
import { AUTOMATION_WRITE_OPS } from "./automation-write-ops";
|
|
4
|
+
import { RUNTIME_TAIL_WRITE_OPS } from "./runtime-tail-write-ops";
|
|
5
|
+
import { LIFECYCLE_TAIL_WRITE_OPS } from "./lifecycle-tail-write-ops";
|
|
6
|
+
import { NOTIFICATION_WRITE_OPS } from "./notification-write-ops";
|
|
7
|
+
import { HISTORY_RETENTION_WRITE_OPS } from "./history-retention-ops";
|
|
1
8
|
import { INTEGRATIONS_WRITE_OPS } from "./integrations-ops.ts";
|
|
2
9
|
import { bufferedLastSeen, type PresenceSnapshot } from "./presence-buffer";
|
|
3
10
|
import { ensureDurablePayloadSchema, readRetryReceipt, storeDurablePayload } from "./durable-payloads";
|
|
@@ -42,6 +49,7 @@ import { CLAUDE_API_BUDGET_WRITE_OPS } from "./claude-api-budget-ops";
|
|
|
42
49
|
// file is the seam they plug into, not the final op set.
|
|
43
50
|
|
|
44
51
|
import type { Database } from "bun:sqlite";
|
|
52
|
+
import { WRITER_FAULT_TEST_OPS } from "./writer-fault-test-ops";
|
|
45
53
|
import { ValidationError } from "@humwick/sdk";
|
|
46
54
|
import { getDb, inWriteTransaction } from "./connection.ts";
|
|
47
55
|
import type { SerializedDbError } from "./db-error-registry.ts";
|
|
@@ -93,6 +101,7 @@ import { PLANS_WRITE_OPS } from "./plans-write-ops.ts";
|
|
|
93
101
|
import { CONTINUATION_ARCHIVES_WRITE_OPS } from "./continuation-archives-write-ops.ts";
|
|
94
102
|
import { WORKFLOW_JOURNAL_WRITE_OPS } from "./workflow-journal-write-ops.ts";
|
|
95
103
|
import { WORKFLOW_STEP_TASK_WRITE_OPS } from "./workflow-step-tasks.ts";
|
|
104
|
+
import { PROJECT_ENTRIES_WRITE_OPS } from "./project-entries-write-ops";
|
|
96
105
|
import { PROJECTS_WRITE_OPS } from "./projects-write-ops.ts";
|
|
97
106
|
import { CHANNELS_WRITE_OPS } from "./channels-write-ops.ts";
|
|
98
107
|
import { CI_INCIDENTS_WRITE_OPS } from "./ci-incidents-write-ops.ts";
|
|
@@ -199,6 +208,10 @@ export interface WriterOpSpec {
|
|
|
199
208
|
/** This complete operation has no main-thread effects and may use the file-backed writer. */
|
|
200
209
|
readonly offLoop?: boolean;
|
|
201
210
|
readonly idempotent: boolean;
|
|
211
|
+
/** #3614 — a non-idempotent op whose keyless callers never resubmit: it runs without a key and
|
|
212
|
+
* writes no ledger row, and a lost acknowledgement stays indeterminate for that caller. A caller
|
|
213
|
+
* that does retry still passes a key and gets the ledger's exactly-once dedup. */
|
|
214
|
+
readonly atMostOnce?: true;
|
|
202
215
|
readonly fn: WriterOp;
|
|
203
216
|
/** Sanitizes a result before it enters the durable retry ledger. Results can include a newly-created
|
|
204
217
|
* command's caller-visible plaintext env, while the ledger is readable as raw DB state. */
|
|
@@ -234,14 +247,7 @@ const PROBE_DDL = `
|
|
|
234
247
|
// durable across a worker death (the phantom-failure case), because it lives in the DB, not in memory.
|
|
235
248
|
export { WRITER_IDEMPOTENCY_RETENTION_MS };
|
|
236
249
|
|
|
237
|
-
|
|
238
|
-
* Create the seam-probe + dedup schema. This models real schema bootstrap (§4, the allowlisted
|
|
239
|
-
* bootstrap layer): DDL runs ONCE at startup as its own committed statement, NOT lazily inside an op —
|
|
240
|
-
* a rolled back op must never undo the schema it ran against. Ops below therefore assume the tables
|
|
241
|
-
* exist, just as relocated real ops assume the migrated catalog exists (H6/§3.1). Idempotent
|
|
242
|
-
* (`IF NOT EXISTS`), so it is safe to call on both the in-process connection and, at bootstrap, on the
|
|
243
|
-
* file that the worker later opens (WAL makes the committed tables visible cross-connection, §3.5).
|
|
244
|
-
*/
|
|
250
|
+
// Probe tables are installed at bootstrap, outside writer transactions.
|
|
245
251
|
export function ensureSeamProbeSchema(db: Database = getDb()): void {
|
|
246
252
|
if (db.inTransaction) throw new Error("ensureSeamProbeSchema must run at bootstrap, not inside a transaction");
|
|
247
253
|
db.run(PROBE_DDL);
|
|
@@ -277,6 +283,7 @@ export const WRITER_OPS: Readonly<Record<string, WriterOpSpec>> = Object.freeze(
|
|
|
277
283
|
// call inserts a NEW row — NON-idempotent: a retry without dedup would double-apply, so the gateway
|
|
278
284
|
// requires an idempotencyKey and the executor dedups on it.
|
|
279
285
|
"seam.append": {
|
|
286
|
+
offLoop: true,
|
|
280
287
|
idempotent: false,
|
|
281
288
|
fn: (args: { tag: string; value?: string }, ctx) => {
|
|
282
289
|
const row = getDb()
|
|
@@ -396,11 +403,17 @@ export function allWriterOps(): Readonly<Record<string, WriterOpSpec>> {
|
|
|
396
403
|
if (!mergedWriterOps) {
|
|
397
404
|
mergedWriterOps = Object.freeze({
|
|
398
405
|
...WRITER_OPS,
|
|
406
|
+
...WRITER_FAULT_TEST_OPS,
|
|
399
407
|
...VAULT_TOKEN_WRITE_OPS,
|
|
400
408
|
...VAULT_HEALTH_WRITE_OPS,
|
|
401
409
|
...WORKSPACE_REKEY_WRITE_OPS,
|
|
402
410
|
...PAIRS_WRITE_OPS,
|
|
403
411
|
...MESSAGE_WRITE_OPS,
|
|
412
|
+
...NOTIFICATION_WRITE_OPS,
|
|
413
|
+
...LIFECYCLE_TAIL_WRITE_OPS,
|
|
414
|
+
...RUNTIME_TAIL_WRITE_OPS,
|
|
415
|
+
...AUTOMATION_WRITE_OPS,
|
|
416
|
+
...RUNTIME_ADMIN_WRITE_OPS,
|
|
404
417
|
...COMMANDS_WRITE_OPS,
|
|
405
418
|
...RESTART_EPOCH_WRITE_OPS,
|
|
406
419
|
...BUS_OUTBOX_WRITE_OPS,
|
|
@@ -416,6 +429,7 @@ export function allWriterOps(): Readonly<Record<string, WriterOpSpec>> {
|
|
|
416
429
|
...TIMER_INGRESS_WRITE_OPS,
|
|
417
430
|
...TIMER_TRANSFER_WRITE_OPS,
|
|
418
431
|
...TIMER_CUTOVER_WRITE_OPS,
|
|
432
|
+
...TIMER_CUTOVER_COORDINATOR_WRITE_OPS,
|
|
419
433
|
...TASK_WRITE_OPS,
|
|
420
434
|
...AGENTS_WRITE_OPS,
|
|
421
435
|
...AGENT_LIFECYCLE_WRITE_OPS,
|
|
@@ -443,6 +457,7 @@ export function allWriterOps(): Readonly<Record<string, WriterOpSpec>> {
|
|
|
443
457
|
...WORKFLOW_STEP_TASK_WRITE_OPS,
|
|
444
458
|
...SPAWN_TREE_CLEANUP_WRITE_OPS,
|
|
445
459
|
...PROJECTS_WRITE_OPS,
|
|
460
|
+
...PROJECT_ENTRIES_WRITE_OPS,
|
|
446
461
|
...CHANNELS_WRITE_OPS,
|
|
447
462
|
...INTEGRATIONS_WRITE_OPS,
|
|
448
463
|
...CI_INCIDENTS_WRITE_OPS,
|
|
@@ -476,6 +491,7 @@ export function allWriterOps(): Readonly<Record<string, WriterOpSpec>> {
|
|
|
476
491
|
...ISSUE_REFS_WRITE_OPS,
|
|
477
492
|
...WORKSPACE_WRITE_OPS,
|
|
478
493
|
...WEB_TOOL_CALL_WRITE_OPS,
|
|
494
|
+
...HISTORY_RETENTION_WRITE_OPS,
|
|
479
495
|
...CLAUDE_API_BUDGET_WRITE_OPS,
|
|
480
496
|
...CLAUDE_APP_OAUTH_WRITE_OPS,
|
|
481
497
|
});
|
|
@@ -487,10 +503,12 @@ export function isKnownWriterOp(op: string): boolean {
|
|
|
487
503
|
return Object.prototype.hasOwnProperty.call(allWriterOps(), op);
|
|
488
504
|
}
|
|
489
505
|
|
|
490
|
-
/** Whether a registered op
|
|
491
|
-
* unknown op so a typo cannot silently pass the
|
|
492
|
-
|
|
493
|
-
|
|
506
|
+
/** Whether a registered op may be submitted without an idempotency key (H5 §3.7.1): it is safe to
|
|
507
|
+
* re-apply, or it is at-most-once. Throws for an unknown op so a typo cannot silently pass the
|
|
508
|
+
* gateway's keyless-write gate. */
|
|
509
|
+
export function writerOpAllowsKeyless(op: string): boolean {
|
|
510
|
+
const spec = resolveOp(op);
|
|
511
|
+
return spec.idempotent || spec.atMostOnce === true;
|
|
494
512
|
}
|
|
495
513
|
|
|
496
514
|
export function isOffLoopWriterOp(op: string): boolean {
|
|
@@ -512,6 +530,7 @@ function resolveOp(op: string): WriterOpSpec {
|
|
|
512
530
|
*/
|
|
513
531
|
function applyOnce(op: string, spec: WriterOpSpec, args: unknown, ctx: WriterOpContext, key: string | undefined): unknown {
|
|
514
532
|
if (spec.idempotent) return spec.fn(args, ctx); // safe to re-apply, no key needed
|
|
533
|
+
if (key === undefined && spec.atMostOnce) return spec.fn(args, ctx); // no retry, so nothing to dedup
|
|
515
534
|
// STRUCTURAL enforcement at the executor (H5 §3.7.1): a non-idempotent op MUST carry a key here, so
|
|
516
535
|
// the invariant holds even for a direct executor caller that bypassed the gateway guard. Rejecting
|
|
517
536
|
// BEFORE spec.fn means zero rows are written, so a keyless non-idempotent write can never duplicate.
|
|
@@ -24,10 +24,6 @@
|
|
|
24
24
|
// `threadId` is still recorded and checked as belt-and-braces for an exotic case where a fault is
|
|
25
25
|
// armed on a thread that later dispatches ops elsewhere.
|
|
26
26
|
//
|
|
27
|
-
// This is deliverable under the IN-PROCESS backend, which is the gateway default (`initGateway`
|
|
28
|
-
// chooses in-process even for a file DB) and therefore what the whole default suite and every
|
|
29
|
-
// existing chaos-worker subprocess run — so no existing crash seam loses its injection.
|
|
30
|
-
|
|
31
27
|
import { threadId } from "node:worker_threads";
|
|
32
28
|
|
|
33
29
|
interface ArmedWriterFault {
|
|
@@ -36,10 +32,7 @@ interface ArmedWriterFault {
|
|
|
36
32
|
readonly threadId: number;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
//
|
|
40
|
-
// writer thread this map is empty, so the lookup is a harmless no-op there. Delivery is kept honest at
|
|
41
|
-
// dispatch: while a fault is armed the in-process backend runs off-loop ops on the arming thread
|
|
42
|
-
// (writer-gateway `dispatchEntry`), and arming refuses under the worker backend.
|
|
35
|
+
// Each writer thread owns its fault registry. File-backed tests arm named actions on that thread.
|
|
43
36
|
const armed = new Map<string, ArmedWriterFault>();
|
|
44
37
|
|
|
45
38
|
/**
|
|
@@ -67,7 +60,7 @@ export function armWriterFault(name: string, fn: () => void): () => void {
|
|
|
67
60
|
throw new Error(
|
|
68
61
|
`writer fault '${name}' cannot be armed: the gateway writer backend is 'worker', so this ` +
|
|
69
62
|
`main-thread closure can never run inside the writer op's transaction and the crash would ` +
|
|
70
|
-
`not be injected at all.
|
|
63
|
+
`not be injected at all. Use :memory: for closure faults, arm a named seam.armFault action on the writer, ` +
|
|
71
64
|
`or assert the behaviour without an in-transaction crash seam.`,
|
|
72
65
|
);
|
|
73
66
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { armWriterFault } from "./writer-fault-seam";
|
|
2
|
+
import type { WriterOpSpec } from "./write-ops";
|
|
3
|
+
|
|
4
|
+
// Named actions cross the worker seam; main-thread closures cannot (#1870 S8).
|
|
5
|
+
export const WRITER_FAULT_TEST_OPS = {
|
|
6
|
+
"seam.armFault": {
|
|
7
|
+
offLoop: true,
|
|
8
|
+
idempotent: true,
|
|
9
|
+
fn: (args: { name: string; action: "kill" | "throw" }) => {
|
|
10
|
+
if (args.action !== "kill" && args.action !== "throw") throw new Error("unknown writer test fault");
|
|
11
|
+
armWriterFault(args.name, () => {
|
|
12
|
+
if (args.action === "kill") process.kill(process.pid, "SIGKILL");
|
|
13
|
+
throw new Error(`injected writer fault: ${args.name}`);
|
|
14
|
+
});
|
|
15
|
+
return null;
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
} satisfies Record<string, WriterOpSpec>;
|
package/src/db/writer-gateway.ts
CHANGED
|
@@ -42,17 +42,16 @@ import { WriterDrain } from "./writer-drain";
|
|
|
42
42
|
|
|
43
43
|
import { errMessage } from "@humwick/sdk";
|
|
44
44
|
import { createWorkerPool, type WorkerPool } from "@humwick/offload";
|
|
45
|
-
import {
|
|
46
|
-
import { armedWriterFaultCount } from "./writer-fault-seam.ts";
|
|
47
|
-
import { onLoopWriteTransaction } from "./on-loop-write-transaction.ts";
|
|
45
|
+
import { getDbPath } from "./connection.ts";
|
|
48
46
|
// On-loop-block attribution (#1882 / ADR §5). `resolveLoopCallsite` pins the effective callsite onto a
|
|
49
47
|
// submission in the CALLER's context; `captureSyncFrame` times the op body where it actually runs — in
|
|
50
48
|
// the in-process drain microtask, NOT the synchronous enqueue. One-way import (loop-block-attribution
|
|
51
49
|
// imports only event-loop-lag + config), so no cycle.
|
|
52
50
|
import { captureSyncFrame, resolveLoopCallsite } from "../loop-block-attribution.ts";
|
|
51
|
+
import { markCallerResume } from "../hub-metrics.ts";
|
|
53
52
|
import { reconstructDbError, serializeDbError } from "./db-error-registry.ts";
|
|
54
53
|
import {
|
|
55
|
-
|
|
54
|
+
writerOpAllowsKeyless,
|
|
56
55
|
isOffLoopWriterOp,
|
|
57
56
|
runWriterBatch,
|
|
58
57
|
runWriterOp,
|
|
@@ -110,7 +109,7 @@ export class WriteIndeterminateError extends Error {
|
|
|
110
109
|
// ── Options & per-call opts ───────────────────────────────────────────────────────────────────────
|
|
111
110
|
|
|
112
111
|
export interface InitWriterOptions {
|
|
113
|
-
/**
|
|
112
|
+
/** File path to attach. A live file DB always uses its worker, including legacy :memory: requests. */
|
|
114
113
|
path?: string;
|
|
115
114
|
/** Soft high-water mark on the writer queue; past it, non-critical writes shed (§3.6). */
|
|
116
115
|
queueHighWater?: number;
|
|
@@ -183,6 +182,9 @@ export interface WriterStats {
|
|
|
183
182
|
* the observable signal that write convoys are long enough to need the throttle.
|
|
184
183
|
*/
|
|
185
184
|
drainYields: number;
|
|
185
|
+
/** #3798 — writes executed since boot on the main thread and on a writer thread (a batch counts its entries). */
|
|
186
|
+
onLoopOps: number;
|
|
187
|
+
writerThreadOps: number;
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
// ── Backend state ─────────────────────────────────────────────────────────────────────────────────
|
|
@@ -243,8 +245,6 @@ interface InProcessBackend {
|
|
|
243
245
|
kind: "in-process";
|
|
244
246
|
stop: AbortController;
|
|
245
247
|
busy: boolean;
|
|
246
|
-
offLoop?: WorkerBackend;
|
|
247
|
-
offLoopGeneration?: number;
|
|
248
248
|
}
|
|
249
249
|
type Backend = WorkerBackend | InProcessBackend;
|
|
250
250
|
|
|
@@ -256,6 +256,8 @@ let writerTimeoutMs = DEFAULT_WRITER_TIMEOUT_MS;
|
|
|
256
256
|
let drainSliceMs = DEFAULT_DRAIN_SLICE_MS;
|
|
257
257
|
// Monotonic across re-inits (like any counter): a test that wants a delta reads it before and after.
|
|
258
258
|
let drainYields = 0;
|
|
259
|
+
let onLoopOps = 0;
|
|
260
|
+
let writerThreadOps = 0;
|
|
259
261
|
// Set while a writer job has blown its gateway deadline and the sole writer is stalled holding the
|
|
260
262
|
// lock (§3.6.1). Cleared when that in-flight job finally settles (the op completed and released the
|
|
261
263
|
// lock), which re-kicks the FIFO drain. Worker backend only — an in-process op runs synchronously on
|
|
@@ -265,17 +267,17 @@ let wedgedOp: { op: string; startedAt: number } | undefined;
|
|
|
265
267
|
// Test-only fault injection for the precise H5 window: the op has COMMITTED, but its
|
|
266
268
|
// acknowledgement is lost before the caller observes the result. One-shot by design so a retry
|
|
267
269
|
// exercises the persisted idempotency ledger rather than repeatedly failing at the transport edge.
|
|
268
|
-
let postCommitAckLossForTests: { op: string
|
|
270
|
+
let postCommitAckLossForTests: { op: string } | undefined;
|
|
269
271
|
|
|
270
272
|
export function rejectNextWriterAckAfterCommitForTests(op: string): void {
|
|
271
|
-
postCommitAckLossForTests = { op
|
|
273
|
+
postCommitAckLossForTests = { op };
|
|
272
274
|
}
|
|
273
275
|
|
|
274
|
-
function
|
|
275
|
-
if (
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
function resolveWriterOp(entry: Extract<Submission, { kind: "op" }>, result: unknown): void {
|
|
277
|
+
if (postCommitAckLossForTests?.op === entry.op) {
|
|
278
|
+
postCommitAckLossForTests = undefined;
|
|
279
|
+
entry.reject(new WriteIndeterminateError(entry.op, new Error("injected post-commit lost acknowledgement"), entry.opts?.idempotencyKey));
|
|
280
|
+
} else entry.resolve(result);
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
/**
|
|
@@ -284,7 +286,8 @@ function losePostCommitAckForTests(op: string): boolean {
|
|
|
284
286
|
* size-1 writer pool.
|
|
285
287
|
*/
|
|
286
288
|
export function initWriter(options: InitWriterOptions = {}): void {
|
|
287
|
-
|
|
289
|
+
// A file-backed main handle never executes writer bodies, even when an old caller requests memory.
|
|
290
|
+
const path = options.path && options.path !== ":memory:" ? options.path : getDbPath() ?? options.path;
|
|
288
291
|
// EVERY caller-supplied number is validated BEFORE any of them is assigned: a RangeError thrown
|
|
289
292
|
// after an earlier field was already written would leave the module in a half-applied config the
|
|
290
293
|
// caller never asked for.
|
|
@@ -371,7 +374,6 @@ export async function shutdownWriter(): Promise<void> {
|
|
|
371
374
|
}
|
|
372
375
|
if (current?.kind === "in-process") current.stop.abort(new Error("writer gateway shut down"));
|
|
373
376
|
if (current?.kind === "worker") await current.pool.destroy();
|
|
374
|
-
if (current?.kind === "in-process") await current.offLoop?.pool.destroy();
|
|
375
377
|
}
|
|
376
378
|
|
|
377
379
|
/** Whether the gateway is initialized (some callers guard bootstrap ordering on this). */
|
|
@@ -380,13 +382,14 @@ export function isWriterInitialized(): boolean {
|
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
export function writerStats(): WriterStats {
|
|
383
|
-
|
|
385
|
+
const ops = { drainYields, onLoopOps, writerThreadOps };
|
|
386
|
+
if (!backend) return { backend: "uninitialized", queued: 0, busy: 0, highWater: queueHighWater, wedged: null, ...ops };
|
|
384
387
|
const wedged = wedgedOp ? { op: wedgedOp.op, runningMs: Math.round(performance.now() - wedgedOp.startedAt) } : null;
|
|
385
388
|
if (backend.kind === "in-process") {
|
|
386
|
-
return { backend: "in-process", queued: pendingWriteDepth(), busy: inFlightWrites(), highWater: queueHighWater, wedged,
|
|
389
|
+
return { backend: "in-process", queued: pendingWriteDepth(), busy: inFlightWrites(), highWater: queueHighWater, wedged, ...ops };
|
|
387
390
|
}
|
|
388
391
|
const stats = backend.pool.stats;
|
|
389
|
-
return { backend: "worker", queued: pendingWriteDepth(), busy: stats.busy, highWater: queueHighWater, wedged,
|
|
392
|
+
return { backend: "worker", queued: pendingWriteDepth(), busy: stats.busy, highWater: queueHighWater, wedged, ...ops };
|
|
390
393
|
}
|
|
391
394
|
|
|
392
395
|
function requireBackend(): Backend {
|
|
@@ -450,7 +453,7 @@ function pendingWriteDepth(): number {
|
|
|
450
453
|
|
|
451
454
|
/** Includes an in-process writer waiting asynchronously for BEGIN. */
|
|
452
455
|
function inFlightWrites(): number {
|
|
453
|
-
return backend?.kind === "worker" ? backend.pool.stats.busy :
|
|
456
|
+
return backend?.kind === "worker" ? backend.pool.stats.busy : Number(backend?.busy ?? false);
|
|
454
457
|
}
|
|
455
458
|
|
|
456
459
|
/** Reject every caller attached to a submission (used for teardown / dispatch-time backend loss). */
|
|
@@ -463,7 +466,7 @@ function rejectEntry(entry: Submission, error: unknown): void {
|
|
|
463
466
|
* before the write is ever submitted, so a keyless non-idempotent write can never be retried into a
|
|
464
467
|
* duplicate. `key` is the per-write key (the batch/coalesce paths pass each entry's own key). */
|
|
465
468
|
function guardIdempotency(op: string, key: string | undefined): void {
|
|
466
|
-
if (key === undefined && !
|
|
469
|
+
if (key === undefined && !writerOpAllowsKeyless(op)) throw new WriteRetrySafetyError(op);
|
|
467
470
|
}
|
|
468
471
|
|
|
469
472
|
/** Backpressure admission (§3.6/§3.6.1). `count` is the number of writes this submission would ADD (1
|
|
@@ -553,19 +556,9 @@ async function dispatchEntry(entry: Submission): Promise<void> {
|
|
|
553
556
|
if (back.kind === "in-process") {
|
|
554
557
|
back.busy = true;
|
|
555
558
|
try {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if (path && path !== ":memory:" && isOffLoopWriterOp(entry.op) && armedWriterFaultCount() === 0) {
|
|
560
|
-
if (back.offLoop && back.offLoopGeneration !== getDbGeneration()) {
|
|
561
|
-
await back.offLoop.pool.destroy();
|
|
562
|
-
back.offLoop = undefined;
|
|
563
|
-
}
|
|
564
|
-
back.stop.signal.throwIfAborted();
|
|
565
|
-
back.offLoop ??= createFileWriter(path);
|
|
566
|
-
back.offLoopGeneration = getDbGeneration();
|
|
567
|
-
await dispatchWorker(entry, back.offLoop);
|
|
568
|
-
} else await dispatchInProcess(entry, back.stop.signal);
|
|
559
|
+
back.stop.signal.throwIfAborted();
|
|
560
|
+
onLoopOps += entryWrites(entry);
|
|
561
|
+
await dispatchInProcess(entry);
|
|
569
562
|
} catch (error) { rejectEntry(entry, error); }
|
|
570
563
|
finally { back.busy = false; }
|
|
571
564
|
return;
|
|
@@ -573,6 +566,10 @@ async function dispatchEntry(entry: Submission): Promise<void> {
|
|
|
573
566
|
await dispatchWorker(entry, back);
|
|
574
567
|
}
|
|
575
568
|
|
|
569
|
+
function entryWrites(entry: Submission): number {
|
|
570
|
+
return entry.kind === "op" ? 1 : entry.argsList.length;
|
|
571
|
+
}
|
|
572
|
+
|
|
576
573
|
/**
|
|
577
574
|
* The per-entry dedup keys for a batch/coalesce job (H5 §3.7.1). A dbWriteBatch carries ONE key for
|
|
578
575
|
* the whole call and derives a stable per-entry key `${key}#${i}`, so a blind retry of the same batch
|
|
@@ -600,18 +597,12 @@ function entryKeys(entry: Extract<Submission, { kind: "batch" | "coalesce" }>):
|
|
|
600
597
|
* (`dispatchWorker`) is deliberately NOT wrapped: its op body runs off-thread, so there is no on-loop
|
|
601
598
|
* block to attribute (only the sub-floor enqueue, which the record floor drops).
|
|
602
599
|
*/
|
|
603
|
-
async function dispatchInProcess(entry: Submission
|
|
604
|
-
const execute = <T>(fn: () => T) =>
|
|
605
|
-
fn, (attempt) => captureSyncFrame({ op: entry.op, callsite: entry.callsite }, attempt), writerTimeoutMs, signal,
|
|
606
|
-
);
|
|
600
|
+
async function dispatchInProcess(entry: Submission): Promise<void> {
|
|
601
|
+
const execute = <T>(fn: () => T) => captureSyncFrame({ op: entry.op, callsite: entry.callsite }, fn);
|
|
607
602
|
if (entry.kind === "op") {
|
|
608
603
|
try {
|
|
609
|
-
const result = cloneIn(
|
|
610
|
-
|
|
611
|
-
entry.reject(new WriteIndeterminateError(entry.op, new Error("injected post-commit lost acknowledgement"), entry.opts?.idempotencyKey));
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
entry.resolve(result);
|
|
604
|
+
const result = cloneIn(execute(() => runWriterOp(entry.op, cloneIn(entry.args), { now: entry.now, presence: cloneIn(entry.presence) }, entry.opts?.idempotencyKey)));
|
|
605
|
+
resolveWriterOp(entry, result);
|
|
615
606
|
} catch (error) {
|
|
616
607
|
entry.reject(reconstructInProcess(error));
|
|
617
608
|
}
|
|
@@ -620,7 +611,7 @@ async function dispatchInProcess(entry: Submission, signal: AbortSignal): Promis
|
|
|
620
611
|
// batch + coalesce share the SAVEPOINT-per-caller executor; only the fan-out differs.
|
|
621
612
|
let coalesced: CoalescedEntry[];
|
|
622
613
|
try {
|
|
623
|
-
const entries: BatchEntry[] =
|
|
614
|
+
const entries: BatchEntry[] = execute(() => runWriterBatch(
|
|
624
615
|
entry.op,
|
|
625
616
|
entry.argsList.map((a) => cloneIn(a)),
|
|
626
617
|
entryKeys(entry),
|
|
@@ -653,6 +644,10 @@ async function dispatchWorker(entry: Submission, back: WorkerBackend): Promise<v
|
|
|
653
644
|
// retrySafe stays false (default): a write must NEVER be auto-re-dispatched (it would repeat its
|
|
654
645
|
// effect). No pool timeout is armed — the deadline is the gateway's own (§3.6.1).
|
|
655
646
|
const startedAt = performance.now();
|
|
647
|
+
// #3798 — the drain runs outside any caller, so its dispatch and result handling are charged to the
|
|
648
|
+
// caller that submitted this entry (the reader pool does the same per read).
|
|
649
|
+
markCallerResume(entry.callsite);
|
|
650
|
+
writerThreadOps += entryWrites(entry);
|
|
656
651
|
const poolPromise = back.pool.run<WriterJobInput, WriterJobResult>(WRITER_TASK_NAME, input, { labels });
|
|
657
652
|
|
|
658
653
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
@@ -663,6 +658,7 @@ async function dispatchWorker(entry: Submission, back: WorkerBackend): Promise<v
|
|
|
663
658
|
}),
|
|
664
659
|
]);
|
|
665
660
|
if (timer) clearTimeout(timer);
|
|
661
|
+
markCallerResume(entry.callsite);
|
|
666
662
|
|
|
667
663
|
if (raced.done === "timeout") {
|
|
668
664
|
// WEDGED (§3.6.1). The op is still running and still holds the lock; do NOT replace the worker.
|
|
@@ -707,7 +703,7 @@ function fanOutWorker(entry: Submission, result: WriterJobResult): void {
|
|
|
707
703
|
entry.reject(new Error(`writer worker returned a ${result.kind} result for a single op`));
|
|
708
704
|
return;
|
|
709
705
|
}
|
|
710
|
-
if (result.ok) entry
|
|
706
|
+
if (result.ok) resolveWriterOp(entry, result.result);
|
|
711
707
|
else entry.reject(reconstructDbError(result.error));
|
|
712
708
|
return;
|
|
713
709
|
}
|