borgmcp 2.0.11 → 2.1.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.
Files changed (58) hide show
  1. package/README.md +11 -3
  2. package/dist/assimilate-cmd.d.ts +22 -4
  3. package/dist/assimilate-cmd.d.ts.map +1 -1
  4. package/dist/assimilate-cmd.js +109 -110
  5. package/dist/assimilate-cmd.js.map +1 -1
  6. package/dist/assimilate-deps.d.ts +7 -1
  7. package/dist/assimilate-deps.d.ts.map +1 -1
  8. package/dist/assimilate-deps.js +69 -25
  9. package/dist/assimilate-deps.js.map +1 -1
  10. package/dist/claude.d.ts +3 -1
  11. package/dist/claude.d.ts.map +1 -1
  12. package/dist/claude.js +26 -12
  13. package/dist/claude.js.map +1 -1
  14. package/dist/cli-help.d.ts +2 -0
  15. package/dist/cli-help.d.ts.map +1 -1
  16. package/dist/cli-help.js +25 -7
  17. package/dist/cli-help.js.map +1 -1
  18. package/dist/config.d.ts +7 -4
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +21 -4
  21. package/dist/config.js.map +1 -1
  22. package/dist/remote-client.js +1 -1
  23. package/dist/remote-client.js.map +1 -1
  24. package/dist/repository-cube-init.d.ts +72 -0
  25. package/dist/repository-cube-init.d.ts.map +1 -0
  26. package/dist/repository-cube-init.js +341 -0
  27. package/dist/repository-cube-init.js.map +1 -0
  28. package/dist/repository-identity.d.ts +29 -0
  29. package/dist/repository-identity.d.ts.map +1 -0
  30. package/dist/repository-identity.js +143 -0
  31. package/dist/repository-identity.js.map +1 -0
  32. package/dist/server-errors.d.ts +17 -0
  33. package/dist/server-errors.d.ts.map +1 -1
  34. package/dist/server-errors.js +32 -0
  35. package/dist/server-errors.js.map +1 -1
  36. package/dist/server-facade.d.ts +11 -1
  37. package/dist/server-facade.d.ts.map +1 -1
  38. package/dist/server-facade.js +33 -3
  39. package/dist/server-facade.js.map +1 -1
  40. package/dist/server-handshake.d.ts +38 -4
  41. package/dist/server-handshake.d.ts.map +1 -1
  42. package/dist/server-handshake.js +175 -13
  43. package/dist/server-handshake.js.map +1 -1
  44. package/docs/EXTRACTION_PROVENANCE.md +9 -7
  45. package/docs/LOCAL_SERVER.md +44 -9
  46. package/docs/RELEASING.md +21 -4
  47. package/package.json +2 -2
  48. package/src/assimilate-cmd.ts +179 -137
  49. package/src/assimilate-deps.ts +96 -24
  50. package/src/claude.ts +34 -14
  51. package/src/cli-help.ts +28 -7
  52. package/src/config.ts +30 -8
  53. package/src/remote-client.ts +1 -1
  54. package/src/repository-cube-init.ts +452 -0
  55. package/src/repository-identity.ts +199 -0
  56. package/src/server-errors.ts +41 -0
  57. package/src/server-facade.ts +47 -2
  58. package/src/server-handshake.ts +258 -14
@@ -25,10 +25,12 @@ import {
25
25
  } from './remote-client.js';
26
26
  import {
27
27
  DEFAULT_LOCAL_SERVER_ORIGIN,
28
+ associateLocalBorgServerRepositoryCube,
28
29
  connectLocalBorgServer,
29
30
  createLocalBorgServerCube,
30
31
  enrollLocalBorgServer,
31
32
  probeLocalBorgServer,
33
+ resolveLocalBorgServerRepositoryCube,
32
34
  resumeLocalBorgServerEnrollment,
33
35
  sendBorgServerAttach,
34
36
  } from './server-handshake.js';
@@ -45,7 +47,7 @@ import {
45
47
  } from './cubes.js';
46
48
  import { loadBorgServerTrust } from './server-trust.js';
47
49
  import { defaultProbeSeat } from './seat-probe.js';
48
- import { BorgServerError } from './server-errors.js';
50
+ import { BorgServerError, CubeCreationConfirmationError } from './server-errors.js';
49
51
  import {
50
52
  findProjectRoot as cubesFindProjectRoot,
51
53
  getActiveCube as cubesGetActive,
@@ -61,8 +63,47 @@ import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote
61
63
  import { findLoadedCodexThread } from './codex-app-server.js';
62
64
  import { defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
63
65
  import { ensurePrivateBorgConfigRoot } from './private-root.js';
66
+ import {
67
+ getOrCreateRepositoryIdentity,
68
+ getRepositoryAssociation,
69
+ resolveGitRepositoryContext,
70
+ saveRepositoryAssociation,
71
+ } from './repository-identity.js';
72
+ import { PromptInterruptedError } from './repository-cube-init.js';
73
+
74
+ /**
75
+ * Wraps the readline question operation with the production interruption
76
+ * mapping. Tests inject the question operation; production uses readline.
77
+ */
78
+ export type PromptQuestion = (message: string) => Promise<string>;
79
+
80
+ async function defaultPromptQuestion(message: string): Promise<string> {
81
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
82
+ try {
83
+ return await rl.question(message);
84
+ } finally {
85
+ rl.close();
86
+ }
87
+ }
88
+
89
+ export function createPromptAdapter(
90
+ question: PromptQuestion = defaultPromptQuestion,
91
+ ): (message: string) => Promise<string> {
92
+ return async (message: string): Promise<string> => {
93
+ try {
94
+ return await question(message);
95
+ } catch (err) {
96
+ if (err instanceof Error && (err.message === 'SIGINT' || err.message === 'Interrupted by signal.')) {
97
+ throw new PromptInterruptedError();
98
+ }
99
+ throw err;
100
+ }
101
+ };
102
+ }
64
103
 
65
- export function buildDefaultAssimilateDeps(): AssimilateDeps {
104
+ export function buildDefaultAssimilateDeps(
105
+ question: PromptQuestion = defaultPromptQuestion,
106
+ ): AssimilateDeps {
66
107
  return {
67
108
  runSync: (cmd, args, cwd) => {
68
109
  const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
@@ -97,14 +138,7 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
97
138
 
98
139
  stderr: (line) => process.stderr.write(line),
99
140
  stdout: (line) => process.stdout.write(line),
100
- prompt: async (message: string): Promise<string> => {
101
- const rl = createInterface({ input: process.stdin, output: process.stdout });
102
- try {
103
- return await rl.question(message);
104
- } finally {
105
- rl.close();
106
- }
107
- },
141
+ prompt: createPromptAdapter(question),
108
142
  promptSecret: async (message: string): Promise<string> => {
109
143
  const result = await prompts({
110
144
  type: 'password',
@@ -178,6 +212,10 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
178
212
  : { committed: false, reason: 'activation-failed' };
179
213
  },
180
214
  findProjectRoot: (cwd) => cubesFindProjectRoot(cwd),
215
+ resolveRepositoryContext: resolveGitRepositoryContext,
216
+ getRepositoryIdentity: getOrCreateRepositoryIdentity,
217
+ getRepositoryAssociation,
218
+ saveRepositoryAssociation,
181
219
 
182
220
  // gh#673 P2 (WI-1): project-local SessionStart hook for the launch root.
183
221
  installProjectSessionHook: (projectRoot) => {
@@ -202,6 +240,29 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
202
240
  ...(onPending === undefined ? {} : { onPending }),
203
241
  }),
204
242
 
243
+ resolveRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
244
+ if (serverTrustIdentity === undefined) {
245
+ throw new Error('Selected Borg server authority state is missing or unreadable');
246
+ }
247
+ return resolveLocalBorgServerRepositoryCube(
248
+ apiUrl,
249
+ serverTrustIdentity,
250
+ token,
251
+ input,
252
+ );
253
+ },
254
+ associateRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
255
+ if (serverTrustIdentity === undefined) {
256
+ throw new Error('Selected Borg server authority state is missing or unreadable');
257
+ }
258
+ return associateLocalBorgServerRepositoryCube(
259
+ apiUrl,
260
+ serverTrustIdentity,
261
+ token,
262
+ input,
263
+ );
264
+ },
265
+
205
266
  listCubes: async (apiUrl, token, serverTrustIdentity) => {
206
267
  if (serverTrustIdentity === undefined) {
207
268
  throw new Error('Selected Borg server authority state is missing or unreadable');
@@ -237,32 +298,43 @@ export function buildDefaultAssimilateDeps(): AssimilateDeps {
237
298
  throw new Error('Selected Borg server authority state is missing or unreadable');
238
299
  }
239
300
  {
240
- if (!params.name || !params.projectRoot) {
241
- throw new Error('Local Borg server cube creation requires a repository name and root');
242
- }
243
301
  const created = await createLocalBorgServerCube(
244
302
  apiUrl,
245
303
  serverTrustIdentity,
246
304
  token,
247
- { projectRoot: params.projectRoot, name: params.name },
305
+ {
306
+ name: params.name,
307
+ workingRepoName: params.workingRepoName,
308
+ repository: params.repository,
309
+ template: params.template,
310
+ },
248
311
  );
249
- const cube = await remoteGetCube(created.cube_id, {
250
- apiUrl,
251
- authToken: token,
252
- serverTrustIdentity,
253
- });
312
+ let cube;
313
+ try {
314
+ cube = await remoteGetCube(created.cube_id, {
315
+ apiUrl,
316
+ authToken: token,
317
+ serverTrustIdentity,
318
+ });
319
+ } catch {
320
+ throw new CubeCreationConfirmationError('The server returned a cube result that could not be read back.');
321
+ }
254
322
  if (
255
323
  cube.id !== created.cube_id ||
324
+ cube.name !== created.name ||
256
325
  !Array.isArray(cube.roles) ||
257
326
  !cube.roles.some((role: { id?: string }) => role.id === created.default_worker_role_id)
258
327
  ) {
259
- throw new Error('Borg server returned cube details outside the creation result');
328
+ throw new CubeCreationConfirmationError('The server returned cube details outside the creation result.');
260
329
  }
261
330
  return {
262
- id: cube.id,
263
- name: cube.name,
264
- roles: cube.roles,
265
- drones: cube.drones ?? [],
331
+ response: created,
332
+ cube: {
333
+ id: cube.id,
334
+ name: cube.name,
335
+ roles: cube.roles,
336
+ drones: cube.drones ?? [],
337
+ },
266
338
  };
267
339
  }
268
340
  },
package/src/claude.ts CHANGED
@@ -19,8 +19,10 @@
19
19
 
20
20
  import { spawn } from 'child_process';
21
21
  import { randomUUID } from 'node:crypto';
22
+ import { realpathSync } from 'node:fs';
22
23
  import { basename } from 'node:path';
23
24
  import { createInterface } from 'node:readline/promises';
25
+ import { fileURLToPath } from 'node:url';
24
26
  import chalk from 'chalk';
25
27
  import { findProjectRoot, getActiveCube, inboxPathForDrone, setCodexWakeTarget, pruneDeadCodexWakeTargets } from './cubes.js';
26
28
  import { monitorStateRootForWorktree } from './inbox-monitor.js';
@@ -81,6 +83,23 @@ import { connectOpenCodeDrone, computeOpenCodePort, createOpenCodeLaunchKickoff,
81
83
  import { buildOpenCodeLaunchArgs, defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
82
84
  import { runEarlyServerFacade } from './server-facade.js';
83
85
 
86
+ export type AssimilateDepsBuilder = typeof buildDefaultAssimilateDeps;
87
+
88
+ export async function runAssimilateEntry(
89
+ args: readonly string[],
90
+ buildDeps: AssimilateDepsBuilder = buildDefaultAssimilateDeps,
91
+ ): Promise<number> {
92
+ const parsed = parseAssimilateArgs([...args]);
93
+ if (!parsed.ok) {
94
+ process.stderr.write(
95
+ chalk.red(`${consolePrefix()}◼ borg assimilate: ${parsed.error}\n`)
96
+ );
97
+ process.stderr.write(`Run \`borg --help\` for usage.\n`);
98
+ return 1;
99
+ }
100
+ return runAssimilate({ role: parsed.role, flags: parsed.flags }, buildDeps());
101
+ }
102
+
84
103
  async function main() {
85
104
  const serverExitCode = await runEarlyServerFacade(process.argv);
86
105
  if (serverExitCode !== null) process.exit(serverExitCode);
@@ -128,16 +147,7 @@ async function main() {
128
147
  process.stdout.write(assimilateHelpText(getPackageVersion()));
129
148
  process.exit(0);
130
149
  }
131
- const parsed = parseAssimilateArgs(process.argv.slice(3));
132
- if (!parsed.ok) {
133
- process.stderr.write(
134
- chalk.red(`${consolePrefix()}◼ borg assimilate: ${parsed.error}\n`)
135
- );
136
- process.stderr.write(`Run \`borg --help\` for usage.\n`);
137
- process.exit(1);
138
- }
139
- const deps = buildDefaultAssimilateDeps();
140
- const code = await runAssimilate({ role: parsed.role, flags: parsed.flags }, deps);
150
+ const code = await runAssimilateEntry(process.argv.slice(3));
141
151
  process.exit(code);
142
152
  }
143
153
  if (process.argv[2] === 'reset-local-seat') {
@@ -560,7 +570,17 @@ function ensureDetectedCliConfigured(): void {
560
570
  }
561
571
  }
562
572
 
563
- main().catch((error) => {
564
- console.error(`${consolePrefix()}${chalk.red(`\n◼ Error: ${error.message}\n`)}`);
565
- process.exit(1);
566
- });
573
+ function isEntryInvocation(): boolean {
574
+ try {
575
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
576
+ } catch {
577
+ return false;
578
+ }
579
+ }
580
+
581
+ if (isEntryInvocation()) {
582
+ main().catch((error) => {
583
+ console.error(`${consolePrefix()}${chalk.red(`\n◼ Error: ${error.message}\n`)}`);
584
+ process.exit(1);
585
+ });
586
+ }
package/src/cli-help.ts CHANGED
@@ -30,6 +30,7 @@ export function topLevelHelpText(version: string): string {
30
30
  ` borg assimilate [role] Join or create a cube\n` +
31
31
  ` borg assimilate --host <host> Join or create on an explicit server\n` +
32
32
  ` borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)\n` +
33
+ ` borg server cube init Initialize this repository's cube without creating a drone\n` +
33
34
  ` borg reset-local-seat Clear ONLY this worktree's saved local seat (offline; after a rejection)\n` +
34
35
  ` borg sync [--prune] Sync this worktree's branch to origin/main\n` +
35
36
  ` borg cleanup [--prune] Report (or --prune) worktrees orphaned by evicted drones\n` +
@@ -53,11 +54,28 @@ export function serverHelpText(): string {
53
54
  ` stop Stop the managed local server.\n` +
54
55
  ` status Report verified runtime evidence.\n` +
55
56
  ` update Verify and activate a local server artifact.\n` +
56
- ` invite Create a single-use invitation in an interactive terminal.\n\n` +
57
+ ` invite Create a single-use invitation in an interactive terminal.\n` +
58
+ ` cube init Initialize this Git repository's cube; does not create a drone.\n\n` +
57
59
  `Run borg server <command> --help for server command options.\n`
58
60
  );
59
61
  }
60
62
 
63
+ /** Client-owned help for repository cube initialization without a drone. */
64
+ export function cubeInitHelpText(): string {
65
+ return (
66
+ `borg server cube init — initialize this Git repository's cube without creating a drone\n\n` +
67
+ `Usage:\n` +
68
+ ` borg server cube init [options]\n\n` +
69
+ `Options:\n` +
70
+ ` --host <host> Borg server host or URL (bare hosts default to HTTPS)\n` +
71
+ ` --enroll Prompt for a hidden enrollment invitation\n` +
72
+ ` --cube-name <name> Repository cube name (otherwise edit the proposed name)\n` +
73
+ ` --template software-dev|starter New-cube template (default: software-dev)\n` +
74
+ ` --yes, -y Accept new-cube defaults; never adopt by name\n` +
75
+ ` --help, -h Show this help\n`
76
+ );
77
+ }
78
+
61
79
  /**
62
80
  * Help text for `borg assimilate --help` — the home for the full assimilate flag
63
81
  * set. Model/provider configuration belongs to the selected agent CLI.
@@ -76,16 +94,19 @@ export function assimilateHelpText(version: string): string {
76
94
  `Flags:\n` +
77
95
  ` --worktree <name> Create + launch the drone in a sibling git worktree\n` +
78
96
  ` --here Stay in the current worktree (no sibling spawn)\n` +
79
- ` --cube-name <name> Cube to join/create (otherwise confirm repo basename)\n` +
97
+ ` --cube-name <name> Repository cube name (otherwise edit the proposed name)\n` +
80
98
  ` --host <host> Borg server host or URL (bare hosts default to HTTPS)\n` +
81
99
  ` --enroll Prompt for a hidden enrollment invitation in the operator terminal\n` +
82
- ` --template <name> Bootstrap a new cube from a bundled role template\n` +
83
- ` --no-template Create the cube with no template roles\n` +
100
+ ` --template software-dev|starter New-cube template (default: software-dev)\n` +
101
+ ` --no-template Unsupported for repository cube creation\n` +
84
102
  ` --cli claude|codex|opencode Agent CLI to launch (default: claude)\n` +
85
103
  ` --model claude:<model> Legacy Claude model override (configure models in the agent CLI)\n` +
86
- ` --yes, -y Skip confirmation prompts\n\n` +
87
- `An enrolled owner client may create an idempotent repository cube; ordinary clients\n` +
88
- `require an explicit cube grant. Agent seats begin only after enrollment. Preview only.\n` +
104
+ ` --yes, -y Accept new-cube defaults; never adopt by name\n\n` +
105
+ `Creation shows repository context, name, template, and one confirmation. An existing\n` +
106
+ `repository association skips all prompts. One accessible exact-name legacy cube requires\n` +
107
+ `explicit interactive adoption; ambiguous matches fail closed. An enrolled owner client may\n` +
108
+ `create an idempotent repository cube; ordinary clients require an explicit cube grant.\n` +
109
+ `Agent seats begin only after enrollment. Preview only.\n` +
89
110
  `See docs/LOCAL_SERVER.md for self-hosted setup and current status.\n\n` +
90
111
  `For local or provider-specific models, configure the selected agent CLI directly.\n` +
91
112
  `OpenCode supports Ollama and other providers through its own model configuration.\n`
package/src/config.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * read-compare-write shared by client enrollment and same-machine server setup.
8
8
  */
9
9
  import { createHash, randomBytes, randomUUID } from 'crypto';
10
- import type { ServerCapability } from 'borgmcp-shared/protocol';
10
+ import type { CreateCubeRepository, CubeTemplate, ServerCapability } from 'borgmcp-shared/protocol';
11
11
  import { withStoreLock } from './seat-store.js';
12
12
  import {
13
13
  makeFileBackend,
@@ -17,7 +17,7 @@ import { BORG_USER_ROOT, SERVER_CREDENTIALS_FILE } from './credential-paths.js';
17
17
 
18
18
  const SERVER_CREDENTIAL_RECORD_VERSION = 2 as const;
19
19
  const SERVER_PENDING_ENROLLMENT_RECORD_VERSION = 1 as const;
20
- const SERVER_CUBE_RETRY_RECORD_VERSION = 1 as const;
20
+ const SERVER_CUBE_RETRY_RECORD_VERSION = 2 as const;
21
21
  // The 0600 credential store (Queen rescope: replaces the OS keychain). A single
22
22
  // file holds every parent credential/enrollment record; a single flock
23
23
  // serializes every mutator + observer that must (SR-seven #4).
@@ -57,7 +57,9 @@ export interface PendingServerCubeCreationRecord {
57
57
  repositoryBinding: string;
58
58
  retryKey: string;
59
59
  name: string;
60
- template: 'default';
60
+ workingRepoName: string;
61
+ repository: CreateCubeRepository;
62
+ template: CubeTemplate;
61
63
  }
62
64
 
63
65
  /**
@@ -510,21 +512,29 @@ export async function getOrCreatePendingServerCubeCreation(
510
512
  origin: string;
511
513
  trustIdentity: string;
512
514
  clientId: string;
513
- projectRoot: string;
514
515
  name: string;
515
- template: 'default';
516
+ workingRepoName: string;
517
+ repository: CreateCubeRepository;
518
+ template: CubeTemplate;
516
519
  },
517
520
  ): Promise<PendingServerCubeCreationRecord> {
518
521
  validateServerCredentialBinding(input.origin, input.trustIdentity);
519
522
  validateUuid(input.clientId, 'client identity');
520
- if (input.projectRoot.length < 1 || input.projectRoot.length > 4096 || /[\u0000-\u001f\u007f]/.test(input.projectRoot)) {
523
+ if (
524
+ (input.repository.kind !== 'origin' && input.repository.kind !== 'local') ||
525
+ typeof input.repository.value !== 'string' || input.repository.value.length < 1
526
+ ) {
521
527
  throw new Error('invalid Borg server repository binding');
522
528
  }
523
529
  if (Buffer.byteLength(input.name, 'utf8') < 1 || Buffer.byteLength(input.name, 'utf8') > 120 ||
524
530
  !/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(input.name)) {
525
531
  throw new Error('invalid Borg server cube name');
526
532
  }
527
- const repositoryBinding = createHash('sha256').update(input.projectRoot).digest('hex');
533
+ const repositoryBinding = createHash('sha256')
534
+ .update(input.repository.kind)
535
+ .update('\0')
536
+ .update(input.repository.value)
537
+ .digest('hex');
528
538
  const backend = await getServerCredentialBackend();
529
539
  const account = serverCubeRetryAccount(
530
540
  input.origin,
@@ -548,6 +558,7 @@ export async function getOrCreatePendingServerCubeCreation(
548
558
  record.clientId !== input.clientId ||
549
559
  record.repositoryBinding !== repositoryBinding ||
550
560
  record.name !== input.name ||
561
+ record.workingRepoName !== input.workingRepoName ||
551
562
  record.template !== input.template ||
552
563
  typeof record.retryKey !== 'string' || !UUID_RE.test(record.retryKey)
553
564
  ) {
@@ -560,6 +571,8 @@ export async function getOrCreatePendingServerCubeCreation(
560
571
  repositoryBinding,
561
572
  retryKey: record.retryKey,
562
573
  name: input.name,
574
+ workingRepoName: input.workingRepoName,
575
+ repository: input.repository,
563
576
  template: input.template,
564
577
  };
565
578
  } catch {
@@ -573,12 +586,21 @@ export async function getOrCreatePendingServerCubeCreation(
573
586
  repositoryBinding,
574
587
  retryKey: randomUUID(),
575
588
  name: input.name,
589
+ workingRepoName: input.workingRepoName,
590
+ repository: input.repository,
576
591
  template: input.template,
577
592
  };
578
593
  await backend.set(account, JSON.stringify({
579
594
  version: SERVER_CUBE_RETRY_RECORD_VERSION,
580
595
  state: 'pending',
581
- ...record,
596
+ origin: record.origin,
597
+ trustIdentity: record.trustIdentity,
598
+ clientId: record.clientId,
599
+ repositoryBinding: record.repositoryBinding,
600
+ retryKey: record.retryKey,
601
+ name: record.name,
602
+ workingRepoName: record.workingRepoName,
603
+ template: record.template,
582
604
  }));
583
605
  return record;
584
606
  });
@@ -712,7 +712,7 @@ async function authedFetch(
712
712
  parsed.error !== null && typeof parsed.error === 'object' &&
713
713
  parsed.error.code === ROLE_SECTION_CONFLICT_CODE
714
714
  ) {
715
- // Shared 0.6.2 intentionally omits this server-local code. Re-validate the whole
715
+ // The shared protocol intentionally omits this server-local code. Re-validate the whole
716
716
  // envelope through the strict shared decoder with only the recognized
717
717
  // code substituted; no server-provided diagnostic is ever surfaced.
718
718
  decodeProtocolErrorEnvelope({