Skip to content

Commit 34d3f88

Browse files
dzhelezovclaude
andcommitted
docs: final-review nits — INV-15 range refs, corrected INV-13 citation, honest test counts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 75b7139 commit 34d3f88

6 files changed

Lines changed: 7 additions & 7 deletions

File tree

HOW-IT-WORKS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ This is proven, not asserted. The differential harness ([`harness/diff`](harness
121121

122122
## Where the code lives
123123

124-
The layer is organised around explicit, provable **invariants** — a functional core (pure, property-tested) behind an imperative shell. See [`portal/INVARIANTS.md`](portal/INVARIANTS.md) for the catalog (INV-1…INV-14) that ties doc ⟷ code ⟷ test together.
124+
The layer is organised around explicit, provable **invariants** — a functional core (pure, property-tested) behind an imperative shell. See [`portal/INVARIANTS.md`](portal/INVARIANTS.md) for the catalog (INV-1…INV-15) that ties doc ⟷ code ⟷ test together.
125125

126126
| File | What it holds |
127127
|---|---|

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ The free public Portal is ideal for trying the fork and for development, but sha
9999
## Learn more
100100

101101
- [**How it works**](HOW-IT-WORKS.md) — the design story: why a streamed range beats per-topic RPC lookups, the historical-sync seam, the shared read-ahead controller, factory discovery over ranges, and where the single-thread ceiling honestly is. Operational reference: [`portal/INTEGRATION.md`](portal/INTEGRATION.md).
102-
- [**Invariants**](portal/INVARIANTS.md) — the catalog (INV-1…INV-14) the `portal/` layer is built around: each invariant's statement, how it's enforced, where it's checked at runtime, and the property test that proves it.
102+
- [**Invariants**](portal/INVARIANTS.md) — the catalog (INV-1…INV-15) the `portal/` layer is built around: each invariant's statement, how it's enforced, where it's checked at runtime, and the property test that proves it.
103103
- [**Observability**](portal/INTEGRATION.md)`PORTAL_METRICS_FILE` writes a per-chain JSON metrics file (throughput, bytes, errors, RPC-fallback); `PORTAL_GATE_LOG=1` logs the adaptive controller.
104104
- **Portal-native realtime** (experimental) — realtime runs on your RPC by default; set `PORTAL_REALTIME=stream` to serve the tip from the Portal's fork-aware `/stream` instead of RPC.
105105
- [**Versioning & releases**](PUBLISHING.md)`@subsquid/ponder@<ponder-version>-sqd.<rev>`, generated from upstream Ponder + a per-version patch.

portal/INTEGRATION.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ Both runs indexed the identical 28.4M events; only the configuration differed. T
152152

153153
## Where the code lives
154154

155-
The `portal/` layer is a functional core behind an imperative shell, organised around explicit invariants (see [`INVARIANTS.md`](INVARIANTS.md), INV-1…INV-14):
155+
The `portal/` layer is a functional core behind an imperative shell, organised around explicit invariants (see [`INVARIANTS.md`](INVARIANTS.md), INV-1…INV-15):
156156

157157
- `portal.ts` — orchestration shell (`createPortalHistoricalSync`): chunk cache, stash, delegation, seam methods.
158158
- `portal-config.ts` / `portal-errors.ts` / `portal-invariant.ts` — frozen config (INV-14), typed errors, runtime checks.

portal/INVARIANTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Legacy `C#` tags from the pre-refactor comments are mapped where they existed.
2828
| **INV-10** | **Realtime chain linkage.** The unfinalized list is strictly increasing in number and parentHash-linked; `reconcile` yields reorgs whose `reorgedBlocks` are exactly the trimmed suffix; `gap` resets; `finalize` is monotonic and never at/below the startup boundary. | Realtime reorg/finalize handling must be provably correct to never regress persisted checkpoints. | Pure `reconcile`/`takeFinalized` in `portal-realtime.ts`; the wire suppresses finalize ≤ startup/lastFinalized. | `portalRealtimeEvents` asserts the parentHash link + increasing number on every append (O(1) tripwire — reconcile guarantees it). | `portal-realtime.test.ts` (reconcile/takeFinalized cases); `portal-realtime-wire.test.ts` (finalize monotonic + generator flows). |
2929
| **INV-11** | **Merge equivalence.** `mergeLogRequests(reqs)` matches exactly the same logs as `reqs` (∀log: matches(reqs) ⟺ matches(merged)); the merge key includes the FULL (address set, topic1, topic2, topic3) tuple; batches respect `PORTAL_MAX_ADDRESSES`; body-size guard at the client. | An N-event contract emits N requests repeating the child list; merging shrinks the body without changing the result set. | Pure `mergeLogRequests` (topic0 union, undefined-absorbs-all). | Body-size guard in `portal-client`. | `portal-filters.test.ts` — "INV-11: mergeLogRequests matches exactly the same logs as the originals" (the model covers address + topic0..3), "INV-11: requests differing ONLY in topic2/topic3 are NOT merged (key includes every topic)". |
3030
| **INV-12** | **Exactly-once insertion / stash lifecycle.** Per interval: logs inserted once (range-data), blocks/txs/receipts/traces once (block-data); a stash entry is created then consumed exactly once; `delegated` entries consumed once; stash stays bounded. An upstream range RETRY legitimately re-creates an entry: production (`on`) keeps the pre-refactor overwrite semantics (debug-logged); `strict` makes the double-set loud. | Double insertion corrupts; a leaked stash entry leaks memory; but a benign upstream retry must not crash production. | `portal.ts` inserts logs in range-data, block-data in block-data; stash `set` then `delete`. | `invariantStrict("INV-12", …)` on set (strict mode only); `delete` on take. | `portal-shell.test.ts` — "INV-12: the stash is consumed exactly once — a second syncBlockData returns undefined without re-inserting", "INV-12: 'on' mode keeps overwrite semantics for an upstream range retry; 'strict' makes it loud"; `portal.test.ts` (insert-exactly-once round-trips). |
31-
| **INV-13** | **Progress & cache bounds.** The stream cursor strictly advances BY CONSTRUCTION (`cursor = last + 1 ≥ cursor + 1` per batch; a 204 terminates — no runtime guard could ever fire, so none exists); the chunk cache never exceeds the in-service span + READAHEAD; **a rejected chunk promise is evicted immediately** so a later interval retries instead of replaying the cached rejection (fixes **G1**); **a FRONTIER chunk truncated at a then-lower finalized head is EXTENDED (tail-only stream + merge) before being served past its `coveredTo`** — never served stale (head-boundary bug found in review; fixed on main by PR #5/802ceed and RE-IMPLEMENTED here with behavior parity — main's own #5 regression test passes unchanged). A failed extend evicts the whole entry — never leaves the optimistic high-water in place. | A cached rejection permanently poisons a chunk; an unbounded cache OOMs; a blind idx cache hit over a head-truncated chunk marks an interval synced over a silent gap. | `readAheadPlan`/`evictionPlan` bound the cache (pure); `dataChunk` records `coveredTo` per cache entry and extends via the shared `runStreams` (append-only merge; rows accounted through the same per-fetch token); `p.catch`/`extended.catch` evict + free exactly once. | `dataChunk`'s INV-1/INV-9/INV-3 asserts bracket every fetch; `portal-client.ts` documents progress-by-construction at the cursor advance. | `portal-chunks.test.ts` — "INV-13: read-ahead plan is bounded (≤ readahead), within raEnd, and depth-1-only when saturated", "eviction plan…"; `portal-client.test.ts` — "INV-13: the cursor strictly advances and the stream terminates"; `portal-shell.test.ts` — "G1: a failed chunk is evicted (rows freed) and a later call refetches…", "regression: frontier chunk truncated at a lagging Portal head is EXTENDED…" (main's #5 test, unchanged) and "frontier extend streams ONLY the newly-finalized tail…". |
31+
| **INV-13** | **Progress & cache bounds.** The stream cursor strictly advances BY CONSTRUCTION (`cursor = last + 1 ≥ cursor + 1` per batch; a 204 terminates — no runtime guard could ever fire, so none exists); the chunk cache never exceeds the in-service span + READAHEAD; **a rejected chunk promise is evicted immediately** so a later interval retries instead of replaying the cached rejection (fixes **G1**); **a FRONTIER chunk truncated at a then-lower finalized head is EXTENDED (tail-only stream + merge) before being served past its `coveredTo`** — never served stale (head-boundary bug found in review; fixed on main by PR #5/802ceed and RE-IMPLEMENTED here with behavior parity — main's own #5 regression test passes unchanged). A failed extend evicts the whole entry — never leaves the optimistic high-water in place. | A cached rejection permanently poisons a chunk; an unbounded cache OOMs; a blind idx cache hit over a head-truncated chunk marks an interval synced over a silent gap. | `readAheadPlan`/`evictionPlan` bound the cache (pure); `dataChunk` records `coveredTo` per cache entry and extends via the shared `runStreams` (append-only merge; rows accounted through the same per-fetch token); `p.catch`/`extended.catch` evict + free exactly once. | `dataChunk`'s INV-1/INV-9/INV-3 asserts bracket every fetch; `portal-client.ts` documents progress-by-construction at the cursor advance. | `portal-chunks.test.ts` — "INV-13: read-ahead plan is bounded (≤ readahead), within raEnd, and depth-1-only when saturated", "eviction plan…"; `portal-client.test.ts` — "INV-13: the cursor strictly advances and the stream terminates"; `portal.test.ts` — "regression: frontier chunk truncated at a lagging Portal head is EXTENDED…" (main's #5 test, verbatim); `portal-shell.test.ts` — "G1: a failed chunk is evicted (rows freed) and a later call refetches…" and "frontier extend streams ONLY the newly-finalized tail…". |
3232
| **INV-14** | **Config validity.** All `PORTAL_*` env is parsed ONCE into a frozen, validated `PortalConfig`; garbage values fail fast with actionable messages (previously `Number("abc") → NaN` silently poisoned the chunk grid). | A silent NaN in the chunk grid corrupts every downstream computation. | `loadPortalConfig` validates + `Object.freeze`s once. | `PortalConfigError` at parse. | `portal-config.test.ts` — "INV-14: garbage numeric → loud PortalConfigError (not silent NaN)" + the defaults/bounds/checks matrix. |
3333
| **INV-15** | **Factory-children persistence.** A factory interval marked cached ⇒ its children are persisted in the sync store: newly-discovered children are queued and flushed via `syncStore.insertChildAddresses` inside the SAME `syncBlockRangeData` whose transaction ponder commits with `insertIntervals`; the flush is scoped to children whose creation block ∈ the interval (cross-interval children stay queued for their owning interval); a failed flush restores the queue and fails the interval loud; restart-preloaded children re-discover at `prev ≤ bn` and are NEVER re-queued (no double-insert). Ported from PR #2 into this architecture. | Ponder's core marks `requiredFactoryIntervals` cached after every interval and on startup loads children ONLY from the store — an unpersisted child means every restart of a factory app silently drops that child's events. | `portal-discovery.ts` queues inside the min-merge guard (`prev === undefined \|\| prev > bn`) and exposes `takePendingInRange`/`restorePending`; `portal.ts` flushes after the interval's chunks resolve, before read-ahead/assemble/insert. | A flush failure rethrows (ponder rolls the interval back). | `portal.test.ts` — "regression: discovered factory children are persisted via insertChildAddresses in the SAME syncBlockRangeData call", "regression: a failing insertChildAddresses fails LOUD and the children re-flush on the next call…", "restart: a new sync seeded ONLY with the persisted children still fetches child logs…", "regression: each syncBlockRangeData persists ONLY its interval's children…", "regression: a post-flush insertLogs failure fails the interval LOUD…", "regression: a child already known from the store is NOT re-flushed when discovery re-runs live". |
3434

portal/portal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* assemble→ portal-assemble metrics → portal-metrics transforms → portal-transform
1414
*
1515
* The public seam is FROZEN: `createPortalHistoricalSync({common, chain, rpc, childAddresses,
16-
* eventCallbacks}) : HistoricalSync`. See portal/INVARIANTS.md for the invariant catalog (INV-1…INV-14).
16+
* eventCallbacks}) : HistoricalSync`. See portal/INVARIANTS.md for the invariant catalog (INV-1…INV-15).
1717
*/
1818
import type { Common } from '@/internal/common.js';
1919
import type {

versions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"upstream": "ponder",
44
"policy": "Version = `<ponder-version>-sqd.<rev>`: @subsquid/ponder@X.Y.Z-sqd.N is built from ponder@X.Y.Z + the Portal layer (rev N). The ponder version stays visible; bump `-sqd.<rev>` for a fork-side fix on the SAME ponder version (npm permanently retires an unpublished version number, so a plain mirror like 0.16.6 can't be re-cut after a bad publish). Published with `--tag latest` so the prerelease-form version is the default `npm install`. The bin stays `ponder` (drop-in). We cut a release only for ponder versions a client needs; `compat.tested` records which versions the seam is proven against.",
55
"layer": "portal/",
6-
"nextFromMain": "0.16.6-sqd.2 — the invariant-first re-architecture of portal/ (functional core + imperative shell, INV-1…INV-14, portal/INVARIANTS.md; fixes G1/G2/G3). Behavior + seam frozen; all 53 existing tests pass byte-unchanged plus the new property suite. DO NOT publish from this PR — publish is a separate, deliberate step.",
6+
"nextFromMain": "0.16.6-sqd.2 — the invariant-first re-architecture of portal/ (functional core + imperative shell, INV-1…INV-15, portal/INVARIANTS.md; fixes G1/G2/G3). Behavior + seam frozen; all 60 pre-existing tests (incl. main's PR #2/#5 fixes) pass unchanged, plus a 72-test property/regression suite (132 total). DO NOT publish from this PR — publish is a separate, deliberate step.",
77
"seam": "HistoricalSync.syncBlockRangeData / syncBlockData (interval-scoped) — identical in shape across the tested range",
88
"versions": [
99
{
@@ -20,7 +20,7 @@
2020
"rev": 1,
2121
"status": "verified",
2222
"published": true,
23-
"note": "Seam identical to 0.16.6 — the 0.16.6 wiring patch applies cleanly, builds, and passes all 53 tests on ponder@0.15.17."
23+
"note": "Seam identical to 0.16.6 — the 0.16.6 wiring patch applies cleanly, builds, and passes the full portal suite on ponder@0.15.17 (CI matrix)."
2424
}
2525
],
2626
"compat": {

0 commit comments

Comments
 (0)