@serfab/cadre-host 0.8.1 → 0.9.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.
Files changed (136) hide show
  1. package/dist/auth/trust-circle.d.ts +5 -5
  2. package/dist/auth/trust-circle.js +10 -10
  3. package/dist/auth/trust-circle.js.map +1 -1
  4. package/dist/auth/types.d.ts +2 -2
  5. package/dist/bin/host.js +255 -54
  6. package/dist/bin/host.js.map +1 -1
  7. package/dist/donation/donation-service.d.ts +136 -0
  8. package/dist/donation/donation-service.js +306 -0
  9. package/dist/donation/donation-service.js.map +1 -0
  10. package/dist/donation/donation-store.d.ts +42 -0
  11. package/dist/donation/donation-store.js +141 -0
  12. package/dist/donation/donation-store.js.map +1 -0
  13. package/dist/donation/grant-service.d.ts +71 -0
  14. package/dist/donation/grant-service.js +163 -0
  15. package/dist/donation/grant-service.js.map +1 -0
  16. package/dist/donation/grant-store.d.ts +38 -0
  17. package/dist/donation/grant-store.js +135 -0
  18. package/dist/donation/grant-store.js.map +1 -0
  19. package/dist/donation/index.d.ts +18 -0
  20. package/dist/donation/index.js +16 -0
  21. package/dist/donation/index.js.map +1 -0
  22. package/dist/donation/types.d.ts +175 -0
  23. package/dist/donation/types.js +40 -0
  24. package/dist/donation/types.js.map +1 -0
  25. package/dist/index.d.ts +6 -4
  26. package/dist/index.js +4 -2
  27. package/dist/index.js.map +1 -1
  28. package/dist/installer/config.d.ts +26 -0
  29. package/dist/installer/config.js +13 -0
  30. package/dist/installer/config.js.map +1 -1
  31. package/dist/installer/index.d.ts +2 -0
  32. package/dist/installer/index.js +3 -0
  33. package/dist/installer/index.js.map +1 -1
  34. package/dist/installer/wizard.d.ts +4 -0
  35. package/dist/installer/wizard.js +5 -1
  36. package/dist/installer/wizard.js.map +1 -1
  37. package/dist/nat/nat-service.d.ts +5 -5
  38. package/dist/nat/nat-service.js +9 -9
  39. package/dist/nat/nat-service.js.map +1 -1
  40. package/dist/nat/types.d.ts +1 -1
  41. package/dist/orchestrator/host-process-orchestrator.d.ts +35 -27
  42. package/dist/orchestrator/host-process-orchestrator.js +79 -53
  43. package/dist/orchestrator/host-process-orchestrator.js.map +1 -1
  44. package/dist/orchestrator/index.d.ts +3 -3
  45. package/dist/orchestrator/index.js +1 -1
  46. package/dist/orchestrator/index.js.map +1 -1
  47. package/dist/orchestrator/state-store.d.ts +5 -5
  48. package/dist/orchestrator/state-store.js +2 -2
  49. package/dist/orchestrator/state-store.js.map +1 -1
  50. package/dist/orchestrator/types.d.ts +8 -8
  51. package/dist/owner/index.d.ts +11 -0
  52. package/dist/owner/index.js +11 -0
  53. package/dist/owner/index.js.map +1 -0
  54. package/dist/{authority/authority-node-client.d.ts → owner/owner-node-client.d.ts} +32 -14
  55. package/dist/{authority/authority-node-client.js → owner/owner-node-client.js} +39 -15
  56. package/dist/owner/owner-node-client.js.map +1 -0
  57. package/dist/server/error-handler.d.ts +15 -0
  58. package/dist/server/error-handler.js +41 -0
  59. package/dist/server/error-handler.js.map +1 -1
  60. package/dist/server/index.d.ts +26 -2
  61. package/dist/server/index.js +37 -16
  62. package/dist/server/index.js.map +1 -1
  63. package/dist/server/routes/grants-admin.d.ts +17 -0
  64. package/dist/server/routes/grants-admin.js +32 -0
  65. package/dist/server/routes/grants-admin.js.map +1 -0
  66. package/dist/server/routes/grants.d.ts +28 -0
  67. package/dist/server/routes/grants.js +163 -0
  68. package/dist/server/routes/grants.js.map +1 -0
  69. package/dist/server/routes/nodes.d.ts +5 -4
  70. package/dist/server/routes/nodes.js +19 -18
  71. package/dist/server/routes/nodes.js.map +1 -1
  72. package/dist/server/routes/settings.d.ts +6 -1
  73. package/dist/server/routes/settings.js +5 -2
  74. package/dist/server/routes/settings.js.map +1 -1
  75. package/dist/server/routes/status.d.ts +8 -4
  76. package/dist/server/routes/status.js +9 -4
  77. package/dist/server/routes/status.js.map +1 -1
  78. package/dist/update/release-key.js +7 -5
  79. package/dist/update/release-key.js.map +1 -1
  80. package/package.json +4 -4
  81. package/src/__tests__/{host-authority.smoke.test.ts → host-owner.smoke.test.ts} +7 -7
  82. package/src/__tests__/{orchestrator-authority.test.ts → orchestrator-owner.test.ts} +33 -33
  83. package/src/__tests__/orchestrator-pin-keys.test.ts +153 -0
  84. package/src/__tests__/orchestrator-push.test.ts +10 -10
  85. package/src/auth/__tests__/trust-circle-integration.test.ts +11 -11
  86. package/src/auth/__tests__/trust-circle.test.ts +10 -10
  87. package/src/auth/trust-circle.ts +10 -10
  88. package/src/auth/types.ts +2 -2
  89. package/src/bin/host.ts +273 -53
  90. package/src/donation/__tests__/donation-service.test.ts +290 -0
  91. package/src/donation/__tests__/grant-service.test.ts +213 -0
  92. package/src/donation/__tests__/grant-store.test.ts +113 -0
  93. package/src/donation/donation-service.ts +392 -0
  94. package/src/donation/donation-store.ts +164 -0
  95. package/src/donation/grant-service.ts +200 -0
  96. package/src/donation/grant-store.ts +152 -0
  97. package/src/donation/index.ts +49 -0
  98. package/src/donation/types.ts +220 -0
  99. package/src/index.ts +37 -5
  100. package/src/installer/__tests__/config.test.ts +27 -1
  101. package/src/installer/__tests__/installer.smoke.test.ts +31 -1
  102. package/src/installer/__tests__/wizard.test.ts +5 -1
  103. package/src/installer/config.ts +34 -0
  104. package/src/installer/index.ts +5 -0
  105. package/src/installer/wizard.ts +12 -1
  106. package/src/nat/__tests__/nat-service.test.ts +5 -5
  107. package/src/nat/nat-service.ts +10 -10
  108. package/src/nat/types.ts +1 -1
  109. package/src/orchestrator/host-process-orchestrator.ts +88 -60
  110. package/src/orchestrator/index.ts +3 -3
  111. package/src/orchestrator/state-store.ts +7 -7
  112. package/src/orchestrator/types.ts +8 -8
  113. package/src/{authority/__tests__/authority-node-client.test.ts → owner/__tests__/owner-node-client.test.ts} +45 -17
  114. package/src/owner/index.ts +12 -0
  115. package/src/{authority/authority-node-client.ts → owner/owner-node-client.ts} +55 -21
  116. package/src/server/__tests__/error-handler.test.ts +22 -0
  117. package/src/server/__tests__/grants-admin-route.test.ts +73 -0
  118. package/src/server/__tests__/grants-route.test.ts +189 -0
  119. package/src/server/__tests__/nodes-route.test.ts +25 -25
  120. package/src/server/__tests__/server.smoke.test.ts +75 -0
  121. package/src/server/__tests__/settings-route.test.ts +17 -0
  122. package/src/server/__tests__/status-route.test.ts +21 -0
  123. package/src/server/error-handler.ts +43 -0
  124. package/src/server/index.ts +62 -17
  125. package/src/server/routes/grants-admin.ts +41 -0
  126. package/src/server/routes/grants.ts +186 -0
  127. package/src/server/routes/nodes.ts +19 -18
  128. package/src/server/routes/settings.ts +11 -3
  129. package/src/server/routes/status.ts +18 -8
  130. package/src/update/__tests__/release-key.test.ts +16 -3
  131. package/src/update/release-key.ts +7 -5
  132. package/dist/authority/authority-node-client.js.map +0 -1
  133. package/dist/authority/index.d.ts +0 -11
  134. package/dist/authority/index.js +0 -11
  135. package/dist/authority/index.js.map +0 -1
  136. package/src/authority/index.ts +0 -12
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
 
6
- import { readHostConfig, writeHostConfig, type HostConfigFile } from '../config.js';
6
+ import { readHostConfig, writeHostConfig, hostOwnsCadre, type HostConfigFile } from '../config.js';
7
7
 
8
8
  describe('host.config.json round-trip', () => {
9
9
  let tmp: string;
@@ -83,4 +83,30 @@ describe('host.config.json round-trip', () => {
83
83
  const path = join(tmp, 'host.config.json');
84
84
  expect(() => writeHostConfig(path, { ...makeCfg(), version: 3 as never })).toThrow(/version=3/);
85
85
  });
86
+
87
+ it('round-trips ownCadre and hostOwnsCadre reflects it', () => {
88
+ const path = join(tmp, 'host.config.json');
89
+ const cfg = makeCfg({ ownCadre: { enabled: true } });
90
+ writeHostConfig(path, cfg);
91
+ const read = readHostConfig(path);
92
+ expect(read.ownCadre).toEqual({ enabled: true });
93
+ expect(hostOwnsCadre(read)).toBe(true);
94
+ });
95
+
96
+ it('treats an absent ownCadre as donor-only (hostOwnsCadre false)', () => {
97
+ // A pre-ownCadre v2 config (field absent) must read back cleanly and be
98
+ // donor-only — no migration, field stays absent.
99
+ const path = join(tmp, 'host.config.json');
100
+ const cfg = makeCfg();
101
+ writeHostConfig(path, cfg);
102
+ const read = readHostConfig(path);
103
+ expect(read.ownCadre).toBeUndefined();
104
+ expect(hostOwnsCadre(read)).toBe(false);
105
+ });
106
+
107
+ it('rejects a malformed ownCadre shape', () => {
108
+ const path = join(tmp, 'host.config.json');
109
+ writeFileSync(path, JSON.stringify({ ...makeCfg(), ownCadre: { enabled: 'yes' } }));
110
+ expect(() => readHostConfig(path)).toThrow(/missing required fields/);
111
+ });
86
112
  });
@@ -117,7 +117,7 @@ describe('Installer smoke', () => {
117
117
  // returned answers stand on their own.
118
118
  const fake = new FakeServiceHost();
119
119
  const installer = new Installer({ platform: 'linux' });
120
- let seenDefaults: { dataDir: string; uiPort: number; libp2pPort: number; upnpEnabled: boolean } | undefined;
120
+ let seenDefaults: { dataDir: string; uiPort: number; libp2pPort: number; upnpEnabled: boolean; ownCadre: boolean } | undefined;
121
121
  await installer.install({
122
122
  nonInteractive: false,
123
123
  dataDir: tmp,
@@ -136,6 +136,7 @@ describe('Installer smoke', () => {
136
136
  libp2pPort: defaults.libp2pPort,
137
137
  upnpEnabled: defaults.upnpEnabled,
138
138
  configureDdns: false,
139
+ ownCadre: defaults.ownCadre,
139
140
  };
140
141
  },
141
142
  });
@@ -145,6 +146,7 @@ describe('Installer smoke', () => {
145
146
  uiPort: 19997,
146
147
  libp2pPort: 14003,
147
148
  upnpEnabled: false,
149
+ ownCadre: false,
148
150
  });
149
151
  expect(fake.installCalls).toHaveLength(1);
150
152
  expect(fake.installCalls[0]!.dataDir).toBe(tmp);
@@ -166,12 +168,40 @@ describe('Installer smoke', () => {
166
168
  libp2pPort: 14004,
167
169
  upnpEnabled: true,
168
170
  configureDdns: false,
171
+ ownCadre: false,
169
172
  }),
170
173
  });
171
174
  expect(fake.installCalls).toHaveLength(1);
172
175
  expect(fake.installCalls[0]!.dataDir).toBe(tmp);
173
176
  });
174
177
 
178
+ it('writes ownCadre.enabled=false by default and true when opted in', async () => {
179
+ const { readHostConfig } = await import('../config.js');
180
+
181
+ // Default (donor-only): --own-cadre absent → ownCadre.enabled false.
182
+ const fakeA = new FakeServiceHost();
183
+ const installerA = new Installer({ platform: 'linux' });
184
+ await installerA.install({
185
+ nonInteractive: true, dataDir: tmp, openBrowser: false, noInvite: true, serviceHost: fakeA,
186
+ });
187
+ const donorCfg = readHostConfig(join(tmp, 'host.config.json'));
188
+ expect(donorCfg.ownCadre).toEqual({ enabled: false });
189
+
190
+ // Opt-in: ownCadre:true (the --own-cadre flag) → ownCadre.enabled true.
191
+ const tmp2 = mkdtempSync(join(tmpdir(), 'cadre-host-installer-owncadre-'));
192
+ try {
193
+ const fakeB = new FakeServiceHost();
194
+ const installerB = new Installer({ platform: 'linux' });
195
+ await installerB.install({
196
+ nonInteractive: true, dataDir: tmp2, ownCadre: true, openBrowser: false, noInvite: true, serviceHost: fakeB,
197
+ });
198
+ const founderCfg = readHostConfig(join(tmp2, 'host.config.json'));
199
+ expect(founderCfg.ownCadre).toEqual({ enabled: true });
200
+ } finally {
201
+ rmSync(tmp2, { recursive: true, force: true });
202
+ }
203
+ });
204
+
175
205
  it('re-running install preserves the existing identity', async () => {
176
206
  const fake = new FakeServiceHost();
177
207
  const installer = new Installer({ platform: 'linux' });
@@ -7,6 +7,7 @@ const defaults: WizardDefaults = {
7
7
  uiPort: 8765,
8
8
  libp2pPort: 4001,
9
9
  upnpEnabled: true,
10
+ ownCadre: false,
10
11
  };
11
12
 
12
13
  function scripted(answers: ReadonlyArray<string>): (label: string, fallback: string) => Promise<string> {
@@ -19,13 +20,14 @@ function scripted(answers: ReadonlyArray<string>): (label: string, fallback: str
19
20
 
20
21
  describe('runWizardWith', () => {
21
22
  it('returns the defaults when every answer is blank', async () => {
22
- const out = await runWizardWith(defaults, scripted(['', '', '', '', '']));
23
+ const out = await runWizardWith(defaults, scripted(['', '', '', '', '', '']));
23
24
  expect(out).toEqual({
24
25
  dataDir: '/data/cadre-host',
25
26
  uiPort: 8765,
26
27
  libp2pPort: 4001,
27
28
  upnpEnabled: true,
28
29
  configureDdns: false,
30
+ ownCadre: false,
29
31
  });
30
32
  });
31
33
 
@@ -36,6 +38,7 @@ describe('runWizardWith', () => {
36
38
  '5000',
37
39
  'n',
38
40
  'y',
41
+ 'y',
39
42
  ]));
40
43
  expect(out).toEqual({
41
44
  dataDir: '/srv/host',
@@ -43,6 +46,7 @@ describe('runWizardWith', () => {
43
46
  libp2pPort: 5000,
44
47
  upnpEnabled: false,
45
48
  configureDdns: true,
49
+ ownCadre: true,
46
50
  });
47
51
  });
48
52
 
@@ -21,6 +21,21 @@ export interface UpdatesConfig {
21
21
  manifestUrl?: string;
22
22
  }
23
23
 
24
+ /**
25
+ * Whether this host also runs its own personal cadre (the opt-in "founder"
26
+ * persona), on top of the always-on node-donor role.
27
+ *
28
+ * Default (absent or `enabled: false`) is donor-only: `cadre-host start` brings
29
+ * up the donation surface but spawns **no** owner node, so the trust-circle and
30
+ * NAT surfaces stay inactive. Set `enabled: true` (install-time choice) to also
31
+ * spawn the host's own owner node and enable `/auth` + `/nat`. See
32
+ * docs/cadre-host.md § Two roles: donor and founder.
33
+ */
34
+ export interface OwnCadreConfig {
35
+ /** When true, `start` spawns the host-owned owner node + trust-circle + NAT. */
36
+ enabled: boolean;
37
+ }
38
+
24
39
  /**
25
40
  * Non-secret push (FCM/APNs) settings. The actual private keys (and the FCM
26
41
  * service-account / APNs key identifiers) live in the secret store — see
@@ -61,6 +76,12 @@ export interface HostConfigFile {
61
76
  installerVersion: string;
62
77
  /** Update-flow settings; defaults: { autoApply: false }. */
63
78
  updates: UpdatesConfig;
79
+ /**
80
+ * Host-own-cadre (founder persona) opt-in. Optional — absent is treated as
81
+ * `{ enabled: false }` (donor-only). Written by the installer; read via
82
+ * `hostOwnsCadre()`. Not editable through /api/settings (install-time only).
83
+ */
84
+ ownCadre?: OwnCadreConfig;
64
85
  /**
65
86
  * Non-secret push settings (bundle id, sandbox/prod toggle, cooldown/debounce).
66
87
  * Optional — absent when push is not configured. Private keys never live here;
@@ -69,6 +90,15 @@ export interface HostConfigFile {
69
90
  push?: PushSettings;
70
91
  }
71
92
 
93
+ /**
94
+ * Whether this host runs its own personal cadre (founder persona). Absent
95
+ * `ownCadre` ⇒ donor-only (false). The single source of truth for the
96
+ * founder/donor gate in `bin/host.ts start`.
97
+ */
98
+ export function hostOwnsCadre(cfg: HostConfigFile): boolean {
99
+ return cfg.ownCadre?.enabled === true;
100
+ }
101
+
72
102
  const CURRENT_VERSION = 2;
73
103
 
74
104
  export function writeHostConfig(filePath: string, cfg: HostConfigFile): void {
@@ -175,5 +205,9 @@ function isHostConfigShape(v: unknown): v is HostConfigFile {
175
205
  if (!updates || typeof updates !== 'object') return false;
176
206
  if (typeof updates.autoApply !== 'boolean') return false;
177
207
  if (updates.manifestUrl !== undefined && typeof updates.manifestUrl !== 'string') return false;
208
+ if (o.ownCadre !== undefined) {
209
+ const own = o.ownCadre as Record<string, unknown>;
210
+ if (!own || typeof own !== 'object' || typeof own.enabled !== 'boolean') return false;
211
+ }
178
212
  return true;
179
213
  }
@@ -58,6 +58,8 @@ export interface InstallOptions {
58
58
  libp2pPort?: number;
59
59
  /** Disable UPnP probing on first run. */
60
60
  noUpnp?: boolean;
61
+ /** Also run the host's own personal cadre here (founder persona). Default false. */
62
+ ownCadre?: boolean;
61
63
  /** Open browser at end (default true; suppressed by --no-browser or non-TTY). */
62
64
  openBrowser?: boolean;
63
65
  /** Skip the enrollment-invite step (no QR printed). */
@@ -146,6 +148,7 @@ export class Installer {
146
148
  installedAt: new Date().toISOString(),
147
149
  installerVersion: this.installerVersion,
148
150
  updates: { autoApply: false },
151
+ ownCadre: { enabled: answers.ownCadre },
149
152
  };
150
153
  const cfgPath = configPath(answers.dataDir);
151
154
  writeHostConfig(cfgPath, cfg);
@@ -228,6 +231,7 @@ export class Installer {
228
231
  libp2pPort: opts.libp2pPort ?? defaults.libp2pPort,
229
232
  upnpEnabled: opts.noUpnp ? false : defaults.upnpEnabled,
230
233
  configureDdns: false,
234
+ ownCadre: opts.ownCadre ?? defaults.ownCadre,
231
235
  };
232
236
  }
233
237
 
@@ -246,6 +250,7 @@ export class Installer {
246
250
  uiPort: opts.uiPort ?? platformDefaults.uiPort,
247
251
  libp2pPort: opts.libp2pPort ?? platformDefaults.libp2pPort,
248
252
  upnpEnabled: opts.noUpnp ? false : platformDefaults.upnpEnabled,
253
+ ownCadre: opts.ownCadre ?? platformDefaults.ownCadre,
249
254
  };
250
255
  const wizard = opts.wizard ?? runWizard;
251
256
  return await wizard(defaults);
@@ -16,6 +16,8 @@ export interface WizardDefaults {
16
16
  uiPort: number;
17
17
  libp2pPort: number;
18
18
  upnpEnabled: boolean;
19
+ /** Also run the host's own personal cadre here (founder persona). Default false. */
20
+ ownCadre: boolean;
19
21
  }
20
22
 
21
23
  export interface WizardAnswers {
@@ -24,6 +26,8 @@ export interface WizardAnswers {
24
26
  libp2pPort: number;
25
27
  upnpEnabled: boolean;
26
28
  configureDdns: boolean;
29
+ /** Also run the host's own personal cadre here (founder persona). */
30
+ ownCadre: boolean;
27
31
  }
28
32
 
29
33
  export const DEFAULT_UI_PORT = 8765;
@@ -35,6 +39,7 @@ export function defaultsForPlatform(platform: SupportedPlatform): WizardDefaults
35
39
  uiPort: DEFAULT_UI_PORT,
36
40
  libp2pPort: DEFAULT_LIBP2P_PORT,
37
41
  upnpEnabled: true,
42
+ ownCadre: false,
38
43
  };
39
44
  }
40
45
 
@@ -54,7 +59,13 @@ export async function runWizardWith(
54
59
  defaults.upnpEnabled,
55
60
  );
56
61
  const configureDdns = parseBool(await ask('Configure DDNS now? [y/N]', 'N'), false);
57
- return { dataDir, uiPort, libp2pPort, upnpEnabled, configureDdns };
62
+ // Founder persona (opt-in). Most installs just donate nodes to friends and
63
+ // answer no — a no leaves cadre-host donor-only (no owner node, no /auth+/nat).
64
+ const ownCadre = parseBool(
65
+ await ask('Also run your own personal cadre on this machine? (most people just donating nodes to friends say no) [y/N]', defaults.ownCadre ? 'Y' : 'N'),
66
+ defaults.ownCadre,
67
+ );
68
+ return { dataDir, uiPort, libp2pPort, upnpEnabled, configureDdns, ownCadre };
58
69
  }
59
70
 
60
71
  /** Production entrypoint — drives the wizard over stdin/stdout. */
@@ -9,7 +9,7 @@ import type { PortMapper, PortMappingResult } from '../port-mapper.js';
9
9
  import type { SecretsStore } from '../secrets/index.js';
10
10
  import { ddnsAccount } from '../secrets/index.js';
11
11
  import type { CadreNodeLike } from '../nat-service.js';
12
- import { AuthorityNodeUnavailableError } from '../../authority/authority-node-client.js';
12
+ import { OwnerNodeUnavailableError } from '../../owner/owner-node-client.js';
13
13
 
14
14
  class StubMapper implements PortMapper {
15
15
  mapCalls = 0;
@@ -53,8 +53,8 @@ function makeNode(peerId = '12D3KooWHost', addrs: string[] = ['/ip4/192.168.1.10
53
53
  }
54
54
 
55
55
  /**
56
- * A node that mimics a freshly spawned authority child: `getPeerId` throws
57
- * `AuthorityNodeUnavailableError` for the first `failTimes` calls (admin channel
56
+ * A node that mimics a freshly spawned owner child: `getPeerId` throws
57
+ * `OwnerNodeUnavailableError` for the first `failTimes` calls (admin channel
58
58
  * not bound yet), then returns a real peer ID. Exposes the call count so tests
59
59
  * can assert how many attempts the retry loop made.
60
60
  */
@@ -67,7 +67,7 @@ function makeFlakyNode(
67
67
  const node: CadreNodeLike = {
68
68
  getPeerId: async () => {
69
69
  calls += 1;
70
- if (calls <= failTimes) throw new AuthorityNodeUnavailableError('admin channel not bound yet');
70
+ if (calls <= failTimes) throw new OwnerNodeUnavailableError('admin channel not bound yet');
71
71
  return peerId;
72
72
  },
73
73
  getMultiaddrs: async () => addrs,
@@ -421,7 +421,7 @@ describe('NatService — initial invite-address push retry', () => {
421
421
  it('gives up after the bounded timeout but still resolves start() (best-effort)', async () => {
422
422
  // Always unavailable — the retry budget elapses without a successful push.
423
423
  const node: CadreNodeLike = {
424
- getPeerId: async () => { throw new AuthorityNodeUnavailableError('never ready'); },
424
+ getPeerId: async () => { throw new OwnerNodeUnavailableError('never ready'); },
425
425
  getMultiaddrs: async () => [],
426
426
  };
427
427
  const svc = new NatService({
@@ -24,7 +24,7 @@ import {
24
24
  type BuildInviteAddressesInput,
25
25
  } from './address-resolver.js';
26
26
  import { createSecretsStore, ddnsAccount, type SecretsStore } from './secrets/index.js';
27
- import { AuthorityNodeUnavailableError } from '../authority/authority-node-client.js';
27
+ import { OwnerNodeUnavailableError } from '../owner/owner-node-client.js';
28
28
 
29
29
  const log = debug('cadre:host:nat-service');
30
30
 
@@ -41,7 +41,7 @@ function sleep(ms: number): Promise<void> {
41
41
  * so tests can inject a mock without a real libp2p stack. Mirrors
42
42
  * `CadreNodeLike` in `auth/trust-circle.ts`.
43
43
  *
44
- * Both methods are async: in production they round-trip over the authority
44
+ * Both methods are async: in production they round-trip over the owner
45
45
  * node's loopback admin channel (`GET /admin/identity`, `GET /admin/multiaddrs`).
46
46
  * Synchronous test mocks can return a value directly (it's awaited).
47
47
  */
@@ -165,7 +165,7 @@ export class NatService {
165
165
  await this.ddnsUpdater.start();
166
166
 
167
167
  // Initial invite-address push. Retries on `node_unavailable` until the
168
- // freshly spawned authority node accepts it (bounded; best-effort after).
168
+ // freshly spawned owner node accepts it (bounded; best-effort after).
169
169
  await this.pushInitialAddresses();
170
170
  }
171
171
 
@@ -237,7 +237,7 @@ export class NatService {
237
237
 
238
238
  /**
239
239
  * Build the multiaddrs to embed in invites. The peer ID and libp2p
240
- * fallback addresses are fetched over the authority node's admin channel; a
240
+ * fallback addresses are fetched over the owner node's admin channel; a
241
241
  * node-unavailable failure surfaces as `NatError('node_unavailable')`.
242
242
  */
243
243
  async getInviteAddresses(): Promise<string[]> {
@@ -247,8 +247,8 @@ export class NatService {
247
247
  peerId = await this.cadreNode.getPeerId();
248
248
  libp2pAddrs = await this.cadreNode.getMultiaddrs();
249
249
  } catch (err) {
250
- if (err instanceof AuthorityNodeUnavailableError) {
251
- throw new NatError('node_unavailable', `Authority node unavailable: ${err.message}`);
250
+ if (err instanceof OwnerNodeUnavailableError) {
251
+ throw new NatError('node_unavailable', `Owner node unavailable: ${err.message}`);
252
252
  }
253
253
  throw err;
254
254
  }
@@ -256,7 +256,7 @@ export class NatService {
256
256
  // an empty peerId. Building addresses now would mint malformed `…/p2p/`
257
257
  // suffixes (and push them to the node), so treat it as not-ready.
258
258
  if (!peerId) {
259
- throw new NatError('node_unavailable', 'Authority node has no peer ID yet (not ready)');
259
+ throw new NatError('node_unavailable', 'Owner node has no peer ID yet (not ready)');
260
260
  }
261
261
  const status = this.getStatus();
262
262
  const input: BuildInviteAddressesInput = {
@@ -273,7 +273,7 @@ export class NatService {
273
273
  /**
274
274
  * Register a listener fired when the invite addresses may have changed
275
275
  * (after `start`, `putSettings`, and `testReachability`). `cadre-host start`
276
- * wires this to `AuthorityNodeClient.pushInviteAddresses`. Returns an
276
+ * wires this to `OwnerNodeClient.pushInviteAddresses`. Returns an
277
277
  * unsubscribe fn.
278
278
  */
279
279
  onAddressesChanged(listener: AddressesChangedListener): () => void {
@@ -283,7 +283,7 @@ export class NatService {
283
283
 
284
284
  /**
285
285
  * Recompute invite addresses and notify listeners. Best-effort: when the
286
- * authority node is unreachable (or the build fails), the notification is
286
+ * owner node is unreachable (or the build fails), the notification is
287
287
  * skipped — the push is retried on the next NAT event.
288
288
  */
289
289
  private async fireAddressesChanged(): Promise<void> {
@@ -310,7 +310,7 @@ export class NatService {
310
310
  }
311
311
 
312
312
  /**
313
- * Initial invite-address push. The authority node's admin channel is a freshly
313
+ * Initial invite-address push. The owner node's admin channel is a freshly
314
314
  * spawned detached child that may not be bound yet (and may not have a libp2p
315
315
  * peer ID yet), so a single attempt races readiness and silently drops the first
316
316
  * NAT-resolved address set. Retry on `node_unavailable` until the node accepts it
package/src/nat/types.ts CHANGED
@@ -107,7 +107,7 @@ export type NatErrorCode =
107
107
  | 'secrets_unavailable'
108
108
  | 'storage_error'
109
109
  | 'invalid_config'
110
- /** The authority node's admin channel is unreachable / not ready. */
110
+ /** The owner node's admin channel is unreachable / not ready. */
111
111
  | 'node_unavailable';
112
112
 
113
113
  /** Typed error carrying a stable `code` for HTTP mapping in the local-ui. */