@cotal-ai/delivery 0.57.0 → 0.59.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.
@@ -0,0 +1,111 @@
1
+ import { LoopLagMeter } from "./watchdog.js";
2
+ /** Delivery's broker coupling, driven by the resident NATS connection rather than a second
3
+ * authenticated connection. A successful credential adoption counts only after the caller has
4
+ * proved it against the broker; a disconnected socket cannot be made healthy by an adoption. */
5
+ export class DeliveryTransportHealth {
6
+ onGone;
7
+ onCredentialExpired;
8
+ windowMs;
9
+ backstopMs;
10
+ now;
11
+ connected = true;
12
+ disconnectedAt = 0;
13
+ lag = new LoopLagMeter(250);
14
+ timer;
15
+ stopped = false;
16
+ expired = false;
17
+ constructor(onGone, onCredentialExpired, windowMs = 15_000, backstopMs = windowMs * 4, now = Date.now) {
18
+ this.onGone = onGone;
19
+ this.onCredentialExpired = onCredentialExpired;
20
+ this.windowMs = windowMs;
21
+ this.backstopMs = backstopMs;
22
+ this.now = now;
23
+ if (windowMs <= 0 || backstopMs < windowMs)
24
+ throw new Error("invalid delivery broker health windows");
25
+ }
26
+ /** The endpoint's own current-epoch `transport` edge, including initial connection, reconnect,
27
+ * expiry close and terminal close. No timer and no dial run while it is connected. */
28
+ transport(connected) {
29
+ if (this.stopped)
30
+ return;
31
+ if (connected) {
32
+ this.connected = true;
33
+ if (this.expired)
34
+ return; // a stale JWT can reconnect briefly before the broker closes it again
35
+ this.expired = false;
36
+ this.disconnectedAt = 0;
37
+ this.clear();
38
+ return;
39
+ }
40
+ if (!this.connected)
41
+ return;
42
+ this.connected = false;
43
+ if (!this.disconnectedAt)
44
+ this.disconnectedAt = this.now();
45
+ this.armTimer();
46
+ }
47
+ /** Called only after the real credential-adoption operation succeeded. A preflight failure,
48
+ * file change, or merely queued adoption must never reset the broker-loss clock. */
49
+ adopted() {
50
+ if (this.stopped || !this.connected)
51
+ return;
52
+ this.expired = false;
53
+ this.disconnectedAt = 0;
54
+ this.lag.reset();
55
+ this.clear();
56
+ }
57
+ /** NATS reports authentication expiry as an explicit status error before its disconnect edge.
58
+ * Surface it immediately, leave room for renewal, and never call it broker unavailability. */
59
+ credentialExpired() {
60
+ if (this.stopped || this.expired)
61
+ return;
62
+ this.expired = true;
63
+ if (!this.disconnectedAt)
64
+ this.disconnectedAt = this.now();
65
+ this.armTimer();
66
+ this.onCredentialExpired();
67
+ }
68
+ armTimer() {
69
+ if (this.timer !== undefined)
70
+ return;
71
+ this.lag = new LoopLagMeter(250);
72
+ this.lag.tick(this.now());
73
+ this.timer = setInterval(() => this.evaluate(), 250);
74
+ this.timer.unref?.();
75
+ }
76
+ evaluate() {
77
+ if ((this.connected && !this.expired) || this.stopped)
78
+ return;
79
+ const now = this.now();
80
+ this.lag.tick(now);
81
+ const elapsed = now - this.disconnectedAt;
82
+ if (elapsed < this.windowMs)
83
+ return;
84
+ if (this.expired) {
85
+ if (elapsed >= this.backstopMs) {
86
+ this.clear();
87
+ this.stopped = true;
88
+ this.onGone("credential-expired");
89
+ }
90
+ return;
91
+ }
92
+ // A late local callback is not evidence that the broker spent that time unavailable. The
93
+ // absolute backstop still prevents indefinite survival after a real disconnect.
94
+ const reason = elapsed >= this.backstopMs ? "backstop" : elapsed - this.lag.starvedMsWithin(elapsed) >= this.windowMs ? "broker-gone" : undefined;
95
+ if (reason) {
96
+ this.clear();
97
+ this.stopped = true;
98
+ this.onGone(reason);
99
+ }
100
+ }
101
+ stop() {
102
+ this.stopped = true;
103
+ this.clear();
104
+ }
105
+ clear() {
106
+ if (this.timer !== undefined)
107
+ clearInterval(this.timer);
108
+ this.timer = undefined;
109
+ }
110
+ }
111
+ //# sourceMappingURL=transport-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport-health.js","sourceRoot":"","sources":["../src/transport-health.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/delivery",
3
3
  "description": "Cotal delivery daemon: the server-side Plane-3 durable backstop (fan-out writer + trusted reader + membership/ACL authority), a scoped least-privilege NATS client co-located with the broker.",
4
- "version": "0.57.0",
4
+ "version": "0.59.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,9 +18,9 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "@nats-io/jetstream": "^3.4.0",
22
- "@cotal-ai/core": "0.57.0",
23
- "@cotal-ai/workspace": "0.57.0"
21
+ "@cotal-ai/core": "0.59.0",
22
+ "@cotal-ai/workspace": "0.59.0",
23
+ "@nats-io/jetstream": "^3.4.0"
24
24
  },
25
25
  "files": [
26
26
  "dist"
@@ -29,9 +29,9 @@
29
29
  "access": "public"
30
30
  },
31
31
  "devDependencies": {
32
+ "@cotal-ai/smoke-kit": "0.0.0",
32
33
  "@nats-io/kv": "^3.4.0",
33
- "@nats-io/transport-node": "^3.4.0",
34
- "@cotal-ai/smoke-kit": "0.0.0"
34
+ "@nats-io/transport-node": "^3.4.0"
35
35
  },
36
36
  "scripts": {
37
37
  "test": "tsx smoke/delivery-store-boundary.smoke.ts",