@cotal-ai/core 0.56.1 → 0.58.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/auth-provider.d.ts +13 -1
- package/dist/auth-provider.d.ts.map +1 -1
- package/dist/auth-provider.js.map +1 -1
- package/dist/endpoint-envelope.js +2 -2
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-grants.d.ts +1 -1
- package/dist/endpoint-grants.d.ts.map +1 -1
- package/dist/endpoint-grants.js +9 -6
- package/dist/endpoint-grants.js.map +1 -1
- package/dist/endpoint-handle.d.ts.map +1 -1
- package/dist/endpoint-handle.js +5 -2
- package/dist/endpoint-handle.js.map +1 -1
- package/dist/endpoint-subjects.d.ts +6 -3
- package/dist/endpoint-subjects.d.ts.map +1 -1
- package/dist/endpoint-subjects.js +5 -5
- package/dist/endpoint-subjects.js.map +1 -1
- package/dist/endpoint-verbs.d.ts +1 -1
- package/dist/endpoint-verbs.d.ts.map +1 -1
- package/dist/endpoint-verbs.js +2 -2
- package/dist/endpoint-verbs.js.map +1 -1
- package/dist/endpoint.d.ts +27 -2
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +67 -8
- package/dist/endpoint.js.map +1 -1
- package/dist/kv-scan.d.ts +5 -2
- package/dist/kv-scan.d.ts.map +1 -1
- package/dist/kv-scan.js +103 -27
- package/dist/kv-scan.js.map +1 -1
- package/dist/members.d.ts +10 -0
- package/dist/members.d.ts.map +1 -1
- package/dist/members.js +28 -1
- package/dist/members.js.map +1 -1
- package/dist/membership-feed.d.ts.map +1 -1
- package/dist/membership-feed.js +19 -6
- package/dist/membership-feed.js.map +1 -1
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +28 -25
- package/dist/provision.js.map +1 -1
- package/dist/remote-manager-authority.d.ts +120 -1
- package/dist/remote-manager-authority.d.ts.map +1 -1
- package/dist/remote-manager-authority.js.map +1 -1
- package/dist/run-record.d.ts +19 -0
- package/dist/run-record.d.ts.map +1 -1
- package/dist/run-record.js +20 -0
- package/dist/run-record.js.map +1 -1
- package/dist/streams.d.ts +51 -1
- package/dist/streams.d.ts.map +1 -1
- package/dist/streams.js +214 -29
- package/dist/streams.js.map +1 -1
- package/dist/subjects.d.ts +22 -5
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +25 -6
- package/dist/subjects.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/endpoint.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createConnection } from "node:net";
|
|
|
4
4
|
import { connect, credsAuthenticator, headers, tokenAuthenticator, nanos, AuthorizationError, PermissionViolationError, UserAuthenticationExpiredError, NoRespondersError, RequestError, } from "@nats-io/transport-node";
|
|
5
5
|
import { wsconnect } from "@nats-io/nats-core";
|
|
6
6
|
import { credsClaims, credsFingerprint, credsRenewalDelayMs, idFromCreds } from "./identity.js";
|
|
7
|
+
import { requireBrokerFloor } from "./broker-floor.js";
|
|
7
8
|
import { inspectCredHealth } from "./provision.js";
|
|
8
9
|
import { parseSecretStoreIdentity, } from "./secret-store.js";
|
|
9
10
|
import { resolveService, invokeCommand, submitAndFollowGoal } from "./endpoint-invoke.js";
|
|
@@ -19,7 +20,7 @@ import { jetstream, jetstreamManager, AckPolicy, DeliverPolicy, JetStreamApiCode
|
|
|
19
20
|
import {} from "@nats-io/jetstream";
|
|
20
21
|
import { Kvm } from "@nats-io/kv";
|
|
21
22
|
import { Bucket, KvWatchInclude } from "@nats-io/kv/internal";
|
|
22
|
-
import { openMembersRegistry, commitMember, tombstoneMember, activateMember, readMember, listMembers, durableEligible, StaleMembershipWrite, } from "./members.js";
|
|
23
|
+
import { openMembersRegistry, commitMember, tombstoneMember, activateMember, readMember, listMembers, listLifecycleMemberChannels, durableEligible, StaleMembershipWrite, } from "./members.js";
|
|
23
24
|
import { openAclRegistry, readAcl, readAclForAlias, AmbiguousAclAlias, commitAcl as writeAclRecord, reissueAcl as writeAclReissue } from "./acls.js";
|
|
24
25
|
import { openDeliveryRegistry } from "./lease.js";
|
|
25
26
|
import { openChannelRegistry, effectiveReplay, effectiveReplayWindowMs, effectiveDeliveryClass, readChannelConfig, readChannelDefaults, } from "./channels.js";
|
|
@@ -143,6 +144,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
143
144
|
authExpiryReconnectTimer;
|
|
144
145
|
sentinelCreds;
|
|
145
146
|
tls;
|
|
147
|
+
transportPingIntervalMs;
|
|
148
|
+
transportMaxPingOut;
|
|
146
149
|
heartbeatMs;
|
|
147
150
|
ttlMs;
|
|
148
151
|
doRegister;
|
|
@@ -483,6 +486,14 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
483
486
|
this.user = opts.user;
|
|
484
487
|
this.pass = opts.pass;
|
|
485
488
|
this.tls = opts.tls ?? false;
|
|
489
|
+
for (const [label, value] of [["transportPingIntervalMs", opts.transportPingIntervalMs], ["transportMaxPingOut", opts.transportMaxPingOut]]) {
|
|
490
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0))
|
|
491
|
+
throw new Error(`EndpointOptions.${label} must be a positive safe integer`);
|
|
492
|
+
}
|
|
493
|
+
if ((opts.transportPingIntervalMs === undefined) !== (opts.transportMaxPingOut === undefined))
|
|
494
|
+
throw new Error("EndpointOptions transportPingIntervalMs and transportMaxPingOut must be configured together");
|
|
495
|
+
this.transportPingIntervalMs = opts.transportPingIntervalMs;
|
|
496
|
+
this.transportMaxPingOut = opts.transportMaxPingOut;
|
|
486
497
|
// No implicit channel: an endpoint reads exactly what its caller lists. Omitted means none.
|
|
487
498
|
this.channels = opts.channels ?? [];
|
|
488
499
|
this.heartbeatMs = opts.heartbeatMs ?? 2000;
|
|
@@ -849,6 +860,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
849
860
|
CotalEndpoint.assertRenewableGeneration(candidate, this.currentCreds, delay);
|
|
850
861
|
this.currentCreds = candidate;
|
|
851
862
|
this.armCredsRefresh(delay);
|
|
863
|
+
this.emit("creds-adopted", credsClaims(candidate));
|
|
852
864
|
return credsClaims(candidate);
|
|
853
865
|
}
|
|
854
866
|
/** The 75%-of-lifetime renewal timer tick: prove + adopt + swap the LIVE connection. Preflights (it
|
|
@@ -979,6 +991,8 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
979
991
|
// sub.allow=[_INBOX_<connId>.>] it stops a peer from subscribing the wildcard inbox to sniff
|
|
980
992
|
// others' DM deliveries. Set unconditionally so the prefix can never drift from the ACL.
|
|
981
993
|
inboxPrefix: `_INBOX_${this.connId}`,
|
|
994
|
+
...(this.transportPingIntervalMs === undefined ? {} : { pingInterval: this.transportPingIntervalMs }),
|
|
995
|
+
...(this.transportMaxPingOut === undefined ? {} : { maxPingOut: this.transportMaxPingOut }),
|
|
982
996
|
// The bearer rides a GETTER: nats.js re-evaluates the token authenticator per (re)connect
|
|
983
997
|
// attempt, so internal reconnects present whatever refreshBearer last fetched.
|
|
984
998
|
// Creds ALWAYS ride the CHECKED getter, renewed or static, so every attempt — including the
|
|
@@ -992,6 +1006,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
992
1006
|
// instead. Anonymous access stays reachable the only way it should be: by passing no creds.
|
|
993
1007
|
...authOpts({ token: this.token, user: this.user, pass: this.pass, creds: this.currentCreds !== undefined || this.credsSource ? () => this.credsForWire() : undefined, bearer: this.userMode ? () => this.currentBearer : undefined, sentinelCreds: this.sentinelCreds, tls: this.tls }),
|
|
994
1008
|
});
|
|
1009
|
+
// SPEC §13.12: the control surface requires nats-server >= 2.12; this runs on every
|
|
1010
|
+
// fresh connection, including the reconnects the library performs on its own here.
|
|
1011
|
+
requireBrokerFloor(this.nc);
|
|
995
1012
|
this.armAuthExpiryReconnectFence(this.nc);
|
|
996
1013
|
this.watchStatus();
|
|
997
1014
|
this.js = jetstream(this.nc);
|
|
@@ -1759,6 +1776,14 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1759
1776
|
}
|
|
1760
1777
|
/** Unicast: direct message to one specific instance. */
|
|
1761
1778
|
async unicast(instanceId, text, opts) {
|
|
1779
|
+
const { msg } = await this.unicastAttributed(instanceId, text, opts);
|
|
1780
|
+
return msg;
|
|
1781
|
+
}
|
|
1782
|
+
/** Unicast, returning the JetStream publish ack alongside the message: the only truthful proof
|
|
1783
|
+
* a sender has is that the broker stored the message at a sequence, never that anyone read it. */
|
|
1784
|
+
async unicastAttributed(instanceId, text, opts) {
|
|
1785
|
+
if (!this.js)
|
|
1786
|
+
throw new Error(this.notLiveMsg());
|
|
1762
1787
|
const msg = {
|
|
1763
1788
|
id: randomUUID(),
|
|
1764
1789
|
ts: Date.now(),
|
|
@@ -1774,8 +1799,11 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1774
1799
|
const recip = parsePrincipalKey(instanceId);
|
|
1775
1800
|
if (!recip)
|
|
1776
1801
|
throw new Error(`unicast: "${instanceId}" is not a valid recipient principal <owner>.<actor>`);
|
|
1777
|
-
|
|
1778
|
-
|
|
1802
|
+
assertPartsSerializable(msg.parts);
|
|
1803
|
+
// Publish DIRECTLY rather than through publishMsg, the way multicastExpecting does: this path
|
|
1804
|
+
// must read the ack, and publishMsg deliberately discards it.
|
|
1805
|
+
const ack = await this.js.publish(unicastSubject(this.space, recip.owner, recip.actor, this.owner, this.actor), JSON.stringify(msg), { msgID: msg.id });
|
|
1806
|
+
return { msg, ack: { seq: ack.seq, duplicate: ack.duplicate === true } };
|
|
1779
1807
|
}
|
|
1780
1808
|
/** Anycast: deliver to ANY one instance of a service (role) — queue-group load balancing. */
|
|
1781
1809
|
async anycast(service, text, opts) {
|
|
@@ -2233,17 +2261,23 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2233
2261
|
/** Overlay the host's live model and optional variant onto the card's display-only metadata, then
|
|
2234
2262
|
* republish presence. For connectors that learn their actual selection only after launch (e.g.
|
|
2235
2263
|
* Claude Code's `SessionStart` hook). The mutated card is read live by every later publish, so even
|
|
2236
|
-
* a pre-connect call surfaces on the first presence write.
|
|
2237
|
-
|
|
2264
|
+
* a pre-connect call surfaces on the first presence write. `provider` is the effective provider
|
|
2265
|
+
* the connector reported serving the model; when omitted an existing `meta.provider` is left
|
|
2266
|
+
* untouched (unlike `variant`, it is never deleted, since a caller that does not know the
|
|
2267
|
+
* provider says nothing about whether one still applies). */
|
|
2268
|
+
async setCardModel(model, variant, provider) {
|
|
2238
2269
|
const m = model.trim();
|
|
2239
2270
|
const v = variant?.trim();
|
|
2240
|
-
|
|
2271
|
+
const p = provider?.trim();
|
|
2272
|
+
if (!m || (this.card.meta?.model === m && this.card.meta.variant === v && (p === undefined || this.card.meta?.provider === p)))
|
|
2241
2273
|
return;
|
|
2242
2274
|
const meta = { ...(this.card.meta ?? {}), model: m };
|
|
2243
2275
|
if (v)
|
|
2244
2276
|
meta.variant = v;
|
|
2245
2277
|
else
|
|
2246
2278
|
delete meta.variant;
|
|
2279
|
+
if (p)
|
|
2280
|
+
meta.provider = p;
|
|
2247
2281
|
this.card.meta = meta;
|
|
2248
2282
|
await this.publishPresence();
|
|
2249
2283
|
}
|
|
@@ -3988,7 +4022,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3988
4022
|
async startPlane3(aclFor, opts = {}) {
|
|
3989
4023
|
if (!this.js)
|
|
3990
4024
|
throw new Error("endpoint not started");
|
|
3991
|
-
this.plane3 = { aclFor, reloadMembershipCreds: opts.reloadMembershipCreds, evictPrincipal: opts.evictPrincipal, planeConnLiveness: opts.planeConnLiveness, principalLiveness: opts.principalLiveness, reloadStoreIdentity: opts.reloadStoreIdentity };
|
|
4025
|
+
this.plane3 = { aclFor, reloadMembershipCreds: opts.reloadMembershipCreds, evictPrincipal: opts.evictPrincipal, planeConnLiveness: opts.planeConnLiveness, principalLiveness: opts.principalLiveness, reloadStoreIdentity: opts.reloadStoreIdentity, onDeliveryCredsAdopted: opts.onDeliveryCredsAdopted };
|
|
3992
4026
|
await this.armPlane3();
|
|
3993
4027
|
}
|
|
3994
4028
|
/** Serve one runtime durable-membership control request (the server-side delivery daemon). The caller
|
|
@@ -4388,8 +4422,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
4388
4422
|
// Arm the resident wire swap ONLY now — after BOTH proofs settled, right before the reply is
|
|
4389
4423
|
// returned+responded — so a slow membership proof can never let the delivery reconnect strand
|
|
4390
4424
|
// this reply. Only when delivery actually adopted a new candidate (currentCreds was updated).
|
|
4391
|
-
if (delivery.ok)
|
|
4425
|
+
if (delivery.ok) {
|
|
4426
|
+
this.plane3?.onDeliveryCredsAdopted?.();
|
|
4392
4427
|
this.scheduleResidentSwap();
|
|
4428
|
+
}
|
|
4393
4429
|
return failures.length
|
|
4394
4430
|
? { ok: false, error: failures.join("; "), data: { delivery, membership } }
|
|
4395
4431
|
: { ok: true, data: { delivery, membership } };
|
|
@@ -4425,6 +4461,29 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
4425
4461
|
return { ok: false, error: e.message };
|
|
4426
4462
|
}
|
|
4427
4463
|
}
|
|
4464
|
+
if (req.op === "lifecycleMemberships") {
|
|
4465
|
+
// The terminal teardown's membership INVENTORY: a complete, read-only, lifecycle-exact listing
|
|
4466
|
+
// of one principal's durable membership rows (tombstones included) from the trusted daemon that
|
|
4467
|
+
// owns the members bucket. The caller deletes only the exact keys it gets back, through its
|
|
4468
|
+
// target-pinned deprovisioner grant; this verb deletes nothing and returns no other lifecycle.
|
|
4469
|
+
const principal = typeof req.args?.principal === "string" ? req.args.principal.trim() : "";
|
|
4470
|
+
const uid = typeof req.args?.lifecycleUid === "string" ? req.args.lifecycleUid : "";
|
|
4471
|
+
if (!parsePrincipalKey(principal))
|
|
4472
|
+
return { ok: false, error: "lifecycleMemberships: a principal (owner.actor dot-form) is required" };
|
|
4473
|
+
try {
|
|
4474
|
+
assertLifecycleToken(uid);
|
|
4475
|
+
}
|
|
4476
|
+
catch (e) {
|
|
4477
|
+
return { ok: false, error: `lifecycleMemberships: ${e.message}` };
|
|
4478
|
+
}
|
|
4479
|
+
try {
|
|
4480
|
+
const channels = await listLifecycleMemberChannels(await this.membersRegistry(), principal, uid);
|
|
4481
|
+
return { ok: true, data: { complete: true, channels } };
|
|
4482
|
+
}
|
|
4483
|
+
catch (e) {
|
|
4484
|
+
return { ok: false, error: `lifecycleMemberships: the inventory read did not complete (${e.message})` };
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4428
4487
|
if (req.op === "principalLiveness") {
|
|
4429
4488
|
// The freeze-holder liveness probe (#391): the READ-ONLY half of `evictPrincipal`. A repair
|
|
4430
4489
|
// that must REFUSE while the holder is alive cannot use eviction as its own precheck — that
|