ftown 0.20.9 → 0.21.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 (118) hide show
  1. package/dist/centrifugo-client.d.ts +15 -11
  2. package/dist/centrifugo-client.js +63 -70
  3. package/dist/centrifugo-client.js.map +1 -1
  4. package/dist/cli-client.d.ts +11 -1
  5. package/dist/cli-client.js +23 -3
  6. package/dist/cli-client.js.map +1 -1
  7. package/dist/command-rpc.js +4 -8
  8. package/dist/command-rpc.js.map +1 -1
  9. package/dist/identity-reset.d.ts +1 -0
  10. package/dist/identity-reset.js +31 -5
  11. package/dist/identity-reset.js.map +1 -1
  12. package/dist/index.js +184 -288
  13. package/dist/index.js.map +1 -1
  14. package/dist/ledger-pairing-client.d.ts +28 -0
  15. package/dist/ledger-pairing-client.js +152 -0
  16. package/dist/ledger-pairing-client.js.map +1 -0
  17. package/dist/ledger-relay.d.ts +124 -0
  18. package/dist/ledger-relay.js +453 -0
  19. package/dist/ledger-relay.js.map +1 -0
  20. package/dist/ledger-startup.d.ts +151 -0
  21. package/dist/ledger-startup.js +203 -0
  22. package/dist/ledger-startup.js.map +1 -0
  23. package/dist/relay-token.d.ts +39 -0
  24. package/dist/relay-token.js +130 -0
  25. package/dist/relay-token.js.map +1 -0
  26. package/dist/revocation-gating.d.ts +0 -13
  27. package/dist/revocation-gating.js +0 -16
  28. package/dist/revocation-gating.js.map +1 -1
  29. package/dist/security/bridge/identity.d.ts +2 -47
  30. package/dist/security/bridge/identity.js +3 -3
  31. package/dist/security/bridge/identity.js.map +1 -1
  32. package/dist/security/bridge/ledger-identity.d.ts +48 -0
  33. package/dist/security/bridge/ledger-identity.js +342 -0
  34. package/dist/security/bridge/ledger-identity.js.map +1 -0
  35. package/dist/security/bridge/ledger-store.d.ts +84 -0
  36. package/dist/security/bridge/ledger-store.js +485 -0
  37. package/dist/security/bridge/ledger-store.js.map +1 -0
  38. package/dist/security/bridge/metadata.d.ts +2 -2
  39. package/dist/security/bridge/permissions.js +2 -2
  40. package/dist/security/bridge/permissions.js.map +1 -1
  41. package/dist/security/bridge/policy.d.ts +42 -23
  42. package/dist/security/bridge/policy.js +214 -146
  43. package/dist/security/bridge/policy.js.map +1 -1
  44. package/dist/security/bridge/security.d.ts +84 -33
  45. package/dist/security/bridge/security.js +240 -166
  46. package/dist/security/bridge/security.js.map +1 -1
  47. package/dist/security/crypto.js +1 -1
  48. package/dist/security/crypto.js.map +1 -1
  49. package/dist/security/ledger/channels.d.ts +5 -0
  50. package/dist/security/ledger/channels.js +21 -0
  51. package/dist/security/ledger/channels.js.map +1 -0
  52. package/dist/security/ledger/ledger.d.ts +61 -0
  53. package/dist/security/ledger/ledger.js +597 -0
  54. package/dist/security/ledger/ledger.js.map +1 -0
  55. package/dist/security/ledger/pairing.d.ts +81 -0
  56. package/dist/security/ledger/pairing.js +414 -0
  57. package/dist/security/ledger/pairing.js.map +1 -0
  58. package/dist/security/ledger/proof.d.ts +24 -0
  59. package/dist/security/ledger/proof.js +161 -0
  60. package/dist/security/ledger/proof.js.map +1 -0
  61. package/dist/security/ledger/protocol.d.ts +100 -0
  62. package/dist/security/ledger/protocol.js +16 -0
  63. package/dist/security/ledger/protocol.js.map +1 -0
  64. package/dist/security/ledger/testing.d.ts +27 -0
  65. package/dist/security/ledger/testing.js +70 -0
  66. package/dist/security/ledger/testing.js.map +1 -0
  67. package/dist/security/ledger/webauthn.d.ts +12 -0
  68. package/dist/security/ledger/webauthn.js +147 -0
  69. package/dist/security/ledger/webauthn.js.map +1 -0
  70. package/dist/security/protocol.d.ts +5 -10
  71. package/dist/security-cli.d.ts +5 -0
  72. package/dist/security-cli.js +28 -16
  73. package/dist/security-cli.js.map +1 -1
  74. package/dist/token-provider.d.ts +39 -0
  75. package/dist/token-provider.js +101 -0
  76. package/dist/token-provider.js.map +1 -0
  77. package/dist/types.d.ts +1 -1
  78. package/package.json +2 -2
  79. package/skills/ftown/references/sessions.md +3 -2
  80. package/dist/bridge-auth.d.ts +0 -31
  81. package/dist/bridge-auth.js +0 -70
  82. package/dist/bridge-auth.js.map +0 -1
  83. package/dist/pairing-client.d.ts +0 -62
  84. package/dist/pairing-client.js +0 -469
  85. package/dist/pairing-client.js.map +0 -1
  86. package/dist/rotating-token-refresher.d.ts +0 -42
  87. package/dist/rotating-token-refresher.js +0 -91
  88. package/dist/rotating-token-refresher.js.map +0 -1
  89. package/dist/security/account-crypto.d.ts +0 -33
  90. package/dist/security/account-crypto.js +0 -649
  91. package/dist/security/account-crypto.js.map +0 -1
  92. package/dist/security/account-protocol.d.ts +0 -228
  93. package/dist/security/account-protocol.js +0 -2
  94. package/dist/security/account-protocol.js.map +0 -1
  95. package/dist/security/bridge/account-client.d.ts +0 -7
  96. package/dist/security/bridge/account-client.js +0 -101
  97. package/dist/security/bridge/account-client.js.map +0 -1
  98. package/dist/security/bridge/account-state.d.ts +0 -105
  99. package/dist/security/bridge/account-state.js +0 -79
  100. package/dist/security/bridge/account-state.js.map +0 -1
  101. package/dist/security/bridge/pending-approval.d.ts +0 -21
  102. package/dist/security/bridge/pending-approval.js +0 -80
  103. package/dist/security/bridge/pending-approval.js.map +0 -1
  104. package/dist/security/bridge/pending-enrollment.d.ts +0 -33
  105. package/dist/security/bridge/pending-enrollment.js +0 -177
  106. package/dist/security/bridge/pending-enrollment.js.map +0 -1
  107. package/dist/security/bridge/revocation.d.ts +0 -14
  108. package/dist/security/bridge/revocation.js +0 -40
  109. package/dist/security/bridge/revocation.js.map +0 -1
  110. package/dist/security/pending-approval-crypto.d.ts +0 -29
  111. package/dist/security/pending-approval-crypto.js +0 -716
  112. package/dist/security/pending-approval-crypto.js.map +0 -1
  113. package/dist/security/pending-approval-protocol.d.ts +0 -329
  114. package/dist/security/pending-approval-protocol.js +0 -2
  115. package/dist/security/pending-approval-protocol.js.map +0 -1
  116. package/dist/security/security-nudge.d.ts +0 -31
  117. package/dist/security/security-nudge.js +0 -98
  118. package/dist/security/security-nudge.js.map +0 -1
@@ -0,0 +1,203 @@
1
+ /*
2
+ * Account-mode startup decisions (passkey ledger), kept out of index.ts so each one is unit-tested:
3
+ * - step 2: which identity reset runs before anything else (resetIdentityForStart);
4
+ * - steps 3-4: pairing, ledger recovery and this bridge's own status (startAccountIdentity);
5
+ * - steps 5-6: the first relay token (online, offline or exit) and the endpoint registration (startRelay, createEndpointRegistrar).
6
+ * Solo and --local never enter any of it: they only finish a reset that is already pending, exactly as before.
7
+ */
8
+ import { beginIdentityReset, completePendingIdentityReset } from './identity-reset.js';
9
+ import { hasLegacyAccountState, loadOrCreateLedgerIdentity, readLedgerIdentity } from './security/bridge/ledger-identity.js';
10
+ import { BridgeLedgerStore, LedgerStoreError, reconcileIdentityAndStore } from './security/bridge/ledger-store.js';
11
+ import { MISSING_RELAY_URL_MESSAGE, isRefusal, relayTokenFailureMessage } from './ledger-relay.js';
12
+ export const LEGACY_ACCOUNT_RESET_MESSAGE = '[Bridge] Old account data from a previous ftown version was cleared. Pair this computer again with the link below.';
13
+ export const NOT_IN_LEDGER_MESSAGE = "This computer is not in your account's ledger. Run `ftown start --new-identity` to pair it again.";
14
+ export const PAIRING_REJECTED_HINT = 'Pairing did not complete. Run `ftown start` to try again.';
15
+ export const SERVER_LEDGER_MISMATCH_MESSAGE = "[Bridge] The server's account ledger does not list this computer. If it was removed from your account, run `ftown start --new-identity` to pair it again.";
16
+ export const UPDATE_RESTART_UNPAIRED_MESSAGE = '[Bridge] An update restart cannot pair this computer. Run `ftown start` to pair it.';
17
+ export const UPDATED_TO_LEDGER_MESSAGE = '[Bridge] ftown was updated to the passkey ledger; run `ftown start` to pair this computer.';
18
+ /** Only account bridges use the passkey ledger (legacy reset, pairing, ledger replica, relay token by proof). */
19
+ export function usesLedger(mode) {
20
+ return mode === 'account';
21
+ }
22
+ /**
23
+ * Step 2. Account mode: `--new-identity`, or any v1 account state on disk, starts a manual reset (the latter says so in one line).
24
+ * An update restart (a v1 -> v2 auto-update) has no visible terminal for the pairing link: with v1 state on disk it changes nothing
25
+ * and exits, and the next `ftown start` resets and pairs. Every mode then finishes a pending reset, as before (a revoked restart or a
26
+ * crash may have left one).
27
+ */
28
+ export function resetIdentityForStart(input, deps = {}) {
29
+ const legacy = deps.hasLegacyAccountState ?? hasLegacyAccountState;
30
+ const begin = deps.beginIdentityReset ?? beginIdentityReset;
31
+ const complete = deps.completePendingIdentityReset ?? completePendingIdentityReset;
32
+ if (usesLedger(input.mode)) {
33
+ if (input.newIdentity)
34
+ begin(input.dataDir, 'manual');
35
+ else if (legacy(input.dataDir)) {
36
+ if (input.updateRestart)
37
+ return { kind: 'exit', message: UPDATED_TO_LEDGER_MESSAGE };
38
+ input.log(LEGACY_ACCOUNT_RESET_MESSAGE);
39
+ begin(input.dataDir, 'manual');
40
+ }
41
+ }
42
+ return { kind: 'continue', reset: complete(input.dataDir) };
43
+ }
44
+ /** Step 4. Only a signed revocation resets the identity; a bridge the ledger does not list fails closed and is never reset. */
45
+ export function ownStatusAction(own) {
46
+ if (own === 'active')
47
+ return 'continue';
48
+ if (own === 'revoked')
49
+ return 'revoked_restart';
50
+ return 'exit_absent';
51
+ }
52
+ const messageOf = (error) => error instanceof Error ? error.message : String(error);
53
+ export function ledgerUnavailableMessage(reason) {
54
+ return `[Bridge] Could not load your account ledger from the server (${reason}). Check the connection and run \`ftown start\` again.`;
55
+ }
56
+ function bound(identity) {
57
+ return identity !== null && identity.endpoint !== null && identity.accountId !== null;
58
+ }
59
+ /**
60
+ * Steps 3-4, account mode only, before any network use of the identity:
61
+ * - keys exist (created when absent), then identity and replica are reconciled;
62
+ * - 'unpaired' -> one pairing attempt (expired/rejected -> exit 1 with the hint);
63
+ * - 'needs_ledger' -> the server ledger is adopted only through adoptBound (any failure -> exit 1; the identity stays bound);
64
+ * - own status from the trusted replica: 'revoked' -> revoked restart; 'absent' -> exit 1 (never a reset); 'active' -> ready.
65
+ * Corrupted local files throw (they are never reset).
66
+ */
67
+ export async function startAccountIdentity(deps) {
68
+ const loadOrCreate = deps.loadOrCreateIdentity ?? loadOrCreateLedgerIdentity;
69
+ const reconcile = deps.reconcile ?? ((dataDir) => reconcileIdentityAndStore(dataDir));
70
+ const readIdentity = deps.readIdentity ?? readLedgerIdentity;
71
+ const keys = await loadOrCreate(deps.dataDir);
72
+ const reconciled = await reconcile(deps.dataDir);
73
+ if (reconciled === 'unpaired') {
74
+ if (!deps.allowPairing)
75
+ return { kind: 'exit', message: UPDATE_RESTART_UNPAIRED_MESSAGE };
76
+ try {
77
+ await deps.pair(keys, new BridgeLedgerStore(deps.dataDir));
78
+ }
79
+ catch (error) {
80
+ const code = messageOf(error);
81
+ if (code === 'pairing_expired')
82
+ return { kind: 'exit', message: null };
83
+ if (code === 'pairing_rejected')
84
+ return { kind: 'exit', message: PAIRING_REJECTED_HINT };
85
+ throw error;
86
+ }
87
+ }
88
+ const identity = await readIdentity(deps.dataDir);
89
+ if (!bound(identity))
90
+ return { kind: 'exit', message: PAIRING_REJECTED_HINT };
91
+ const store = new BridgeLedgerStore(deps.dataDir, { accountId: identity.accountId });
92
+ if (reconciled === 'needs_ledger') {
93
+ try {
94
+ await store.adoptBound(await deps.fetchLedger(identity.accountId));
95
+ }
96
+ catch (error) {
97
+ if (error instanceof LedgerStoreError && error.code === 'ledger_store_bound_rejected')
98
+ return { kind: 'exit', message: SERVER_LEDGER_MISMATCH_MESSAGE };
99
+ return { kind: 'exit', message: ledgerUnavailableMessage(messageOf(error)) };
100
+ }
101
+ }
102
+ const action = ownStatusAction(await store.ownBridgeStatus(deps.bridgeId, identity.authPublicKey));
103
+ if (action === 'revoked_restart')
104
+ return { kind: 'revoked' };
105
+ if (action === 'exit_absent')
106
+ return { kind: 'exit', message: NOT_IN_LEDGER_MESSAGE };
107
+ return { kind: 'ready', identity, store, reconciled };
108
+ }
109
+ export function offlineWithoutRelayUrlMessage(reason) {
110
+ return `[Bridge] Could not reach the ftown server (${reason}) and no relay URL was saved by an earlier start. Check the connection and run \`ftown start\` again.`;
111
+ }
112
+ /**
113
+ * Step 5, the first relay token. A proven revocation (the token hooks offer the server's evidence first) wins over everything; a
114
+ * refusal (401/403) exits; a temporary failure (network, timeout, 5xx, cooldown) starts offline with the saved relay URL, and exits
115
+ * only when there is none. After a successful token the revocation is checked again: the token response's ledger may prove it.
116
+ */
117
+ export async function startRelay(input) {
118
+ let token;
119
+ try {
120
+ token = await input.getToken();
121
+ }
122
+ catch (error) {
123
+ if (input.bridgeRevoked())
124
+ return { kind: 'revoked' };
125
+ if (isRefusal(error))
126
+ return { kind: 'exit', message: relayTokenFailureMessage(error, input.apiUrl) };
127
+ const reason = messageOf(error);
128
+ const relayUrl = input.relayUrl();
129
+ return relayUrl === null ? { kind: 'exit', message: offlineWithoutRelayUrlMessage(reason) } : { kind: 'offline', relayUrl, reason };
130
+ }
131
+ if (input.bridgeRevoked())
132
+ return { kind: 'revoked' };
133
+ const relayUrl = input.relayUrl();
134
+ if (relayUrl === null)
135
+ return { kind: 'exit', message: MISSING_RELAY_URL_MESSAGE };
136
+ return { kind: 'online', token, relayUrl };
137
+ }
138
+ export function endpointRefusedMessage(reason) {
139
+ return `[Bridge] The ftown server refused this computer's encrypted endpoint (${reason}). If it was removed from your account, run \`ftown start --new-identity\` to pair it again.`;
140
+ }
141
+ export function createEndpointRegistrar(deps) {
142
+ const schedule = deps.schedule ?? ((run, ms) => { const timer = setTimeout(run, ms); timer.unref(); return () => clearTimeout(timer); });
143
+ const maxDelay = deps.maxDelayMs ?? 600_000;
144
+ let delay = deps.baseDelayMs ?? 15_000;
145
+ let done = false;
146
+ let stopped = false;
147
+ let cancel = null;
148
+ const next = () => {
149
+ const wait = Math.max(deps.tokens.cooldownRemainingMs(), delay);
150
+ delay = Math.min(delay * 2, maxDelay);
151
+ cancel = schedule(() => { cancel = null; void attempt(); }, wait);
152
+ };
153
+ const attempt = async () => {
154
+ if (done || stopped)
155
+ return;
156
+ try {
157
+ await deps.register(await deps.tokens.getToken());
158
+ done = true;
159
+ deps.log('[Bridge] Registered this computer\'s encrypted endpoint.');
160
+ }
161
+ catch (error) {
162
+ if (stopped)
163
+ return;
164
+ if (isRefusal(error)) {
165
+ // A verified revocation restarts the bridge on its own; anything else needs the owner.
166
+ if (!deps.bridgeRevoked())
167
+ deps.log(endpointRefusedMessage(messageOf(error)));
168
+ stopped = true;
169
+ return;
170
+ }
171
+ next();
172
+ }
173
+ };
174
+ return {
175
+ async registerNow(token) {
176
+ try {
177
+ await deps.register(token);
178
+ done = true;
179
+ return { kind: 'registered' };
180
+ }
181
+ catch (error) {
182
+ if (deps.bridgeRevoked())
183
+ return { kind: 'revoked' };
184
+ if (isRefusal(error))
185
+ return { kind: 'exit', message: endpointRefusedMessage(messageOf(error)) };
186
+ deps.log(`[Bridge] Could not register this computer's encrypted endpoint yet (${messageOf(error)}); retrying in the background.`);
187
+ return { kind: 'retry' };
188
+ }
189
+ },
190
+ retryInBackground() {
191
+ if (done || stopped || cancel !== null)
192
+ return;
193
+ next();
194
+ },
195
+ registered: () => done,
196
+ stop() {
197
+ stopped = true;
198
+ cancel?.();
199
+ cancel = null;
200
+ },
201
+ };
202
+ }
203
+ //# sourceMappingURL=ledger-startup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-startup.js","sourceRoot":"","sources":["../src/ledger-startup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { UnauthorizedError } from 'centrifuge';
2
+ export interface RelayTokenFetcherDeps {
3
+ apiUrl: string;
4
+ accountId: () => string;
5
+ bridgeId: string;
6
+ authPrivateKey: CryptoKey;
7
+ /** Head of the locally trusted ledger, or null when there is none. */
8
+ head: () => string | null;
9
+ /** Called with a ledger returned by the server, BEFORE the token is handed back. */
10
+ onLedger: (ledger: unknown) => Promise<void>;
11
+ /** Called with the evidence ledger of a 401 key_revoked, BEFORE UnauthorizedError is thrown. */
12
+ onRevoked: (ledger: unknown) => Promise<void>;
13
+ fetchImpl?: typeof fetch;
14
+ /** Per-request timeout in ms (default 15000). */
15
+ timeoutMs?: number;
16
+ /** Presence info claim, as refreshBridgeToken sends today (os.hostname()). Omitted when undefined. */
17
+ hostname?: () => string;
18
+ /** Loopback port advert (discovery only). Omitted when undefined. */
19
+ localPort?: () => number | undefined;
20
+ /** On a plain 401 (not key_revoked): resolve true to retry the whole sequence exactly once. */
21
+ onUnauthorized?: () => Promise<boolean>;
22
+ }
23
+ /** A 401 key_revoked; still terminal for centrifuge-js. `ledger` is the (unverified) evidence. */
24
+ export declare class RelayKeyRevokedError extends UnauthorizedError {
25
+ readonly ledger: unknown;
26
+ constructor(ledger: unknown);
27
+ }
28
+ export interface RelayToken {
29
+ token: string;
30
+ expiresAt: number;
31
+ }
32
+ /**
33
+ * Proves possession of the bridge auth key to the server and returns a relay (Centrifugo) token.
34
+ * Replaces the rotating refresh token: there is no credential to persist.
35
+ *
36
+ * A 401 is terminal (UnauthorizedError stops centrifuge-js retrying); everything else is a plain
37
+ * Error so the caller's backoff and cooldown apply.
38
+ */
39
+ export declare function createRelayTokenFetcher(deps: RelayTokenFetcherDeps): () => Promise<RelayToken>;
@@ -0,0 +1,130 @@
1
+ import { UnauthorizedError } from 'centrifuge';
2
+ import { signAuthProof } from './security/ledger/proof.js';
3
+ /** A 401 key_revoked; still terminal for centrifuge-js. `ledger` is the (unverified) evidence. */
4
+ export class RelayKeyRevokedError extends UnauthorizedError {
5
+ ledger;
6
+ constructor(ledger) {
7
+ super('Bridge revoked');
8
+ this.name = 'RelayKeyRevokedError';
9
+ this.ledger = ledger;
10
+ }
11
+ }
12
+ const BODY_CAP = 200;
13
+ const MAX_RESPONSE_BYTES = 3 * 1024 * 1024;
14
+ const DEFAULT_TIMEOUT_MS = 15_000;
15
+ function parseJson(text) {
16
+ try {
17
+ const value = JSON.parse(text);
18
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ async function post(fetchImpl, url, body, timeoutMs) {
25
+ try {
26
+ const res = await fetchImpl(url, {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/json' },
29
+ body: JSON.stringify(body),
30
+ signal: AbortSignal.timeout(timeoutMs),
31
+ });
32
+ const declared = Number(res.headers.get('content-length'));
33
+ if (declared > MAX_RESPONSE_BYTES)
34
+ throw new Error('relay_token_response_too_large');
35
+ const text = await res.text();
36
+ if (text.length > MAX_RESPONSE_BYTES)
37
+ throw new Error('relay_token_response_too_large');
38
+ return { status: res.status, ok: res.ok, text };
39
+ }
40
+ catch (error) {
41
+ if (error instanceof Error && error.message === 'relay_token_response_too_large')
42
+ throw error;
43
+ if (error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
44
+ throw new Error(`Relay request timed out after ${timeoutMs}ms`);
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+ class PlainUnauthorized extends UnauthorizedError {
50
+ }
51
+ function toUnauthorized(error) {
52
+ return error instanceof PlainUnauthorized ? new UnauthorizedError(error.message) : error;
53
+ }
54
+ /**
55
+ * Proves possession of the bridge auth key to the server and returns a relay (Centrifugo) token.
56
+ * Replaces the rotating refresh token: there is no credential to persist.
57
+ *
58
+ * A 401 is terminal (UnauthorizedError stops centrifuge-js retrying); everything else is a plain
59
+ * Error so the caller's backoff and cooldown apply.
60
+ */
61
+ export function createRelayTokenFetcher(deps) {
62
+ const fetchImpl = deps.fetchImpl ?? fetch;
63
+ const apiUrl = deps.apiUrl.replace(/\/+$/, '');
64
+ const audience = new URL(apiUrl).origin;
65
+ const timeoutMs = deps.timeoutMs ?? DEFAULT_TIMEOUT_MS;
66
+ const presence = () => {
67
+ const out = {};
68
+ const hostname = deps.hostname?.();
69
+ if (hostname !== undefined)
70
+ out.hostname = hostname;
71
+ const localPort = deps.localPort?.();
72
+ if (localPort !== undefined)
73
+ out.localPort = localPort;
74
+ return out;
75
+ };
76
+ const attempt = async () => {
77
+ const nonceRes = await post(fetchImpl, `${apiUrl}/api/auth/nonce`, { purpose: 'relay' }, timeoutMs);
78
+ const nonceText = nonceRes.text;
79
+ if (!nonceRes.ok)
80
+ throw new Error(`Relay nonce failed (${nonceRes.status}): ${nonceText.slice(0, BODY_CAP)}`);
81
+ const nonce = parseJson(nonceText)?.nonce;
82
+ if (typeof nonce !== 'string' || nonce.length === 0)
83
+ throw new Error('Relay nonce response malformed');
84
+ const proof = await signAuthProof(deps.authPrivateKey, {
85
+ accountId: deps.accountId(),
86
+ kind: 'bridge',
87
+ keyId: deps.bridgeId,
88
+ nonce,
89
+ purpose: 'relay',
90
+ audience,
91
+ });
92
+ const res = await post(fetchImpl, `${apiUrl}/api/auth/relay-token`, { proof, head: deps.head(), ...presence() }, timeoutMs);
93
+ const text = res.text;
94
+ if (res.status === 401) {
95
+ const body = parseJson(text);
96
+ if (body && body.error === 'key_revoked' && body.ledger !== undefined) {
97
+ await deps.onRevoked(body.ledger);
98
+ throw new RelayKeyRevokedError(body.ledger);
99
+ }
100
+ throw new PlainUnauthorized(`Relay token rejected (401): ${text.slice(0, BODY_CAP)}`);
101
+ }
102
+ if (!res.ok)
103
+ throw new Error(`Relay token failed (${res.status}): ${text.slice(0, BODY_CAP)}`);
104
+ const body = parseJson(text);
105
+ if (!body || typeof body.token !== 'string' || !body.token || typeof body.expiresAt !== 'number') {
106
+ throw new Error('Relay token response malformed');
107
+ }
108
+ if (body.ledger !== undefined && body.ledger !== null)
109
+ await deps.onLedger(body.ledger);
110
+ return { token: body.token, expiresAt: body.expiresAt };
111
+ };
112
+ return async () => {
113
+ try {
114
+ return await attempt();
115
+ }
116
+ catch (error) {
117
+ if (!(error instanceof PlainUnauthorized) || !deps.onUnauthorized)
118
+ throw toUnauthorized(error);
119
+ if (!(await deps.onUnauthorized()))
120
+ throw toUnauthorized(error);
121
+ try {
122
+ return await attempt();
123
+ }
124
+ catch (retryError) {
125
+ throw toUnauthorized(retryError);
126
+ }
127
+ }
128
+ };
129
+ }
130
+ //# sourceMappingURL=relay-token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-token.js","sourceRoot":"","sources":["../src/relay-token.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,8 @@
1
- import type { AccountCheckpoint } from './security/account-protocol.js';
2
1
  export type BridgeMode = 'account' | 'solo' | 'local';
3
2
  export declare function bridgeMode(opts: {
4
3
  local?: boolean;
5
4
  solo?: boolean;
6
5
  }): BridgeMode;
7
- /** Solo and local bridges belong to no account, so nothing can revoke them. */
8
- export declare function handlesRevocation(mode: BridgeMode): boolean;
9
- /** A pin that already tombstones this bridgeId is signed proof on its own. */
10
- export declare function restartForPinnedRevocation(mode: BridgeMode, pinned: AccountCheckpoint | null, bridgeId: string): boolean;
11
- /** Only a refusal whose signed history verifies against our own pin counts; a bare 401 or a forged body never does. */
12
- export declare function restartForRefreshError(input: {
13
- mode: BridgeMode;
14
- error: unknown;
15
- pinned: AccountCheckpoint | null;
16
- bridgeId: string;
17
- now?: number;
18
- }): Promise<boolean>;
19
6
  /** Same phases as the update coordinator's active operation: the update reservation is held. */
20
7
  export declare function updateOperationActive(phase: string | null): boolean;
21
8
  /**
@@ -1,22 +1,6 @@
1
- import { BridgeRevokedError } from './bridge-auth.js';
2
- import { confirmOwnRevocation, isOwnBridgeRevoked } from './security/bridge/revocation.js';
3
1
  export function bridgeMode(opts) {
4
2
  return opts.local ? 'local' : opts.solo ? 'solo' : 'account';
5
3
  }
6
- /** Solo and local bridges belong to no account, so nothing can revoke them. */
7
- export function handlesRevocation(mode) {
8
- return mode === 'account';
9
- }
10
- /** A pin that already tombstones this bridgeId is signed proof on its own. */
11
- export function restartForPinnedRevocation(mode, pinned, bridgeId) {
12
- return handlesRevocation(mode) && isOwnBridgeRevoked(pinned, bridgeId);
13
- }
14
- /** Only a refusal whose signed history verifies against our own pin counts; a bare 401 or a forged body never does. */
15
- export async function restartForRefreshError(input) {
16
- if (!handlesRevocation(input.mode) || !(input.error instanceof BridgeRevokedError))
17
- return false;
18
- return confirmOwnRevocation({ evidence: input.error.evidence, pinned: input.pinned, bridgeId: input.bridgeId, now: input.now });
19
- }
20
4
  const ACTIVE_UPDATE_PHASES = ['staging', 'prepared', 'restarting'];
21
5
  /** Same phases as the update coordinator's active operation: the update reservation is held. */
22
6
  export function updateOperationActive(phase) {
@@ -1 +1 @@
1
- {"version":3,"file":"revocation-gating.js","sourceRoot":"","sources":["../src/revocation-gating.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAI3F,MAAM,UAAU,UAAU,CAAC,IAAyC;IAClE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAAC,IAAgB;IAChD,OAAO,IAAI,KAAK,SAAS,CAAC;AAC5B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,0BAA0B,CAAC,IAAgB,EAAE,MAAgC,EAAE,QAAgB;IAC7G,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACzE,CAAC;AAED,uHAAuH;AACvH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAA6G;IACxJ,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY,kBAAkB,CAAC;QAAE,OAAO,KAAK,CAAC;IACjG,OAAO,oBAAoB,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;AAClI,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAEnE,gGAAgG;AAChG,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,OAAO,KAAK,KAAK,IAAI,IAAI,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAA6D;IACpG,OAAO,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"revocation-gating.js","sourceRoot":"","sources":["../src/revocation-gating.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,IAAyC;IAClE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAEnE,gGAAgG;AAChG,MAAM,UAAU,qBAAqB,CAAC,KAAoB;IACxD,OAAO,KAAK,KAAK,IAAI,IAAI,oBAAoB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAA6D;IACpG,OAAO,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC3E,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import type { EndpointIdentity, EndpointKind } from '../protocol.js';
3
- import type { VerifiedAccountTrust } from '../account-protocol.js';
3
+ /** Solo, --local and local-CLI endpoint keys (`<kind>-identity.json`); account bridges keep theirs in ledger-identity.json. */
4
4
  export declare const identityRecordSchema: z.ZodObject<{
5
5
  version: z.ZodLiteral<1>;
6
6
  endpoint: z.ZodObject<{
@@ -8,8 +8,8 @@ export declare const identityRecordSchema: z.ZodObject<{
8
8
  id: z.ZodString;
9
9
  ownerId: z.ZodString;
10
10
  kind: z.ZodEnum<{
11
- browser: "browser";
12
11
  bridge: "bridge";
12
+ browser: "browser";
13
13
  "local-cli": "local-cli";
14
14
  }>;
15
15
  publicKey: z.ZodString;
@@ -23,50 +23,6 @@ export declare const identityRecordSchema: z.ZodObject<{
23
23
  key_ops: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
24
  ext: z.ZodOptional<z.ZodBoolean>;
25
25
  }, z.core.$strict>;
26
- accountBootstrap: z.ZodOptional<z.ZodObject<{
27
- history: z.ZodCustom<import("../account-protocol.js").AccountHistory, import("../account-protocol.js").AccountHistory>;
28
- checkpoint: z.ZodObject<{
29
- version: z.ZodLiteral<1>;
30
- ownerId: z.ZodString;
31
- rootId: z.ZodString;
32
- head: z.ZodString;
33
- revision: z.ZodNumber;
34
- devices: z.ZodArray<z.ZodObject<{
35
- endpoint: z.ZodObject<{
36
- version: z.ZodLiteral<1>;
37
- id: z.ZodString;
38
- ownerId: z.ZodString;
39
- kind: z.ZodEnum<{
40
- browser: "browser";
41
- bridge: "bridge";
42
- "local-cli": "local-cli";
43
- }>;
44
- publicKey: z.ZodString;
45
- }, z.core.$strict>;
46
- signingPublicKey: z.ZodString;
47
- label: z.ZodString;
48
- approvedAt: z.ZodUnion<readonly [z.ZodISODateTime, z.ZodISODateTime]>;
49
- }, z.core.$strict>>;
50
- bridges: z.ZodArray<z.ZodObject<{
51
- bridgeId: z.ZodString;
52
- endpoint: z.ZodObject<{
53
- version: z.ZodLiteral<1>;
54
- id: z.ZodString;
55
- ownerId: z.ZodString;
56
- kind: z.ZodEnum<{
57
- browser: "browser";
58
- bridge: "bridge";
59
- "local-cli": "local-cli";
60
- }>;
61
- publicKey: z.ZodString;
62
- }, z.core.$strict>;
63
- label: z.ZodString;
64
- approvedAt: z.ZodUnion<readonly [z.ZodISODateTime, z.ZodISODateTime]>;
65
- }, z.core.$strict>>;
66
- revokedDeviceIds: z.ZodArray<z.ZodString>;
67
- revokedBridgeIds: z.ZodArray<z.ZodString>;
68
- }, z.core.$strict>;
69
- }, z.core.$strict>>;
70
26
  }, z.core.$strict>;
71
27
  /** OS-local persisted keys; corruption/wrong owner never silently generates a replacement identity. */
72
28
  export declare function loadOrCreateIdentity(dataDir: string, ownerId: string, kind: 'bridge' | 'local-cli'): Promise<EndpointIdentity>;
@@ -75,5 +31,4 @@ export declare function loadLocalIdentity(dataDir: string, ownerId: string): Pro
75
31
  /** Absence alone permits virgin onboarding; corruption must never generate a new key/root. */
76
32
  export declare function readBoundBridgeIdentity(dataDir: string): Promise<{
77
33
  identity: EndpointIdentity;
78
- bootstrap?: VerifiedAccountTrust;
79
34
  } | null>;
@@ -5,11 +5,11 @@ import { generateEndpoint, importEndpoint } from '../crypto.js';
5
5
  import { endpointSchema } from './metadata.js';
6
6
  import { AtomicSnapshot } from './snapshot.js';
7
7
  import { BridgeSecurityError } from './errors.js';
8
- import { verifiedAccountSchema } from './account-state.js';
8
+ /** Solo, --local and local-CLI endpoint keys (`<kind>-identity.json`); account bridges keep theirs in ledger-identity.json. */
9
9
  export const identityRecordSchema = z.object({ version: z.literal(1), endpoint: endpointSchema, privateKey: z.object({
10
10
  kty: z.literal('EC'), crv: z.literal('P-256'), x: z.string(), y: z.string(), d: z.string(),
11
11
  key_ops: z.array(z.string()).optional(), ext: z.boolean().optional(),
12
- }).strict(), accountBootstrap: verifiedAccountSchema.optional() }).strict();
12
+ }).strict() }).strict();
13
13
  /** OS-local persisted keys; corruption/wrong owner never silently generates a replacement identity. */
14
14
  export async function loadOrCreateIdentity(dataDir, ownerId, kind) {
15
15
  const generated = await generateEndpoint(ownerId, kind, true);
@@ -55,7 +55,7 @@ export async function readBoundBridgeIdentity(dataDir) {
55
55
  const record = identityRecordSchema.parse(JSON.parse(raw));
56
56
  if (record.endpoint.kind !== 'bridge')
57
57
  throw new BridgeSecurityError('wrong_context');
58
- return { identity: await importEndpoint(record.endpoint, record.privateKey, false), bootstrap: record.accountBootstrap };
58
+ return { identity: await importEndpoint(record.endpoint, record.privateKey, false) };
59
59
  }
60
60
  catch {
61
61
  throw new BridgeSecurityError('key_unavailable');
@@ -1 +1 @@
1
- {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/security/bridge/identity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/security/bridge/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,+HAA+H;AAC/H,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACnH,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE;QAC1F,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACrE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;AAExB,uGAAuG;AACvG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAe,EAAE,OAAe,EAAE,IAA4B;IACvG,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1G,MAAM,KAAK,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAC1H,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;QACzC,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,IAAI;YAAE,MAAM,IAAI,mBAAmB,CAAC,eAAe,CAAC,CAAC;QACvH,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,IAAI,CAAC;QAAC,OAAO,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAAC,CAAC;IAC7E,MAAM,CAAC;QAAC,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAAC,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAkB,IAAoC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC;AAE7I,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,OAAe;IACtE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,yBAAyB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;QACnI,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAClH,OAAO,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAAC,CAAC;AAC/D,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,OAAe;IAC3D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QAAC,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,sBAAsB,CAAC,EAAE,MAAM,CAAC,CAAC;IAAC,CAAC;IACxF,OAAO,KAAK,EAAE,CAAC;QACb,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACpE,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,mBAAmB,CAAC,eAAe,CAAC,CAAC;QACtF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IACvF,CAAC;IAAC,MAAM,CAAC;QAAC,MAAM,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { EndpointPublic } from '../protocol.js';
2
+ export interface LedgerBridgeIdentity {
3
+ /** ECDH P-256 endpoint key pair for HPKE; the private key is non-extractable. */
4
+ keyPair: CryptoKeyPair;
5
+ /** Raw uncompressed ECDH point, base64url: BridgePairRequest.endpointPublicKey. */
6
+ endpointPublicKey: string;
7
+ /** base64url SHA-256 of the raw endpoint point: EndpointPublic.id. */
8
+ endpointId: string;
9
+ /** Raw uncompressed ECDSA point, base64url: BridgePairRequest/LedgerBridge.authPublicKey. */
10
+ authPublicKey: string;
11
+ /** Non-extractable ECDSA P-256 signing key for signAuthProof. */
12
+ authPrivateKey: CryptoKey;
13
+ /** The bound endpoint, or null before pairing. With `keyPair` it is the bridge's EndpointIdentity. */
14
+ endpoint: EndpointPublic | null;
15
+ /** endpoint.ownerId once bound, else null. */
16
+ accountId: string | null;
17
+ }
18
+ /** What is on disk, without importing keys. A corrupted file throws BridgeSecurityError('key_unavailable'). */
19
+ export type LedgerIdentityFileInfo = {
20
+ state: 'absent';
21
+ accountId: null;
22
+ } | {
23
+ state: 'ready';
24
+ accountId: string | null;
25
+ };
26
+ /** Reports the v2 identity file's state without importing keys. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
27
+ export declare function inspectIdentityFile(dataDir: string): Promise<LedgerIdentityFileInfo>;
28
+ /**
29
+ * True iff the data dir holds v1 ACCOUNT state: `refresh-token`, `security/account-enrollment.json`,
30
+ * `security/pending-approval-status.json` or `security/pending-approval-confirmation.json` exists, or `security/bridge-identity.json`
31
+ * has a readable endpoint owner that is not `solo:` (Solo's identity is never reported). An unreadable bridge-identity.json is not
32
+ * reported: its owner cannot be told apart from Solo's. Reads only; never modifies anything.
33
+ */
34
+ export declare function hasLegacyAccountState(dataDir: string): boolean;
35
+ /** Loads the identity without creating one: null when absent. Throws BridgeSecurityError('key_unavailable') for a corrupted file. */
36
+ export declare function readLedgerIdentity(dataDir: string): Promise<LedgerBridgeIdentity | null>;
37
+ /**
38
+ * Loads the identity, creating fresh keys only when the file is absent. Throws BridgeSecurityError: key_unavailable for an unreadable
39
+ * or corrupted file (never replaced), storage_unavailable when the new file cannot be written.
40
+ */
41
+ export declare function loadOrCreateLedgerIdentity(dataDir: string): Promise<LedgerBridgeIdentity>;
42
+ /**
43
+ * Binds the identity to `accountId` once: sets endpoint {version:1, id, ownerId: accountId, kind:'bridge', publicKey}. Idempotent for
44
+ * the same account. Throws BridgeSecurityError: wrong_context for a different (or malformed) accountId, key_unavailable when there is
45
+ * no valid identity to bind, storage_unavailable when the file cannot be written. Binds are serialized within this process only: the
46
+ * bridge's single-instance guarantee keeps a second process from binding concurrently.
47
+ */
48
+ export declare function bindLedgerIdentity(dataDir: string, accountId: string): Promise<LedgerBridgeIdentity>;