@cotal-ai/connector-core 0.22.0 → 0.23.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/agui-holder.d.ts.map +1 -1
- package/dist/agui-holder.js +30 -6
- package/dist/agui-holder.js.map +1 -1
- package/dist/agui-wal-path.d.ts +13 -0
- package/dist/agui-wal-path.d.ts.map +1 -1
- package/dist/agui-wal-path.js +2 -1
- package/dist/agui-wal-path.js.map +1 -1
- package/dist/agui.d.ts +11 -0
- package/dist/agui.d.ts.map +1 -1
- package/dist/agui.js +42 -4
- package/dist/agui.js.map +1 -1
- package/dist/docs-bundle.generated.js +3 -3
- package/dist/docs-bundle.generated.js.map +1 -1
- package/dist/event-wal.d.ts +35 -0
- package/dist/event-wal.d.ts.map +1 -1
- package/dist/event-wal.js +122 -4
- package/dist/event-wal.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/subject-frontier.d.ts +110 -0
- package/dist/subject-frontier.d.ts.map +1 -0
- package/dist/subject-frontier.js +416 -0
- package/dist/subject-frontier.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SUBJECT frontier, scoped to the PRINCIPAL rather than to one thread.
|
|
3
|
+
*
|
|
4
|
+
* **WHY THIS FILE EXISTS.** The write-ahead log is keyed per thread, so a new native session opens
|
|
5
|
+
* `virgin` with `lastSubjectSeq: 0` and publishes that value as its expectation. The subject is
|
|
6
|
+
* keyed per PRINCIPAL: `eventChannelForSession` returns `eventChannel(ep.principal)`, so every
|
|
7
|
+
* thread of one agent shares `events.<owner>.<actor>`. A virgin frontier therefore expected an
|
|
8
|
+
* empty subject that the agent's own previous session had already filled, the broker refused the
|
|
9
|
+
* publish, and the emitter halted permanently. Measured before this file was written: thread 1
|
|
10
|
+
* published, threads 2 and 3 halted identically. Two correct components with a false assumption
|
|
11
|
+
* between them.
|
|
12
|
+
*
|
|
13
|
+
* **WHAT IT IS NOT.** It is not a cache of something readable. Agent credentials hold neither read
|
|
14
|
+
* shape for the stream, so the tip cannot be looked up, and guessing it is the failure this
|
|
15
|
+
* replaces. It is writable without any read capability for one reason: the writer learns the
|
|
16
|
+
* assigned sequence from its OWN ack, so it only ever records what it was told about a publish it
|
|
17
|
+
* made.
|
|
18
|
+
*
|
|
19
|
+
* **MONOTONE, AND A DECREASE IS REFUSED RATHER THAN ACCEPTED.** The tip only advances. A recorded
|
|
20
|
+
* value lower than the one on disk means either a stale writer or a corrupted file, and writing it
|
|
21
|
+
* would produce an expectation the broker rejects forever with no indication of why. The one thing
|
|
22
|
+
* that legitimately moves the tip backwards is a filtered channel purge, which is an abandonment
|
|
23
|
+
* for every thread on the channel and is therefore an explicit {@link reset}, never an implicit
|
|
24
|
+
* decrease.
|
|
25
|
+
*
|
|
26
|
+
* Durability discipline is the write-ahead log's, deliberately: atomic temp-and-rename, fsync of
|
|
27
|
+
* the file and its directory, fatal UTF-8 decode, 0600, and corruption that refuses rather than
|
|
28
|
+
* guesses.
|
|
29
|
+
*/
|
|
30
|
+
import { randomUUID } from "node:crypto";
|
|
31
|
+
import { constants } from "node:fs";
|
|
32
|
+
import { lstat, open, readdir, readFile, rename, unlink } from "node:fs/promises";
|
|
33
|
+
import { dirname, join } from "node:path";
|
|
34
|
+
import { fsyncDir } from "./agui-wal-path.js";
|
|
35
|
+
/** The document on disk. Versioned, because a shape change must be refused rather than misread. */
|
|
36
|
+
const SUBJECT_FRONTIER_VERSION = 1;
|
|
37
|
+
/** Every refusal is one of these, so a caller never mistakes it for an I/O blip. */
|
|
38
|
+
export class SubjectFrontierCorruptError extends Error {
|
|
39
|
+
path;
|
|
40
|
+
invariant;
|
|
41
|
+
constructor(path, invariant, detail) {
|
|
42
|
+
super(`subject frontier ${path}: expected ${invariant} — ${detail}`);
|
|
43
|
+
this.path = path;
|
|
44
|
+
this.invariant = invariant;
|
|
45
|
+
this.name = "SubjectFrontierCorruptError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The record on disk is not the one this view was opened from, so this writer's number is not the
|
|
50
|
+
* one to write.
|
|
51
|
+
*
|
|
52
|
+
* DISTINCT FROM CORRUPTION: the file is perfectly well formed, it just belongs to a later state
|
|
53
|
+
* than this object remembers. Both are refusals rather than repairs, and telling them apart is what
|
|
54
|
+
* lets an operator know whether to look at the filesystem or at a second writer.
|
|
55
|
+
*/
|
|
56
|
+
export class SubjectFrontierMovedError extends Error {
|
|
57
|
+
path;
|
|
58
|
+
viewTip;
|
|
59
|
+
diskTip;
|
|
60
|
+
constructor(path, viewTip, diskTip) {
|
|
61
|
+
super(`subject frontier ${path}: the record moved under this writer (this view holds ${viewTip}, ` +
|
|
62
|
+
`the file holds ${diskTip === undefined ? "no record at all" : diskTip}). The tip is shared by ` +
|
|
63
|
+
`every thread of the principal, so writing this view's number would take the record backwards ` +
|
|
64
|
+
`to a sequence the broker has already passed, and every later publish would expect a tip the ` +
|
|
65
|
+
`subject no longer has.`);
|
|
66
|
+
this.path = path;
|
|
67
|
+
this.viewTip = viewTip;
|
|
68
|
+
this.diskTip = diskTip;
|
|
69
|
+
this.name = "SubjectFrontierMovedError";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const isSafeNonNegInt = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
|
|
73
|
+
/** The durable implementation, one file per principal beside that principal's thread directories. */
|
|
74
|
+
export class FileSubjectFrontier {
|
|
75
|
+
path;
|
|
76
|
+
doc;
|
|
77
|
+
constructor(path, doc) {
|
|
78
|
+
this.path = path;
|
|
79
|
+
this.doc = doc;
|
|
80
|
+
}
|
|
81
|
+
get tip() {
|
|
82
|
+
return this.doc.tip;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Open, or create a virgin record.
|
|
86
|
+
*
|
|
87
|
+
* A MISSING file is virgin and legal: this principal has never published, which is the ordinary
|
|
88
|
+
* state on a first run and after a fresh install. A ZERO-BYTE file is NOT, for the same reason
|
|
89
|
+
* the write-ahead log refuses one: an atomic temp-and-rename never produces it, so it is a
|
|
90
|
+
* filesystem that lost the tail, and reading it as "never published" is the guess this whole
|
|
91
|
+
* mechanism exists to remove.
|
|
92
|
+
*/
|
|
93
|
+
static async open(path, opts) {
|
|
94
|
+
let bytes;
|
|
95
|
+
try {
|
|
96
|
+
bytes = await readFile(path);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
if (e.code !== "ENOENT")
|
|
100
|
+
throw e;
|
|
101
|
+
}
|
|
102
|
+
if (bytes === undefined) {
|
|
103
|
+
// ABSENT, so this record has never existed. Before calling the principal virgin, look at the
|
|
104
|
+
// thread logs that already sit beside it. See {@link recoverTipFromThreadLogs}.
|
|
105
|
+
const recovered = await FileSubjectFrontier.recoverTipFromThreadLogs(dirname(path), opts.principal);
|
|
106
|
+
const fresh = new FileSubjectFrontier(path, { v: SUBJECT_FRONTIER_VERSION, space: opts.space, principal: opts.principal, tip: 0 });
|
|
107
|
+
if (recovered > 0)
|
|
108
|
+
await fresh.write({ ...fresh.doc, tip: recovered });
|
|
109
|
+
return fresh;
|
|
110
|
+
}
|
|
111
|
+
return new FileSubjectFrontier(path, FileSubjectFrontier.parse(path, bytes, opts));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Bytes to a validated document, or a refusal.
|
|
115
|
+
*
|
|
116
|
+
* SHARED BY `open` AND BY THE RE-READ IN {@link advance} on purpose. A record that went corrupt
|
|
117
|
+
* underneath a live writer has to meet the same wall as one that was corrupt at boot; validating
|
|
118
|
+
* only on the way in would let a writer that opened a good file overwrite a bad one, which
|
|
119
|
+
* destroys the evidence of whatever produced it.
|
|
120
|
+
*/
|
|
121
|
+
static parse(path, bytes, opts) {
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
// FATAL decode, never `readFile(path, "utf8")`: Node's default substitutes U+FFFD for invalid
|
|
125
|
+
// bytes, so a corrupted file arrives as a changed-but-parseable document. For a record whose
|
|
126
|
+
// whole posture is that an unreadable state fails loud, "quietly altered and accepted" is the
|
|
127
|
+
// one outcome it must not produce.
|
|
128
|
+
raw = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new SubjectFrontierCorruptError(path, "valid UTF-8", "invalid UTF-8 bytes; refusing rather than substituting U+FFFD");
|
|
132
|
+
}
|
|
133
|
+
if (raw.length === 0)
|
|
134
|
+
throw new SubjectFrontierCorruptError(path, "a non-empty file", "the file is zero bytes — distinct from missing, and never treated as virgin");
|
|
135
|
+
let parsed;
|
|
136
|
+
try {
|
|
137
|
+
parsed = JSON.parse(raw);
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
throw new SubjectFrontierCorruptError(path, "parseable JSON", e.message);
|
|
141
|
+
}
|
|
142
|
+
const d = parsed;
|
|
143
|
+
if (d?.v !== SUBJECT_FRONTIER_VERSION)
|
|
144
|
+
throw new SubjectFrontierCorruptError(path, `v === ${SUBJECT_FRONTIER_VERSION}`, String(d?.v));
|
|
145
|
+
if (d.space !== opts.space)
|
|
146
|
+
throw new SubjectFrontierCorruptError(path, "space matches", `file=${String(d.space)} caller=${opts.space}`);
|
|
147
|
+
// The principal is the whole key of this record: a file belonging to another identity would
|
|
148
|
+
// hand this writer another principal's tip, which is a fabricated frontier of exactly the kind
|
|
149
|
+
// the write-ahead log refuses for the same reason.
|
|
150
|
+
if (d.principal !== opts.principal)
|
|
151
|
+
throw new SubjectFrontierCorruptError(path, "principal matches", `file=${String(d.principal)} caller=${opts.principal}`);
|
|
152
|
+
if (!isSafeNonNegInt(d.tip))
|
|
153
|
+
throw new SubjectFrontierCorruptError(path, "tip is a safe non-negative integer", String(d.tip));
|
|
154
|
+
return { v: d.v, space: d.space, principal: d.principal, tip: d.tip };
|
|
155
|
+
}
|
|
156
|
+
async advance(seq) {
|
|
157
|
+
return this.serialize(async () => {
|
|
158
|
+
if (!isSafeNonNegInt(seq))
|
|
159
|
+
throw new Error(`subject frontier ${this.path}: seq must be a safe non-negative integer, got ${String(seq)}`);
|
|
160
|
+
// VALIDATE BEFORE THE DURABLE WRITE. A bad value written first bricks the record permanently
|
|
161
|
+
// while the call that wrote it reports success, and the next open refuses a file nothing can
|
|
162
|
+
// repair. Fail-closed has to happen before the write, not on the boot after it.
|
|
163
|
+
if (seq <= this.doc.tip)
|
|
164
|
+
throw new Error(`subject frontier ${this.path}: seq=${seq} does not advance the tip ${this.doc.tip}`);
|
|
165
|
+
// THE FILE IS THE FRONTIER; THIS OBJECT IS ONLY A VIEW OF IT, AND THE CHECK ABOVE GRADES THE
|
|
166
|
+
// VIEW. The header of this file says a decrease is refused because it is lower than the one
|
|
167
|
+
// ON DISK, and until this line the comparison was against memory, so two views of one record
|
|
168
|
+
// took it backwards with no error at all: `A.advance(10)` then `B.advance(6)` left 6 on disk.
|
|
169
|
+
//
|
|
170
|
+
// NOT REACHABLE THROUGH A PUBLISH TODAY, AND THAT IS EXACTLY WHY IT IS GUARDED. A view that
|
|
171
|
+
// has gone stale publishes a stale `E`, and the broker's compare-and-set refuses it before
|
|
172
|
+
// any ack exists to record, so JetStream is what holds this file monotone right now.
|
|
173
|
+
// Measured, not assumed: two emitters on one principal, the second opened early, halted on
|
|
174
|
+
// `wrong last sequence` with the record still holding the first one's number. That is the
|
|
175
|
+
// same shape the released defect shipped on, two correct components with an assumption
|
|
176
|
+
// standing where a guard belongs, so the assumption becomes a guard here too.
|
|
177
|
+
const disk = await this.readDiskTip();
|
|
178
|
+
// ABSENT IS NOT ZERO, and this is the third place in this plane where conflating them is the
|
|
179
|
+
// bug. A missing record is legal only for a view that has not written one either; a view
|
|
180
|
+
// holding a tip whose file has gone is a record something removed underneath a live writer,
|
|
181
|
+
// and re-creating it would resurrect a frontier that was deliberately or accidentally cleared.
|
|
182
|
+
if (disk === undefined ? this.doc.tip !== 0 : disk !== this.doc.tip)
|
|
183
|
+
throw new SubjectFrontierMovedError(this.path, this.doc.tip, disk);
|
|
184
|
+
await this.write({ ...this.doc, tip: seq });
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The tip the FILE holds, or `undefined` when no record exists yet.
|
|
189
|
+
*
|
|
190
|
+
* Fully validated, not a bare `JSON.parse().tip`: the disagreement this feeds is decided on a
|
|
191
|
+
* number, and a number taken from a document that failed its own shape checks is not evidence.
|
|
192
|
+
*/
|
|
193
|
+
async readDiskTip() {
|
|
194
|
+
let bytes;
|
|
195
|
+
try {
|
|
196
|
+
bytes = await readFile(this.path);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
if (e.code === "ENOENT")
|
|
200
|
+
return undefined;
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
return FileSubjectFrontier.parse(this.path, bytes, { space: this.doc.space, principal: this.doc.principal }).tip;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* One mutation at a time on THIS instance.
|
|
207
|
+
*
|
|
208
|
+
* The re-read above is a read-modify-write, so two callers that interleave between the read and
|
|
209
|
+
* the rename would both pass a check neither still satisfies. One frontier is legitimately bound
|
|
210
|
+
* to SEVERAL logs (the pinning runs the other way: a log may not change which record it
|
|
211
|
+
* publishes onto), so concurrent callers on one instance are an ordinary state, not a misuse.
|
|
212
|
+
*
|
|
213
|
+
* It serializes this instance and nothing else. Two instances have two chains, which is the case
|
|
214
|
+
* the re-read exists for.
|
|
215
|
+
*/
|
|
216
|
+
chain = Promise.resolve();
|
|
217
|
+
serialize(op) {
|
|
218
|
+
const next = this.chain.then(op, op);
|
|
219
|
+
// Keep the chain alive after a rejection so one refused write cannot wedge every later one.
|
|
220
|
+
this.chain = next.catch(() => undefined);
|
|
221
|
+
return next;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Recover the tip from the THREAD LOGS beside this record, for an installation upgrading from a
|
|
225
|
+
* release where this record did not exist.
|
|
226
|
+
*
|
|
227
|
+
* **THIS IS THE WHOLE UPGRADE PATH AND LEAVING IT OUT MAKES THE FIX APPLY TO NOBODY WHO ALREADY
|
|
228
|
+
* RAN THE BROKEN VERSION.** My first attempt seeded from the log of the thread being opened, which
|
|
229
|
+
* is empty in the case that matters: upgrading restarts the seat, so the first session after the
|
|
230
|
+
* upgrade is a NEW thread with a virgin log, while the sequence it needs sits in the PREVIOUS
|
|
231
|
+
* thread's log. A cell in `smoke:agui-multi-session` failed on exactly that and is the reason this
|
|
232
|
+
* function exists rather than the reasoning that produced the first version.
|
|
233
|
+
*
|
|
234
|
+
* **ONLY WHEN THE RECORD IS ABSENT, NEVER WHEN IT READS ZERO.** A record holding zero is what
|
|
235
|
+
* abandonment writes after a filtered purge, and re-seeding it from a thread log would silently
|
|
236
|
+
* undo the abandonment and restore an expectation the subject no longer has. Missing and zero are
|
|
237
|
+
* different states and this is the second place in this plane where conflating them is the bug.
|
|
238
|
+
*
|
|
239
|
+
* A sibling that cannot be read or does not parse is FATAL rather than skipped. Skipping it
|
|
240
|
+
* under-counts the tip, which produces a permanent halt later with a message about a moved tip,
|
|
241
|
+
* pointing at everything except the file that was quietly ignored here.
|
|
242
|
+
*/
|
|
243
|
+
static async recoverTipFromThreadLogs(principalDir, principal) {
|
|
244
|
+
let entries;
|
|
245
|
+
try {
|
|
246
|
+
entries = await readdir(principalDir, { withFileTypes: true });
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
if (e.code === "ENOENT")
|
|
250
|
+
return 0;
|
|
251
|
+
throw e;
|
|
252
|
+
}
|
|
253
|
+
let best = 0;
|
|
254
|
+
for (const ent of entries) {
|
|
255
|
+
// THE SCAN MAY NOT BE WALKED OUTSIDE THE PRINCIPAL DIRECTORY. The writer that creates these
|
|
256
|
+
// directories refuses a symlinked component (`ensureDirNoSymlink`), so a symlink here is a
|
|
257
|
+
// state it cannot produce, and following one would take a tip from a log belonging to some
|
|
258
|
+
// other tree. The create path and the recovery path have to agree about that or the guard is
|
|
259
|
+
// only on the half nobody attacks.
|
|
260
|
+
if (ent.isSymbolicLink())
|
|
261
|
+
throw new SubjectFrontierCorruptError(join(principalDir, ent.name), "a real directory beside the record, never a symlink", "following it would carry this scan outside the principal directory, and the writer that creates these directories refuses a symlinked component for the same reason");
|
|
262
|
+
if (!ent.isDirectory())
|
|
263
|
+
continue; // the record itself, the lock, anything else that is not a thread
|
|
264
|
+
const walPath = join(principalDir, ent.name, "wal.json");
|
|
265
|
+
// The entry check above clears the DIRECTORY and stops there, so a real thread directory
|
|
266
|
+
// holding a symlinked `wal.json` reaches the same foreign log by one more hop. Two layers,
|
|
267
|
+
// and they are not redundant:
|
|
268
|
+
//
|
|
269
|
+
// - `lstat` is the GRADED guard and the portable one. It decides the refusal on every
|
|
270
|
+
// platform, which matters because `O_NOFOLLOW` does not exist on Windows and a guard that
|
|
271
|
+
// silently evaporates there is worse than one that was never claimed.
|
|
272
|
+
// - `O_NOFOLLOW` narrows the window between the `lstat` and the `open`, where the file could
|
|
273
|
+
// be replaced by a link. It applies to the FINAL COMPONENT ONLY, so a thread directory
|
|
274
|
+
// swapped for a link in that same window is still followed; closing that would take an
|
|
275
|
+
// `openat` walk per component, which this scan does not do. NO CELL CAN GRADE EITHER
|
|
276
|
+
// WINDOW, and the mutation config says so rather than registering a mutant that would
|
|
277
|
+
// survive and be explained away.
|
|
278
|
+
let st;
|
|
279
|
+
try {
|
|
280
|
+
st = await lstat(walPath);
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
const code = e.code;
|
|
284
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
285
|
+
continue; // not a thread directory
|
|
286
|
+
throw e;
|
|
287
|
+
}
|
|
288
|
+
if (st.isSymbolicLink())
|
|
289
|
+
throw new SubjectFrontierCorruptError(walPath, "a real thread log, never a symlink", "following it would read a log this principal's writer never wrote");
|
|
290
|
+
// A HARD link is not a symlink and neither check above sees one, so a log hardlinked into
|
|
291
|
+
// this directory from elsewhere reads as an ordinary file and hands over its tip. The writer
|
|
292
|
+
// creates each log fresh, so more than one name for it is a state it cannot produce, which
|
|
293
|
+
// is the same reason the symlink is refused rather than resolved.
|
|
294
|
+
if (st.nlink > 1)
|
|
295
|
+
throw new SubjectFrontierCorruptError(walPath, "a thread log with exactly one name", `it has ${st.nlink}, so the same file is reachable from outside this principal's directory and its tip is not this principal's to read`);
|
|
296
|
+
let raw;
|
|
297
|
+
try {
|
|
298
|
+
const fh = await open(walPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
299
|
+
try {
|
|
300
|
+
raw = await fh.readFile();
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
await fh.close();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (e) {
|
|
307
|
+
const code = e.code;
|
|
308
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
309
|
+
continue;
|
|
310
|
+
if (code === "ELOOP")
|
|
311
|
+
throw new SubjectFrontierCorruptError(walPath, "a real thread log, never a symlink", "the file became a symlink between the check and the open");
|
|
312
|
+
throw e;
|
|
313
|
+
}
|
|
314
|
+
let doc;
|
|
315
|
+
try {
|
|
316
|
+
doc = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(raw));
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
throw new SubjectFrontierCorruptError(walPath, "a readable thread log while recovering the subject tip", e.message);
|
|
320
|
+
}
|
|
321
|
+
// A log under another principal's directory is not ours to read a tip from. It cannot happen
|
|
322
|
+
// in the shipped layout, which is why it is checked: a recovery component must refuse the
|
|
323
|
+
// states its own writer cannot produce.
|
|
324
|
+
if (doc.principal !== principal)
|
|
325
|
+
throw new SubjectFrontierCorruptError(walPath, `a thread log for principal ${principal}`, `found ${String(doc.principal)}`);
|
|
326
|
+
const seq = doc.frontier?.lastSubjectSeq;
|
|
327
|
+
if (!isSafeNonNegInt(seq))
|
|
328
|
+
throw new SubjectFrontierCorruptError(walPath, "frontier.lastSubjectSeq is a safe non-negative integer", String(seq));
|
|
329
|
+
if (seq > best)
|
|
330
|
+
best = seq;
|
|
331
|
+
// AN ACKED PENDING HOLDS A SEQUENCE THE BROKER ASSIGNED AND THE FRONTIER DOES NOT.
|
|
332
|
+
//
|
|
333
|
+
// A log that took an ack and died before folding keeps the assigned sequence in
|
|
334
|
+
// `pending.ackSeq`, which `EventWal` requires to be strictly AHEAD of
|
|
335
|
+
// `frontier.lastSubjectSeq` (that is the crash window the acked state exists to survive).
|
|
336
|
+
// Reading the frontier alone recovers the older number, persists it, and hands the next
|
|
337
|
+
// publish an expectation the subject passed some time ago. The session that could fold it
|
|
338
|
+
// will never run again, because upgrading forks the session id, so this log is the only
|
|
339
|
+
// place that sequence survives. Under-counting here is the same defect this file fixes,
|
|
340
|
+
// reintroduced at the one boundary the file exists for.
|
|
341
|
+
const pending = doc.pending;
|
|
342
|
+
if (pending && pending.state === "acked") {
|
|
343
|
+
const acked = pending.ackSeq;
|
|
344
|
+
if (!isSafeNonNegInt(acked))
|
|
345
|
+
throw new SubjectFrontierCorruptError(walPath, "an acked pending carries a safe non-negative ackSeq", String(acked));
|
|
346
|
+
if (!(acked > seq))
|
|
347
|
+
throw new SubjectFrontierCorruptError(walPath, "an acked pending is ahead of the frontier it will fold into", `ackSeq=${acked} frontier.lastSubjectSeq=${seq}`);
|
|
348
|
+
if (acked > best)
|
|
349
|
+
best = acked;
|
|
350
|
+
}
|
|
351
|
+
// A `sent_unacked` PENDING IS PASSED OVER DELIBERATELY, AND NOT BECAUSE NOTHING WAS ASSIGNED.
|
|
352
|
+
//
|
|
353
|
+
// An earlier description of this scan said the broker assigned nothing to such a frame. That
|
|
354
|
+
// is the one thing the state does not know: the frame went out and the acknowledgement was
|
|
355
|
+
// never observed, so the subject may or may not have taken it. What is certain is structural
|
|
356
|
+
// and about the log rather than the broker: `sent_unacked` carries no `ackSeq` at all, and a
|
|
357
|
+
// document that pairs the two is refused as contradicting its own tag. There is therefore no
|
|
358
|
+
// sequence in it to fold, and inventing one, `frontier.lastSubjectSeq + 1` for instance, would
|
|
359
|
+
// assert an assignment nobody saw.
|
|
360
|
+
//
|
|
361
|
+
// The residue is real and is left standing on purpose. If the broker did assign a sequence to
|
|
362
|
+
// that frame, this scan recovers a number one short of the tip, and the next publish halts on
|
|
363
|
+
// a moved tip rather than publishing into a gap or overwriting anything. That is the safe
|
|
364
|
+
// direction of the two, it is the halt this file's message now explains, and its remedy is the
|
|
365
|
+
// one the message names. The owning session would have republished the frozen id and let the
|
|
366
|
+
// broker deduplicate, but that session is exactly what an upgrade forks away from, which is
|
|
367
|
+
// why the case reaches here at all.
|
|
368
|
+
}
|
|
369
|
+
return best;
|
|
370
|
+
}
|
|
371
|
+
// `seedFromThread` used to live here, and it is GONE rather than kept for a caller that might
|
|
372
|
+
// want it. Recovery moved into `open`, which is the only place that can see every sibling log,
|
|
373
|
+
// and what was left behind was a public method that writes a tip into a record whose only
|
|
374
|
+
// precondition is that the record reads 0. A record reading 0 is exactly what abandonment writes
|
|
375
|
+
// after a channel purge, so the leftover was a supported route back into the state this file
|
|
376
|
+
// exists to prevent, with no shipped caller to justify it.
|
|
377
|
+
async reset() {
|
|
378
|
+
// UNCONDITIONAL, and deliberately not re-read. Abandonment is the one thing that legitimately
|
|
379
|
+
// takes the tip backwards, so a record that moved under this writer is not an obstacle to it:
|
|
380
|
+
// clearing is correct whatever the file currently holds.
|
|
381
|
+
return this.serialize(async () => {
|
|
382
|
+
await this.write({ ...this.doc, tip: 0 });
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/** Atomic replace: sibling temp, fsync, rename, fsync the directory. */
|
|
386
|
+
async write(next) {
|
|
387
|
+
const tmp = `${this.path}.${randomUUID()}.tmp`;
|
|
388
|
+
const fh = await open(tmp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
389
|
+
try {
|
|
390
|
+
await fh.writeFile(JSON.stringify(next));
|
|
391
|
+
await fh.sync();
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
await fh.close();
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
await rename(tmp, this.path);
|
|
398
|
+
}
|
|
399
|
+
catch (e) {
|
|
400
|
+
await unlink(tmp).catch(() => { });
|
|
401
|
+
throw e;
|
|
402
|
+
}
|
|
403
|
+
// The rename itself must be durable, or a crash can lose the new name and leave the old file:
|
|
404
|
+
// the record would silently go backwards, which is the one direction `advance` refuses.
|
|
405
|
+
//
|
|
406
|
+
// THE SHARED HELPER, NOT A SECOND STRICTER COPY. This was an inline open/sync that let every
|
|
407
|
+
// error propagate, and it sits on the ACK path: on a filesystem that refuses to fsync a
|
|
408
|
+
// directory handle, or under a permission that refuses the open, the same conditions the
|
|
409
|
+
// directory-creating path already tolerates would throw HERE, after the broker has acknowledged
|
|
410
|
+
// the frame, leaving an ack with no durable record and a halt on the next start. Two paths over
|
|
411
|
+
// the same operation disagreeing about which errors are fatal is a divergence, not a policy.
|
|
412
|
+
await fsyncDir(dirname(this.path));
|
|
413
|
+
this.doc = next;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=subject-frontier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subject-frontier.js","sourceRoot":"","sources":["../src/subject-frontier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAElF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,mGAAmG;AACnG,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAUnC,oFAAoF;AACpF,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAC/B;IAAuB;IAA5C,YAAqB,IAAY,EAAW,SAAiB,EAAE,MAAc;QAC3E,KAAK,CAAC,oBAAoB,IAAI,cAAc,SAAS,MAAM,MAAM,EAAE,CAAC,CAAC;QADlD,SAAI,GAAJ,IAAI,CAAQ;QAAW,cAAS,GAAT,SAAS,CAAQ;QAE3D,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC5C,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAC7B;IAAuB;IAA0B;IAAtE,YAAqB,IAAY,EAAW,OAAe,EAAW,OAA2B;QAC/F,KAAK,CACH,oBAAoB,IAAI,yDAAyD,OAAO,IAAI;YAC1F,kBAAkB,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,0BAA0B;YAChG,+FAA+F;YAC/F,8FAA8F;YAC9F,wBAAwB,CAC3B,CAAC;QAPiB,SAAI,GAAJ,IAAI,CAAQ;QAAW,YAAO,GAAP,OAAO,CAAQ;QAAW,YAAO,GAAP,OAAO,CAAoB;QAQ/F,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAkBD,MAAM,eAAe,GAAG,CAAC,CAAU,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEhH,qGAAqG;AACrG,MAAM,OAAO,mBAAmB;IAEX;IACT;IAFV,YACmB,IAAY,EACrB,GAAe;QADN,SAAI,GAAJ,IAAI,CAAQ;QACrB,QAAG,GAAH,GAAG,CAAY;IACtB,CAAC;IAEJ,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACtB,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAA0C;QACxE,IAAI,KAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,6FAA6F;YAC7F,gFAAgF;YAChF,MAAM,SAAS,GAAG,MAAM,mBAAmB,CAAC,wBAAwB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACpG,MAAM,KAAK,GAAG,IAAI,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,wBAAwB,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnI,IAAI,SAAS,GAAG,CAAC;gBAAE,MAAM,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YACvE,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,mBAAmB,CAAC,IAAI,EAAE,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;OAOG;IACK,MAAM,CAAC,KAAK,CAAC,IAAY,EAAE,KAAa,EAAE,IAA0C;QAC1F,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,8FAA8F;YAC9F,6FAA6F;YAC7F,8FAA8F;YAC9F,mCAAmC;YACnC,GAAG,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,aAAa,EAAE,+DAA+D,CAAC,CAAC;QAC9H,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAClB,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,kBAAkB,EAAE,6EAA6E,CAAC,CAAC;QAEjJ,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,CAAC,GAAG,MAA6B,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,KAAK,wBAAwB;YAAE,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,SAAS,wBAAwB,EAAE,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACtI,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QACzI,4FAA4F;QAC5F,+FAA+F;QAC/F,mDAAmD;QACnD,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;YAChC,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,mBAAmB,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,WAAW,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QAC3H,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,MAAM,IAAI,2BAA2B,CAAC,IAAI,EAAE,oCAAoC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9H,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE;YAC/B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,IAAI,kDAAkD,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACzI,6FAA6F;YAC7F,6FAA6F;YAC7F,gFAAgF;YAChF,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG;gBACrB,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,CAAC,IAAI,SAAS,GAAG,6BAA6B,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;YACxG,6FAA6F;YAC7F,4FAA4F;YAC5F,6FAA6F;YAC7F,8FAA8F;YAC9F,EAAE;YACF,4FAA4F;YAC5F,2FAA2F;YAC3F,qFAAqF;YACrF,2FAA2F;YAC3F,0FAA0F;YAC1F,uFAAuF;YACvF,8EAA8E;YAC9E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YACtC,6FAA6F;YAC7F,yFAAyF;YACzF,4FAA4F;YAC5F,+FAA+F;YAC/F,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG;gBACjE,MAAM,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACrE,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,WAAW;QACvB,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAC;YACrE,MAAM,CAAC,CAAC;QACV,CAAC;QACD,OAAO,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC;IACnH,CAAC;IAED;;;;;;;;;;OAUG;IACK,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC5C,SAAS,CAAI,EAAoB;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrC,4FAA4F;QAC5F,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACK,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,YAAoB,EAAE,SAAiB;QACnF,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,CAAC,CAAC;YAC7D,MAAM,CAAC,CAAC;QACV,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,4FAA4F;YAC5F,2FAA2F;YAC3F,2FAA2F;YAC3F,6FAA6F;YAC7F,mCAAmC;YACnC,IAAI,GAAG,CAAC,cAAc,EAAE;gBACtB,MAAM,IAAI,2BAA2B,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,qDAAqD,EAAE,qKAAqK,CAAC,CAAC;YACpS,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE;gBAAE,SAAS,CAAC,kEAAkE;YACpG,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YACzD,yFAAyF;YACzF,2FAA2F;YAC3F,8BAA8B;YAC9B,EAAE;YACF,uFAAuF;YACvF,6FAA6F;YAC7F,yEAAyE;YACzE,8FAA8F;YAC9F,0FAA0F;YAC1F,0FAA0F;YAC1F,wFAAwF;YACxF,yFAAyF;YACzF,oCAAoC;YACpC,IAAI,EAAE,CAAC;YACP,IAAI,CAAC;gBACH,EAAE,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,GAAI,CAA2B,CAAC,IAAI,CAAC;gBAC/C,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;oBAAE,SAAS,CAAC,yBAAyB;gBAChF,MAAM,CAAC,CAAC;YACV,CAAC;YACD,IAAI,EAAE,CAAC,cAAc,EAAE;gBACrB,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,oCAAoC,EAAE,mEAAmE,CAAC,CAAC;YAC5J,0FAA0F;YAC1F,6FAA6F;YAC7F,2FAA2F;YAC3F,kEAAkE;YAClE,IAAI,EAAE,CAAC,KAAK,GAAG,CAAC;gBACd,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,oCAAoC,EAAE,UAAU,EAAE,CAAC,KAAK,qHAAqH,CAAC,CAAC;YAChO,IAAI,GAAW,CAAC;YAChB,IAAI,CAAC;gBACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,GAAG,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;gBACjF,IAAI,CAAC;oBACH,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC;gBAC5B,CAAC;wBAAS,CAAC;oBACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;gBACnB,CAAC;YACH,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,GAAI,CAA2B,CAAC,IAAI,CAAC;gBAC/C,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;oBAAE,SAAS;gBACtD,IAAI,IAAI,KAAK,OAAO;oBAClB,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,oCAAoC,EAAE,0DAA0D,CAAC,CAAC;gBACnJ,MAAM,CAAC,CAAC;YACV,CAAC;YACD,IAAI,GAA6H,CAAC;YAClI,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAe,CAAC;YACxF,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,wDAAwD,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;YACjI,CAAC;YACD,6FAA6F;YAC7F,0FAA0F;YAC1F,wCAAwC;YACxC,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS;gBAC7B,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,8BAA8B,SAAS,EAAE,EAAE,SAAS,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YAC9H,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC;YACzC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;gBACvB,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,wDAAwD,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACxH,IAAI,GAAG,GAAG,IAAI;gBAAE,IAAI,GAAG,GAAG,CAAC;YAC3B,mFAAmF;YACnF,EAAE;YACF,gFAAgF;YAChF,sEAAsE;YACtE,0FAA0F;YAC1F,wFAAwF;YACxF,0FAA0F;YAC1F,wFAAwF;YACxF,wFAAwF;YACxF,wDAAwD;YACxD,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;YAC5B,IAAI,OAAO,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;gBACzC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;gBAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;oBACzB,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,qDAAqD,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBACvH,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC;oBAChB,MAAM,IAAI,2BAA2B,CAAC,OAAO,EAAE,6DAA6D,EAAE,UAAU,KAAK,4BAA4B,GAAG,EAAE,CAAC,CAAC;gBAClK,IAAI,KAAK,GAAG,IAAI;oBAAE,IAAI,GAAG,KAAK,CAAC;YACjC,CAAC;YACD,8FAA8F;YAC9F,EAAE;YACF,6FAA6F;YAC7F,2FAA2F;YAC3F,6FAA6F;YAC7F,6FAA6F;YAC7F,6FAA6F;YAC7F,+FAA+F;YAC/F,mCAAmC;YACnC,EAAE;YACF,8FAA8F;YAC9F,8FAA8F;YAC9F,0FAA0F;YAC1F,+FAA+F;YAC/F,6FAA6F;YAC7F,4FAA4F;YAC5F,oCAAoC;QACtC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8FAA8F;IAC9F,+FAA+F;IAC/F,0FAA0F;IAC1F,iGAAiG;IACjG,6FAA6F;IAC7F,2DAA2D;IAE3D,KAAK,CAAC,KAAK;QACT,8FAA8F;QAC9F,8FAA8F;QAC9F,yDAAyD;QACzD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE;YAC/B,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,wEAAwE;IAChE,KAAK,CAAC,KAAK,CAAC,IAAgB;QAClC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,IAAI,UAAU,EAAE,MAAM,CAAC;QAC/C,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC7F,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;YACzC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;QAClB,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAClC,MAAM,CAAC,CAAC;QACV,CAAC;QACD,8FAA8F;QAC9F,wFAAwF;QACxF,EAAE;QACF,6FAA6F;QAC7F,wFAAwF;QACxF,yFAAyF;QACzF,gGAAgG;QAChG,gGAAgG;QAChG,6FAA6F;QAC7F,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;IAClB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/connector-core",
|
|
3
3
|
"description": "Shared MCP-bridge runtime for Cotal connectors: the mesh agent, cotal_* tools, and hook relay.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@ag-ui/core": "0.0.57",
|
|
33
33
|
"@cotal-ai/smoke-kit": "0.0.0",
|
|
34
|
-
"@cotal-ai/core": "0.
|
|
34
|
+
"@cotal-ai/core": "0.23.0"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist"
|