@cotal-ai/core 0.69.0 → 0.70.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/dist/auth-provider.d.ts +9 -2
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/auth-provider.js +10 -0
- package/dist/auth-provider.js.map +1 -1
- package/dist/channels.js +1 -1
- package/dist/channels.js.map +1 -1
- package/dist/connector-setup.d.ts +9 -5
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/contract-manifest.d.ts +20 -0
- package/dist/contract-manifest.d.ts.map +1 -0
- package/dist/contract-manifest.js +28 -0
- package/dist/contract-manifest.js.map +1 -0
- package/dist/endpoint-action.d.ts +3 -0
- package/dist/endpoint-action.d.ts.map +1 -1
- package/dist/endpoint-action.js +8 -6
- package/dist/endpoint-action.js.map +1 -1
- package/dist/endpoint-binding.d.ts.map +1 -1
- package/dist/endpoint-binding.js +5 -0
- package/dist/endpoint-binding.js.map +1 -1
- package/dist/endpoint-cluster.d.ts.map +1 -1
- package/dist/endpoint-cluster.js +9 -4
- package/dist/endpoint-cluster.js.map +1 -1
- package/dist/endpoint-contract-store.d.ts +2 -18
- package/dist/endpoint-contract-store.d.ts.map +1 -1
- package/dist/endpoint-contract-store.js +3 -19
- package/dist/endpoint-contract-store.js.map +1 -1
- package/dist/endpoint-envelope.d.ts +6 -2
- package/dist/endpoint-envelope.d.ts.map +1 -1
- package/dist/endpoint-envelope.js +19 -2
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-error.d.ts +13 -0
- package/dist/endpoint-error.d.ts.map +1 -1
- package/dist/endpoint-error.js +15 -0
- package/dist/endpoint-error.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +5 -2
- package/dist/endpoint-invoke.d.ts.map +1 -1
- package/dist/endpoint-invoke.js +16 -6
- package/dist/endpoint-invoke.js.map +1 -1
- package/dist/endpoint-records.d.ts +2 -1
- package/dist/endpoint-records.d.ts.map +1 -1
- package/dist/endpoint-records.js +1 -1
- package/dist/endpoint-records.js.map +1 -1
- package/dist/endpoint-serve.d.ts.map +1 -1
- package/dist/endpoint-serve.js +4 -3
- package/dist/endpoint-serve.js.map +1 -1
- package/dist/endpoint.d.ts +9 -4
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +47 -37
- package/dist/endpoint.js.map +1 -1
- package/dist/evict.d.ts.map +1 -1
- package/dist/evict.js +2 -1
- package/dist/evict.js.map +1 -1
- package/dist/identity.d.ts +3 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +5 -0
- package/dist/identity.js.map +1 -1
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js +1 -6
- package/dist/kv-scan.js.map +1 -1
- package/dist/provision.d.ts +23 -24
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +112 -116
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +22 -2
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js +23 -15
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/resolve.d.ts +15 -2
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +15 -2
- package/dist/resolve.js.map +1 -1
- package/dist/run-driver-grants.d.ts.map +1 -1
- package/dist/run-driver-grants.js +4 -1
- package/dist/run-driver-grants.js.map +1 -1
- package/dist/run-journal.d.ts.map +1 -1
- package/dist/run-journal.js +17 -2
- package/dist/run-journal.js.map +1 -1
- package/dist/run-record.d.ts +6 -8
- package/dist/run-record.d.ts.map +1 -1
- package/dist/run-record.js.map +1 -1
- package/dist/schema-profile.d.ts +11 -0
- package/dist/schema-profile.d.ts.map +1 -1
- package/dist/schema-profile.js +13 -2
- package/dist/schema-profile.js.map +1 -1
- package/dist/secret-fs.d.ts +4 -3
- package/dist/secret-fs.d.ts.map +1 -1
- package/dist/secret-fs.js +10 -11
- package/dist/secret-fs.js.map +1 -1
- package/dist/streams.d.ts +2 -2
- package/dist/streams.js +2 -2
- package/package.json +5 -1
package/dist/endpoint.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { createConnection } from "node:net";
|
|
4
|
-
import { connect, credsAuthenticator, headers, tokenAuthenticator, nanos, AuthorizationError, PermissionViolationError, UserAuthenticationExpiredError, NoRespondersError, RequestError, } from "@nats-io/transport-node";
|
|
4
|
+
import { connect, credsAuthenticator, headers, tokenAuthenticator, nanos, AuthorizationError, PermissionViolationError, UserAuthenticationExpiredError, NoRespondersError, RequestError, TimeoutError, ClosedConnectionError, } from "@nats-io/transport-node";
|
|
5
5
|
import { wsconnect } from "@nats-io/nats-core";
|
|
6
6
|
import { parseLivenessAnswer, responderFromLease, responderFromProbe, isLivenessPlane, LIVENESS_PLANES, } from "./liveness.js";
|
|
7
7
|
import { credsClaims, credsFingerprint, credsRenewalDelayMs, idFromCreds } from "./identity.js";
|
|
@@ -9,7 +9,7 @@ import { requireBrokerFloor } from "./broker-floor.js";
|
|
|
9
9
|
import { inspectCredHealth } from "./provision.js";
|
|
10
10
|
import { parseSecretStoreIdentity, } from "./secret-store.js";
|
|
11
11
|
import { BIND_SPLIT_REISSUES, resolveService, invokeCommand, submitAndFollowGoal } from "./endpoint-invoke.js";
|
|
12
|
-
import { EpEnvelopeError, respondedButUnbound, replyRefusedBeforeEffect, EP_BIND_REFUSED } from "./endpoint-envelope.js";
|
|
12
|
+
import { EpEnvelopeError, respondedButUnbound, replyRefusedBeforeEffect, replyTargetUnmapped, EP_BIND_REFUSED } from "./endpoint-envelope.js";
|
|
13
13
|
import { isRepeatSafeCommand } from "./endpoint-grants.js";
|
|
14
14
|
import { assertIdToken, assertGeneration } from "./endpoint-subjects.js";
|
|
15
15
|
import { readAcceptedRow } from "./issued-authority.js";
|
|
@@ -363,8 +363,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
363
363
|
/** Active goal followers awaiting outcome, tracked so endpoint stop can cancel them immediately
|
|
364
364
|
* and connection replacement / reconnect can trigger reconciliation. */
|
|
365
365
|
activeGoalFollowers = new Set();
|
|
366
|
-
/** How many calls {@link invokeService} has silently recovered from a bind refusal (§13.2)
|
|
367
|
-
* class-queue splits this endpoint hit
|
|
366
|
+
/** How many calls {@link invokeService} has silently recovered from a bind refusal (§13.2) or a
|
|
367
|
+
* member holding no mapping for the target (§13.3) — the class-queue splits this endpoint hit
|
|
368
|
+
* and survived.
|
|
368
369
|
*
|
|
369
370
|
* Counted because it is recovered: handling the split is what makes it invisible, so this is the
|
|
370
371
|
* only evidence the split rate exists. Always on, never behind a flag — a counter you have to
|
|
@@ -1907,8 +1908,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1907
1908
|
this.emit("error", new Error(`rejected ${service} request on ${m.subject}: reply target "${m.reply ?? "(none)"}" is not under the sender's own reply subtree`));
|
|
1908
1909
|
continue;
|
|
1909
1910
|
}
|
|
1910
|
-
let
|
|
1911
|
+
let body;
|
|
1911
1912
|
try {
|
|
1913
|
+
let reply;
|
|
1912
1914
|
const req = m.json();
|
|
1913
1915
|
// Authenticity guard (fail closed): control is the most privileged surface
|
|
1914
1916
|
// (start/stop). The sender is encoded in the subject (ctl.<svc>.<sender>), which
|
|
@@ -1923,15 +1925,25 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1923
1925
|
else {
|
|
1924
1926
|
reply = await handler(req);
|
|
1925
1927
|
}
|
|
1928
|
+
body = JSON.stringify(reply);
|
|
1926
1929
|
}
|
|
1927
1930
|
catch (e) {
|
|
1928
|
-
|
|
1931
|
+
body = JSON.stringify({ ok: false, error: e.message });
|
|
1929
1932
|
}
|
|
1930
1933
|
try {
|
|
1931
|
-
m.respond(
|
|
1934
|
+
m.respond(body);
|
|
1932
1935
|
}
|
|
1933
|
-
catch {
|
|
1934
|
-
|
|
1936
|
+
catch (e) {
|
|
1937
|
+
// `respond` signals a missing reply inbox by returning false; it throws when the reply exceeds the
|
|
1938
|
+
// broker's `max_payload`. Silence would leave the caller a bare timeout it cannot tell apart from
|
|
1939
|
+
// a dead service.
|
|
1940
|
+
const refused = { ok: false, error: `the ${service} reply is ${Buffer.byteLength(body)} bytes and was not published: ${e.message}` };
|
|
1941
|
+
try {
|
|
1942
|
+
m.respond(JSON.stringify(refused));
|
|
1943
|
+
}
|
|
1944
|
+
catch (err) {
|
|
1945
|
+
this.emit("error", new Error(`could not answer ${service} request on ${m.subject}: ${refused.error}, and the refusal failed too: ${err.message}`));
|
|
1946
|
+
}
|
|
1935
1947
|
}
|
|
1936
1948
|
}
|
|
1937
1949
|
})().catch((e) => this.emit("error", e));
|
|
@@ -2032,7 +2044,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2032
2044
|
* call re-issued for any command, up to {@link BIND_SPLIT_REISSUES} times while each re-issue
|
|
2033
2045
|
* is refused the same way. If a re-issue cannot be resolved, the refusal
|
|
2034
2046
|
* surfaces — still saying the command did not run — naming the resolve failure as why the
|
|
2035
|
-
* repair could not be attempted.
|
|
2047
|
+
* repair could not be attempted. An unpinned targeted call that reached a member holding no
|
|
2048
|
+
* mapping for its target ({@link replyTargetUnmapped}) is repaired the same way, and when the
|
|
2049
|
+
* re-issues run out, the latest such refusal is the one surfaced.
|
|
2036
2050
|
* - this CLIENT caught it on the reply ({@link respondedButUnbound}: a different instance,
|
|
2037
2051
|
* `failed-precondition`; the same instance at any other epoch, `expired`), which is what a
|
|
2038
2052
|
* responder too old to know the field produces. A live instance received and answered it, so
|
|
@@ -2103,7 +2117,14 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2103
2117
|
// did not run, so each re-issue is a FIRST attempt, not a second. It repeats up to the bound
|
|
2104
2118
|
// the CLI uses, because the re-issue rides the same class queue and splits again at the same
|
|
2105
2119
|
// rate; repairing once left a quarter of all calls in a two-manager space failing (#443).
|
|
2106
|
-
|
|
2120
|
+
// A member that holds no mapping for the target also ran nothing, and on the class rail a
|
|
2121
|
+
// sibling may host it. A pinned call named its instance, so that refusal is its answer.
|
|
2122
|
+
// That member looked the target up, so when the re-issues run out its refusal is surfaced
|
|
2123
|
+
// in place of a trailing bind refusal, which says nothing about the target.
|
|
2124
|
+
const unmappedHere = (x) => opts.instanceId === undefined && replyTargetUnmapped(x.reply.error);
|
|
2125
|
+
let unmapped = unmappedHere(r) ? r : undefined;
|
|
2126
|
+
for (let reissues = 0; reissues < BIND_SPLIT_REISSUES && r.reply.ok === false
|
|
2127
|
+
&& (replyRefusedBeforeEffect(r.reply.error) || unmappedHere(r)); reissues++) {
|
|
2107
2128
|
// Counted before it is repaired: a recovery that leaves no trace takes the split rate with it.
|
|
2108
2129
|
this.splitsRecovered++;
|
|
2109
2130
|
// `boundTo` is the other half of `servedBy`: who the handle THOUGHT it was talking to,
|
|
@@ -2154,8 +2175,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2154
2175
|
"not-executed");
|
|
2155
2176
|
}
|
|
2156
2177
|
r = await invokeResolved(reissueTarget, signal);
|
|
2178
|
+
if (unmappedHere(r))
|
|
2179
|
+
unmapped = r;
|
|
2157
2180
|
}
|
|
2158
|
-
return r;
|
|
2181
|
+
return replyRefusedBeforeEffect(r.reply.error) ? unmapped ?? r : r;
|
|
2159
2182
|
}
|
|
2160
2183
|
catch (e) {
|
|
2161
2184
|
// DO NOT auto-retry a command a responder already ANSWERED. This path covers the responders
|
|
@@ -2762,8 +2785,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2762
2785
|
watch.consumerName = undefined;
|
|
2763
2786
|
}
|
|
2764
2787
|
else {
|
|
2765
|
-
const closedEpoch = err
|
|
2766
|
-
const timeout =
|
|
2788
|
+
const closedEpoch = err instanceof ClosedConnectionError;
|
|
2789
|
+
const timeout = err instanceof TimeoutError;
|
|
2767
2790
|
const dyingEpochTimeout = timeout && (this.reconnecting || !this.nc || this.nc.isClosed());
|
|
2768
2791
|
// Cleanup of an ordered consumer: a delete timeout means the broker did not answer in time,
|
|
2769
2792
|
// not that the endpoint is unusable. The broker reaps an idle/ephemeral consumer anyway.
|
|
@@ -4361,9 +4384,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
4361
4384
|
const reachedStart = page.length <= limit;
|
|
4362
4385
|
const wanted = reachedStart ? page : page.slice(-limit);
|
|
4363
4386
|
// A page that cannot be SENT is not a page. The reply rides one NATS message, so `limit` alone is
|
|
4364
|
-
// the wrong bound: 200 large messages serialize past `max_payload`, `
|
|
4365
|
-
//
|
|
4366
|
-
// dead daemon. Measured, not predicted: 200 x ~6 KB timed out at 5s with the message "timeout".
|
|
4387
|
+
// the wrong bound: 200 large messages serialize past `max_payload`, and `serveControl` can then only
|
|
4388
|
+
// answer with a refusal in place of the page.
|
|
4367
4389
|
//
|
|
4368
4390
|
// So bound by BYTES too, keeping the NEWEST that fit — which needs no new vocabulary, because
|
|
4369
4391
|
// `complete: false` already means "older history remains behind this page". Trimming here is the
|
|
@@ -4705,9 +4727,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
4705
4727
|
probeFailureOutcome(e) {
|
|
4706
4728
|
if (e instanceof AuthorizationError || e instanceof PermissionViolationError)
|
|
4707
4729
|
return "refused";
|
|
4708
|
-
|
|
4709
|
-
const msg = e?.message ?? "";
|
|
4710
|
-
if (name === "TimeoutError" || /timeout/i.test(msg))
|
|
4730
|
+
if (e instanceof TimeoutError)
|
|
4711
4731
|
return "timeout";
|
|
4712
4732
|
// Anything else is a transport or client failure: it says something about our link, not about
|
|
4713
4733
|
// the plane, so it is graded like a refusal rather than guessed at.
|
|
@@ -5450,7 +5470,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
5450
5470
|
await this.pump(taskStream(this.space), taskDurable(this.card.role));
|
|
5451
5471
|
}
|
|
5452
5472
|
}
|
|
5453
|
-
/** Drive one consumer: decode
|
|
5473
|
+
/** Drive one consumer: decode and hand each message to listeners with ack control. Our own sends are
|
|
5474
|
+
* delivered too: on the DM inbox and the role queue they were addressed to us, so none is an echo,
|
|
5475
|
+
* and acking one unseen on the work queue would delete the only copy of the request. */
|
|
5454
5476
|
async pump(stream, durable) {
|
|
5455
5477
|
if (!this.js)
|
|
5456
5478
|
throw new Error("endpoint not started");
|
|
@@ -5484,10 +5506,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
5484
5506
|
`does not match subject sender ${parsed?.sender ?? "(unparseable)"}`));
|
|
5485
5507
|
continue;
|
|
5486
5508
|
}
|
|
5487
|
-
if (msg.from.id === this.card.id) {
|
|
5488
|
-
m.ack(); // our own echo — advance past it
|
|
5489
|
-
continue;
|
|
5490
|
-
}
|
|
5491
5509
|
// No-replay + dedup (chat only): drop a message at/below this channel's join watermark
|
|
5492
5510
|
// — pre-join history the New tail still carries for a *lagging* joiner (cursor behind the
|
|
5493
5511
|
// frontier), and the overlap a replay backfill already delivered. Must ack, or JetStream
|
|
@@ -7043,14 +7061,6 @@ export async function isReachable(servers = DEFAULT_SERVER, opts = {}) {
|
|
|
7043
7061
|
return e instanceof AuthorizationError || e instanceof UserAuthenticationExpiredError;
|
|
7044
7062
|
}
|
|
7045
7063
|
}
|
|
7046
|
-
/** True when `err` is a dial/consumer-op timeout rather than a real refusal — the one shared test
|
|
7047
|
-
* for "the operation ran out of its own budget", used both by {@link classifyProbeFailure} (#851:
|
|
7048
|
-
* a probe timeout must never collapse into `unreachable`, which a TLS-required target then
|
|
7049
|
-
* misreads as a trust failure) and by {@link Endpoint#disarmMembershipWatch}'s consumer-delete
|
|
7050
|
-
* cleanup, which predates it. */
|
|
7051
|
-
function isTimeoutError(err) {
|
|
7052
|
-
return err instanceof Error && (err.name === "TimeoutError" || /timeout/i.test(err.message));
|
|
7053
|
-
}
|
|
7054
7064
|
/** Like {@link isReachable}, but distinguishes "up but won't take these creds" from "nothing there".
|
|
7055
7065
|
* `spawn` needs the difference: auth-required → name the trust dir + next step; unreachable → the
|
|
7056
7066
|
* mesh is down (prune the stale entry, tell the user to `cotal up`). Pass `creds` to confirm a
|
|
@@ -7116,10 +7126,10 @@ function classifyProbeFailure(e, opts) {
|
|
|
7116
7126
|
// The broker answered but rejected these creds (so it IS up) — auth-required, not stale-auth.
|
|
7117
7127
|
if (e instanceof AuthorizationError)
|
|
7118
7128
|
return { ok: false, reason: "auth-required" };
|
|
7119
|
-
// A dial that ran out of its own budget is neither a refusal nor a dead broker — it is latency
|
|
7120
|
-
// `
|
|
7121
|
-
//
|
|
7122
|
-
if (e
|
|
7129
|
+
// A dial that ran out of its own budget is neither a refusal nor a dead broker — it is latency,
|
|
7130
|
+
// and grading it `unreachable` made a TLS-required target read as a trust failure (#851). Only
|
|
7131
|
+
// nats-core's TimeoutError means that: a peer's `-ERR` that mentions a timeout answered at once.
|
|
7132
|
+
if (e instanceof TimeoutError)
|
|
7123
7133
|
return { ok: false, reason: "timeout" };
|
|
7124
7134
|
return { ok: false, reason: "unreachable" };
|
|
7125
7135
|
}
|