@cotal-ai/core 0.84.0 → 0.86.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/agent-file.d.ts.map +1 -1
- package/dist/agent-file.js +10 -3
- package/dist/agent-file.js.map +1 -1
- package/dist/base64url.d.ts +5 -0
- package/dist/base64url.d.ts.map +1 -0
- package/dist/base64url.js +14 -0
- package/dist/base64url.js.map +1 -0
- package/dist/browser-control.d.ts +97 -0
- package/dist/browser-control.d.ts.map +1 -0
- package/dist/browser-control.js +66 -0
- package/dist/browser-control.js.map +1 -0
- package/dist/browser-validators-build.d.ts +23 -0
- package/dist/browser-validators-build.d.ts.map +1 -0
- package/dist/browser-validators-build.js +149 -0
- package/dist/browser-validators-build.js.map +1 -0
- package/dist/canonical-json.d.ts +14 -0
- package/dist/canonical-json.d.ts.map +1 -0
- package/dist/canonical-json.js +121 -0
- package/dist/canonical-json.js.map +1 -0
- package/dist/canonical.d.ts +2 -10
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +2 -108
- package/dist/canonical.js.map +1 -1
- package/dist/checkpoint-answer.d.ts.map +1 -1
- package/dist/checkpoint-answer.js +3 -4
- package/dist/checkpoint-answer.js.map +1 -1
- package/dist/connector-setup.d.ts +10 -1
- package/dist/connector-setup.d.ts.map +1 -1
- package/dist/connector.d.ts +8 -2
- package/dist/connector.d.ts.map +1 -1
- package/dist/connector.js.map +1 -1
- package/dist/digest-id.d.ts +4 -0
- package/dist/digest-id.d.ts.map +1 -0
- package/dist/digest-id.js +7 -0
- package/dist/digest-id.js.map +1 -0
- package/dist/endpoint-action.d.ts.map +1 -1
- package/dist/endpoint-action.js +2 -16
- package/dist/endpoint-action.js.map +1 -1
- package/dist/endpoint-auth-digest.d.ts +7 -0
- package/dist/endpoint-auth-digest.d.ts.map +1 -0
- package/dist/endpoint-auth-digest.js +13 -0
- package/dist/endpoint-auth-digest.js.map +1 -0
- package/dist/endpoint-call.d.ts +176 -0
- package/dist/endpoint-call.d.ts.map +1 -0
- package/dist/endpoint-call.js +490 -0
- package/dist/endpoint-call.js.map +1 -0
- package/dist/endpoint-caller.d.ts +113 -0
- package/dist/endpoint-caller.d.ts.map +1 -0
- package/dist/endpoint-caller.js +260 -0
- package/dist/endpoint-caller.js.map +1 -0
- package/dist/endpoint-checkpoint.d.ts.map +1 -1
- package/dist/endpoint-checkpoint.js +5 -14
- package/dist/endpoint-checkpoint.js.map +1 -1
- package/dist/endpoint-envelope.d.ts +0 -6
- package/dist/endpoint-envelope.d.ts.map +1 -1
- package/dist/endpoint-envelope.js +17 -18
- package/dist/endpoint-envelope.js.map +1 -1
- package/dist/endpoint-guard.d.ts.map +1 -1
- package/dist/endpoint-guard.js +2 -10
- package/dist/endpoint-guard.js.map +1 -1
- package/dist/endpoint-invoke.d.ts +36 -119
- package/dist/endpoint-invoke.d.ts.map +1 -1
- package/dist/endpoint-invoke.js +56 -300
- package/dist/endpoint-invoke.js.map +1 -1
- package/dist/endpoint-publish-denial.d.ts +1 -1
- package/dist/endpoint-publish-denial.d.ts.map +1 -1
- package/dist/endpoint-publish-denial.js +1 -1
- package/dist/endpoint-publish-denial.js.map +1 -1
- package/dist/endpoint-receipt.d.ts.map +1 -1
- package/dist/endpoint-receipt.js +2 -16
- package/dist/endpoint-receipt.js.map +1 -1
- package/dist/endpoint-subjects.d.ts.map +1 -1
- package/dist/endpoint-subjects.js.map +1 -1
- package/dist/endpoint-traits.d.ts.map +1 -1
- package/dist/endpoint-traits.js +3 -20
- package/dist/endpoint-traits.js.map +1 -1
- package/dist/endpoint-verbs.d.ts +18 -151
- package/dist/endpoint-verbs.d.ts.map +1 -1
- package/dist/endpoint-verbs.js +4 -500
- package/dist/endpoint-verbs.js.map +1 -1
- package/dist/endpoint-work.d.ts.map +1 -1
- package/dist/endpoint-work.js +2 -17
- package/dist/endpoint-work.js.map +1 -1
- package/dist/endpoint.d.ts +39 -15
- package/dist/endpoint.d.ts.map +1 -1
- package/dist/endpoint.js +90 -42
- package/dist/endpoint.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/issued-accepted.d.ts +27 -0
- package/dist/issued-accepted.d.ts.map +1 -0
- package/dist/issued-accepted.js +62 -0
- package/dist/issued-accepted.js.map +1 -0
- package/dist/issued-authority.d.ts +3 -18
- package/dist/issued-authority.d.ts.map +1 -1
- package/dist/issued-authority.js +4 -55
- package/dist/issued-authority.js.map +1 -1
- package/dist/lifecycle-state.d.ts +3 -0
- package/dist/lifecycle-state.d.ts.map +1 -1
- package/dist/lifecycle-state.js +7 -0
- package/dist/lifecycle-state.js.map +1 -1
- package/dist/manager-caller.d.ts.map +1 -1
- package/dist/manager-caller.js +2 -1
- package/dist/manager-caller.js.map +1 -1
- package/dist/one-line.d.ts +6 -0
- package/dist/one-line.d.ts.map +1 -0
- package/dist/one-line.js +8 -0
- package/dist/one-line.js.map +1 -0
- package/dist/peer-label.d.ts +16 -0
- package/dist/peer-label.d.ts.map +1 -0
- package/dist/peer-label.js +15 -0
- package/dist/peer-label.js.map +1 -0
- package/dist/permission-denied.d.ts +8 -0
- package/dist/permission-denied.d.ts.map +1 -0
- package/dist/permission-denied.js +15 -0
- package/dist/permission-denied.js.map +1 -0
- package/dist/presence-text.d.ts +6 -0
- package/dist/presence-text.d.ts.map +1 -0
- package/dist/presence-text.js +25 -0
- package/dist/presence-text.js.map +1 -0
- package/dist/provision.d.ts.map +1 -1
- package/dist/provision.js +8 -2
- package/dist/provision.js.map +1 -1
- package/dist/resolve.d.ts +2 -13
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +2 -12
- package/dist/resolve.js.map +1 -1
- package/dist/run-driver-grants.d.ts +5 -0
- package/dist/run-driver-grants.d.ts.map +1 -1
- package/dist/run-driver-grants.js +16 -4
- package/dist/run-driver-grants.js.map +1 -1
- package/dist/run-migration.d.ts +21 -0
- package/dist/run-migration.d.ts.map +1 -1
- package/dist/run-migration.js +2 -27
- package/dist/run-migration.js.map +1 -1
- package/dist/run-notice.d.ts +20 -0
- package/dist/run-notice.d.ts.map +1 -1
- package/dist/run-notice.js +3 -28
- package/dist/run-notice.js.map +1 -1
- package/dist/schema-bounds.d.ts +24 -0
- package/dist/schema-bounds.d.ts.map +1 -0
- package/dist/schema-bounds.js +74 -0
- package/dist/schema-bounds.js.map +1 -0
- package/dist/schema-profile.d.ts +11 -24
- package/dist/schema-profile.d.ts.map +1 -1
- package/dist/schema-profile.js +16 -73
- package/dist/schema-profile.js.map +1 -1
- package/dist/startup-confirm.d.ts +9 -5
- package/dist/startup-confirm.d.ts.map +1 -1
- package/dist/startup-confirm.js +51 -18
- package/dist/startup-confirm.js.map +1 -1
- package/dist/subjects.d.ts.map +1 -1
- package/dist/subjects.js +1 -2
- package/dist/subjects.js.map +1 -1
- package/dist/turn-contract.d.ts +94 -0
- package/dist/turn-contract.d.ts.map +1 -0
- package/dist/turn-contract.js +62 -0
- package/dist/turn-contract.js.map +1 -0
- package/package.json +14 -1
package/dist/endpoint.js
CHANGED
|
@@ -29,6 +29,8 @@ import { openAclRegistry, readAcl, readAclForAlias, aclRetired, AmbiguousAclAlia
|
|
|
29
29
|
import { openDeliveryRegistry } from "./lease.js";
|
|
30
30
|
import { openChannelRegistry, effectiveReplay, effectiveReplayWindowMs, effectiveDeliveryClass, readChannelConfig, readChannelDefaults, decodeChannelEntry, } from "./channels.js";
|
|
31
31
|
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";
|
|
32
|
+
import { isPermissionDenied } from "./permission-denied.js";
|
|
33
|
+
export { isPermissionDenied };
|
|
32
34
|
export const DEFAULT_SERVER = "nats://127.0.0.1:4222";
|
|
33
35
|
const PLANE3_FRAME_HEADER = "Cotal-Delivery-Frame";
|
|
34
36
|
/** Space joined when none is given on the CLI (the `cotal-<space>` cmux tab, etc.). */
|
|
@@ -178,6 +180,14 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
178
180
|
* live iterator. See {@link watchDeliveryLease}. */
|
|
179
181
|
leaseWatchIntent;
|
|
180
182
|
leaseWatchIter;
|
|
183
|
+
/** Consumer deletes of retired lease watches still in flight. The stop handle cannot await its
|
|
184
|
+
* delete, so {@link stop} and a live-epoch rebuild await these before they close the connection
|
|
185
|
+
* that carries them (#3966). */
|
|
186
|
+
leaseWatchDeletes = new Set();
|
|
187
|
+
/** Lease-watch binds still awaiting the broker. One that lands after {@link stop} or a rebuild
|
|
188
|
+
* moved the epoch retires its watch and starts that delete itself, so the same two paths wait for
|
|
189
|
+
* these first. Each is bounded by the JetStream request timeout. */
|
|
190
|
+
leaseWatchBinds = new Set();
|
|
181
191
|
/** Plane-3 durable-membership registry KV — lazily opened by the privileged delivery daemon (or a
|
|
182
192
|
* short-lived provisioner). */
|
|
183
193
|
membersKv;
|
|
@@ -294,9 +304,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
294
304
|
presenceViewState = "unpopulated";
|
|
295
305
|
/** A presence-watch rebind in flight (see {@link rebindStalePresenceWatch}); one at a time. */
|
|
296
306
|
presenceRebind;
|
|
297
|
-
/** Bumped by every connection-scoped teardown and by {@link stop}. A presence
|
|
298
|
-
* awaiting the broker when the epoch moved belongs to a retired epoch: it releases the
|
|
299
|
-
* iterator it got and installs nothing (see {@link startPresenceWatch}
|
|
307
|
+
/** Bumped by every connection-scoped teardown and by {@link stop}. A presence or lease-watch bind
|
|
308
|
+
* that was awaiting the broker when the epoch moved belongs to a retired epoch: it releases the
|
|
309
|
+
* iterator it got and installs nothing (see {@link startPresenceWatch} and
|
|
310
|
+
* {@link bindDeliveryLeaseWatch}). */
|
|
300
311
|
presenceEpoch = 0;
|
|
301
312
|
/** The current watch was bound onto a bucket with NO keys (see {@link markPresenceBucketEmpty}).
|
|
302
313
|
* Such a watch cannot deliver until someone writes, so its silence is not staleness. */
|
|
@@ -1087,9 +1098,9 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1087
1098
|
await this.startChannelWatch();
|
|
1088
1099
|
}
|
|
1089
1100
|
// Rebind the daemon's lease-loss watch onto the fresh connection (#1596). The intent survived
|
|
1090
|
-
// clearConnectionScoped; a bind that completes after a stop or another rebuild
|
|
1091
|
-
// and
|
|
1092
|
-
// in the null window still reaches the trigger.
|
|
1101
|
+
// clearConnectionScoped; a bind that completes after a stop or another rebuild finds the epoch
|
|
1102
|
+
// moved and retires itself. The replay re-delivers the row's current state, so a change that
|
|
1103
|
+
// landed in the null window still reaches the trigger.
|
|
1093
1104
|
if (this.leaseWatchIntent)
|
|
1094
1105
|
await this.bindDeliveryLeaseWatch();
|
|
1095
1106
|
// FAIL BEFORE PRESENCE (SPEC 13.1): an AUTHED endpoint that will register on the roster or
|
|
@@ -1432,7 +1443,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1432
1443
|
// Manual reconnect still has a live old epoch: complete broker-consumer cleanup before close.
|
|
1433
1444
|
// Terminal self-heal has an already-closed epoch: disarm retains stream/name for fresh cleanup.
|
|
1434
1445
|
if (oldNc && !oldNc.isClosed())
|
|
1435
|
-
await Promise.all([...this.membershipFeedWatches].map((watch) => watch.arm));
|
|
1446
|
+
await Promise.all([...[...this.membershipFeedWatches].map((watch) => watch.arm), this.settleDeliveryLeaseWatches()]);
|
|
1436
1447
|
this.nc = undefined;
|
|
1437
1448
|
this.js = undefined;
|
|
1438
1449
|
this.jsm = undefined;
|
|
@@ -1629,6 +1640,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
1629
1640
|
catch {
|
|
1630
1641
|
/* best-effort graceful leave */
|
|
1631
1642
|
}
|
|
1643
|
+
await this.settleDeliveryLeaseWatches();
|
|
1632
1644
|
await this.closeWithoutLibraryReconnect(this.nc);
|
|
1633
1645
|
// #1356: stop() does its own teardown and never runs clearConnectionScoped, so clear here too —
|
|
1634
1646
|
// AFTER the best-effort offline publish above, which would otherwise re-record a refusal on its
|
|
@@ -2397,6 +2409,18 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2397
2409
|
this.card.meta = meta;
|
|
2398
2410
|
await this.publishPresence();
|
|
2399
2411
|
}
|
|
2412
|
+
/** Overlay the session's working directory onto the card's display-only `meta.cwd`, then
|
|
2413
|
+
* republish presence. For connectors that learn the directory from the harness after launch (e.g.
|
|
2414
|
+
* Claude Code's `SessionStart` hook) rather than from their own process. Advisory display data,
|
|
2415
|
+
* never a routing or access input. The pathname is kept byte for byte: a directory name may hold
|
|
2416
|
+
* or end in whitespace, so it is never trimmed or normalized. An empty string names no directory
|
|
2417
|
+
* and leaves the card unchanged. */
|
|
2418
|
+
async setCardCwd(cwd) {
|
|
2419
|
+
if (!cwd || this.card.meta?.cwd === cwd)
|
|
2420
|
+
return;
|
|
2421
|
+
this.card.meta = { ...(this.card.meta ?? {}), cwd };
|
|
2422
|
+
await this.publishPresence();
|
|
2423
|
+
}
|
|
2400
2424
|
// ---- channel discovery ---------------------------------------------------
|
|
2401
2425
|
/** This channel's registry config from the live local cache (undefined if unset). */
|
|
2402
2426
|
getChannelConfig(channel) {
|
|
@@ -2677,8 +2701,6 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2677
2701
|
watch.arm = watch.arm.catch(() => { }).then(async () => {
|
|
2678
2702
|
await this.disarmMembershipWatch(watch);
|
|
2679
2703
|
await this.deleteRetainedMembershipConsumer(watch);
|
|
2680
|
-
if (watch.consumerStream || watch.consumerName)
|
|
2681
|
-
return;
|
|
2682
2704
|
this.finishMembershipWatchStop(watch);
|
|
2683
2705
|
}).catch((err) => {
|
|
2684
2706
|
// A real authorization/server failure remains loud. A terminal close is retained rather than
|
|
@@ -2772,10 +2794,10 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
2772
2794
|
const cut = subject.lastIndexOf("_") + 1;
|
|
2773
2795
|
return /^\d+$/.test(subject.slice(cut)) && this.ownWatchDeletePrefixes.has(subject.slice(0, cut));
|
|
2774
2796
|
}
|
|
2775
|
-
/** Delete
|
|
2776
|
-
* the
|
|
2777
|
-
* consumer at its inactive threshold. */
|
|
2778
|
-
async
|
|
2797
|
+
/** Delete one of this connection's own ephemeral consumers, a history reader's or a KV watch's. A
|
|
2798
|
+
* refused delete is the designed outcome for a profile without a stream-wide CONSUMER.DELETE
|
|
2799
|
+
* (#691): the broker reaps the consumer at its inactive threshold. */
|
|
2800
|
+
async deleteOwnConsumer(consumer) {
|
|
2779
2801
|
try {
|
|
2780
2802
|
await consumer.delete();
|
|
2781
2803
|
}
|
|
@@ -3185,7 +3207,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3185
3207
|
throw new Error(`history: the broker reported messages on ${subjectLabel(subjects)} but delivered none - the read was cut short, not empty`);
|
|
3186
3208
|
}
|
|
3187
3209
|
finally {
|
|
3188
|
-
await this.
|
|
3210
|
+
await this.deleteOwnConsumer(consumer);
|
|
3189
3211
|
}
|
|
3190
3212
|
}
|
|
3191
3213
|
/** The newest stream sequence matching `subject`, or 0 when the subject has no messages.
|
|
@@ -3225,7 +3247,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3225
3247
|
throw new Error(`history: the broker reported messages on ${subjectLabel(subjects)} but delivered none - the read was cut short, not empty`);
|
|
3226
3248
|
}
|
|
3227
3249
|
finally {
|
|
3228
|
-
await this.
|
|
3250
|
+
await this.deleteOwnConsumer(consumer);
|
|
3229
3251
|
}
|
|
3230
3252
|
}
|
|
3231
3253
|
/** Drain every message matching `subject` with sequence in `[start, ceiling]`, oldest-first.
|
|
@@ -3302,7 +3324,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3302
3324
|
// resulting resource exhaustion would land in streamHistory's catch and read as empty history.
|
|
3303
3325
|
// The observer and admin profiles may not delete (#691), so the dashboard's readers do live out
|
|
3304
3326
|
// that threshold; only a profile holding the delete row reclaims them here.
|
|
3305
|
-
await this.
|
|
3327
|
+
await this.deleteOwnConsumer(consumer).finally(() => { this.ownWatchDeletePrefixes.delete(readerPrefix); });
|
|
3306
3328
|
}
|
|
3307
3329
|
}
|
|
3308
3330
|
// ---- internals -----------------------------------------------------------
|
|
@@ -3700,20 +3722,49 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
3700
3722
|
};
|
|
3701
3723
|
}
|
|
3702
3724
|
stopDeliveryLeaseWatch() {
|
|
3725
|
+
if (this.leaseWatchIter)
|
|
3726
|
+
this.retireDeliveryLeaseWatch(this.leaseWatchIter);
|
|
3727
|
+
this.leaseWatchIter = undefined;
|
|
3728
|
+
}
|
|
3729
|
+
/** Stop a lease watch and delete its ordered consumer, which the stop alone leaves on the broker
|
|
3730
|
+
* until its five-minute inactive threshold (#3966). A connection that closed on its own cannot
|
|
3731
|
+
* carry the delete, so that consumer is left to the threshold. A failed delete is a warning: the
|
|
3732
|
+
* broker still reaps the consumer, and the daemon's synchronous stop handle has no caller to
|
|
3733
|
+
* reject. */
|
|
3734
|
+
retireDeliveryLeaseWatch(iter) {
|
|
3703
3735
|
try {
|
|
3704
|
-
|
|
3736
|
+
iter.stop();
|
|
3705
3737
|
}
|
|
3706
3738
|
catch { /* already closed with its connection */ }
|
|
3707
|
-
this.
|
|
3708
|
-
|
|
3709
|
-
|
|
3739
|
+
if (!this.nc || this.nc.isClosed())
|
|
3740
|
+
return;
|
|
3741
|
+
const deleting = this.deleteOwnConsumer(kvWatchConsumer(iter)).catch((e) => this.emitRecoverable(new Error(`a retired delivery lease watch consumer was not deleted (${e.message}); the broker reaps it at its inactive threshold`)));
|
|
3742
|
+
this.leaseWatchDeletes.add(deleting);
|
|
3743
|
+
void deleting.then(() => this.leaseWatchDeletes.delete(deleting));
|
|
3744
|
+
}
|
|
3745
|
+
/** Wait for the lease-watch binds still in flight, then for every retired watch's consumer delete.
|
|
3746
|
+
* {@link stop} and a live-epoch rebuild run this before they close the connection those deletes
|
|
3747
|
+
* need (#3966). */
|
|
3748
|
+
async settleDeliveryLeaseWatches() {
|
|
3749
|
+
await Promise.allSettled(this.leaseWatchBinds);
|
|
3750
|
+
await Promise.all(this.leaseWatchDeletes);
|
|
3751
|
+
}
|
|
3752
|
+
/** Bind the lease watch on the current connection. A bind that lands after a newer watch replaced
|
|
3753
|
+
* its intent, or after a stop or connection teardown moved the epoch, retires its watch instead of
|
|
3754
|
+
* installing it: the epoch it was bound for is gone, and a rebuilt one binds its own. */
|
|
3755
|
+
bindDeliveryLeaseWatch() {
|
|
3756
|
+
const binding = this.bindDeliveryLeaseWatchOnce();
|
|
3757
|
+
const settled = () => { this.leaseWatchBinds.delete(binding); };
|
|
3758
|
+
this.leaseWatchBinds.add(binding);
|
|
3759
|
+
void binding.then(settled, settled);
|
|
3760
|
+
return binding;
|
|
3761
|
+
}
|
|
3762
|
+
async bindDeliveryLeaseWatchOnce() {
|
|
3710
3763
|
const intent = this.leaseWatchIntent;
|
|
3764
|
+
const epoch = this.presenceEpoch;
|
|
3711
3765
|
const iter = await (await this.deliveryRegistry()).watch({ key: leaseKey(intent.shardIndex) });
|
|
3712
|
-
if (this.leaseWatchIntent !== intent) {
|
|
3713
|
-
|
|
3714
|
-
iter.stop();
|
|
3715
|
-
}
|
|
3716
|
-
catch { /* already closed */ }
|
|
3766
|
+
if (this.leaseWatchIntent !== intent || epoch !== this.presenceEpoch) {
|
|
3767
|
+
this.retireDeliveryLeaseWatch(iter);
|
|
3717
3768
|
return;
|
|
3718
3769
|
}
|
|
3719
3770
|
this.leaseWatchIter = iter;
|
|
@@ -6311,7 +6362,7 @@ export class CotalEndpoint extends EventEmitter {
|
|
|
6311
6362
|
}
|
|
6312
6363
|
if (!isUsablePresence(p)) {
|
|
6313
6364
|
this.presenceBindingDrops++;
|
|
6314
|
-
this.emitRecoverable(new Error(`dropped presence entry for key ${JSON.stringify(e.key)}: not a record with a string card.name, a known status and a
|
|
6365
|
+
this.emitRecoverable(new Error(`dropped presence entry for key ${JSON.stringify(e.key)}: not a record with a string card.name, a known status, a finite ts, string display fields and a condition with a string code`));
|
|
6315
6366
|
return;
|
|
6316
6367
|
}
|
|
6317
6368
|
this.applyPresence(e.key, p);
|
|
@@ -6738,12 +6789,22 @@ const PRESENCE_STATUSES = { idle: true, waiting: true, working: true, offline: t
|
|
|
6738
6789
|
* such as `"nope"` makes the liveness arithmetic NaN, and a JSON `1e400` parses as `Infinity`;
|
|
6739
6790
|
* neither age is ever greater than the TTL, so the row would stay live after its key expired
|
|
6740
6791
|
* (#2612). `card.name` is the roster sort key: a non-string throws inside the watch loop and
|
|
6741
|
-
* ends the watch. `status` decides offline and is what roster consumers read. `card.
|
|
6742
|
-
*
|
|
6792
|
+
* ends the watch. `status` decides offline and is what roster consumers read. `card.role`,
|
|
6793
|
+
* `activity`, `attention` and `condition.code` are printed as text, and the peer writes its own
|
|
6794
|
+
* row: an object such as `{"toString":null,"valueOf":null}` throws on conversion and would fail
|
|
6795
|
+
* every reader's roster. A present `condition` must be an object with a string `code`, which SPEC
|
|
6796
|
+
* §6 makes required: `null`, a bare string or an object with no code is dropped too. `card.id` is
|
|
6797
|
+
* left to the KV-key check, which rejects anything but the key string. */
|
|
6743
6798
|
function isUsablePresence(value) {
|
|
6744
6799
|
return isRecord(value) && isRecord(value.card) && typeof value.card.name === "string" &&
|
|
6800
|
+
isOptionalString(value.card.role) &&
|
|
6745
6801
|
typeof value.status === "string" && Object.hasOwn(PRESENCE_STATUSES, value.status) &&
|
|
6746
|
-
typeof value.ts === "number" && Number.isFinite(value.ts)
|
|
6802
|
+
typeof value.ts === "number" && Number.isFinite(value.ts) &&
|
|
6803
|
+
isOptionalString(value.activity) && isOptionalString(value.attention) &&
|
|
6804
|
+
(value.condition === undefined || (isRecord(value.condition) && typeof value.condition.code === "string"));
|
|
6805
|
+
}
|
|
6806
|
+
function isOptionalString(value) {
|
|
6807
|
+
return value === undefined || typeof value === "string";
|
|
6747
6808
|
}
|
|
6748
6809
|
/** Shallow-equal two per-channel-mode maps (presence dedup): a change must re-emit, so an attention
|
|
6749
6810
|
* toggle isn't swallowed as a quiet heartbeat. Absent and empty compare equal. */
|
|
@@ -6837,19 +6898,6 @@ function describeStatusError(err) {
|
|
|
6837
6898
|
}
|
|
6838
6899
|
return err;
|
|
6839
6900
|
}
|
|
6840
|
-
/** True when a failure is a NATS *permission denial* — the subject is forbidden to this
|
|
6841
|
-
* endpoint's creds — rather than a missing responder or a timeout. The two need opposite
|
|
6842
|
-
* fixes (grant the capability vs. start/await the service), so callers (e.g. a control
|
|
6843
|
-
* request that can't reach the manager) must tell them apart instead of defaulting to
|
|
6844
|
-
* "service down". Unwraps a wrapped `cause` and falls back to the server's error text, since
|
|
6845
|
-
* a denied publish can surface either as the typed error or inside a request rejection. */
|
|
6846
|
-
export function isPermissionDenied(e) {
|
|
6847
|
-
if (e instanceof PermissionViolationError)
|
|
6848
|
-
return true;
|
|
6849
|
-
if (e?.cause instanceof PermissionViolationError)
|
|
6850
|
-
return true;
|
|
6851
|
-
return /permissions?\s+violation/i.test(String(e?.message ?? ""));
|
|
6852
|
-
}
|
|
6853
6901
|
/** The ordered consumer behind a KV watch, which nats.js keeps on the iterator and the KV types hide. */
|
|
6854
6902
|
function kvWatchConsumer(iter) {
|
|
6855
6903
|
const consumer = iter._data;
|