@cotal-ai/core 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.
Files changed (124) hide show
  1. package/dist/acls.d.ts +7 -0
  2. package/dist/acls.d.ts.map +1 -1
  3. package/dist/acls.js +10 -0
  4. package/dist/acls.js.map +1 -1
  5. package/dist/auth-provider.d.ts +25 -1
  6. package/dist/auth-provider.d.ts.map +1 -1
  7. package/dist/auth-provider.js.map +1 -1
  8. package/dist/backup-config.d.ts +10 -2
  9. package/dist/backup-config.d.ts.map +1 -1
  10. package/dist/backup-config.js +12 -3
  11. package/dist/backup-config.js.map +1 -1
  12. package/dist/broker-tls.d.ts +15 -0
  13. package/dist/broker-tls.d.ts.map +1 -1
  14. package/dist/broker-tls.js +6 -6
  15. package/dist/broker-tls.js.map +1 -1
  16. package/dist/checkpoint-answer.d.ts +24 -0
  17. package/dist/checkpoint-answer.d.ts.map +1 -1
  18. package/dist/checkpoint-answer.js +45 -4
  19. package/dist/checkpoint-answer.js.map +1 -1
  20. package/dist/command.d.ts +4 -3
  21. package/dist/command.d.ts.map +1 -1
  22. package/dist/command.js +20 -4
  23. package/dist/command.js.map +1 -1
  24. package/dist/connector-setup.d.ts +27 -0
  25. package/dist/connector-setup.d.ts.map +1 -1
  26. package/dist/connector.d.ts +12 -0
  27. package/dist/connector.d.ts.map +1 -1
  28. package/dist/connector.js.map +1 -1
  29. package/dist/endpoint-envelope.js +2 -2
  30. package/dist/endpoint-envelope.js.map +1 -1
  31. package/dist/endpoint-grants.d.ts.map +1 -1
  32. package/dist/endpoint-grants.js +4 -2
  33. package/dist/endpoint-grants.js.map +1 -1
  34. package/dist/endpoint-handle.d.ts.map +1 -1
  35. package/dist/endpoint-handle.js +5 -2
  36. package/dist/endpoint-handle.js.map +1 -1
  37. package/dist/endpoint-reconcile.d.ts +5 -1
  38. package/dist/endpoint-reconcile.d.ts.map +1 -1
  39. package/dist/endpoint-reconcile.js +43 -21
  40. package/dist/endpoint-reconcile.js.map +1 -1
  41. package/dist/endpoint-service.d.ts +25 -0
  42. package/dist/endpoint-service.d.ts.map +1 -1
  43. package/dist/endpoint-service.js +25 -25
  44. package/dist/endpoint-service.js.map +1 -1
  45. package/dist/endpoint-subjects.d.ts +6 -3
  46. package/dist/endpoint-subjects.d.ts.map +1 -1
  47. package/dist/endpoint-subjects.js +5 -5
  48. package/dist/endpoint-subjects.js.map +1 -1
  49. package/dist/endpoint-verbs.d.ts +1 -1
  50. package/dist/endpoint-verbs.d.ts.map +1 -1
  51. package/dist/endpoint-verbs.js +2 -2
  52. package/dist/endpoint-verbs.js.map +1 -1
  53. package/dist/endpoint.d.ts +114 -8
  54. package/dist/endpoint.d.ts.map +1 -1
  55. package/dist/endpoint.js +393 -45
  56. package/dist/endpoint.js.map +1 -1
  57. package/dist/evict.d.ts +27 -0
  58. package/dist/evict.d.ts.map +1 -1
  59. package/dist/evict.js +95 -38
  60. package/dist/evict.js.map +1 -1
  61. package/dist/index.d.ts +2 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +2 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/issued-authority.d.ts +0 -2
  66. package/dist/issued-authority.d.ts.map +1 -1
  67. package/dist/issued-authority.js +0 -2
  68. package/dist/issued-authority.js.map +1 -1
  69. package/dist/kv-scan.d.ts +5 -2
  70. package/dist/kv-scan.d.ts.map +1 -1
  71. package/dist/kv-scan.js +103 -27
  72. package/dist/kv-scan.js.map +1 -1
  73. package/dist/launch-artifacts.d.ts +42 -0
  74. package/dist/launch-artifacts.d.ts.map +1 -0
  75. package/dist/launch-artifacts.js +155 -0
  76. package/dist/launch-artifacts.js.map +1 -0
  77. package/dist/lease.d.ts +5 -0
  78. package/dist/lease.d.ts.map +1 -1
  79. package/dist/lease.js.map +1 -1
  80. package/dist/liveness.d.ts +180 -0
  81. package/dist/liveness.d.ts.map +1 -0
  82. package/dist/liveness.js +114 -0
  83. package/dist/liveness.js.map +1 -0
  84. package/dist/members.d.ts +10 -0
  85. package/dist/members.d.ts.map +1 -1
  86. package/dist/members.js +28 -1
  87. package/dist/members.js.map +1 -1
  88. package/dist/membership-feed.d.ts.map +1 -1
  89. package/dist/membership-feed.js +19 -6
  90. package/dist/membership-feed.js.map +1 -1
  91. package/dist/provision.d.ts +24 -11
  92. package/dist/provision.d.ts.map +1 -1
  93. package/dist/provision.js +154 -66
  94. package/dist/provision.js.map +1 -1
  95. package/dist/remote-manager-authority.d.ts +193 -3
  96. package/dist/remote-manager-authority.d.ts.map +1 -1
  97. package/dist/remote-manager-authority.js +104 -0
  98. package/dist/remote-manager-authority.js.map +1 -1
  99. package/dist/run-host.d.ts +66 -2
  100. package/dist/run-host.d.ts.map +1 -1
  101. package/dist/run-journal.d.ts +14 -6
  102. package/dist/run-journal.d.ts.map +1 -1
  103. package/dist/run-journal.js +14 -14
  104. package/dist/run-journal.js.map +1 -1
  105. package/dist/run-record.d.ts +24 -5
  106. package/dist/run-record.d.ts.map +1 -1
  107. package/dist/run-record.js +20 -0
  108. package/dist/run-record.js.map +1 -1
  109. package/dist/runtime.d.ts +6 -1
  110. package/dist/runtime.d.ts.map +1 -1
  111. package/dist/schema-profile.d.ts.map +1 -1
  112. package/dist/schema-profile.js +4 -3
  113. package/dist/schema-profile.js.map +1 -1
  114. package/dist/streams.d.ts +59 -3
  115. package/dist/streams.d.ts.map +1 -1
  116. package/dist/streams.js +226 -21
  117. package/dist/streams.js.map +1 -1
  118. package/dist/subjects.d.ts +52 -7
  119. package/dist/subjects.d.ts.map +1 -1
  120. package/dist/subjects.js +62 -3
  121. package/dist/subjects.js.map +1 -1
  122. package/dist/types.d.ts +4 -0
  123. package/dist/types.d.ts.map +1 -1
  124. package/package.json +1 -1
package/dist/kv-scan.js CHANGED
@@ -103,7 +103,20 @@ export class IncompleteKvScan extends Error {
103
103
  * Throws {@link IncompleteKvScan} if the pass is cut short. Returns `[]` for a bucket that is
104
104
  * genuinely empty (proven at bind time, not inferred from silence).
105
105
  */
106
- export async function liveKvEntries(kv, filter) {
106
+ export async function liveKvEntries(kv, filterOrOptions, options) {
107
+ let filter;
108
+ // The three-argument form may deliberately omit the filter: (kv, undefined, options).
109
+ let opts = options;
110
+ if (typeof filterOrOptions === "string" || Array.isArray(filterOrOptions)) {
111
+ filter = filterOrOptions;
112
+ opts = options;
113
+ }
114
+ else if (filterOrOptions && typeof filterOrOptions === "object") {
115
+ opts = filterOrOptions;
116
+ }
117
+ if (opts?.signal?.aborted) {
118
+ throw opts.signal.reason ?? new Error("scan aborted");
119
+ }
107
120
  // OWN THE PASS. This deliberately does NOT call `kv.history()`. That helper hides the consumer's
108
121
  // bind-time `num_pending`, and without it an empty result is ambiguous: a genuinely empty bucket
109
122
  // and a pass that died before its first message look identical. For a FILTERED scan that ambiguity
@@ -134,38 +147,101 @@ export async function liveKvEntries(kv, filter) {
134
147
  let sawTerminal = false;
135
148
  let bucketName = bucket.bucket;
136
149
  let expected = 0;
150
+ let activeConsumerName;
151
+ let initialName;
137
152
  try {
138
153
  // THE BIND-TIME PROOF, continued: zero here is the only thing that yields an empty result.
139
- expected = (await oc.info(true)).num_pending;
140
- if (expected === 0)
141
- return [];
142
- // Greatest revision per key, markers INCLUDED — see the header. Collapsing after the fact is
143
- // what makes concurrent rewrites and drifted `history` settings both correct.
144
- const iter = await oc.consume();
145
- try {
146
- for await (const m of iter) {
147
- const e = bucket.jmToWatchEntry(m, false);
148
- received++;
149
- bucketName = e.bucket;
150
- const prior = latest.get(e.key);
151
- if (prior === undefined || e.revision >= prior.revision)
152
- latest.set(e.key, e);
153
- // The ONLY completion signal accepted: a delivered message that says nothing is left behind
154
- // it. Unlike `history()`, an idle heartbeat is NOT treated as "we got everything" — that
155
- // shortcut is precisely how a stalled pass returns a short list wearing a clean end.
156
- if (m.info.pending === 0) {
157
- sawTerminal = true;
158
- break;
154
+ const initialInfo = await oc.info(true);
155
+ initialName = initialInfo.name;
156
+ activeConsumerName = initialInfo.name;
157
+ expected = initialInfo.num_pending;
158
+ if (opts?.signal?.aborted) {
159
+ throw opts.signal.reason ?? new Error("scan aborted");
160
+ }
161
+ // Keep the empty path inside this try/finally too. Returning here would commit [] before
162
+ // cleanup finishes, even if a caller aborts while the consumer delete is pending.
163
+ if (expected !== 0) {
164
+ // Greatest revision per key, markers INCLUDED — see the header. Collapsing after the fact is
165
+ // what makes concurrent rewrites and drifted `history` settings both correct.
166
+ const iter = await oc.consume();
167
+ const statusIter = typeof iter.status === "function" ? iter.status() : undefined;
168
+ if (statusIter) {
169
+ (async () => {
170
+ try {
171
+ for await (const s of statusIter) {
172
+ if (s.type === "ordered_consumer_recreated" && "name" in s && typeof s.name === "string") {
173
+ activeConsumerName = s.name;
174
+ }
175
+ }
176
+ }
177
+ catch {
178
+ /* status iterator closed */
179
+ }
180
+ })();
181
+ }
182
+ const onAbort = () => {
183
+ iter.stop(opts?.signal?.reason ?? new Error("scan aborted"));
184
+ };
185
+ opts?.signal?.addEventListener("abort", onAbort, { once: true });
186
+ try {
187
+ for await (const m of iter) {
188
+ if (opts?.signal?.aborted)
189
+ break;
190
+ const e = bucket.jmToWatchEntry(m, false);
191
+ received++;
192
+ bucketName = e.bucket;
193
+ const prior = latest.get(e.key);
194
+ if (prior === undefined || e.revision >= prior.revision)
195
+ latest.set(e.key, e);
196
+ // The ONLY completion signal accepted: a delivered message that says nothing is left behind
197
+ // it. Unlike `history()`, an idle heartbeat is NOT treated as "we got everything" — that
198
+ // shortcut is precisely how a stalled pass returns a short list wearing a clean end.
199
+ if (m.info.pending === 0) {
200
+ sawTerminal = true;
201
+ break;
202
+ }
203
+ }
204
+ }
205
+ finally {
206
+ opts?.signal?.removeEventListener("abort", onAbort);
207
+ if (typeof iter.close === "function") {
208
+ await iter.close().catch(() => { });
209
+ }
210
+ else {
211
+ iter.stop();
159
212
  }
160
213
  }
161
- }
162
- finally {
163
- iter.stop();
164
214
  }
165
215
  }
166
216
  finally {
167
- await oc.delete().catch(() => { });
217
+ // Delete ONLY this scan's own consumer in finally. If rotation occurred, delete the rotated consumer too.
218
+ // TTL (inactive_threshold) remains the crash/deletion-failure backstop.
219
+ const targetName = oc.name ?? activeConsumerName;
220
+ if (targetName && initialName && targetName !== initialName) {
221
+ for (let i = 0; i < 20; i++) {
222
+ let deleted = false;
223
+ try {
224
+ deleted = await bucket.jsm.consumers.delete(bucket.stream, targetName);
225
+ }
226
+ catch {
227
+ // in-flight creation or already deleted
228
+ }
229
+ if (deleted)
230
+ break;
231
+ await new Promise((r) => setTimeout(r, 20));
232
+ }
233
+ }
234
+ else {
235
+ await oc.delete().catch(() => { });
236
+ }
237
+ }
238
+ if (opts?.signal?.aborted) {
239
+ throw opts.signal.reason ?? new Error("scan aborted");
168
240
  }
241
+ // No original scan error was caught or replaced; only a successful empty result waits for
242
+ // cleanup and this final abort check before it can be returned.
243
+ if (expected === 0)
244
+ return [];
169
245
  // Fell out without the terminal message: the connection dropped, the consumer was removed, or the
170
246
  // stream stalled past the heartbeat. Whatever the cause, this is a PARTIAL view and saying so is
171
247
  // the whole point. Filtered and unfiltered obey the same rule, including zero-received.
@@ -242,9 +318,9 @@ export async function walkKvEntries(kv, filter) {
242
318
  /** {@link liveKvEntries}, decoded. `decode` returning `undefined` drops the entry — for callers that
243
319
  * skip garbled records rather than failing the whole read (the prevailing convention in the
244
320
  * registries: one unparseable row must not blind the surface to every other row). */
245
- export async function liveKvValues(kv, decode, filter) {
321
+ export async function liveKvValues(kv, decode, filterOrOptions, options) {
246
322
  const out = [];
247
- for (const e of await liveKvEntries(kv, filter)) {
323
+ for (const e of await liveKvEntries(kv, filterOrOptions, options)) {
248
324
  const v = decode(e);
249
325
  if (v !== undefined)
250
326
  out.push(v);
@@ -1 +1 @@
1
- {"version":3,"file":"kv-scan.js","sourceRoot":"","sources":["../src/kv-scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+EG;AAEH;sFACsF;AACtF,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAE9B;IACA;IACA;IAHX,YACW,MAAc,EACd,QAAgB,EAChB,QAAgB;QAEzB,KAAK,CACH,WAAW,MAAM,gBAAgB,QAAQ,QAAQ,QAAQ,kQAAkQ,CAC5T,CAAC;QANO,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,aAAQ,GAAR,QAAQ,CAAQ;QAKzB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAM,EAAE,MAA0B;IACpE,iGAAiG;IACjG,iGAAiG;IACjG,mGAAmG;IACnG,mGAAmG;IACnG,mGAAmG;IACnG,8FAA8F;IAC9F,EAAE;IACF,+FAA+F;IAC/F,6FAA6F;IAC7F,8FAA8F;IAC9F,gGAAgG;IAChG,2DAA2D;IAC3D,IAAI,CAAC,CAAC,EAAE,YAAY,MAAM,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,2FAA2F,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,OAAO,EAAE,6HAA6H,CAC3P,CAAC;IACJ,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,cAAc,CAAC,UAAU,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAExE,kGAAkG;IAClG,8FAA8F;IAC9F,+BAA+B;IAC/B,8FAA8F;IAC9F,iGAAiG;IACjG,0FAA0F;IAC1F,mGAAmG;IACnG,4BAA4B;IAC5B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC/C,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC;IAC/B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,CAAC;QACH,2FAA2F;QAC3F,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;QAC7C,IAAI,QAAQ,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAE9B,6FAA6F;QAC7F,8EAA8E;QAC9E,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC3B,MAAM,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC1C,QAAQ,EAAE,CAAC;gBACX,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;gBACtB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;gBAC9E,4FAA4F;gBAC5F,yFAAyF;gBACzF,qFAAqF;gBACrF,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;oBAAC,WAAW,GAAG,IAAI,CAAC;oBAAC,MAAM;gBAAC,CAAC;YAC1D,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAqD,CAAC,CAAC,CAAC;IACvF,CAAC;IACD,kGAAkG;IAClG,iGAAiG;IACjG,wFAAwF;IACxF,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,gBAAgB,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAE7E,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnG,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAM,EAAE,MAAc;IACxD,IAAI,CAAC,CAAC,EAAE,YAAY,MAAM,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,gGAAgG,EAAE,EAAE,WAAW,EAAE,IAAI,IAAI,OAAO,EAAE,GAAG,CACtI,CAAC;IACJ,MAAM,MAAM,GAAW,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC1C,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,SAAS,CAAC;QACR,IAAI,EAAE,CAAC;QACP,IAAI,CAAC;YACH,+FAA+F;YAC/F,+FAA+F;YAC/F,6FAA6F;YAC7F,wEAAwE;YACxE,MAAM,GAAG,GAAmB,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;YAC3D,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,GAA4B,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,yFAAyF;YACzF,0FAA0F;YAC1F,IAAK,CAAwB,EAAE,IAAI,KAAK,KAAK;gBAAE,MAAM;YACrD,MAAM,CAAC,CAAC;QACV,CAAC;QACD,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,SAAS;YAAE,MAAM;QAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9E,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,MAAM,GAAG,GAAc,EAAE,CAAC;IAC1B,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnG,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;sFAEsF;AACtF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAM,EACN,MAAqC,EACrC,MAA0B;IAE1B,MAAM,GAAG,GAAQ,EAAE,CAAC;IACpB,KAAK,MAAM,CAAC,IAAI,MAAM,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;QAChD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"kv-scan.js","sourceRoot":"","sources":["../src/kv-scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { LaunchSpec } from "./connector.js";
2
+ /**
3
+ * Thrown by a runtime's `spawn` for a failure before it handed the spec's command to anything that
4
+ * could start it (an unsafe name, a backend that is not reachable, a launch script it could not
5
+ * write). It is the one spawn failure that proves no child will read the spec's artifacts, so the
6
+ * launcher removes them on it. Throw it only from code that runs before that handoff.
7
+ */
8
+ export declare class SpawnRefused extends Error {
9
+ constructor(message: string);
10
+ }
11
+ /**
12
+ * Write `body` to `<fresh private dir>/<file>` and return the file path. The directory is appended to
13
+ * `artifacts` as soon as it exists, so a write that fails afterwards still leaves it owned; on a
14
+ * failure every artifact recorded so far is removed before the error propagates, because the launch
15
+ * that would have owned them is not going to happen.
16
+ *
17
+ * `mkdtemp` rather than a predictable name: a pre-created or symlinked path in the world-writable
18
+ * tmpdir cannot be raced, and a fresh file guarantees the private mode applies at create. The name
19
+ * also carries 128 random bits, which makes it this launch's identity: a stale list of directories
20
+ * already removed (a custodian killed between removing them and updating its record) cannot name a
21
+ * later launch's directory, because no later launch draws the same name.
22
+ */
23
+ export declare function writeLaunchArtifact(artifacts: string[], prefix: string, file: string, body: string): string;
24
+ /**
25
+ * Remove a launch's artifact directories. Call it only once the child is gone or never started.
26
+ * Refuses, before removing anything, a path that is not a `cotal-` directory directly under the OS
27
+ * temp dir: a spec carrying anything else is a connector bug, and recursive removal of whatever it
28
+ * named is the one mistake this must never make. Otherwise it tries every directory and then throws
29
+ * naming each one it could not remove, so the caller can keep them owned and try again.
30
+ */
31
+ export declare function discardLaunchArtifacts(artifacts: readonly string[] | undefined): void;
32
+ /**
33
+ * Wrap a spec so its child owns its own artifacts. `/bin/sh` starts a watcher and then `exec`s the
34
+ * spec's command, so the agent keeps the shell's pid, its argv after the wrapper, its signals and its
35
+ * exit status. The watcher removes this launch's directories, and no other, once that pid is gone,
36
+ * whether or not the launcher is still alive. Use it wherever the launcher is the only other owner
37
+ * (every runtime but one with durable custody, and the foreground `cotal spawn`). The launcher still
38
+ * removes them on its own proof of exit; both removals are idempotent. A spec with no artifacts, and
39
+ * any spec on Windows, which has no POSIX shell, comes back unchanged.
40
+ */
41
+ export declare function reclaimWithChild(spec: LaunchSpec): LaunchSpec;
42
+ //# sourceMappingURL=launch-artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-artifacts.d.ts","sourceRoot":"","sources":["../src/launch-artifacts.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAOjD;;;;;GAKG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAa3G;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,IAAI,CAYrF;AAoCD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAS7D"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Launch artifacts: the private temporary directories a connector writes for ONE launch's child to
3
+ * read, such as a persona carrier or an MCP config file. They keep text off the process argv, which
4
+ * every same-host observer can read.
5
+ *
6
+ * WHO OWNS THEM. The connector creates them in `buildLaunch` and lists them on
7
+ * `LaunchSpec.artifacts`; it cannot remove them itself, because the child reads them after
8
+ * `buildLaunch` returns (Claude re-reads its MCP config on `/mcp reconnect`). The launcher that
9
+ * spawns the spec owns them from then on: it removes them once it has proved the child gone. Nothing
10
+ * earlier is safe, since the child may read them at any point in its life. A spawn that throws is not
11
+ * that proof, since a backend can fail after its child has started, and neither is losing the
12
+ * connection to whatever watches the child. A failure before the runtime has handed the spec's
13
+ * command to anything that could start it is: a runtime says so by throwing {@link SpawnRefused},
14
+ * and the launcher removes the files on that error only. A removal that fails leaves them owned, and
15
+ * the owner tries again.
16
+ *
17
+ * WHAT IS LEFT. On a custodial runtime the seat's custodian, the child's parent, removes them when it
18
+ * sees the child exit, whether or not the launcher is still alive. Its custody record keeps any it
19
+ * could not remove, and the reap that proves the seat gone removes those and the ones a custodian
20
+ * killed first left, against the temp dir the record names, and keeps the record until it has. Every
21
+ * other launcher starts the child through {@link reclaimWithChild}, so a watcher beside the child
22
+ * removes them once the child is gone, even when the launcher was killed.
23
+ * What stays until the OS temp reaper removes it: the files of a spawn that threw anything but
24
+ * {@link SpawnRefused} before its child started, those of a watcher that was itself SIGKILLed while
25
+ * its launcher was dead, and on Windows, which has no POSIX shell to run the watcher, those of a
26
+ * killed launcher.
27
+ *
28
+ * WHAT OWNER-PRIVATE MEANS. Each file is 0600 inside a 0700 directory. That is OS-user isolation:
29
+ * any process running as the same user can read the file while it exists, as it can the agent file
30
+ * it came from.
31
+ */
32
+ import { randomBytes } from "node:crypto";
33
+ import { mkdtempSync, rmSync } from "node:fs";
34
+ import { tmpdir } from "node:os";
35
+ import { basename, dirname, join, resolve } from "node:path";
36
+ import { hardenPrivate, writeSecretFile } from "./secret-fs.js";
37
+ /** Every artifact directory name starts with this, so {@link discardLaunchArtifacts} can refuse a
38
+ * path this module did not create. */
39
+ const DIR_PREFIX = "cotal-";
40
+ /**
41
+ * Thrown by a runtime's `spawn` for a failure before it handed the spec's command to anything that
42
+ * could start it (an unsafe name, a backend that is not reachable, a launch script it could not
43
+ * write). It is the one spawn failure that proves no child will read the spec's artifacts, so the
44
+ * launcher removes them on it. Throw it only from code that runs before that handoff.
45
+ */
46
+ export class SpawnRefused extends Error {
47
+ constructor(message) {
48
+ super(message);
49
+ this.name = "SpawnRefused";
50
+ }
51
+ }
52
+ /**
53
+ * Write `body` to `<fresh private dir>/<file>` and return the file path. The directory is appended to
54
+ * `artifacts` as soon as it exists, so a write that fails afterwards still leaves it owned; on a
55
+ * failure every artifact recorded so far is removed before the error propagates, because the launch
56
+ * that would have owned them is not going to happen.
57
+ *
58
+ * `mkdtemp` rather than a predictable name: a pre-created or symlinked path in the world-writable
59
+ * tmpdir cannot be raced, and a fresh file guarantees the private mode applies at create. The name
60
+ * also carries 128 random bits, which makes it this launch's identity: a stale list of directories
61
+ * already removed (a custodian killed between removing them and updating its record) cannot name a
62
+ * later launch's directory, because no later launch draws the same name.
63
+ */
64
+ export function writeLaunchArtifact(artifacts, prefix, file, body) {
65
+ if (!prefix.startsWith(DIR_PREFIX))
66
+ throw new Error(`launch artifact prefix must start with "${DIR_PREFIX}": ${prefix}`);
67
+ try {
68
+ const dir = mkdtempSync(join(tmpdir(), `${prefix}${randomBytes(16).toString("hex")}-`));
69
+ artifacts.push(dir);
70
+ hardenPrivate(dir, "dir"); // win32: mkdtemp's 0700 is a no-op, so harden the ACL before the file lands
71
+ const path = join(dir, file);
72
+ writeSecretFile(path, body);
73
+ return path;
74
+ }
75
+ catch (e) {
76
+ discardLaunchArtifacts(artifacts);
77
+ throw e;
78
+ }
79
+ }
80
+ /**
81
+ * Remove a launch's artifact directories. Call it only once the child is gone or never started.
82
+ * Refuses, before removing anything, a path that is not a `cotal-` directory directly under the OS
83
+ * temp dir: a spec carrying anything else is a connector bug, and recursive removal of whatever it
84
+ * named is the one mistake this must never make. Otherwise it tries every directory and then throws
85
+ * naming each one it could not remove, so the caller can keep them owned and try again.
86
+ */
87
+ export function discardLaunchArtifacts(artifacts) {
88
+ if (!artifacts?.length)
89
+ return;
90
+ assertArtifactDirs(artifacts);
91
+ const failed = [];
92
+ for (const dir of artifacts) {
93
+ try {
94
+ rmSync(dir, { recursive: true, force: true });
95
+ }
96
+ catch (e) {
97
+ failed.push(`${dir} (${e.message})`);
98
+ }
99
+ }
100
+ if (failed.length)
101
+ throw new Error(`could not remove launch artifact ${failed.join(", ")}`);
102
+ }
103
+ /** Refuse any path that is not a `cotal-` directory directly under the OS temp dir. */
104
+ function assertArtifactDirs(artifacts) {
105
+ const root = resolve(tmpdir());
106
+ for (const dir of artifacts) {
107
+ if (dirname(resolve(dir)) !== root || !basename(dir).startsWith(DIR_PREFIX))
108
+ throw new Error(`refusing to remove launch artifact ${dir}: not a ${DIR_PREFIX}* directory directly under ${root}`);
109
+ }
110
+ }
111
+ /** Run by `/bin/sh -c` as `<script> cotal-launch <n> <n dirs> <command> <args...>`. The watcher
112
+ * ignores the hangup, interrupt and terminate signals a closing terminal or a stop sends to the
113
+ * child's process group, holds no terminal, and polls the shell's pid, which `exec` hands to the
114
+ * command. A reused pid only delays the removal. It is the only owner left once the launcher is
115
+ * dead, so it tries every directory it could not remove again every five seconds until all are
116
+ * gone. */
117
+ const RECLAIM_SCRIPT = `p=$$ n=$1
118
+ shift
119
+ (
120
+ trap '' HUP INT QUIT TERM
121
+ while kill -0 "$p" 2>/dev/null; do sleep 1; done
122
+ while :; do
123
+ i=0 left=0
124
+ for d; do
125
+ [ "$i" -lt "$n" ] || break
126
+ rm -rf -- "$d" || left=1
127
+ i=$((i + 1))
128
+ done
129
+ [ "$left" = 0 ] && break
130
+ sleep 5
131
+ done
132
+ ) </dev/null >/dev/null 2>&1 &
133
+ shift "$n"
134
+ exec "$@"`;
135
+ /**
136
+ * Wrap a spec so its child owns its own artifacts. `/bin/sh` starts a watcher and then `exec`s the
137
+ * spec's command, so the agent keeps the shell's pid, its argv after the wrapper, its signals and its
138
+ * exit status. The watcher removes this launch's directories, and no other, once that pid is gone,
139
+ * whether or not the launcher is still alive. Use it wherever the launcher is the only other owner
140
+ * (every runtime but one with durable custody, and the foreground `cotal spawn`). The launcher still
141
+ * removes them on its own proof of exit; both removals are idempotent. A spec with no artifacts, and
142
+ * any spec on Windows, which has no POSIX shell, comes back unchanged.
143
+ */
144
+ export function reclaimWithChild(spec) {
145
+ const dirs = spec.artifacts;
146
+ if (!dirs?.length || process.platform === "win32")
147
+ return spec;
148
+ assertArtifactDirs(dirs);
149
+ return {
150
+ ...spec,
151
+ command: "/bin/sh",
152
+ args: ["-c", RECLAIM_SCRIPT, "cotal-launch", String(dirs.length), ...dirs, spec.command, ...spec.args],
153
+ };
154
+ }
155
+ //# sourceMappingURL=launch-artifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-artifacts.js","sourceRoot":"","sources":["../src/launch-artifacts.ts"],"names":[],"mappings":"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"}
package/dist/lease.d.ts CHANGED
@@ -41,6 +41,11 @@ export interface DeliveryLeaseInfo {
41
41
  * Optional because rows written by daemons from before this field exist in live buckets; a row
42
42
  * without one cannot be proven ours (some other process wrote it), which is the safe reading. */
43
43
  incarnation?: string;
44
+ /** Epoch ms when this incarnation acquired the shard. Unlike `since`, which every ready flip and
45
+ * renewal re-stamps, it is written once at acquisition and carried on every later write. Optional
46
+ * because rows written before this field exist; their acquisition time is unknown. */
47
+ acquiredAt?: number;
48
+ /** Epoch ms of this row's latest write: the acquisition, a ready flip, or a renewal. */
44
49
  since: number;
45
50
  ready: boolean;
46
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAO,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAK3C;;;;;;;;mGAQmG;AACnG,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;qEAIqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;sGAUkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;4GAK4G;AAC5G,MAAM,WAAW,gBAAgB;IAC/B;;;;;8BAK0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf;0GACsG;IACtG,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;6GAC6G;AAC7G,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,OAAO,yBAAyB,EAAE,cAAc,EACpD,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,EAAE,CAAC,CAEb;AAED;;;;;;;;;;;;gGAYgG;AAChG,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkCnB"}
1
+ {"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAO,KAAK,EAAE,EAAE,MAAM,aAAa,CAAC;AAK3C;;;;;;;;mGAQmG;AACnG,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;qEAIqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;;;sGAUkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;2FAEuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;4GAK4G;AAC5G,MAAM,WAAW,gBAAgB;IAC/B;;;;;8BAK0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf;0GACsG;IACtG,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;6GAC6G;AAC7G,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,OAAO,yBAAyB,EAAE,cAAc,EACpD,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,EAAE,CAAC,CAEb;AAED;;;;;;;;;;;;gGAYgG;AAChG,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkCnB"}
package/dist/lease.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lease.js","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,GAAG,EAAW,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;mGAQmG;AACnG,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,OAAO,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC;AAgDD;6GAC6G;AAC7G,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,EAAoD,EACpD,KAAa;IAEb,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;gGAYgG;AAChG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAO1C;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IACvD,IAAI,EAAmD,CAAC;IACxD,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,OAAO,CAAC;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,aAAa,EAAE,kBAAkB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvE,WAAW,EAAE,UAAU,IAAI,CAAC,EAAE,EAAE;YAChC,oBAAoB,EAAE,CAAC;SACxB,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;gBAC1D,yFAAyF;gBACzF,uFAAuF;gBACvF,+CAA+C;gBAC/C,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAqB,CAAC;oBACzC,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;wBAAE,OAAO,IAAI,CAAC;gBACrG,CAAC;gBAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;YAC3B,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"lease.js","sourceRoot":"","sources":["../src/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,GAAG,EAAW,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;mGAQmG;AACnG,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,OAAO,YAAY,CAAC,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;AACzD,CAAC;AAqDD;6GAC6G;AAC7G,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,EAAoD,EACpD,KAAa;IAEb,OAAO,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;gGAYgG;AAChG,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAO1C;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;IACvD,IAAI,EAAmD,CAAC;IACxD,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,OAAO,CAAC;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,aAAa,EAAE,kBAAkB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvE,WAAW,EAAE,UAAU,IAAI,CAAC,EAAE,EAAE;YAChC,oBAAoB,EAAE,CAAC;SACxB,CAAC,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;gBAC1D,yFAAyF;gBACzF,uFAAuF;gBACvF,+CAA+C;gBAC/C,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAqB,CAAC;oBACzC,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC;wBAAE,OAAO,IAAI,CAAC;gBACrG,CAAC;gBAAC,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;YAC3B,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0DAA0D;IAC5D,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC;QACpB,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * THE PEER-READABLE LIVENESS SURFACE (#1577) — "which plane is broken?", askable by a credentialed
3
+ * peer that does not own the plane it is asking about.
4
+ *
5
+ * THE DEFECT THIS MODULE EXISTS TO REMOVE. The subjects that answer "is the manager alive" are
6
+ * owner-only, so a peer holding valid credentials cannot ask. When its join or its send fails, the
7
+ * peer cannot tell a credential problem from a dead manager, an unbound delivery daemon, or a broker
8
+ * that is perfectly fine — so EVERY failure presents as a credential failure, because that is the
9
+ * only hypothesis the peer is able to form. The reporter's week produced six independent surfaces
10
+ * that each reported success over a failure, including a `pgrep` that matched its own command line
11
+ * and therefore fails in BOTH directions. An instrument that cannot fail is not an instrument.
12
+ *
13
+ * WHAT IS BORROWED RATHER THAN INVENTED. The vocabulary is #1594's responder classifier
14
+ * ({@link ResponderState}), moved here from the CLI so there is ONE of it: bound, unbound, stale,
15
+ * unknown, with `unknown` first class. That module's whole point was that a lease which cannot be
16
+ * read is NOT health, and this surface inherits that property rather than re-deciding it. The CLI's
17
+ * `delivery-responder.ts` now re-exports these, so both surfaces cannot drift apart.
18
+ *
19
+ * WHAT IS NEW: the ASK, and only the ask. A peer cannot read either lease — the manager bucket is
20
+ * denied to agents by omission and must stay that way (see the note on {@link LivenessAnswer}) — so
21
+ * the peer does not read state at all. It sends a presence request and grades THE OUTCOME OF ASKING.
22
+ *
23
+ * WHY THE OUTCOME OF ASKING IS A SOUND LIVENESS SIGNAL, AND WHERE IT STOPS BEING ONE. NATS reports
24
+ * "nobody is subscribed to this subject" (NoResponders, status 503) as a distinct outcome from "I
25
+ * waited and nothing came back" (a timeout). That distinction is the entire instrument:
26
+ *
27
+ * • a reply → the responder is bound AND answered, so it grades its own readiness;
28
+ * • NoResponders (503) → the broker itself says no responder is bound: `unbound`, positively;
29
+ * • a timeout → SOMETHING is subscribed but did not answer, or the broker never
30
+ * answered us at all. That is `unknown`, never `unbound` and never health.
31
+ * • a permission error → `unknown`. The peer has learnt about its own credential, not about the
32
+ * plane, and converting that into a verdict about the plane is the
33
+ * reported bug wearing new clothes.
34
+ *
35
+ * This is why a timeout must not be folded into `unbound` even though both "look like" nothing came
36
+ * back. A 503 is the BROKER's own report about subscription state and is evidence. A timeout is the
37
+ * absence of evidence, and an absence of evidence is exactly what this issue says must stop being
38
+ * rendered as a verdict.
39
+ *
40
+ * SHAPE. This is the Synadia micro protocol's `$SRV.INFO` in intent — a read-only, presence-only,
41
+ * request/reply probe on a well-known subject — but it is deliberately NOT the literal `$SRV` wire
42
+ * protocol. `$SRV` appears nowhere in this repository, and its subjects sit OUTSIDE the
43
+ * `cotal.<space>.>` tree: a surface there would escape per-space account isolation, every existing
44
+ * grant builder, and the subject audit that sweeps a space by prefix. So the probe rides a Cotal
45
+ * rail inside the space ({@link livenessSubject}), carries the micro protocol's semantics, and stays
46
+ * inside the boundaries the rest of the system already enforces.
47
+ */
48
+ import type { DeliveryLeaseInfo } from "./lease.js";
49
+ /** What is known about a RESPONDER, as opposed to a process. Moved verbatim from the CLI's #1594
50
+ * classifier so the liveness surface and `cotal status` cannot drift apart.
51
+ *
52
+ * `unknown` IS A FIRST-CLASS ANSWER AND MUST NOT BE FOLDED INTO `bound`. A read can fail for
53
+ * reasons that say nothing about the daemon (no target resolved, broker unreachable, a denied read
54
+ * on a mesh whose creds predate the read grant). Reporting any of those as health is the original
55
+ * defect in a new place.
56
+ *
57
+ * `stale` IS ALSO NOT `bound`. A ready record whose holder is not the daemon this workspace
58
+ * launched is a dead or foreign daemon's leftover, and the mesh it describes cannot spawn, retire
59
+ * or join. It is kept distinct from `unbound` because the operator's next move differs. */
60
+ export type ResponderState = "bound" | "unbound" | "stale" | "unknown";
61
+ /** The two planes a peer may ask about. Deliberately a CLOSED set rather than a free string: the
62
+ * whole complaint is that liveness is unaskable, and the fix must not become a general-purpose
63
+ * probe rail that grows a third meaning later. `manager` is the lifecycle/control plane (its
64
+ * control moved to the v0.4 `service` endpoint at 1d); `delivery` is the server-side delivery
65
+ * daemon that serves `ctl.delivery`. */
66
+ export declare const LIVENESS_PLANES: readonly ["manager", "delivery"];
67
+ export type LivenessPlane = (typeof LIVENESS_PLANES)[number];
68
+ /** Is `plane` one this surface answers for? A closed-set guard, so an unrecognised plane is refused
69
+ * rather than answered with a default — a probe that answers every input is the "sweep that gives
70
+ * the same answer at every input" failure, and it would report health for planes that do not
71
+ * exist. */
72
+ export declare function isLivenessPlane(plane: string): plane is LivenessPlane;
73
+ /** THE ANSWER, AND EVERYTHING IT IS NOT.
74
+ *
75
+ * Two fields: whether a responder is bound for the named plane, and an OPAQUE token for which
76
+ * responder said so. No holder, no pid, no workspace root, no instance id, no runtime, no roster,
77
+ * no membership — none of which a peer needs in order to learn WHICH PLANE IS BROKEN, and all of
78
+ * which the issue explicitly says the surface does not need to expose.
79
+ *
80
+ * THIS IS THE SECURITY BOUNDARY AND IT IS WHY THE PEER DOES NOT SIMPLY READ THE LEASE. The manager
81
+ * lease row ({@link import("./lease.js").ManagerLeaseInfo}) carries `holder`, `instanceId`,
82
+ * `runtime`, `root` and `pid` — the operator's workspace path and process id. An agent holds no
83
+ * grant on that bucket at all, by deliberate omission (`provision.ts`: "an agent must never read,
84
+ * write, or delete it"), and granting one to make liveness askable would hand every peer the
85
+ * operator's filesystem layout and a pid to signal. So the row NEVER leaves the responder: the
86
+ * responder reduces it to one enum and answers with that. Presence is the whole ask, and
87
+ * presence is the whole answer.
88
+ *
89
+ * `since`/`uptime` are deliberately absent too, though `$SRV.INFO` would carry them: a timestamp
90
+ * on the manager's lease dates the operator's last restart, which is state. */
91
+ export interface LivenessAnswer {
92
+ /** The plane this answer is about, echoed so a reply cannot be mistaken for another plane's. */
93
+ plane: LivenessPlane;
94
+ /** Whether a responder is bound for that plane, in #1594's vocabulary. */
95
+ responder: ResponderState;
96
+ /** WHICH RESPONDER ANSWERED, as an opaque per-bind token, or `undefined` when no responder
97
+ * answered at all (a 503, a timeout, a refusal, an unreadable reply).
98
+ *
99
+ * IT ANSWERS "WAS THIS THE SAME ONE" AND NOTHING ELSE, and that narrowness is the design. Manager
100
+ * instances coexist per instance id, each answers only about ITSELF, and the queue group hands a
101
+ * probe to an arbitrary member — so two probes that disagree are, without this field,
102
+ * indistinguishable from one responder that changed state. Comparing tokens tells those apart.
103
+ *
104
+ * IT IS NOT AN IDENTITY. The value is minted at bind time from nothing ({@link
105
+ * import("./endpoint.js").CotalEndpoint.serveLiveness}) and is not the endpoint's principal, the
106
+ * lease row's `instanceId`, a pid, a host or a path. A peer can tell two responders apart and can
107
+ * learn nothing else about either, which is the same boundary the rest of this type holds: a
108
+ * field that named the real instance would have re-opened the lease row the responder exists to
109
+ * keep off the wire.
110
+ *
111
+ * ONE PROBE STILL SAMPLES ONE RESPONDER. This field does not aggregate the plane, and a single
112
+ * answer cannot report a split. It makes a split OBSERVABLE across repeated probes, where before
113
+ * it was not observable at all. */
114
+ instance?: string;
115
+ }
116
+ /** Classify a lease record into responder state — #1594's pure classifier, moved here so the CLI
117
+ * status surfaces and this liveness surface grade by the SAME rule.
118
+ *
119
+ * `undefined` means the lease key is absent, which on a mesh that HAS a daemon recorded is an
120
+ * unbound responder rather than a missing opinion: the daemon creates its lease before it binds, so
121
+ * no lease at all means nothing has even claimed the slot.
122
+ *
123
+ * `expectedHolder` is not optional information when it is knowable. A daemon that is SIGKILLed
124
+ * leaves its `ready:true` record in the bucket for the rest of the bucket TTL, so `ready` alone
125
+ * answers "did SOME daemon bind recently enough that its record has not expired", which is not the
126
+ * question being asked. When the caller can name the daemon it means, a foreign or dead holder's
127
+ * record classifies as `stale` rather than as health. When it genuinely cannot know, pass
128
+ * `undefined` and the holder is not checked. */
129
+ export declare function responderFromLease(lease: DeliveryLeaseInfo | undefined, expectedHolder?: string): ResponderState;
130
+ /** Read the axis through a caller-supplied lease reader, mapping a FAILED read to `unknown` rather
131
+ * than to either health state. The reader is injected so offline surfaces (which must not open a
132
+ * connection) and connected ones share one classification without sharing a transport. */
133
+ export declare function responderStateFromReader(readLease: () => Promise<DeliveryLeaseInfo | undefined>, expectedHolder?: string): Promise<ResponderState>;
134
+ /** How a peer's probe ENDED, as distinct from what it means. Kept separate from
135
+ * {@link ResponderState} on purpose: the caller reports what happened on the wire, and this module
136
+ * owns the single rule that turns that into a verdict, so no call site can invent its own mapping.
137
+ *
138
+ * • `replied` — a well-formed answer came back; the responder graded itself.
139
+ * • `noResponders` — the BROKER reported 503/no-responders: positively nothing is subscribed.
140
+ * • `timeout` — we waited and nothing came back. Something may be subscribed and wedged.
141
+ * • `refused` — the broker refused us (permissions), or the transport failed. This says
142
+ * something about OUR credential or OUR link, and nothing about the plane.
143
+ * • `malformed` — a reply arrived that this surface cannot read. */
144
+ export type ProbeOutcome = "replied" | "noResponders" | "timeout" | "refused" | "malformed";
145
+ /** THE ONE RULE that turns a probe outcome into a responder verdict, and the heart of #1577.
146
+ *
147
+ * ONLY `noResponders` may become `unbound`, because only it is the broker's own positive report
148
+ * that no responder is subscribed. `timeout`, `refused` and `malformed` ALL become `unknown`: each
149
+ * is a failure to find out, and this issue exists because failures to find out were being rendered
150
+ * as findings. A peer that cannot reach the broker learns that it cannot reach the broker.
151
+ *
152
+ * `replied` defers to the responder's OWN verdict, which it derived from its lease through
153
+ * {@link responderFromLease} — so a daemon that has claimed its slot but not bound its loops
154
+ * answers `unbound` about itself rather than being counted as alive merely because it answered. A
155
+ * reply carrying no readable verdict is `malformed`, hence `unknown`; it is not silently promoted
156
+ * to `bound` on the strength of having replied at all. That promotion is precisely the `pgrep`
157
+ * error — treating evidence that a process exists as evidence that it works. */
158
+ export declare function responderFromProbe(outcome: ProbeOutcome, answered?: ResponderState): ResponderState;
159
+ /** Read a wire reply into an answer, or `undefined` if it is not one. Strict on purpose: an
160
+ * unrecognised `responder` value, a plane that does not match the one asked about, or a body of the
161
+ * wrong shape all return `undefined`, which the caller grades as `malformed` and therefore
162
+ * `unknown`. A lenient parse here would let a garbled or foreign reply be read as health, and this
163
+ * is the exact seam where "it said something" becomes "it said it was fine".
164
+ *
165
+ * `instance` IS STRICT TOO, FOR A DIFFERENT REASON THAN THE OTHER FIELDS. It is not the verdict, so
166
+ * a bad one cannot say "healthy" — but a caller compares tokens to tell two responders apart, and
167
+ * a value it cannot compare (a number, an object, an empty string) would silently read as "the same
168
+ * responder as the other answer that also had no usable token". That is a split rendered as
169
+ * agreement. A reply naming a responder in a form this surface cannot read is therefore `malformed`
170
+ * rather than an answer with the field dropped. A reply with NO `instance` at all is still
171
+ * readable: it is an answer from a responder that predates the field. */
172
+ export declare function parseLivenessAnswer(body: unknown, expectPlane: LivenessPlane): LivenessAnswer | undefined;
173
+ /** The consequence of an unbound responder, in the operator's terms rather than the daemon's — per
174
+ * plane, because they break different things and the reporter's cost was diagnosis time, not
175
+ * ignorance that something was wrong. Purely presentational: no state, no identity. */
176
+ export declare function livenessConsequence(plane: LivenessPlane): string;
177
+ /** Render one axis for a human, naming UNKNOWN as unchecked rather than as either health state.
178
+ * The `unknown` arm is the line this whole issue is about: it must never read as reassurance. */
179
+ export declare function livenessLine(answer: LivenessAnswer): string;
180
+ //# sourceMappingURL=liveness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveness.d.ts","sourceRoot":"","sources":["../src/liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;;4FAU4F;AAC5F,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE;;;;yCAIyC;AACzC,eAAO,MAAM,eAAe,kCAAkD,CAAC;AAC/E,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D;;;aAGa;AACb,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAErE;AAED;;;;;;;;;;;;;;;;;gFAiBgF;AAChF,MAAM,WAAW,cAAc;IAC7B,gGAAgG;IAChG,KAAK,EAAE,aAAa,CAAC;IACrB,0EAA0E;IAC1E,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;;;;;;;;;;;;;;wCAiBoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;iDAYiD;AACjD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,GAAG,SAAS,EACpC,cAAc,CAAC,EAAE,MAAM,GACtB,cAAc,CAKhB;AAED;;2FAE2F;AAC3F,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,EACvD,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,CAMzB;AAED;;;;;;;;;yEASyE;AACzE,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAE5F;;;;;;;;;;;;iFAYiF;AACjF,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,cAAc,CAInG;AAED;;;;;;;;;;;;0EAY0E;AAC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,GAAG,cAAc,GAAG,SAAS,CAOzG;AAED;;wFAEwF;AACxF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAIhE;AAED;kGACkG;AAClG,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAW3D"}