|
8 | 8 |
|
9 | 9 | - **PY `ReactiveMapBundle` parity — `.get(key)`, `.has(key)`, `.size` (noted 2026-04-07):** |
10 | 10 | - **Level A: DONE (2026-04-07).** Added `.get(key)`, `.has(key)`, `.size` to PY `ReactiveMapBundle` matching TS signatures. PY harness `strategy.py` updated to use `.get(key)` instead of Versioned navigation. |
11 | | - - **Level B: Deferred (post-1.0).** `ReactiveMapBundle.node` (TS) / `.data` (PY) emits `Versioned<{ map: ReadonlyMap<K,V> }>` / `Versioned(version, MappingProxyType)`. The `Versioned` wrapper is a protocol optimization (efficient RESOLVED dedup via version comparison) that leaks into composition code when using the node as a derived dep. **Proposed fix:** `.node` / `.data` emits the unwrapped map directly; version-based equality handled internally via `equals` option on the state node. Consumers see `ReadonlyMap<K,V>` / `MappingProxyType`, not `Versioned`. Breaking change — defer to post-1.0 audit of all `Versioned` usage. |
| 11 | + - **Level B: DONE (PY, 2026-04-07).** Removed `Versioned` wrapper from all reactive bundle APIs (ReactiveMap, ReactiveLog, ReactiveList, ReactiveIndex). `.data` / `.entries` / `.items` / `.ordered` now emit unwrapped domain types (`MappingProxyType`, `tuple`, etc.). Internal version counter drives efficient equality without leaking into composition code (spec §5.12). All downstream consumers updated (messaging, cqrs, ai, domain-templates, composite, harness/strategy). |
12 | 12 |
|
13 | 13 | - **Whole-repo `emit` → `down` audit + `up` / backpressure / `message_tier` sweep (all phases, noted 2026-04-07):** |
14 | 14 | - **TS: DONE (2026-04-07).** Renames: `emitWithBatch` → `downWithBatch`, `_emitToSinks` → `_downToSinks`, `_emitAutoValue` → `_downAutoValue`, `_boundEmitToSinks` → `_boundDownToSinks`, `_emitSequential` → `_downSequential`, `emitLine` → `flushLine` (reactive-layout). Batch param `emit` → `sink`. `up()` audit: no asymmetries. `messageTier()` audit: already clean. `NodeActions.emit()` kept (different semantics from `actions.down()`). CQRS `CommandActions.emit()` kept (domain concept). Spec updated (`_emitAutoValue` → `_downAutoValue`). |
@@ -42,38 +42,37 @@ Non-blocking items tracked for later. **Keep this section identical in both repo |
42 | 42 |
|
43 | 43 | | Item | Notes | |
44 | 44 | |------|-------| |
45 | | -| **`lastDepValues` + `Object.is` / referential equality (resolved 2026-03-31 — keep + document)** | Default `Object.is` identity check is correct for the common immutable-value case. The `node({ equals })` option already exists for custom comparison. Document clearly that mutable dep values should use a custom `equals` function. No code change needed. | |
| 45 | +| **`lastDepValues` + `Object.is` / referential equality (resolved 2026-03-31 — documented)** | Default identity check is correct for the common immutable-value case. The `node(equals=...)` option already exists for custom comparison. Mutable dep values should use a custom `equals` function. **Documented in `node()` docstring (2026-04-07).** | |
46 | 46 | | **`sideEffects: false` in `package.json`** | TypeScript package only. Safe while the library has no import-time side effects. Revisit if global registration or polyfills are added at module load. | |
47 | | -| **JSDoc / docstrings on `node()` and public APIs** | `docs/docs-guidance.md`: JSDoc on new TS exports; docstrings on new Python public APIs. | |
| 47 | +| **JSDoc / docstrings on `node()` and public APIs** | `docs/docs-guidance.md`: JSDoc on new TS exports; docstrings on new Python public APIs. `node()` equals guidance added (2026-04-07). `flat_map` ERROR behavior documented (2026-04-07). `from_redis_stream` COMPLETE/disconnect documented (2026-04-07). | |
48 | 48 | | **Roadmap §0.3 checkboxes** | Mark Phase 0.3 items when the team agrees the milestone is complete. | |
49 | 49 |
|
50 | 50 | ### Factory teardown — `dispose()` pattern (D1/D2, noted 2026-04-07) |
51 | 51 |
|
52 | 52 | | Item | Status | Notes | |
53 | 53 | |------|--------|-------| |
54 | | -| **Phase 4+ factories don't register internal nodes on the graph** | Proposed (2026-04-07) | `harness_loop` (and other factories) create internal nodes (`triage_node`, `_router` effect, `_retry_effect`, `execute_node`, `verify_node`, `with_latest_from` combinators, etc.) that are never added to the parent graph via `graph.add()`. When `graph.destroy()` fires, these nodes retain their subscriptions and closures — memory leak on repeated create/destroy cycles. Keepalive subscriptions (`_router.subscribe(...)`, `_retry_effect.subscribe(...)`) also leak because their unsub handles are discarded. | |
55 | | -| **Proposed: `dispose()` convention** | Proposed (2026-04-07) | Factories collect all cleanup functions into a `_disposers: list[Callable[[], None]]` list. Override `destroy()` on the returned graph class to drain the list. Keepalive unsubs are just more entries in the same list. This is a Phase 0 primitive addition — a lightweight contract that any factory can adopt. Alternatives considered: (A) `graph.add()` all internal nodes — simple but pollutes the node registry with internals; (B) manual bookkeeping of unsub handles — fragile, easy to miss one. Option C (dispose list) is composable, private, and hard to get wrong. D2 (leaked keepalive handles) collapses into D1 — same list, same `destroy()` drain. | |
| 54 | +| **Phase 4+ factories don't register internal nodes on the graph** | **DONE (TS + PY, 2026-04-07)** | Added `Graph.addDisposer(fn)` / `Graph.add_disposer(fn)` — general-purpose disposer registration drained on `destroy()` **before** TEARDOWN signal. TS: Fixed `harnessLoop`, `strategyModel`, `agentMemory`, `feedback`, `gate`, `contentModerationGraph`, `funnel` bridge, `ChatStreamGraph`, `ToolRegistryGraph`. PY: Fixed `harness_loop`, `reduction.py`, `ChatStreamGraph`, `ToolRegistryGraph`, `AgentMemoryGraph`. Dead `_version` counter removed from all reactive bundles (TS + PY). | |
56 | 55 |
|
57 | 56 | ### AI surface (Phase 4.4) — deferred optimizations |
58 | 57 |
|
59 | 58 | | Item | Status | Notes | |
60 | 59 | |------|--------|-------| |
61 | | -| **Re-indexes entire store on every change** | Deferred | Decision: diff-based indexing using `Versioned` snapshot version field to track indexed entries. Deferred to after Phase 6 — current N is small enough that full re-index is acceptable pre-1.0. | |
| 60 | +| **Re-indexes entire store on every change** | Deferred | Decision: diff-based indexing using internal version counter to track indexed entries. Deferred to after Phase 6 — current N is small enough that full re-index is acceptable pre-1.0. | |
62 | 61 | | **Budget packing always includes first item** | Documented behavior | The retrieval budget packer always includes the first ranked result even if it exceeds `maxTokens`. This is intentional "never return empty" semantics — a query that matches at least one entry always returns something. Callers who need strict budget enforcement should post-filter. | |
63 | 62 | | **Retrieval pipeline auto-wires when vectors/KG enabled** | Documented behavior | When `embedFn` or `enableKnowledgeGraph` is set, the retrieval pipeline automatically wires vector search and KG expansion into the retrieval derived node. There is no explicit opt-in/opt-out per retrieval stage — the presence of the capability implies its use. Callers who need selective retrieval should use the individual nodes directly. | |
64 | 63 |
|
65 | 64 | ### Tier 2 extra operators — deferred semantics |
66 | 65 |
|
67 | 66 | | Item | Status | Notes | |
68 | 67 | |------|--------|-------| |
69 | | -| **`mergeMap` / `merge_map` + `ERROR`** | Documented limitation (2026-03-31) | When the outer stream or one inner emits `ERROR`, other inner subscriptions may keep running until they complete or unsubscribe. Rx-style "first error cancels all sibling inners" is **not** specified or implemented. Current behavior (inner errors don't cascade) is arguably more useful for parallel work — no change needed. Document in JSDoc/docstrings. | |
| 68 | +| **`mergeMap` / `merge_map` + `ERROR`** | **Documented (PY docstring, 2026-04-07)** | Inner errors propagate downstream but do not cancel sibling inners. Outer ERROR cancels all inners. Current behavior is intentional for parallel work. **Documented in `flat_map` docstring.** | |
70 | 69 |
|
71 | 70 | ### Ingest adapters — deferred items |
72 | 71 |
|
73 | 72 | | Item | Status | Notes | |
74 | 73 | |------|--------|-------| |
75 | | -| **`fromRedisStream` / `from_redis_stream` never emits COMPLETE** | Documented limitation (2026-04-03) | Long-lived stream consumers intentionally never complete. The consumer loop runs until teardown. This is expected behavior for persistent stream sources (same as Kafka). Document in JSDoc/docstrings. | |
76 | | -| **`fromRedisStream` / `from_redis_stream` does not disconnect client** | Documented limitation (2026-04-03) | The caller owns the Redis client lifecycle. The adapter does not call `disconnect()` on teardown — the caller is responsible for closing the connection. Same contract as `fromKafka` (caller owns `consumer.connect()`/`disconnect()`). | |
| 74 | +| **`fromRedisStream` / `from_redis_stream` never emits COMPLETE** | **Documented (PY docstring, 2026-04-07)** | Long-lived stream consumers intentionally never complete. **Documented in `from_redis_stream` docstring.** | |
| 75 | +| **`fromRedisStream` / `from_redis_stream` does not disconnect client** | **Documented (PY docstring, 2026-04-07)** | The caller owns the Redis client lifecycle. **Documented in `from_redis_stream` docstring.** | |
77 | 76 | | **PY `from_csv` / `from_ndjson` thread not joined on cleanup** | Documented limitation (2026-04-03) | Python file-ingest adapters run in a daemon thread. On teardown, `active[0] = False` signals the thread to exit but does not `join()` it. The daemon flag ensures the thread does not block process exit. A future optimization could add optional `join(timeout)` on cleanup for stricter resource control. | |
78 | 77 |
|
79 | 78 | ### Intentional cross-language divergences |
|
0 commit comments