@zakkster/lite-stream 1.1.1 → 1.2.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/CHANGELOG.md +85 -3
- package/README.md +31 -3
- package/Stream.d.ts +13 -0
- package/Stream.js +5 -1
- package/llms.txt +8 -3
- package/package.json +10 -6
- package/ROADMAP.md +0 -711
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,88 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## 1.2.0 -- 2026-09-02
|
|
11
|
+
|
|
12
|
+
**The law's gate, able to fail.** A torture harness that proves the package's
|
|
13
|
+
own claims -- including the zero-steady-state-allocation sentence that has been
|
|
14
|
+
unverified since 1.0.0 -- plus the drift guards that keep docs, exports, and
|
|
15
|
+
ASCII honest, a `VERSION` export, and the LS-11 packaging cleanup. No behavior
|
|
16
|
+
change: the only `Stream.js` code change is the additive `VERSION` const.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Torture harness** -- `test/torture.mjs` + `test/torture/s0-s8`, run by
|
|
21
|
+
`npm run torture` (`node --expose-gc test/torture.mjs`): metamorphic laws
|
|
22
|
+
(s0), degenerate values and hostile iterators (s1), async-iteration protocol
|
|
23
|
+
conformance (s2), API abuse with a decided policy per case (s3), seeded
|
|
24
|
+
fuzz-vs-oracle (s4), the allocation gate (s5, `@zakkster/lite-gc-profiler`,
|
|
25
|
+
`maxMajor` 0 with per-scenario byte budgets), a retention soak (s6,
|
|
26
|
+
`@zakkster/lite-leak` WeakRef census), the StreamQuery conformance corpus and
|
|
27
|
+
TimeoutError duck-pact (s7), and the controls tier (s8). Prints `ok`, exits
|
|
28
|
+
0/1; `TORTURE_SEED` replays a failure; `STREAM_TORTURE_BREAK=<s0..s8|1>` arms
|
|
29
|
+
each tier's injected breakage so every tier's control exits non-zero.
|
|
30
|
+
- **Scripts** -- `torture`, `torture:control` (all-tiers break lane), and
|
|
31
|
+
`test:floor`. `verify` gains `torture`. **LS-09 amendment:** the floor lane
|
|
32
|
+
runs `npm test` ONLY against the pinned peer -- `@zakkster/lite-leak` peers
|
|
33
|
+
`@zakkster/lite-signal >= 1.5.0`, so pinning `1.2.2` alongside it would put
|
|
34
|
+
lite-leak outside its supported peer range; torture (which imports lite-leak)
|
|
35
|
+
runs against the resolved latest only. Fail closed: never run a tool outside
|
|
36
|
+
its peer range.
|
|
37
|
+
- **`VERSION` export** (LS-05) -- a string that always equals the installed
|
|
38
|
+
`package.json` version. Declared in `Stream.d.ts`; documented in llms.txt and
|
|
39
|
+
the README API reference with no embedded version literal. Never re-exported
|
|
40
|
+
by consumers (suite ruling). The `Stream.d.ts` `TimeoutError` type declaration
|
|
41
|
+
was also added (the shipped runtime export had no type; the surface-guard
|
|
42
|
+
requires three-way parity).
|
|
43
|
+
- **`test/09-guards.test.mjs`** (5 tests) -- ascii-guard (every `files[]` +
|
|
44
|
+
`test/`/`bench/`/`demo/` byte is printable ASCII or LF, empty allowlist) and
|
|
45
|
+
surface-guard (runtime exports == llms.txt import list == `Stream.d.ts` value
|
|
46
|
+
exports; `VERSION` string-equals `package.json` version; the declared peer is
|
|
47
|
+
present in both docs). Each family carries an inline failing control.
|
|
48
|
+
- **devDependencies** -- `@zakkster/lite-leak ^1.10.0` and
|
|
49
|
+
`@zakkster/lite-gc-profiler ^1.16.0` (torture only; not runtime deps).
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **`files[]` drops `ROADMAP.md`** (LS-11) -- the roadmap is a working doc, not
|
|
54
|
+
a shipped artifact. llms.txt's Files section and the CHANGELOG [1.1.0]
|
|
55
|
+
forward-reference were reworded so tarball readers are not pointed at an
|
|
56
|
+
unshipped file.
|
|
57
|
+
- **`test` / `test:gc` globs scoped to `test/*.test.mjs`** so the `node:test`
|
|
58
|
+
runner does not discover the torture files.
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- Docs and packaging only; no runtime behavior changed.
|
|
63
|
+
|
|
64
|
+
### Known issues
|
|
65
|
+
|
|
66
|
+
- **LS-13** (S1, opened this release, fix targeted for 1.3.0) -- a `next()`
|
|
67
|
+
result object whose `done` or `value` is a throwing getter escapes the pump's
|
|
68
|
+
`.then` onFulfilled uncaught: it surfaces as an unhandledRejection and
|
|
69
|
+
`fromAsyncIterable` never reaches a terminal state (it should transition to
|
|
70
|
+
the error state). Torture tier s1 reproduces it with a bounded sentinel and a
|
|
71
|
+
scoped rejection capture and prints it as `TODO(LS3, LS-13)` in the gate
|
|
72
|
+
output; it does not fail the gate. This refutes the "iterator throws -> error
|
|
73
|
+
state" triplet leg for the throwing-getter shape specifically.
|
|
74
|
+
|
|
75
|
+
### Testing
|
|
76
|
+
|
|
77
|
+
- **90 tests** across `test/01-*` through `test/09-*` (up from 85), 0 fail,
|
|
78
|
+
0 skip under `npm test` and `npm run test:gc`.
|
|
79
|
+
- `npm run torture` -> `ok`, ~1.8s wall (node v26.3.1, macOS arm64,
|
|
80
|
+
2026-09-02); zero steady-state majors in every s5 scenario.
|
|
81
|
+
- `npm run test:floor` -> FLOOR-PASS (`npm test` 90/0 against
|
|
82
|
+
`@zakkster/lite-signal@1.2.2`) and LATEST-PASS (`npm test` 90/0 + torture ok
|
|
83
|
+
against the resolved `@zakkster/lite-signal@1.5.0`). The `^1.2.0` peer floor
|
|
84
|
+
is now a tested fact, not a claim.
|
|
85
|
+
- s5 budget doctrine: per-scenario `maxBytesPerOp` budgets (32 B/op for the
|
|
86
|
+
caught-up toAsyncIterable and pipeToSignal steady pump; 16 B/op for the
|
|
87
|
+
by-design fromAsyncIterable state-object and waiter-heavy paths), each ~3x the
|
|
88
|
+
measured 3-pass peak; the global `maxMajor` rule never widens.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
10
92
|
## 1.1.1 -- 2026-09-01
|
|
11
93
|
|
|
12
94
|
**Truth patch. No API change, no behavior change; the `Stream.js` diff is
|
|
@@ -65,9 +147,9 @@ corrected, and the previously dark GC tier now gates on the default test run.
|
|
|
65
147
|
## 1.1.0 -- 2026-07-10
|
|
66
148
|
|
|
67
149
|
**Additive `toAsyncIterable` enrichment.** Six additions, no breaking
|
|
68
|
-
changes. All 1.0.0 callers continue to work without modification.
|
|
69
|
-
|
|
70
|
-
|
|
150
|
+
changes. All 1.0.0 callers continue to work without modification. Each
|
|
151
|
+
addition below records the triggering signal that justified it; the
|
|
152
|
+
`Added` and `Changed` entries in this section stand on their own.
|
|
71
153
|
|
|
72
154
|
### Added
|
|
73
155
|
|
package/README.md
CHANGED
|
@@ -249,6 +249,16 @@ Treat this as the secondary API for classic "consume signal changes"
|
|
|
249
249
|
patterns; for the streaming pipeline case (paginated APIs, SSE, pubsub)
|
|
250
250
|
the forward direction is what most consumers want.
|
|
251
251
|
|
|
252
|
+
### VERSION
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
VERSION: string
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
A string that always equals the installed `package.json` version. Consumers
|
|
259
|
+
do NOT re-export it (suite ruling); the surface-guard asserts the equality on
|
|
260
|
+
every test run.
|
|
261
|
+
|
|
252
262
|
## Modes: latest vs buffer
|
|
253
263
|
|
|
254
264
|
Pick `"latest"` when you only care about the most recent value:
|
|
@@ -486,7 +496,7 @@ target.
|
|
|
486
496
|
|
|
487
497
|
### Tier 1 -- behavior (unit tests, fast)
|
|
488
498
|
|
|
489
|
-
|
|
499
|
+
90 tests across `test/01-*` through `test/09-*`:
|
|
490
500
|
|
|
491
501
|
- `01-from-async-iterable-latest.test.mjs` -- state shape, lifecycle,
|
|
492
502
|
Iterable acceptance variants, pre-aborted, subscriber observability
|
|
@@ -506,6 +516,11 @@ target.
|
|
|
506
516
|
(LS-01: disposing a result/target signal does NOT stop the pump;
|
|
507
517
|
LS-04: a pending `next()` on a disposed source never settles, `timeout`
|
|
508
518
|
is the only escape hatch)
|
|
519
|
+
- `09-guards.test.mjs` -- drift guards: ascii-guard (every shipped +
|
|
520
|
+
test/bench/demo byte is printable ASCII or LF) and surface-guard (runtime
|
|
521
|
+
exports == llms.txt == Stream.d.ts, VERSION == package.json version, the
|
|
522
|
+
declared peer is present in both docs). Each family carries an inline
|
|
523
|
+
failing control proving the guard can fail.
|
|
509
524
|
|
|
510
525
|
Run via `npm test`.
|
|
511
526
|
|
|
@@ -525,10 +540,23 @@ retained as an alias.
|
|
|
525
540
|
`bench/bench.mjs` -- six scenarios; throughput and B/op retained. Run via
|
|
526
541
|
`npm run bench`.
|
|
527
542
|
|
|
543
|
+
### Tier 4 -- torture (retention + alloc gate + controls)
|
|
544
|
+
|
|
545
|
+
`npm run torture` (`node --expose-gc test/torture.mjs`) runs tiers s0-s8
|
|
546
|
+
sequentially -- metamorphic laws, degenerate values, protocol conformance,
|
|
547
|
+
API abuse, seeded fuzz-vs-oracle, the s5 per-scenario allocation gate
|
|
548
|
+
(maxMajor 0), a lite-leak retention soak, the StreamQuery conformance corpus,
|
|
549
|
+
and the controls tier. It prints `ok` and exits 0 on success.
|
|
550
|
+
`STREAM_TORTURE_BREAK=<s0..s8|1>` arms one tier's (or every tier's) injected
|
|
551
|
+
breakage so the control lane exits non-zero; `npm run torture:control` runs
|
|
552
|
+
the all-tiers form. `npm run test:floor` runs `npm test` against the peer
|
|
553
|
+
floor `@zakkster/lite-signal@1.2.2` and then `npm test` + torture against the
|
|
554
|
+
resolved latest, printing both verdicts.
|
|
555
|
+
|
|
528
556
|
No `package-lock.json` is committed: devDependencies float within their
|
|
529
557
|
stated ranges, and correctness against the peer floor is proven by running
|
|
530
|
-
|
|
531
|
-
lockfile.
|
|
558
|
+
`npm run test:floor` against `@zakkster/lite-signal@1.2.2` rather than by
|
|
559
|
+
pinning a lockfile.
|
|
532
560
|
|
|
533
561
|
## What this is not
|
|
534
562
|
|
package/Stream.d.ts
CHANGED
|
@@ -135,3 +135,16 @@ export function toAsyncIterable<T>(
|
|
|
135
135
|
sig: Signal<T> | Computed<T>,
|
|
136
136
|
opts?: ToAsyncIterableOptions
|
|
137
137
|
): AsyncIterable<T> & { readonly droppedCount: number };
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Thrown when the `timeout` option on `toAsyncIterable` elapses. Structurally
|
|
141
|
+
* identical to `@zakkster/lite-await`'s TimeoutError (`e.name === "TimeoutError"`
|
|
142
|
+
* duck-checks work across both packages); not imported -- lite-stream stays zero-dep.
|
|
143
|
+
*/
|
|
144
|
+
export declare class TimeoutError extends Error {
|
|
145
|
+
readonly name: "TimeoutError";
|
|
146
|
+
readonly timeout: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Package version string; always equals the installed package.json version. */
|
|
150
|
+
export declare const VERSION: string;
|
package/Stream.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @zakkster/lite-stream 1.
|
|
1
|
+
// @zakkster/lite-stream 1.2.0
|
|
2
2
|
//
|
|
3
3
|
// Zero-GC bridge between async iterators and @zakkster/lite-signal. The
|
|
4
4
|
// multi-shot dual of lite-await's fromPromise: project an async source of N
|
|
@@ -873,3 +873,7 @@ export {
|
|
|
873
873
|
toAsyncIterable,
|
|
874
874
|
TimeoutError
|
|
875
875
|
};
|
|
876
|
+
|
|
877
|
+
// Always equals the installed package.json version (single source of truth is
|
|
878
|
+
// package.json; the /release drill bumps both sites in the same commit).
|
|
879
|
+
export const VERSION = "1.2.0";
|
package/llms.txt
CHANGED
|
@@ -14,7 +14,7 @@ Peer dep: `@zakkster/lite-signal ^1.2.0`.
|
|
|
14
14
|
|
|
15
15
|
## Imports
|
|
16
16
|
|
|
17
|
-
import { fromAsyncIterable, pipeToSignal, toAsyncIterable, TimeoutError } from "@zakkster/lite-stream";
|
|
17
|
+
import { fromAsyncIterable, pipeToSignal, toAsyncIterable, TimeoutError, VERSION } from "@zakkster/lite-stream";
|
|
18
18
|
|
|
19
19
|
## Exports
|
|
20
20
|
|
|
@@ -120,6 +120,12 @@ Structurally identical to `@zakkster/lite-await`'s TimeoutError so
|
|
|
120
120
|
`e.name === "TimeoutError"` duck-checks work across both packages. Not
|
|
121
121
|
imported from lite-await -- lite-stream stays zero-dep.
|
|
122
122
|
|
|
123
|
+
### `VERSION`
|
|
124
|
+
|
|
125
|
+
String, always equals the installed package.json version. Not re-exported
|
|
126
|
+
by consumers (suite ruling); the surface-guard asserts the equality on
|
|
127
|
+
every test run.
|
|
128
|
+
|
|
123
129
|
## Cleanup termination triplet
|
|
124
130
|
|
|
125
131
|
`fromAsyncIterable` ends on exactly three paths, all structural:
|
|
@@ -278,7 +284,7 @@ Per-yield, `fromAsyncIterable` latest mode allocates one wrapper state
|
|
|
278
284
|
object; buffer mode adds one snapshot array. `toAsyncIterable` `mode:
|
|
279
285
|
"latest"` allocates zero on the steady-state hot path when the consumer
|
|
280
286
|
is caught up; `mode: "buffer"` also zero when no waiter is pending
|
|
281
|
-
(
|
|
287
|
+
(gated: test/torture.mjs s5, per-scenario budgets).
|
|
282
288
|
|
|
283
289
|
## Files
|
|
284
290
|
|
|
@@ -287,7 +293,6 @@ is caught up; `mode: "buffer"` also zero when no waiter is pending
|
|
|
287
293
|
- `README.md` -- full docs with integration recipes
|
|
288
294
|
- `llms.txt` -- this file
|
|
289
295
|
- `CHANGELOG.md` -- version history
|
|
290
|
-
- `ROADMAP.md` -- deferred features
|
|
291
296
|
|
|
292
297
|
## Author
|
|
293
298
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zakkster/lite-stream",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Zero-GC bridge between async iterators and @zakkster/lite-signal. Project async streams (paginated APIs, SSE, network frames, pubsub topics) into signals. Bounded buffering with explicit overflow diagnostics, structural cleanup on three termination paths (iterator done, iterator throws, AbortSignal abort). The multi-shot dual of lite-await's fromPromise.",
|
|
5
5
|
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,14 +22,16 @@
|
|
|
22
22
|
"README.md",
|
|
23
23
|
"llms.txt",
|
|
24
24
|
"LICENSE.txt",
|
|
25
|
-
"CHANGELOG.md"
|
|
26
|
-
"ROADMAP.md"
|
|
25
|
+
"CHANGELOG.md"
|
|
27
26
|
],
|
|
28
27
|
"scripts": {
|
|
29
|
-
"test": "node --expose-gc --test --test-reporter=spec",
|
|
30
|
-
"test:gc": "node --expose-gc --test --test-reporter=spec",
|
|
28
|
+
"test": "node --expose-gc --test --test-reporter=spec test/*.test.mjs",
|
|
29
|
+
"test:gc": "node --expose-gc --test --test-reporter=spec test/*.test.mjs",
|
|
31
30
|
"bench": "node --expose-gc bench/bench.mjs",
|
|
32
|
-
"
|
|
31
|
+
"torture": "node --expose-gc test/torture.mjs",
|
|
32
|
+
"torture:control": "for t in s0 s1 s2 s3 s4 s5 s6 s7 s8; do if STREAM_TORTURE_BREAK=$t node --expose-gc test/torture.mjs >/dev/null 2>&1; then echo \"control $t FAILED-TO-FAIL\"; exit 1; else echo \"control $t failed-as-expected\"; fi; done",
|
|
33
|
+
"test:floor": "echo '== floor lane: pin @zakkster/lite-signal@1.2.2 (torture NOT run here -- lite-leak peers lite-signal>=1.5.0, LS-09 amendment) ==' && npm i --no-save --no-package-lock @zakkster/lite-signal@1.2.2 --legacy-peer-deps && npm test && echo 'FLOOR-PASS: npm test green @ lite-signal@1.2.2' && echo '== latest lane: restore @zakkster/lite-signal@^1.2.0 ==' && npm i --no-save --no-package-lock @zakkster/lite-signal@^1.2.0 && npm test && npm run torture && echo 'LATEST-PASS: npm test + torture green @ lite-signal latest'",
|
|
34
|
+
"verify": "npm test && npm run test:gc && npm run bench && npm run torture"
|
|
33
35
|
},
|
|
34
36
|
"keywords": [
|
|
35
37
|
"stream",
|
|
@@ -66,6 +68,8 @@
|
|
|
66
68
|
"@zakkster/lite-signal": "^1.2.0"
|
|
67
69
|
},
|
|
68
70
|
"devDependencies": {
|
|
71
|
+
"@zakkster/lite-gc-profiler": "^1.16.0",
|
|
72
|
+
"@zakkster/lite-leak": "^1.10.0",
|
|
69
73
|
"@zakkster/lite-signal": "^1.2.2"
|
|
70
74
|
}
|
|
71
75
|
}
|
package/ROADMAP.md
DELETED
|
@@ -1,711 +0,0 @@
|
|
|
1
|
-
# lite-stream -- enriched roadmap (post-1.1.0)
|
|
2
|
-
|
|
3
|
-
Blueprint: `../BLUEPRINT_ROADMAP.md`. Sibling roadmaps this one is wired to:
|
|
4
|
-
`../LiteQuery/ROADMAP.md` (the flagship consumer), `../LiteBakeStream/ROADMAP.md`
|
|
5
|
-
(the torture-spec model), `../LiteAwait/AWAIT_ROADMAP.md` (the TimeoutError
|
|
6
|
-
duck-pact). Ethos carried over unchanged: **reproduce by running, never infer.**
|
|
7
|
-
Every finding below was produced by executing code on 2026-09-01, not by
|
|
8
|
-
reading it. Reproductions live in `bench/findings-probes-2026-09-01.mjs`.
|
|
9
|
-
|
|
10
|
-
Sessions: LS1 (v1.1.1, truth patch) -> LS2 (v1.2.0, the law's gate) ->
|
|
11
|
-
LS3 (v1.3.0, the consumer contract) -> LS4 (v1.4.0, Q8 enablement, GATED).
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 0. Ground truth (verified 2026-09-01)
|
|
16
|
-
|
|
17
|
-
- Registry: `@zakkster/lite-stream` 1.0.0 and 1.1.0 published; `latest` =
|
|
18
|
-
1.1.0. Local HEAD `e9d2151 1.1.0;` IS the published state; tree clean.
|
|
19
|
-
- Surface: 4 named exports -- `fromAsyncIterable`, `pipeToSignal`,
|
|
20
|
-
`toAsyncIterable`, `TimeoutError`. NO `VERSION` export (LS-05). `Stream.d.ts`
|
|
21
|
-
and `llms.txt` agree with the export block (hand-checked; guard pending LS2).
|
|
22
|
-
- Tests: 79. Default `npm test` -> 71 pass, **8 skipped silently** (the entire
|
|
23
|
-
GC tier, LS-02). `npm run test:gc` -> 79 pass, 0 skip, 0 fail -- against
|
|
24
|
-
lite-signal **1.5.0** AND against the floor **1.2.2** (both run today; the
|
|
25
|
-
`^1.2.0` peer claim is TRUE, previously untested -- LS-09).
|
|
26
|
-
- Torture: none. No `test/torture.mjs`, no `@zakkster/lite-leak`, no
|
|
27
|
-
`@zakkster/lite-gc-profiler`, no seeded fuzz, no controls. The 8 GC tests
|
|
28
|
-
use coarse "retain < 2 MB" ceilings, not the law's `maxMajor: 0` gate (LS-03).
|
|
29
|
-
- Bench (node v26.3.1, this machine, 2026-09-01): from-latest 61,464 ops/s
|
|
30
|
-
(0.35 B/op retained), from-buffer 63,382 (0.46), abort-cycle 29,652 (1.49),
|
|
31
|
-
pipe-to-signal 42,382 (0.26), to-async-iterable 200,956 (negative retained),
|
|
32
|
-
overflow 204,774 (0.13). Shipped llms.txt claims different numbers with no
|
|
33
|
-
provenance on the 1.0 block (LS-08).
|
|
34
|
-
- Consumers in the suite: exactly one. `../LiteQuery/StreamQuery.js:36`
|
|
35
|
-
imports `pipeToSignal` (peer `^1.0.0`, optional). lite-query also vendors
|
|
36
|
-
a 1.0.0 copy for demos (`demo/vendor/lite-stream.js`).
|
|
37
|
-
- ASCII: **zero** non-ASCII bytes across Stream.js, Stream.d.ts, all docs,
|
|
38
|
-
test/, bench/, demo/. Already clean; LS2's guard keeps it that way.
|
|
39
|
-
- Hygiene: no `.gitignore` (added at roadmap-pass time: `node_modules/`,
|
|
40
|
-
`package-lock.json` -- same operator ruling as lite-query Q1), no lockfile
|
|
41
|
-
(deliberate, now recorded). `files[]` ships ROADMAP.md (LS-11).
|
|
42
|
-
- Runtime facts: engines `>=18`; `Symbol.asyncDispose` feature-detected
|
|
43
|
-
(present on node 26, its conditional test runs). devDep `^1.2.2` resolves
|
|
44
|
-
to lite-signal 1.5.0 at install time.
|
|
45
|
-
|
|
46
|
-
## 1. Shared law (holds in every session)
|
|
47
|
-
|
|
48
|
-
1. Zero runtime deps; `@zakkster/lite-signal` is the single peer. node:test
|
|
49
|
-
only. ASCII-only source and docs. Single main file `Stream.js`.
|
|
50
|
-
2. Bounded buffering is this package's own law: "unbounded buffering is a
|
|
51
|
-
memory bug pretending to be a feature." Every buffer has a required,
|
|
52
|
-
validated ceiling; every drop is counted and observable.
|
|
53
|
-
3. Fail closed on every unverified state. The LS-01 lesson is the local
|
|
54
|
-
phrasing: a cleanup path that depends on a peer behavior nobody ever
|
|
55
|
-
executed is not a cleanup path, it is a comment.
|
|
56
|
-
4. **AbortSignal is the only cleanup contract.** Dispose-based teardown is
|
|
57
|
-
not promised anywhere until a lite-signal disposed-oracle exists (parked).
|
|
58
|
-
Docs, tests, and torture pin this in every direction.
|
|
59
|
-
5. No silent skips (the Q-02/AW-14/LS-02 class): the default `npm test` runs
|
|
60
|
-
every test the repo owns. Environment-conditional skips (asyncDispose on
|
|
61
|
-
node < 20) must name their condition in the skip message and have a
|
|
62
|
-
counterpart that runs.
|
|
63
|
-
6. Every module change is proven by `node --expose-gc test/torture.mjs`
|
|
64
|
-
(lite-leak + lite-gc-profiler) once LS2 lands. No gate output is a FAIL.
|
|
65
|
-
A gate that cannot fail is decorative -- controls are part of the gate.
|
|
66
|
-
7. Docs parity is release-blocking: exports == llms.txt == Stream.d.ts ==
|
|
67
|
-
README API section; peer floors in docs == package.json; perf numbers
|
|
68
|
-
carry provenance (node version, date, machine class) or do not ship.
|
|
69
|
-
8. Measured numbers are measured once, at the source of truth, and cited
|
|
70
|
-
everywhere else (the bake-stream MQ2 discipline).
|
|
71
|
-
|
|
72
|
-
## 2. Verified findings
|
|
73
|
-
|
|
74
|
-
Severity: S1 = a shipped claim is false / correctness; S2 = a gate or
|
|
75
|
-
guarantee is dark; S3 = drift, hygiene, or docs debt. Every row was
|
|
76
|
-
reproduced by running on 2026-09-01; probe bodies live in
|
|
77
|
-
`bench/findings-probes-2026-09-01.mjs` (P0..P4).
|
|
78
|
-
|
|
79
|
-
| ID | Sev | Finding | Reproduce |
|
|
80
|
-
| --- | --- | --- | --- |
|
|
81
|
-
| **LS-01** | S1 | **The shipped dispose-teardown claim is false, and it has never been true.** llms.txt (lines 132-134): "If the consumer disposes the result signal ... the pump detects on next `sig.set()` and tears down silently." Reality: lite-signal's `set()` after `dispose()` is a **silent no-op** in 1.5.0 AND 1.2.2 (P0), so the pump's catch-based detection (`Stream.js:343`) is dead code born dead -- it never fired against any published peer in the range. Observed: dispose mid-pump leaves the iterator **pulling forever** (P1: 14 pulls at dispose, 64 and climbing 60ms later, `return()` never called); same for `pipeToSignal` with a disposed target (P2, `onError` never fires). For an infinite source (SSE -- the package's own headline use case) this is an unbounded background fetch loop. The `Stream.js:148-152` docstring even offers dispose as a stop mechanism ("the iterator continues until natural completion" -- an infinite source has none). Sole suite consumer verified SAFE: StreamQuery always `ac.abort()`s via `streamStop` before entry teardown (`StreamQuery.js:156`). | `node bench/findings-probes-2026-09-01.mjs` (P0/P1/P2) |
|
|
82
|
-
| **LS-02** | S2 | **Default `npm test` silently skips the entire GC tier.** All 8 tests in `test/06-gc.test.mjs` are `{ skip: !hasGc }` and the `test` script lacks `--expose-gc`; 71/79 green LOOKS complete. The `verify` script runs `test:gc`, but the habitual gate is `npm test`. Third suite recurrence of the green-light-over-a-hole class (Q-02, AW-14). Fix pre-verified: the `test:gc` form runs 79/79 green in 21.5s. | `npm test 2>&1 \| grep skipped` -> 8; `npm run test:gc` -> 0 |
|
|
83
|
-
| **LS-03** | S2 | **The pipeline law's torture gate does not exist.** No `test/torture.mjs`, no lite-leak, no lite-gc-profiler, no seeded fuzz, no zero-alloc gate (llms.txt claims "allocates zero on the steady-state hot path" -- nothing gates it), no controls. The GC tests' "retain < 2 MB" ceilings would pass a 1.9 MB leak. | `ls test/torture.mjs` -> ENOENT; `grep -c lite-leak package.json` -> 0 |
|
|
84
|
-
| **LS-04** | S3 | **Disposing the SOURCE under `toAsyncIterable` hangs the pending `next()` forever** (P3: still PENDING 50ms after dispose; no resolve, no reject). llms.txt already anti-patterns this as "undefined behavior", so it is documented-away rather than false -- but the observed behavior is now on record, and `timeout` is the only escape hatch. Hardening parked on a lite-signal disposed-oracle (see parking ledger). | probes P3 |
|
|
85
|
-
| **LS-05** | S3 | **No `VERSION` const or export.** Suite convention (lite-query, lite-await) and the /release drill's site list expect one in the main file. Current version sites: `package.json`, `Stream.js:1` header comment, CHANGELOG headings. Additive export -> lands with LS2 (1.2.0), never re-exported by consumers (suite ruling, lite-await pass). | `grep -c VERSION Stream.js` -> 0 |
|
|
86
|
-
| **LS-06** | S3 | **Shipped source references a document that does not exist.** `Stream.js:25` and `:560` cite `CONTRACT-toAsyncIterable-1.1.md`; no such file in the repo or the tarball. The TimeoutError comment (`Stream.js:44-45`) still narrates its own dev sessions ("Session B's territory ... Session A"). | `ls CONTRACT-toAsyncIterable-1.1.md` -> ENOENT |
|
|
87
|
-
| **LS-07** | S3 | **The pump comment describes code that is not there.** `Stream.js:352-353`: "Defer the next pull through the microtask queue so back-to-back synchronous yields don't blow the stack" -- `pump()` is called directly; no deferral exists at that site. Behavior is stack-safe anyway (`.then` handlers unwind): P4 drains a 1,000,000-value sync source in ~80ms with no overflow. Fix is the comment, plus a torture case pinning the law. | probes P4 |
|
|
88
|
-
| **LS-08** | S3 | **Shipped perf numbers lack provenance and do not match a current run.** llms.txt's 1.0 block ("178K ops/s ... 235K ... 84K") names no node version, date, or machine; today's run on node v26.3.1 gives 61K/42K/29K for the same scenarios. Not a lie -- a rotting number. Re-measure once with provenance at LS1, cite everywhere (README Benchmarks included). | `npm run bench` vs llms.txt Performance section |
|
|
89
|
-
| **LS-09** | S3 | **The peer floor `^1.2.0` was a claim, not a fact -- now verified, must stay verified.** Tests had only ever run against the latest resolution (1.5.0). Run today against pinned 1.2.2: 79/79 green. The floor holds; nothing keeps it holding. LS2's floor matrix (scripted `test:floor`) institutionalizes the check. | `npm i --no-save --no-package-lock @zakkster/lite-signal@1.2.2 && npm run test:gc` |
|
|
90
|
-
| **LS-10** | S3 | **No repo hygiene layer.** No `.gitignore` -- a plain `npm install` leaves `node_modules/` (and would leave `package-lock.json`) as untracked noise poised to be committed; no recorded lockfile policy. Mitigated at roadmap-pass time (2-line `.gitignore`, same ruling as lite-query Q1); policy recorded here: no lockfile, devDeps float within their stated ranges, the floor matrix pins what must stay true. | `git status` after `npm install` (pre-fix) |
|
|
91
|
-
| **LS-11** | S3 | **ROADMAP.md ships in the tarball, and two shipped docs point into its old shape.** `files[]` includes ROADMAP.md; llms.txt's Files section describes it as "deferred features"; CHANGELOG 1.1.0 cites "ROADMAP.md's 'Shipped in 1.1.0' section" (preserved below under History so the published 1.1.0 pointer stays honest). Ruling to execute at LS2: drop ROADMAP.md from `files[]` (align with lite-query/bake-stream -- the roadmap is a working doc), fix the llms.txt Files line and CHANGELOG forward-refs in the same release. | `npm pack --dry-run \| grep ROADMAP` |
|
|
92
|
-
| **LS-12** | S3 | **The two directions disagree about what abort means, and the flagship consumer pays for it.** `fromAsyncIterable`/`pipeToSignal` route abort through `onError` (error framing, `makeAbortError`); `toAsyncIterable` treats abort as graceful done. StreamQuery hand-filters the noise: "pipeToSignal funnels intentional aborts through onError too. Those are not failures" (`StreamQuery.js:139-142`). Each direction is documented locally; the asymmetry as a whole is undocumented and exports a workaround obligation to every consumer. LS3 owns the decision doc (pin the rationale or add an `onAbort` hook -- see the brief). | read `StreamQuery.js:139-142`; compare `Stream.js:261` vs `:815` |
|
|
93
|
-
|
|
94
|
-
### 2b. Cross-package facts (verified against the neighbors' code, not their docs)
|
|
95
|
-
|
|
96
|
-
- **lite-signal 1.5.0 has no public disposed-oracle.** Exports: signal,
|
|
97
|
-
computed, signalBox, computedBox, effect, dispose, batch, createRegistry,
|
|
98
|
-
setDefaultRegistry, CapacityError, getOwner, runWithOwner. Nothing answers
|
|
99
|
-
"is this signal disposed?" -- so LS-01's behavioral fix (pump detects
|
|
100
|
-
dispose) is IMPOSSIBLE in-library today. Tripwire: if lite-signal ships an
|
|
101
|
-
oracle, un-park "dispose-aware pumps" (parking ledger).
|
|
102
|
-
- **StreamQuery's exact usage shape** (the contract LS2's conformance tier
|
|
103
|
-
freezes): `pipeToSignal(source, entry.data, { signal, transform, onError,
|
|
104
|
-
onDone })` where `transform` doubles as a first-value status tap and a
|
|
105
|
-
hand-rolled buffer window (`ring.push/shift/slice`, `StreamQuery.js:119-137`),
|
|
106
|
-
and `onError` filters `signal.aborted`. Three shipped triggering signals for
|
|
107
|
-
LS3, cited by line.
|
|
108
|
-
- **TimeoutError duck-pact.** Three packages now carry the shape
|
|
109
|
-
(`e.name === "TimeoutError"`, `.timeout` field): lite-await (origin),
|
|
110
|
-
lite-stream (structural copy, deliberate zero-dep), lite-query's /await
|
|
111
|
-
entry (re-export of lite-await's). The duck-check IS suite vocabulary;
|
|
112
|
-
renaming it anywhere is a breaking change everywhere. Pinned by a shape
|
|
113
|
-
test in LS2's conformance tier.
|
|
114
|
-
- **lite-bake-stream is bytes-domain** (`ReadableStream<Uint8Array>` in,
|
|
115
|
-
LBK1 containers out) -- no structural integration with value-domain
|
|
116
|
-
lite-stream. One honest recipe candidate, docs-only: RangeReader shard
|
|
117
|
-
walk -> async generator -> `fromAsyncIterable` for a UI-reactive lazy
|
|
118
|
-
view (parking ledger; needs bake-stream >= MQ1 for abortability).
|
|
119
|
-
- **lite-query Q8 (2.0.0, shared streams)** is the design event LS4 waits
|
|
120
|
-
for: leader owns one iterator, followers receive frames over lite-channel
|
|
121
|
-
and need "push externally-delivered values into entry signals with the
|
|
122
|
-
same windowed-view discipline", plus "leader hung (frames stop, channel
|
|
123
|
-
alive)" stall detection. Whether those primitives land in lite-stream 1.4.0
|
|
124
|
-
or inside lite-query is Q8's session-start spike ruling. Their spike is
|
|
125
|
-
our tripwire; recorded on both sides.
|
|
126
|
-
|
|
127
|
-
## 3. The torture suite (`test/torture.mjs`) -- spec
|
|
128
|
-
|
|
129
|
-
Model: `../LiteBakeStream/ROADMAP.md` section 3 (itself ported from
|
|
130
|
-
`../LiteBvh/test/torture/`), tiers renamed to this package's threat model:
|
|
131
|
-
lifecycle leaks, protocol violations, scheduling races, and false zero-GC
|
|
132
|
-
claims. Gate: `node --expose-gc test/torture.mjs` prints exactly "ok",
|
|
133
|
-
exit 0/1. Control switch: `STREAM_TORTURE_BREAK=1` must exit non-zero.
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
test/
|
|
137
|
-
torture.mjs # entry: tiers in order, prints "ok", exit 0/1
|
|
138
|
-
torture/
|
|
139
|
-
harness.mjs # xorshift32 PRNG (TORTURE_SEED replay), check() with
|
|
140
|
-
# message thunks, runOpsGate (stabilize:'deep'),
|
|
141
|
-
# STREAM_TORTURE_BREAK switch, preflight: exit 2 with
|
|
142
|
-
# a clear message when devDeps are missing
|
|
143
|
-
s0-laws.mjs # metamorphic invariants
|
|
144
|
-
s1-degenerate.mjs # nasty values and nasty iterators
|
|
145
|
-
s2-protocol.mjs # async-iteration protocol conformance
|
|
146
|
-
s3-abuse.mjs # API misuse: every case has a decided policy
|
|
147
|
-
s4-fuzz.mjs # seeded schedule fuzz vs a naive oracle
|
|
148
|
-
s5-alloc.mjs # the zero/bounded-alloc gate (gc-profiler)
|
|
149
|
-
s6-soak.mjs # lite-leak retention + lifecycle churn
|
|
150
|
-
s7-conformance.mjs # consumer corpus + floor matrix + duck-pact
|
|
151
|
-
s8-controls.mjs # every tier, deliberately broken, exits non-zero
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Harness rules (verbatim from the blueprint; all apply):
|
|
155
|
-
- All scratch allocated once, outside every loop; assertion messages built
|
|
156
|
-
only on failure.
|
|
157
|
-
- Seeded xorshift32; any failure prints seed + op index, replayable via
|
|
158
|
-
`TORTURE_SEED=... node --expose-gc test/torture.mjs`.
|
|
159
|
-
- lite-gc-profiler is one-measurement-at-a-time; tiers run sequentially;
|
|
160
|
-
read `../lite-gc-profiler/llms.txt` before wiring -- rule names from
|
|
161
|
-
memory are how gates rot. Unknown rule keys throw as of 1.10.0; floor the
|
|
162
|
-
devDep there.
|
|
163
|
-
- lite-leak held-value contract: neither `cleanup` nor `tag` closes over the
|
|
164
|
-
tracked target. Never resolve an unexpected `inconclusive` with
|
|
165
|
-
`allowInconclusive`.
|
|
166
|
-
- `test/` never enters `files[]`; `npm pack --dry-run` proves it.
|
|
167
|
-
|
|
168
|
-
### s0 -- metamorphic laws
|
|
169
|
-
|
|
170
|
-
For any seeded op sequence: `state.count` equals values yielded (both modes);
|
|
171
|
-
buffer `values.length <= maxBuffer` always; `droppedCount` equals exactly
|
|
172
|
-
`max(0, yields - maxBuffer)` when no consumer drains; latest-mode terminal
|
|
173
|
-
`value` equals the last yield; toAsyncIterable delivery order equals source
|
|
174
|
-
order (filtered) in buffer mode; **timing invariance**: the same logical
|
|
175
|
-
value sequence delivered via sync iterables, microtask-resolved promises,
|
|
176
|
-
and macrotask-resolved promises produces the same terminal state (count,
|
|
177
|
-
last value/values, done, error class) -- the scheduling analog of
|
|
178
|
-
bake-stream's re-chunk invariance. Terminal states are frozen: after
|
|
179
|
-
done/error, no further mutation ever (already unit-pinned; fuzz-scale it).
|
|
180
|
-
|
|
181
|
-
### s1 -- degenerate values and hostile iterators
|
|
182
|
-
|
|
183
|
-
Values: `undefined` yields (state.count is the oracle for "did a value
|
|
184
|
-
arrive", value alone is ambiguous -- pin it), null, NaN, -0, frozen objects,
|
|
185
|
-
functions, symbols. Sources: 1M-value sync iterable (stack law, P4 baseline
|
|
186
|
-
~80ms), empty iterable (immediate done), iterator with `.next` but no
|
|
187
|
-
`Symbol.asyncIterator`, thenable-returning next(), next() returning
|
|
188
|
-
non-object (pinned TypeError), result objects with throwing getters
|
|
189
|
-
(`{get done() {throw}}`), `return()` that throws (swallowed -- pin),
|
|
190
|
-
`return()` that never settles, an iterator that resolves a pull AFTER
|
|
191
|
-
stop/abort landed (the `if (stopped)` guards, fuzz-reachable). Aborts:
|
|
192
|
-
pre-aborted (pinned), abort inside the first `queueMicrotask` window before
|
|
193
|
-
pull one, abort reentrantly from inside `onError`/`onDone`, abort with a
|
|
194
|
-
custom `reason` (pinned), double abort.
|
|
195
|
-
|
|
196
|
-
### s2 -- protocol conformance (toAsyncIterable)
|
|
197
|
-
|
|
198
|
-
next() after done; `return(value)` passthrough vs pending waiters resolving
|
|
199
|
-
`{value: undefined, done: true}` (the documented split); `throw()` rejecting
|
|
200
|
-
both waiters and its own call site; multi-waiter FIFO under fuzz (N waiters
|
|
201
|
-
x M values, orders compared to a queue oracle); `Promise.all` batch pulls;
|
|
202
|
-
`for await` + break; `await using` on node >= 20; the emitInitial x filter x
|
|
203
|
-
initial-value matrix (8 cells, each named); filter throwing on the initial
|
|
204
|
-
synchronous fire (the late-binding unsubscribe path, `Stream.js:656-663`).
|
|
205
|
-
|
|
206
|
-
### s3 -- API abuse (throw / documented no-op / documented undefined -- pick one per case)
|
|
207
|
-
|
|
208
|
-
Mode typos ("Latest", "newest", 5); maxBuffer 0 / -1 / 1.5 / Infinity / NaN /
|
|
209
|
-
"5"; `mode: "latest"` + maxBuffer (pinned TypeError); timeout 0 (DECIDE and
|
|
210
|
-
pin: fires on the first turn -> first pending next() rejects TimeoutError),
|
|
211
|
-
timeout Infinity (non-finite -> RangeError, pin), timeout -1; filter
|
|
212
|
-
non-function; non-signal source/target (missing peek, missing subscribe,
|
|
213
|
-
missing set, frozen impostor objects); `stop()` twice (pinned) and `stop()`
|
|
214
|
-
racing a pending pull; dispose-then-X: every LS-01/LS-04 case lands here as
|
|
215
|
-
a NAMED test pinning the documented behavior ("dispose does not stop the
|
|
216
|
-
pump; abort does" / "dispose under a consuming iterable hangs -- documented
|
|
217
|
-
anti-pattern, timeout is the escape").
|
|
218
|
-
|
|
219
|
-
### s4 -- seeded schedule fuzz vs a naive oracle
|
|
220
|
-
|
|
221
|
-
A seeded generator emits op schedules over both directions: producer-set /
|
|
222
|
-
consumer-next / filter-toggle-outcome / abort / break / return / timeout-arm,
|
|
223
|
-
with micro/macro-task jitter. Oracle: a deliberately naive reference model
|
|
224
|
-
(unbounded array + hand bookkeeping of drops/overflow per the documented
|
|
225
|
-
semantics). Compare delivered sequences, terminal states, and counter values
|
|
226
|
-
cell-for-cell. Divergence prints seed + op index + a minimal replay line.
|
|
227
|
-
The multi-waiter FIFO and the ring head/wrap arithmetic are the two
|
|
228
|
-
mechanisms this tier exists to break.
|
|
229
|
-
|
|
230
|
-
### s5 -- the alloc gate (the llms.txt claims, gated at last)
|
|
231
|
-
|
|
232
|
-
`runOpsGate(fn, { ops })` with `RULES = { maxMajor: 0, maxPauseMs: 4 }`,
|
|
233
|
-
stabilize `'deep'`. Scenarios and their DOCUMENTED budgets (the M0 doctrine:
|
|
234
|
-
per-scenario budgets, never a widened global rule):
|
|
235
|
-
- toAsyncIterable `mode:"latest"`, consumer caught up: llms.txt claims ZERO
|
|
236
|
-
steady-state allocation -- gate exactly that (plus the per-next Promise
|
|
237
|
-
cost intrinsic to the protocol, measured and documented once).
|
|
238
|
-
- toAsyncIterable buffer, no waiter pending: same claim, same gate.
|
|
239
|
-
- pipeToSignal steady pump: budget from today's 0.26 B/op retained baseline.
|
|
240
|
-
- fromAsyncIterable latest: one state object per yield BY DESIGN -- assert
|
|
241
|
-
the documented envelope (today 0.35 B/op retained steady-state), assert
|
|
242
|
-
nothing HIDDEN grows (retained slope flat).
|
|
243
|
-
- fromAsyncIterable buffer: state object + snapshot array per yield BY
|
|
244
|
-
DESIGN -- envelope pinned per maxBuffer tier (8 / 64 / 1024).
|
|
245
|
-
- waiter-heavy path (consumer faster than producer): waiter objects are
|
|
246
|
-
intrinsic; retained slope must be flat.
|
|
247
|
-
|
|
248
|
-
### s6 -- soak and retention (lite-leak enters here)
|
|
249
|
-
|
|
250
|
-
4096 cycles per surface of: construct -> pump N values -> terminate (each of
|
|
251
|
-
the three paths in rotation) -> drop refs; tracker with the standard kernels;
|
|
252
|
-
`tracker.size()` returns to 0. Abort-listener accumulation on a SHARED
|
|
253
|
-
AbortSignal across 4096 streams (the removeEventListener discipline at
|
|
254
|
-
scale). Timer discipline: 4096 timeout-armed iterables that settle before
|
|
255
|
-
the deadline -- `clearTimeout` called every time (the llms.txt "negative
|
|
256
|
-
retained" bench hints this is right; prove it). The existing 4K-cycle unit
|
|
257
|
-
leak tests stay in `test/07`; s6 is the deeper per-commit version.
|
|
258
|
-
|
|
259
|
-
### s7 -- conformance: the consumer corpus, the floor, the pact
|
|
260
|
-
|
|
261
|
-
- **StreamQuery corpus**: the exact call shape from `StreamQuery.js:124-154`
|
|
262
|
-
(transform-as-tap + hand ring + abort-filtering onError), run against a
|
|
263
|
-
seeded source, asserting the semantics lite-query depends on: transform
|
|
264
|
-
called once per value BEFORE set, abort fires onError with
|
|
265
|
-
`signal.aborted === true` observable, onDone exactly once, stop idempotent.
|
|
266
|
-
lite-stream must never drift under its flagship consumer silently. When
|
|
267
|
-
LS3 ships the enriched pipeToSignal, the corpus gains the enriched shape
|
|
268
|
-
and KEEPS the 1.1.0 shape (both are public API).
|
|
269
|
-
- **Floor matrix**: `npm run test:floor` -- scripted: install the peer floor
|
|
270
|
-
(`@zakkster/lite-signal@1.2.2`) via `--no-save --no-package-lock`, full
|
|
271
|
-
`test:gc` + torture, restore latest, print both verdicts. Run per release
|
|
272
|
-
(release drill step), not per commit. LS-09 stays a fact, not a claim.
|
|
273
|
-
- **TimeoutError duck-pact**: shape assertions (`name`, `timeout` field,
|
|
274
|
-
`instanceof Error`, message prefix) pinning the cross-package duck-check
|
|
275
|
-
contract (2b). No lite-await devDep -- the pact is structural.
|
|
276
|
-
|
|
277
|
-
### s8 -- controls
|
|
278
|
-
|
|
279
|
-
`STREAM_TORTURE_BREAK=1 node --expose-gc test/torture.mjs` must exit
|
|
280
|
-
non-zero via injected breakage, one per tier minimum: an allocating closure
|
|
281
|
-
in the s5 caught-up loop; an oracle that ignores droppedCount in s4; a
|
|
282
|
-
skipped abort case in s1; a waiter-order swap in s2; a retained-stream ref
|
|
283
|
-
in s6; a corpus assertion inverted in s7. A gate that cannot fail is
|
|
284
|
-
decorative.
|
|
285
|
-
|
|
286
|
-
## 4. Session order
|
|
287
|
-
|
|
288
|
-
```
|
|
289
|
-
LS1 (1.1.1 truth patch)
|
|
290
|
-
|
|
|
291
|
-
v
|
|
292
|
-
LS2 (1.2.0 the law's gate + guards + VERSION + packaging)
|
|
293
|
-
|
|
|
294
|
-
v
|
|
295
|
-
LS3 (1.3.0 the consumer contract: pipeToSignal enrichment + abort decision)
|
|
296
|
-
|
|
|
297
|
-
v [tripwire: lite-query Q8 session-start spike rules the split]
|
|
298
|
-
LS4 (1.4.0 Q8 enablement: stall detection / push-writer / share primitives)
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Serial on purpose: LS1's honest docs are the floor LS2's gates stand on;
|
|
302
|
-
LS2's harness is what makes LS3's surface growth safe to ship; LS4 is
|
|
303
|
-
design-gated on lite-query Q8 and MUST NOT jump the queue (building shared-
|
|
304
|
-
stream primitives before the consumer's failover matrix exists would be
|
|
305
|
-
speculative API -- the exact thing the 1.0 deferral discipline refused).
|
|
306
|
-
|
|
307
|
-
## 5. The briefs
|
|
308
|
-
|
|
309
|
-
===============================================================================
|
|
310
|
-
# LS1 -- lite-stream v1.1.1 -- the docs tell the truth and the gates run
|
|
311
|
-
===============================================================================
|
|
312
|
-
|
|
313
|
-
```markdown
|
|
314
|
-
---
|
|
315
|
-
package: "@zakkster/lite-stream"
|
|
316
|
-
version_target: 1.1.1
|
|
317
|
-
status: planned
|
|
318
|
-
tests_min: 85
|
|
319
|
-
skip_max: 1 # only the named asyncDispose environment skip, node < 20
|
|
320
|
-
findings: [LS-01, LS-02, LS-04, LS-06, LS-07, LS-08, LS-10]
|
|
321
|
-
depends_on: []
|
|
322
|
-
---
|
|
323
|
-
|
|
324
|
-
# lite-stream -- no shipped sentence the probes can refute
|
|
325
|
-
|
|
326
|
-
PURPOSE
|
|
327
|
-
A patch release with zero API change that makes every shipped claim true
|
|
328
|
-
and every existing test actually run. The package's core positioning is
|
|
329
|
-
"structural cleanup on every termination path"; today one documented path
|
|
330
|
-
is a leak generator. Fix the words now, the machinery in LS2/LS3.
|
|
331
|
-
|
|
332
|
-
TASKS
|
|
333
|
-
- LS-01 docs surgery, all three shipped docs: llms.txt lines 132-134
|
|
334
|
-
replaced with the truth ("disposing the result signal does NOT stop the
|
|
335
|
-
pump; the iterator keeps pulling. AbortSignal or natural completion are
|
|
336
|
-
the only stop mechanisms. See ROADMAP LS-01."); the Stream.js:148-152
|
|
337
|
-
docstring stops offering dispose as a stop mechanism; README cleanup
|
|
338
|
-
section aligned. The dead catch at Stream.js:343-351 STAYS (belt against
|
|
339
|
-
a future lite-signal that throws) but its comment stops claiming it is
|
|
340
|
-
the dispose path and starts naming what it actually is: defensive.
|
|
341
|
-
- LS-01/LS-04 pinned tests (new test/08-dispose-behavior.test.mjs, ~6
|
|
342
|
-
tests): dispose-mid-pump -> pump continues, return() not called (pin the
|
|
343
|
-
TRUE behavior so any future change is a conscious one); same for
|
|
344
|
-
pipeToSignal; dispose-source under toAsyncIterable -> pending next()
|
|
345
|
-
hangs (pin via a bounded race against a sentinel timer); each test cites
|
|
346
|
-
LS-01/LS-04 in its name.
|
|
347
|
-
- LS-02: default gate becomes `"test": "node --expose-gc --test
|
|
348
|
-
--test-reporter=spec"`; `test:gc` kept as an alias for muscle memory;
|
|
349
|
-
`verify` unchanged. Assert in-session: 79 -> 79 pass 0 skip (pre-verified
|
|
350
|
-
2026-09-01: 21.5s).
|
|
351
|
-
- LS-06: delete the two `CONTRACT-toAsyncIterable-1.1.md` references (the
|
|
352
|
-
locked semantics live in llms.txt + this roadmap's s2/s3 tiers now);
|
|
353
|
-
rewrite the TimeoutError comment without the Session A/B narrative.
|
|
354
|
-
- LS-07: fix the pump comment to state the actual mechanism (".then
|
|
355
|
-
handlers unwind the stack; no explicit deferral needed") and cite the 1M
|
|
356
|
-
sync-source probe number.
|
|
357
|
-
- LS-08: re-run `npm run bench`, stamp llms.txt Performance and README
|
|
358
|
-
Benchmarks with node version + date + "your numbers will differ", one
|
|
359
|
-
source of truth (llms.txt), README cites it.
|
|
360
|
-
- LS-10 (partially done at roadmap-pass time): .gitignore landed; record
|
|
361
|
-
the no-lockfile policy line in README's Testing section or CONTRIBUTING
|
|
362
|
-
stub -- one sentence, not a document.
|
|
363
|
-
- CHANGELOG [1.1.1]: Fixed (docs corrections, each naming its finding),
|
|
364
|
-
Added (dispose-behavior pins, gate flag). Facts only.
|
|
365
|
-
|
|
366
|
-
HOT PATH
|
|
367
|
-
Untouched. `git diff -- Stream.js` shows comment-only hunks; reviewer
|
|
368
|
-
proves token-stream identity (the Q2 discipline).
|
|
369
|
-
|
|
370
|
-
ASSERTIONS
|
|
371
|
-
- npm test -> >= 85 pass, 0 fail, skip <= 1 (asyncDispose env skip only,
|
|
372
|
-
named).
|
|
373
|
-
- bench/findings-probes-2026-09-01.mjs output UNCHANGED (the probes pin
|
|
374
|
-
behavior; this release changes words).
|
|
375
|
-
- grep proves no shipped doc contains "tears down" dispose claims; grep
|
|
376
|
-
finds the AbortSignal-only sentence in llms.txt AND README.
|
|
377
|
-
- npm pack --dry-run: no test/, no bench/, no demo/.
|
|
378
|
-
|
|
379
|
-
NON-GOALS
|
|
380
|
-
No API change. No VERSION export (that is additive -> LS2). No torture
|
|
381
|
-
harness (LS2). No behavior change to the pump.
|
|
382
|
-
|
|
383
|
-
DONE WHEN
|
|
384
|
-
Every sentence the probes can check is true, the default gate runs
|
|
385
|
-
everything, and 1.1.1 is published with the corrected llms.txt.
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
===============================================================================
|
|
389
|
-
# LS2 -- lite-stream v1.2.0 -- the law's gate, able to fail
|
|
390
|
-
===============================================================================
|
|
391
|
-
|
|
392
|
-
```markdown
|
|
393
|
-
---
|
|
394
|
-
package: "@zakkster/lite-stream"
|
|
395
|
-
version_target: 1.2.0
|
|
396
|
-
status: planned
|
|
397
|
-
tests_min: 90
|
|
398
|
-
skip_max: 1
|
|
399
|
-
torture: "s0-s8 ok; STREAM_TORTURE_BREAK=1 fails every tier; floor matrix green"
|
|
400
|
-
findings: [LS-03, LS-05, LS-09, LS-11]
|
|
401
|
-
depends_on: [LS1]
|
|
402
|
-
---
|
|
403
|
-
|
|
404
|
-
# lite-stream -- prove the claims, then keep them proven
|
|
405
|
-
|
|
406
|
-
PURPOSE
|
|
407
|
-
Port the suite-law torture harness (bake-stream M0 discipline, tiers per
|
|
408
|
-
section 3), add the drift guards that keep docs/exports/ASCII honest
|
|
409
|
-
(lite-query Q2 discipline), export VERSION, and execute the LS-11
|
|
410
|
-
packaging ruling. After this session the package can prove its own
|
|
411
|
-
headline claims -- including the zero-steady-state-alloc sentence that has
|
|
412
|
-
been unverified since 1.0.0.
|
|
413
|
-
|
|
414
|
-
TASKS
|
|
415
|
-
- test/torture.mjs + test/torture/ per section 3: harness (seeded PRNG,
|
|
416
|
-
check thunks, runOpsGate stabilize:'deep', STREAM_TORTURE_BREAK,
|
|
417
|
-
preflight exit 2 on missing devDeps), tiers s0-s8, controls for every
|
|
418
|
-
tier. devDeps: @zakkster/lite-leak (floor from its llms.txt),
|
|
419
|
-
@zakkster/lite-gc-profiler ^1.10.0 (unknown-rule-keys-throw relied on).
|
|
420
|
-
Read both llms.txt files before wiring; rule names from memory are how
|
|
421
|
-
gates rot.
|
|
422
|
-
- package.json: "torture": "node --expose-gc test/torture.mjs";
|
|
423
|
-
"torture:control": "STREAM_TORTURE_BREAK=1 node --expose-gc
|
|
424
|
-
test/torture.mjs || echo control-failed-as-expected"; "test:floor" per
|
|
425
|
-
s7; "verify" gains torture.
|
|
426
|
-
- Guards (Q2-modeled, ~5 tests): ascii-guard (walk = files[] parsed at
|
|
427
|
-
runtime + test/ + bench/ + demo/; printable ASCII + LF; empty allowlist;
|
|
428
|
-
inline failing control) and surface-guard (exports == llms.txt == d.ts
|
|
429
|
-
export names; peer names in llms.txt exist in peerDependencies; failing
|
|
430
|
-
fixture control). Pre-verified 2026-09-01: repo is already 0 non-ASCII.
|
|
431
|
-
- LS-05: `const VERSION = "1.2.0"` exported from Stream.js; string-equals
|
|
432
|
-
package.json (release-drill site). Never re-exported by consumers
|
|
433
|
-
(suite ruling). Surface-guard and d.ts updated in the same commit.
|
|
434
|
-
- LS-11 ruling executes: ROADMAP.md out of files[]; llms.txt Files section
|
|
435
|
-
updated; CHANGELOG forward-references fixed (the published 1.1.0 tarball
|
|
436
|
-
keeps its pointer -- the History section below preserves the target).
|
|
437
|
-
- LS-09: test:floor scripted and run in-session (pre-verified green
|
|
438
|
-
2026-09-01); result recorded in CHANGELOG with provenance. Peer floor
|
|
439
|
-
STAYS ^1.2.0 because it is now a tested fact.
|
|
440
|
-
- Expected s5 friction, decided in advance (M0 lesson): the per-next
|
|
441
|
-
Promise allocation in toAsyncIterable and the by-design per-yield state
|
|
442
|
-
objects in fromAsyncIterable get per-scenario documented budgets --
|
|
443
|
-
never a widened global rule. A budget that moves is not a gate.
|
|
444
|
-
|
|
445
|
-
ASSERTIONS
|
|
446
|
-
- npm test >= 90 pass, 0 fail, skip <= 1; torture prints "ok" exit 0;
|
|
447
|
-
torture:control exits non-zero FOR EVERY TIER (each control verified
|
|
448
|
-
individually in-session).
|
|
449
|
-
- The llms.txt zero-steady-state-alloc sentence now has a gate (s5) and
|
|
450
|
-
the gate has a control (s8).
|
|
451
|
-
- npm pack --dry-run: ROADMAP.md ABSENT, test/ bench/ demo/ absent,
|
|
452
|
-
llms.txt + CHANGELOG.md present; VERSION === package.json version.
|
|
453
|
-
- Fresh-clone drill: clone to scratch, npm install, npm test, npm run
|
|
454
|
-
torture -- all green (the lite-query G10 habit).
|
|
455
|
-
|
|
456
|
-
NON-GOALS
|
|
457
|
-
No surface growth beyond VERSION. No pipeToSignal enrichment (LS3). No
|
|
458
|
-
behavior changes -- if a tier finds a real bug, it lands as a NAMED
|
|
459
|
-
failing todo + ledger row and ships fixed in LS3, not silently here.
|
|
460
|
-
|
|
461
|
-
DONE WHEN
|
|
462
|
-
the law's command runs from the law's path, every control fails, the
|
|
463
|
-
floor matrix is scripted, and 1.2.0 is published with VERSION exported
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
===============================================================================
|
|
467
|
-
# LS3 -- lite-stream v1.3.0 -- the consumer contract session
|
|
468
|
-
===============================================================================
|
|
469
|
-
|
|
470
|
-
```markdown
|
|
471
|
-
---
|
|
472
|
-
package: "@zakkster/lite-stream"
|
|
473
|
-
version_target: 1.3.0
|
|
474
|
-
status: planned
|
|
475
|
-
tests_min: 105
|
|
476
|
-
skip_max: 1
|
|
477
|
-
torture: "ok + s7 corpus extended to the enriched shape"
|
|
478
|
-
findings: [LS-12]
|
|
479
|
-
depends_on: [LS2]
|
|
480
|
-
consumer_evidence: "StreamQuery.js:119-137 (hand ring), :127 (status tap), :139-142 (abort filter)"
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
# lite-stream -- what its flagship consumer already built by hand, offered properly
|
|
484
|
-
|
|
485
|
-
PURPOSE
|
|
486
|
-
Every addition in this session has a shipped consumer code-site as its
|
|
487
|
-
triggering signal -- the 1.0 deferral discipline, satisfied for real.
|
|
488
|
-
lite-query's StreamQuery hand-rolls three things around pipeToSignal:
|
|
489
|
-
a buffer window (push/shift/slice -- O(n) shift where our ring is O(1)),
|
|
490
|
-
a first-value status tap smuggled through `transform`, and an
|
|
491
|
-
abort-filtering onError. Offer all three natively; let the consumer
|
|
492
|
-
delete code.
|
|
493
|
-
|
|
494
|
-
TASKS
|
|
495
|
-
- pipeToSignal enrichment (design settled in-session by the planner,
|
|
496
|
-
candidates): `mode: "latest" | "buffer"` + `maxBuffer` + a
|
|
497
|
-
droppedCount observable (return-object upgrade or stats getter --
|
|
498
|
-
decide against the "returns stop fn" 1.x compat constraint: the stop
|
|
499
|
-
FUNCTION must remain callable as before; additive properties on it are
|
|
500
|
-
the compat-safe shape), and `onValue(v)` tap firing before set (the
|
|
501
|
-
status-tap consumer). All additive; 1.1.0 call shapes byte-compatible
|
|
502
|
-
(s7 corpus keeps both shapes green).
|
|
503
|
-
- LS-12 decision doc, decisions/0001-abort-vocabulary.md (the lite-await
|
|
504
|
-
decisions/ convention): EITHER pin the asymmetry as deliberate with the
|
|
505
|
-
producer/consumer rationale (from-side: abort interrupts production =
|
|
506
|
-
error framing; to-side: abort stops listening = graceful done) and
|
|
507
|
-
document it in llms.txt as a contract table -- OR add `onAbort`
|
|
508
|
-
callbacks so consumers stop filtering. Recommendation: PIN the
|
|
509
|
-
asymmetry + add `onAbort` to pipeToSignal only (the direction with the
|
|
510
|
-
shipped workaround); do not touch toAsyncIterable's graceful-done
|
|
511
|
-
contract (1.0.0-locked, correct for its side).
|
|
512
|
-
- s7 corpus: add the enriched-shape cases; keep the 1.1.0-shape cases
|
|
513
|
-
forever.
|
|
514
|
-
- Cross-repo rider recorded (not executed here): lite-query parking entry
|
|
515
|
-
exists pointing at this session -- after 1.3.0 ships, StreamQuery can
|
|
516
|
-
collapse its ring + tap + filter into the enriched call in a lite-query
|
|
517
|
-
minor, and refresh its vendored demo copy (still 1.0.0).
|
|
518
|
-
|
|
519
|
-
ASSERTIONS
|
|
520
|
-
- Old-shape s7 corpus green UNCHANGED against the new build (no drift
|
|
521
|
-
under the flagship consumer).
|
|
522
|
-
- A StreamQuery-shaped rewrite using the enriched surface (as a TEST, in
|
|
523
|
-
this repo) produces identical observable semantics to the hand-rolled
|
|
524
|
-
original, including droppedCount parity on overflow.
|
|
525
|
-
- Suite >= 105, 0 fail; torture ok; guards green; VERSION bumped.
|
|
526
|
-
|
|
527
|
-
NON-GOALS
|
|
528
|
-
No fromAsyncIterable changes. No toAsyncIterable changes. No shared/
|
|
529
|
-
multicast primitives (LS4, gated). No combinators (never -- History).
|
|
530
|
-
|
|
531
|
-
DONE WHEN
|
|
532
|
-
lite-query COULD delete StreamQuery.js:119-137 without behavior change,
|
|
533
|
-
and 1.3.0 is published with the decision doc on disk
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
===============================================================================
|
|
537
|
-
# LS4 -- lite-stream v1.4.0 -- Q8 enablement (GATED, do not start on a hunch)
|
|
538
|
-
===============================================================================
|
|
539
|
-
|
|
540
|
-
```markdown
|
|
541
|
-
---
|
|
542
|
-
package: "@zakkster/lite-stream"
|
|
543
|
-
version_target: 1.4.0
|
|
544
|
-
status: gated
|
|
545
|
-
gate: "lite-query Q8 session-start spike has ruled which primitives land here"
|
|
546
|
-
tests_min: 120
|
|
547
|
-
depends_on: [LS3]
|
|
548
|
-
---
|
|
549
|
-
|
|
550
|
-
# lite-stream -- the primitives a one-connection browser needs, if Q8 wants them here
|
|
551
|
-
|
|
552
|
-
PURPOSE
|
|
553
|
-
lite-query 2.0 (Q8) makes one tab own an upstream iterator while N tabs
|
|
554
|
-
consume frames. Three primitive-shaped needs are visible from Q8's brief
|
|
555
|
-
today; WHICH of them belong in lite-stream (vs inside lite-query, vs
|
|
556
|
-
nowhere) is Q8's session-start spike ruling. This brief is a menu, not a
|
|
557
|
-
commitment.
|
|
558
|
-
|
|
559
|
-
CANDIDATE MENU (each with its Q8 obligation)
|
|
560
|
-
- Idle/stall detection: an `idleTimeout` that resets per value (today's
|
|
561
|
-
`timeout` is an overall deadline -- wrong tool for "leader hung, frames
|
|
562
|
-
stopped, channel alive"). Smallest candidate, clearest fit.
|
|
563
|
-
- Push-writer surface: values that arrive by callback (channel frames),
|
|
564
|
-
not by iterator pull, entering a signal with the same mode/maxBuffer/
|
|
565
|
-
droppedCount discipline (the follower side's projection). Shape TBD --
|
|
566
|
-
possibly just documenting the enriched pipeToSignal over a hand-made
|
|
567
|
-
async queue, possibly a real `createSignalWriter`.
|
|
568
|
-
- Share/refcount: one source iterator, N signal consumers, last-consumer
|
|
569
|
-
teardown. The most API-heavy candidate and the most likely to belong
|
|
570
|
-
INSIDE lite-query next to its cache entries instead -- do not build
|
|
571
|
-
speculatively.
|
|
572
|
-
|
|
573
|
-
ASSERTIONS (floor, if the gate opens)
|
|
574
|
-
Whatever ships: torture tiers extended same-session (a primitive without
|
|
575
|
-
its soak + fuzz + controls does not ship); Q8's failover matrix consumes
|
|
576
|
-
the new surface in the same quarter, or the surface is reverted before
|
|
577
|
-
2.0 -- an enabler nobody enabled is speculative API with a version number.
|
|
578
|
-
|
|
579
|
-
DONE WHEN
|
|
580
|
-
the Q8 ruling is recorded here (menu -> commitments), and either 1.4.0
|
|
581
|
-
ships what Q8 consumes, or this session closes as "not needed in
|
|
582
|
-
lite-stream" with the reasoning -- both are wins
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
## 6. How to run it
|
|
586
|
-
|
|
587
|
-
In order: LS1 -> LS2 -> LS3 -> LS4 (gated). Pipeline per session: planner ->
|
|
588
|
-
coder -> reviewer -> qa; reviewer REJECTED goes back to coder. `status:
|
|
589
|
-
planned -> shipped` in the brief's frontmatter on publish, with the /release
|
|
590
|
-
drill (fail closed; the drill owns version bumps; VERSION site list from
|
|
591
|
-
LS2 on: package.json, Stream.js header, VERSION const, llms.txt if it ever
|
|
592
|
-
gains a version string).
|
|
593
|
-
|
|
594
|
-
Habits this roadmap is built around (inherited, locally re-proven):
|
|
595
|
-
1. **Reproduce by running, never infer.** Every ledger row above has a
|
|
596
|
-
command. LS-01 existed for two shipped versions because a comment was
|
|
597
|
-
trusted over a run.
|
|
598
|
-
2. **A green suite that skips its GC tier is a green light over a hole.**
|
|
599
|
-
Fourth suite recurrence (Q-02, AR-02, AW-14, LS-02). The fix is always
|
|
600
|
-
the same: the default gate runs everything.
|
|
601
|
-
3. **Claims about a peer are facts about a VERSION of a peer.** The dispose
|
|
602
|
-
assumption was never true of any version; the floor claim happened to be
|
|
603
|
-
true of all of them. Neither status was known until today. The floor
|
|
604
|
-
matrix and the conformance corpus exist so both stay known.
|
|
605
|
-
4. **Budgets, not widened rules.** By-design allocations (state objects,
|
|
606
|
-
snapshot arrays, protocol promises) get named per-scenario budgets in
|
|
607
|
-
s5; the global maxMajor: 0 rule never widens.
|
|
608
|
-
|
|
609
|
-
## Parking ledger (deferred, with reasons and tripwires)
|
|
610
|
-
|
|
611
|
-
- **Dispose-aware pumps** (the LS-01 behavioral fix): IMPOSSIBLE in-library
|
|
612
|
-
today -- lite-signal 1.5.0 exposes no disposed-oracle (2b). Tripwire:
|
|
613
|
-
lite-signal ships one -> un-park, wire into fromAsyncIterable/pipeToSignal
|
|
614
|
-
teardown, upgrade the LS1 pinned "pump continues" tests into "pump stops"
|
|
615
|
-
tests in the same change. Until then: docs + pins (LS1).
|
|
616
|
-
- **toAsyncIterable dispose-source hardening** (LS-04): same tripwire, same
|
|
617
|
-
fate (pending next() could reject instead of hang).
|
|
618
|
-
- **mapLimit / transform concurrency** on fromAsyncIterable (from the 1.0
|
|
619
|
-
ledger): trigger re-grounded -- two or more SUITE consumers writing the
|
|
620
|
-
same bounded-concurrency wrapper. None exist today (checked: only
|
|
621
|
-
StreamQuery consumes, no concurrency wrapper).
|
|
622
|
-
- **toAsyncIterable overflow policies** (drop-newest / throw / block):
|
|
623
|
-
trigger unchanged -- a consumer demonstrating drop-oldest causes
|
|
624
|
-
observable correctness issues. None known.
|
|
625
|
-
- **mergeIterables / splitSignal**: trigger re-grounded from "first Twitch
|
|
626
|
-
consumer" to "first suite consumer needing >= 2 async sources into one
|
|
627
|
-
reactive surface with shared cancellation". Q8's leader might be that
|
|
628
|
-
consumer -- if so it routes through the LS4 menu, not around it.
|
|
629
|
-
- **fromEventTarget(target, eventName)**: browser-facing sugar; trigger:
|
|
630
|
-
the pattern recurring across suite demos. demo/index.html is the place to
|
|
631
|
-
look first (unaudited against the demo-audit law -- run that skill next
|
|
632
|
-
time the demo is touched).
|
|
633
|
-
- **pipeToSignal backpressure "block"**: requires effect-completion
|
|
634
|
-
semantics lite-signal does not have (2b-class cross-package fact).
|
|
635
|
-
Tripwire: lite-signal ships them.
|
|
636
|
-
- **opts.equals for buffer-mode snapshot dedup**: trigger: a consumer
|
|
637
|
-
demonstrating redundant snapshot propagation mattering in a profile.
|
|
638
|
-
- **stats() diagnostic surface**: superseded in part by droppedCount/
|
|
639
|
-
overflowCount; remainder (yields, startedAt, settledAt) waits for the
|
|
640
|
-
lite-query Q7 devtools feed to define what a stream should report --
|
|
641
|
-
tripwire: Q7 ships and asks.
|
|
642
|
-
- **RangeReader recipe** (bake-stream): docs-only candidate -- lazy shard
|
|
643
|
-
walk -> async generator -> fromAsyncIterable; needs bake-stream >= MQ1
|
|
644
|
-
(abortable range reads) so the AbortSignal story is end-to-end. Tripwire:
|
|
645
|
-
MQ1 ships.
|
|
646
|
-
- **propagate: "always"** and **full combinators (map/filter/debounce/...)**:
|
|
647
|
-
never -- see History; the async-generator composition answer stands.
|
|
648
|
-
|
|
649
|
-
## History (preserved from the 1.0/1.1 roadmap; the shipped 1.1.0 CHANGELOG points here)
|
|
650
|
-
|
|
651
|
-
### Shipped in 1.1.0 -- toAsyncIterable enrichment
|
|
652
|
-
|
|
653
|
-
Six additions, each with the triggering signal that moved it out of
|
|
654
|
-
deferred status: `mode: "latest" | "buffer"` (vocabulary match with
|
|
655
|
-
lite-query's streamQuery + reactive-state consumers); `filter` (removes the
|
|
656
|
-
per-yield wrapper-generator allocation); `timeout` + `TimeoutError` export
|
|
657
|
-
(replaces external AbortController boilerplate; structurally identical to
|
|
658
|
-
lite-await's class, deliberately not imported -- the zero-dep story);
|
|
659
|
-
`Symbol.asyncDispose` on node >= 20 (`await using`); multi-waiter FIFO
|
|
660
|
-
queue (latent 1.0.0 bug: `Promise.all([iter.next(), iter.next()])` silently
|
|
661
|
-
lost the first resolver); `overflowCount` (mode-neutral alias for
|
|
662
|
-
`droppedCount`). Full details: CHANGELOG.md [1.1.0].
|
|
663
|
-
|
|
664
|
-
### Non-goals carried forward verbatim
|
|
665
|
-
|
|
666
|
-
No standalone scheduler; no retry/backoff policy (compose lite-await);
|
|
667
|
-
no WebSocket/EventSource adapters (they belong to the consuming SDK); no
|
|
668
|
-
ReactiveX combinator surface (async generators are the composition story --
|
|
669
|
-
inspectable, debuggable, cancellation-transparent); no multi-iterator
|
|
670
|
-
orchestration beyond a potential mergeIterables (lite-await's allOf/anyOf/
|
|
671
|
-
raceOf own that).
|
|
672
|
-
|
|
673
|
-
### Versioning policy (unchanged)
|
|
674
|
-
|
|
675
|
-
Strict semver. 1.x additive only (new exports, new opts fields, new
|
|
676
|
-
state-shape fields); breaking waits for 2.0; patches are fixes/docs only.
|
|
677
|
-
Every addition lands with: a concrete suite consumer, full-lifecycle tests
|
|
678
|
-
(happy + all termination paths), a bench with provenance, and a CHANGELOG
|
|
679
|
-
line with a hot-path-cost note.
|
|
680
|
-
|
|
681
|
-
## Progress log
|
|
682
|
-
|
|
683
|
-
- 2026-09-01 -- Roadmap enriched to blueprint form. Ground truth verified by
|
|
684
|
-
running: 79 tests (71+8 dark under default gate; 79/79 under test:gc,
|
|
685
|
-
against lite-signal 1.5.0 and 1.2.2 both), bench re-run with provenance,
|
|
686
|
-
five probes written and committed (bench/findings-probes-2026-09-01.mjs).
|
|
687
|
-
Ledger LS-01..LS-12 opened; LS-01 (dispose forever-pump, llms.txt claim
|
|
688
|
-
false since 1.0.0) is the headline. .gitignore added (LS-10 mitigation,
|
|
689
|
-
lite-query Q1 ruling). Cross-package wiring recorded on both sides:
|
|
690
|
-
lite-query Q8 spike <-> LS4 gate; LS3 <-> StreamQuery hand-ring rider.
|
|
691
|
-
Sessions LS1..LS4 defined. Nothing else changed; the tree still builds
|
|
692
|
-
and tests exactly as published 1.1.0.
|
|
693
|
-
|
|
694
|
-
- 2026-09-01 -- LS1 (v1.1.1 truth patch) landed in the working tree. LS-01:
|
|
695
|
-
the dispose-teardown claim corrected in llms.txt, README (two sites), the
|
|
696
|
-
Stream.js fromAsyncIterable + pipeToSignal docstrings, and the package.json
|
|
697
|
-
description; the defensive set() catch and its forward comment re-framed as
|
|
698
|
-
peer-defense (not disposal detection), and the LS-07 pump comment corrected
|
|
699
|
-
to "direct call, no deferral" -- the Stream.js diff is comment-only and the
|
|
700
|
-
code-line stream is byte-identical (md5 unchanged, exports unchanged). LS-06:
|
|
701
|
-
the three phantom CONTRACT-toAsyncIterable-1.1.md references and the
|
|
702
|
-
Session A/B narrative removed from Stream.js and test/07. LS-08: bench
|
|
703
|
-
re-measured with provenance (node v26.3.1, macOS arm64, 150ms warmup /
|
|
704
|
-
500ms runs) and consolidated as llms.txt Performance (the single source of
|
|
705
|
-
truth); README now cites it with no ops/s digits; the four unreproducible
|
|
706
|
-
1.1 bench scenarios deleted from the docs (no runnable source in the repo).
|
|
707
|
-
LS-02: --expose-gc added to the default npm test so the GC tier gates on
|
|
708
|
-
every run. Added test/08-dispose-behavior.test.mjs (6 pins, LS-01/LS-04);
|
|
709
|
-
LS-10 no-lockfile sentence added to the README. Measured: npm test 85 pass
|
|
710
|
-
/ 0 fail / 0 skipped; probes P0-P4 structurally unchanged. The LS1 brief's
|
|
711
|
-
`status:` is left at planned -- it flips at publish, not here.
|