Patch Changes
-
9f767cf: Add fast-check model-based and micro-target property tests (plus static analysis for unbounded retry loops, unconditional 409 cache busters, tail-position awaits, and error-path
#publishcalls) and fix client bugs uncovered by the new PBT suite:Stream / retry-loop fixes (uncovered by model-based PBT):
- Unconditionally create a new cache buster on every 409 response so that the follow-up request URL always differs from the pre-409 URL (prevents CDN infinite loops on cached 409s).
- Fix a parked stack-frame leak in
ShapeStream#startwhere awaiting a never-resolving live fetch retained the full error handler chain. - Add
EXPERIMENTAL_LIVE_SSE_QUERY_PARAMtoELECTRIC_PROTOCOL_QUERY_PARAMSsocanonicalShapeKeystrips it; previously the SSE and long-polling code paths produced divergent cache keys for the same shape. - Replace the raw 409 response body publish in
#requestShapewith a syntheticmust-refetchcontrol message so subscribers clear accumulated state rather than receiving stale data rows. - Bound the
onErrorretry loop at 50 consecutive retries so a brokenonErrorhandler can no longer spin forever.
Micro-target PBT fixes:
canonicalShapeKeycollapsing duplicate query paramsShape#processclobbering notifications on[up-to-date, insert]batchessubset__limit=0/subset__offset=0dropped on GET path due to truthiness check- Non-canonical JSON keys in
Shape#reexecuteSnapshotsdedup snakeToCamelcolliding multi-underscore columnsShape#reexecuteSnapshotsswallowing errors silentlySnapshotTrackerleaving stale reverse-index entries on re-add/removeShape#awaitUpToDatehanging forever on a terminally-errored stream
Shape notification contract fix:
Shape#processno longer notifies subscribers on data messages while the shape is stillsyncing(i.e. before the firstup-to-datecontrol message). Previously, the sync-service's initial response (offset=-1) could cause subscribers to fire with a partial view whilestream.lastSyncedAt()was stillundefined. Shape now follows the N1/N2 invariants documented inSPEC.md(Shape notification semantics).Shape#processno longer fires an intermediate empty-rows notification onmust-refetch. The status transitions back tosyncingand subscribers receive the post-rotation state on the nextup-to-date, matching the long-standingshould resync from scratch on a shape rotationintegration test.
-
b449f70: Bound the
onErrorretry loop to prevent unbounded retries and memory growth. WhenonErroralways returns a retry directive for a persistent error (e.g. a 400 from a misconfigured proxy), the client now limits consecutive retries to 50 before tearing down the stream and notifying subscribers. The counter resets on successful data (non-empty message batch or 204 No Content), so intermittent errors that recover do not accumulate toward the limit. -
690e25a: Fix permanently stuck expired shape handles in localStorage by adding self-healing retry. When stale cache retries are exhausted (3 attempts with cache busters), the client now clears the expired entry from localStorage and retries once without the
expired_handleparameter. Since the server never reuses handles (documented as SPEC.md S0), the fresh response will have a new handle and bypass stale detection. This prevents shapes from being permanently unloadable when a proxy strips cache-buster query parameters.