This release includes 2 breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Affected surfaces
ReleasePort's take
Light signalv1.2.0 introduces unified null‑handling across multiple functions and a major overhaul of rate‑limiting primitives, while removing several legacy APIs.
Why it matters: Plan migration: Penstock replaces ThrottleStrategy, implicit per‑host throttling was removed, and several API‑related symbols were dropped; test updated code paths before upgrade.
Summary
AI summaryUpdates Internal, docs/api_atlas.md, and host-aware across a mixed release.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Breaking | High |
`_EdgeState` now composes a `FlowState` field, requiring access via `flow_state` namespace. `_EdgeState` now composes a `FlowState` field, requiring access via `flow_state` namespace. Source: granite4.1:30b@2026-05-22-audit Confidence: low |
— |
| Breaking | Medium |
Removed implicit per-host throttling for api.coingecko.com, pokeapi.co, vpic.nhtsa.dot.gov. Removed implicit per-host throttling for api.coingecko.com, pokeapi.co, vpic.nhtsa.dot.gov. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Breaking | Medium |
Unified rate-limit primitive: Penstock replaces ThrottleStrategy. Unified rate-limit primitive: Penstock replaces ThrottleStrategy. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Breaking | Medium |
Removed watershed.json legacy aliases dependency_mode and mode. Removed watershed.json legacy aliases dependency_mode and mode. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Breaking | Medium |
Narrowed Gate, SurgeBarrier, Penstock, Spillway method signatures (dropped scheduler arg). Narrowed Gate, SurgeBarrier, Penstock, Spillway method signatures (dropped scheduler arg). Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Breaking | Medium |
_EdgeState now composes a FlowState field. _EdgeState now composes a FlowState field. Source: llm_adapter@2026-05-22 Confidence: low |
— |
| Breaking | Medium |
Dropped _KNOWN_API_RATE_LIMITS and _resolve_host_safe_rate. Dropped _KNOWN_API_RATE_LIMITS and _resolve_host_safe_rate. Source: llm_adapter@2026-05-22 Confidence: low |
— |
| Feature | Medium |
Unified null-handling across calc, calc_all, pluck, link_to, link_to_list, split_and_get. Unified null-handling across calc, calc_all, pluck, link_to, link_to_list, split_and_get. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Feature | Medium |
`RejectEntry` structured reject list added to IncorporatorList. `RejectEntry` structured reject list added to IncorporatorList. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Feature | Medium |
`SourceRef` value type introduced for source dispatch. `SourceRef` value type introduced for source dispatch. Source: llm_adapter@2026-05-22 Confidence: high |
— |
| Feature | Medium |
register_host_throttle promoted to package top-level. register_host_throttle promoted to package top-level. Source: llm_adapter@2026-05-22 Confidence: low |
— |
| Feature | Low |
Legacy `failed_sources` view remains as `[entry.source for entry in rejects]`. Legacy `failed_sources` view remains as `[entry.source for entry in rejects]`. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Performance | Medium |
Performance win from unified null-handling (≈95% faster on garbage-heavy datasets). Performance win from unified null-handling (≈95% faster on garbage-heavy datasets). Source: llm_adapter@2026-05-22 Confidence: low |
— |
| Performance | Low |
Null‑handling pre‑check costs ~50 ns per row but yields up to ~95% speedup on garbage‑heavy data and <0.5% overhead otherwise. Null‑handling pre‑check costs ~50 ns per row but yields up to ~95% speedup on garbage‑heavy data and <0.5% overhead otherwise. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Bugfix | Low |
`BurstPenstock` refill logic now correctly handles a legitimate zero watermark, fixing latent bug from v1.0. `BurstPenstock` refill logic now correctly handles a legitimate zero watermark, fixing latent bug from v1.0. Source: granite4.1:30b@2026-05-22-audit Confidence: high |
— |
| Other | Medium |
Observability routes Penstock tier-1 recommendations through live known_host_rates view. Observability routes Penstock tier-1 recommendations through live known_host_rates view. Source: llm_adapter@2026-05-22 Confidence: low |
— |
Full changelog
Changed
-
Unified null-handling across
calc/calc_all/pluck/
link_to/link_to_list/split_and_get. Aligned with the
null contractinc()has always provided: when input values are
garbage (None,"","N/A","null","unknown",
"nan","undefined"), the user-supplied callable
(func/chain/extractor/cast_type) is no
longer invoked.calcreturnsdefault(orNonefor the
extractors) silently — no warning emitted. Warnings still fire
when the callable raises on real data, separating the
"missing data" case from the "function exploded" case.Migration: explicit null guards in user lambdas are no longer
necessary. Use stdlib callables directly:# before — defensive null guard inside the lambda calc(lambda v: v.lower() if v else "", "title", default="", target_type=str) pluck("data.title", chain=lambda v: v.lower() if v else "") link_to(books, extractor=lambda v: v.upper() if v else None) # after — same behaviour, no log noise, no lambda calc(str.lower, "title", default="", target_type=str) pluck("data.title", chain=str.lower) link_to(books, extractor=str.upper)Performance: net win.
is_garbage_valuepre-checks cost
~50 ns per row but eliminate the Python exception raise (~30 µs)logger.warningcall (~10 µs) that previously fired on every
garbage row. On garbage-heavy datasets the dispatch path is now
~95% faster; on garbage-free datasets the overhead is <0.5%.
split_and_get's narrow null check (None/"") is
widened to the full garbage set for consistency.
Added
-
RejectEntrystructured reject list.IncorporatorList
now carries arejects: List[RejectEntry]property with structured
failure records (source,error_kind,message,retry_after,
wave_index). HTTP error sites inincorporator/io/fetch.py
build entries witherror_kindfrom the exception type and
retry_afterparsed from anyRetry-Afterheader. ETL practice
calls failed-load rows rejects rather than the messaging-system
dead-letter queue term — the rename follows that convention.The legacy
failed_sources: List[str]attribute remains as a
derived view ([entry.source for entry in rejects]) so existing
user code, tests, and tutorials continue to work unchanged.
Reach forrejectswhen you need structured access to the
exception type or retry hint:result = await Coin.incorp(inc_url=["...", "https://broken/"]) for entry in result.rejects: if entry.error_kind == "HTTPStatusError" and entry.retry_after: schedule_retry(entry.source, after=entry.retry_after)Sidecar pipeline write sites (
chunked.py,_outflow.py,
_stateful_shim.py) still route through the back-compat
failed_sources=[...]constructor kwarg and are auto-wrapped into
entries witherror_kind="Unknown". -
SourceRefvalue type for source dispatch. A new
incorporator.io.SourceReffrozen dataclass consolidates the
"what kind of source is this?" classification used by
incorp(),architect(), and other source-consuming verbs. Five
factories (from_url,from_file,from_parent,from_payload,
from_kwargs) plus an auto-detectparse()classmethod. Public
verb signatures unchanged;SourceRefis internal scaffolding plus
an opt-in public type for callers that want explicit source typing.
Breaking
-
_EdgeStatenow composes aFlowStatefield. The Tideweaver
scheduler's per-edge bookkeeping (_EdgeState) used to declare four
fields —last_consumed_at,bucket_tokens,bucket_last_refill_at,
window_log— directly. They're now grouped under
_EdgeState.flow_state: FlowState(the canal-toolkit type from
incorporator.io.penstock). Built-in Penstocks were updated; only
third-party Penstock subclasses that override
consume_reason(edge_state, flow, now)and read the old top-level
fields are affected.Migration:
# Before: if edge_state.last_consumed_at is None: ... edge_state.bucket_tokens = float(self.burst) edge_state.window_log.append(now) # After: if edge_state.flow_state.last_consumed_at is None: ... edge_state.flow_state.bucket_tokens = float(self.burst) edge_state.flow_state.window_log.append(now)Finishes Phase A2's intent (i14): the dead Penstock-specific fields
no longer live at the top of_EdgeStatefor subclasses that don't
use them — they're behind theflow_statenamespace owned by the
edge's Penstock. -
Removed implicit per-host throttling for
api.coingecko.com,
pokeapi.co, andvpic.nhtsa.dot.gov. The framework now ships
throttle-agnostic — calls to these hosts that previously auto-paced
at 0.2 / 1.5 / 1.5 req/sec respectively will hit the
DEFAULT_RPS=15fallback unless you explicitly opt in.Migration — pick one of:
# Option A: per-call kwarg (most local, easiest to discover). await Coin.incorp( inc_url="https://api.coingecko.com/api/v3/coins/markets", requests_per_second=0.2, ) # Option B: register once at startup; every subsequent call against # the host respects the rate. from incorporator import register_host_penstock from incorporator.io.penstock import SustainedPenstock register_host_penstock("api.coingecko.com", SustainedPenstock(rate_per_sec=0.2)) register_host_penstock("pokeapi.co", SustainedPenstock(rate_per_sec=1.5)) register_host_penstock("vpic.nhtsa.dot.gov", SustainedPenstock(rate_per_sec=1.5))Per-host rationale (rates from the previous registry — re-verify
against the provider's published docs when you copy):api.coingecko.com→ 0.2 r/s (12/min, comfortably under the 5-15/min anon ceiling).pokeapi.co→ 1.5 r/s (90/min, under the 100/min documented ceiling).vpic.nhtsa.dot.gov→ 1.5 r/s (under the 100-200/min documented ceiling).
-
Dropped
incorporator.io.fetch._KNOWN_API_RATE_LIMITSand
_resolve_host_safe_rate— the backward-compat shims have no
remaining callers. Useincorporator.io.penstock.known_host_rates()
for the live registry view. -
Unified rate-limit primitive:
PenstockreplacesThrottleStrategy.
The HTTP throttle layer and the Tideweaver edge layer now share one
canal-toolkit primitive —Penstockis the structural gate, the
throttle settings (rate_per_sec,burst,window_sec) configure
it, and the rate is the computed output.io/throttle.pyis gone;
the new home isio/penstock.py. JSON config shapes (watershed.json)
are unchanged — the same{"type": "burst", "rate_per_sec": ..., "burst": ...}
payload works at both layers.Migration:
| Before | After |
| --- | --- |
|from incorporator.io.throttle import FixedIntervalThrottle|from incorporator.io.penstock import SustainedPenstock|
|FixedIntervalThrottle(0.2)|SustainedPenstock(rate_per_sec=0.2)|
|BurstThrottle(2.0, 10)|BurstPenstock(rate_per_sec=2.0, burst=10)|
|NullThrottle()|NullPenstock()|
|register_host_throttle("h", lambda: FixedIntervalThrottle(0.2))|register_host_penstock("h", SustainedPenstock(rate_per_sec=0.2))|
|from incorporator import register_host_throttle|from incorporator import register_host_penstock|
|ThrottleStrategy(Protocol) |Penstock(Pydantic BaseModel) |
|resolve_throttle(...)|resolve_penstock(...)|The legacy factory-callable form still works on
register_host_penstock(it accepts either aPenstockinstance or
a zero-arg callable returning one), solambda: FixedIntervalThrottle(...)
ports cleanly by changing the inner class name.Bug fix included:
BurstPenstock's refill logic now does an explicit
Nonecheck onbucket_last_refill_atinstead ofor now, so a
legitimate0.0watermark no longer silently erases the refill
window (latent in the previousBurstThrottlesince v1.0). -
Removed
watershed.jsonlegacy aliases — the v1.2.0
dependency_mode(top-level) and"mode"(per-edge) aliases for
gate_modeare gone. Passing them now raisesValueErrorwith
inline migration guidance instead of silently warning.Migration:
// Before v1.3.0: {"shape": "chain", "dependency_mode": "hard", "currents": [...]} // After v1.3.0: {"shape": "chain", "gate_mode": "hard", "currents": [...]} // Per-edge: {"shape": "custom", "edges": [{"from": "a", "to": "b", "mode": "hard"}]} // becomes: {"shape": "custom", "edges": [{"from": "a", "to": "b", "gate_mode": "hard"}]}The valid values (
"hard"/"soft"/"weir") are unchanged. -
Narrowed
Gate/SurgeBarrier/Penstock/Spillwaymethod
signatures. Strategies no longer accept the fullTideweaver
scheduler as their first argument — they read narrowGateContext
/SurgeContextvalue types instead. Tightens the FlowControl ↔
scheduler boundary so subclasses can be unit-tested without a real
scheduler. Affected:Gate.gate_reason(scheduler, dependent, up_name, now)→
Gate.gate_reason(ctx: GateContext)SurgeBarrier.is_tripped(scheduler, dependent, up_name, now)→
SurgeBarrier.is_tripped(ctx: SurgeContext)Penstock.consume_reason(scheduler, edge_state, flow, now)→
Penstock.consume_reason(edge_state, flow, now)Spillway.overflow(scheduler, edge, displaced_wave)→
Spillway.overflow(edge, displaced_wave, overflow_count)SignalPenstock.rate_fn(scheduler, edge_state, now) -> float→
rate_fn(edge_state, now) -> float
Most users never override these; the change is invisible. Users
with custom Gate/Penstock/Spillway subclasses orrate_fn
callables update their signatures (drop the first scheduler arg).
Added
register_host_throttlepromoted to package top-level.
from incorporator import register_host_throttleworks; the
submodule pathincorporator.io.throttle.register_host_throttle
continues to work and is the same callable. New entry in
docs/api_atlas.mdwalks the registration API
side-by-side with the existingresolve_throttleresolver.
Internal
incorporator/observability/tideweaver/architect.pyroutes
Penstock tier-1 (host-aware) recommendations through the live
known_host_rates()view rather than the import-time
_KNOWN_API_RATE_LIMITSshim. Behavior unchanged for users who
register hosts; tier-1 falls silent for users who don't.
Breaking Changes
- User-supplied callables in `calc`, `calc_all`, `pluck`, `link_to`, `link_to_list`, and `split_and_get` are no longer invoked for garbage input values (None, "", "N/A", etc.). Migration: remove explicit null guards from lambdas.
- Custom Gate/Penstock/Spillway subclasses or `rate_fn` callables must drop the first scheduler argument.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
Track Incorporator, Turn any API/File into typed Python graph with pipeline
Get notified when new releases ship.
Sign up freeAbout Incorporator, Turn any API/File into typed Python graph with pipeline
All releases →Related context
Related tools
Beta — feedback welcome: [email protected]