@bastani/atomic 0.9.26-alpha.4 → 0.9.26-alpha.6
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/CHANGELOG.md +27 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +3 -3
- package/dist/builtin/web-access/package.json +2 -2
- package/dist/builtin/workflows/CHANGELOG.md +17 -0
- package/dist/builtin/workflows/builtin/{chunk-73p2cm5s.js → chunk-9bhs02gm.js} +318 -52
- package/dist/builtin/workflows/builtin/{chunk-n95myjcg.js → chunk-gts8wgjr.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-ycvg8y8k.js → chunk-gw7rghda.js} +1 -1
- package/dist/builtin/workflows/builtin/goal.js +2 -2
- package/dist/builtin/workflows/builtin/index.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +2 -2
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1383 -266
- package/dist/builtin/workflows/src/index.js +1129 -234
- package/dist/core/agent-session-export.js +1 -1
- package/dist/core/agent-session-export.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts +3 -3
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +3 -3
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/api-types.d.ts +3 -1
- package/dist/core/extensions/api-types.d.ts.map +1 -1
- package/dist/core/extensions/api-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader-api.d.ts.map +1 -1
- package/dist/core/extensions/loader-api.js +5 -0
- package/dist/core/extensions/loader-api.js.map +1 -1
- package/dist/core/extensions/loader-bindings.d.ts +1 -1
- package/dist/core/extensions/loader-bindings.d.ts.map +1 -1
- package/dist/core/extensions/loader-bindings.js +1 -0
- package/dist/core/extensions/loader-bindings.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/runtime-types.d.ts +2 -1
- package/dist/core/extensions/runtime-types.d.ts.map +1 -1
- package/dist/core/extensions/runtime-types.js.map +1 -1
- package/dist/core/extensions/tool-types.d.ts +3 -0
- package/dist/core/extensions/tool-types.d.ts.map +1 -1
- package/dist/core/extensions/tool-types.js.map +1 -1
- package/dist/core/extensions/ui-types.d.ts +2 -2
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/extensions/workflow-run-control.d.ts +3 -0
- package/dist/core/extensions/workflow-run-control.d.ts.map +1 -1
- package/dist/core/extensions/workflow-run-control.js.map +1 -1
- package/dist/core/fast-model-routing.d.ts.map +1 -1
- package/dist/core/fast-model-routing.js +1 -5
- package/dist/core/fast-model-routing.js.map +1 -1
- package/dist/core/model-config.d.ts +36 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +14 -2
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver-defaults.js +1 -1
- package/dist/core/model-resolver-defaults.js.map +1 -1
- package/dist/core/package-manager-auto-resources.d.ts.map +1 -1
- package/dist/core/package-manager-auto-resources.js +6 -2
- package/dist/core/package-manager-auto-resources.js.map +1 -1
- package/dist/core/package-manager-resolver.d.ts.map +1 -1
- package/dist/core/package-manager-resolver.js +4 -2
- package/dist/core/package-manager-resolver.js.map +1 -1
- package/dist/core/provider-composer-internal.d.ts +2 -1
- package/dist/core/provider-composer-internal.d.ts.map +1 -1
- package/dist/core/provider-composer-internal.js +13 -0
- package/dist/core/provider-composer-internal.js.map +1 -1
- package/dist/core/settings-manager-resource-accessors.js +7 -3
- package/dist/core/settings-manager-resource-accessors.js.map +1 -1
- package/dist/core/settings-merge.d.ts.map +1 -1
- package/dist/core/settings-merge.js +10 -0
- package/dist/core/settings-merge.js.map +1 -1
- package/dist/core/settings-types.d.ts +7 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +20 -1
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +3 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +19 -13
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -3
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/config-selector-list.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector-list.js +3 -1
- package/dist/modes/interactive/components/config-selector-list.js.map +1 -1
- package/dist/modes/interactive/components/config-selector-project-scope.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector-project-scope.js +2 -1
- package/dist/modes/interactive/components/config-selector-project-scope.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +2 -5
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +14 -40
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +3 -3
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +6 -1
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +5 -0
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.js +9 -0
- package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.js +10 -1
- package/dist/modes/interactive-engine/engine-render-service.js.map +1 -1
- package/dist/utils/image-convert.d.ts +5 -0
- package/dist/utils/image-convert.d.ts.map +1 -1
- package/dist/utils/image-convert.js +36 -6
- package/dist/utils/image-convert.js.map +1 -1
- package/docs/extensions.md +6 -0
- package/docs/models/reference.md +28 -1
- package/docs/models.md +4 -0
- package/docs/providers.md +10 -0
- package/docs/sdk.md +26 -2
- package/docs/settings.md +28 -1
- package/docs/workflows/operations.md +40 -4
- package/npm-shrinkwrap.json +71 -92
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.26-alpha.6] - 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `samplingParamsByThinkingLevel` to `models.json` for per-thinking-level sampling parameter overrides on OpenAI-compatible APIs, including native fast routes. See [Sampling by Thinking Level](docs/models/reference.md#sampling-by-thinking-level) ([#9776](https://github.com/earendil-works/pi/pull/9776) by [@mrexodia](https://github.com/mrexodia))
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed fresh SDK sessions being unable to inspect or resume crashed durable workflow runs from another process. Hosts using `SessionManager.inMemory()` can recover by full run UUID in the original working directory and workflow database without reopening a session file. Database ownership fencing protects healthy connected owners, including across cloned VM identities, and rejects late durable writes after connection loss. Older unfenced runs support explicit controlled enrollment after their workers stop. Completed checkpoints are preserved; interrupted external effects still require idempotency ([#3419](https://github.com/bastani-inc/atomic/issues/3419)).
|
|
14
|
+
- Fixed managed workflow database monitoring timeouts unnecessarily dropping healthy run ownership. A persistent timeout now reports database unavailability without closing existing ownership connections ([#3419](https://github.com/bastani-inc/atomic/issues/3419)).
|
|
15
|
+
|
|
16
|
+
## [0.9.26-alpha.5] - 2026-10-03
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added `pi.registerToolRenderer()` to choose renderers for any tool, including tools not yet registered ([#10285](https://github.com/earendil-works/pi/issues/10285)).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed managed workflow PostgreSQL staying down after shutdown when its old launch options were missing. Database-unavailable errors and workflow status now show the provider, credential-redacted endpoint, and retained recovery refusal or failure ([#3413](https://github.com/bastani-inc/atomic/issues/3413)).
|
|
25
|
+
- Fixed NVIDIA's built-in default pointing to a model missing from its current catalog. Automatic selection now prefers Nemotron 3 Ultra 550B A55B.
|
|
26
|
+
- Fixed MCP tool calls in resumed sessions and HTML exports rendering fully expanded before their server connects, or when it never connects ([#10285](https://github.com/earendil-works/pi/issues/10285)).
|
|
27
|
+
- Fixed JPEG, GIF, and WebP images rendered by extensions through `Image` not appearing on Kitty-protocol terminals ([#10292](https://github.com/earendil-works/pi/issues/10292)).
|
|
28
|
+
- Fixed fullscreen Kitty images collapsing after scrolling in WezTerm ([#10319](https://github.com/earendil-works/pi/issues/10319)).
|
|
29
|
+
- Fixed `codemode` scripts that print in a loop exhausting memory. Scripts now fail after exceeding 16 Mi characters or 100000 output items ([#10283](https://github.com/earendil-works/pi/issues/10283)).
|
|
30
|
+
- Fixed resumed MCP calls and HTML exports assigning the wrong server to ambiguous tool names. Calls without known renderers now retain their full identifier when the server/tool boundary cannot be recovered.
|
|
31
|
+
|
|
5
32
|
## [0.9.26-alpha.3] - 2026-10-03
|
|
6
33
|
|
|
7
34
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.26-alpha.
|
|
3
|
+
"version": "0.9.26-alpha.6",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@bastani/atomic": "*",
|
|
47
|
-
"@earendil-works/pi-tui": "^1.0.
|
|
47
|
+
"@earendil-works/pi-tui": "^1.0.2",
|
|
48
48
|
"typebox": "*"
|
|
49
49
|
},
|
|
50
50
|
"peerDependenciesMeta": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.26-alpha.
|
|
3
|
+
"version": "0.9.26-alpha.6",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@bastani/atomic": "*",
|
|
37
37
|
"@bastani/pi-ai": "*",
|
|
38
|
-
"@earendil-works/pi-agent-core": "^1.0.
|
|
39
|
-
"@earendil-works/pi-tui": "^1.0.
|
|
38
|
+
"@earendil-works/pi-agent-core": "^1.0.2",
|
|
39
|
+
"@earendil-works/pi-tui": "^1.0.2",
|
|
40
40
|
"typebox": "*"
|
|
41
41
|
},
|
|
42
42
|
"peerDependenciesMeta": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.26-alpha.
|
|
3
|
+
"version": "0.9.26-alpha.6",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@bastani/atomic": "*",
|
|
34
|
-
"@earendil-works/pi-tui": "^1.0.
|
|
34
|
+
"@earendil-works/pi-tui": "^1.0.2"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@bastani/atomic": {
|
|
@@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.26-alpha.6] - 2026-10-04
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed SDK inspection rejecting earlier-process durable runs and crash recovery retaining the old session owner. Explicit SDK resume atomically adopts an eligible crashed root in the same working directory and workflow database, preserves its UUID and completed checkpoints, and excludes healthy connected owners and competing adopters. Database ownership fencing handles cloned VM identities and rejects disconnected owners' late durable writes. Older unfenced runs support controlled enrollment after their workers stop; unfinished external callbacks may overlap recovery and must use idempotent effects ([#3419](https://github.com/bastani-inc/atomic/issues/3419)).
|
|
14
|
+
- Fixed managed PostgreSQL monitoring connection timeouts unnecessarily closing healthy workflow ownership connections. Monitoring retries once; a persistent timeout reports database unavailability without discarding connected owners or restarting the database ([#3419](https://github.com/bastani-inc/atomic/issues/3419)).
|
|
15
|
+
|
|
16
|
+
## [0.9.26-alpha.5] - 2026-10-03
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added `workflows.durability.systemDatabaseUrl` and `systemDatabaseUrlFile` in global and trusted project settings to choose a persistent workflow database without launcher-specific environment configuration. `DBOS_SYSTEM_DATABASE_URL` retains precedence; explicit selections never provision embedded PostgreSQL or Docker. Interactive and RPC sessions report the selected provider and host without credentials on the first workflow action; headless sessions print only a Docker fallback selection to stderr. Existing workflow-path arrays remain supported alongside the new object form with `paths` ([#3416](https://github.com/bastani-inc/atomic/issues/3416)).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed managed PostgreSQL staying down after shutdown when its old launch options were missing. Recovery preserves cluster ownership and identity checks, and database-unavailable admission errors and workflow status now show the provider, credential-redacted endpoint, and retained probe or recovery failure ([#3413](https://github.com/bastani-inc/atomic/issues/3413)).
|
|
25
|
+
|
|
9
26
|
## [0.9.26-alpha.4] - 2026-10-02
|
|
10
27
|
|
|
11
28
|
### Fixed
|
|
@@ -5107,10 +5107,97 @@ function getDurableBackendProcessOwner() {
|
|
|
5107
5107
|
}
|
|
5108
5108
|
// dist/builtin/workflows/src/durable/dbos-admission.ts
|
|
5109
5109
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5110
|
+
|
|
5111
|
+
// dist/builtin/workflows/src/durable/dbos-database-diagnostics.ts
|
|
5112
|
+
function redactedDatabaseEndpoint(url) {
|
|
5113
|
+
try {
|
|
5114
|
+
const parsed = new URL(url);
|
|
5115
|
+
parsed.username = "";
|
|
5116
|
+
parsed.password = "";
|
|
5117
|
+
parsed.search = "";
|
|
5118
|
+
parsed.hash = "";
|
|
5119
|
+
return parsed.toString();
|
|
5120
|
+
} catch {
|
|
5121
|
+
return "unparseable PostgreSQL endpoint";
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
function redactedDatabaseMessage(message, url) {
|
|
5125
|
+
let safe = message.replace(/postgres(?:ql)?:\/\/[^\s"'<>]+/gi, (value) => redactedDatabaseEndpoint(value));
|
|
5126
|
+
let configuredSecrets = [];
|
|
5127
|
+
if (url !== undefined) {
|
|
5128
|
+
try {
|
|
5129
|
+
const parsed = new URL(url);
|
|
5130
|
+
configuredSecrets = [parsed.username, parsed.password, ...parsed.searchParams.values()].flatMap((secret) => {
|
|
5131
|
+
try {
|
|
5132
|
+
return [secret, decodeURIComponent(secret)];
|
|
5133
|
+
} catch {
|
|
5134
|
+
return [secret];
|
|
5135
|
+
}
|
|
5136
|
+
}).filter((secret) => secret.length > 0);
|
|
5137
|
+
} catch {
|
|
5138
|
+
return "PostgreSQL diagnostic could not be safely redacted; check the configured endpoint.";
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
safe = safe.replace(/\b(password|passwd|token|secret|sslpassword|user(?:name)?)\s*(?:([=:])\s*|((?:is|was)\s+)|\s+)(?:"([^"]*)"|'([^']*)'|([^\s,;]+))/gi, (match, key, separator, filler, doubleQuoted, singleQuoted, bare) => {
|
|
5142
|
+
const value = doubleQuoted ?? singleQuoted ?? bare ?? "";
|
|
5143
|
+
const isUserContext = /^user(?:name)?$/i.test(key);
|
|
5144
|
+
if (separator === undefined && !isUserContext && !configuredSecrets.includes(value))
|
|
5145
|
+
return match;
|
|
5146
|
+
const rendered = doubleQuoted !== undefined ? '"[redacted]"' : singleQuoted !== undefined ? "'[redacted]'" : "[redacted]";
|
|
5147
|
+
return `${key}${separator ?? " "}${filler ?? ""}${rendered}`;
|
|
5148
|
+
});
|
|
5149
|
+
for (const secret of new Set(configuredSecrets)) {
|
|
5150
|
+
if (secret.length < 4)
|
|
5151
|
+
continue;
|
|
5152
|
+
const escaped = secret.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5153
|
+
const expression = new RegExp(`(?<![\\p{L}\\p{N}_\\[])${escaped}(?![\\p{L}\\p{N}_\\]])`, "gu");
|
|
5154
|
+
safe = safe.replace(expression, "[redacted]");
|
|
5155
|
+
}
|
|
5156
|
+
return safe;
|
|
5157
|
+
}
|
|
5158
|
+
function databaseDependencyMessage(message, diagnostics) {
|
|
5159
|
+
const safe = redactedDatabaseMessage(message, diagnostics?.url);
|
|
5160
|
+
if (diagnostics === undefined)
|
|
5161
|
+
return safe;
|
|
5162
|
+
const failure = diagnostics.failure === undefined ? "" : ` Last probe/recovery failure: ${redactedDatabaseMessage(diagnostics.failure, diagnostics.url)}`;
|
|
5163
|
+
return `${safe} Provider: ${diagnostics.provider}; endpoint: ${redactedDatabaseEndpoint(diagnostics.url)}.${failure}`;
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
// dist/builtin/workflows/src/durable/dbos-process-owner.ts
|
|
5167
|
+
var DBOS_PROCESS_OWNER_KEY = Symbol.for("atomic-workflows/dbos-process-owner@1");
|
|
5168
|
+
function emptyOwner2() {
|
|
5169
|
+
return {
|
|
5170
|
+
version: 1,
|
|
5171
|
+
state: "uninitialized",
|
|
5172
|
+
configured: undefined,
|
|
5173
|
+
active: undefined,
|
|
5174
|
+
launchPromise: undefined,
|
|
5175
|
+
shutdownPromise: undefined,
|
|
5176
|
+
failure: undefined,
|
|
5177
|
+
wrappers: undefined,
|
|
5178
|
+
leases: new Set
|
|
5179
|
+
};
|
|
5180
|
+
}
|
|
5181
|
+
function ownerBag2() {
|
|
5182
|
+
return globalThis;
|
|
5183
|
+
}
|
|
5184
|
+
function getDbosProcessOwner() {
|
|
5185
|
+
const bag = ownerBag2();
|
|
5186
|
+
const existing = bag[DBOS_PROCESS_OWNER_KEY];
|
|
5187
|
+
if (existing !== undefined && existing.version === 1)
|
|
5188
|
+
return existing;
|
|
5189
|
+
const created = emptyOwner2();
|
|
5190
|
+
bag[DBOS_PROCESS_OWNER_KEY] = created;
|
|
5191
|
+
return created;
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
// dist/builtin/workflows/src/durable/dbos-admission.ts
|
|
5110
5195
|
class DbosDependencyError extends Error {
|
|
5111
5196
|
code = "ATOMIC_DBOS_DEPENDENCY";
|
|
5112
|
-
|
|
5113
|
-
|
|
5197
|
+
admissionDetail;
|
|
5198
|
+
constructor(message = "Workflow database unavailable during admission. Restore PostgreSQL before retrying.", diagnostics = getDbosProcessOwner().databaseDiagnostics?.(), admissionDetail) {
|
|
5199
|
+
super(databaseDependencyMessage(message, diagnostics ?? undefined));
|
|
5200
|
+
this.admissionDetail = admissionDetail === undefined ? undefined : redactedDatabaseMessage(admissionDetail);
|
|
5114
5201
|
this.name = "DbosDependencyError";
|
|
5115
5202
|
}
|
|
5116
5203
|
}
|
|
@@ -5218,36 +5305,186 @@ var STATUS_RELATED_MESSAGE_REFINEMENT_CODES = new Set([
|
|
|
5218
5305
|
"quota_limited",
|
|
5219
5306
|
"cancelled"
|
|
5220
5307
|
]);
|
|
5221
|
-
// dist/builtin/workflows/src/durable/dbos-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5308
|
+
// dist/builtin/workflows/src/durable/dbos-owner-fence.ts
|
|
5309
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
5310
|
+
|
|
5311
|
+
// dist/builtin/workflows/src/durable/dbos-admission-pool.ts
|
|
5312
|
+
var connectionErrnos = new Set([
|
|
5313
|
+
"ECONNRESET",
|
|
5314
|
+
"ECONNREFUSED",
|
|
5315
|
+
"ECONNABORTED",
|
|
5316
|
+
"EHOSTUNREACH",
|
|
5317
|
+
"ENETUNREACH",
|
|
5318
|
+
"ETIMEDOUT",
|
|
5319
|
+
"ENOTFOUND",
|
|
5320
|
+
"EAI_AGAIN",
|
|
5321
|
+
"EPIPE"
|
|
5322
|
+
]);
|
|
5323
|
+
var connectionMessages = new Set([
|
|
5324
|
+
"Connection terminated unexpectedly",
|
|
5325
|
+
"Connection terminated due to connection timeout",
|
|
5326
|
+
"Client has encountered a connection error and is not queryable",
|
|
5327
|
+
"timeout exceeded when trying to connect",
|
|
5328
|
+
"Client was closed and is not queryable",
|
|
5329
|
+
"Postgres connection pool changed while acquiring a connection"
|
|
5330
|
+
]);
|
|
5331
|
+
|
|
5332
|
+
// dist/builtin/workflows/src/durable/dbos-row-guard.ts
|
|
5333
|
+
import { createHash } from "node:crypto";
|
|
5334
|
+
var guardSql = `
|
|
5335
|
+
CREATE TABLE IF NOT EXISTS dbos.atomic_owner_generation (
|
|
5336
|
+
root text PRIMARY KEY, owner text, generation bigint,
|
|
5337
|
+
terminal boolean NOT NULL DEFAULT false, claim text, claimant text, protocol integer NOT NULL DEFAULT 1 CHECK(protocol = 1)
|
|
5338
|
+
);
|
|
5339
|
+
CREATE TABLE IF NOT EXISTS dbos.atomic_guard_rows (workflow_uuid text PRIMARY KEY, root text NOT NULL);
|
|
5340
|
+
CREATE OR REPLACE FUNCTION dbos.atomic_metadata(value text) RETURNS jsonb
|
|
5341
|
+
LANGUAGE plpgsql IMMUTABLE SET search_path = pg_catalog AS $$
|
|
5342
|
+
DECLARE decoded jsonb;
|
|
5343
|
+
BEGIN
|
|
5344
|
+
IF value IS NULL THEN RETURN NULL; END IF;
|
|
5345
|
+
BEGIN decoded := value::jsonb; EXCEPTION WHEN invalid_text_representation THEN RETURN NULL; END;
|
|
5346
|
+
IF decoded->>'__dbos_serializer' = 'superjson' THEN decoded := decoded->'json'; END IF;
|
|
5347
|
+
IF decoded->>'__atomicDurableMetadata' IS DISTINCT FROM 'true' OR decoded->>'version' IS DISTINCT FROM '3' THEN RETURN NULL; END IF;
|
|
5348
|
+
RETURN decoded->'metadata';
|
|
5349
|
+
END $$;
|
|
5350
|
+
CREATE OR REPLACE FUNCTION dbos.atomic_guard_row() RETURNS trigger
|
|
5351
|
+
LANGUAGE plpgsql SET search_path = pg_catalog AS $$
|
|
5352
|
+
DECLARE
|
|
5353
|
+
row_value jsonb; id text; root_id text; authority jsonb; state dbos.atomic_owner_generation%ROWTYPE;
|
|
5354
|
+
meta jsonb; prior jsonb; prior_generation bigint; next_generation bigint; absorbing boolean;
|
|
5355
|
+
allowed boolean; cap_owner text; cap_generation bigint; claim_token text;
|
|
5356
|
+
BEGIN
|
|
5357
|
+
row_value := CASE WHEN TG_OP = 'DELETE' THEN to_jsonb(OLD) ELSE to_jsonb(NEW) END;
|
|
5358
|
+
id := row_value->>'workflow_uuid';
|
|
5359
|
+
root_id := split_part(id, ':checkpoint:', 1);
|
|
5360
|
+
IF TG_OP = 'UPDATE' AND to_jsonb(OLD)->>'workflow_uuid' IS DISTINCT FROM id AND (
|
|
5361
|
+
EXISTS(SELECT 1 FROM dbos.atomic_guard_rows WHERE workflow_uuid = to_jsonb(OLD)->>'workflow_uuid') OR
|
|
5362
|
+
(TG_TABLE_NAME = 'workflow_status' AND to_jsonb(OLD)->>'name' IN ('atomicWorkflowHandle','atomicWorkflowCheckpoint')) OR
|
|
5363
|
+
(TG_TABLE_NAME = 'operation_outputs' AND EXISTS(SELECT 1 FROM dbos.workflow_status WHERE workflow_uuid = to_jsonb(OLD)->>'workflow_uuid' AND name IN ('atomicWorkflowHandle','atomicWorkflowCheckpoint')))
|
|
5364
|
+
) THEN
|
|
5365
|
+
IF NULLIF(current_setting('atomic.row_authority',true),'') IS NULL OR current_setting('atomic.row_authority',true) = 'null' THEN RETURN NULL; END IF;
|
|
5366
|
+
RAISE EXCEPTION 'Atomic workflow identities are immutable';
|
|
5367
|
+
END IF;
|
|
5368
|
+
IF TG_TABLE_NAME = 'workflow_status' AND NOT (
|
|
5369
|
+
COALESCE(row_value->>'name' IN ('atomicWorkflowHandle','atomicWorkflowCheckpoint'), false) OR
|
|
5370
|
+
COALESCE(to_jsonb(OLD)->>'name' IN ('atomicWorkflowHandle','atomicWorkflowCheckpoint'), false) OR
|
|
5371
|
+
EXISTS(SELECT 1 FROM dbos.atomic_guard_rows WHERE workflow_uuid = id)
|
|
5372
|
+
) THEN
|
|
5373
|
+
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
|
|
5374
|
+
END IF;
|
|
5375
|
+
authority := NULLIF(current_setting('atomic.row_authority', true), '')::jsonb;
|
|
5376
|
+
IF TG_TABLE_NAME = 'operation_outputs' AND NOT EXISTS (
|
|
5377
|
+
SELECT 1 FROM dbos.workflow_status WHERE workflow_uuid = id AND name IN ('atomicWorkflowHandle','atomicWorkflowCheckpoint')
|
|
5378
|
+
) AND NOT EXISTS (SELECT 1 FROM dbos.atomic_guard_rows WHERE workflow_uuid = id) THEN
|
|
5379
|
+
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
|
|
5380
|
+
END IF;
|
|
5381
|
+
INSERT INTO dbos.atomic_guard_rows VALUES(id, root_id) ON CONFLICT DO NOTHING;
|
|
5382
|
+
INSERT INTO dbos.atomic_guard_rows SELECT workflow_uuid, root_id FROM dbos.workflow_status
|
|
5383
|
+
WHERE workflow_uuid = root_id AND name = 'atomicWorkflowHandle' ON CONFLICT DO NOTHING;
|
|
5384
|
+
INSERT INTO dbos.atomic_owner_generation(root) VALUES(root_id)
|
|
5385
|
+
ON CONFLICT(root) DO UPDATE SET root = EXCLUDED.root RETURNING * INTO state;
|
|
5386
|
+
IF state.owner IS NULL THEN
|
|
5387
|
+
IF TG_TABLE_NAME <> 'workflow_status' OR TG_OP = 'DELETE' OR row_value->>'status' <> 'SUCCESS'
|
|
5388
|
+
OR id NOT LIKE root_id || ':checkpoint:__atomic_metadata:%'
|
|
5389
|
+
OR authority->>'root' IS DISTINCT FROM root_id OR authority->>'enroll' IS DISTINCT FROM 'true' THEN
|
|
5390
|
+
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
|
|
5391
|
+
END IF;
|
|
5392
|
+
meta := dbos.atomic_metadata(row_value->>'output');
|
|
5393
|
+
IF meta IS NULL OR meta->>'workflowId' IS DISTINCT FROM root_id THEN
|
|
5394
|
+
RAISE EXCEPTION 'Atomic metadata encoding is unavailable for enrollment';
|
|
5395
|
+
END IF;
|
|
5396
|
+
SELECT candidate.metadata, candidate.generation INTO prior, prior_generation FROM (
|
|
5397
|
+
SELECT dbos.atomic_metadata(output) AS metadata,
|
|
5398
|
+
split_part(substring(workflow_uuid FROM length(root_id) + 13), ':', 2)::bigint AS generation
|
|
5399
|
+
FROM dbos.workflow_status
|
|
5400
|
+
WHERE workflow_uuid LIKE root_id || ':checkpoint:__atomic_metadata:%' AND status = 'SUCCESS'
|
|
5401
|
+
) candidate WHERE candidate.metadata IS NOT NULL
|
|
5402
|
+
ORDER BY COALESCE(candidate.metadata->>'status' IN ('completed', 'cancelled') OR
|
|
5403
|
+
(candidate.metadata->>'status' IN ('failed','blocked') AND candidate.metadata->>'resumable' = 'false'), false) DESC,
|
|
5404
|
+
candidate.generation DESC LIMIT 1;
|
|
5405
|
+
IF prior_generation IS DISTINCT FROM (authority->>'generation')::bigint
|
|
5406
|
+
OR prior->>'ownerExecutorId' IS DISTINCT FROM authority->>'owner' THEN
|
|
5407
|
+
RAISE EXCEPTION 'Atomic ownership generation changed before enrollment';
|
|
5408
|
+
END IF;
|
|
5409
|
+
ELSE
|
|
5410
|
+
IF authority->>'root' IS DISTINCT FROM root_id THEN RETURN NULL; END IF;
|
|
5411
|
+
cap_owner := authority->>'owner'; cap_generation := (authority->>'generation')::bigint;
|
|
5412
|
+
allowed := cap_owner = state.owner AND cap_generation = state.generation AND (
|
|
5413
|
+
authority->>'actor' = state.owner OR
|
|
5414
|
+
(authority->>'claim' = state.claim AND authority->>'actor' = state.claimant) OR
|
|
5415
|
+
(authority->>'claim' IS NOT NULL AND authority->>'checkpoint' = id));
|
|
5416
|
+
IF NOT COALESCE(allowed, false) THEN RAISE EXCEPTION 'Atomic ownership generation changed; stale database writes are refused'; END IF;
|
|
5417
|
+
END IF;
|
|
5418
|
+
IF TG_TABLE_NAME = 'workflow_status' AND TG_OP = 'UPDATE' THEN
|
|
5419
|
+
IF OLD.status = 'SUCCESS' AND OLD.name = 'atomicWorkflowCheckpoint' THEN
|
|
5420
|
+
IF NEW.output IS DISTINCT FROM OLD.output OR NEW.status <> 'SUCCESS' THEN RAISE EXCEPTION 'Completed Atomic checkpoints are immutable'; END IF;
|
|
5421
|
+
RETURN NEW;
|
|
5422
|
+
END IF;
|
|
5423
|
+
END IF;
|
|
5424
|
+
IF TG_TABLE_NAME = 'workflow_status' AND TG_OP <> 'DELETE' AND row_value->>'status' = 'SUCCESS'
|
|
5425
|
+
AND id LIKE root_id || ':checkpoint:__atomic_metadata:%' THEN
|
|
5426
|
+
meta := dbos.atomic_metadata(row_value->>'output');
|
|
5427
|
+
IF meta IS NULL OR meta->>'workflowId' IS DISTINCT FROM root_id OR meta->>'ownerExecutorId' IS DISTINCT FROM authority->>'actor' THEN RAISE EXCEPTION 'Atomic metadata encoding or owner is unavailable'; END IF;
|
|
5428
|
+
next_generation := split_part(substring(id FROM length(root_id) + 13), ':', 2)::bigint;
|
|
5429
|
+
absorbing := COALESCE(meta->>'status' IN ('completed', 'cancelled') OR
|
|
5430
|
+
(meta->>'status' IN ('failed','blocked') AND meta->>'resumable' = 'false'), false);
|
|
5431
|
+
claim_token := meta->>'transitionClaimId';
|
|
5432
|
+
IF state.root IS NOT NULL AND (next_generation < state.generation OR (state.terminal AND NOT absorbing)) THEN
|
|
5433
|
+
RAISE EXCEPTION 'Atomic terminal or newer generation cannot be overwritten';
|
|
5434
|
+
END IF;
|
|
5435
|
+
IF state.owner IS NOT NULL AND authority->>'actor' <> state.owner THEN
|
|
5436
|
+
IF id NOT LIKE '%:claim' OR next_generation <> state.generation + 1 OR claim_token IS DISTINCT FROM authority->>'claim' OR meta->>'ownerExecutorId' IS DISTINCT FROM authority->>'actor' THEN
|
|
5437
|
+
RAISE EXCEPTION 'Atomic ownership transfer requires an exact generation claim';
|
|
5438
|
+
END IF;
|
|
5439
|
+
END IF;
|
|
5440
|
+
END IF;
|
|
5441
|
+
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
|
|
5442
|
+
END $$;
|
|
5443
|
+
CREATE OR REPLACE FUNCTION dbos.atomic_project_metadata() RETURNS trigger
|
|
5444
|
+
LANGUAGE plpgsql SET search_path = pg_catalog AS $$
|
|
5445
|
+
DECLARE root_id text; meta jsonb; authority jsonb; state dbos.atomic_owner_generation%ROWTYPE; projected_generation bigint; absorbing boolean;
|
|
5446
|
+
BEGIN
|
|
5447
|
+
IF NEW.name <> 'atomicWorkflowCheckpoint' OR NEW.status <> 'SUCCESS' THEN RETURN NEW; END IF;
|
|
5448
|
+
root_id := split_part(NEW.workflow_uuid, ':checkpoint:', 1);
|
|
5449
|
+
IF NEW.workflow_uuid NOT LIKE root_id || ':checkpoint:__atomic_metadata:%' THEN RETURN NEW; END IF;
|
|
5450
|
+
IF TG_OP = 'UPDATE' THEN IF OLD.status = 'SUCCESS' THEN RETURN NEW; END IF; END IF;
|
|
5451
|
+
authority := NULLIF(current_setting('atomic.row_authority', true), '')::jsonb;
|
|
5452
|
+
SELECT * INTO state FROM dbos.atomic_owner_generation WHERE root = root_id FOR UPDATE;
|
|
5453
|
+
IF state.owner IS NULL AND authority->>'enroll' IS DISTINCT FROM 'true' THEN RETURN NEW; END IF;
|
|
5454
|
+
meta := dbos.atomic_metadata(NEW.output);
|
|
5455
|
+
projected_generation := split_part(substring(NEW.workflow_uuid FROM length(root_id) + 13), ':', 2)::bigint;
|
|
5456
|
+
absorbing := COALESCE(meta->>'status' IN ('completed','cancelled') OR (meta->>'status' IN ('failed','blocked') AND meta->>'resumable' = 'false'), false);
|
|
5457
|
+
UPDATE dbos.atomic_owner_generation SET owner = meta->>'ownerExecutorId', generation = projected_generation,
|
|
5458
|
+
terminal = absorbing, claim = CASE WHEN NEW.workflow_uuid LIKE '%:claim' THEN meta->>'transitionClaimId' END,
|
|
5459
|
+
claimant = CASE WHEN NEW.workflow_uuid LIKE '%:claim' THEN authority->>'actor' END WHERE root = root_id;
|
|
5460
|
+
RETURN NEW;
|
|
5461
|
+
END $$;
|
|
5462
|
+
DO $$ BEGIN
|
|
5463
|
+
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = 'dbos.workflow_status'::regclass AND tgname = 'atomic_generation_guard') THEN
|
|
5464
|
+
CREATE TRIGGER atomic_generation_guard BEFORE INSERT OR UPDATE OR DELETE ON dbos.workflow_status
|
|
5465
|
+
FOR EACH ROW EXECUTE FUNCTION dbos.atomic_guard_row();
|
|
5466
|
+
END IF;
|
|
5467
|
+
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = 'dbos.operation_outputs'::regclass AND tgname = 'atomic_generation_guard') THEN
|
|
5468
|
+
CREATE TRIGGER atomic_generation_guard BEFORE INSERT OR UPDATE OR DELETE ON dbos.operation_outputs
|
|
5469
|
+
FOR EACH ROW EXECUTE FUNCTION dbos.atomic_guard_row();
|
|
5470
|
+
END IF;
|
|
5471
|
+
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = 'dbos.workflow_status'::regclass AND tgname = 'atomic_generation_projection') THEN
|
|
5472
|
+
CREATE TRIGGER atomic_generation_projection AFTER INSERT OR UPDATE ON dbos.workflow_status
|
|
5473
|
+
FOR EACH ROW EXECUTE FUNCTION dbos.atomic_project_metadata();
|
|
5474
|
+
END IF;
|
|
5475
|
+
END $$;
|
|
5476
|
+
`;
|
|
5477
|
+
var fingerprint = createHash("sha256").update(guardSql).digest("hex");
|
|
5248
5478
|
|
|
5249
|
-
// dist/builtin/workflows/src/durable/dbos-
|
|
5250
|
-
var
|
|
5479
|
+
// dist/builtin/workflows/src/durable/dbos-owner-fence.ts
|
|
5480
|
+
var contextKey = Symbol.for("atomic-workflows/dbos-owner-write-context@1");
|
|
5481
|
+
var contextBag = globalThis;
|
|
5482
|
+
contextBag[contextKey] ??= new AsyncLocalStorage2;
|
|
5483
|
+
var writeContext = contextBag[contextKey];
|
|
5484
|
+
var rowContextKey = Symbol.for("atomic-workflows/dbos-row-authority@1");
|
|
5485
|
+
var rowContextBag = globalThis;
|
|
5486
|
+
rowContextBag[rowContextKey] ??= new AsyncLocalStorage2;
|
|
5487
|
+
var rowContext = rowContextBag[rowContextKey];
|
|
5251
5488
|
|
|
5252
5489
|
// dist/builtin/workflows/src/durable/dbos-system-database-url.ts
|
|
5253
5490
|
function nonEmptyTrimmed(value) {
|
|
@@ -5319,7 +5556,7 @@ import { tmpdir, uptime } from "node:os";
|
|
|
5319
5556
|
import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute3, join as join4, relative as relative2, sep as sep2 } from "node:path";
|
|
5320
5557
|
|
|
5321
5558
|
// dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts
|
|
5322
|
-
import { createHash } from "node:crypto";
|
|
5559
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5323
5560
|
import {
|
|
5324
5561
|
chmod,
|
|
5325
5562
|
chown,
|
|
@@ -5802,8 +6039,8 @@ async function snapshotSourceRuntime(root, publisher, progress) {
|
|
|
5802
6039
|
const rootStat = await lstat(root);
|
|
5803
6040
|
assertRuntimeRoot(root, rootStat);
|
|
5804
6041
|
const rootRealPath = await realpath(root);
|
|
5805
|
-
const sourceHash =
|
|
5806
|
-
const sealedHash =
|
|
6042
|
+
const sourceHash = createHash2("sha256");
|
|
6043
|
+
const sealedHash = createHash2("sha256");
|
|
5807
6044
|
await hashSourceEntry(sourceHash, sealedHash, root, ".", rootRealPath, publisher, progress);
|
|
5808
6045
|
return { sourceIdentity: sourceHash.digest("hex"), sealedIdentity: sealedHash.digest("hex") };
|
|
5809
6046
|
}
|
|
@@ -6068,7 +6305,7 @@ async function snapshotSealedManifest(root, progress) {
|
|
|
6068
6305
|
const rootStat = await lstat(root);
|
|
6069
6306
|
assertRuntimeRoot(root, rootStat);
|
|
6070
6307
|
const rootRealPath = await realpath(root);
|
|
6071
|
-
const hash =
|
|
6308
|
+
const hash = createHash2("sha256");
|
|
6072
6309
|
const entries = [];
|
|
6073
6310
|
await hashSealedEntry(hash, root, ".", rootRealPath, progress, entries);
|
|
6074
6311
|
return { version: 1, sealedIdentity: hash.digest("hex"), entries };
|
|
@@ -6301,6 +6538,13 @@ function resolveEmbeddedPostgresTarget(host) {
|
|
|
6301
6538
|
// dist/builtin/workflows/src/durable/dbos-postgres-health.ts
|
|
6302
6539
|
var HEALTH_INTERVAL_MS = 5000;
|
|
6303
6540
|
var RECOVERY_ATTEMPTS = 3;
|
|
6541
|
+
function isMonitoringConnectionTimeout(error) {
|
|
6542
|
+
if (!(error instanceof Error))
|
|
6543
|
+
return false;
|
|
6544
|
+
if ("code" in error)
|
|
6545
|
+
return error.code === "ETIMEDOUT";
|
|
6546
|
+
return error.message === "timeout expired" || error.message === "Connection terminated due to connection timeout";
|
|
6547
|
+
}
|
|
6304
6548
|
function isMonitoringConnectionFailure(error) {
|
|
6305
6549
|
if (!(error instanceof Error))
|
|
6306
6550
|
return false;
|
|
@@ -6321,6 +6565,7 @@ class PostgresHealth {
|
|
|
6321
6565
|
attempts = 0;
|
|
6322
6566
|
nextRecoveryAt = 0;
|
|
6323
6567
|
failure;
|
|
6568
|
+
healthySinceFailure = false;
|
|
6324
6569
|
revision = 0;
|
|
6325
6570
|
listeners = new Set;
|
|
6326
6571
|
constructor(operations) {
|
|
@@ -6329,6 +6574,18 @@ class PostgresHealth {
|
|
|
6329
6574
|
get lastFailure() {
|
|
6330
6575
|
return this.failure;
|
|
6331
6576
|
}
|
|
6577
|
+
get endpoint() {
|
|
6578
|
+
return this.operations.url?.();
|
|
6579
|
+
}
|
|
6580
|
+
retainFailure(error) {
|
|
6581
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6582
|
+
const safe = redactedDatabaseMessage(message, this.endpoint);
|
|
6583
|
+
this.failure = new Error(safe);
|
|
6584
|
+
this.healthySinceFailure = false;
|
|
6585
|
+
}
|
|
6586
|
+
dependencyFailure(message) {
|
|
6587
|
+
return new DbosDependencyError(message, this.endpoint === undefined ? undefined : { provider: "managed", url: this.endpoint, failure: this.failure?.message });
|
|
6588
|
+
}
|
|
6332
6589
|
subscribe(listener) {
|
|
6333
6590
|
this.listeners.add(listener);
|
|
6334
6591
|
return () => this.listeners.delete(listener);
|
|
@@ -6351,12 +6608,15 @@ class PostgresHealth {
|
|
|
6351
6608
|
await this.operations.validate?.(client);
|
|
6352
6609
|
}
|
|
6353
6610
|
} catch (error) {
|
|
6354
|
-
this.
|
|
6611
|
+
this.retainFailure(error);
|
|
6355
6612
|
if (!isQueryReadTimeout(error)) {
|
|
6356
6613
|
this.invalidate();
|
|
6357
|
-
|
|
6614
|
+
if (this.endpoint === undefined)
|
|
6615
|
+
throw error;
|
|
6616
|
+
const failure = this.dependencyFailure("Managed PostgreSQL connection validation refused. Preserve its data and ownership records.");
|
|
6617
|
+
throw error instanceof DbosDependencyError ? failure : new Error(failure.message);
|
|
6358
6618
|
}
|
|
6359
|
-
throw
|
|
6619
|
+
throw this.dependencyFailure("Managed PostgreSQL did not answer a connection identity check in time.");
|
|
6360
6620
|
}
|
|
6361
6621
|
}
|
|
6362
6622
|
check() {
|
|
@@ -6395,9 +6655,9 @@ class PostgresHealth {
|
|
|
6395
6655
|
try {
|
|
6396
6656
|
identity = await this.operations.probe();
|
|
6397
6657
|
} catch (retryError) {
|
|
6398
|
-
if (!isMonitoringConnectionFailure(retryError))
|
|
6658
|
+
if (isMonitoringConnectionTimeout(retryError) || !isMonitoringConnectionFailure(retryError))
|
|
6399
6659
|
throw retryError;
|
|
6400
|
-
this.
|
|
6660
|
+
this.retainFailure(retryError);
|
|
6401
6661
|
return;
|
|
6402
6662
|
}
|
|
6403
6663
|
}
|
|
@@ -6412,6 +6672,7 @@ class PostgresHealth {
|
|
|
6412
6672
|
this.available = identity;
|
|
6413
6673
|
this.attempts = 0;
|
|
6414
6674
|
this.nextRecoveryAt = 0;
|
|
6675
|
+
this.healthySinceFailure = true;
|
|
6415
6676
|
return identity.url;
|
|
6416
6677
|
}
|
|
6417
6678
|
async refresh() {
|
|
@@ -6420,20 +6681,22 @@ class PostgresHealth {
|
|
|
6420
6681
|
if (ready !== undefined)
|
|
6421
6682
|
return ready;
|
|
6422
6683
|
} catch (error) {
|
|
6423
|
-
if (isQueryReadTimeout(error)) {
|
|
6424
|
-
this.
|
|
6425
|
-
throw this.
|
|
6684
|
+
if (isQueryReadTimeout(error) || isMonitoringConnectionTimeout(error)) {
|
|
6685
|
+
this.retainFailure(new Error("Managed PostgreSQL did not answer a health check in time."));
|
|
6686
|
+
throw this.dependencyFailure("Managed PostgreSQL did not answer a health check in time.");
|
|
6426
6687
|
}
|
|
6427
|
-
this.
|
|
6688
|
+
this.retainFailure(error);
|
|
6428
6689
|
if (!(error instanceof Error && ("code" in error) && error.code === "53300"))
|
|
6429
6690
|
this.invalidate();
|
|
6430
|
-
throw error;
|
|
6691
|
+
throw this.endpoint === undefined ? error : this.dependencyFailure("Managed PostgreSQL recovery refused after a health probe failure. Preserve its data and ownership records.");
|
|
6692
|
+
}
|
|
6693
|
+
if (this.attempts === 0 && (this.failure === undefined || this.healthySinceFailure)) {
|
|
6694
|
+
this.retainFailure(new Error("Managed PostgreSQL failed its live health check."));
|
|
6695
|
+
this.healthySinceFailure = false;
|
|
6431
6696
|
}
|
|
6432
|
-
if (this.attempts === 0)
|
|
6433
|
-
this.failure = new DbosDependencyError("Managed PostgreSQL failed its live health check.");
|
|
6434
6697
|
this.invalidate();
|
|
6435
6698
|
if ((this.operations.now ?? Date.now)() < this.nextRecoveryAt)
|
|
6436
|
-
throw
|
|
6699
|
+
throw this.dependencyFailure("Managed Postgres recovery is cooling down after bounded attempts.");
|
|
6437
6700
|
while (!this.stopped && this.attempts < RECOVERY_ATTEMPTS) {
|
|
6438
6701
|
const attempt = this.attempts++;
|
|
6439
6702
|
if (attempt > 0)
|
|
@@ -6446,12 +6709,12 @@ class PostgresHealth {
|
|
|
6446
6709
|
if (ready !== undefined)
|
|
6447
6710
|
return ready;
|
|
6448
6711
|
} catch (error) {
|
|
6449
|
-
this.
|
|
6712
|
+
this.retainFailure(error);
|
|
6450
6713
|
}
|
|
6451
6714
|
}
|
|
6452
6715
|
this.attempts = 0;
|
|
6453
6716
|
this.nextRecoveryAt = (this.operations.now ?? Date.now)() + HEALTH_INTERVAL_MS;
|
|
6454
|
-
throw
|
|
6717
|
+
throw this.dependencyFailure("Managed Postgres is unavailable after bounded recovery. Preserve its data and ownership records.");
|
|
6455
6718
|
}
|
|
6456
6719
|
}
|
|
6457
6720
|
|
|
@@ -6725,7 +6988,7 @@ async function probePostgresIdentity(port) {
|
|
|
6725
6988
|
const result = await client.query(POSTGRES_IDENTITY_SQL);
|
|
6726
6989
|
return result.rows[0];
|
|
6727
6990
|
} catch (error) {
|
|
6728
|
-
if (isQueryReadTimeout(error))
|
|
6991
|
+
if (isQueryReadTimeout(error) || isMonitoringConnectionTimeout(error))
|
|
6729
6992
|
throw error;
|
|
6730
6993
|
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
6731
6994
|
if (code === "ECONNREFUSED" || code === "ECONNRESET" || code === "EPIPE" || code === "57P01" || code === "57P02" || code === "57P03" || error instanceof Error && /timeout|timed out|connection terminated/i.test(error.message))
|
|
@@ -6898,7 +7161,7 @@ async function ensureCluster(options = {}) {
|
|
|
6898
7161
|
try {
|
|
6899
7162
|
return await existingServerProbe()(probePort);
|
|
6900
7163
|
} catch (error) {
|
|
6901
|
-
if (isQueryReadTimeout(error))
|
|
7164
|
+
if (isQueryReadTimeout(error) || isMonitoringConnectionTimeout(error))
|
|
6902
7165
|
return;
|
|
6903
7166
|
throw error;
|
|
6904
7167
|
}
|
|
@@ -7106,6 +7369,8 @@ async function ensureCluster(options = {}) {
|
|
|
7106
7369
|
const server = current.server;
|
|
7107
7370
|
if (!server)
|
|
7108
7371
|
throw new Error("Managed Postgres published identity is missing.");
|
|
7372
|
+
if (managedPostmaster(current) === undefined)
|
|
7373
|
+
return;
|
|
7109
7374
|
if (!managedPostgresRuntimeHealthy(current, server.port)) {
|
|
7110
7375
|
verifyManagedPostmasterProcess(current, server);
|
|
7111
7376
|
return;
|
|
@@ -7122,6 +7387,7 @@ async function ensureCluster(options = {}) {
|
|
|
7122
7387
|
};
|
|
7123
7388
|
health = new PostgresHealth({
|
|
7124
7389
|
probe: () => inspect(),
|
|
7390
|
+
url: embeddedDbosSystemDatabaseUrl,
|
|
7125
7391
|
validate: async (client) => {
|
|
7126
7392
|
const identity = await inspect(async () => {
|
|
7127
7393
|
const query = { text: POSTGRES_IDENTITY_SQL, query_timeout: POSTGRES_HEALTH_QUERY_TIMEOUT_MS };
|