agents-can-communicate 0.8.0 → 0.8.2
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/bin/entrypoints/acc-mcp.mjs +0 -1
- package/bin/entrypoints/acc.mjs +0 -1
- package/docs/ADAPTER_AUTHORING.md +50 -17
- package/docs/ARCHITECTURE.md +9 -6
- package/docs/CAPABILITIES.md +24 -14
- package/docs/CLI.md +22 -3
- package/docs/CONFIGURATION.md +73 -8
- package/docs/GETTING_STARTED.md +6 -3
- package/docs/HOW_IT_WORKS.md +3 -1
- package/docs/PROTOCOL.md +2 -2
- package/docs/RELEASING.md +6 -4
- package/docs/SECURITY_MODEL.md +12 -1
- package/docs/TROUBLESHOOTING.md +34 -7
- package/docs/UPGRADING.md +29 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/plugin.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-antigravity/plugin/skills/acc/SKILL.md +7 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/adapter.mjs +5 -4
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/allow-rule.mjs +204 -0
- package/node_modules/@agents-can-communicate/adapter-antigravity/src/install.mjs +45 -10
- package/node_modules/@agents-can-communicate/adapter-claude-code/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/adapter.mjs +14 -4
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-delivery.mjs +12 -17
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-endpoint.mjs +5 -3
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-observed-directories.mjs +62 -0
- package/node_modules/@agents-can-communicate/adapter-claude-code/src/inbox-socket-directories.mjs +41 -0
- package/node_modules/@agents-can-communicate/adapter-codex/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-codex/src/adapter.mjs +6 -4
- package/node_modules/@agents-can-communicate/adapter-codex/src/app-server-client.mjs +10 -13
- package/node_modules/@agents-can-communicate/adapter-codex/src/install.mjs +5 -4
- package/node_modules/@agents-can-communicate/adapter-codex/src/live-permissions.mjs +69 -17
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance-host.mjs +46 -10
- package/node_modules/@agents-can-communicate/adapter-codex/src/maintenance.mjs +5 -7
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-delivery.mjs +16 -12
- package/node_modules/@agents-can-communicate/adapter-codex/src/native-endpoint.mjs +12 -2
- package/node_modules/@agents-can-communicate/adapter-codex/src/service-setup.mjs +26 -9
- package/node_modules/@agents-can-communicate/adapter-codex/src/ws-json-rpc.mjs +12 -0
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/extension/gemini-extension.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-gemini-cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-grok/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-kimi/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/package.json +1 -1
- package/node_modules/@agents-can-communicate/adapter-sdk/src/capabilities.mjs +7 -0
- package/node_modules/@agents-can-communicate/adapter-sdk/src/certification.mjs +2 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/hook-shim.mjs +16 -2
- package/node_modules/@agents-can-communicate/adapter-sdk/src/index.mjs +4 -3
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-delivery.mjs +85 -77
- package/node_modules/@agents-can-communicate/adapter-sdk/src/native-vocabulary.mjs +4 -4
- package/node_modules/@agents-can-communicate/cli/package.json +1 -1
- package/node_modules/@agents-can-communicate/cli/src/doctor-command.mjs +9 -4
- package/node_modules/@agents-can-communicate/cli/src/install-command.mjs +11 -4
- package/node_modules/@agents-can-communicate/cli/src/main.mjs +49 -12
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/maintenance.mjs +9 -15
- package/node_modules/@agents-can-communicate/cli/src/managed-runtime/pins.mjs +13 -4
- package/node_modules/@agents-can-communicate/cli/src/native-session-diagnostics.mjs +20 -10
- package/node_modules/@agents-can-communicate/core/package.json +1 -1
- package/node_modules/@agents-can-communicate/core/src/receipts.mjs +17 -2
- package/node_modules/@agents-can-communicate/core/src/status.mjs +26 -2
- package/node_modules/@agents-can-communicate/delivery-router/package.json +1 -1
- package/node_modules/@agents-can-communicate/delivery-router/src/refresh-binding.mjs +2 -2
- package/node_modules/@agents-can-communicate/delivery-router/src/router.mjs +9 -41
- package/node_modules/@agents-can-communicate/hook-runner/package.json +1 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/client-pid.mjs +30 -1
- package/node_modules/@agents-can-communicate/hook-runner/src/native-binding.mjs +3 -4
- package/node_modules/@agents-can-communicate/hook-runner/src/process-table.mjs +30 -14
- package/node_modules/@agents-can-communicate/hook-runner/src/runner.mjs +12 -1
- package/node_modules/@agents-can-communicate/installer/package.json +1 -1
- package/node_modules/@agents-can-communicate/installer/src/delivery-diagnostics.mjs +0 -2
- package/node_modules/@agents-can-communicate/installer/src/detect.mjs +13 -7
- package/node_modules/@agents-can-communicate/installer/src/index.mjs +1 -0
- package/node_modules/@agents-can-communicate/installer/src/receiver-sockets.mjs +19 -0
- package/node_modules/@agents-can-communicate/mcp-server/package.json +1 -1
- package/node_modules/@agents-can-communicate/protocol/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/package.json +1 -1
- package/node_modules/@agents-can-communicate/storage-filesystem/src/atomic-json.mjs +30 -4
- package/node_modules/@agents-can-communicate/storage-filesystem/src/safe-directory.mjs +56 -25
- package/node_modules/@agents-can-communicate/storage-filesystem/src/store.mjs +6 -2
- package/package.json +1 -1
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
// reasons; installation and doctor must not turn a version floor into readiness.
|
|
3
3
|
const REASONS = Object.freeze({
|
|
4
4
|
native_delivery_unsupported: "this adapter has no native delivery channel",
|
|
5
|
-
platform_not_captured: "native delivery is not verified on this platform",
|
|
6
5
|
version_unavailable: "the client version could not be verified",
|
|
7
|
-
prerelease_not_captured: "this client version is not a verified stable release",
|
|
8
6
|
below_minimum_version: "the client is below the native delivery minimum version",
|
|
9
7
|
known_bad_version: "this client version has a known native delivery failure",
|
|
10
8
|
native_endpoint_unavailable: "the client's local delivery service is unavailable",
|
|
@@ -13,9 +13,8 @@ const run = promisify(execFile);
|
|
|
13
13
|
|
|
14
14
|
// Reasons the client itself will not change within an install: unsupported,
|
|
15
15
|
// as opposed to degraded, which a repaired probe or shell may lift.
|
|
16
|
-
const STATIC_REASONS = new Set(["native_delivery_unsupported", "
|
|
17
|
-
"
|
|
18
|
-
"known_bad_version"]);
|
|
16
|
+
const STATIC_REASONS = new Set(["native_delivery_unsupported", "version_unavailable",
|
|
17
|
+
"below_minimum_version", "known_bad_version"]);
|
|
19
18
|
|
|
20
19
|
const DEFAULT_PROBE_TIMEOUT_MS = 3_000;
|
|
21
20
|
|
|
@@ -32,8 +31,10 @@ const VERSION = /(?:^|[^0-9A-Za-z])v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)(?:\b
|
|
|
32
31
|
* has: nothing is written, and a client that is not installed simply fails to
|
|
33
32
|
* start.
|
|
34
33
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
// `env` decides where the command is looked up, so detection given an
|
|
35
|
+
// environment finds the client on that environment's PATH.
|
|
36
|
+
export const spawnProbe = async (command, args, { env } = {}) => {
|
|
37
|
+
const { stdout, stderr } = await run(command, args, env === undefined ? {} : { env });
|
|
37
38
|
return `${stdout}${stderr}`.trim();
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -76,7 +77,7 @@ async function detectNative(adapter, entry, { context, platform, probeTimeoutMs,
|
|
|
76
77
|
}
|
|
77
78
|
try {
|
|
78
79
|
facts.eligibility = evaluateNativeEligibility(adapter,
|
|
79
|
-
{ clientVersion: entry.version,
|
|
80
|
+
{ clientVersion: entry.version, probe: facts.probe });
|
|
80
81
|
} catch {
|
|
81
82
|
facts.eligibility = { eligible: false, reasonCode: "feature_probe_failed" };
|
|
82
83
|
}
|
|
@@ -123,8 +124,11 @@ export async function detectInstallation({ adapters, context, probe = spawnProbe
|
|
|
123
124
|
const output = await withTimeout(
|
|
124
125
|
// The declared binary, never the adapter id. Guessing the id made
|
|
125
126
|
// every client whose command differs from it look uninstalled.
|
|
127
|
+
// On the PATH the native probes below use: a caller that passes its
|
|
128
|
+
// own environment must not find a different client, or none.
|
|
126
129
|
Promise.resolve(probe(adapter.client.command,
|
|
127
|
-
adapter.client.versionArgs ?? ["--version"]
|
|
130
|
+
adapter.client.versionArgs ?? ["--version"],
|
|
131
|
+
{ env: context?.env === undefined ? undefined : { ...process.env, ...context.env } })),
|
|
128
132
|
probeTimeoutMs, `${adapter.id} version probe`);
|
|
129
133
|
if (typeof output === "string" && output !== "") {
|
|
130
134
|
entry.present = true;
|
|
@@ -161,6 +165,8 @@ export async function detectInstallation({ adapters, context, probe = spawnProbe
|
|
|
161
165
|
if (detected.blocked) entry.blocked = detected.blocked;
|
|
162
166
|
if (detected.outgoingDelivery) entry.outgoingDelivery = detected.outgoingDelivery;
|
|
163
167
|
if (detected.inboundDelivery) entry.inboundDelivery = detected.inboundDelivery;
|
|
168
|
+
// Whether the client asks before each ACC command, and what would stop it.
|
|
169
|
+
if (detected.commandApproval) entry.commandApproval = detected.commandApproval;
|
|
164
170
|
if (detected.nativeSetup) entry.nativeSetup = detected.nativeSetup;
|
|
165
171
|
entry.diagnostics = [...(detected.diagnostics ?? [])];
|
|
166
172
|
// What a person has to do, as opposed to what is true. Adapters that
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Detection, planning, application, and the record of what was installed.
|
|
2
2
|
export { detectInstallation, spawnProbe } from "./detect.mjs";
|
|
3
3
|
export { planInstallation } from "./plan.mjs";
|
|
4
|
+
export { receiverSockets } from "./receiver-sockets.mjs";
|
|
4
5
|
export { applyPlan } from "./apply.mjs";
|
|
5
6
|
export { finalizeRemoval, fingerprint, loadOwnership, missingArtifactParents, recordInstall,
|
|
6
7
|
removeEmptyOwnedDirectories, removeOwned, removeOwnedArtifacts, treeFingerprint, verifyOwned }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every Unix socket path ACC can deliver to on this machine.
|
|
5
|
+
*
|
|
6
|
+
* A sender whose client sandboxes its shell - Codex - has to be allowed to
|
|
7
|
+
* connect to the recipient's socket, and the recipient's adapter is the only
|
|
8
|
+
* one that knows where that socket lives. Each adapter declares its own through
|
|
9
|
+
* `inboundSockets(context)`; the sender's adapter receives the composed list as
|
|
10
|
+
* `context.receiverSockets` and never names another vendor's paths itself.
|
|
11
|
+
*
|
|
12
|
+
* Composed from every known adapter, not the ones an install names: installing
|
|
13
|
+
* Codex alone must still allow a Claude Code session installed before or after.
|
|
14
|
+
*/
|
|
15
|
+
export function receiverSockets(adapters, context) {
|
|
16
|
+
const paths = [...adapters].flatMap(adapter =>
|
|
17
|
+
typeof adapter.inboundSockets === "function" ? adapter.inboundSockets(context) ?? [] : []);
|
|
18
|
+
return [...new Set(paths.filter(item => typeof item === "string" && path.isAbsolute(item)))].sort();
|
|
19
|
+
}
|
|
@@ -64,7 +64,8 @@ async function replaceHandleBytes(handle, bytes) {
|
|
|
64
64
|
* @returns {Promise<"published" | "already_published">}
|
|
65
65
|
*/
|
|
66
66
|
export async function publishAtomic(destination, bytes,
|
|
67
|
-
{ root, tmpDir, stageDir, replace = false, deadlineAt, afterAccepted
|
|
67
|
+
{ root, tmpDir, stageDir, replace = false, deadlineAt, afterAccepted, afterStageEnsured,
|
|
68
|
+
afterStageRenamed }) {
|
|
68
69
|
assertPublicationDeadline(deadlineAt);
|
|
69
70
|
// The accepted stage lives apart from the partial a failed publication
|
|
70
71
|
// leaves, so what a file is follows from the directory it was created in
|
|
@@ -145,9 +146,8 @@ export async function publishAtomic(destination, bytes,
|
|
|
145
146
|
// The seam the race test uses, in the manner retainFile already
|
|
146
147
|
// establishes: the window this closes is invisible without one.
|
|
147
148
|
await afterAccepted?.();
|
|
148
|
-
await
|
|
149
|
-
|
|
150
|
-
await syncDirectory(stageDir);
|
|
149
|
+
await retainAcceptedStage({ root, stageDir, temporary, stage, afterStageEnsured,
|
|
150
|
+
afterStageRenamed });
|
|
151
151
|
} else {
|
|
152
152
|
// A crash/error before immutable acceptance keeps its unique partial.
|
|
153
153
|
// Retention avoids the unsafe parent-check/unlink pathname window, while
|
|
@@ -157,6 +157,32 @@ export async function publishAtomic(destination, bytes,
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
// The sweep can take `stage` between any two of these steps: it renames the
|
|
161
|
+
// directory aside, and a store still opening publishes here without the writer
|
|
162
|
+
// mutex the sweep holds (CI on #217). The bytes are already published, so the
|
|
163
|
+
// retained copy goes to whichever directory carries the name when it moves, and
|
|
164
|
+
// a copy the sweep took along with the directory needs no sync here - the sweep
|
|
165
|
+
// discards that directory. The two callbacks are seams for the race tests.
|
|
166
|
+
const STAGE_RETRIES = 4;
|
|
167
|
+
|
|
168
|
+
async function retainAcceptedStage({ root, stageDir, temporary, stage, afterStageEnsured,
|
|
169
|
+
afterStageRenamed }) {
|
|
170
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
171
|
+
await ensureManagedDirectory(root, stageDir);
|
|
172
|
+
await afterStageEnsured?.();
|
|
173
|
+
try {
|
|
174
|
+
await rename(temporary, stage);
|
|
175
|
+
break;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error.code !== "ENOENT" || attempt >= STAGE_RETRIES) throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await afterStageRenamed?.();
|
|
181
|
+
await syncDirectory(stageDir).catch(error => {
|
|
182
|
+
if (error.code !== "ENOENT") throw error;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
160
186
|
// The opener is the seam the race tests use, and stays last so callers that do
|
|
161
187
|
// not care never see it.
|
|
162
188
|
export async function readJsonIfPresent(filePath, root, openFile) {
|
|
@@ -29,32 +29,42 @@ function absolutePath(value, name, root = value) {
|
|
|
29
29
|
return path.normalize(value);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// How many more times a create-mode check takes a directory that left its name
|
|
33
|
+
// while being checked. The stage sweep renames `stage` aside and recreates it,
|
|
34
|
+
// without regard to a store that is still opening beside it, so one more try is
|
|
35
|
+
// the real case; the bound only stops a directory that never stays put from
|
|
36
|
+
// holding its caller (CI on #217).
|
|
37
|
+
const VANISHED_RETRIES = 4;
|
|
38
|
+
|
|
32
39
|
async function inspectRealDirectory(directory, root, create) {
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
details = await lstat(directory);
|
|
36
|
-
} catch (error) {
|
|
37
|
-
if (error.code === "ENOENT" && !create) throw error;
|
|
38
|
-
if (error.code !== "ENOENT") {
|
|
39
|
-
throw invalidDirectory("cannot inspect managed directory", directory, root, error.message);
|
|
40
|
-
}
|
|
40
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
41
|
+
let details;
|
|
41
42
|
try {
|
|
42
|
-
await
|
|
43
|
-
} catch (
|
|
44
|
-
if (
|
|
45
|
-
throw invalidDirectory("cannot
|
|
46
|
-
mkdirError.message);
|
|
43
|
+
details = await lstat(directory);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.code !== "ENOENT") {
|
|
46
|
+
throw invalidDirectory("cannot inspect managed directory", directory, root, error.message);
|
|
47
47
|
}
|
|
48
|
+
// Missing again right after it was created means a rename took it.
|
|
49
|
+
if (!create || attempt > VANISHED_RETRIES) throw error;
|
|
50
|
+
try {
|
|
51
|
+
await mkdir(directory);
|
|
52
|
+
} catch (mkdirError) {
|
|
53
|
+
if (mkdirError.code !== "EEXIST") {
|
|
54
|
+
throw invalidDirectory("cannot create managed directory", directory, root,
|
|
55
|
+
mkdirError.message);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
48
59
|
}
|
|
49
|
-
details
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
if (!details.isDirectory() || details.isSymbolicLink()) {
|
|
61
|
+
throw invalidDirectory("managed directory is not a real directory", directory, root);
|
|
62
|
+
}
|
|
63
|
+
return details;
|
|
53
64
|
}
|
|
54
|
-
return details;
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
async function inspectManagedDirectory(rootPath, directoryPath, create) {
|
|
67
|
+
async function inspectManagedDirectory(rootPath, directoryPath, create, { afterInspect } = {}) {
|
|
58
68
|
const root = absolutePath(rootPath, "managed root");
|
|
59
69
|
const directory = absolutePath(directoryPath, "managed directory", root);
|
|
60
70
|
const relative = relativeWithin(root, directory);
|
|
@@ -67,7 +77,17 @@ async function inspectManagedDirectory(rootPath, directoryPath, create) {
|
|
|
67
77
|
let canonicalParent = canonicalRoot;
|
|
68
78
|
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
69
79
|
current = path.join(current, segment);
|
|
70
|
-
|
|
80
|
+
let resolved;
|
|
81
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
82
|
+
details = await inspectRealDirectory(current, root, create);
|
|
83
|
+
await afterInspect?.(current);
|
|
84
|
+
// A create-mode caller uses this name next. A directory renamed away
|
|
85
|
+
// between the check and the resolution leaves realpath nothing to resolve
|
|
86
|
+
// (ENOENT), so it is checked and created again, then resolved again; every
|
|
87
|
+
// check below applies to whichever attempt settles.
|
|
88
|
+
resolved = await resolveSegment(current, create && attempt < VANISHED_RETRIES);
|
|
89
|
+
if (resolved !== null) break;
|
|
90
|
+
}
|
|
71
91
|
// realpath answers with the name the directory carries *now*, which is not
|
|
72
92
|
// always the name that was asked for: on Darwin it resolves by opening the
|
|
73
93
|
// path and asking the kernel for that vnode's current path, so a directory
|
|
@@ -86,7 +106,6 @@ async function inspectManagedDirectory(rootPath, directoryPath, create) {
|
|
|
86
106
|
// swapped in under this name is not. ENOENT on the re-stat means it moved
|
|
87
107
|
// again between the two calls, which cannot redirect anything: whatever
|
|
88
108
|
// the caller opens through this path next fails the same way.
|
|
89
|
-
const resolved = await realpath(current);
|
|
90
109
|
if (path.dirname(resolved) !== canonicalParent
|
|
91
110
|
|| (path.basename(resolved) !== segment
|
|
92
111
|
&& !await stillTheSameDirectory(details, resolved, current, root))) {
|
|
@@ -97,6 +116,16 @@ async function inspectManagedDirectory(rootPath, directoryPath, create) {
|
|
|
97
116
|
return { directory, stat: details };
|
|
98
117
|
}
|
|
99
118
|
|
|
119
|
+
// null when the name went away and the caller may take it again.
|
|
120
|
+
async function resolveSegment(current, retry) {
|
|
121
|
+
try {
|
|
122
|
+
return await realpath(current);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (error.code === "ENOENT" && retry) return null;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
100
129
|
async function stillTheSameDirectory(details, resolved, current, root) {
|
|
101
130
|
const served = await stat(resolved).then(found => found, error => {
|
|
102
131
|
if (error.code === "ENOENT") return null;
|
|
@@ -105,10 +134,12 @@ async function stillTheSameDirectory(details, resolved, current, root) {
|
|
|
105
134
|
return served === null || (served.dev === details.dev && served.ino === details.ino);
|
|
106
135
|
}
|
|
107
136
|
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
// `afterInspect` is the seam the race tests use: it runs between a segment's
|
|
138
|
+
// check and its resolution, the window a concurrent rename lands in.
|
|
139
|
+
export async function assertManagedDirectory(rootPath, directoryPath, options) {
|
|
140
|
+
return inspectManagedDirectory(rootPath, directoryPath, false, options);
|
|
110
141
|
}
|
|
111
142
|
|
|
112
|
-
export async function ensureManagedDirectory(rootPath, directoryPath) {
|
|
113
|
-
return (await inspectManagedDirectory(rootPath, directoryPath, true)).directory;
|
|
143
|
+
export async function ensureManagedDirectory(rootPath, directoryPath, options) {
|
|
144
|
+
return (await inspectManagedDirectory(rootPath, directoryPath, true, options)).directory;
|
|
114
145
|
}
|
|
@@ -121,8 +121,12 @@ export async function openFilesystemStore({ root, clock, ids, workspaceId, failA
|
|
|
121
121
|
// must not roll the same journal forward concurrently.
|
|
122
122
|
await recoverOpenJournals();
|
|
123
123
|
|
|
124
|
-
// Sweeping is a write, so it holds the same mutex recovery does and no
|
|
125
|
-
//
|
|
124
|
+
// Sweeping is a write, so it holds the same mutex recovery does, and no
|
|
125
|
+
// transaction publishes while the stage directory is detached. A store that is
|
|
126
|
+
// still opening beside it does: the identity record, the directory checks and
|
|
127
|
+
// the journal's first slot above run without the mutex, so the managed
|
|
128
|
+
// directory checks and publishAtomic take a `stage` that left its name once
|
|
129
|
+
// more rather than failing (CI on #217). It is bounded
|
|
126
130
|
// per pass: a store carrying a large accumulation drains over several opens
|
|
127
131
|
// rather than spending one hook's whole budget on it. The mutex is taken only
|
|
128
132
|
// on the opens that actually sweep - see sweepIfDue, which decides before it
|