@senpi/runtime 3.0.59 → 3.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -2
- package/dist/actions/decision-engine.d.ts +0 -2
- package/dist/actions/decision-engine.d.ts.map +1 -1
- package/dist/actions/decision-engine.js +1 -79
- package/dist/actions/decision-engine.js.map +1 -1
- package/dist/cli/deploy-render.d.ts +91 -0
- package/dist/cli/deploy-render.d.ts.map +1 -0
- package/dist/cli/deploy-render.js +216 -0
- package/dist/cli/deploy-render.js.map +1 -0
- package/dist/cli/guide-commands.d.ts.map +1 -1
- package/dist/cli/guide-commands.js +78 -6
- package/dist/cli/guide-commands.js.map +1 -1
- package/dist/cli/senpi-commands.d.ts +6 -1
- package/dist/cli/senpi-commands.d.ts.map +1 -1
- package/dist/cli/senpi-commands.js +244 -8
- package/dist/cli/senpi-commands.js.map +1 -1
- package/dist/cli/validate-command.d.ts.map +1 -1
- package/dist/cli/validate-command.js +32 -1
- package/dist/cli/validate-command.js.map +1 -1
- package/dist/deploy/deadlines.d.ts +47 -0
- package/dist/deploy/deadlines.d.ts.map +1 -0
- package/dist/deploy/deadlines.js +56 -0
- package/dist/deploy/deadlines.js.map +1 -0
- package/dist/deploy/error-text.d.ts +25 -0
- package/dist/deploy/error-text.d.ts.map +1 -0
- package/dist/deploy/error-text.js +42 -0
- package/dist/deploy/error-text.js.map +1 -0
- package/dist/deploy/funding.d.ts +87 -0
- package/dist/deploy/funding.d.ts.map +1 -0
- package/dist/deploy/funding.js +229 -0
- package/dist/deploy/funding.js.map +1 -0
- package/dist/deploy/install-gate-runner.d.ts +12 -0
- package/dist/deploy/install-gate-runner.d.ts.map +1 -0
- package/dist/deploy/install-gate-runner.js +17 -0
- package/dist/deploy/install-gate-runner.js.map +1 -0
- package/dist/deploy/job.d.ts +199 -0
- package/dist/deploy/job.d.ts.map +1 -0
- package/dist/deploy/job.js +463 -0
- package/dist/deploy/job.js.map +1 -0
- package/dist/deploy/min-budget.d.ts +68 -0
- package/dist/deploy/min-budget.d.ts.map +1 -0
- package/dist/deploy/min-budget.js +297 -0
- package/dist/deploy/min-budget.js.map +1 -0
- package/dist/deploy/orchestrator.d.ts +305 -0
- package/dist/deploy/orchestrator.d.ts.map +1 -0
- package/dist/deploy/orchestrator.js +2910 -0
- package/dist/deploy/orchestrator.js.map +1 -0
- package/dist/deploy/package.d.ts +130 -0
- package/dist/deploy/package.d.ts.map +1 -0
- package/dist/deploy/package.js +351 -0
- package/dist/deploy/package.js.map +1 -0
- package/dist/deploy/proof-gate.d.ts +82 -0
- package/dist/deploy/proof-gate.d.ts.map +1 -0
- package/dist/deploy/proof-gate.js +269 -0
- package/dist/deploy/proof-gate.js.map +1 -0
- package/dist/deploy/python-float.d.ts +34 -0
- package/dist/deploy/python-float.d.ts.map +1 -0
- package/dist/deploy/python-float.js +46 -0
- package/dist/deploy/python-float.js.map +1 -0
- package/dist/deploy/recheck-commands.d.ts +50 -0
- package/dist/deploy/recheck-commands.d.ts.map +1 -0
- package/dist/deploy/recheck-commands.js +59 -0
- package/dist/deploy/recheck-commands.js.map +1 -0
- package/dist/deploy/register.d.ts +99 -0
- package/dist/deploy/register.d.ts.map +1 -0
- package/dist/deploy/register.js +689 -0
- package/dist/deploy/register.js.map +1 -0
- package/dist/deploy/taxonomy.d.ts +24 -0
- package/dist/deploy/taxonomy.d.ts.map +1 -0
- package/dist/deploy/taxonomy.js +31 -0
- package/dist/deploy/taxonomy.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +244 -132
- package/dist/index.js.map +1 -1
- package/dist/runtime/strategy-registry.d.ts +8 -0
- package/dist/runtime/strategy-registry.d.ts.map +1 -1
- package/dist/runtime/strategy-registry.js +14 -1
- package/dist/runtime/strategy-registry.js.map +1 -1
- package/dist/runtime-api/intake/intake-mount.d.ts.map +1 -1
- package/dist/runtime-api/intake/intake-mount.js +7 -4
- package/dist/runtime-api/intake/intake-mount.js.map +1 -1
- package/dist/scanners/implementations/market-regime.d.ts +1 -1
- package/dist/senpi/client.d.ts +58 -4
- package/dist/senpi/client.d.ts.map +1 -1
- package/dist/senpi/client.js +192 -15
- package/dist/senpi/client.js.map +1 -1
- package/dist/senpi/strategy-status.d.ts +32 -0
- package/dist/senpi/strategy-status.d.ts.map +1 -0
- package/dist/senpi/strategy-status.js +70 -0
- package/dist/senpi/strategy-status.js.map +1 -0
- package/dist/senpi/types.d.ts +47 -3
- package/dist/senpi/types.d.ts.map +1 -1
- package/dist/skills-manager/manifest.js +1 -1
- package/dist/skills-manager/skills-manifest.json +1 -1
- package/dist/telemetry/metrics.d.ts +23 -0
- package/dist/telemetry/metrics.d.ts.map +1 -1
- package/dist/telemetry/metrics.js +65 -0
- package/dist/telemetry/metrics.js.map +1 -1
- package/dist/types/strategy.d.ts +9 -1
- package/dist/types/strategy.d.ts.map +1 -1
- package/dist/universe/package-universe.d.ts +97 -0
- package/dist/universe/package-universe.d.ts.map +1 -0
- package/dist/universe/package-universe.js +275 -0
- package/dist/universe/package-universe.js.map +1 -0
- package/dist/validate/codes.d.ts +20 -0
- package/dist/validate/codes.d.ts.map +1 -1
- package/dist/validate/codes.js +36 -0
- package/dist/validate/codes.js.map +1 -1
- package/dist/validate/import-stage.js +5 -5
- package/dist/validate/import-stage.js.map +1 -1
- package/dist/validate/index.d.ts +1 -1
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/install-gate.d.ts.map +1 -1
- package/dist/validate/install-gate.js +10 -2
- package/dist/validate/install-gate.js.map +1 -1
- package/dist/validate/live-instruments.d.ts +44 -0
- package/dist/validate/live-instruments.d.ts.map +1 -0
- package/dist/validate/live-instruments.js +71 -0
- package/dist/validate/live-instruments.js.map +1 -0
- package/dist/validate/live-stage.js +5 -5
- package/dist/validate/live-stage.js.map +1 -1
- package/dist/validate/proof.d.ts +11 -1
- package/dist/validate/proof.d.ts.map +1 -1
- package/dist/validate/proof.js +7 -6
- package/dist/validate/proof.js.map +1 -1
- package/dist/validate/recipe-checks.d.ts.map +1 -1
- package/dist/validate/recipe-checks.js +6 -4
- package/dist/validate/recipe-checks.js.map +1 -1
- package/dist/validate/run.d.ts +5 -0
- package/dist/validate/run.d.ts.map +1 -1
- package/dist/validate/run.js +10 -1
- package/dist/validate/run.js.map +1 -1
- package/dist/validate/target.d.ts +41 -0
- package/dist/validate/target.d.ts.map +1 -1
- package/dist/validate/target.js +192 -16
- package/dist/validate/target.js.map +1 -1
- package/dist/validate/universe-stage.d.ts +71 -0
- package/dist/validate/universe-stage.d.ts.map +1 -0
- package/dist/validate/universe-stage.js +183 -0
- package/dist/validate/universe-stage.js.map +1 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/runtime/managed-wallet-guard.d.ts +0 -47
- package/dist/runtime/managed-wallet-guard.d.ts.map +0 -1
- package/dist/runtime/managed-wallet-guard.js +0 -106
- package/dist/runtime/managed-wallet-guard.js.map +0 -1
- package/dist/runtime/protection-nudge.d.ts +0 -67
- package/dist/runtime/protection-nudge.d.ts.map +0 -1
- package/dist/runtime/protection-nudge.js +0 -244
- package/dist/runtime/protection-nudge.js.map +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/** The platform per-wallet floor, under this module's own long-standing name. */
|
|
2
|
+
export { WALLET_FLOOR as MIN_WALLET, FEE_BUFFER } from "./min-budget.js";
|
|
3
|
+
/** Lifecycle-relevant per-instance funding inputs — the only fields this module reads. */
|
|
4
|
+
export interface FundingInstance {
|
|
5
|
+
name: string;
|
|
6
|
+
fundingShare: number | null;
|
|
7
|
+
}
|
|
8
|
+
export interface Shortfall {
|
|
9
|
+
requested: number;
|
|
10
|
+
available: number;
|
|
11
|
+
usable: number;
|
|
12
|
+
shortBy: number;
|
|
13
|
+
wallets: number;
|
|
14
|
+
shares: number[];
|
|
15
|
+
}
|
|
16
|
+
export interface FundingPlan {
|
|
17
|
+
/** instance name → initialBudget for the create call. */
|
|
18
|
+
amounts: Map<string, number>;
|
|
19
|
+
/** Non-null means HALT: the budget is a hard target, never silently scaled down. */
|
|
20
|
+
shortfall: Shortfall | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Total USDC `strategy_create_custom_strategy` can fund from — its whole
|
|
24
|
+
* waterfall, in order: HL perps → HL spot USDC → EVM USDC (auto-bridged).
|
|
25
|
+
*
|
|
26
|
+
* These are DISJOINT buckets, so reading one reads a FRACTION of the fundable
|
|
27
|
+
* balance. `total_withdrawable` is deliberately EXCLUDED: it is free margin
|
|
28
|
+
* sitting inside OTHER strategy wallets, not spendable here.
|
|
29
|
+
*
|
|
30
|
+
* Returns null when no bucket key is present at all — an unknown shape must
|
|
31
|
+
* never halt a deploy the backend would have funded.
|
|
32
|
+
*/
|
|
33
|
+
export declare function accessibleUsd(payload: Record<string, unknown> | null): number | null;
|
|
34
|
+
/**
|
|
35
|
+
* Per-instance `initialBudget` for the instances still needing a wallet, split
|
|
36
|
+
* by `fundingShare` and floored at {@link MIN_WALLET}.
|
|
37
|
+
*
|
|
38
|
+
* The requested budget is a HARD TARGET: on shortfall the caller HALTS. Scaling
|
|
39
|
+
* every wallet down to fit turned a "$1,000 across 2 wallets" request into two
|
|
40
|
+
* floor wallets, silently — that is the bug this shape exists to prevent.
|
|
41
|
+
*/
|
|
42
|
+
export declare function planFunding(instances: FundingInstance[], budget: number, available: number | null): FundingPlan;
|
|
43
|
+
/**
|
|
44
|
+
* The largest budget `b` whose funding plan still fits within `usable`:
|
|
45
|
+
* `b* = max { b : Σᵢ max(MIN_WALLET, round2(b·shareᵢ)) ≤ usable }`.
|
|
46
|
+
*
|
|
47
|
+
* Bisected in integer cents with the SAME per-wallet rounding `planFunding`
|
|
48
|
+
* uses, so re-running deploy at the hinted budget round-trips with no shortfall
|
|
49
|
+
* even for uneven shares — where the bare `usable` over-hints, because a floored
|
|
50
|
+
* small leg pushes the funded total above the budget.
|
|
51
|
+
*/
|
|
52
|
+
export declare function maxFeasibleBudget(instances: Array<{
|
|
53
|
+
fundingShare: number | null;
|
|
54
|
+
}>, usable: number): number;
|
|
55
|
+
/** Money in agent-facing prose: comma-grouped, two decimals, never scientific notation. */
|
|
56
|
+
export declare function usd(value: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* A dollar amount as the `--budget` flag accepts it: bare digits, no `$`, no
|
|
59
|
+
* comma grouping. {@link usd}'s commas would not parse as a number, so a hinted
|
|
60
|
+
* command an agent copies verbatim must render the flag value with this.
|
|
61
|
+
*/
|
|
62
|
+
export declare function budgetArg(value: number): string;
|
|
63
|
+
/**
|
|
64
|
+
* The re-run command every deploy surface emits — ONE producer, because the same report prints it
|
|
65
|
+
* more than once (a refusal's copy and the report's `next` line) and two copies drifting would hand
|
|
66
|
+
* the reader two different commands for one situation. `budget` undefined renders the placeholder
|
|
67
|
+
* the caller must fill in, never a fabricated number.
|
|
68
|
+
*
|
|
69
|
+
* The path is single-quoted for the same reason `pkg.id` is wherever it is emitted: these strings
|
|
70
|
+
* are copied into a shell verbatim, and a real workspace path with a space in it
|
|
71
|
+
* ("/data/My Strategies/spider") would otherwise render a command that cannot run. Quoting is
|
|
72
|
+
* unconditional — a conditional quote is a second rule to get wrong, and the quoted form is
|
|
73
|
+
* correct for every path.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resumeCommand(packageDir: string, budget?: number): string;
|
|
76
|
+
/**
|
|
77
|
+
* Agent-facing halt text for a funding shortfall.
|
|
78
|
+
*
|
|
79
|
+
* The lower-budget escape renders ONLY when the usable balance can still fund
|
|
80
|
+
* every wallet at the {@link MIN_WALLET} floor. Below that no budget is valid,
|
|
81
|
+
* and suggesting one produces nonsense the agent then follows ("--budget ≤ $0",
|
|
82
|
+
* the M381223 churn). Codes: senpi-skills `docs/error-code-taxonomy.md`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function underfundedNote(shortfall: Shortfall, instances: Array<{
|
|
85
|
+
fundingShare: number | null;
|
|
86
|
+
}>, packageDir: string): string;
|
|
87
|
+
//# sourceMappingURL=funding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funding.d.ts","sourceRoot":"","sources":["../../src/deploy/funding.ts"],"names":[],"mappings":"AAqBA,iFAAiF;AACjF,OAAO,EAAE,YAAY,IAAI,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEzE,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,oFAAoF;IACpF,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CAC7B;AAqDD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAapF;AAMD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,eAAe,EAAE,EAC5B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,WAAW,CAsBb;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,KAAK,CAAC;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EACjD,MAAM,EAAE,MAAM,GACb,MAAM,CAoBR;AAED,2FAA2F;AAC3F,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAGzE;AAYD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,KAAK,CAAC;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EACjD,UAAU,EAAE,MAAM,GACjB,MAAM,CA6BR"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Funding preflight for `senpi deploy` — a 1:1 TS port of deploy.py v2.11.0's
|
|
3
|
+
* `available_usd` / `plan_funding` / `max_feasible_budget` / `underfunded_note`.
|
|
4
|
+
*
|
|
5
|
+
* This layer is UX, not enforcement: the backend funds from its own waterfall
|
|
6
|
+
* during `strategy_create_custom_strategy` and remains the authority on
|
|
7
|
+
* sufficiency. So it refuses only on POSITIVE evidence of a shortfall — an
|
|
8
|
+
* unreadable or unrecognised portfolio shape yields `null` and the deploy
|
|
9
|
+
* proceeds (create fails loudly if the balance really is short). Under-counting
|
|
10
|
+
* is the dangerous direction: it produced the Starling false refusal, where
|
|
11
|
+
* ~$248 of idle Base USDC was invisible to a single-bucket read.
|
|
12
|
+
*
|
|
13
|
+
* The two platform constants it runs on live in `min-budget.ts`, which mirrors
|
|
14
|
+
* `senpi-strategy-ops/scripts/min_budget.py` in Senpi-ai/senpi-skills — ONE source of truth for the
|
|
15
|
+
* physics on each side: a **$10** per-wallet floor (`WALLET_FLOOR`, itself mirroring the backend's
|
|
16
|
+
* `MINIMUM_STRATEGY_BUDGET` default) and a **$1.50** per-wallet fee buffer. Change them there, never
|
|
17
|
+
* here, and never against a remembered number: the floor was $100 until the skills $10-minimum
|
|
18
|
+
* rollout (#519/#521), and every refusal below quotes whatever the constant says.
|
|
19
|
+
*/
|
|
20
|
+
import { FEE_BUFFER, WALLET_FLOOR as MIN_WALLET } from "./min-budget.js";
|
|
21
|
+
/** The platform per-wallet floor, under this module's own long-standing name. */
|
|
22
|
+
export { WALLET_FLOOR as MIN_WALLET, FEE_BUFFER } from "./min-budget.js";
|
|
23
|
+
function round2(value) {
|
|
24
|
+
return Math.round(value * 100) / 100;
|
|
25
|
+
}
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* First of `values` that parses as a finite number, else 0.
|
|
31
|
+
*
|
|
32
|
+
* The payload mixes numbers, numeric strings (`spot_balances` rows carry
|
|
33
|
+
* `"5.46"`) and nulls — a null must fall THROUGH to the next candidate rather
|
|
34
|
+
* than zero the row, because an under-count here is a false `underfunded` halt.
|
|
35
|
+
*/
|
|
36
|
+
function firstNumber(...values) {
|
|
37
|
+
for (const value of values) {
|
|
38
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
39
|
+
return value;
|
|
40
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
41
|
+
const parsed = Number(value);
|
|
42
|
+
if (Number.isFinite(parsed))
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* USD held as USDC in a balance list (`spot_balances` / `token_balances`).
|
|
50
|
+
* Filtered by symbol because the funding waterfall pulls USDC, while the
|
|
51
|
+
* `total_*` scalars lump in every other token — a spot HYPE bag or EVM WETH is
|
|
52
|
+
* not fundable balance. Deliberately chain-agnostic: hardcoding the bridgeable
|
|
53
|
+
* chain list is how this gate would start refusing fundable deploys the day a
|
|
54
|
+
* chain is added.
|
|
55
|
+
*/
|
|
56
|
+
function usdcUsd(rows, ...keys) {
|
|
57
|
+
if (!Array.isArray(rows))
|
|
58
|
+
return 0;
|
|
59
|
+
return rows.reduce((sum, row) => {
|
|
60
|
+
if (!isRecord(row))
|
|
61
|
+
return sum;
|
|
62
|
+
if (String(row.tokenSymbol ?? "").trim().toUpperCase() !== "USDC")
|
|
63
|
+
return sum;
|
|
64
|
+
return sum + firstNumber(...keys.map((key) => row[key]));
|
|
65
|
+
}, 0);
|
|
66
|
+
}
|
|
67
|
+
/** The bucket keys whose presence proves the payload is a portfolio we understand. */
|
|
68
|
+
const BUCKET_KEYS = [
|
|
69
|
+
"total_in_hyperliquid",
|
|
70
|
+
"total_spot_usd_in_hyperliquid",
|
|
71
|
+
"spot_balances",
|
|
72
|
+
"token_balances",
|
|
73
|
+
];
|
|
74
|
+
/**
|
|
75
|
+
* Total USDC `strategy_create_custom_strategy` can fund from — its whole
|
|
76
|
+
* waterfall, in order: HL perps → HL spot USDC → EVM USDC (auto-bridged).
|
|
77
|
+
*
|
|
78
|
+
* These are DISJOINT buckets, so reading one reads a FRACTION of the fundable
|
|
79
|
+
* balance. `total_withdrawable` is deliberately EXCLUDED: it is free margin
|
|
80
|
+
* sitting inside OTHER strategy wallets, not spendable here.
|
|
81
|
+
*
|
|
82
|
+
* Returns null when no bucket key is present at all — an unknown shape must
|
|
83
|
+
* never halt a deploy the backend would have funded.
|
|
84
|
+
*/
|
|
85
|
+
export function accessibleUsd(payload) {
|
|
86
|
+
const portfolio = isRecord(payload?.portfolio) ? payload.portfolio : null;
|
|
87
|
+
if (!portfolio)
|
|
88
|
+
return null;
|
|
89
|
+
if (!BUCKET_KEYS.some((key) => portfolio[key] !== undefined && portfolio[key] !== null))
|
|
90
|
+
return null;
|
|
91
|
+
const perps = firstNumber(portfolio.total_in_hyperliquid);
|
|
92
|
+
const spotRows = portfolio.spot_balances;
|
|
93
|
+
const spot = Array.isArray(spotRows) && spotRows.length > 0
|
|
94
|
+
? usdcUsd(spotRows, "usdValue", "total")
|
|
95
|
+
: firstNumber(portfolio.total_spot_usd_in_hyperliquid);
|
|
96
|
+
const evm = usdcUsd(portfolio.token_balances, "balanceInUSD", "formattedBalance");
|
|
97
|
+
return round2(perps + spot + evm);
|
|
98
|
+
}
|
|
99
|
+
function sharesOf(instances) {
|
|
100
|
+
return instances.map((inst) => inst.fundingShare ?? 1 / instances.length);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Per-instance `initialBudget` for the instances still needing a wallet, split
|
|
104
|
+
* by `fundingShare` and floored at {@link MIN_WALLET}.
|
|
105
|
+
*
|
|
106
|
+
* The requested budget is a HARD TARGET: on shortfall the caller HALTS. Scaling
|
|
107
|
+
* every wallet down to fit turned a "$1,000 across 2 wallets" request into two
|
|
108
|
+
* floor wallets, silently — that is the bug this shape exists to prevent.
|
|
109
|
+
*/
|
|
110
|
+
export function planFunding(instances, budget, available) {
|
|
111
|
+
const shares = sharesOf(instances);
|
|
112
|
+
const amounts = new Map(instances.map((inst, i) => [inst.name, Math.max(MIN_WALLET, round2((budget || 0) * shares[i]))]));
|
|
113
|
+
const requested = round2([...amounts.values()].reduce((sum, amount) => sum + amount, 0));
|
|
114
|
+
let shortfall = null;
|
|
115
|
+
if (available !== null) {
|
|
116
|
+
const usable = Math.max(0, round2(available - FEE_BUFFER * instances.length));
|
|
117
|
+
if (requested > usable) {
|
|
118
|
+
shortfall = {
|
|
119
|
+
requested,
|
|
120
|
+
available: round2(available),
|
|
121
|
+
usable,
|
|
122
|
+
shortBy: round2(requested - usable),
|
|
123
|
+
wallets: instances.length,
|
|
124
|
+
shares,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { amounts, shortfall };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The largest budget `b` whose funding plan still fits within `usable`:
|
|
132
|
+
* `b* = max { b : Σᵢ max(MIN_WALLET, round2(b·shareᵢ)) ≤ usable }`.
|
|
133
|
+
*
|
|
134
|
+
* Bisected in integer cents with the SAME per-wallet rounding `planFunding`
|
|
135
|
+
* uses, so re-running deploy at the hinted budget round-trips with no shortfall
|
|
136
|
+
* even for uneven shares — where the bare `usable` over-hints, because a floored
|
|
137
|
+
* small leg pushes the funded total above the budget.
|
|
138
|
+
*/
|
|
139
|
+
export function maxFeasibleBudget(instances, usable) {
|
|
140
|
+
const shares = sharesOf(instances);
|
|
141
|
+
const totalCents = (cents) => {
|
|
142
|
+
const budget = cents / 100;
|
|
143
|
+
const total = shares.reduce((sum, share) => sum + Math.max(MIN_WALLET, round2(budget * share)), 0);
|
|
144
|
+
return Math.round(total * 100);
|
|
145
|
+
};
|
|
146
|
+
const usableCents = Math.round(usable * 100);
|
|
147
|
+
if (totalCents(0) > usableCents)
|
|
148
|
+
return 0; // even the all-floor minimum cannot fit
|
|
149
|
+
let lo = 0;
|
|
150
|
+
let hi = usableCents + shares.length * Math.round(MIN_WALLET * 100) + 100;
|
|
151
|
+
while (totalCents(hi) <= usableCents)
|
|
152
|
+
hi *= 2; // keep hi a true upper bound (defensive vs Σshare < 1)
|
|
153
|
+
while (lo < hi) {
|
|
154
|
+
const mid = Math.floor((lo + hi + 1) / 2);
|
|
155
|
+
if (totalCents(mid) <= usableCents)
|
|
156
|
+
lo = mid;
|
|
157
|
+
else
|
|
158
|
+
hi = mid - 1;
|
|
159
|
+
}
|
|
160
|
+
return lo / 100;
|
|
161
|
+
}
|
|
162
|
+
/** Money in agent-facing prose: comma-grouped, two decimals, never scientific notation. */
|
|
163
|
+
export function usd(value) {
|
|
164
|
+
return `$${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* A dollar amount as the `--budget` flag accepts it: bare digits, no `$`, no
|
|
168
|
+
* comma grouping. {@link usd}'s commas would not parse as a number, so a hinted
|
|
169
|
+
* command an agent copies verbatim must render the flag value with this.
|
|
170
|
+
*/
|
|
171
|
+
export function budgetArg(value) {
|
|
172
|
+
return String(Number(value.toFixed(2)));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The re-run command every deploy surface emits — ONE producer, because the same report prints it
|
|
176
|
+
* more than once (a refusal's copy and the report's `next` line) and two copies drifting would hand
|
|
177
|
+
* the reader two different commands for one situation. `budget` undefined renders the placeholder
|
|
178
|
+
* the caller must fill in, never a fabricated number.
|
|
179
|
+
*
|
|
180
|
+
* The path is single-quoted for the same reason `pkg.id` is wherever it is emitted: these strings
|
|
181
|
+
* are copied into a shell verbatim, and a real workspace path with a space in it
|
|
182
|
+
* ("/data/My Strategies/spider") would otherwise render a command that cannot run. Quoting is
|
|
183
|
+
* unconditional — a conditional quote is a second rule to get wrong, and the quoted form is
|
|
184
|
+
* correct for every path.
|
|
185
|
+
*/
|
|
186
|
+
export function resumeCommand(packageDir, budget) {
|
|
187
|
+
const budgetFlag = budget === undefined ? "--budget <usd>" : `--budget ${budgetArg(budget)}`;
|
|
188
|
+
return `openclaw senpi deploy -p '${packageDir}' ${budgetFlag}`;
|
|
189
|
+
}
|
|
190
|
+
/** The requested/available/usable/short-by facts both refusal branches lead with. */
|
|
191
|
+
function shortfallFacts(shortfall) {
|
|
192
|
+
return (`Requested ${usd(shortfall.requested)} across ${shortfall.wallets} wallet(s) ` +
|
|
193
|
+
`(min ${usd(MIN_WALLET)}/wallet), but only ${usd(shortfall.available)} is accessible ` +
|
|
194
|
+
`(${usd(shortfall.usable)} after fees) — short by ${usd(shortfall.shortBy)}. ` +
|
|
195
|
+
`NOT funding; no wallet was created. `);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Agent-facing halt text for a funding shortfall.
|
|
199
|
+
*
|
|
200
|
+
* The lower-budget escape renders ONLY when the usable balance can still fund
|
|
201
|
+
* every wallet at the {@link MIN_WALLET} floor. Below that no budget is valid,
|
|
202
|
+
* and suggesting one produces nonsense the agent then follows ("--budget ≤ $0",
|
|
203
|
+
* the M381223 churn). Codes: senpi-skills `docs/error-code-taxonomy.md`.
|
|
204
|
+
*/
|
|
205
|
+
export function underfundedNote(shortfall, instances, packageDir) {
|
|
206
|
+
const floorNeeded = shortfall.wallets * MIN_WALLET;
|
|
207
|
+
const facts = shortfallFacts(shortfall);
|
|
208
|
+
if (shortfall.usable < floorNeeded) {
|
|
209
|
+
// Computed from `available`, not `usable`: usable is clamped to 0 when the balance does not
|
|
210
|
+
// even cover the fee reserve, and `floor - usable` would then understate the deposit by up to
|
|
211
|
+
// FEE_BUFFER × wallets — depositing the hinted amount must always clear the floor check.
|
|
212
|
+
const missing = round2(floorNeeded + FEE_BUFFER * shortfall.wallets - shortfall.available);
|
|
213
|
+
return (`[E_FUNDS_BELOW_FLOOR] ${facts}` +
|
|
214
|
+
`No budget can fund ${shortfall.wallets} wallet(s) below the ${usd(MIN_WALLET)}/wallet floor — ` +
|
|
215
|
+
`at least ${usd(missing)} more USDC is needed. Deposit USDC first (the ` +
|
|
216
|
+
`\`senpi-deposit-withdraw-transfer\` skill has the flow), then re-run: ` +
|
|
217
|
+
`\`${resumeCommand(packageDir)}\`. ` +
|
|
218
|
+
`Do NOT retry with a lower --budget: no lower budget is valid.`);
|
|
219
|
+
}
|
|
220
|
+
const best = maxFeasibleBudget(instances, shortfall.usable);
|
|
221
|
+
return (`[E_FUNDS_SHORT] ${facts}` +
|
|
222
|
+
`Two ways forward: deposit at least ${usd(shortfall.shortBy)} more USDC (the ` +
|
|
223
|
+
`\`senpi-deposit-withdraw-transfer\` skill has the flow) and re-run the same command, or ` +
|
|
224
|
+
`confirm a lower amount with the user and re-run: ` +
|
|
225
|
+
`\`${resumeCommand(packageDir, best)}\` — ${usd(best)} is the largest ` +
|
|
226
|
+
`budget your accessible balance can fully fund across these ${shortfall.wallets} wallet(s) at ` +
|
|
227
|
+
`the ${usd(MIN_WALLET)}/wallet floor.`);
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=funding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funding.js","sourceRoot":"","sources":["../../src/deploy/funding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One instance → one install-gate verdict.
|
|
3
|
+
*
|
|
4
|
+
* The gate predicate lives in `validate/install-gate.ts` and is not duplicated here; this module
|
|
5
|
+
* only supplies the findings it decides over, from the instance's own recipe. Deploy calls it
|
|
6
|
+
* BEFORE money so a refusal costs nothing; `installRuntimeCore` calls the same predicate at the
|
|
7
|
+
* gateway boundary for installs that never went through deploy.
|
|
8
|
+
*/
|
|
9
|
+
import { type InstallGateResult } from "../validate/index.js";
|
|
10
|
+
import type { DeployInstance } from "./package.js";
|
|
11
|
+
export declare function gateInstance(inst: DeployInstance): Promise<InstallGateResult>;
|
|
12
|
+
//# sourceMappingURL=install-gate-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-gate-runner.d.ts","sourceRoot":"","sources":["../../src/deploy/install-gate-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA2C,KAAK,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,wBAAsB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAMnF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One instance → one install-gate verdict.
|
|
3
|
+
*
|
|
4
|
+
* The gate predicate lives in `validate/install-gate.ts` and is not duplicated here; this module
|
|
5
|
+
* only supplies the findings it decides over, from the instance's own recipe. Deploy calls it
|
|
6
|
+
* BEFORE money so a refusal costs nothing; `installRuntimeCore` calls the same predicate at the
|
|
7
|
+
* gateway boundary for installs that never went through deploy.
|
|
8
|
+
*/
|
|
9
|
+
import { checkInstallGate, collectRecipeFindings } from "../validate/index.js";
|
|
10
|
+
export async function gateInstance(inst) {
|
|
11
|
+
const validation = await collectRecipeFindings({
|
|
12
|
+
path: inst.runtimeYamlPath,
|
|
13
|
+
baseDir: inst.runtimeYamlDir,
|
|
14
|
+
});
|
|
15
|
+
return checkInstallGate(validation.findings);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=install-gate-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-gate-runner.js","sourceRoot":"","sources":["../../src/deploy/install-gate-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAA0B,MAAM,sBAAsB,CAAC;AAGvG,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAoB;IACrD,MAAM,UAAU,GAAG,MAAM,qBAAqB,CAAC;QAC7C,IAAI,EAAE,IAAI,CAAC,eAAe;QAC1B,OAAO,EAAE,IAAI,CAAC,cAAc;KAC7B,CAAC,CAAC;IACH,OAAO,gBAAgB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { DeployEvent, DeployEventBody, DeployReport, JobPhase } from "./orchestrator.js";
|
|
2
|
+
export type JobState =
|
|
3
|
+
/** `stopRequested` = the deadline watchdog has asked the run to stop at its next step boundary. */
|
|
4
|
+
{
|
|
5
|
+
status: "running";
|
|
6
|
+
phase: JobPhase;
|
|
7
|
+
stopRequested: boolean;
|
|
8
|
+
} | {
|
|
9
|
+
status: "done";
|
|
10
|
+
overall: DeployReport["overall"];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Stamped by `scanOnBoot`, and SYNTHESIZED by {@link DeployJobController.readJournal} for any
|
|
14
|
+
* journal with no terminal line — which is the same thing in almost every case, and not in one:
|
|
15
|
+
* `append` swallows write failures (see its doc), so a deploy that ran to `live` on a full or
|
|
16
|
+
* read-only volume never got its terminal line written and reads `interrupted` forever after.
|
|
17
|
+
* A live process still never produces this state for its OWN job — `status` answers from memory
|
|
18
|
+
* while the job is running.
|
|
19
|
+
*/
|
|
20
|
+
| {
|
|
21
|
+
status: "interrupted";
|
|
22
|
+
};
|
|
23
|
+
export interface DeployJobMeta {
|
|
24
|
+
/** "dpl-" + 8 lowercase hex */
|
|
25
|
+
deployId: string;
|
|
26
|
+
packageId: string;
|
|
27
|
+
version: string;
|
|
28
|
+
packageDir: string;
|
|
29
|
+
budget?: number;
|
|
30
|
+
/** ISO */
|
|
31
|
+
startedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export interface JobSnapshot {
|
|
34
|
+
meta: DeployJobMeta;
|
|
35
|
+
state: JobState;
|
|
36
|
+
/** tail, newest last (capped in memory; the journal holds the full history) */
|
|
37
|
+
events: DeployEvent[];
|
|
38
|
+
/** terminal jobs only */
|
|
39
|
+
report?: DeployReport;
|
|
40
|
+
}
|
|
41
|
+
/** What `run` is handed: its own id, narration in, the deadline stop out. Nothing else. */
|
|
42
|
+
export interface JobHandle {
|
|
43
|
+
/** This job's id — the `senpi.deploy.id` span attribute the run's own spans carry. */
|
|
44
|
+
deployId: string;
|
|
45
|
+
emit(event: DeployEventBody): void;
|
|
46
|
+
shouldAbandon(): boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface DeployJobControllerOptions {
|
|
49
|
+
deploysDir: string;
|
|
50
|
+
now(): number;
|
|
51
|
+
log(line: string): void;
|
|
52
|
+
/** journals retained by the boot scan; default 20 */
|
|
53
|
+
keep?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* H6: every MCP call a deploy makes is separately deadline-bounded. Owned here, beside the grace it
|
|
57
|
+
* determines, so the two can never be hand-synced apart; `register.ts` imports it for its own
|
|
58
|
+
* per-call signals.
|
|
59
|
+
*/
|
|
60
|
+
export declare const MCP_CALL_TIMEOUT_MS = 90000;
|
|
61
|
+
/**
|
|
62
|
+
* How long the soft stop gets to land at a step boundary before the slot is taken back by force.
|
|
63
|
+
*
|
|
64
|
+
* DERIVED, never a bare number: force-terminate releases the single-flight slot while the abandoned
|
|
65
|
+
* run may still be waiting on a call. Sized past the per-call deadline so that by the time the slot
|
|
66
|
+
* frees, every call the abandoned run could still be awaiting has already been abandoned by its own
|
|
67
|
+
* deadline — a NEW deploy never starts its preflight/create while the old job is still parked on a
|
|
68
|
+
* money-moving call against the same funding waterfall. The headroom covers the abort landing and
|
|
69
|
+
* the step-boundary check that follows it.
|
|
70
|
+
*
|
|
71
|
+
* What this does NOT buy: a deadline bounds the WAIT, not the backend's work (`boundAwait` in
|
|
72
|
+
* `senpi/client.ts` — the gateway-injected MCP transport accepts no abort option). A create or fund
|
|
73
|
+
* this job stopped waiting on may still complete server-side after the slot has been handed on.
|
|
74
|
+
* What the grace guarantees is that the job is no longer *waiting* on a money-moving call when the
|
|
75
|
+
* slot comes back; the residue is reconciled by the next deploy, not prevented here.
|
|
76
|
+
*
|
|
77
|
+
* (This supersedes the plan's locked 60s: 60 < the locked 90s call timeout, so the two locked
|
|
78
|
+
* defaults were mutually inconsistent.)
|
|
79
|
+
*/
|
|
80
|
+
export declare const GRACE_MS: number;
|
|
81
|
+
/**
|
|
82
|
+
* The single-flight slot is IN-MEMORY ONLY (`this.live`). It must never be persisted: an
|
|
83
|
+
* interrupted job belongs to a dead process and leaves the slot free by construction — persisting
|
|
84
|
+
* it would wedge every deploy on the box after one crash.
|
|
85
|
+
*/
|
|
86
|
+
export declare class DeployJobController {
|
|
87
|
+
private readonly deploysDir;
|
|
88
|
+
private readonly now;
|
|
89
|
+
private readonly log;
|
|
90
|
+
private readonly keep;
|
|
91
|
+
private live;
|
|
92
|
+
constructor(opts: DeployJobControllerOptions);
|
|
93
|
+
/** The running job's identity and progress, or null. The single-flight gate reads this. */
|
|
94
|
+
running(): (DeployJobMeta & {
|
|
95
|
+
phase: JobPhase;
|
|
96
|
+
stopRequested: boolean;
|
|
97
|
+
}) | null;
|
|
98
|
+
/**
|
|
99
|
+
* Claim the single job slot and fire `run` un-awaited.
|
|
100
|
+
*
|
|
101
|
+
* Single-flight is a money invariant, not a convenience: concurrent deploys
|
|
102
|
+
* share one funding waterfall, so two preflights can both pass while jointly
|
|
103
|
+
* overdrawing. Throws when a job is already running. The ordinary
|
|
104
|
+
* `E_DEPLOY_IN_PROGRESS` refusal comes from the gateway's own `running()`
|
|
105
|
+
* check BEFORE it gets here; this throw is the lost race between that check
|
|
106
|
+
* and this call, and the gateway catches it and renders the same refusal.
|
|
107
|
+
*
|
|
108
|
+
* `capMs` is the job's wall-clock cap (H6). Every MCP call the run makes is separately
|
|
109
|
+
* timeout-bounded, but a cap is what guarantees the slot comes back: one hung await used to hold
|
|
110
|
+
* it until the gateway restarted, and a boundary-honored stop flag never protected that case.
|
|
111
|
+
*/
|
|
112
|
+
start(meta: Omit<DeployJobMeta, "deployId" | "startedAt">, capMs: number, run: (job: JobHandle) => Promise<DeployReport>): DeployJobMeta;
|
|
113
|
+
/**
|
|
114
|
+
* The running job (from memory) when it is the one being asked about — by id or by omission —
|
|
115
|
+
* else the newest journal, or the named one. Unknown id → null.
|
|
116
|
+
*
|
|
117
|
+
* The live check MUST come first even for an explicit id: a
|
|
118
|
+
* running job's journal has no terminal line yet, so falling through to {@link readJournal}
|
|
119
|
+
* would render it `interrupted` — a state only the boot scan may ever assign, and a flat lie
|
|
120
|
+
* about a job that is actively moving money.
|
|
121
|
+
*/
|
|
122
|
+
status(deployId?: string): Promise<JobSnapshot | null>;
|
|
123
|
+
/**
|
|
124
|
+
* C2 carve-out: the ONE sanctioned journal read that feeds the orchestrator — and only as a
|
|
125
|
+
* LOOKUP KEY. Every id returned here is verified against the live backend (strategy_list
|
|
126
|
+
* strategyIds:[…]) before reconcile acts on it; the journal never decides anything.
|
|
127
|
+
*
|
|
128
|
+
* It exists because a create whose custom name the backend rejected lands under a
|
|
129
|
+
* backend-assigned name, which the reconcile name match can never find again: without the key,
|
|
130
|
+
* a re-run creates and funds a second wallet beside the first.
|
|
131
|
+
*
|
|
132
|
+
* Returns instance name → journaled created-strategy ids, newest journals first.
|
|
133
|
+
*
|
|
134
|
+
* `deployId` scopes the answer to ONE journal. Reconcile deliberately passes nothing: every
|
|
135
|
+
* journal of the package is a legitimate key source there, because any of them may hold the id
|
|
136
|
+
* of the wallet a re-run must adopt instead of funding a second one (D-9). The interrupted RENDER
|
|
137
|
+
* passes its own id, because it answers a narrower question — what did THIS job leave behind —
|
|
138
|
+
* and a still-live wallet from an earlier completed deploy is not that. Rendering it there would
|
|
139
|
+
* offer to close a strategy that is running exactly as intended.
|
|
140
|
+
*/
|
|
141
|
+
priorCreatedStrategyIds(packageId: string, deployId?: string): Map<string, string[]>;
|
|
142
|
+
/**
|
|
143
|
+
* The second D-9 lookup input off the SAME create-key event: strategyId → the amount that create
|
|
144
|
+
* call asked the backend to fund.
|
|
145
|
+
*
|
|
146
|
+
* Narrower than {@link priorCreatedStrategyIds} in what it may do, and deliberately so. It feeds
|
|
147
|
+
* exactly one thing — the report's funded-vs-requested comparison — and only the REQUESTED half
|
|
148
|
+
* of it; the funded half is always re-read live from `strategy_list` on the current run. No
|
|
149
|
+
* orchestration branch reads it, so a stale, missing or wrong entry costs a warn, never an action.
|
|
150
|
+
*
|
|
151
|
+
* It exists because the ask lives only in the memory of the run that funded. A deploy that funds
|
|
152
|
+
* $60 of a $500 ask and dies before assembling its report resumes as an adoption, and without
|
|
153
|
+
* this the shortfall is simply never mentioned again.
|
|
154
|
+
*
|
|
155
|
+
* Newest journal wins on a repeated strategyId — the ask that most recently produced that wallet.
|
|
156
|
+
*/
|
|
157
|
+
priorRequestedFunding(packageId: string): Map<string, number>;
|
|
158
|
+
/**
|
|
159
|
+
* Boot janitor: stamp every unterminated journal `interrupted`, then prune to
|
|
160
|
+
* the newest `keep`. Discovery is pull-based — the next `openclaw senpi deploy status`
|
|
161
|
+
* renders the interruption; nothing is resumed or notified here.
|
|
162
|
+
*/
|
|
163
|
+
scanOnBoot(): Promise<void>;
|
|
164
|
+
private recordEvent;
|
|
165
|
+
/**
|
|
166
|
+
* Close a job out. The slot is released in `finally`: a journal-write failure is narration lost,
|
|
167
|
+
* never a wedged single-flight slot that would refuse every future deploy with a stale
|
|
168
|
+
* `E_DEPLOY_IN_PROGRESS`.
|
|
169
|
+
*/
|
|
170
|
+
private terminate;
|
|
171
|
+
private journalPath;
|
|
172
|
+
/**
|
|
173
|
+
* Append one journal line. **Never throws**: the journal is narration, and the narration-only
|
|
174
|
+
* rule cuts both ways — a full or read-only state volume must not be able to change what a
|
|
175
|
+
* deploy does (or wedge its slot). A failed write is logged and the line is lost.
|
|
176
|
+
*/
|
|
177
|
+
private append;
|
|
178
|
+
private journalIds;
|
|
179
|
+
private readLines;
|
|
180
|
+
/** Journals newest-first by header `startedAt` — the only ordering the controller trusts. */
|
|
181
|
+
private journalIdsNewestFirst;
|
|
182
|
+
private newestJournalId;
|
|
183
|
+
/**
|
|
184
|
+
* Journal → snapshot. A journal with no terminal line IS an interrupted job.
|
|
185
|
+
*
|
|
186
|
+
* The FIRST terminal line wins. That ordering is load-bearing: when the deadline watchdog
|
|
187
|
+
* force-terminates a wedged job, the zombie `run` promise may settle later and append a second
|
|
188
|
+
* terminal line — the watchdog's verdict is the true one, and reading the first keeps it.
|
|
189
|
+
*/
|
|
190
|
+
private readJournal;
|
|
191
|
+
/** Retain the newest `keep` journals. Runs on every terminate and on boot, so a long-lived
|
|
192
|
+
* gateway does not grow `deploys/` without bound between restarts. Never throws.
|
|
193
|
+
*
|
|
194
|
+
* Unlocked by design: prune and every journal read are fully synchronous on one thread, so they
|
|
195
|
+
* cannot interleave in-process; a torn last line is already tolerated by `readLines`. Do not add
|
|
196
|
+
* locking without a cross-process caller. */
|
|
197
|
+
private prune;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=job.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../../src/deploy/job.ts"],"names":[],"mappings":"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"}
|