@claude-flow/cli 3.54.1 → 3.56.0
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/.claude/helpers/helpers.manifest.json +2 -2
- package/.claude/settings.json +9 -9
- package/bin/cli.js +1 -1
- package/bin/mcp-server.js +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/auth/client.js +40 -24
- package/dist/src/commands/agent.js +12 -0
- package/dist/src/commands/analyze.js +18 -14
- package/dist/src/commands/autopilot.js +2 -2
- package/dist/src/commands/daemon.js +15 -3
- package/dist/src/commands/doctor.js +65 -58
- package/dist/src/commands/hive-mind.js +7 -0
- package/dist/src/commands/mcp.js +35 -1
- package/dist/src/commands/memory-backup.d.ts +1 -1
- package/dist/src/commands/memory-backup.js +23 -9
- package/dist/src/commands/memory.d.ts +0 -4
- package/dist/src/commands/memory.js +10 -2
- package/dist/src/commands/plugins.js +13 -1
- package/dist/src/commands/policy.js +10 -2
- package/dist/src/commands/task.js +12 -0
- package/dist/src/init/settings-generator.js +3 -3
- package/dist/src/mcp-server.d.ts +33 -0
- package/dist/src/mcp-server.js +96 -4
- package/dist/src/mcp-tools/agent-execute-core.js +244 -188
- package/dist/src/mcp-tools/agent-tools.js +15 -16
- package/dist/src/mcp-tools/agentdb-tools.js +22 -3
- package/dist/src/mcp-tools/claims-tools.js +3 -11
- package/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -4
- package/dist/src/mcp-tools/hive-mind-tools.js +214 -40
- package/dist/src/mcp-tools/hooks-tools.js +47 -2
- package/dist/src/mcp-tools/memory-tools.js +29 -10
- package/dist/src/mcp-tools/record-store.d.ts +3 -0
- package/dist/src/mcp-tools/record-store.js +21 -0
- package/dist/src/mcp-tools/session-tools.js +47 -13
- package/dist/src/mcp-tools/task-tools.js +5 -11
- package/dist/src/mcp-tools/workflow-tools.js +78 -29
- package/dist/src/memory/append-conditions.d.ts +28 -0
- package/dist/src/memory/append-conditions.js +56 -0
- package/dist/src/memory/graph-edge-writer.d.ts +9 -0
- package/dist/src/memory/graph-edge-writer.js +90 -0
- package/dist/src/memory/memory-bridge.d.ts +4 -0
- package/dist/src/memory/memory-bridge.js +50 -9
- package/dist/src/memory/memory-initializer.d.ts +4 -0
- package/dist/src/memory/memory-initializer.js +95 -61
- package/dist/src/memory/memory-root.d.ts +2 -0
- package/dist/src/memory/memory-root.js +28 -0
- package/dist/src/memory/sona-optimizer.d.ts +2 -0
- package/dist/src/memory/sona-optimizer.js +4 -0
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/parser.d.ts +3 -0
- package/dist/src/parser.js +71 -13
- package/dist/src/plugins/store/discovery.d.ts +6 -0
- package/dist/src/plugins/store/discovery.js +7 -3
- package/dist/src/production/retry.js +1 -1
- package/dist/src/ruvector/neural-router.js +7 -2
- package/dist/src/services/bounded-worker-pool.js +3 -0
- package/dist/src/services/claim-service.js +39 -18
- package/dist/src/services/claims-store.d.ts +3 -0
- package/dist/src/services/claims-store.js +40 -0
- package/dist/src/services/config-file-manager.d.ts +3 -0
- package/dist/src/services/config-file-manager.js +24 -7
- package/dist/src/services/distill-tuning.js +17 -10
- package/dist/src/services/memory-backup.d.ts +7 -0
- package/dist/src/services/memory-backup.js +29 -2
- package/dist/src/services/memory-distillation.js +2 -2
- package/dist/src/services/policy-ledger-anchor.d.ts +42 -0
- package/dist/src/services/policy-ledger-anchor.js +163 -0
- package/dist/src/services/policy-runtime.d.ts +11 -5
- package/dist/src/services/policy-runtime.js +70 -15
- package/dist/src/services/worker-daemon.d.ts +5 -2
- package/dist/src/services/worker-daemon.js +26 -6
- package/dist/src/services/worker-queue.js +11 -2
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/connection-pool.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js +5 -2
- package/node_modules/@claude-flow/mcp/dist/connection-pool.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts +7 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/transport/http.js +60 -47
- package/node_modules/@claude-flow/mcp/dist/transport/http.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/application/federation-coordinator.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts +3 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js +4 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/domain/services/routing-service.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.d.ts.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js +1 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/plugin.js.map +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/package.json +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts +2 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js +48 -16
- package/node_modules/@claude-flow/security/dist/oauth/callback-server.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts +2 -2
- package/node_modules/@claude-flow/security/dist/path-validator.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/path-validator.js +15 -4
- package/node_modules/@claude-flow/security/dist/path-validator.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +11 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +19 -3
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +4 -2
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/CHANGELOG.md +48 -0
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +80 -7
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
- package/plugins/ruflo-metaharness/scripts/test-cli-resolution-3558.mjs +141 -0
- package/plugins/ruflo-metaharness/scripts/test-no-tool-time-npx.mjs +5 -0
- package/.claude/agents/tmp.json +0 -0
|
@@ -20,6 +20,13 @@ export interface BackupResult {
|
|
|
20
20
|
skipped?: string;
|
|
21
21
|
}
|
|
22
22
|
export declare function defaultMemoryDbPath(cwd?: string): string;
|
|
23
|
+
export interface ProjectBackupResult extends BackupResult {
|
|
24
|
+
backups: Array<BackupResult & {
|
|
25
|
+
dbPath: string;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
/** Back up the active project stores; an explicit full-file override stays single-store. */
|
|
29
|
+
export declare function backupProjectMemoryDbs(cwd: string, opts?: Omit<BackupOptions, 'dbPath'>): Promise<ProjectBackupResult>;
|
|
23
30
|
export declare function backupMemoryDb(opts?: BackupOptions): Promise<BackupResult>;
|
|
24
31
|
export interface RestoreResult {
|
|
25
32
|
restored: boolean;
|
|
@@ -15,9 +15,37 @@
|
|
|
15
15
|
import * as fs from 'fs';
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import { readFileMaybeEncrypted } from '../fs-secure.js';
|
|
18
|
+
import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
|
|
19
|
+
import { resolveMemoryRoot } from '../memory/memory-root.js';
|
|
18
20
|
export function defaultMemoryDbPath(cwd = process.cwd()) {
|
|
19
21
|
return path.join(cwd, '.swarm', 'memory.db');
|
|
20
22
|
}
|
|
23
|
+
/** Back up the active project stores; an explicit full-file override stays single-store. */
|
|
24
|
+
export async function backupProjectMemoryDbs(cwd, opts = {}) {
|
|
25
|
+
const override = process.env.CLAUDE_FLOW_DB_PATH;
|
|
26
|
+
let sources;
|
|
27
|
+
if (override && override.trim().length > 0) {
|
|
28
|
+
sources = [path.resolve(cwd, override)];
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const root = resolveMemoryRoot(cwd);
|
|
32
|
+
sources = [path.join(root, 'memory.db'), path.join(root, 'agentdb-memory.db')];
|
|
33
|
+
}
|
|
34
|
+
const backups = [];
|
|
35
|
+
for (const dbPath of sources) {
|
|
36
|
+
backups.push({ ...await backupMemoryDb({ ...opts, dbPath }), dbPath });
|
|
37
|
+
}
|
|
38
|
+
const successful = backups.filter(result => result.backedUp);
|
|
39
|
+
const failed = backups.find(result => !result.backedUp && result.skipped !== 'no-db');
|
|
40
|
+
return {
|
|
41
|
+
...successful[0],
|
|
42
|
+
backedUp: successful.length > 0 && !failed,
|
|
43
|
+
sizeBytes: successful.reduce((sum, result) => sum + (result.sizeBytes ?? 0), 0),
|
|
44
|
+
rotatedAway: successful.flatMap(result => result.rotatedAway ?? []),
|
|
45
|
+
skipped: failed?.skipped ?? (successful.length === 0 ? 'no-db' : undefined),
|
|
46
|
+
backups,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
21
49
|
/** ISO timestamp safe for filenames (no ':' or '.'). */
|
|
22
50
|
function fileStamp(ms) {
|
|
23
51
|
return new Date(ms).toISOString().replace(/[:.]/g, '-');
|
|
@@ -228,8 +256,7 @@ export async function restoreMemoryDbFromBackup(dbPath, opts = {}) {
|
|
|
228
256
|
// encrypted snapshots and provides the same checks on WASM-only hosts.
|
|
229
257
|
let Database = null;
|
|
230
258
|
try {
|
|
231
|
-
|
|
232
|
-
Database = (await import(mod)).default;
|
|
259
|
+
Database = await loadBetterSqlite3();
|
|
233
260
|
}
|
|
234
261
|
catch {
|
|
235
262
|
/* use the sql.js verifier */
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import * as fs from 'fs';
|
|
28
28
|
import * as path from 'path';
|
|
29
29
|
import { distillTrajectoryContent, serialiseDistilled } from '../memory/structured-distill.js';
|
|
30
|
+
import { loadBetterSqlite3 } from '../memory/shared-sqlite.js';
|
|
30
31
|
function emptyReport(dryRun, extra = {}) {
|
|
31
32
|
return {
|
|
32
33
|
processed: 0, episodes: 0, episodeEmbeddings: 0,
|
|
@@ -104,8 +105,7 @@ export async function runDistillation(options) {
|
|
|
104
105
|
return emptyReport(dryRun, { skipped: 'no-db' });
|
|
105
106
|
let Database;
|
|
106
107
|
try {
|
|
107
|
-
|
|
108
|
-
Database = (await import(mod)).default;
|
|
108
|
+
Database = await loadBetterSqlite3();
|
|
109
109
|
}
|
|
110
110
|
catch {
|
|
111
111
|
return emptyReport(dryRun, { skipped: 'better-sqlite3 unavailable' });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { PolicyState } from '@claude-flow/security';
|
|
2
|
+
export type AnchorEvent = 'append' | 'migrated-from-state' | 'establish-anchor';
|
|
3
|
+
export interface AnchorEntry {
|
|
4
|
+
seq: number;
|
|
5
|
+
length: number;
|
|
6
|
+
headHash: string | null;
|
|
7
|
+
prevAnchorHash: string | null;
|
|
8
|
+
ts: number;
|
|
9
|
+
event: AnchorEvent;
|
|
10
|
+
by?: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
}
|
|
13
|
+
export type AnchorAssessment = {
|
|
14
|
+
kind: 'absent';
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'ok';
|
|
17
|
+
last: AnchorEntry;
|
|
18
|
+
behind: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'fail';
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
export type AnchorWriter = (file: string, value: unknown) => Promise<void>;
|
|
24
|
+
export declare const anchorLogPath: (projectRoot: string) => string;
|
|
25
|
+
/**
|
|
26
|
+
* How the stored anchors relate to `state`; read-only. `rebuild` is the
|
|
27
|
+
* explicit-establish path: an unreadable log or mirror is treated as absent, but
|
|
28
|
+
* whatever readable anchor remains must still agree with the chain, so it can
|
|
29
|
+
* never bless a truncation the anchors still remember.
|
|
30
|
+
*/
|
|
31
|
+
export declare function assessAnchors(projectRoot: string, state: PolicyState, rebuild?: boolean): AnchorAssessment;
|
|
32
|
+
/**
|
|
33
|
+
* Append an entry for the current state if the newest entry does not already
|
|
34
|
+
* describe it. Must run under the policy lock. Throws if the log is corrupt:
|
|
35
|
+
* only an explicit establish may replace a corrupt log.
|
|
36
|
+
*/
|
|
37
|
+
export declare function recordAnchor(projectRoot: string, state: PolicyState, write: AnchorWriter, event?: AnchorEvent, by?: string): Promise<void>;
|
|
38
|
+
export declare function anchorPosture(projectRoot: string): {
|
|
39
|
+
log: boolean;
|
|
40
|
+
mirror: boolean;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=policy-ledger-anchor.d.ts.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Second, hash-chained copy of the policy ledger anchor (#3602, ADR-475).
|
|
3
|
+
*
|
|
4
|
+
* The primary anchor (`ledgerLength` / `ledgerHead`) lives inside state.json,
|
|
5
|
+
* the same file it protects, so deleting it together with receipts used to
|
|
6
|
+
* make `verify` bless the truncated chain. This module keeps an append-only
|
|
7
|
+
* chain of anchor entries in a SEPARATE file plus a mirror of the newest entry
|
|
8
|
+
* outside the project directory (~/.config/ruflo/policy-trust/<project>/).
|
|
9
|
+
*
|
|
10
|
+
* Threat model: this detects any tampering confined to state.json, and any
|
|
11
|
+
* tampering confined to the project directory. It does NOT stop an attacker
|
|
12
|
+
* who can rewrite both the project directory and the user's home directory.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { userInfo } from 'node:os';
|
|
18
|
+
const LOG_FILE = 'ledger-anchors.json';
|
|
19
|
+
const MIRROR_FILE = 'ledger-anchor-head.json';
|
|
20
|
+
export const anchorLogPath = (projectRoot) => join(resolve(projectRoot), '.claude-flow', 'policy', LOG_FILE);
|
|
21
|
+
function mirrorPath(projectRoot) {
|
|
22
|
+
const id = createHash('sha256').update(realpathSync(projectRoot)).digest('hex');
|
|
23
|
+
return join(userInfo().homedir, '.config', 'ruflo', 'policy-trust', id, MIRROR_FILE);
|
|
24
|
+
}
|
|
25
|
+
function entryHash(entry) {
|
|
26
|
+
return createHash('sha256').update(JSON.stringify([
|
|
27
|
+
entry.seq, entry.length, entry.headHash, entry.prevAnchorHash, entry.ts, entry.event, entry.by ?? null,
|
|
28
|
+
])).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
function validEntry(value, seq, prev) {
|
|
31
|
+
const e = value;
|
|
32
|
+
return !!e && typeof e === 'object'
|
|
33
|
+
&& e.seq === seq
|
|
34
|
+
&& Number.isInteger(e.length) && e.length >= 0
|
|
35
|
+
&& (e.headHash === null || typeof e.headHash === 'string')
|
|
36
|
+
&& e.prevAnchorHash === prev
|
|
37
|
+
&& typeof e.ts === 'number'
|
|
38
|
+
&& ['append', 'migrated-from-state', 'establish-anchor'].includes(e.event)
|
|
39
|
+
&& typeof e.hash === 'string'
|
|
40
|
+
&& e.hash === entryHash({ ...e, hash: undefined });
|
|
41
|
+
}
|
|
42
|
+
function readLog(projectRoot) {
|
|
43
|
+
const file = anchorLogPath(projectRoot);
|
|
44
|
+
if (!existsSync(file))
|
|
45
|
+
return [];
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
48
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.entries))
|
|
49
|
+
return 'corrupt';
|
|
50
|
+
let prev = null;
|
|
51
|
+
for (let i = 0; i < parsed.entries.length; i++) {
|
|
52
|
+
if (!validEntry(parsed.entries[i], i, prev))
|
|
53
|
+
return 'corrupt';
|
|
54
|
+
prev = parsed.entries[i].hash;
|
|
55
|
+
}
|
|
56
|
+
return parsed.entries;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return 'corrupt';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readMirror(projectRoot) {
|
|
63
|
+
const file = mirrorPath(projectRoot);
|
|
64
|
+
if (!existsSync(file))
|
|
65
|
+
return null;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
68
|
+
return typeof parsed?.hash === 'string' && parsed.hash === entryHash({ ...parsed, hash: undefined })
|
|
69
|
+
? parsed
|
|
70
|
+
: 'corrupt';
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return 'corrupt';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* How the stored anchors relate to `state`; read-only. `rebuild` is the
|
|
78
|
+
* explicit-establish path: an unreadable log or mirror is treated as absent, but
|
|
79
|
+
* whatever readable anchor remains must still agree with the chain, so it can
|
|
80
|
+
* never bless a truncation the anchors still remember.
|
|
81
|
+
*/
|
|
82
|
+
export function assessAnchors(projectRoot, state, rebuild = false) {
|
|
83
|
+
let entries = readLog(projectRoot);
|
|
84
|
+
if (entries === 'corrupt') {
|
|
85
|
+
if (!rebuild)
|
|
86
|
+
return { kind: 'fail', error: 'policy-anchor-log-corrupt' };
|
|
87
|
+
entries = [];
|
|
88
|
+
}
|
|
89
|
+
let mirror = readMirror(projectRoot);
|
|
90
|
+
if (mirror === 'corrupt') {
|
|
91
|
+
if (!rebuild)
|
|
92
|
+
return { kind: 'fail', error: 'policy-anchor-mirror-corrupt' };
|
|
93
|
+
mirror = null;
|
|
94
|
+
}
|
|
95
|
+
if (entries.length === 0) {
|
|
96
|
+
if (!mirror)
|
|
97
|
+
return { kind: 'absent' };
|
|
98
|
+
if (!rebuild)
|
|
99
|
+
return { kind: 'fail', error: 'policy-anchor-log-missing' };
|
|
100
|
+
if (state.receipts.length < mirror.length)
|
|
101
|
+
return { kind: 'fail', error: 'policy-ledger-truncated' };
|
|
102
|
+
if (mirror.length > 0 && state.receipts[mirror.length - 1].hash !== mirror.headHash) {
|
|
103
|
+
return { kind: 'fail', error: 'policy-ledger-anchor-mismatch' };
|
|
104
|
+
}
|
|
105
|
+
return { kind: 'absent' };
|
|
106
|
+
}
|
|
107
|
+
if (mirror && !entries.some((entry) => entry.hash === mirror.hash)) {
|
|
108
|
+
return { kind: 'fail', error: 'policy-anchor-log-rolled-back' };
|
|
109
|
+
}
|
|
110
|
+
const last = entries[entries.length - 1];
|
|
111
|
+
for (const anchor of mirror ? [last, mirror] : [last]) {
|
|
112
|
+
if (state.receipts.length < anchor.length)
|
|
113
|
+
return { kind: 'fail', error: 'policy-ledger-truncated' };
|
|
114
|
+
if (anchor.length > 0 && state.receipts[anchor.length - 1].hash !== anchor.headHash) {
|
|
115
|
+
return { kind: 'fail', error: 'policy-ledger-anchor-mismatch' };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { kind: 'ok', last, behind: state.receipts.length > last.length };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Append an entry for the current state if the newest entry does not already
|
|
122
|
+
* describe it. Must run under the policy lock. Throws if the log is corrupt:
|
|
123
|
+
* only an explicit establish may replace a corrupt log.
|
|
124
|
+
*/
|
|
125
|
+
export async function recordAnchor(projectRoot, state, write, event = 'append', by) {
|
|
126
|
+
if (state.receipts.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
const loaded = readLog(projectRoot);
|
|
129
|
+
const entries = loaded === 'corrupt' ? [] : loaded;
|
|
130
|
+
if (loaded === 'corrupt' && event !== 'establish-anchor')
|
|
131
|
+
throw new Error('policy-anchor-log-corrupt');
|
|
132
|
+
const last = entries[entries.length - 1];
|
|
133
|
+
const length = state.receipts.length;
|
|
134
|
+
const headHash = state.receipts[length - 1].hash;
|
|
135
|
+
let tip = last;
|
|
136
|
+
if (!last || last.length !== length || last.headHash !== headHash) {
|
|
137
|
+
const base = {
|
|
138
|
+
seq: entries.length,
|
|
139
|
+
length,
|
|
140
|
+
headHash,
|
|
141
|
+
prevAnchorHash: last?.hash ?? null,
|
|
142
|
+
ts: Date.now(),
|
|
143
|
+
event,
|
|
144
|
+
...(by ? { by } : {}),
|
|
145
|
+
};
|
|
146
|
+
tip = { ...base, hash: entryHash(base) };
|
|
147
|
+
await write(anchorLogPath(projectRoot), { version: 1, entries: [...entries, tip] });
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const mirror = mirrorPath(projectRoot);
|
|
151
|
+
mkdirSync(dirname(mirror), { recursive: true, mode: 0o700 });
|
|
152
|
+
await write(mirror, tip);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
// A read-only home must not stop the ledger; verify reports the weaker
|
|
156
|
+
// posture (no mirror) rather than failing closed on every decision.
|
|
157
|
+
process.stderr.write(`[policy] anchor mirror not written: ${error.message}\n`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function anchorPosture(projectRoot) {
|
|
161
|
+
return { log: existsSync(anchorLogPath(projectRoot)), mirror: existsSync(mirrorPath(projectRoot)) };
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=policy-ledger-anchor.js.map
|
|
@@ -22,13 +22,19 @@ export declare function issuePolicyApproval(approval: Omit<PolicyApproval, 'uses
|
|
|
22
22
|
issuedAt?: number;
|
|
23
23
|
}, projectRoot?: string, approvalIssuerVerifier?: (issuer: string) => boolean): Promise<PolicyApproval>;
|
|
24
24
|
export declare function revokePolicyApproval(id: string, projectRoot?: string): Promise<boolean>;
|
|
25
|
+
export type PolicyLedgerVerification = ReturnType<AgenticPolicyEngine['verifyLedger']> & {
|
|
26
|
+
/** How the second anchor was brought in line during this call. */
|
|
27
|
+
secondaryAnchor?: 'recorded-from-state' | 'restored-primary' | 'established-explicitly';
|
|
28
|
+
};
|
|
25
29
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `policy-ledger-verification-failed` and hides which check failed.
|
|
29
|
-
*
|
|
30
|
+
* Verification (#3568, #3602). It must not run inside `withPolicyTransaction`,
|
|
31
|
+
* whose own post-operation check throws a generic
|
|
32
|
+
* `policy-ledger-verification-failed` and hides which check failed. It writes
|
|
33
|
+
* only to bring the second anchor in line, or on an explicit `establishAnchor`.
|
|
30
34
|
*/
|
|
31
|
-
export declare function verifyPolicyLedger(projectRoot?: string
|
|
35
|
+
export declare function verifyPolicyLedger(projectRoot?: string, options?: {
|
|
36
|
+
establishAnchor?: boolean;
|
|
37
|
+
}): Promise<PolicyLedgerVerification>;
|
|
32
38
|
export declare function authorizeMcpTool(toolName: string, input: Record<string, unknown>, context?: Record<string, unknown>, attributes?: Readonly<{
|
|
33
39
|
actionType?: string;
|
|
34
40
|
network?: boolean;
|
|
@@ -5,6 +5,7 @@ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync,
|
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { hostname, userInfo } from 'node:os';
|
|
7
7
|
import { syncPolicyProjection } from '../mods/policy-projection.js';
|
|
8
|
+
import { assessAnchors, recordAnchor } from './policy-ledger-anchor.js';
|
|
8
9
|
const POLICY_DIR = join('.claude-flow', 'policy');
|
|
9
10
|
const POLICY_FILE = 'state.json';
|
|
10
11
|
const LOCK_FILE = 'state.lock';
|
|
@@ -231,8 +232,11 @@ function verifyStateAnchor(projectRoot, state) {
|
|
|
231
232
|
throw new Error('policy-state-authentication-failed');
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
|
-
async function writePolicyState(projectRoot, statePath, state) {
|
|
235
|
+
async function writePolicyState(projectRoot, statePath, state, anchorEvent = 'append', by) {
|
|
235
236
|
await writePolicyStateFiles(projectRoot, statePath, state);
|
|
237
|
+
// #3602: the second anchor is written after the state, so a crash leaves the
|
|
238
|
+
// state AHEAD of the log (caught up on the next write), never behind it.
|
|
239
|
+
await recordAnchor(projectRoot, state, writeJsonAtomic, anchorEvent, by);
|
|
236
240
|
// ADR-404: the ruflo mod reads Claude Code tool rules from a small
|
|
237
241
|
// projection, never from state.json. Written only after the state and its
|
|
238
242
|
// anchor are safely down; a failure here never fails the state write. A
|
|
@@ -350,20 +354,26 @@ export async function withPolicyTransaction(projectRoot, operation, options = {}
|
|
|
350
354
|
mkdirSync(target.dir, { recursive: true, mode: 0o700 });
|
|
351
355
|
const release = await acquireLock(target.lock);
|
|
352
356
|
try {
|
|
353
|
-
const
|
|
357
|
+
const loaded = loadPolicyState(projectRoot);
|
|
358
|
+
const engine = AgenticPolicyEngine.fromState(loaded, {
|
|
354
359
|
signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
|
|
355
360
|
keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
|
|
356
361
|
evidenceVerifier: verifyPolicyEvidence,
|
|
357
362
|
approvalIssuerVerifier: options.approvalIssuerVerifier,
|
|
358
363
|
});
|
|
364
|
+
// #3602: refuse to operate on a ledger whose anchors are missing or
|
|
365
|
+
// disagree; a restored primary anchor is persisted by the write below.
|
|
366
|
+
const gate = reconcileLedger(projectRoot, engine, {});
|
|
367
|
+
if (!gate.result.valid) {
|
|
368
|
+
throw new Error(gate.result.error === 'anchor-missing'
|
|
369
|
+
? `policy-ledger-anchor-missing: receipts exist but no ledger anchor was found. If this ledger predates anchors, run \`ruflo policy verify --establish-anchor\`; otherwise treat it as tampering.`
|
|
370
|
+
: gate.result.error ?? 'policy-ledger-verification-failed');
|
|
371
|
+
}
|
|
359
372
|
const result = await operation(engine);
|
|
360
|
-
// Verify before exporting: verification establishes the ledger anchor
|
|
361
|
-
// (#3568) on state written before the anchor existed, and that anchor
|
|
362
|
-
// must be part of what is persisted.
|
|
363
373
|
if (!engine.verifyLedger().valid)
|
|
364
374
|
throw new Error('policy-ledger-verification-failed');
|
|
365
375
|
const nextState = engine.exportState();
|
|
366
|
-
await writePolicyState(projectRoot, target.state, nextState);
|
|
376
|
+
await writePolicyState(projectRoot, target.state, nextState, gate.event);
|
|
367
377
|
return result;
|
|
368
378
|
}
|
|
369
379
|
finally {
|
|
@@ -416,12 +426,56 @@ export async function revokePolicyApproval(id, projectRoot = process.cwd()) {
|
|
|
416
426
|
return withPolicyTransaction(projectRoot, (engine) => engine.revokeApproval(id));
|
|
417
427
|
}
|
|
418
428
|
/**
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
429
|
+
* Decide whether the ledger may be trusted, using the chain, the primary anchor
|
|
430
|
+
* in state.json and the second anchor (#3602). It never creates an anchor over
|
|
431
|
+
* existing receipts on its own: only an authentic second anchor (restoring a
|
|
432
|
+
* deleted primary) or an explicit `establishAnchor` does.
|
|
433
|
+
*/
|
|
434
|
+
function reconcileLedger(projectRoot, engine, options) {
|
|
435
|
+
const state = engine.exportState();
|
|
436
|
+
const length = state.receipts.length;
|
|
437
|
+
const primary = typeof state.ledgerLength === 'number';
|
|
438
|
+
const base = engine.verifyLedger();
|
|
439
|
+
const fail = (error) => ({ result: { valid: false, length, error }, persist: false });
|
|
440
|
+
if (!base.valid && base.error !== 'anchor-missing')
|
|
441
|
+
return { result: base, persist: false };
|
|
442
|
+
const assessed = assessAnchors(projectRoot, state, options.establishAnchor === true);
|
|
443
|
+
if (assessed.kind === 'fail')
|
|
444
|
+
return fail(assessed.error);
|
|
445
|
+
if (primary) {
|
|
446
|
+
if (assessed.kind === 'absent' && length > 0) {
|
|
447
|
+
return options.establishAnchor
|
|
448
|
+
? { result: { ...base, secondaryAnchor: 'established-explicitly' }, persist: true, event: 'establish-anchor' }
|
|
449
|
+
: { result: { ...base, secondaryAnchor: 'recorded-from-state' }, persist: true, event: 'migrated-from-state' };
|
|
450
|
+
}
|
|
451
|
+
return { result: base, persist: assessed.kind === 'ok' && assessed.behind, event: 'append' };
|
|
452
|
+
}
|
|
453
|
+
// No primary anchor.
|
|
454
|
+
if (length === 0)
|
|
455
|
+
return { result: { valid: true, length, state: 'empty' }, persist: false };
|
|
456
|
+
if (assessed.kind === 'ok') {
|
|
457
|
+
// The second anchor is authentic and agrees with the chain: the deleted
|
|
458
|
+
// primary can be restored from it. Truncation was already rejected above.
|
|
459
|
+
engine.verifyLedger({ establishAnchor: true });
|
|
460
|
+
return { result: { valid: true, length, secondaryAnchor: 'restored-primary' }, persist: true, event: 'append' };
|
|
461
|
+
}
|
|
462
|
+
if (options.establishAnchor) {
|
|
463
|
+
const established = engine.verifyLedger({ establishAnchor: true });
|
|
464
|
+
return {
|
|
465
|
+
result: { ...established, secondaryAnchor: 'established-explicitly' },
|
|
466
|
+
persist: true,
|
|
467
|
+
event: 'establish-anchor',
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return fail('anchor-missing');
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Verification (#3568, #3602). It must not run inside `withPolicyTransaction`,
|
|
474
|
+
* whose own post-operation check throws a generic
|
|
475
|
+
* `policy-ledger-verification-failed` and hides which check failed. It writes
|
|
476
|
+
* only to bring the second anchor in line, or on an explicit `establishAnchor`.
|
|
423
477
|
*/
|
|
424
|
-
export async function verifyPolicyLedger(projectRoot = process.cwd()) {
|
|
478
|
+
export async function verifyPolicyLedger(projectRoot = process.cwd(), options = {}) {
|
|
425
479
|
const target = paths(projectRoot);
|
|
426
480
|
mkdirSync(target.dir, { recursive: true, mode: 0o700 });
|
|
427
481
|
const release = await acquireLock(target.lock);
|
|
@@ -430,11 +484,12 @@ export async function verifyPolicyLedger(projectRoot = process.cwd()) {
|
|
|
430
484
|
signingKey: process.env.CLAUDE_FLOW_POLICY_SIGNING_KEY,
|
|
431
485
|
keyId: process.env.CLAUDE_FLOW_POLICY_KEY_ID,
|
|
432
486
|
});
|
|
433
|
-
const
|
|
434
|
-
if (
|
|
435
|
-
|
|
487
|
+
const outcome = reconcileLedger(projectRoot, engine, options);
|
|
488
|
+
if (outcome.persist) {
|
|
489
|
+
const by = outcome.event === 'establish-anchor' ? userInfo().username : undefined;
|
|
490
|
+
await writePolicyState(projectRoot, target.state, engine.exportState(), outcome.event, by);
|
|
436
491
|
}
|
|
437
|
-
return result;
|
|
492
|
+
return outcome.result;
|
|
438
493
|
}
|
|
439
494
|
finally {
|
|
440
495
|
release();
|
|
@@ -67,7 +67,10 @@ export interface DaemonConfig {
|
|
|
67
67
|
idleShutdownMs: number;
|
|
68
68
|
aiWorkersEnabled: boolean;
|
|
69
69
|
workers: WorkerConfig[];
|
|
70
|
+
enabledWorkers?: string[];
|
|
70
71
|
}
|
|
72
|
+
/** Parse only worker types the daemon can actually schedule. */
|
|
73
|
+
export declare function parseEnabledWorkers(value: string): WorkerType[];
|
|
71
74
|
/**
|
|
72
75
|
* Worker Daemon - Manages background workers with Node.js
|
|
73
76
|
*/
|
|
@@ -367,8 +370,8 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
367
370
|
*/
|
|
368
371
|
private runConsolidateWorker;
|
|
369
372
|
/**
|
|
370
|
-
* Nightly memory-DB backup worker (24h interval). Takes
|
|
371
|
-
*
|
|
373
|
+
* Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
|
|
374
|
+
* snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
|
|
372
375
|
* worker must not crash the daemon; a skip/error is written to the metrics
|
|
373
376
|
* file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
|
|
374
377
|
* RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
|
|
@@ -24,7 +24,7 @@ import { HeadlessWorkerExecutor, isHeadlessWorker, } from './headless-worker-exe
|
|
|
24
24
|
// incremental (rowid cursor), non-destructive, transactional, and
|
|
25
25
|
// quick_check-gated, so it's safe to call unconditionally on every tick.
|
|
26
26
|
import { runDistillation, defaultMemoryDbPath } from './memory-distillation.js';
|
|
27
|
-
import {
|
|
27
|
+
import { backupProjectMemoryDbs } from './memory-backup.js';
|
|
28
28
|
import { resolveGitWorkspaceIdentity } from './git-workspace-identity.js';
|
|
29
29
|
import { getWorkspaceLeaseRegistry } from './workspace-lease.js';
|
|
30
30
|
import { getRepoSupervisorRegistry } from './repo-supervisor.js';
|
|
@@ -43,6 +43,14 @@ const DEFAULT_WORKERS = [
|
|
|
43
43
|
{ type: 'predict', intervalMs: 10 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Predictive preloading', enabled: false },
|
|
44
44
|
{ type: 'document', intervalMs: 60 * 60 * 1000, offsetMs: 0, priority: 'low', description: 'Auto-documentation', enabled: false },
|
|
45
45
|
];
|
|
46
|
+
/** Parse only worker types the daemon can actually schedule. */
|
|
47
|
+
export function parseEnabledWorkers(value) {
|
|
48
|
+
const names = value.split(',');
|
|
49
|
+
if (!names.length || names.some(name => !DEFAULT_WORKERS.some(worker => worker.type === name))) {
|
|
50
|
+
throw new Error(`--workers must be a comma-separated list of: ${DEFAULT_WORKERS.map(worker => worker.type).join(',')}`);
|
|
51
|
+
}
|
|
52
|
+
return [...new Set(names)];
|
|
53
|
+
}
|
|
46
54
|
// Worker timeout — must exceed the longest per-worker headless timeout (15 min for audit/refactor).
|
|
47
55
|
// Previously 5 min, which caused orphan processes when daemon timeout fired before executor timeout (#1117).
|
|
48
56
|
const DEFAULT_WORKER_TIMEOUT_MS = 16 * 60 * 1000;
|
|
@@ -192,7 +200,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
192
200
|
aiWorkersEnabled: config?.aiWorkersEnabled
|
|
193
201
|
?? fileConfig.aiWorkersEnabled
|
|
194
202
|
?? (process.env.RUFLO_DAEMON_AI_WORKERS === '1'),
|
|
195
|
-
workers: config?.workers ?? DEFAULT_WORKERS,
|
|
203
|
+
workers: (config?.workers ?? DEFAULT_WORKERS).map(worker => ({ ...worker })),
|
|
204
|
+
enabledWorkers: config?.enabledWorkers,
|
|
196
205
|
};
|
|
197
206
|
// #2935 — deferred from readDaemonConfigFromFile(): this.config (and
|
|
198
207
|
// therefore this.config.logDir) doesn't exist until the assignment
|
|
@@ -700,6 +709,17 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
700
709
|
// Ignore parse errors, start fresh
|
|
701
710
|
}
|
|
702
711
|
}
|
|
712
|
+
// #3547 (#1968 follow-up): an explicit `--workers` selection wins over
|
|
713
|
+
// both DEFAULT_WORKERS and whatever daemon-state.json just restored
|
|
714
|
+
// above — otherwise `daemon start --workers map,audit` always ran the
|
|
715
|
+
// full default set (or a stale saved selection) because nothing ever
|
|
716
|
+
// applied the requested list to `enabled`.
|
|
717
|
+
if (this.config.enabledWorkers) {
|
|
718
|
+
const selected = new Set(this.config.enabledWorkers);
|
|
719
|
+
for (const workerConfig of this.config.workers) {
|
|
720
|
+
workerConfig.enabled = selected.has(workerConfig.type);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
703
723
|
// Initialize any missing workers
|
|
704
724
|
for (const workerConfig of this.config.workers) {
|
|
705
725
|
if (!this.workers.has(workerConfig.type)) {
|
|
@@ -1615,8 +1635,8 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1615
1635
|
return result;
|
|
1616
1636
|
}
|
|
1617
1637
|
/**
|
|
1618
|
-
* Nightly memory-DB backup worker (24h interval). Takes
|
|
1619
|
-
*
|
|
1638
|
+
* Nightly memory-DB backup worker (24h interval). Takes WAL-safe, consistent
|
|
1639
|
+
* snapshots of the active CLI and AgentDB stores with rotation (keep last N). Never throws — a
|
|
1620
1640
|
* worker must not crash the daemon; a skip/error is written to the metrics
|
|
1621
1641
|
* file. Opt-out by omitting `backup` from `-w`; offsite GCS is opt-in via
|
|
1622
1642
|
* RUFLO_BACKUP_GCS (a gs:// prefix), retention via RUFLO_BACKUP_KEEP.
|
|
@@ -1629,14 +1649,14 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1629
1649
|
let result;
|
|
1630
1650
|
try {
|
|
1631
1651
|
const keepEnv = Number(process.env.RUFLO_BACKUP_KEEP);
|
|
1632
|
-
const r = await
|
|
1633
|
-
dbPath: defaultMemoryDbPath(this.projectRoot),
|
|
1652
|
+
const r = await backupProjectMemoryDbs(this.projectRoot, {
|
|
1634
1653
|
keep: Number.isFinite(keepEnv) && keepEnv > 0 ? keepEnv : 7,
|
|
1635
1654
|
gcs: process.env.RUFLO_BACKUP_GCS || undefined,
|
|
1636
1655
|
});
|
|
1637
1656
|
result = {
|
|
1638
1657
|
timestamp: new Date().toISOString(),
|
|
1639
1658
|
backedUp: r.backedUp,
|
|
1659
|
+
backups: r.backups,
|
|
1640
1660
|
path: r.path,
|
|
1641
1661
|
sizeBytes: r.sizeBytes ?? 0,
|
|
1642
1662
|
rotatedAway: r.rotatedAway?.length ?? 0,
|
|
@@ -233,8 +233,9 @@ export class WorkerQueue extends EventEmitter {
|
|
|
233
233
|
// Check queues in priority order
|
|
234
234
|
for (const workerType of workerTypes) {
|
|
235
235
|
const queueName = this.getQueueName(workerType);
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
let taskId;
|
|
237
|
+
// Cancelled or otherwise stale entries do not exhaust this worker type.
|
|
238
|
+
while ((taskId = this.store.popFromQueue(queueName)) !== null) {
|
|
238
239
|
const task = this.store.getTask(taskId);
|
|
239
240
|
if (task && task.status === 'pending') {
|
|
240
241
|
task.status = 'processing';
|
|
@@ -258,6 +259,10 @@ export class WorkerQueue extends EventEmitter {
|
|
|
258
259
|
this.emit('warning', { message: `Task ${taskId} not found for completion` });
|
|
259
260
|
return;
|
|
260
261
|
}
|
|
262
|
+
// Late or duplicate outcomes must not rewrite a settled task.
|
|
263
|
+
if (['completed', 'failed', 'timeout', 'cancelled'].includes(task.status)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
261
266
|
task.status = 'completed';
|
|
262
267
|
task.completedAt = new Date();
|
|
263
268
|
task.result = result;
|
|
@@ -276,6 +281,10 @@ export class WorkerQueue extends EventEmitter {
|
|
|
276
281
|
this.emit('warning', { message: `Task ${taskId} not found for failure` });
|
|
277
282
|
return;
|
|
278
283
|
}
|
|
284
|
+
// Late or duplicate outcomes must not rewrite a settled task.
|
|
285
|
+
if (['completed', 'failed', 'timeout', 'cancelled'].includes(task.status)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
279
288
|
this.processingTasks.delete(taskId);
|
|
280
289
|
// Check if we should retry
|
|
281
290
|
if (retryable && task.retryCount < task.maxRetries) {
|
|
File without changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,OAAO,EACP,aAAa,EACd,MAAM,YAAY,CAAC;AAoDpB,qBAAa,cAAe,SAAQ,YAAa,YAAW,eAAe;IAmBvE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAnBhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IACzE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IACtD,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,cAAc,CAAkB;IAExC,OAAO,CAAC,KAAK,CAOX;gBAGA,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EACzB,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B;YAQhD,wBAAwB;YAWxB,gBAAgB;
|
|
1
|
+
{"version":3,"file":"connection-pool.d.ts","sourceRoot":"","sources":["../src/connection-pool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,OAAO,EACP,aAAa,EACd,MAAM,YAAY,CAAC;AAoDpB,qBAAa,cAAe,SAAQ,YAAa,YAAW,eAAe;IAmBvE,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAnBhC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6C;IACzE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAuB;IACtD,OAAO,CAAC,aAAa,CAAC,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,cAAc,CAAkB;IAExC,OAAO,CAAC,KAAK,CAOX;gBAGA,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EACzB,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B;YAQhD,wBAAwB;YAWxB,gBAAgB;IAexB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAiC1C,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAuB3C,OAAO,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI;IAoB3C,QAAQ,IAAI,mBAAmB;IAwBzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,oBAAoB;IAwB5B,OAAO,CAAC,iBAAiB;IAMzB,cAAc,IAAI,gBAAgB,EAAE;IAIpC,SAAS,IAAI,OAAO;CAGrB;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,CAAC,oBAAoB,CAAC,YAAK,EAC1C,MAAM,EAAE,OAAO,EACf,aAAa,GAAE,aAA4B,GAC1C,cAAc,CAEhB"}
|
|
@@ -80,9 +80,12 @@ export class ConnectionPool extends EventEmitter {
|
|
|
80
80
|
minConnections: this.config.minConnections,
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
-
async createConnection() {
|
|
83
|
+
async createConnection(reserved = false) {
|
|
84
84
|
const id = `conn-${++this.connectionCounter}-${Date.now()}`;
|
|
85
85
|
const connection = new ManagedConnection(id, this.transportType);
|
|
86
|
+
// Reserve before exposing it in the map or yielding to another acquirer.
|
|
87
|
+
if (reserved)
|
|
88
|
+
connection.state = 'busy';
|
|
86
89
|
this.connections.set(id, connection);
|
|
87
90
|
this.stats.totalCreated++;
|
|
88
91
|
this.emit('pool:connection:created', { connectionId: id });
|
|
@@ -105,7 +108,7 @@ export class ConnectionPool extends EventEmitter {
|
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
110
|
if (this.connections.size < this.config.maxConnections) {
|
|
108
|
-
const connection = await this.createConnection();
|
|
111
|
+
const connection = await this.createConnection(true);
|
|
109
112
|
connection.acquire();
|
|
110
113
|
this.stats.totalAcquired++;
|
|
111
114
|
this.recordAcquireTime(startTime);
|