actual-mcp-server 0.22.0 → 0.22.1
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
CHANGED
|
@@ -781,7 +781,7 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
781
781
|
|
|
782
782
|
---
|
|
783
783
|
|
|
784
|
-
**Version:** 0.22.
|
|
784
|
+
**Version:** 0.22.1 | **Tool Count:** 81 (verified LibreChat-compatible)
|
|
785
785
|
|
|
786
786
|
## Sponsor
|
|
787
787
|
|
|
@@ -389,13 +389,29 @@ export async function withActualApi(rawOperation) {
|
|
|
389
389
|
// Denial here means the lock is never acquired and no upstream resource is
|
|
390
390
|
// touched.
|
|
391
391
|
_enforceBudgetAcl();
|
|
392
|
-
// #276: on the FIRST successful op, warn (once) if the Actual server version is outside
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
// #276: on the FIRST successful op, warn (once) if the Actual server version is outside the
|
|
393
|
+
// range this build supports, reusing the live connection via rawGetServerVersion (NOT the
|
|
394
|
+
// withActualApi-wrapped getServerVersion, which would re-enter the lock).
|
|
395
|
+
//
|
|
396
|
+
// It runs AFTER withOpTimeout, not inside it. Review caught that bounding the probe was not
|
|
397
|
+
// enough while it still sat inside the operation's own timeout: its 5 seconds came out of the
|
|
398
|
+
// operation's 30, so an op that took more than ~25s and SUCCEEDED was still reported to the
|
|
399
|
+
// caller as "Actual API operation timed out", and on a deliberately lowered
|
|
400
|
+
// ACTUAL_OP_TIMEOUT_MS the probe alone could consume the entire budget. The timeout text is
|
|
401
|
+
// classed transient, so it would also drop the pooled connection. Still inside withApiLock,
|
|
402
|
+
// because it needs the connection the operation established.
|
|
403
|
+
//
|
|
404
|
+
// WHAT THIS DOES AND DOES NOT BUY, stated precisely because the first version of this comment
|
|
405
|
+
// overclaimed. The probe can no longer FAIL an operation that succeeded. It can still SLOW one,
|
|
406
|
+
// by up to SERVER_VERSION_PROBE_TIMEOUT_MS, and that bound is FIXED: it does not scale down
|
|
407
|
+
// with ACTUAL_OP_TIMEOUT_MS, so an operator who sets the 250ms floor for a fast-fail posture
|
|
408
|
+
// still pays up to ~5s of api-mutex hold against a stalled /info, and on the legacy path can
|
|
409
|
+
// pay it twice in one call (the pre-download site, then here). It is bounded and finite (at
|
|
410
|
+
// most MAX_PROBE_ATTEMPTS times per process, then never again), which is the trade being made:
|
|
411
|
+
// a diagnostic that is occasionally slow beats one that is silently disabled by a single
|
|
412
|
+
// transient failure, which is what the previous design did.
|
|
413
|
+
const runOperation = async () => {
|
|
414
|
+
const result = await withOpTimeout(rawOperation);
|
|
399
415
|
await checkServerVersionOnce(() => rawGetServerVersion(), logger);
|
|
400
416
|
return result;
|
|
401
417
|
};
|
|
@@ -411,7 +427,7 @@ export async function withActualApi(rawOperation) {
|
|
|
411
427
|
// #390: verify the singleton holds THIS session's budget before the operation runs.
|
|
412
428
|
// Inside the lock, so no other session can change it in between.
|
|
413
429
|
await ensureLoadedBudgetMatchesSession();
|
|
414
|
-
return await
|
|
430
|
+
return await runOperation();
|
|
415
431
|
}
|
|
416
432
|
catch (err) {
|
|
417
433
|
// Only drop the pool connection on errors that suggest the api
|
|
@@ -442,7 +458,7 @@ export async function withActualApi(rawOperation) {
|
|
|
442
458
|
let forceFullShutdown = false;
|
|
443
459
|
try {
|
|
444
460
|
await initActualApiForOperation();
|
|
445
|
-
return await
|
|
461
|
+
return await runOperation();
|
|
446
462
|
}
|
|
447
463
|
catch (err) {
|
|
448
464
|
// #419: the stdio keep-alive branch leaves the singleton live; on an
|
|
@@ -3002,13 +3018,13 @@ export async function runBankSync(accountId) {
|
|
|
3002
3018
|
const reset = error?.details?.rateLimitHeaders?.http_x_ratelimit_account_success_reset;
|
|
3003
3019
|
const retryIn = reset ? ` Retry in ~${Math.ceil(Number(reset) / 60)} minute(s).` : '';
|
|
3004
3020
|
throw new Error(`Bank sync failed: GoCardless rate limit exceeded for this account.${retryIn} ` +
|
|
3005
|
-
`(NORDIGEN RATE_LIMIT_EXCEEDED
|
|
3021
|
+
`(NORDIGEN RATE_LIMIT_EXCEEDED: account success quota exhausted)`);
|
|
3006
3022
|
}
|
|
3007
3023
|
if (error?.type === 'BankSyncError' ||
|
|
3008
3024
|
errorMsg.includes('BankSyncError') ||
|
|
3009
3025
|
errorMsg.includes('NORDIGEN_ERROR') ||
|
|
3010
3026
|
errorMsg.includes('Failed syncing account')) {
|
|
3011
|
-
throw new Error(`Bank sync failed: Provider error
|
|
3027
|
+
throw new Error(`Bank sync failed: Provider error: ${errorMsg}`, { cause: error });
|
|
3012
3028
|
}
|
|
3013
3029
|
throw new Error(`Bank sync failed: ${errorMsg}`, { cause: error }); // #452: keep the brand
|
|
3014
3030
|
}
|
|
@@ -411,13 +411,6 @@ async function trackBudgetMutation(start, describeLoaded, label, verify, onRecor
|
|
|
411
411
|
throw err;
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
|
-
/**
|
|
415
|
-
* How long the #453 pre-download version probe may take before it is abandoned. Deliberately a
|
|
416
|
-
* CONSTANT and not an env var: it is an advisory diagnostic, nobody should have to tune it, and
|
|
417
|
-
* a new config key would need to be threaded through config.ts, .env.example and the README
|
|
418
|
-
* table (the config-drift guard enforces all three) to buy nothing.
|
|
419
|
-
*/
|
|
420
|
-
const SERVER_VERSION_PROBE_TIMEOUT_MS = 5000;
|
|
421
414
|
/**
|
|
422
415
|
* #394: the same discipline for an IMPORT.
|
|
423
416
|
*
|
|
@@ -472,27 +465,23 @@ export async function loadBudgetTracked(syncId, encryptionPassword, label = 'dow
|
|
|
472
465
|
// Three properties hold it in place:
|
|
473
466
|
// - OUTSIDE the tracked chain (before the try), because nothing in that chain may take the
|
|
474
467
|
// api lock or extend the window an abandoned load holds open.
|
|
475
|
-
// - BOUNDED by
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// - ONCE per process,
|
|
479
|
-
//
|
|
480
|
-
//
|
|
468
|
+
// - BOUNDED by the GUARD itself, not here. Review of the first version moved it: this runs
|
|
469
|
+
// inside the process-global api mutex, so an unbounded advisory GET would stall every
|
|
470
|
+
// session, and a bound each call site has to remember is one a call site will forget.
|
|
471
|
+
// - ONCE per process ON SUCCESS, and the qualifier matters. The guard's latch is set only
|
|
472
|
+
// when a version was actually READ (#453 review: latching on the attempt meant one failed
|
|
473
|
+
// probe disabled the warning for the life of the process). It is shared with the post-op
|
|
474
|
+
// call site in actual-adapter.ts, so on success whichever fires first wins and the other is
|
|
475
|
+
// a no-op, and this one normally fires first, being earlier. On FAILURE both sites keep
|
|
476
|
+
// retrying until MAX_PROBE_ATTEMPTS, so an unreachable /info costs up to three bounded
|
|
477
|
+
// probes rather than one. That is the deliberate trade: a bounded, finite cost in exchange
|
|
478
|
+
// for not being silenced by a single transient failure.
|
|
481
479
|
//
|
|
482
480
|
// `api.getServerVersion` is read at call time (not destructured) so it needs only the server URL
|
|
483
481
|
// that api.init() already established: NO budget has to be loaded, which is what makes a probe
|
|
484
482
|
// before the download possible at all.
|
|
485
|
-
await checkServerVersionOnce(() =>
|
|
486
|
-
.getServerVersion(),
|
|
487
|
-
// A SHORT dedicated bound, not the general ACTUAL_OP_TIMEOUT_MS (30s by default).
|
|
488
|
-
//
|
|
489
|
-
// Review of the first version caught the reason: `/info` is an unauthenticated GET on a
|
|
490
|
-
// different route from the sync endpoints, so it can stall while the login and download
|
|
491
|
-
// are perfectly healthy. At the general bound that stall costs 30s of the process-global
|
|
492
|
-
// api mutex, and since a failed probe leaves the guard armed it would be paid again on
|
|
493
|
-
// each of the next two loads. An ADVISORY diagnostic must never be able to do that. If
|
|
494
|
-
// the probe cannot answer in 5 seconds it has nothing useful to say.
|
|
495
|
-
SERVER_VERSION_PROBE_TIMEOUT_MS, 'the server-version probe bound (fixed, not configurable)'), log);
|
|
483
|
+
await checkServerVersionOnce(() => api
|
|
484
|
+
.getServerVersion(), log);
|
|
496
485
|
try {
|
|
497
486
|
await trackBudgetMutation(
|
|
498
487
|
// #410: no redundant clear here any more. `trackBudgetMutation` owns the clear-before, and
|
|
@@ -113,15 +113,21 @@ function classifyOne(err) {
|
|
|
113
113
|
const e = err;
|
|
114
114
|
// `.code` FIRST: upstream's api/download-budget and api/load-budget never let a
|
|
115
115
|
// SyncError escape, they throw a plain Error carrying .code via withErrorCode.
|
|
116
|
+
// `Object.hasOwn`, never a bare truthiness test on the lookup: these are object literals, so
|
|
117
|
+
// they inherit Object.prototype and an error carrying `code: 'constructor'` (or 'toString',
|
|
118
|
+
// 'valueOf') would match, returning a `cause` that is not an InitFailureCause and a sentence
|
|
119
|
+
// that is `undefined`. The stdio handler would then answer a tool call with
|
|
120
|
+
// `{ isError: true, content: [{ type: 'text', text: undefined }] }`. Pre-existing from #438,
|
|
121
|
+
// but #452 is what routes EVERY stdio tool error through here, so it is reachable now.
|
|
116
122
|
const code = typeof e?.code === 'string' ? e.code : undefined;
|
|
117
|
-
if (code && REASON_TO_CAUSE
|
|
123
|
+
if (code && Object.hasOwn(REASON_TO_CAUSE, code))
|
|
118
124
|
return done(REASON_TO_CAUSE[code]);
|
|
119
|
-
if (code && SYSTEM_CODE_TO_CAUSE
|
|
125
|
+
if (code && Object.hasOwn(SYSTEM_CODE_TO_CAUSE, code))
|
|
120
126
|
return done(SYSTEM_CODE_TO_CAUSE[code]);
|
|
121
127
|
// `.reason` only as a defensive fallback, for a SyncError that reaches us by
|
|
122
128
|
// some path that does not go through those two handlers.
|
|
123
129
|
const reason = typeof e?.reason === 'string' ? e.reason : undefined;
|
|
124
|
-
if (reason && REASON_TO_CAUSE
|
|
130
|
+
if (reason && Object.hasOwn(REASON_TO_CAUSE, reason))
|
|
125
131
|
return done(REASON_TO_CAUSE[reason]);
|
|
126
132
|
const message = typeof e?.message === 'string' ? e.message : '';
|
|
127
133
|
// Our OWN synthesized post-condition error (#396) embeds the upstream reason
|
|
@@ -130,7 +136,7 @@ function classifyOne(err) {
|
|
|
130
136
|
// handled only the upstream shapes would answer `unknown` for exactly the
|
|
131
137
|
// failures this ticket is about.
|
|
132
138
|
const embedded = /Upstream reason: \[([a-z-]+)\]/.exec(message)?.[1];
|
|
133
|
-
if (embedded && REASON_TO_CAUSE
|
|
139
|
+
if (embedded && Object.hasOwn(REASON_TO_CAUSE, embedded))
|
|
134
140
|
return done(REASON_TO_CAUSE[embedded]);
|
|
135
141
|
// Last resort, message shapes. `network-failure` is tested BEFORE the auth
|
|
136
142
|
// wording because upstream reports an unreachable server as
|
|
@@ -11,12 +11,19 @@
|
|
|
11
11
|
* - The comparator is PURE (no I/O, no logging), so its truth table is unit-testable.
|
|
12
12
|
* - It FAILS OPEN: an unparseable version produces no warning. A spurious warning on a
|
|
13
13
|
* healthy deployment would be worse than staying quiet.
|
|
14
|
-
* - The firing is a per-PROCESS once-guard
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* - The firing is a per-PROCESS once-guard ON SUCCESS, and the qualifier is #453's change:
|
|
15
|
+
* `checked` is set only once a version has actually been READ, with `inFlight` providing the
|
|
16
|
+
* concurrency property the old synchronous latch provided and `attempts` capping the retries
|
|
17
|
+
* at MAX_PROBE_ATTEMPTS. A FAILING probe is therefore retried up to that cap instead of
|
|
18
|
+
* silencing the warning for the life of the process, which is what the synchronous latch did
|
|
19
|
+
* once the probe moved ahead of the first budget download. There are TWO call sites now: the
|
|
20
|
+
* pre-download one in budgetLoader (which issues its own bounded /info read) and the original
|
|
21
|
+
* post-op one in withActualApi (which reuses the connection the triggering op established, so
|
|
22
|
+
* it adds no auth burst, avoiding the #127/#134 class). NEITHER call site bounds its reader:
|
|
23
|
+
* the bound lives in `raceWithBound` below, deliberately, so the set of call sites cannot
|
|
24
|
+
* matter. Do not delete it on the strength of a caller looking safe. There is still NO boot-time probe:
|
|
25
|
+
* index.ts deliberately has no startup connection, and a naive boot call would double-init
|
|
26
|
+
* then be torn down by shutdownActualApi.
|
|
20
27
|
*/
|
|
21
28
|
import { SUPPORTED_ACTUAL_SERVER_RANGE } from './constants.js';
|
|
22
29
|
import { INSTALLED_API_VERSION } from './installed-api-version.js';
|
|
@@ -99,11 +106,63 @@ export function checkServerVersion(running, range = SUPPORTED_ACTUAL_SERVER_RANG
|
|
|
99
106
|
// ---------------------------------------------------------------------------
|
|
100
107
|
// Per-process once-guard.
|
|
101
108
|
// ---------------------------------------------------------------------------
|
|
109
|
+
/**
|
|
110
|
+
* How long a version probe may take before it is abandoned. Deliberately a CONSTANT and not an
|
|
111
|
+
* env var: it is an advisory diagnostic, nobody should have to tune it, and a new config key would
|
|
112
|
+
* need threading through config.ts, .env.example and the README table (the config-drift guard
|
|
113
|
+
* enforces all three) to buy nothing. 5 seconds is far more than a healthy /info needs.
|
|
114
|
+
*/
|
|
115
|
+
export const SERVER_VERSION_PROBE_TIMEOUT_MS = 5000;
|
|
102
116
|
let checked = false; // a version WAS read and judged: never check again
|
|
103
117
|
let inFlight = false; // a check is running right now: a concurrent caller must not duplicate it
|
|
104
118
|
let attempts = 0; // failed attempts, so an unreachable /info cannot be probed forever
|
|
105
119
|
/** Failed probes tolerated before the check gives up for the process. */
|
|
106
120
|
const MAX_PROBE_ATTEMPTS = 3;
|
|
121
|
+
/**
|
|
122
|
+
* Bound the read WITHOUT importing `withOpTimeout`.
|
|
123
|
+
*
|
|
124
|
+
* The obvious implementation reuses that helper, and review caught what it drags in: `opTimeout`
|
|
125
|
+
* imports `config.ts`, which Zod-validates the environment AT MODULE LOAD and hard-exits when it
|
|
126
|
+
* is missing. That breaks this module's own design note, that the comparator is PURE and
|
|
127
|
+
* unit-testable without I/O, and it makes `tests/unit/server_version_guard.test.js` unable to
|
|
128
|
+
* load at all without an environment.
|
|
129
|
+
*
|
|
130
|
+
* ONE CORRECTION to how this was first justified, since a wrong reason in a comment outlives the
|
|
131
|
+
* observation that produced it: the original wording claimed the import turned the unit chain
|
|
132
|
+
* "red locally, green in CI". The chain ALREADY dies without env vars, earlier, at
|
|
133
|
+
* `config_https_validation.test.js`. So the import does not change what the chain does on a bare
|
|
134
|
+
* machine. The property is still worth enforcing (a pure comparator that any harness can import
|
|
135
|
+
* and test in isolation), the drama was not.
|
|
136
|
+
*
|
|
137
|
+
* So the bound is an inline race with a fixed constant, and this module keeps importing nothing
|
|
138
|
+
* but its two pure siblings. The timer is always cleared, so a fast read leaves nothing pending
|
|
139
|
+
* to hold the process open.
|
|
140
|
+
*/
|
|
141
|
+
async function raceWithBound(read) {
|
|
142
|
+
let timer;
|
|
143
|
+
try {
|
|
144
|
+
return await Promise.race([
|
|
145
|
+
read(),
|
|
146
|
+
new Promise((_resolve, reject) => {
|
|
147
|
+
timer = setTimeout(() => reject(new Error(`server-version probe exceeded ${SERVER_VERSION_PROBE_TIMEOUT_MS}ms`)), SERVER_VERSION_PROBE_TIMEOUT_MS);
|
|
148
|
+
// NOT unref'd, and that is deliberate. `unref` was the first instinct ("never hold the
|
|
149
|
+
// process open for an advisory diagnostic") and it quietly breaks the bound: an unref'd
|
|
150
|
+
// timer does not keep the event loop alive, so in a process with nothing else pending
|
|
151
|
+
// Node exits before it fires and the race never rejects. The test that pins this bound
|
|
152
|
+
// caught exactly that, as an unsettled top-level await. A live server always has other
|
|
153
|
+
// work, so it would have fired there and the hazard would have stayed hidden.
|
|
154
|
+
//
|
|
155
|
+
// The cost of keeping it ref'd is bounded by construction: at most
|
|
156
|
+
// SERVER_VERSION_PROBE_TIMEOUT_MS, and the `finally` below clears it as soon as the read
|
|
157
|
+
// answers, so a healthy probe leaves nothing pending at all.
|
|
158
|
+
}),
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
if (timer)
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
107
166
|
/**
|
|
108
167
|
* Run the compatibility check at most once per process. Call this from inside a successful
|
|
109
168
|
* `withActualApi` operation (while the connection is live), passing a function that reads the
|
|
@@ -129,13 +188,19 @@ bundledApi = INSTALLED_API_VERSION) {
|
|
|
129
188
|
// the process, including #276's surviving post-op call site. The deployment that silenced it
|
|
130
189
|
// is precisely the one the guard exists for.
|
|
131
190
|
//
|
|
191
|
+
// THE BOUND IS APPLIED HERE, not by the caller. It started at the call sites and review was
|
|
192
|
+
// right that prose cannot hold it: `inFlight` is cleared in a `finally`, so a reader whose
|
|
193
|
+
// promise NEVER settles leaves it true for the life of the process and permanently disables the
|
|
194
|
+
// warning, which is the very failure this rework removed, just relocated. Bounding inside makes
|
|
195
|
+
// the set of call sites stop mattering, the same argument #393 used for the abandoned-load wait.
|
|
196
|
+
//
|
|
132
197
|
// So: `inFlight` keeps the concurrency property the synchronous latch was actually providing,
|
|
133
198
|
// `checked` is now set only once a version was really read and judged, and `attempts` bounds
|
|
134
199
|
// the retries so an unreachable server costs at most MAX_PROBE_ATTEMPTS extra /info calls per
|
|
135
200
|
// process rather than one per budget load.
|
|
136
201
|
inFlight = true;
|
|
137
202
|
try {
|
|
138
|
-
const result = await readVersion
|
|
203
|
+
const result = await raceWithBound(readVersion);
|
|
139
204
|
if (!result || 'error' in result || typeof result.version !== 'string') {
|
|
140
205
|
attempts++;
|
|
141
206
|
log.debug('[server-version] could not read the Actual server version; skipping the compatibility check');
|
|
@@ -147,10 +212,17 @@ bundledApi = INSTALLED_API_VERSION) {
|
|
|
147
212
|
log.warn(verdict.message);
|
|
148
213
|
}
|
|
149
214
|
}
|
|
150
|
-
catch {
|
|
215
|
+
catch (err) {
|
|
151
216
|
// Advisory only: a failure to check must never surface as an error or affect the op.
|
|
217
|
+
//
|
|
218
|
+
// NAME THE CAUSE. Once the bound moved inside this function, "probe exceeded 5000ms" became
|
|
219
|
+
// the most common way through here, and it is indistinguishable from `rawGetServerVersion`
|
|
220
|
+
// being undefined (it is destructured from `api as any` at module load, so an upstream build
|
|
221
|
+
// that stops exporting it throws a TypeError down this same path). An operator wondering why
|
|
222
|
+
// the compatibility warning never appears would have had nothing to go on.
|
|
152
223
|
attempts++;
|
|
153
|
-
|
|
224
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
225
|
+
log.debug(`[server-version] compatibility check failed; ignored: ${reason}`);
|
|
154
226
|
}
|
|
155
227
|
finally {
|
|
156
228
|
inFlight = false;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.22.
|
|
4
|
+
"version": "0.22.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
31
31
|
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
32
32
|
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
33
|
-
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/account_flow_summary.test.js && node tests/unit/recurring_expenses_summary.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/new_tool_completeness.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/httpServer_init_failure_report.test.js && node tests/unit/stdio_init_failure_report.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/account_flow_summary.test.js && node tests/unit/recurring_expenses_summary.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/new_tool_completeness.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/httpServer_init_failure_report.test.js && node tests/unit/stdio_init_failure_report.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/server_version_probe_outside_op_timeout.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js",
|
|
34
34
|
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
35
35
|
"test:e2e": "npx playwright test",
|
|
36
36
|
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|