@loomwork/ui-changes 0.0.0-stage → 0.1.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 (46) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +134 -2
  4. package/dist/conformance.d.ts +18 -0
  5. package/dist/conformance.d.ts.map +1 -0
  6. package/dist/conformance.js +243 -0
  7. package/dist/conformance.js.map +1 -0
  8. package/dist/cursor.d.ts +43 -0
  9. package/dist/cursor.d.ts.map +1 -0
  10. package/dist/cursor.js +50 -0
  11. package/dist/cursor.js.map +1 -0
  12. package/dist/esm/conformance.js +239 -0
  13. package/dist/esm/conformance.js.map +1 -0
  14. package/dist/esm/cursor.js +44 -0
  15. package/dist/esm/cursor.js.map +1 -0
  16. package/dist/esm/feed.js +301 -0
  17. package/dist/esm/feed.js.map +1 -0
  18. package/dist/esm/index.js +6 -0
  19. package/dist/esm/index.js.map +1 -0
  20. package/dist/esm/signal.js +141 -0
  21. package/dist/esm/signal.js.map +1 -0
  22. package/dist/esm/untrusted.js +14 -0
  23. package/dist/esm/untrusted.js.map +1 -0
  24. package/dist/feed.d.ts +125 -0
  25. package/dist/feed.d.ts.map +1 -0
  26. package/dist/feed.js +305 -0
  27. package/dist/feed.js.map +1 -0
  28. package/dist/index.d.ts +10 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +20 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/signal.d.ts +33 -0
  33. package/dist/signal.d.ts.map +1 -0
  34. package/dist/signal.js +145 -0
  35. package/dist/signal.js.map +1 -0
  36. package/dist/untrusted.d.ts +5 -0
  37. package/dist/untrusted.d.ts.map +1 -0
  38. package/dist/untrusted.js +17 -0
  39. package/dist/untrusted.js.map +1 -0
  40. package/package.json +60 -3
  41. package/src/conformance.ts +248 -0
  42. package/src/cursor.ts +89 -0
  43. package/src/feed.ts +446 -0
  44. package/src/index.ts +18 -0
  45. package/src/signal.ts +200 -0
  46. package/src/untrusted.ts +16 -0
@@ -0,0 +1,301 @@
1
+ import { describeChange, mergeReadPolicy, } from "@loomwork/ui-core";
2
+ import { EXPERIENCE_BRANCHED, EXPERIENCE_PROMOTED, EXPERIENCE_REVERTED, LineErasedError, foldLineage, } from "@loomwork/ui-lineage";
3
+ export const DEFAULT_PAGE = 100;
4
+ export const MAX_PAGE = 1000;
5
+ const err = (code, message) => ({
6
+ ok: false,
7
+ finding: { code, severity: "error", message },
8
+ });
9
+ const asCursor = (v) => typeof v === "number" && Number.isSafeInteger(v) && v >= 0 ? v : undefined;
10
+ /**
11
+ * A `ChangeFeed` over a `Lineage`. `read` folds the whole line (validating it: a malformed log is a
12
+ * finding, never partial events) so it is O(log length) per call; a deployment with very long lines
13
+ * pages with `limit` but still pays the fold: cache above this seam, not inside it.
14
+ */
15
+ export class LineageChangeFeed {
16
+ lineage;
17
+ events;
18
+ signal;
19
+ policy;
20
+ constructor(opts) {
21
+ this.policy = mergeReadPolicy(opts.lineage.readPolicy, opts.readPolicy, opts);
22
+ this.lineage = opts.lineage;
23
+ this.events = opts.events;
24
+ this.signal = opts.signal;
25
+ }
26
+ async read(experienceId, opts = {}) {
27
+ const after = opts.after === undefined ? 0 : asCursor(opts.after);
28
+ if (after === undefined)
29
+ return err("cursor-invalid", "after is not a non-negative integer");
30
+ const limit = opts.limit === undefined
31
+ ? DEFAULT_PAGE
32
+ : Number.isSafeInteger(opts.limit) && opts.limit >= 1
33
+ ? Math.min(opts.limit, MAX_PAGE)
34
+ : undefined;
35
+ if (limit === undefined)
36
+ return err("limit-invalid", "limit is not a positive integer");
37
+ let records;
38
+ try {
39
+ records = await this.events.read(experienceId);
40
+ }
41
+ catch (e) {
42
+ // The feed INHERITS the lineage's `confirmErased` hook (it classifies through `lineage.block`):
43
+ // a confirmed erasure is `line-erased`, any other unreadable line is `line-unreadable` (or
44
+ // `artifact-unsealed`); both fail closed. Anything else propagates unchanged.
45
+ const b = await this.lineage.block(experienceId, e);
46
+ if (b === undefined)
47
+ throw e;
48
+ return err(b instanceof LineErasedError ? "line-erased" : b.reason, b instanceof LineErasedError
49
+ ? `lineage "${experienceId}" is erased: its key is destroyed (confirmed)`
50
+ : `lineage "${experienceId}" is unreadable: its key is unavailable on this node or its ciphertext does not open; not confirmed erased`);
51
+ }
52
+ const fold = foldLineage(experienceId, records);
53
+ if (!fold.ok)
54
+ return { ok: false, finding: fold.finding };
55
+ const out = [];
56
+ let promoted = null;
57
+ let more = false;
58
+ for (const rec of records) {
59
+ // The fold validated every record, so these payload reads are of checked shapes.
60
+ const p = rec.payload;
61
+ const version = fold.state.versions.get(p.versionHash);
62
+ let kind;
63
+ let base;
64
+ if (rec.type === EXPERIENCE_PROMOTED) {
65
+ kind = "promoted";
66
+ base = promoted === null ? null : { experienceId, versionHash: promoted };
67
+ promoted = p.versionHash;
68
+ }
69
+ else {
70
+ kind =
71
+ rec.type === EXPERIENCE_REVERTED
72
+ ? "reverted"
73
+ : rec.type === EXPERIENCE_BRANCHED
74
+ ? "branched"
75
+ : "composed";
76
+ const parent = version?.parents[0];
77
+ base =
78
+ kind === "branched" && version?.from !== undefined
79
+ ? { experienceId: version.from.experienceId, versionHash: version.from.versionHash }
80
+ : parent === undefined
81
+ ? null
82
+ : { experienceId, versionHash: parent };
83
+ }
84
+ if (rec.sequence <= after)
85
+ continue;
86
+ if (out.length === limit) {
87
+ more = true;
88
+ break;
89
+ }
90
+ out.push({
91
+ id: `${experienceId}#${rec.sequence}`,
92
+ experienceId,
93
+ sequence: rec.sequence,
94
+ at: rec.at,
95
+ kind,
96
+ versionHash: p.versionHash,
97
+ base,
98
+ });
99
+ }
100
+ return {
101
+ ok: true,
102
+ events: out,
103
+ cursor: out.at(-1)?.sequence ?? after,
104
+ more,
105
+ tip: fold.state.sequence,
106
+ };
107
+ }
108
+ subscribe(experienceId, handler, opts = {}) {
109
+ let cursor = asCursor(opts.after) ?? 0;
110
+ let stopped = false;
111
+ let running = false;
112
+ let again = false;
113
+ let timer;
114
+ let failures = 0; // consecutive failed passes, for the backoff
115
+ let poisonSeq = 0;
116
+ let poisonAttempts = 0;
117
+ const baseMs = Math.max(1, finite(opts.retry?.baseMs, 250));
118
+ const maxMs = Math.max(baseMs, finite(opts.retry?.maxMs, 30_000));
119
+ const report = (e) => {
120
+ try {
121
+ opts.onError?.(e);
122
+ }
123
+ catch {
124
+ // a reporting hook must not break delivery
125
+ }
126
+ };
127
+ // The cursor is behind the tip whenever a pass failed: retry on a timer, since a quiet line
128
+ // raises no signal. Delay = min(max, base * 2^n), jittered into [50%, 100%].
129
+ const arm = () => {
130
+ if (stopped || timer !== undefined)
131
+ return;
132
+ const delay = Math.min(maxMs, baseMs * 2 ** Math.min(failures, 30));
133
+ failures += 1;
134
+ timer = setTimeout(() => {
135
+ timer = undefined;
136
+ void pump();
137
+ }, Math.max(1, Math.round(delay * (0.5 + Math.random() / 2))));
138
+ timer.unref?.();
139
+ };
140
+ const skipPoison = async (e) => {
141
+ if (poisonSeq !== e.sequence) {
142
+ poisonSeq = e.sequence;
143
+ poisonAttempts = 0;
144
+ }
145
+ poisonAttempts += 1;
146
+ if (opts.onPoison === undefined)
147
+ return false;
148
+ try {
149
+ return (await opts.onPoison(e, poisonAttempts)) === true;
150
+ }
151
+ catch (thrown) {
152
+ report(thrown);
153
+ return false;
154
+ }
155
+ };
156
+ const pump = async () => {
157
+ if (running) {
158
+ again = true;
159
+ return;
160
+ }
161
+ running = true;
162
+ let failed = false;
163
+ try {
164
+ do {
165
+ again = false;
166
+ for (;;) {
167
+ if (stopped)
168
+ return;
169
+ const r = await this.read(experienceId, { after: cursor });
170
+ if (!r.ok) {
171
+ report(r.finding);
172
+ // A CONFIRMED erasure is terminal: report once and stop (no retry, no further wakes).
173
+ // An unconfirmed `line-unreadable` keeps retrying: the key may come back to this node.
174
+ if (r.finding.code === "line-erased") {
175
+ terminate();
176
+ return;
177
+ }
178
+ failed = true;
179
+ return;
180
+ }
181
+ for (const e of r.events) {
182
+ if (stopped)
183
+ return;
184
+ try {
185
+ await handler(e);
186
+ }
187
+ catch (thrown) {
188
+ report(thrown);
189
+ if (await skipPoison(e)) {
190
+ cursor = e.sequence;
191
+ continue;
192
+ }
193
+ failed = true;
194
+ return; // not advanced: redelivered by the retry timer or the next wake
195
+ }
196
+ cursor = e.sequence;
197
+ failures = 0;
198
+ }
199
+ if (!r.more)
200
+ break;
201
+ }
202
+ } while (again && !stopped);
203
+ }
204
+ catch (thrown) {
205
+ report(thrown);
206
+ failed = true;
207
+ }
208
+ finally {
209
+ running = false;
210
+ if (failed)
211
+ arm();
212
+ else
213
+ failures = 0;
214
+ }
215
+ };
216
+ let unwatch;
217
+ const terminate = () => {
218
+ stopped = true;
219
+ if (timer !== undefined)
220
+ clearTimeout(timer);
221
+ timer = undefined;
222
+ unwatch?.();
223
+ unwatch = undefined;
224
+ };
225
+ unwatch = this.signal.watch(experienceId, () => void pump());
226
+ void pump();
227
+ return terminate;
228
+ }
229
+ async describe(event, opts = {}) {
230
+ // The caller's event is a CLAIM about the log, not the log: its experience and sequence are
231
+ // used only to find the recorded event, and `kind`/`base`/`versionHash` come from the fold.
232
+ // An event that does not match the record at its position is refused (a forged `base` would
233
+ // otherwise describe a different line's nodes: a confused deputy).
234
+ const seq = event?.sequence;
235
+ if (typeof event?.experienceId !== "string" || !Number.isSafeInteger(seq) || seq < 1)
236
+ return err("event-invalid", "the event has no readable line and sequence");
237
+ const at = await this.read(event.experienceId, { after: seq - 1, limit: 1 });
238
+ if (!at.ok)
239
+ return { ok: false, finding: at.finding };
240
+ const truth = at.events[0];
241
+ if (truth === undefined || truth.sequence !== seq)
242
+ return err("event-unknown", "the event is not in the line's log");
243
+ if (truth.versionHash !== event.versionHash ||
244
+ truth.kind !== event.kind ||
245
+ JSON.stringify(truth.base) !== JSON.stringify(event.base ?? null))
246
+ return err("event-mismatch", "the event does not match the log's record at its sequence");
247
+ const versions = await this.lineage.versions(truth.experienceId);
248
+ if (!versions.ok)
249
+ return refusal(versions.refusal);
250
+ const record = versions.value.find((v) => v.versionHash === truth.versionHash);
251
+ if (record === undefined)
252
+ return err("version-unknown", `"${truth.versionHash}" is not a version of the line`);
253
+ const next = await this.lineage.get(truth.experienceId, truth.versionHash);
254
+ if (!next.ok)
255
+ return refusal(next.refusal);
256
+ let prev = null;
257
+ if (truth.base !== null) {
258
+ const p = await this.lineage.get(truth.base.experienceId, truth.base.versionHash);
259
+ if (!p.ok)
260
+ return refusal(p.refusal);
261
+ prev = p.value;
262
+ }
263
+ // The ops and the summary belong to ONE version. They describe this event when it IS that
264
+ // version's creation, or when a promotion spans exactly that one step.
265
+ const own = truth.kind !== "promoted" || record.parents.includes(truth.base?.versionHash ?? "");
266
+ // The bound policy first: a per-call value can only ADD to it (a table is joined strictest-wins,
267
+ // `"deny"` sticks, a bound projection catalogue is not replaced), so `{}` or an explicit
268
+ // `undefined` per call cannot lower what the host bound at construction.
269
+ const policy = mergeReadPolicy(this.policy, opts);
270
+ return {
271
+ ok: true,
272
+ description: describeChange(prev, next.value, {
273
+ ...opts,
274
+ // AFTER `...opts`: the joined policy wins over every per-call spelling of these fields.
275
+ ...(policy.projections !== undefined && { projections: policy.projections }),
276
+ ...(policy.hydrationProjectionId !== undefined && {
277
+ hydrationProjectionId: policy.hydrationProjectionId,
278
+ }),
279
+ ...(policy.declaredToolClasses !== undefined && {
280
+ declaredToolClasses: policy.declaredToolClasses,
281
+ }),
282
+ ...(policy.undeclared !== undefined && { undeclared: policy.undeclared }),
283
+ ...(own && record.ops.length > 0 && { ops: record.ops }),
284
+ ...(own && record.summary !== undefined && { summary: record.summary }),
285
+ ...(own && record.summaryClass !== undefined && { summaryClass: record.summaryClass }),
286
+ }),
287
+ };
288
+ }
289
+ }
290
+ const finite = (v, dflt) => typeof v === "number" && Number.isFinite(v) ? v : dflt;
291
+ function refusal(r) {
292
+ return {
293
+ ok: false,
294
+ finding: r.finding ?? {
295
+ code: r.kind,
296
+ severity: "error",
297
+ message: `the lineage refused: ${r.kind}`,
298
+ },
299
+ };
300
+ }
301
+ //# sourceMappingURL=feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.js","sourceRoot":"","sources":["../../src/feed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { LineageChangeFeed, DEFAULT_PAGE, MAX_PAGE } from "./feed";
2
+ export { InMemoryUnreadStore, markRead, unreadChanges } from "./cursor";
3
+ export { eventStoreHeadSignal, pollingHeadSignal } from "./signal";
4
+ export { checkChangeFeedConformance, checkUnreadStoreConformance } from "./conformance";
5
+ export { summaryAsTainted } from "./untrusted";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAWnE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAExE,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEnE,OAAO,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,141 @@
1
+ import { LineErasedError, classifyLineFailure, foldLineage, } from "@loomwork/ui-lineage";
2
+ import { LINEAGE_STREAM_PREFIX } from "@loomwork/ui-lineage";
3
+ /** One watcher's check: at most one read in flight, reports each new tip once. */
4
+ function checker(store, experienceId, onChange, onError, confirmErased, onTerminal) {
5
+ let seen = 0;
6
+ let stopped = false;
7
+ let busy = false;
8
+ let rerun = false;
9
+ const fail = (e) => {
10
+ try {
11
+ onError?.(e);
12
+ }
13
+ catch {
14
+ // never break the signal
15
+ }
16
+ };
17
+ const check = async () => {
18
+ if (stopped)
19
+ return;
20
+ if (busy) {
21
+ rerun = true;
22
+ return;
23
+ }
24
+ busy = true;
25
+ try {
26
+ do {
27
+ rerun = false;
28
+ const records = await store.read(experienceId);
29
+ if (stopped || records.length <= seen)
30
+ continue;
31
+ seen = records.length;
32
+ const fold = foldLineage(experienceId, records);
33
+ if (!fold.ok) {
34
+ fail(fold.finding);
35
+ continue;
36
+ }
37
+ try {
38
+ onChange({
39
+ experienceId,
40
+ sequence: fold.state.sequence,
41
+ head: fold.state.head,
42
+ promoted: fold.state.promoted,
43
+ });
44
+ }
45
+ catch (e) {
46
+ fail(e);
47
+ }
48
+ } while (rerun && !stopped);
49
+ }
50
+ catch (e) {
51
+ // an unreadable line reports a typed error; anything else is reported unchanged
52
+ const b = await classifyLineFailure(experienceId, e, confirmErased);
53
+ fail(b ?? e);
54
+ if (b instanceof LineErasedError) {
55
+ // confirmed erased: terminal, reported once
56
+ stopped = true;
57
+ onTerminal();
58
+ }
59
+ }
60
+ finally {
61
+ busy = false;
62
+ }
63
+ };
64
+ return {
65
+ check,
66
+ stop: () => {
67
+ stopped = true;
68
+ },
69
+ };
70
+ }
71
+ const defaultSchedule = (fn, ms) => {
72
+ const t = setInterval(fn, ms);
73
+ t.unref?.();
74
+ return () => clearInterval(t);
75
+ };
76
+ export function pollingHeadSignal(store, opts = {}) {
77
+ const intervalMs = Math.max(10, Number.isFinite(opts.intervalMs) ? opts.intervalMs : 2000);
78
+ const schedule = opts.schedule ?? defaultSchedule;
79
+ return {
80
+ watch(experienceId, onChange) {
81
+ let cancel;
82
+ let ended = false;
83
+ const c = checker(store, experienceId, onChange, opts.onError, opts.confirmErased, () => {
84
+ ended = true;
85
+ cancel?.();
86
+ });
87
+ void c.check();
88
+ if (!ended)
89
+ cancel = schedule(() => void c.check(), intervalMs);
90
+ return () => {
91
+ c.stop();
92
+ cancel?.();
93
+ };
94
+ },
95
+ };
96
+ }
97
+ export function eventStoreHeadSignal(events, store, opts = {}) {
98
+ const fallback = Number.isFinite(opts.fallbackIntervalMs)
99
+ ? Math.max(10, opts.fallbackIntervalMs)
100
+ : undefined;
101
+ const schedule = opts.schedule ?? defaultSchedule;
102
+ const report = (e) => {
103
+ try {
104
+ opts.onError?.(e);
105
+ }
106
+ catch {
107
+ // never break the signal
108
+ }
109
+ };
110
+ return {
111
+ watch(experienceId, onChange) {
112
+ let cancel;
113
+ let off;
114
+ let stopped = false;
115
+ const c = checker(store, experienceId, onChange, opts.onError, opts.confirmErased, () => {
116
+ stopped = true;
117
+ cancel?.();
118
+ off?.();
119
+ });
120
+ events
121
+ .subscribe(`${LINEAGE_STREAM_PREFIX}${experienceId}`, () => void c.check())
122
+ .then((u) => {
123
+ if (stopped)
124
+ u();
125
+ else
126
+ off = u;
127
+ })
128
+ .catch(report);
129
+ void c.check();
130
+ if (!stopped && fallback !== undefined)
131
+ cancel = schedule(() => void c.check(), fallback);
132
+ return () => {
133
+ stopped = true;
134
+ c.stop();
135
+ cancel?.();
136
+ off?.();
137
+ };
138
+ },
139
+ };
140
+ }
141
+ //# sourceMappingURL=signal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signal.js","sourceRoot":"","sources":["../../src/signal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { tainted } from "@loomwork/classify";
2
+ // ── The summary as loom's untrusted value ─────────────────────────────────────
3
+ // A change summary is model text. `UntrustedText` marks it for DISPLAY; when a host feeds it to
4
+ // another model (a digest, a notification writer) it must cross as loom's ADR 0085 taint
5
+ // (`@loomwork/classify` `TaintedValue`), which `@loomwork/llm`'s `guardRequest` refuses in any instruction
6
+ // position and fences in a data position. This is a server-side helper: the taint package is a
7
+ // loom kernel-side dependency and must not enter a client bundle.
8
+ //
9
+ // NEVER put a summary in a prompt's instruction (system) position, tainted or not.
10
+ /** The summary as an ADR 0085 tainted value, origin `loom-ui:change-summary`. */
11
+ export function summaryAsTainted(summary) {
12
+ return tainted(summary.text, "loom-ui:change-summary");
13
+ }
14
+ //# sourceMappingURL=untrusted.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"untrusted.js","sourceRoot":"","sources":["../../src/untrusted.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGhE,iFAAiF;AACjF,gGAAgG;AAChG,yFAAyF;AACzF,2GAA2G;AAC3G,+FAA+F;AAC/F,kEAAkE;AAClE,EAAE;AACF,mFAAmF;AAEnF,iFAAiF;AACjF,MAAM,UAAU,gBAAgB,CAAC,OAAsB;IACrD,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,wBAAwB,CAAC,CAAC;AACzD,CAAC"}
package/dist/feed.d.ts ADDED
@@ -0,0 +1,125 @@
1
+ import { type ChangeDescription, type DeclaredToolClasses, type DescribeOptions, type Finding, type HeadSignal, type ProjectionCatalog, type ReadPolicy, type UndeclaredToolPolicy } from "@loomwork/ui-core";
2
+ import { type Lineage, type LineageStore } from "@loomwork/ui-lineage";
3
+ export type ChangeEventKind = "composed" | "reverted" | "branched" | "promoted";
4
+ export interface ChangeEvent {
5
+ /** Stable, unique per log position: `<experienceId>#<sequence>`. The idempotency key. */
6
+ readonly id: string;
7
+ readonly experienceId: string;
8
+ /** 1-based position in the line's log; also the cursor value that means "handled through here". */
9
+ readonly sequence: number;
10
+ readonly at: string;
11
+ readonly kind: ChangeEventKind;
12
+ /** The version this event is about (the new head, or the version now shown). */
13
+ readonly versionHash: string;
14
+ /** What it replaced: the parent (composed/reverted), the forked version (branched), the version shown before (promoted); null = nothing. */
15
+ readonly base: {
16
+ readonly experienceId: string;
17
+ readonly versionHash: string;
18
+ } | null;
19
+ }
20
+ export type ReadResult = {
21
+ readonly ok: true;
22
+ readonly events: readonly ChangeEvent[];
23
+ /** The sequence of the last event returned, or `after` when none was: pass it back as `after`. */
24
+ readonly cursor: number;
25
+ /** More events remain past `cursor` (the page was full). */
26
+ readonly more: boolean;
27
+ /** The log's tip at read time (0 = empty). */
28
+ readonly tip: number;
29
+ } | {
30
+ readonly ok: false;
31
+ readonly finding: Finding;
32
+ };
33
+ export type DescribeResult = {
34
+ readonly ok: true;
35
+ readonly description: ChangeDescription;
36
+ } | {
37
+ readonly ok: false;
38
+ readonly finding: Finding;
39
+ };
40
+ export interface RetryOptions {
41
+ /** First retry delay in ms (default 250). Doubles per consecutive failure. */
42
+ readonly baseMs?: number;
43
+ /** Delay ceiling in ms (default 30000). */
44
+ readonly maxMs?: number;
45
+ }
46
+ export interface SubscribeOptions {
47
+ /** Deliver events after this sequence (default 0 = from the start). */
48
+ readonly after?: number;
49
+ /** A failed read, or a handler that threw. Never throws itself. */
50
+ readonly onError?: (err: unknown) => void;
51
+ /** Backoff for re-trying a failed delivery or read with no new append. See `subscribe`. */
52
+ readonly retry?: RetryOptions;
53
+ /**
54
+ * Called after each failed delivery of the SAME event, with the count of consecutive failures.
55
+ * Return `true` to SKIP the event (the cursor moves past it, it is never redelivered to this
56
+ * subscription); anything else (or a throw) keeps retrying. Absent = retry forever with capped
57
+ * backoff: a poison event then stalls this subscription (and only it); a host that wants out
58
+ * resubscribes with `after: <poison sequence>`.
59
+ */
60
+ readonly onPoison?: (event: ChangeEvent, attempts: number) => boolean | Promise<boolean>;
61
+ }
62
+ export interface ChangeFeed {
63
+ read(experienceId: string, opts?: {
64
+ after?: number;
65
+ limit?: number;
66
+ }): Promise<ReadResult>;
67
+ /**
68
+ * Catch up from `after`, then follow the line, until the returned function is called. Events
69
+ * arrive in sequence order, one at a time (the next waits for the handler's promise). A handler
70
+ * that throws stops this pass at that event WITHOUT advancing: it is delivered again (at-least-
71
+ * once), and `onError` hears of it. The retry does NOT wait for a new append: while the cursor is
72
+ * behind the tip (a handler threw, or a read failed) a bounded exponential backoff timer
73
+ * (jittered, `unref`'d, cleared on unsubscribe) re-runs the pass, so a quiet line is never stuck.
74
+ * A permanently failing event is a poison event: see `SubscribeOptions.onPoison`. Resubscribing
75
+ * from an older cursor redelivers the same ids.
76
+ */
77
+ subscribe(experienceId: string, handler: (event: ChangeEvent) => void | Promise<void>, opts?: SubscribeOptions): () => void;
78
+ /** What `event` changed, for one viewer. See `describeChange`: restricted content is never named. */
79
+ describe(event: ChangeEvent, opts?: Omit<DescribeOptions, "ops" | "summary" | "summaryClass">): Promise<DescribeResult>;
80
+ }
81
+ export declare const DEFAULT_PAGE = 100;
82
+ export declare const MAX_PAGE = 1000;
83
+ export interface LineageChangeFeedOptions {
84
+ readonly lineage: Lineage;
85
+ /** The same `LineageStore` the `Lineage` writes to. */
86
+ readonly events: LineageStore;
87
+ /** What wakes a subscription; see `pollingHeadSignal` / `eventStoreHeadSignal`. */
88
+ readonly signal: HeadSignal;
89
+ /**
90
+ * The projections the host offers (the same `ProjectionCatalog` `compile` takes). ABSENT = none:
91
+ * every projection-bound node is hidden from every viewer in `describe`, and a change to one is
92
+ * a `restricted-changed`. An id the catalogue lacks, or a version pin that drifted, hides too.
93
+ */
94
+ readonly projections?: ProjectionCatalog;
95
+ /** The host's hydration projection id (reserved; see `DescribeOptions`). */
96
+ readonly hydrationProjectionId?: string;
97
+ /** The host's declared class per tool: a read-time floor (core `DeclaredToolClasses`). */
98
+ readonly declaredToolClasses?: DeclaredToolClasses;
99
+ /** What a tool the declared table does not list reads as (`"allow"` default, or `"deny"`). */
100
+ readonly undeclared?: UndeclaredToolPolicy;
101
+ /**
102
+ * The read policy bound ONCE (the same shape every reader takes). Joined, raise-only, with the
103
+ * `lineage`'s own bound policy, the individual options above and any per-call `describe` options.
104
+ */
105
+ readonly readPolicy?: ReadPolicy;
106
+ }
107
+ /**
108
+ * A `ChangeFeed` over a `Lineage`. `read` folds the whole line (validating it: a malformed log is a
109
+ * finding, never partial events) so it is O(log length) per call; a deployment with very long lines
110
+ * pages with `limit` but still pays the fold: cache above this seam, not inside it.
111
+ */
112
+ export declare class LineageChangeFeed implements ChangeFeed {
113
+ private readonly lineage;
114
+ private readonly events;
115
+ private readonly signal;
116
+ private readonly policy;
117
+ constructor(opts: LineageChangeFeedOptions);
118
+ read(experienceId: string, opts?: {
119
+ after?: number;
120
+ limit?: number;
121
+ }): Promise<ReadResult>;
122
+ subscribe(experienceId: string, handler: (event: ChangeEvent) => void | Promise<void>, opts?: SubscribeOptions): () => void;
123
+ describe(event: ChangeEvent, opts?: Omit<DescribeOptions, "ops" | "summary" | "summaryClass">): Promise<DescribeResult>;
124
+ }
125
+ //# sourceMappingURL=feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feed.d.ts","sourceRoot":"","sources":["../src/feed.ts"],"names":[],"mappings":"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"}