@floegence/flowersec-core 3.0.4 → 3.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.
package/README.md CHANGED
@@ -22,12 +22,19 @@ The root type exports are:
22
22
 
23
23
  - Artifact lifecycle: `Artifact`, closed `ArtifactError` parse failures, and `ArtifactLease`.
24
24
  - Sessions: `Session`, `SessionTermination`, `RpcPeer`, `RpcResult<Response>`, `ByteStream`, `IncomingStream`, `StreamMetadata`, `OperationOptions`, and `StreamOpenOptions`. Create metadata with `createStreamMetadata(...)`; invalid values throw `StreamMetadataError` before opening a stream.
25
- - Unreliable messages: `UnreliableMessageChannel`, `UnreliableMessageSendOptions`, and `UnreliableMessageSendResult`.
25
+ - Unreliable messages: `UnreliableMessageChannel`, `UnreliableMessageSendOptions`, `UnreliableMessageSendResult`, and the closed `UnreliableMessageErrorCode` set.
26
26
  - JSON values: `JsonPrimitive`, `JsonValue`, and `JsonObject`.
27
27
  - Errors: `ConnectErrorCode`, `SessionErrorCode`, and structured `RetryDisposition`.
28
- - Connection lifecycle: `ArtifactSource`, `ArtifactSourceResult`, `ConnectionController`, `ConnectionState`, `ConnectionSnapshot`, `ConnectionControllerFailure`, `ConnectionControllerError`, and `RetryDisposition`.
28
+ - Connection lifecycle: `ArtifactSource`, `ArtifactSourceResult`, `ConnectionController`, `ConnectionState`, `ConnectionSnapshot`, `ConnectionDiagnostic`, `ConnectionControllerFailure`, `ConnectionControllerError`, and `RetryDisposition`.
29
29
 
30
- Retry ownership belongs to `ConnectionController`; applications do not classify error text or run a parallel retry scheduler. Public failures remain redacted and reveal no carrier, candidate, URL, credential, stage, key, or diagnostic details.
30
+ Unversioned names are the recommended strict-v3 API. Explicit `V3` exports are
31
+ deprecated aliases. `ConnectError.retryDisposition` and
32
+ `retry_after.notBeforeUnixMilliseconds` are canonical; deprecated
33
+ `disposition` and `absoluteUnixMilliseconds` properties remain readable through
34
+ their aliases. Retry ownership belongs to `ConnectionController`;
35
+ applications do not classify error text or run a parallel retry scheduler.
36
+ Public failures remain redacted and reveal no carrier, candidate, URL,
37
+ credential, stage, key, or implementation details.
31
38
 
32
39
  `RpcResult<Response>` is a discriminated union. `RpcPeer.call(...)` requires a decoder for successful payloads, so the typed success value has passed application validation before it is returned. Check `result.ok` before reading either the typed success `payload` or bounded application `error`; a result cannot contain both. RPC call and notify accept only `JsonValue` payloads and reject values that cannot be represented on the wire before sending. TypeScript `RpcPeer.onNotify(typeId, decoder, handler)` receives peer outbound notifications through the local Session's inbound reserved RPC stream. A notification reaches the handler only after its decoder succeeds; decoder and handler failures are isolated from RPC serving.
33
40
 
@@ -50,7 +57,11 @@ const session = await connect(lease, {
50
57
  ### Long-lived Node client
51
58
 
52
59
  ```ts
53
- import { RPCHandlers, createConnectionController } from "@floegence/flowersec-core/node";
60
+ import {
61
+ RPCHandlers,
62
+ connectionDiagnostic,
63
+ createConnectionController,
64
+ } from "@floegence/flowersec-core/node";
54
65
 
55
66
  const rpcHandlers = new RPCHandlers();
56
67
  rpcHandlers.handleRPC(7, async (payload) => ({ payload }));
@@ -60,10 +71,15 @@ const controller = createConnectionController(source, {
60
71
  });
61
72
  controller.start();
62
73
  const session = await controller.waitForSession();
74
+ const unsubscribe = controller.subscribe((snapshot) => {
75
+ monitor(connectionDiagnostic(snapshot));
76
+ });
63
77
  ```
64
78
 
65
79
  The immutable callback definition applies to every generation, while each
66
- Session gets a fresh router. Terminated Session work is never replayed.
80
+ Session gets a fresh router. `waitForSession()` never starts the controller.
81
+ Diagnostics contain only state, attempt, failure phase/code, and retry
82
+ disposition. Terminated Session work is never replayed.
67
83
 
68
84
  ### Application streams on any Session
69
85
 
@@ -3,11 +3,11 @@
3
3
  This file is generated from the canonical Flowersec source dependency inventory for flowersec-ts.
4
4
  Do not edit it manually. License decisions are reviewed by the repository source license policy.
5
5
 
6
- - @floegence/flowersec-node-native 3.0.4 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
7
- - @floegence/flowersec-node-native-darwin-arm64 3.0.4 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
8
- - @floegence/flowersec-node-native-darwin-x64 3.0.4 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
9
- - @floegence/flowersec-node-native-linux-arm64-gnu 3.0.4 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
10
- - @floegence/flowersec-node-native-linux-x64-gnu 3.0.4 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
6
+ - @floegence/flowersec-node-native 3.1.1 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
7
+ - @floegence/flowersec-node-native-darwin-arm64 3.1.1 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
8
+ - @floegence/flowersec-node-native-darwin-x64 3.1.1 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
9
+ - @floegence/flowersec-node-native-linux-arm64-gnu 3.1.1 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
10
+ - @floegence/flowersec-node-native-linux-x64-gnu 3.1.1 (Declared: MIT; selected: MIT; source: https://github.com/floegence/flowersec.git)
11
11
  - @noble/ciphers 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.3.0.tgz)
12
12
  - @noble/curves 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/curves/-/curves-2.3.0.tgz)
13
13
  - @noble/hashes 2.3.0 (Declared: MIT; selected: MIT; source: https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz)
@@ -1,4 +1,8 @@
1
1
  export * from "../facade.js";
2
2
  export * as v2 from "./v2.js";
3
- export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
4
- export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, ConnectionControllerOptionsV3, SessionOptionsV3 as SessionOptions, SessionOptionsV3, } from "./connectSessionV3.js";
3
+ export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, } from "./connectSessionV3.js";
4
+ /** @deprecated Use connect and createConnectionController. */
5
+ export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
6
+ export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, SessionOptionsV3 as SessionOptions, } from "./connectSessionV3.js";
7
+ /** @deprecated Use ConnectionControllerOptions and SessionOptions. */
8
+ export type { ConnectionControllerOptionsV3, SessionOptionsV3 } from "./connectSessionV3.js";
@@ -1,3 +1,5 @@
1
1
  export * from "../facade.js";
2
2
  export * as v2 from "./v2.js";
3
- export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
3
+ export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, } from "./connectSessionV3.js";
4
+ /** @deprecated Use connect and createConnectionController. */
5
+ export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
package/dist/facade.d.ts CHANGED
@@ -1,21 +1,38 @@
1
1
  import type { Session as PublicSession } from "./public/contract.js";
2
2
  import type { ConnectionControllerSnapshotV3 as CoreConnectionControllerSnapshotV3, ConnectionControllerV3 as CoreConnectionControllerV3 } from "./v3/connectionController.js";
3
3
  export * as v2 from "./v2/index.js";
4
- export type { ByteStream, IncomingStream, JsonObject, JsonPrimitive, JsonValue, OperationOptions, RpcPeer, RpcResult, SessionErrorCode, StreamOpenOptions, UnreliableMessageChannel, UnreliableMessageSendOptions, UnreliableMessageSendResult, SessionTermination, Session, } from "./public/contract.js";
4
+ export type { ByteStream, IncomingStream, JsonObject, JsonPrimitive, JsonValue, OperationOptions, RpcPeer, RpcResult, SessionErrorCode, UnreliableMessageErrorCode, StreamOpenOptions, UnreliableMessageChannel, UnreliableMessageSendOptions, UnreliableMessageSendResult, SessionTermination, Session, } from "./public/contract.js";
5
5
  export { SessionError, UnreliableMessageError } from "./public/contract.js";
6
6
  export { createStreamMetadata, StreamMetadataError } from "./public/streamMetadata.js";
7
7
  export type { StreamMetadata } from "./public/streamMetadata.js";
8
8
  export { HandlerRegistrationError, StreamHandlers, } from "./public/streamHandlers.js";
9
9
  export type { StreamHandler, StreamHandlerOptions, } from "./public/streamHandlers.js";
10
- export { ArtifactHandleV3 as Artifact, ArtifactHandleV3, ArtifactParseErrorV3 as ArtifactError, ArtifactParseErrorV3, createArtifactLeaseV3 as createArtifactLease, createArtifactLeaseV3, parseArtifactV3 as parseArtifact, parseArtifactV3, } from "./v3/publicApi.js";
11
- export type { ArtifactParseErrorCodeV3 as ArtifactErrorCode, ArtifactParseErrorCodeV3, } from "./v3/publicApi.js";
12
- export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3, ArtifactLeaseV3Error as ArtifactLeaseError, ArtifactLeaseV3Error, } from "./v3/artifactLease.js";
13
- export type { ArtifactSourceResultV3 as ArtifactSourceResult, ArtifactSourceResultV3, ArtifactSourceV3 as ArtifactSource, ArtifactSourceV3, ConnectionControllerFailureV3 as ConnectionControllerFailure, ConnectionControllerFailureV3, ConnectionControllerSnapshotV3, ConnectionControllerStateV3 as ConnectionState, ConnectionControllerStateV3, ConnectionControllerV3, } from "./v3/connectionController.js";
10
+ export { ArtifactHandleV3 as Artifact, ArtifactParseErrorV3 as ArtifactError, createArtifactLeaseV3 as createArtifactLease, parseArtifactV3 as parseArtifact, } from "./v3/publicApi.js";
11
+ export type { ArtifactParseErrorCodeV3 as ArtifactErrorCode, } from "./v3/publicApi.js";
12
+ /** @deprecated Use Artifact, ArtifactError, createArtifactLease, and parseArtifact. */
13
+ export { ArtifactHandleV3, ArtifactParseErrorV3, createArtifactLeaseV3, parseArtifactV3, } from "./v3/publicApi.js";
14
+ /** @deprecated Use ArtifactErrorCode. */
15
+ export type { ArtifactParseErrorCodeV3 } from "./v3/publicApi.js";
16
+ export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3Error as ArtifactLeaseError, } from "./v3/artifactLease.js";
17
+ /** @deprecated Use ArtifactLease and ArtifactLeaseError. */
18
+ export { ArtifactLeaseV3, ArtifactLeaseV3Error } from "./v3/artifactLease.js";
19
+ export type { ArtifactSourceResultV3 as ArtifactSourceResult, ArtifactSourceV3 as ArtifactSource, ConnectionDiagnosticV3 as ConnectionDiagnostic, ConnectionControllerFailureV3 as ConnectionControllerFailure, ConnectionControllerStateV3 as ConnectionState, } from "./v3/connectionController.js";
20
+ /** @deprecated Use the corresponding unversioned controller types. */
21
+ export type { ArtifactSourceResultV3, ArtifactSourceV3, ConnectionDiagnosticV3, ConnectionControllerFailureV3, ConnectionControllerSnapshotV3, ConnectionControllerStateV3, ConnectionControllerV3, } from "./v3/connectionController.js";
22
+ export { connectionDiagnosticV3 as connectionDiagnostic } from "./v3/connectionController.js";
23
+ /** @deprecated Use connectionDiagnostic. */
24
+ export { connectionDiagnosticV3 } from "./v3/connectionController.js";
14
25
  export type ConnectionController = CoreConnectionControllerV3<PublicSession>;
15
26
  export type ConnectionSnapshot = CoreConnectionControllerSnapshotV3<PublicSession>;
16
27
  export type ConnectionControllerOptions = Readonly<{
17
28
  maximumAttempts?: number;
18
29
  }>;
19
- export { ConnectionControllerV3Error as ConnectionControllerError, ConnectionControllerV3Error, } from "./v3/connectionController.js";
20
- export { ConnectErrorV3 as ConnectError, ConnectErrorV3 } from "./v3/security.js";
21
- export type { PublicConnectErrorCodeV3 as ConnectErrorCode, PublicConnectErrorCodeV3, RetryDispositionV3 as RetryDisposition, RetryDispositionV3, } from "./v3/security.js";
30
+ export { ConnectionControllerV3Error as ConnectionControllerError, } from "./v3/connectionController.js";
31
+ /** @deprecated Use ConnectionControllerError. */
32
+ export { ConnectionControllerV3Error } from "./v3/connectionController.js";
33
+ export { ConnectErrorV3 as ConnectError } from "./v3/security.js";
34
+ /** @deprecated Use ConnectError. */
35
+ export { ConnectErrorV3 } from "./v3/security.js";
36
+ export type { PublicConnectErrorCodeV3 as ConnectErrorCode, RetryDispositionV3 as RetryDisposition, } from "./v3/security.js";
37
+ /** @deprecated Use ConnectErrorCode and RetryDisposition. */
38
+ export type { PublicConnectErrorCodeV3, RetryDispositionV3 } from "./v3/security.js";
package/dist/facade.js CHANGED
@@ -2,7 +2,18 @@ export * as v2 from "./v2/index.js";
2
2
  export { SessionError, UnreliableMessageError } from "./public/contract.js";
3
3
  export { createStreamMetadata, StreamMetadataError } from "./public/streamMetadata.js";
4
4
  export { HandlerRegistrationError, StreamHandlers, } from "./public/streamHandlers.js";
5
- export { ArtifactHandleV3 as Artifact, ArtifactHandleV3, ArtifactParseErrorV3 as ArtifactError, ArtifactParseErrorV3, createArtifactLeaseV3 as createArtifactLease, createArtifactLeaseV3, parseArtifactV3 as parseArtifact, parseArtifactV3, } from "./v3/publicApi.js";
6
- export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3, ArtifactLeaseV3Error as ArtifactLeaseError, ArtifactLeaseV3Error, } from "./v3/artifactLease.js";
7
- export { ConnectionControllerV3Error as ConnectionControllerError, ConnectionControllerV3Error, } from "./v3/connectionController.js";
8
- export { ConnectErrorV3 as ConnectError, ConnectErrorV3 } from "./v3/security.js";
5
+ export { ArtifactHandleV3 as Artifact, ArtifactParseErrorV3 as ArtifactError, createArtifactLeaseV3 as createArtifactLease, parseArtifactV3 as parseArtifact, } from "./v3/publicApi.js";
6
+ /** @deprecated Use Artifact, ArtifactError, createArtifactLease, and parseArtifact. */
7
+ export { ArtifactHandleV3, ArtifactParseErrorV3, createArtifactLeaseV3, parseArtifactV3, } from "./v3/publicApi.js";
8
+ export { ArtifactLeaseV3 as ArtifactLease, ArtifactLeaseV3Error as ArtifactLeaseError, } from "./v3/artifactLease.js";
9
+ /** @deprecated Use ArtifactLease and ArtifactLeaseError. */
10
+ export { ArtifactLeaseV3, ArtifactLeaseV3Error } from "./v3/artifactLease.js";
11
+ export { connectionDiagnosticV3 as connectionDiagnostic } from "./v3/connectionController.js";
12
+ /** @deprecated Use connectionDiagnostic. */
13
+ export { connectionDiagnosticV3 } from "./v3/connectionController.js";
14
+ export { ConnectionControllerV3Error as ConnectionControllerError, } from "./v3/connectionController.js";
15
+ /** @deprecated Use ConnectionControllerError. */
16
+ export { ConnectionControllerV3Error } from "./v3/connectionController.js";
17
+ export { ConnectErrorV3 as ConnectError } from "./v3/security.js";
18
+ /** @deprecated Use ConnectError. */
19
+ export { ConnectErrorV3 } from "./v3/security.js";
@@ -1,5 +1,7 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { createPrivateKey, createPublicKey, X509Certificate } from "node:crypto";
3
+ import { spawn } from "node:child_process";
4
+ import { fileURLToPath } from "node:url";
3
5
  import { createStreamMetadata, } from "../public/streamMetadata.js";
4
6
  import { createAcceptor, RPCHandlers, SessionHandlers } from "../node/acceptor.js";
5
7
  import { createTunnelRuntime } from "../node/tunnelRuntime.js";
@@ -8,7 +10,14 @@ import { parseArtifact } from "../public/artifact.js";
8
10
  import { connect } from "../node/connectSession.js";
9
11
  import { Issuer, createEndpointSet } from "../node/controlplane.js";
10
12
  import { SessionError } from "../public/contract.js";
13
+ import { createAcceptorV3 } from "../node/acceptorV3.js";
14
+ import { createTunnelRuntimeV3 } from "../node/tunnelRuntimeV3.js";
15
+ import { verifyTunnelAuthorizationGrantV3 } from "../node/runtimeAuthorizationV3.js";
16
+ import { connectV3 } from "../node/connectSessionV3.js";
17
+ import { createArtifactLeaseV3, parseArtifactV3 } from "../v3/publicApi.js";
18
+ import { SessionHandlersV3 } from "../node/acceptor.js";
11
19
  const RUNTIME = "node-typescript";
20
+ const PROTOCOL = process.env.FLOWERSEC_PARITY_PROTOCOL ?? "v2";
12
21
  const ORIGIN = process.env.FLOWERSEC_PARITY_ORIGIN ?? "https://client.example";
13
22
  const ECHO_RPC = 7001;
14
23
  const NOTIFY_RPC = 7002;
@@ -64,7 +73,9 @@ class ExecutedCases {
64
73
  }
65
74
  function createHandlers(path) {
66
75
  const rpcHandlers = new RPCHandlers();
67
- const sessionHandlers = new SessionHandlers({ maxConcurrentStreams: 16 });
76
+ const sessionHandlers = PROTOCOL === "v3"
77
+ ? new SessionHandlersV3({ maxConcurrentStreams: 16 })
78
+ : new SessionHandlers({ maxConcurrentStreams: 16 });
68
79
  const notifications = new SignalQueue();
69
80
  const activeStreams = { value: 0 };
70
81
  const executed = new ExecutedCases();
@@ -334,7 +345,29 @@ async function connectArtifact(artifactJSON, relay, rpcHandlers) {
334
345
  rpcHandlers,
335
346
  });
336
347
  }
348
+ async function connectArtifactV3(artifactJSON, relay, rpcHandlers) {
349
+ return await connectV3(createArtifactLeaseV3(parseArtifactV3(artifactJSON), async () => undefined), { origin: relay.origin, roots: relay.trust_pem, rpcHandlers });
350
+ }
351
+ async function issueV3(input) {
352
+ const goRoot = fileURLToPath(new URL("../../../flowersec-go", import.meta.url));
353
+ const child = spawn("go", ["run", "./internal/cmd/parity-artifact-issuer"], {
354
+ cwd: goRoot,
355
+ env: { ...process.env, FLOWERSEC_SERVER_PARITY_PEER: "1" },
356
+ stdio: ["pipe", "pipe", "pipe"],
357
+ });
358
+ child.stdin.end(`${JSON.stringify(input)}\n`);
359
+ const stdout = [];
360
+ const stderr = [];
361
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
362
+ child.stderr.on("data", (chunk) => stderr.push(chunk));
363
+ const code = await new Promise((resolve) => child.once("exit", resolve));
364
+ if (code !== 0)
365
+ throw new Error(`v3 parity issuer failed: ${Buffer.concat(stderr).toString("utf8").trim()}`);
366
+ return JSON.parse(Buffer.concat(stdout).toString("utf8"));
367
+ }
337
368
  async function runServer(tls, carrier) {
369
+ if (PROTOCOL === "v3")
370
+ return await runServerV3(tls, carrier);
338
371
  const issuedByLookup = new Map();
339
372
  const state = createHandlers("direct");
340
373
  const acceptor = await createAcceptor({
@@ -391,6 +424,50 @@ async function runServer(tls, carrier) {
391
424
  await acceptor.close();
392
425
  }
393
426
  }
427
+ async function runServerV3(tls, carrier) {
428
+ const state = createHandlers("direct");
429
+ let trustedArtifact;
430
+ const acceptor = await createAcceptorV3({
431
+ listeners: [directListenerOptionsV3(carrier, tls)],
432
+ maxInboundStreams: 16,
433
+ authorize: async () => trustedArtifact === undefined
434
+ ? { accepted: false, retryable: false, reason: "invalid_credential" }
435
+ : { accepted: true, artifact: trustedArtifact },
436
+ resolveHandlers: () => state.sessionHandlers,
437
+ });
438
+ try {
439
+ const address = acceptor.addresses()[0];
440
+ if (address === undefined)
441
+ throw new Error(`direct ${carrier} listener did not bind`);
442
+ const issued = await issueV3({ mode: "direct", endpoint: endpointURLV3(carrier, "direct", address.port) });
443
+ if (issued.artifact_json === undefined)
444
+ throw new Error("v3 parity issuer omitted direct artifact");
445
+ trustedArtifact = parseArtifactV3(issued.artifact_json);
446
+ const accepting = acceptor.accept();
447
+ writeJSON({
448
+ type: "ready", runtime: RUNTIME, carrier, path: "direct",
449
+ artifact_json: issued.artifact_json, trust_pem: tls.tls.root_certificate_pem, origin: ORIGIN,
450
+ });
451
+ const accepted = await accepting;
452
+ state.executed.record("admission");
453
+ const serving = accepted.serve().catch((error) => error);
454
+ if (process.env.FLOWERSEC_PARITY_CLIENT_PROFILE !== undefined) {
455
+ await externalServer(accepted.session, state);
456
+ }
457
+ else {
458
+ await exerciseServer(accepted.session, state, "direct", true, carrier);
459
+ }
460
+ await serving;
461
+ await accepted.close().catch(() => undefined);
462
+ if (state.activeStreams.value !== 0)
463
+ throw new Error(`direct server cleanup left ${state.activeStreams.value} streams`);
464
+ state.executed.record("cleanup");
465
+ writeJSON(result("server-result", "direct", state.executed.snapshot(), carrier));
466
+ }
467
+ finally {
468
+ await acceptor.close();
469
+ }
470
+ }
394
471
  async function runClient(input, carrier) {
395
472
  const ready = await input.next();
396
473
  if (ready.type !== "ready" ||
@@ -399,7 +476,9 @@ async function runClient(input, carrier) {
399
476
  ready.artifact_json === "")
400
477
  throw new Error("invalid direct ready message");
401
478
  const state = createHandlers("direct");
402
- const session = await connectArtifact(ready.artifact_json, ready, state.rpcHandlers);
479
+ const session = PROTOCOL === "v3"
480
+ ? await connectArtifactV3(ready.artifact_json, ready, state.rpcHandlers)
481
+ : await connectArtifact(ready.artifact_json, ready, state.rpcHandlers);
403
482
  state.executed.record("admission");
404
483
  await exerciseClient(session, state, "direct", carrier);
405
484
  await session.close().catch((error) => {
@@ -413,6 +492,8 @@ async function runClient(input, carrier) {
413
492
  writeJSON(result("client-result", "direct", state.executed.snapshot(), carrier));
414
493
  }
415
494
  async function runRelay(tls, input, carrier) {
495
+ if (PROTOCOL === "v3")
496
+ return await runRelayV3(tls, input, carrier);
416
497
  const decisions = new Map();
417
498
  const released = new Set();
418
499
  const executed = new ExecutedCases();
@@ -492,7 +573,77 @@ async function runRelay(tls, input, carrier) {
492
573
  released_leases: released.size,
493
574
  });
494
575
  }
576
+ async function runRelayV3(tls, input, carrier) {
577
+ const authorizations = new Map();
578
+ const verificationRecords = new Map();
579
+ const released = new Set();
580
+ const executed = new ExecutedCases();
581
+ const runtime = createTunnelRuntimeV3({
582
+ listeners: [tunnelListenerOptionsV3(carrier, tls)],
583
+ maxInboundStreams: 16,
584
+ maxPendingLegs: 16,
585
+ maxActivePairs: 8,
586
+ authorize: async (request) => {
587
+ const item = authorizations.get(request.lookupKey());
588
+ const artifactJSON = verificationRecords.get(request.lookupKey());
589
+ if (item === undefined || artifactJSON === undefined)
590
+ return { decision: "reject", reason: "invalid_credential" };
591
+ return {
592
+ decision: "allow",
593
+ grant: verifyTunnelAuthorizationGrantV3(request, parseArtifactV3(artifactJSON), {
594
+ leaseId: item.leaseId,
595
+ allowReplacement: item.allowReplacement,
596
+ }),
597
+ };
598
+ },
599
+ release: (leaseId) => { released.add(leaseId); },
600
+ });
601
+ try {
602
+ await runtime.start();
603
+ const address = runtime.addresses()[0];
604
+ if (address === undefined)
605
+ throw new Error(`tunnel ${carrier} listener did not bind`);
606
+ writeJSON({
607
+ type: "relay-ready", runtime: RUNTIME, carrier, path: "tunnel",
608
+ endpoint_url: endpointURLV3(carrier, "tunnel", address.port),
609
+ trust_pem: tls.tls.root_certificate_pem,
610
+ trust_roots_der: [tls.tls.root_certificate_der_base64],
611
+ server_certificate_der: tls.tls.leaf_certificate_der_base64,
612
+ origin: ORIGIN,
613
+ });
614
+ const configure = await input.next();
615
+ if (configure.type !== "configure" || !Array.isArray(configure.authorizations) ||
616
+ configure.authorizations.length === 0 || configure.verification_records === undefined) {
617
+ throw new Error("invalid v3 tunnel relay configuration");
618
+ }
619
+ for (const item of configure.authorizations)
620
+ authorizations.set(item.credentialId, item);
621
+ for (const [credentialID, artifactJSON] of Object.entries(configure.verification_records)) {
622
+ verificationRecords.set(credentialID, artifactJSON);
623
+ }
624
+ const close = await input.next();
625
+ if (close.type !== "close")
626
+ throw new Error("invalid tunnel relay close command");
627
+ executed.record("close");
628
+ }
629
+ finally {
630
+ await runtime.close();
631
+ executed.record("cancel");
632
+ }
633
+ if (released.size !== authorizations.size)
634
+ throw new Error(`relay cleanup released ${released.size} of ${authorizations.size} leases`);
635
+ executed.record("admission", "pairing", "opaque-forwarding");
636
+ if (carrier === "raw-quic")
637
+ executed.record("datagram-forwarding");
638
+ executed.record("cleanup");
639
+ writeJSON({
640
+ type: "relay-result", runtime: RUNTIME, carrier, path: "tunnel", cases: executed.snapshot(),
641
+ observed_plaintext: false, released_leases: released.size,
642
+ });
643
+ }
495
644
  async function runTunnelEndpointB(input, carrier) {
645
+ if (PROTOCOL === "v3")
646
+ return await runTunnelEndpointBV3(input, carrier);
496
647
  const envelope = await input.next();
497
648
  const { topology, relay } = envelope;
498
649
  validateTunnelDimensions(topology, relay, "endpoint_b", carrier);
@@ -539,7 +690,53 @@ async function runTunnelEndpointB(input, carrier) {
539
690
  state.executed.record("cleanup");
540
691
  writeJSON(result("endpoint-b-result", "tunnel", state.executed.snapshot(), carrier));
541
692
  }
693
+ async function runTunnelEndpointBV3(input, carrier) {
694
+ const envelope = await input.next();
695
+ const { topology, relay } = envelope;
696
+ validateTunnelDimensions(topology, relay, "endpoint_b", carrier);
697
+ const issued = await issueV3({ mode: "tunnel", endpoint: relay.endpoint_url, topology_id: topology.id });
698
+ if (issued.endpoint_a_artifact_json === undefined || issued.endpoint_b_artifact_json === undefined ||
699
+ issued.authorizations === undefined || issued.verification_records === undefined) {
700
+ throw new Error("v3 parity issuer omitted tunnel material");
701
+ }
702
+ const ready = {
703
+ type: "endpoint-b-ready", runtime: RUNTIME, carrier, path: "tunnel",
704
+ endpoint_a_artifact_json: issued.endpoint_a_artifact_json,
705
+ endpoint_b_artifact_json: issued.endpoint_b_artifact_json,
706
+ relay, authorizations: issued.authorizations, verification_records: issued.verification_records,
707
+ };
708
+ writeJSON(ready);
709
+ const command = await input.next();
710
+ if (command.type !== "connect")
711
+ throw new Error("endpoint B did not receive connect command");
712
+ const state = createHandlers("tunnel");
713
+ const session = await connectArtifactV3(ready.endpoint_b_artifact_json, relay, state.rpcHandlers);
714
+ state.executed.record("admission");
715
+ if (process.env.FLOWERSEC_PARITY_CLIENT_PROFILE !== undefined) {
716
+ await externalServer(session, state);
717
+ }
718
+ else {
719
+ await exerciseServer(session, state, "tunnel", false, carrier);
720
+ }
721
+ await session.close().catch(() => undefined);
722
+ state.executed.record("close");
723
+ if (state.activeStreams.value !== 0)
724
+ throw new Error(`endpoint B cleanup left ${state.activeStreams.value} streams`);
725
+ state.executed.record("cleanup");
726
+ writeJSON(result("endpoint-b-result", "tunnel", state.executed.snapshot(), carrier));
727
+ }
542
728
  async function externalServer(session, state) {
729
+ const cancellation = new AbortController();
730
+ cancellation.abort();
731
+ try {
732
+ await session.waitTermination({ signal: cancellation.signal });
733
+ }
734
+ catch (error) {
735
+ if (!(error instanceof SessionError) || error.code !== "canceled")
736
+ throw error;
737
+ }
738
+ state.executed.record("cancel");
739
+ await session.probeLiveness();
543
740
  await session.waitTermination();
544
741
  state.executed.record("close");
545
742
  }
@@ -553,7 +750,9 @@ async function runTunnelEndpointA(input, carrier) {
553
750
  ready.endpoint_a_artifact_json === "")
554
751
  throw new Error("invalid endpoint B ready message");
555
752
  const state = createHandlers("tunnel");
556
- const session = await connectArtifact(ready.endpoint_a_artifact_json, ready.relay, state.rpcHandlers);
753
+ const session = PROTOCOL === "v3"
754
+ ? await connectArtifactV3(ready.endpoint_a_artifact_json, ready.relay, state.rpcHandlers)
755
+ : await connectArtifact(ready.endpoint_a_artifact_json, ready.relay, state.rpcHandlers);
557
756
  state.executed.record("admission");
558
757
  await exerciseClient(session, state, "tunnel", carrier);
559
758
  await session.close().catch(() => undefined);
@@ -624,6 +823,11 @@ function endpointURL(carrier, path, port) {
624
823
  ? `wss://localhost:${port}/flowersec/v2/${path}`
625
824
  : `quic://127.0.0.1:${port}`;
626
825
  }
826
+ function endpointURLV3(carrier, path, port) {
827
+ return carrier === "websocket"
828
+ ? `wss://localhost:${port}/flowersec/v3/${path}`
829
+ : `quic://127.0.0.1:${port}`;
830
+ }
627
831
  function directListenerOptions(carrier, tls) {
628
832
  const material = { certificate: tls.tls.certificate_chain_pem, privateKey: tls.tls.private_key_pem };
629
833
  return carrier === "websocket"
@@ -636,6 +840,18 @@ function tunnelListenerOptions(carrier, tls) {
636
840
  ? { carrier, host: "127.0.0.1", port: 0, tls: material, allowedOrigins: [ORIGIN] }
637
841
  : { carrier: "raw_quic", host: "127.0.0.1", port: 0, tls: material };
638
842
  }
843
+ function directListenerOptionsV3(carrier, tls) {
844
+ const material = { certificate: tls.tls.certificate_chain_pem, privateKey: tls.tls.private_key_pem };
845
+ return carrier === "websocket"
846
+ ? { carrier, path: "direct", host: "127.0.0.1", port: 0, tls: material, allowedOrigins: [ORIGIN] }
847
+ : { carrier: "raw_quic", path: "direct", host: "127.0.0.1", port: 0, tls: material };
848
+ }
849
+ function tunnelListenerOptionsV3(carrier, tls) {
850
+ const material = { certificate: tls.tls.certificate_chain_pem, privateKey: tls.tls.private_key_pem };
851
+ return carrier === "websocket"
852
+ ? { carrier, host: "127.0.0.1", port: 0, tls: material, allowedOrigins: [ORIGIN] }
853
+ : { carrier: "raw_quic", host: "127.0.0.1", port: 0, tls: material };
854
+ }
639
855
  async function main() {
640
856
  const { role, carrier } = parseArguments(process.argv.slice(2));
641
857
  switch (role) {
@@ -1,16 +1,32 @@
1
- export { AcceptedSessionV3 as AcceptedSession, AcceptedSessionV3, AcceptorV3 as Acceptor, AcceptorV3, createAcceptorV3 as createAcceptor, createAcceptorV3, } from "./acceptorV3.js";
2
- export type { AcceptorListenerV3 as AcceptorListener, AcceptorListenerV3, AcceptorAuthorizationDecisionV3 as AcceptorAuthorizationDecision, AcceptorAuthorizationDecisionV3, AcceptorAuthorizerV3 as AcceptorAuthorizer, AcceptorAuthorizerV3, AcceptorOptionsV3 as AcceptorOptions, AcceptorOptionsV3, } from "./acceptorV3.js";
3
- export { TunnelRuntimeV3 as TunnelRuntime, TunnelRuntimeV3, createTunnelRuntimeV3 as createTunnelRuntime, createTunnelRuntimeV3, } from "./tunnelRuntimeV3.js";
4
- export type { TunnelAuthorizationDecisionV3 as TunnelAuthorizationDecision, TunnelAuthorizationDecisionV3, TunnelRuntimeListenerV3 as TunnelRuntimeListener, TunnelRuntimeListenerV3, TunnelRuntimeOptionsV3 as TunnelRuntimeOptions, TunnelRuntimeOptionsV3, } from "./tunnelRuntimeV3.js";
5
- export { RuntimeAuthorizationRequestV3 as RuntimeAuthorizationRequest, RuntimeAuthorizationRequestV3, TunnelAuthorizationGrantV3 as TunnelAuthorizationGrant, TunnelAuthorizationGrantV3, verifyTunnelAuthorizationGrantV3 as verifyTunnelAuthorizationGrant, verifyTunnelAuthorizationGrantV3, } from "./runtimeAuthorizationV3.js";
6
- export type { TunnelAuthorizationGrantOptionsV3 as TunnelAuthorizationGrantOptions, TunnelAuthorizationGrantOptionsV3, } from "./runtimeAuthorizationV3.js";
1
+ export { AcceptedSessionV3 as AcceptedSession, AcceptorV3 as Acceptor, createAcceptorV3 as createAcceptor, } from "./acceptorV3.js";
2
+ export type { AcceptorListenerV3 as AcceptorListener, AcceptorAuthorizationDecisionV3 as AcceptorAuthorizationDecision, AcceptorAuthorizerV3 as AcceptorAuthorizer, AcceptorOptionsV3 as AcceptorOptions, } from "./acceptorV3.js";
3
+ /** @deprecated Use the corresponding unversioned acceptor exports. */
4
+ export { AcceptedSessionV3, AcceptorV3, createAcceptorV3 } from "./acceptorV3.js";
5
+ /** @deprecated Use the corresponding unversioned acceptor types. */
6
+ export type { AcceptorListenerV3, AcceptorAuthorizationDecisionV3, AcceptorAuthorizerV3, AcceptorOptionsV3, } from "./acceptorV3.js";
7
+ export { TunnelRuntimeV3 as TunnelRuntime, createTunnelRuntimeV3 as createTunnelRuntime, } from "./tunnelRuntimeV3.js";
8
+ export type { TunnelAuthorizationDecisionV3 as TunnelAuthorizationDecision, TunnelRuntimeListenerV3 as TunnelRuntimeListener, TunnelRuntimeOptionsV3 as TunnelRuntimeOptions, } from "./tunnelRuntimeV3.js";
9
+ /** @deprecated Use TunnelRuntime and createTunnelRuntime. */
10
+ export { TunnelRuntimeV3, createTunnelRuntimeV3 } from "./tunnelRuntimeV3.js";
11
+ /** @deprecated Use the corresponding unversioned tunnel runtime types. */
12
+ export type { TunnelAuthorizationDecisionV3, TunnelRuntimeListenerV3, TunnelRuntimeOptionsV3, } from "./tunnelRuntimeV3.js";
13
+ export { RuntimeAuthorizationRequestV3 as RuntimeAuthorizationRequest, TunnelAuthorizationGrantV3 as TunnelAuthorizationGrant, verifyTunnelAuthorizationGrantV3 as verifyTunnelAuthorizationGrant, } from "./runtimeAuthorizationV3.js";
14
+ export type { TunnelAuthorizationGrantOptionsV3 as TunnelAuthorizationGrantOptions, } from "./runtimeAuthorizationV3.js";
15
+ /** @deprecated Use the corresponding unversioned runtime-authorization exports. */
16
+ export { RuntimeAuthorizationRequestV3, TunnelAuthorizationGrantV3, verifyTunnelAuthorizationGrantV3, } from "./runtimeAuthorizationV3.js";
17
+ /** @deprecated Use TunnelAuthorizationGrantOptions. */
18
+ export type { TunnelAuthorizationGrantOptionsV3 } from "./runtimeAuthorizationV3.js";
7
19
  export * from "../facade.js";
8
20
  export * as v2 from "./v2.js";
9
- import type { NodeTLSRootsV3 } from "./connectSessionV3.js";
10
- export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
11
- export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, ConnectionControllerOptionsV3, NodeTLSRootsV3, SessionOptionsV3 as SessionOptions, SessionOptionsV3, } from "./connectSessionV3.js";
21
+ import type { NodeTLSRootsV3 as NodeTLSRoots } from "./connectSessionV3.js";
22
+ export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, } from "./connectSessionV3.js";
23
+ /** @deprecated Use connect and createConnectionController. */
24
+ export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
25
+ export type { ConnectionControllerOptionsV3 as ConnectionControllerOptions, NodeTLSRootsV3 as NodeTLSRoots, SessionOptionsV3 as SessionOptions, } from "./connectSessionV3.js";
26
+ /** @deprecated Use ConnectionControllerOptions, SessionTLSOptions, and SessionOptions. */
27
+ export type { ConnectionControllerOptionsV3, NodeTLSRootsV3, SessionOptionsV3, } from "./connectSessionV3.js";
12
28
  export type SessionTLSOptions = Readonly<{
13
- ca?: NodeTLSRootsV3;
29
+ ca?: NodeTLSRoots;
14
30
  }>;
15
31
  export { HandlerRegistrationError, RPCHandlers, SessionHandlersV3 as SessionHandlers, } from "./acceptor.js";
16
32
  export type { NotificationHandler, RPCHandler, RPCHandlerResult, SessionHandlerOptions, } from "./acceptor.js";
@@ -1,7 +1,15 @@
1
- export { AcceptedSessionV3 as AcceptedSession, AcceptedSessionV3, AcceptorV3 as Acceptor, AcceptorV3, createAcceptorV3 as createAcceptor, createAcceptorV3, } from "./acceptorV3.js";
2
- export { TunnelRuntimeV3 as TunnelRuntime, TunnelRuntimeV3, createTunnelRuntimeV3 as createTunnelRuntime, createTunnelRuntimeV3, } from "./tunnelRuntimeV3.js";
3
- export { RuntimeAuthorizationRequestV3 as RuntimeAuthorizationRequest, RuntimeAuthorizationRequestV3, TunnelAuthorizationGrantV3 as TunnelAuthorizationGrant, TunnelAuthorizationGrantV3, verifyTunnelAuthorizationGrantV3 as verifyTunnelAuthorizationGrant, verifyTunnelAuthorizationGrantV3, } from "./runtimeAuthorizationV3.js";
1
+ export { AcceptedSessionV3 as AcceptedSession, AcceptorV3 as Acceptor, createAcceptorV3 as createAcceptor, } from "./acceptorV3.js";
2
+ /** @deprecated Use the corresponding unversioned acceptor exports. */
3
+ export { AcceptedSessionV3, AcceptorV3, createAcceptorV3 } from "./acceptorV3.js";
4
+ export { TunnelRuntimeV3 as TunnelRuntime, createTunnelRuntimeV3 as createTunnelRuntime, } from "./tunnelRuntimeV3.js";
5
+ /** @deprecated Use TunnelRuntime and createTunnelRuntime. */
6
+ export { TunnelRuntimeV3, createTunnelRuntimeV3 } from "./tunnelRuntimeV3.js";
7
+ export { RuntimeAuthorizationRequestV3 as RuntimeAuthorizationRequest, TunnelAuthorizationGrantV3 as TunnelAuthorizationGrant, verifyTunnelAuthorizationGrantV3 as verifyTunnelAuthorizationGrant, } from "./runtimeAuthorizationV3.js";
8
+ /** @deprecated Use the corresponding unversioned runtime-authorization exports. */
9
+ export { RuntimeAuthorizationRequestV3, TunnelAuthorizationGrantV3, verifyTunnelAuthorizationGrantV3, } from "./runtimeAuthorizationV3.js";
4
10
  export * from "../facade.js";
5
11
  export * as v2 from "./v2.js";
6
- export { connectV3 as connect, connectV3, createConnectionControllerV3 as createConnectionController, createConnectionControllerV3, } from "./connectSessionV3.js";
12
+ export { connectV3 as connect, createConnectionControllerV3 as createConnectionController, } from "./connectSessionV3.js";
13
+ /** @deprecated Use connect and createConnectionController. */
14
+ export { connectV3, createConnectionControllerV3 } from "./connectSessionV3.js";
7
15
  export { HandlerRegistrationError, RPCHandlers, SessionHandlersV3 as SessionHandlers, } from "./acceptor.js";
@@ -18,14 +18,17 @@ export interface UnreliableMessageChannel {
18
18
  send(message: Uint8Array, options: UnreliableMessageSendOptions): Promise<UnreliableMessageSendResult>;
19
19
  receive(options?: OperationOptions): Promise<Uint8Array>;
20
20
  }
21
+ export type UnreliableMessageErrorCode = "unavailable" | "invalid_message" | "too_large" | "canceled" | "closed" | "operation_failed";
21
22
  export declare class UnreliableMessageError extends Error {
22
- readonly code: "invalid_message" | "closed" | "operation_failed";
23
- constructor(code: "invalid_message" | "closed" | "operation_failed");
23
+ readonly code: UnreliableMessageErrorCode;
24
+ constructor(code: UnreliableMessageErrorCode);
24
25
  }
25
26
  export type StreamOpenOptions = OperationOptions & Readonly<{
26
27
  metadata?: StreamMetadata;
27
28
  }>;
28
- export type SessionErrorCode = "canceled" | "timeout" | "closed" | "going_away" | "resource_exhausted" | "stream_rejected" | "stream_reset" | "rekey_failed" | "liveness_failed" | "unreliable_unavailable" | "unreliable_too_large" | "unreliable_dropped" | "operation_failed";
29
+ /** @deprecated Unreliable-message operations use UnreliableMessageErrorCode. */
30
+ export type LegacyUnreliableSessionErrorCode = "unreliable_unavailable" | "unreliable_too_large" | "unreliable_dropped";
31
+ export type SessionErrorCode = "canceled" | "timeout" | "closed" | "going_away" | "resource_exhausted" | "stream_rejected" | "stream_reset" | "rekey_failed" | "liveness_failed" | LegacyUnreliableSessionErrorCode | "operation_failed";
29
32
  /** A closed, carrier-neutral session failure with no internal cause or peer detail. */
30
33
  export declare class SessionError extends Error {
31
34
  readonly code: SessionErrorCode;
@@ -34,10 +34,12 @@ class InternalUnreliableMessageChannelV2 {
34
34
  }
35
35
  async send(message, options) {
36
36
  throwIfAborted(options.signal);
37
- if (!(message instanceof Uint8Array) || message.byteLength < 1 ||
38
- message.byteLength > UNRELIABLE_MESSAGE_MAX_PLAINTEXT_BYTES_V2) {
37
+ if (!(message instanceof Uint8Array) || message.byteLength < 1) {
39
38
  throw new UnreliableMessageError("invalid_message");
40
39
  }
40
+ if (message.byteLength > UNRELIABLE_MESSAGE_MAX_PLAINTEXT_BYTES_V2) {
41
+ throw new UnreliableMessageError("too_large");
42
+ }
41
43
  const payload = message.slice();
42
44
  const expiresAt = requireFutureExpiry(options.expiresAtUnixMs, this.now());
43
45
  if (expiresAt === undefined)
@@ -70,8 +72,8 @@ class InternalUnreliableMessageChannelV2 {
70
72
  });
71
73
  }
72
74
  catch (error) {
73
- if (error instanceof DOMException && error.name === "AbortError")
74
- throw error;
75
+ if (isAbortError(error))
76
+ throw new UnreliableMessageError("canceled");
75
77
  throw new UnreliableMessageError("operation_failed");
76
78
  }
77
79
  finally {
@@ -87,7 +89,7 @@ class InternalUnreliableMessageChannelV2 {
87
89
  }
88
90
  catch (error) {
89
91
  if (isAbortError(error))
90
- throw error;
92
+ throw new UnreliableMessageError("canceled");
91
93
  throw new UnreliableMessageError("closed");
92
94
  }
93
95
  const decoded = decodeHeader(wire);
@@ -219,7 +221,7 @@ function requireFutureExpiry(value, now) {
219
221
  }
220
222
  function throwIfAborted(signal) {
221
223
  if (signal?.aborted === true)
222
- throw new DOMException("operation aborted", "AbortError");
224
+ throw new UnreliableMessageError("canceled");
223
225
  }
224
226
  function isAbortError(error) {
225
227
  return error instanceof DOMException && error.name === "AbortError" ||