@cotal-ai/core 0.15.0 → 0.17.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.
- package/dist/acls.d.ts +21 -0
- package/dist/acls.d.ts.map +1 -1
- package/dist/acls.js +75 -12
- package/dist/acls.js.map +1 -1
- package/dist/artifact.d.ts +45 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +82 -0
- package/dist/artifact.js.map +1 -0
- package/dist/backup-config.d.ts +9 -2
- package/dist/backup-config.d.ts.map +1 -1
- package/dist/backup-config.js +6 -2
- package/dist/backup-config.js.map +1 -1
- package/dist/broker-tls.d.ts +70 -0
- package/dist/broker-tls.d.ts.map +1 -0
- package/dist/broker-tls.js +195 -0
- package/dist/broker-tls.js.map +1 -0
- package/dist/canonical.d.ts +14 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +21 -1
- package/dist/canonical.js.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +18 -9
- package/dist/channels.js.map +1 -1
- package/dist/endpoint-action.d.ts +108 -3
- package/dist/endpoint-action.d.ts.map +1 -1
- package/dist/endpoint-action.js +209 -20
- package/dist/endpoint-action.js.map +1 -1
- package/dist/endpoint-binding.d.ts +138 -8
- package/dist/endpoint-binding.d.ts.map +1 -1
- package/dist/endpoint-binding.js +233 -26
- package/dist/endpoint-binding.js.map +1 -1
- package/dist/endpoint-contract-store.d.ts +24 -1
- package/dist/endpoint-contract-store.d.ts.map +1 -1
- package/dist/endpoint-contract-store.js +79 -27
- package/dist/endpoint-contract-store.js.map +1 -1
- package/dist/endpoint-envelope.d.ts +15 -37
- package/dist/endpoint-envelope.d.ts.map +1 -1
- package/dist/endpoint-envelope.js +93 -44
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-error.d.ts +33 -0
- package/dist/endpoint-error.d.ts.map +1 -0
- package/dist/endpoint-error.js +34 -0
- package/dist/endpoint-error.js.map +1 -0
- package/dist/endpoint-grants.d.ts +48 -7
- package/dist/endpoint-grants.d.ts.map +1 -1
- package/dist/endpoint-grants.js +97 -9
- package/dist/endpoint-grants.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +138 -0
- package/dist/endpoint-invoke.d.ts.map +1 -0
- package/dist/endpoint-invoke.js +340 -0
- package/dist/endpoint-invoke.js.map +1 -0
- package/dist/endpoint-records.d.ts.map +1 -1
- package/dist/endpoint-records.js +17 -0
- package/dist/endpoint-records.js.map +1 -1
- package/dist/endpoint-serve-kv.d.ts +97 -0
- package/dist/endpoint-serve-kv.d.ts.map +1 -0
- package/dist/endpoint-serve-kv.js +336 -0
- package/dist/endpoint-serve-kv.js.map +1 -0
- package/dist/endpoint-serve.d.ts.map +1 -1
- package/dist/endpoint-serve.js +21 -1
- package/dist/endpoint-serve.js.map +1 -1
- package/dist/endpoint-service.d.ts +1 -1
- package/dist/endpoint-service.d.ts.map +1 -1
- package/dist/endpoint-service.js +34 -10
- package/dist/endpoint-service.js.map +1 -1
- package/dist/endpoint-session-rail.d.ts +127 -0
- package/dist/endpoint-session-rail.d.ts.map +1 -0
- package/dist/endpoint-session-rail.js +325 -0
- package/dist/endpoint-session-rail.js.map +1 -0
- package/dist/endpoint-session.d.ts +1 -115
- package/dist/endpoint-session.d.ts.map +1 -1
- package/dist/endpoint-session.js +4 -316
- package/dist/endpoint-session.js.map +1 -1
- package/dist/endpoint-subjects.d.ts +10 -1
- package/dist/endpoint-subjects.d.ts.map +1 -1
- package/dist/endpoint-subjects.js +10 -1
- package/dist/endpoint-subjects.js.map +1 -1
- package/dist/endpoint-verbs.d.ts +1 -2
- package/dist/endpoint-verbs.d.ts.map +1 -1
- package/dist/endpoint-verbs.js +6 -6
- package/dist/endpoint-verbs.js.map +1 -1
- package/dist/endpoint.d.ts +169 -15
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +491 -43
- package/dist/endpoint.js.map +1 -1
- package/dist/identity.d.ts +19 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +12 -0
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/kv-scan.d.ts +105 -0
- package/dist/kv-scan.d.ts.map +1 -0
- package/dist/kv-scan.js +192 -0
- package/dist/kv-scan.js.map +1 -0
- package/dist/lease.d.ts +12 -5
- package/dist/lease.d.ts.map +1 -1
- package/dist/lease.js.map +1 -1
- package/dist/lifecycle-saga.d.ts +174 -0
- package/dist/lifecycle-saga.d.ts.map +1 -0
- package/dist/lifecycle-saga.js +418 -0
- package/dist/lifecycle-saga.js.map +1 -0
- package/dist/lifecycle-state.d.ts +202 -0
- package/dist/lifecycle-state.d.ts.map +1 -0
- package/dist/lifecycle-state.js +347 -0
- package/dist/lifecycle-state.js.map +1 -0
- package/dist/members.d.ts +8 -4
- package/dist/members.d.ts.map +1 -1
- package/dist/members.js +19 -9
- package/dist/members.js.map +1 -1
- package/dist/parts.d.ts +13 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +17 -0
- package/dist/parts.js.map +1 -0
- package/dist/provision.d.ts +106 -9
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +506 -110
- package/dist/provision.js.map +1 -1
- package/dist/schema-profile.d.ts +3 -2
- package/dist/schema-profile.d.ts.map +1 -1
- package/dist/schema-profile.js +242 -18
- package/dist/schema-profile.js.map +1 -1
- package/dist/session-browser.d.ts +15 -0
- package/dist/session-browser.d.ts.map +1 -0
- package/dist/session-browser.js +14 -0
- package/dist/session-browser.js.map +1 -0
- package/dist/session-terminal-frames.d.ts +36 -0
- package/dist/session-terminal-frames.d.ts.map +1 -0
- package/dist/session-terminal-frames.js +149 -0
- package/dist/session-terminal-frames.js.map +1 -0
- package/dist/spaces.d.ts.map +1 -1
- package/dist/spaces.js +6 -1
- package/dist/spaces.js.map +1 -1
- package/dist/streams.d.ts +53 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +135 -8
- package/dist/streams.js.map +1 -1
- package/dist/subjects.d.ts +32 -26
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +38 -25
- package/dist/subjects.js.map +1 -1
- package/dist/types.d.ts +27 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BROWSER-SAFE subset of the §13.6 session composite, for the manager console's session-client
|
|
3
|
+
* bundle (P2 item 6): the rails + the terminal-session frame codec, with ZERO node-only
|
|
4
|
+
* dependencies. The console bundle (esbuild) imports ONLY this subpath
|
|
5
|
+
* (`@cotal-ai/core/session-browser`), NEVER the package root — the root pulls `node:crypto` through
|
|
6
|
+
* the grant mint/verify/redeem + the KV ledger, which a browser has no business bundling.
|
|
7
|
+
*
|
|
8
|
+
* This is a re-export barrel over the two already-split node-free modules; it adds no logic, so the
|
|
9
|
+
* SAME core rail + frame codec that runs on the manager runs in the browser (the point of item 6:
|
|
10
|
+
* a remote holder speaks the identical wire).
|
|
11
|
+
*/
|
|
12
|
+
export { openSessionRail, encodeSessionFrame, parseSessionFrame, assertWindow, SESSION_WINDOW_DEFAULT, SESSION_WINDOW_MAX, type SessionFrame, type SessionRail, type SessionRailOpts, type SessionRole, } from "./endpoint-session-rail.js";
|
|
13
|
+
export { encodeTerminalData, terminalFrameBytes, decodeTerminalFrame, type TerminalFrame, } from "./session-terminal-frames.js";
|
|
14
|
+
export type { SessionGrant } from "./endpoint-session.js";
|
|
15
|
+
//# sourceMappingURL=session-browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-browser.d.ts","sourceRoot":"","sources":["../src/session-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,KAAK,aAAa,GACnB,MAAM,8BAA8B,CAAC;AAEtC,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The BROWSER-SAFE subset of the §13.6 session composite, for the manager console's session-client
|
|
3
|
+
* bundle (P2 item 6): the rails + the terminal-session frame codec, with ZERO node-only
|
|
4
|
+
* dependencies. The console bundle (esbuild) imports ONLY this subpath
|
|
5
|
+
* (`@cotal-ai/core/session-browser`), NEVER the package root — the root pulls `node:crypto` through
|
|
6
|
+
* the grant mint/verify/redeem + the KV ledger, which a browser has no business bundling.
|
|
7
|
+
*
|
|
8
|
+
* This is a re-export barrel over the two already-split node-free modules; it adds no logic, so the
|
|
9
|
+
* SAME core rail + frame codec that runs on the manager runs in the browser (the point of item 6:
|
|
10
|
+
* a remote holder speaks the identical wire).
|
|
11
|
+
*/
|
|
12
|
+
export { openSessionRail, encodeSessionFrame, parseSessionFrame, assertWindow, SESSION_WINDOW_DEFAULT, SESSION_WINDOW_MAX, } from "./endpoint-session-rail.js";
|
|
13
|
+
export { encodeTerminalData, terminalFrameBytes, decodeTerminalFrame, } from "./session-terminal-frames.js";
|
|
14
|
+
//# sourceMappingURL=session-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-browser.js","sourceRoot":"","sources":["../src/session-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,GAKnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,GAEpB,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** One terminal-session frame carried in a §13.6 rail data payload. `end.reason` is an
|
|
2
|
+
* APPLICATION-defined bounded token (the reference manager uses `process-exit` / `closed` /
|
|
3
|
+
* `expired` / `target-despawn` / `manager-restart`); the vocabulary is not fixed by the wire, so
|
|
4
|
+
* a different endpoint can surface its own terminal causes without a core change. */
|
|
5
|
+
export type TerminalFrame = {
|
|
6
|
+
k: "ready";
|
|
7
|
+
} | {
|
|
8
|
+
k: "data";
|
|
9
|
+
b: string;
|
|
10
|
+
} | {
|
|
11
|
+
k: "resize";
|
|
12
|
+
cols: number;
|
|
13
|
+
rows: number;
|
|
14
|
+
} | {
|
|
15
|
+
k: "end";
|
|
16
|
+
reason: string;
|
|
17
|
+
} | {
|
|
18
|
+
k: "drop";
|
|
19
|
+
bytes: number;
|
|
20
|
+
};
|
|
21
|
+
/** Encode raw terminal bytes as a `data` frame (the ruled base64-in-JSON form). Accepts any
|
|
22
|
+
* Uint8Array (a node Buffer is one). FAIL-LOUD on a non-Uint8Array: a STRING (e.g. xterm's onData
|
|
23
|
+
* keystroke, which is a string, not bytes) would `base64Encode` its chars as NaN→0 and silently
|
|
24
|
+
* ship NUL bytes the pty ignores — the browser must UTF-8-encode first. Throwing turns that trap
|
|
25
|
+
* into an immediate error instead of a byte stream that vanishes into the terminal (§13.6). */
|
|
26
|
+
export declare function encodeTerminalData(bytes: Uint8Array): TerminalFrame;
|
|
27
|
+
/** The bytes carried by a decoded `data` frame, as a Uint8Array (a node Buffer is one; the manager
|
|
28
|
+
* bridge decodes utf8 via TextDecoder, the browser writes the bytes straight to xterm). */
|
|
29
|
+
export declare function terminalFrameBytes(frame: Extract<TerminalFrame, {
|
|
30
|
+
k: "data";
|
|
31
|
+
}>): Uint8Array;
|
|
32
|
+
/** Fail-loud closed-schema parse of a terminal-session frame from a rail's opaque `data`. An
|
|
33
|
+
* unknown kind, an extra field, or a malformed value THROWS — the rail turns that into a protocol
|
|
34
|
+
* error, so a corrupt frame can never reach the terminal as a partial write. */
|
|
35
|
+
export declare function decodeTerminalFrame(data: unknown): TerminalFrame;
|
|
36
|
+
//# sourceMappingURL=session-terminal-frames.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-terminal-frames.d.ts","sourceRoot":"","sources":["../src/session-terminal-frames.ts"],"names":[],"mappings":"AA0BA;;;sFAGsF;AACtF,MAAM,MAAM,aAAa,GACrB;IAAE,CAAC,EAAE,OAAO,CAAA;CAAE,GACd;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GACxB;IAAE,CAAC,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,CAAC,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AA+DjC;;;;gGAIgG;AAChG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,aAAa,CAMnE;AAED;4FAC4F;AAC5F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE;IAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,UAAU,CAE3F;AAED;;iFAEiF;AACjF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,CAyBhE"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The §13.6 TERMINAL-SESSION PROFILE: the normative application framing for streaming an
|
|
3
|
+
* interactive terminal (a PTY) over a §13.6 bidirectional session. It rides inside the session
|
|
4
|
+
* rail's opaque `data` field (`SessionFrame` `{t:"f",seq,data}`); the rail owns ordering, the
|
|
5
|
+
* bounded flow window, credits, and its own `close`, and this profile is what a terminal attach
|
|
6
|
+
* puts on top.
|
|
7
|
+
*
|
|
8
|
+
* This is WIRE, not a client concern: a remote holder on another machine (the whole point of the
|
|
9
|
+
* mesh attach — no `127.0.0.1`) MUST speak exactly this codec to drive or watch the terminal, and
|
|
10
|
+
* an extension attaches through the same frames. It therefore lives in core (extensions peer-depend
|
|
11
|
+
* core only) with ZERO dependency on any implementation, as a tightly-bound vocabulary.
|
|
12
|
+
*
|
|
13
|
+
* The ratified session-plane ruling: raw-binary DATA payloads + structured JSON CONTROL frames.
|
|
14
|
+
* The rail encodes every frame as JSON, so raw terminal bytes cannot ride uninterpreted — they take
|
|
15
|
+
* the ruled base64-in-JSON form (`{k:"data",b:<base64>}`), keeping the bytes on the SAME
|
|
16
|
+
* subject/grant as the control frames rather than weakening the ACL to carry a second binary rail.
|
|
17
|
+
* Control frames (`ready`/`resize`/`end`/`drop`) stay structured, bounded, and authenticated.
|
|
18
|
+
*
|
|
19
|
+
* Directions (the rail already scopes who may send on which subject):
|
|
20
|
+
* caller → serving: `ready` (subscribed — replay backlog + stream live), `data` (keystrokes),
|
|
21
|
+
* `resize` (pty geometry).
|
|
22
|
+
* serving → caller: `data` (pty output, incl. the reconstructed backlog), `drop` (a
|
|
23
|
+
* backpressure drop-notice — never a silent loss), `end` (a terminal reason).
|
|
24
|
+
*/
|
|
25
|
+
import { EpEnvelopeError } from "./endpoint-error.js";
|
|
26
|
+
const isRec = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
27
|
+
function invalid(what) {
|
|
28
|
+
throw new EpEnvelopeError("contract-invalid", `${what} (SPEC 13.6 terminal-session frame)`);
|
|
29
|
+
}
|
|
30
|
+
/** Standard base64 (the rail is JSON; base64 is the compact interop form), validated
|
|
31
|
+
* DETERMINISTICALLY by grammar — `Buffer.from(_, "base64")` is lenient (it drops invalid chars),
|
|
32
|
+
* so a garbled byte payload would decode to silent garbage; a strict grammar + length check
|
|
33
|
+
* rejects it LOUD (a protocol error the rail surfaces), never a partial terminal write. */
|
|
34
|
+
const BASE64 = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
35
|
+
/** Ceiling for a single data payload's base64 text (well under the rail's 1 MiB default). */
|
|
36
|
+
const MAX_B64_CHARS = 4 * 1024 * 1024;
|
|
37
|
+
function assertBase64(v) {
|
|
38
|
+
if (typeof v !== "string")
|
|
39
|
+
invalid("data payload is not a string");
|
|
40
|
+
if (v.length > MAX_B64_CHARS)
|
|
41
|
+
invalid(`data payload is ${v.length} base64 chars, over the ${MAX_B64_CHARS} ceiling`);
|
|
42
|
+
if (v.length % 4 !== 0 || !BASE64.test(v))
|
|
43
|
+
invalid("data payload is not valid base64");
|
|
44
|
+
return v;
|
|
45
|
+
}
|
|
46
|
+
/** A bounded terminal-cause token: lowercase kebab, 1..48 chars. Keeps `end.reason` generic
|
|
47
|
+
* (no fixed enum) while refusing an unbounded/hostile string. */
|
|
48
|
+
const REASON = /^[a-z][a-z0-9-]{0,47}$/;
|
|
49
|
+
function assertReason(v) {
|
|
50
|
+
if (typeof v !== "string" || !REASON.test(v))
|
|
51
|
+
invalid(`end reason ${JSON.stringify(v)} is not a bounded kebab-case cause token`);
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
// STANDARD base64 (RFC 4648 §4: the standard alphabet + `=` padding — BYTE-EXACT with node's
|
|
55
|
+
// `Buffer.toString("base64")` / `Buffer.from(_, "base64")`), implemented Buffer-free so the codec
|
|
56
|
+
// runs unchanged in the browser console bundle. NOT url-safe (the rail's JSON carries `+`/`/`
|
|
57
|
+
// fine). Both ends run this same code, so the wire stays identical either way.
|
|
58
|
+
const B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
59
|
+
const B64_INV = (() => { const m = new Int16Array(128).fill(-1); for (let i = 0; i < B64_ALPHABET.length; i++)
|
|
60
|
+
m[B64_ALPHABET.charCodeAt(i)] = i; return m; })();
|
|
61
|
+
function base64Encode(bytes) {
|
|
62
|
+
let out = "";
|
|
63
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
64
|
+
const b0 = bytes[i];
|
|
65
|
+
const has1 = i + 1 < bytes.length, has2 = i + 2 < bytes.length;
|
|
66
|
+
const b1 = has1 ? bytes[i + 1] : 0, b2 = has2 ? bytes[i + 2] : 0;
|
|
67
|
+
out += B64_ALPHABET[b0 >> 2];
|
|
68
|
+
out += B64_ALPHABET[((b0 & 0x03) << 4) | (b1 >> 4)];
|
|
69
|
+
out += has1 ? B64_ALPHABET[((b1 & 0x0f) << 2) | (b2 >> 6)] : "=";
|
|
70
|
+
out += has2 ? B64_ALPHABET[b2 & 0x3f] : "=";
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** Decode STANDARD base64 (already grammar-validated by {@link assertBase64}) to bytes. */
|
|
75
|
+
function base64Decode(s) {
|
|
76
|
+
const clean = s.endsWith("==") ? s.slice(0, -2) : s.endsWith("=") ? s.slice(0, -1) : s;
|
|
77
|
+
const out = new Uint8Array((clean.length * 6) >> 3);
|
|
78
|
+
let o = 0, acc = 0, bits = 0;
|
|
79
|
+
for (let i = 0; i < clean.length; i++) {
|
|
80
|
+
acc = (acc << 6) | (B64_INV[clean.charCodeAt(i)] & 0x3f);
|
|
81
|
+
bits += 6;
|
|
82
|
+
if (bits >= 8) {
|
|
83
|
+
bits -= 8;
|
|
84
|
+
out[o++] = (acc >> bits) & 0xff;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** Encode raw terminal bytes as a `data` frame (the ruled base64-in-JSON form). Accepts any
|
|
90
|
+
* Uint8Array (a node Buffer is one). FAIL-LOUD on a non-Uint8Array: a STRING (e.g. xterm's onData
|
|
91
|
+
* keystroke, which is a string, not bytes) would `base64Encode` its chars as NaN→0 and silently
|
|
92
|
+
* ship NUL bytes the pty ignores — the browser must UTF-8-encode first. Throwing turns that trap
|
|
93
|
+
* into an immediate error instead of a byte stream that vanishes into the terminal (§13.6). */
|
|
94
|
+
export function encodeTerminalData(bytes) {
|
|
95
|
+
// ArrayBuffer.isView is cross-realm safe (a browser/vm-sandbox/node Uint8Array all pass; a string
|
|
96
|
+
// or plain object does not) — unlike `instanceof Uint8Array`, which fails across realms.
|
|
97
|
+
if (!ArrayBuffer.isView(bytes))
|
|
98
|
+
invalid(`encodeTerminalData expects raw bytes (Uint8Array); got ${typeof bytes} — UTF-8-encode a string keystroke first`);
|
|
99
|
+
return { k: "data", b: base64Encode(bytes) };
|
|
100
|
+
}
|
|
101
|
+
/** The bytes carried by a decoded `data` frame, as a Uint8Array (a node Buffer is one; the manager
|
|
102
|
+
* bridge decodes utf8 via TextDecoder, the browser writes the bytes straight to xterm). */
|
|
103
|
+
export function terminalFrameBytes(frame) {
|
|
104
|
+
return base64Decode(frame.b);
|
|
105
|
+
}
|
|
106
|
+
/** Fail-loud closed-schema parse of a terminal-session frame from a rail's opaque `data`. An
|
|
107
|
+
* unknown kind, an extra field, or a malformed value THROWS — the rail turns that into a protocol
|
|
108
|
+
* error, so a corrupt frame can never reach the terminal as a partial write. */
|
|
109
|
+
export function decodeTerminalFrame(data) {
|
|
110
|
+
if (!isRec(data))
|
|
111
|
+
invalid("frame is not an object");
|
|
112
|
+
const o = data;
|
|
113
|
+
switch (o.k) {
|
|
114
|
+
case "ready":
|
|
115
|
+
for (const key of Object.keys(o))
|
|
116
|
+
if (key !== "k")
|
|
117
|
+
invalid(`ready frame carries the unknown field "${key}"`);
|
|
118
|
+
return { k: "ready" };
|
|
119
|
+
case "data":
|
|
120
|
+
for (const key of Object.keys(o))
|
|
121
|
+
if (key !== "k" && key !== "b")
|
|
122
|
+
invalid(`data frame carries the unknown field "${key}"`);
|
|
123
|
+
return { k: "data", b: assertBase64(o.b) };
|
|
124
|
+
case "resize":
|
|
125
|
+
for (const key of Object.keys(o))
|
|
126
|
+
if (key !== "k" && key !== "cols" && key !== "rows")
|
|
127
|
+
invalid(`resize frame carries the unknown field "${key}"`);
|
|
128
|
+
if (!Number.isInteger(o.cols) || o.cols < 1 || o.cols > 100_000)
|
|
129
|
+
invalid("resize cols is not a bounded positive integer");
|
|
130
|
+
if (!Number.isInteger(o.rows) || o.rows < 1 || o.rows > 100_000)
|
|
131
|
+
invalid("resize rows is not a bounded positive integer");
|
|
132
|
+
return { k: "resize", cols: o.cols, rows: o.rows };
|
|
133
|
+
case "end":
|
|
134
|
+
for (const key of Object.keys(o))
|
|
135
|
+
if (key !== "k" && key !== "reason")
|
|
136
|
+
invalid(`end frame carries the unknown field "${key}"`);
|
|
137
|
+
return { k: "end", reason: assertReason(o.reason) };
|
|
138
|
+
case "drop":
|
|
139
|
+
for (const key of Object.keys(o))
|
|
140
|
+
if (key !== "k" && key !== "bytes")
|
|
141
|
+
invalid(`drop frame carries the unknown field "${key}"`);
|
|
142
|
+
if (!Number.isInteger(o.bytes) || o.bytes < 1)
|
|
143
|
+
invalid("drop frame bytes is not a positive integer");
|
|
144
|
+
return { k: "drop", bytes: o.bytes };
|
|
145
|
+
default:
|
|
146
|
+
invalid(`unknown terminal-session frame kind ${JSON.stringify(o.k)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=session-terminal-frames.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-terminal-frames.js","sourceRoot":"","sources":["../src/session-terminal-frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAatD,MAAM,KAAK,GAAG,CAAC,CAAU,EAAgC,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACrH,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,IAAI,eAAe,CAAC,kBAAkB,EAAE,GAAG,IAAI,qCAAqC,CAAC,CAAC;AAC9F,CAAC;AAED;;;4FAG4F;AAC5F,MAAM,MAAM,GAAG,wBAAwB,CAAC;AACxC,6FAA6F;AAC7F,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AACtC,SAAS,YAAY,CAAC,CAAU;IAC9B,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACnE,IAAI,CAAC,CAAC,MAAM,GAAG,aAAa;QAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC,MAAM,2BAA2B,aAAa,UAAU,CAAC,CAAC;IACrH,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,kCAAkC,CAAC,CAAC;IACvF,OAAO,CAAC,CAAC;AACX,CAAC;AAED;kEACkE;AAClE,MAAM,MAAM,GAAG,wBAAwB,CAAC;AACxC,SAAS,YAAY,CAAC,CAAU;IAC9B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,0CAA0C,CAAC,CAAC;IACjI,OAAO,CAAC,CAAC;AACX,CAAC;AAED,6FAA6F;AAC7F,kGAAkG;AAClG,8FAA8F;AAC9F,+EAA+E;AAC/E,MAAM,YAAY,GAAG,kEAAkE,CAAC;AACxF,MAAM,OAAO,GAAe,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE;IAAE,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAE7K,SAAS,YAAY,CAAC,KAAiB;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QAC/D,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjE,GAAG,IAAI,YAAY,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC7B,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACpD,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACjE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,2FAA2F;AAC3F,SAAS,YAAY,CAAC,CAAS;IAC7B,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACzD,IAAI,IAAI,CAAC,CAAC;QACV,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YAAC,IAAI,IAAI,CAAC,CAAC;YAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC;QAAC,CAAC;IAChE,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;gGAIgG;AAChG,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,kGAAkG;IAClG,yFAAyF;IACzF,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;QAC5B,OAAO,CAAC,0DAA0D,OAAO,KAAK,0CAA0C,CAAC,CAAC;IAC5H,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED;4FAC4F;AAC5F,MAAM,UAAU,kBAAkB,CAAC,KAA4C;IAC7E,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC;AAED;;iFAEiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACpD,MAAM,CAAC,GAAG,IAAI,CAAC;IACf,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,GAAG;oBAAE,OAAO,CAAC,0CAA0C,GAAG,GAAG,CAAC,CAAC;YAC7G,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;QACxB,KAAK,MAAM;YACT,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG;oBAAE,OAAO,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;YAC3H,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,KAAK,QAAQ;YACX,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM;oBAAE,OAAO,CAAC,2CAA2C,GAAG,GAAG,CAAC,CAAC;YAClJ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAK,CAAC,CAAC,IAAe,GAAG,CAAC,IAAK,CAAC,CAAC,IAAe,GAAG,OAAO;gBAAE,OAAO,CAAC,+CAA+C,CAAC,CAAC;YAClJ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAK,CAAC,CAAC,IAAe,GAAG,CAAC,IAAK,CAAC,CAAC,IAAe,GAAG,OAAO;gBAAE,OAAO,CAAC,+CAA+C,CAAC,CAAC;YAClJ,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAc,EAAE,IAAI,EAAE,CAAC,CAAC,IAAc,EAAE,CAAC;QACzE,KAAK,KAAK;YACR,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,QAAQ;oBAAE,OAAO,CAAC,wCAAwC,GAAG,GAAG,CAAC,CAAC;YAC/H,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,KAAK,MAAM;YACT,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,OAAO;oBAAE,OAAO,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;YAC/H,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAK,CAAC,CAAC,KAAgB,GAAG,CAAC;gBAAE,OAAO,CAAC,4CAA4C,CAAC,CAAC;YACjH,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAe,EAAE,CAAC;QACjD;YACE,OAAO,CAAC,uCAAuC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
|
package/dist/spaces.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces.d.ts","sourceRoot":"","sources":["../src/spaces.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spaces.d.ts","sourceRoot":"","sources":["../src/spaces.ts"],"names":[],"mappings":"AA8BA,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;2EAE2E;AAC3E,wBAAsB,UAAU,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAmDnF;AAED;;0FAE0F;AAC1F,wBAAsB,WAAW,CAAC,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC1G"}
|
package/dist/spaces.js
CHANGED
|
@@ -7,7 +7,7 @@ import { connect, credsAuthenticator } from "@nats-io/transport-node";
|
|
|
7
7
|
import { jetstreamManager } from "@nats-io/jetstream";
|
|
8
8
|
import { Kvm } from "@nats-io/kv";
|
|
9
9
|
import { DEFAULT_SERVER } from "./endpoint.js";
|
|
10
|
-
import { parseSubject, chatStream, dmStream, taskStream, inboxStream, dlvStream, presenceBucket, channelBucket, membersBucket, aclBucket, membershipBucket, deliveryBucket, managerBucket, } from "./subjects.js";
|
|
10
|
+
import { parseSubject, chatStream, dmStream, taskStream, inboxStream, dlvStream, presenceBucket, channelBucket, membersBucket, aclBucket, membershipBucket, deliveryBucket, managerBucket, artifactBucket, objectStoreStream, } from "./subjects.js";
|
|
11
11
|
import { idFromCreds } from "./identity.js";
|
|
12
12
|
/** Connect opts for a possibly-scoped cred: an authenticator plus the per-id `inboxPrefix` a scoped
|
|
13
13
|
* cred needs (its `sub.allow` is `_INBOX_<id>.>`, so JS API replies must land there, not the default
|
|
@@ -107,6 +107,11 @@ export async function deleteSpace(opts) {
|
|
|
107
107
|
`KV_${membershipBucket(opts.space)}`,
|
|
108
108
|
`KV_${deliveryBucket(opts.space)}`,
|
|
109
109
|
`KV_${managerBucket(opts.space)}`,
|
|
110
|
+
// The artifact Object Store's backing stream. It must be named HERE and not swept by
|
|
111
|
+
// prefix: `$O.<bucket>.>` lives outside the `cotal.<space>.>` grammar, so a subject-based
|
|
112
|
+
// sweep of the space never sees it. Teardown is the sole STREAM.DELETE holder, so a stream
|
|
113
|
+
// missing from this list is not merely un-deleted - nothing in the system can ever reap it.
|
|
114
|
+
objectStoreStream(artifactBucket(opts.space)),
|
|
110
115
|
];
|
|
111
116
|
for (const s of streams)
|
|
112
117
|
await jsm.streams.delete(s).catch(() => { });
|
package/dist/spaces.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spaces.js","sourceRoot":"","sources":["../src/spaces.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,+FAA+F;AAC/F,2FAA2F;AAC3F,+FAA+F;AAC/F,wFAAwF;AAExF,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EACL,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EACtE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,gBAAgB,EACzE,cAAc,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"spaces.js","sourceRoot":"","sources":["../src/spaces.ts"],"names":[],"mappings":"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"}
|
package/dist/streams.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ConsumerConfig, type JetStreamManager } from "@nats-io/jetstream";
|
|
2
|
+
import { type NatsConnection } from "@nats-io/transport-node";
|
|
2
3
|
/** Per-(sender,channel)-subject retention cap on the chat stream — the bound past which the
|
|
3
4
|
* oldest message on a subject is discarded (`DiscardPolicy.Old`). Also the horizon of focus
|
|
4
5
|
* recall: only the last {@link MAX_MSGS_PER_SUBJECT} per sender-subject are recallable. */
|
|
@@ -34,6 +35,19 @@ export declare const MANAGER_LEASE_TTL_MS = 10000;
|
|
|
34
35
|
* far above any realistic readership while keeping the bucket from growing unbounded. A deliberate cap,
|
|
35
36
|
* not a guess at scale — the design is cap-safe by construction (per-agent, store-patterns-not-expanded). */
|
|
36
37
|
export declare const MEMBERSHIP_MAX_BYTES: number;
|
|
38
|
+
/** Bucket-level `max_bytes` cap on the per-space artifact Object Store (`cotal_artifacts_<space>`).
|
|
39
|
+
*
|
|
40
|
+
* THIS NUMBER IS THE ONLY THING BOUNDING ARTIFACT STORAGE, so it is a decision rather than a
|
|
41
|
+
* default. A fresh Object Store bucket ships `max_bytes: -1`, and the space account is provisioned
|
|
42
|
+
* `disk_storage: -1`, so nothing above it says no: without this cap an artifact flood grows until
|
|
43
|
+
* the disk does, starving the chat/DM/delivery streams that share it.
|
|
44
|
+
*
|
|
45
|
+
* 4 GiB is roughly sixteen artifacts at the 256 MiB per-artifact ceiling, which is generous for the
|
|
46
|
+
* transfer use case (screenshots, reports, build outputs) and small enough that filling it is a
|
|
47
|
+
* visible event rather than a silent disk exhaustion. `discard: new` on the bucket means hitting it
|
|
48
|
+
* REFUSES the write rather than evicting older artifacts — the loud failure, not the silent one
|
|
49
|
+
* where a reference published yesterday quietly stops resolving. */
|
|
50
|
+
export declare const ARTIFACT_STORE_MAX_BYTES: number;
|
|
37
51
|
export interface ClearSpaceHistoryResult {
|
|
38
52
|
chat: number;
|
|
39
53
|
dm?: number;
|
|
@@ -45,6 +59,24 @@ export interface StandaloneAuth {
|
|
|
45
59
|
creds?: string;
|
|
46
60
|
bearer?: string;
|
|
47
61
|
sentinelCreds?: string;
|
|
62
|
+
/** Whether this connection must REQUIRE TLS rather than merely tolerate it.
|
|
63
|
+
*
|
|
64
|
+
* REQUIRED, and it is the point of the field. This helper is the STANDALONE connect path, and it
|
|
65
|
+
* was derived from the endpoint path's auth half without its transport half: `authOpts`,
|
|
66
|
+
* `probeConnect` and `RawAuth` all carry a TLS requirement, and this one did not. Every caller
|
|
67
|
+
* of it therefore built connect options that carried the credentials and dropped the thing that
|
|
68
|
+
* protects them in transit.
|
|
69
|
+
*
|
|
70
|
+
* Made required rather than optional because the omitted case is the dangerous one. A client
|
|
71
|
+
* with no TLS requirement still connects to a TLS broker — it upgrades the same socket once it
|
|
72
|
+
* reads `tls_required` in the server's unauthenticated INFO — so nothing looks wrong until an
|
|
73
|
+
* on-path attacker forges an INFO without it and collects the credentials in the clear. An
|
|
74
|
+
* optional field would leave the seam LOOKING transport-aware while callers kept omitting it.
|
|
75
|
+
*
|
|
76
|
+
* Note the honest limit of the compile error: smoke files are outside the tsconfigs, so the type
|
|
77
|
+
* forces every TYPECHECKED caller to state a transport, not every caller. That is why
|
|
78
|
+
* `standaloneConnectOpts` also throws at runtime — see there. */
|
|
79
|
+
tls: boolean;
|
|
48
80
|
}
|
|
49
81
|
/** Connection options for a privileged STANDALONE helper (`setupSpaceStreams`, `clearSpaceHistory`,
|
|
50
82
|
* `clearChannel`, the channel-registry helpers): pin the reply inbox to the connection's own
|
|
@@ -56,7 +88,7 @@ export interface StandaloneAuth {
|
|
|
56
88
|
* sentinel creds land the connection in the callout account, the bearer rides `auth_token`, and a
|
|
57
89
|
* client-chosen inbox NONCE goes out as the connect `name` — the callout scopes `_INBOX_<nonce>.>`
|
|
58
90
|
* from it (the client cannot know its nkey pre-connect). Open mode (no auth) connects bare. */
|
|
59
|
-
export declare function standaloneConnectOpts(auth
|
|
91
|
+
export declare function standaloneConnectOpts(auth: StandaloneAuth): Record<string, unknown>;
|
|
60
92
|
/**
|
|
61
93
|
* Create (idempotently) the five message streams for a space.
|
|
62
94
|
*
|
|
@@ -124,6 +156,26 @@ export declare function fanoutDurableConfig(space: string, opts?: {
|
|
|
124
156
|
}): Partial<ConsumerConfig>;
|
|
125
157
|
/** Connect with the given (privileged) creds, create the space's streams, and disconnect.
|
|
126
158
|
* Used by `cotal up` to pre-create streams once at setup. */
|
|
159
|
+
/**
|
|
160
|
+
* Create the space's artifact Object Store, or VERIFY an existing one — never silently adopt it.
|
|
161
|
+
*
|
|
162
|
+
* `Objm.create(bucket, { max_bytes })` is create-if-MISSING. Measured on nats-server 2.14.4 with
|
|
163
|
+
* `@nats-io/obj` 3.4.0: creating at `max_bytes: 1024`, then calling create again with `4096`, leaves
|
|
164
|
+
* the stream at **1024** — it neither updates the config nor refuses. A bare `create()` with no
|
|
165
|
+
* options does the same.
|
|
166
|
+
*
|
|
167
|
+
* That makes create-alone actively dangerous here, because {@link setupSpaceStreams} is idempotent
|
|
168
|
+
* and re-runs on every `cotal up`. A store that predates this cap, or that an operator widened by
|
|
169
|
+
* hand, would be adopted forever: the code would look like it enforces a 4 GiB ceiling while the
|
|
170
|
+
* broker enforced whatever was there first, and nothing would ever say so. The cap is the ONLY thing
|
|
171
|
+
* bounding artifact storage (account disk is provisioned unlimited), so an unenforced cap is not a
|
|
172
|
+
* smaller cap — it is no cap.
|
|
173
|
+
*
|
|
174
|
+
* So: create, then read the config back and refuse loudly on drift. Same create-or-verify discipline
|
|
175
|
+
* `ensureAuthorityStores` already uses, and the same reason: an idempotent setup path must either
|
|
176
|
+
* converge the resource or report that it cannot.
|
|
177
|
+
*/
|
|
178
|
+
export declare function ensureArtifactStore(nc: NatsConnection, space: string): Promise<void>;
|
|
127
179
|
export declare function setupSpaceStreams(opts: {
|
|
128
180
|
servers: string;
|
|
129
181
|
space: string;
|
package/dist/streams.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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"}
|
package/dist/streams.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { jetstreamManager, AckPolicy, DeliverPolicy, } from "@nats-io/jetstream";
|
|
1
|
+
import { jetstream, jetstreamManager, AckPolicy, DeliverPolicy, } from "@nats-io/jetstream";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { connect, credsAuthenticator, tokenAuthenticator, nanos } from "@nats-io/transport-node";
|
|
4
4
|
import { Kvm } from "@nats-io/kv";
|
|
5
|
-
import {
|
|
5
|
+
import { Objm } from "@nats-io/obj";
|
|
6
|
+
import { spacePrefix, artifactBucket, objectStoreStream, chatStream, chatSubject, chatWildcard, isConcreteChannel, dmStream, dmDurable, unicastRecvFilter, taskStream, taskDurable, anycastServeFilter, presenceBucket, channelBucket, membersBucket, aclBucket, membershipBucket, deliveryBucket, managerBucket, inboxStream, dlvStream, dlvSubject, dlvDurable, fanoutDurable, readerDurable, DEV_OWNER, principalKey, deprovisionTargetPrincipal, } from "./subjects.js";
|
|
6
7
|
import { idFromCreds } from "./identity.js";
|
|
8
|
+
import { ensureAuthorityStores } from "./endpoint-binding.js";
|
|
7
9
|
import { openAclRegistry, deleteAcl } from "./acls.js";
|
|
8
10
|
import { BACKUP_MAX_MSGS_PER_SUBJECT, BACKUP_PLANE3_DEDUP_WINDOW_MS, canonicalBackupStreamConfig, } from "./backup-config.js";
|
|
9
11
|
/** Default presence-bucket entry TTL (ms) — matches the endpoint's default liveness window. */
|
|
@@ -43,6 +45,19 @@ export const MANAGER_LEASE_TTL_MS = 10_000;
|
|
|
43
45
|
* far above any realistic readership while keeping the bucket from growing unbounded. A deliberate cap,
|
|
44
46
|
* not a guess at scale — the design is cap-safe by construction (per-agent, store-patterns-not-expanded). */
|
|
45
47
|
export const MEMBERSHIP_MAX_BYTES = 64 * 1024 * 1024;
|
|
48
|
+
/** Bucket-level `max_bytes` cap on the per-space artifact Object Store (`cotal_artifacts_<space>`).
|
|
49
|
+
*
|
|
50
|
+
* THIS NUMBER IS THE ONLY THING BOUNDING ARTIFACT STORAGE, so it is a decision rather than a
|
|
51
|
+
* default. A fresh Object Store bucket ships `max_bytes: -1`, and the space account is provisioned
|
|
52
|
+
* `disk_storage: -1`, so nothing above it says no: without this cap an artifact flood grows until
|
|
53
|
+
* the disk does, starving the chat/DM/delivery streams that share it.
|
|
54
|
+
*
|
|
55
|
+
* 4 GiB is roughly sixteen artifacts at the 256 MiB per-artifact ceiling, which is generous for the
|
|
56
|
+
* transfer use case (screenshots, reports, build outputs) and small enough that filling it is a
|
|
57
|
+
* visible event rather than a silent disk exhaustion. `discard: new` on the bucket means hitting it
|
|
58
|
+
* REFUSES the write rather than evicting older artifacts — the loud failure, not the silent one
|
|
59
|
+
* where a reference published yesterday quietly stops resolving. */
|
|
60
|
+
export const ARTIFACT_STORE_MAX_BYTES = 4 * 1024 * 1024 * 1024;
|
|
46
61
|
/** Connection options for a privileged STANDALONE helper (`setupSpaceStreams`, `clearSpaceHistory`,
|
|
47
62
|
* `clearChannel`, the channel-registry helpers): pin the reply inbox to the connection's own
|
|
48
63
|
* identity. A scoped cred (provisioner/purger/operator) subscribes only `_INBOX_<id>.>`, so without
|
|
@@ -53,7 +68,21 @@ export const MEMBERSHIP_MAX_BYTES = 64 * 1024 * 1024;
|
|
|
53
68
|
* sentinel creds land the connection in the callout account, the bearer rides `auth_token`, and a
|
|
54
69
|
* client-chosen inbox NONCE goes out as the connect `name` — the callout scopes `_INBOX_<nonce>.>`
|
|
55
70
|
* from it (the client cannot know its nkey pre-connect). Open mode (no auth) connects bare. */
|
|
56
|
-
export function standaloneConnectOpts(auth
|
|
71
|
+
export function standaloneConnectOpts(auth) {
|
|
72
|
+
// The `= {}` default is deliberately GONE. It was the omission hole: it let a caller build
|
|
73
|
+
// connect options without ever naming a transport, and the result silently connected non-strict.
|
|
74
|
+
//
|
|
75
|
+
// The throw exists because the type alone does not reach far enough. Smoke files sit outside the
|
|
76
|
+
// tsconfigs, so 16 of this seam's 28 call sites are never typechecked - the compile error covers
|
|
77
|
+
// every TYPECHECKED caller, not every caller. Without this guard those would keep passing no
|
|
78
|
+
// transport and degrade to non-strict in silence, which is precisely the defect being fixed.
|
|
79
|
+
// Positioned BEFORE the options are built and before any connect: a guard only fences what comes
|
|
80
|
+
// after it.
|
|
81
|
+
if (auth?.tls === undefined)
|
|
82
|
+
throw new Error("standaloneConnectOpts requires an explicit `tls` boolean: pass `tls: true` to REQUIRE TLS, " +
|
|
83
|
+
"or `tls: false` for a plaintext broker. It has no default, because defaulting it would " +
|
|
84
|
+
"silently connect without the requirement that protects the credentials being passed.");
|
|
85
|
+
const tlsOpt = auth.tls ? { tls: {} } : {};
|
|
57
86
|
if (auth.bearer !== undefined) {
|
|
58
87
|
if (!auth.sentinelCreds)
|
|
59
88
|
throw new Error("user-mode standalone connect requires sentinelCreds alongside the bearer");
|
|
@@ -64,14 +93,16 @@ export function standaloneConnectOpts(auth = {}) {
|
|
|
64
93
|
name: nonce,
|
|
65
94
|
inboxPrefix: `_INBOX_${nonce}`,
|
|
66
95
|
authenticator: [credsAuthenticator(new TextEncoder().encode(auth.sentinelCreds)), tokenAuthenticator(auth.bearer)],
|
|
96
|
+
...tlsOpt,
|
|
67
97
|
};
|
|
68
98
|
}
|
|
69
99
|
return auth.creds
|
|
70
100
|
? {
|
|
71
101
|
authenticator: credsAuthenticator(new TextEncoder().encode(auth.creds)),
|
|
72
102
|
inboxPrefix: `_INBOX_${idFromCreds(auth.creds)}`,
|
|
103
|
+
...tlsOpt,
|
|
73
104
|
}
|
|
74
|
-
: {};
|
|
105
|
+
: { ...tlsOpt };
|
|
75
106
|
}
|
|
76
107
|
/**
|
|
77
108
|
* Create (idempotently) the five message streams for a space.
|
|
@@ -187,8 +218,95 @@ export function fanoutDurableConfig(space, opts = {}) {
|
|
|
187
218
|
}
|
|
188
219
|
/** Connect with the given (privileged) creds, create the space's streams, and disconnect.
|
|
189
220
|
* Used by `cotal up` to pre-create streams once at setup. */
|
|
221
|
+
/**
|
|
222
|
+
* Create the space's artifact Object Store, or VERIFY an existing one — never silently adopt it.
|
|
223
|
+
*
|
|
224
|
+
* `Objm.create(bucket, { max_bytes })` is create-if-MISSING. Measured on nats-server 2.14.4 with
|
|
225
|
+
* `@nats-io/obj` 3.4.0: creating at `max_bytes: 1024`, then calling create again with `4096`, leaves
|
|
226
|
+
* the stream at **1024** — it neither updates the config nor refuses. A bare `create()` with no
|
|
227
|
+
* options does the same.
|
|
228
|
+
*
|
|
229
|
+
* That makes create-alone actively dangerous here, because {@link setupSpaceStreams} is idempotent
|
|
230
|
+
* and re-runs on every `cotal up`. A store that predates this cap, or that an operator widened by
|
|
231
|
+
* hand, would be adopted forever: the code would look like it enforces a 4 GiB ceiling while the
|
|
232
|
+
* broker enforced whatever was there first, and nothing would ever say so. The cap is the ONLY thing
|
|
233
|
+
* bounding artifact storage (account disk is provisioned unlimited), so an unenforced cap is not a
|
|
234
|
+
* smaller cap — it is no cap.
|
|
235
|
+
*
|
|
236
|
+
* So: create, then read the config back and refuse loudly on drift. Same create-or-verify discipline
|
|
237
|
+
* `ensureAuthorityStores` already uses, and the same reason: an idempotent setup path must either
|
|
238
|
+
* converge the resource or report that it cannot.
|
|
239
|
+
*/
|
|
240
|
+
export async function ensureArtifactStore(nc, space) {
|
|
241
|
+
const bucket = artifactBucket(space);
|
|
242
|
+
const stream = objectStoreStream(bucket);
|
|
243
|
+
await new Objm(jetstream(nc)).create(bucket, { max_bytes: ARTIFACT_STORE_MAX_BYTES });
|
|
244
|
+
const { config } = await (await jetstreamManager(nc)).streams.info(stream);
|
|
245
|
+
const drift = [];
|
|
246
|
+
// SUBJECTS FIRST, because they decide whether this is an object store AT ALL. A stream created
|
|
247
|
+
// under the right name with the right cap and the right discard, but bound to other subjects, is
|
|
248
|
+
// adopted by a cap-only check while artifact puts can never land - and setup reports success.
|
|
249
|
+
// (Measured: a stream named OBJ_<bucket> over `foreign.capture.>` survived setup untouched.)
|
|
250
|
+
const want = [`$O.${bucket}.C.>`, `$O.${bucket}.M.>`];
|
|
251
|
+
if (JSON.stringify(config.subjects ?? []) !== JSON.stringify(want))
|
|
252
|
+
drift.push(`subjects are ${JSON.stringify(config.subjects ?? [])}, expected ${JSON.stringify(want)}`);
|
|
253
|
+
// A MIRROR or SOURCE under this name makes the space's artifact store a view of someone else's
|
|
254
|
+
// stream. Nothing about the cap would look wrong; the bytes would simply not be the space's own.
|
|
255
|
+
if (config.mirror)
|
|
256
|
+
drift.push("it is a MIRROR of another stream");
|
|
257
|
+
if (config.sources?.length)
|
|
258
|
+
drift.push(`it SOURCES ${config.sources.length} other stream(s)`);
|
|
259
|
+
if (config.max_bytes !== ARTIFACT_STORE_MAX_BYTES)
|
|
260
|
+
drift.push(`max_bytes is ${config.max_bytes}, expected ${ARTIFACT_STORE_MAX_BYTES}`);
|
|
261
|
+
// `discard: new` is what makes a full store REFUSE a put instead of evicting a live artifact whose
|
|
262
|
+
// reference is already published. Drift here is silent data loss, not a capacity difference.
|
|
263
|
+
if (String(config.discard) !== "new")
|
|
264
|
+
drift.push(`discard is ${config.discard}, expected new`);
|
|
265
|
+
// File storage: memory-backed artifacts vanish on a broker restart while every published reference
|
|
266
|
+
// survives, which turns a restart into a wave of unresolvable references.
|
|
267
|
+
if (String(config.storage) !== "file")
|
|
268
|
+
drift.push(`storage is ${config.storage}, expected file`);
|
|
269
|
+
// Limits retention: any interest/work-queue retention DELETES messages once consumed, so a fetch
|
|
270
|
+
// would destroy the artifact it just read.
|
|
271
|
+
if (String(config.retention) !== "limits")
|
|
272
|
+
drift.push(`retention is ${config.retention}, expected limits`);
|
|
273
|
+
// Rollup headers: WRITE-CRITICAL, and measured rather than assumed. With `allow_rollup_hdrs:false`
|
|
274
|
+
// on an otherwise canonical store, `put` fails outright with "rollup not permitted" - the object
|
|
275
|
+
// store uses a rollup to replace an object's metadata. So a store drifted here accepts setup and
|
|
276
|
+
// then rejects every artifact, which is the worst shape: green provisioning, broken feature.
|
|
277
|
+
//
|
|
278
|
+
// `allow_direct` is deliberately NOT checked. Measured on the same probe: with it false, put and
|
|
279
|
+
// get both still succeed, because this client's info path uses STREAM.MSG.GET rather than
|
|
280
|
+
// DIRECT.GET. Asserting it would refuse a store that works.
|
|
281
|
+
if (config.allow_rollup_hdrs !== true)
|
|
282
|
+
drift.push("allow_rollup_hdrs is false, expected true (put fails without it)");
|
|
283
|
+
// max_age: SILENT DATA LOSS, measured. With max_age 1s on an otherwise canonical store, a put
|
|
284
|
+
// succeeds and the object is GONE 1.8s later (stream messages back to 0) - while every reference
|
|
285
|
+
// already published to a channel survives forever. That is the dangling-reference wave this design
|
|
286
|
+
// refuses everywhere else, arriving from a config field rather than from GC.
|
|
287
|
+
if (config.max_age !== 0)
|
|
288
|
+
drift.push(`max_age is ${config.max_age}, expected 0 (a non-zero age silently deletes stored artifacts)`);
|
|
289
|
+
// sealed: cheap to check, and the mechanism is NOT the one it is usually described by. Measured:
|
|
290
|
+
// the broker REFUSES to create a sealed stream at all ("stream configuration for create can not be
|
|
291
|
+
// sealed"), so a sealed store cannot arrive through the create path - only by a later update. That
|
|
292
|
+
// narrows it to a deliberate operator action, which is exactly the drift worth naming.
|
|
293
|
+
if (config.sealed === true)
|
|
294
|
+
drift.push("the stream is SEALED (writes permanently refused)");
|
|
295
|
+
// The message-count and size limits must stay unbounded, because THE CAP IS SUPPOSED TO BE THE
|
|
296
|
+
// OPERATIVE BOUND. Reproduced: max_msgs=2 accepts setup, the first 1-byte object succeeds (chunk +
|
|
297
|
+
// meta = 2 messages), and the second fails "maximum messages exceeded" with 4 GiB still free. A
|
|
298
|
+
// hidden limit that overrides the advertised capacity makes the number this code reports a lie -
|
|
299
|
+
// loud rather than silent, but still a bound nobody configured deliberately.
|
|
300
|
+
for (const [field, value] of [["max_msgs", config.max_msgs], ["max_msgs_per_subject", config.max_msgs_per_subject],
|
|
301
|
+
["max_msg_size", config.max_msg_size]])
|
|
302
|
+
if (value !== -1)
|
|
303
|
+
drift.push(`${field} is ${value}, expected -1 (max_bytes is the only bound this store advertises)`);
|
|
304
|
+
if (drift.length)
|
|
305
|
+
throw new Error(`artifact store ${stream} has drifted: ${drift.join("; ")} - refusing to adopt a store whose ` +
|
|
306
|
+
`bounds are not the ones this space enforces (delete it, or reconcile it deliberately)`);
|
|
307
|
+
}
|
|
190
308
|
export async function setupSpaceStreams(opts) {
|
|
191
|
-
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts({ creds: opts.creds }) });
|
|
309
|
+
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts({ creds: opts.creds, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }) });
|
|
192
310
|
try {
|
|
193
311
|
const jsm = await jetstreamManager(nc);
|
|
194
312
|
await createSpaceStreams(jsm, opts.space);
|
|
@@ -219,6 +337,15 @@ export async function setupSpaceStreams(opts) {
|
|
|
219
337
|
// Config matches `managerLeaseRegistry()`'s create-first exactly, so that path stays idempotent until
|
|
220
338
|
// the supervisor profile drops bucket-create. Idempotent.
|
|
221
339
|
await kvm.create(managerBucket(opts.space), { ttl: MANAGER_LEASE_TTL_MS });
|
|
340
|
+
// The two §13.12 AUTHORITY stores (records + auth): every auth-mode mesh now carries a
|
|
341
|
+
// lifecycle registry — user mode's service re-ensures at its own boot, the STATIC manager's
|
|
342
|
+
// start reconcile re-ensures for pre-existing spaces (Unit B) — and the up-time seed creates
|
|
343
|
+
// them so neither daemon needs first-write stream creation. Create-or-verify, idempotent,
|
|
344
|
+
// drift fails loud.
|
|
345
|
+
await ensureAuthorityStores(jsm, kvm, opts.space);
|
|
346
|
+
// Artifact Object Store (SPEC section 5): the bytes an `artifact` reference part points at.
|
|
347
|
+
// Create-or-VERIFY, drift fails loud - see ensureArtifactStore for why create alone is not enough.
|
|
348
|
+
await ensureArtifactStore(nc, opts.space);
|
|
222
349
|
}
|
|
223
350
|
finally {
|
|
224
351
|
await nc.drain();
|
|
@@ -227,7 +354,7 @@ export async function setupSpaceStreams(opts) {
|
|
|
227
354
|
/** Purge retained message history for a running space. This intentionally leaves TASK alone:
|
|
228
355
|
* anycast is queued work, not replay history. */
|
|
229
356
|
export async function clearSpaceHistory(opts) {
|
|
230
|
-
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts(opts) });
|
|
357
|
+
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts({ ...opts, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }) });
|
|
231
358
|
try {
|
|
232
359
|
const jsm = await jetstreamManager(nc);
|
|
233
360
|
const chat = (await jsm.streams.purge(chatStream(opts.space))).purged;
|
|
@@ -249,7 +376,7 @@ export async function clearSpaceHistory(opts) {
|
|
|
249
376
|
export async function clearChannel(opts) {
|
|
250
377
|
if (!isConcreteChannel(opts.channel))
|
|
251
378
|
throw new Error(`"${opts.channel}" is a wildcard, not a deletable channel`);
|
|
252
|
-
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts(opts) });
|
|
379
|
+
const nc = await connect({ servers: opts.servers, ...standaloneConnectOpts({ ...opts, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }) });
|
|
253
380
|
try {
|
|
254
381
|
const jsm = await jetstreamManager(nc);
|
|
255
382
|
const { purged } = await jsm.streams.purge(chatStream(opts.space), {
|
|
@@ -285,7 +412,7 @@ export async function clearChannel(opts) {
|
|
|
285
412
|
export async function deprovisionAgent(opts) {
|
|
286
413
|
const nc = await connect({
|
|
287
414
|
servers: opts.servers,
|
|
288
|
-
...standaloneConnectOpts({ creds: opts.creds }),
|
|
415
|
+
...standaloneConnectOpts({ creds: opts.creds, /* not yet wired to a recorded transport - see broker-policy/MeshEntry work */ tls: false }),
|
|
289
416
|
// This is a detached, fire-and-forget teardown — it must FAIL FAST, never hang, so the caller's
|
|
290
417
|
// fail-loud `.catch` is load-bearing: no reconnect loop (a wedged broker rejects promptly instead of
|
|
291
418
|
// looping silently) and a bounded initial connect. Without this a broker-down deprovision would sit
|