@cotal-ai/core 0.70.0 → 0.71.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/agent-file.d.ts.map +1 -1
  2. package/dist/agent-file.js +16 -2
  3. package/dist/agent-file.js.map +1 -1
  4. package/dist/auth-provider.d.ts +9 -1
  5. package/dist/auth-provider.d.ts.map +1 -1
  6. package/dist/auth-provider.js +10 -0
  7. package/dist/auth-provider.js.map +1 -1
  8. package/dist/channels.js +1 -1
  9. package/dist/channels.js.map +1 -1
  10. package/dist/connector-setup.d.ts +9 -5
  11. package/dist/connector-setup.d.ts.map +1 -1
  12. package/dist/connector.d.ts +2 -1
  13. package/dist/connector.d.ts.map +1 -1
  14. package/dist/connector.js.map +1 -1
  15. package/dist/endpoint-action.d.ts +3 -0
  16. package/dist/endpoint-action.d.ts.map +1 -1
  17. package/dist/endpoint-action.js +8 -6
  18. package/dist/endpoint-action.js.map +1 -1
  19. package/dist/endpoint-records.d.ts +2 -1
  20. package/dist/endpoint-records.d.ts.map +1 -1
  21. package/dist/endpoint-records.js +1 -1
  22. package/dist/endpoint-records.js.map +1 -1
  23. package/dist/endpoint.d.ts +3 -1
  24. package/dist/endpoint.d.ts.map +1 -1
  25. package/dist/endpoint.js +18 -25
  26. package/dist/endpoint.js.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/kv-scan.d.ts.map +1 -1
  32. package/dist/kv-scan.js +1 -6
  33. package/dist/kv-scan.js.map +1 -1
  34. package/dist/provision.js +1 -1
  35. package/dist/provision.js.map +1 -1
  36. package/dist/run-driver-grants.d.ts.map +1 -1
  37. package/dist/run-driver-grants.js +5 -1
  38. package/dist/run-driver-grants.js.map +1 -1
  39. package/dist/secret-fs.d.ts +4 -3
  40. package/dist/secret-fs.d.ts.map +1 -1
  41. package/dist/secret-fs.js +10 -11
  42. package/dist/secret-fs.js.map +1 -1
  43. package/dist/startup-confirm.d.ts +20 -0
  44. package/dist/startup-confirm.d.ts.map +1 -0
  45. package/dist/startup-confirm.js +49 -0
  46. package/dist/startup-confirm.js.map +1 -0
  47. package/dist/streams.d.ts +2 -2
  48. package/dist/streams.js +2 -2
  49. package/dist/subjects.d.ts +6 -7
  50. package/dist/subjects.d.ts.map +1 -1
  51. package/dist/subjects.js +20 -10
  52. package/dist/subjects.js.map +1 -1
  53. 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";
@@ -26,7 +26,7 @@ import { openMembersRegistry, commitMember, tombstoneMember, activateMember, rea
26
26
  import { openAclRegistry, readAcl, readAclForAlias, aclRetired, AmbiguousAclAlias, commitAcl as writeAclRecord, reissueAcl as writeAclReissue } from "./acls.js";
27
27
  import { openDeliveryRegistry } from "./lease.js";
28
28
  import { openChannelRegistry, effectiveReplay, effectiveReplayWindowMs, effectiveDeliveryClass, readChannelConfig, readChannelDefaults, } from "./channels.js";
29
- import { anycastSubject, CHANNEL_DEFAULTS_KEY, chatStream, chatHistDurable, chatSubject, controlServiceSubject, livenessSubject, livenessServeFilter, CONTROL_DELIVERY, CONTROL_DELIVERY_ADMIN, dmStream, dmDurable, dlvStream, dlvDurable, dlvSubject, dinboxSubject, inboxStream, parseDinboxPrincipal, FANOUT_DURABLE, INBOX_READER_DURABLE, leaseKey, managerBucket, MANAGER_LEASE_KEY, MANAGER_RENEWAL_LEASE_KEY, managerLeaseKey, chatWildcard, assertValidChannel, channelInAllow, isConcreteChannel, normalizeMentions, parseSubject, isPrincipalOwnerToken, assertInboxConnId, presenceBucket, membershipBucket, MEMBERSHIP_FEED_KEY, principalKey, parsePrincipalKey, assertLifecycleToken, mintLifecycleUid, lifecycleNameKey, DEV_OWNER, spacePrefix, spaceWildcard, subjectMatches, taskStream, taskDurable, token, unicastSubject, unicastRecvFilter, } from "./subjects.js";
29
+ import { anycastSubject, assertValidRole, CHANNEL_DEFAULTS_KEY, chatStream, chatHistDurable, chatSubject, controlServiceSubject, livenessSubject, livenessServeFilter, CONTROL_DELIVERY, CONTROL_DELIVERY_ADMIN, dmStream, dmDurable, dlvStream, dlvDurable, dlvSubject, dinboxSubject, inboxStream, parseDinboxPrincipal, FANOUT_DURABLE, INBOX_READER_DURABLE, leaseKey, managerBucket, MANAGER_LEASE_KEY, MANAGER_RENEWAL_LEASE_KEY, managerLeaseKey, chatWildcard, assertValidChannel, channelInAllow, isConcreteChannel, normalizeMentions, parseSubject, isPrincipalOwnerToken, assertInboxConnId, presenceBucket, membershipBucket, MEMBERSHIP_FEED_KEY, principalKey, parsePrincipalKey, assertLifecycleToken, mintLifecycleUid, lifecycleNameKey, DEV_OWNER, spacePrefix, spaceWildcard, subjectMatches, taskStream, taskDurable, token, unicastSubject, unicastRecvFilter, } from "./subjects.js";
30
30
  export const DEFAULT_SERVER = "nats://127.0.0.1:4222";
31
31
  const PLANE3_FRAME_HEADER = "Cotal-Delivery-Frame";
32
32
  /** Space joined when none is given on the CLI (the `cotal-<space>` cmux tab, etc.). */
@@ -403,8 +403,11 @@ export class CotalEndpoint extends EventEmitter {
403
403
  this.space = opts.space;
404
404
  // A display name is the client-side handle a peer is addressed by; reject the reserved `/`
405
405
  // (the future owner/name separator) and surrounding whitespace at the one identity choke
406
- // point every join/spawn path flows through.
406
+ // point every join/spawn path flows through. A role is an address too, refused here before
407
+ // presence can advertise it.
407
408
  assertValidName(opts.card.name);
409
+ if (opts.card.role !== undefined)
410
+ assertValidRole(opts.card.role);
408
411
  // Auth mode is EITHER static creds (dev/no-login) OR a user bearer (login → callout) — never both.
409
412
  if (opts.bearer) {
410
413
  if (opts.creds || opts.token || opts.user || opts.pass)
@@ -1842,6 +1845,8 @@ export class CotalEndpoint extends EventEmitter {
1842
1845
  async anycastAttributed(service, text, opts) {
1843
1846
  if (!this.js)
1844
1847
  throw new Error(this.notLiveMsg());
1848
+ // The subject builder admits `*` for allow rules; a publish needs one concrete role.
1849
+ assertValidRole(service);
1845
1850
  const msg = {
1846
1851
  id: randomUUID(),
1847
1852
  ts: Date.now(),
@@ -2785,8 +2790,8 @@ export class CotalEndpoint extends EventEmitter {
2785
2790
  watch.consumerName = undefined;
2786
2791
  }
2787
2792
  else {
2788
- const closedEpoch = err.name === "ClosedConnectionError" || /^closed connection$/i.test(err.message);
2789
- const timeout = isTimeoutError(err);
2793
+ const closedEpoch = err instanceof ClosedConnectionError;
2794
+ const timeout = err instanceof TimeoutError;
2790
2795
  const dyingEpochTimeout = timeout && (this.reconnecting || !this.nc || this.nc.isClosed());
2791
2796
  // Cleanup of an ordered consumer: a delete timeout means the broker did not answer in time,
2792
2797
  // not that the endpoint is unusable. The broker reaps an idle/ephemeral consumer anyway.
@@ -4727,9 +4732,7 @@ export class CotalEndpoint extends EventEmitter {
4727
4732
  probeFailureOutcome(e) {
4728
4733
  if (e instanceof AuthorizationError || e instanceof PermissionViolationError)
4729
4734
  return "refused";
4730
- const name = e?.name;
4731
- const msg = e?.message ?? "";
4732
- if (name === "TimeoutError" || /timeout/i.test(msg))
4735
+ if (e instanceof TimeoutError)
4733
4736
  return "timeout";
4734
4737
  // Anything else is a transport or client failure: it says something about our link, not about
4735
4738
  // the plane, so it is graded like a refusal rather than guessed at.
@@ -5472,7 +5475,9 @@ export class CotalEndpoint extends EventEmitter {
5472
5475
  await this.pump(taskStream(this.space), taskDurable(this.card.role));
5473
5476
  }
5474
5477
  }
5475
- /** Drive one consumer: decode, drop our own echo, and hand each message to listeners with ack control. */
5478
+ /** Drive one consumer: decode and hand each message to listeners with ack control. Our own sends are
5479
+ * delivered too: on the DM inbox and the role queue they were addressed to us, so none is an echo,
5480
+ * and acking one unseen on the work queue would delete the only copy of the request. */
5476
5481
  async pump(stream, durable) {
5477
5482
  if (!this.js)
5478
5483
  throw new Error("endpoint not started");
@@ -5506,10 +5511,6 @@ export class CotalEndpoint extends EventEmitter {
5506
5511
  `does not match subject sender ${parsed?.sender ?? "(unparseable)"}`));
5507
5512
  continue;
5508
5513
  }
5509
- if (msg.from.id === this.card.id) {
5510
- m.ack(); // our own echo — advance past it
5511
- continue;
5512
- }
5513
5514
  // No-replay + dedup (chat only): drop a message at/below this channel's join watermark
5514
5515
  // — pre-join history the New tail still carries for a *lagging* joiner (cursor behind the
5515
5516
  // frontier), and the overlap a replay backfill already delivered. Must ack, or JetStream
@@ -7065,14 +7066,6 @@ export async function isReachable(servers = DEFAULT_SERVER, opts = {}) {
7065
7066
  return e instanceof AuthorizationError || e instanceof UserAuthenticationExpiredError;
7066
7067
  }
7067
7068
  }
7068
- /** True when `err` is a dial/consumer-op timeout rather than a real refusal — the one shared test
7069
- * for "the operation ran out of its own budget", used both by {@link classifyProbeFailure} (#851:
7070
- * a probe timeout must never collapse into `unreachable`, which a TLS-required target then
7071
- * misreads as a trust failure) and by {@link Endpoint#disarmMembershipWatch}'s consumer-delete
7072
- * cleanup, which predates it. */
7073
- function isTimeoutError(err) {
7074
- return err instanceof Error && (err.name === "TimeoutError" || /timeout/i.test(err.message));
7075
- }
7076
7069
  /** Like {@link isReachable}, but distinguishes "up but won't take these creds" from "nothing there".
7077
7070
  * `spawn` needs the difference: auth-required → name the trust dir + next step; unreachable → the
7078
7071
  * mesh is down (prune the stale entry, tell the user to `cotal up`). Pass `creds` to confirm a
@@ -7138,10 +7131,10 @@ function classifyProbeFailure(e, opts) {
7138
7131
  // The broker answered but rejected these creds (so it IS up) — auth-required, not stale-auth.
7139
7132
  if (e instanceof AuthorizationError)
7140
7133
  return { ok: false, reason: "auth-required" };
7141
- // A dial that ran out of its own budget is neither a refusal nor a dead broker — it is latency.
7142
- // `e` is undefined when the tcpDialable gate refused before any connect() attempt; that path has
7143
- // no timeout to inspect and must stay `unreachable` (nothing answered at all).
7144
- if (e !== undefined && isTimeoutError(e))
7134
+ // A dial that ran out of its own budget is neither a refusal nor a dead broker — it is latency,
7135
+ // and grading it `unreachable` made a TLS-required target read as a trust failure (#851). Only
7136
+ // nats-core's TimeoutError means that: a peer's `-ERR` that mentions a timeout answered at once.
7137
+ if (e instanceof TimeoutError)
7145
7138
  return { ok: false, reason: "timeout" };
7146
7139
  return { ok: false, reason: "unreachable" };
7147
7140
  }