Repository navigation
feat(core): fix #268 #269 #270 #271 bounded cache, synthetic monitor,… - #297
Merged
BigDella merged 1 commit intoOct 18, 2026
Conversation
…ol#270 Parcel-Protocol#271 bounded cache, synthetic monitor, fuzz and concurrency tests Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@Oge8 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #268
Closes #269
Closes #270
Closes #271
PR Description
This PR resolves four core hardening issues: #270, #269, #268 and #271.
Each issue is a focused change under
core/with its own tests and docs. Nofeature slice, route or UI changed. Every change to existing code either adds
something new or only affects inputs that used to throw, so existing contracts
are preserved.
#270: Bounded cache with explicit invalidation and stale detection
Problem.
core/cachehad a stale detector (#236) but no cache for it toprotect. Nothing bounded memory, nothing gave callers a way to invalidate
entries, and nothing stopped an out-of-order Horizon response from overwriting
newer state.
How it works now.
createBoundedCache<V>()incore/cache/bounded-cache.ts:maxEntries. AMap's insertionorder is the recency order, so
get,set,invalidateandgetOrLoadareall O(1).
cache_expired. It is dropped whenread, so no timers are needed.
ledgerSequencethan the live entry returnscache_stale_write, so a projection can never move back to an older ledger.invalidate(key)drops one key andinvalidateWhere(predicate)drops a scope. Both also cancel any load inflight for those keys.
getOrLoadmakes concurrent misses for a key share one load.its value.
clear()flushes everything and requires theadmin:recoverypermission. Without it,clear()returnscache_unauthorized. Both outcomes emitcache.flushedtelemetry.snapshot()returns the existingCacheEntryshape, so
StaleProjectionDetector.inspectreports ledger drift, schema skewand orphans for this cache with no changes.
Why it matters. Horizon reads can be cached safely: memory stays bounded,
duplicate requests are coalesced, and stale or rolled-back values are never
served.
#269: Synthetic monitoring for the critical end-to-end journey
Problem.
tests/e2e/critical-journey.spec.tstests the operation-browserjourney only against mocked Horizon. An upstream break (wrong network, changed
payload, broken pagination) was invisible until users reported it.
How it works now.
core/health/synthetic.tssends the journey's exactrequests to live Horizon (
order=desc,limit=20, cursor = lastpaging_token):horizon.root(GET /): checks thatnetwork_passphrasematches thenetwork and that ingestion lag (
core_latest_ledger − history_latest_ledger)is at most 5 ledgers.
operations.discover(GET /operations?order=desc&limit=1): picks acurrently active
G…account. No fixture account is needed, so testnetresets cannot break the probe.
account.operations: page one must be non-empty, with numeric, strictlydescending paging tokens.
account.operations.older: follows the cursor when page one is full,just as the "Load more" button does. Every token must be below the cursor.
This matches Horizon's own
id < cursor ORDER BY id descquery.retried up to 3 attempts with exponential backoff. A
Retry-Afterheader ishonoured (RFC 9110: seconds or an HTTP-date), and no wait exceeds 10 s.
network_mismatch,malformed_payload,http_status,no_activityandpagination_brokenfail at once, becauseretrying cannot fix them.
unhealthy: a step failed.degraded: the run passed but needed a retry, took longer than 5 s, oringestion lagged.
healthy: everything else.admin:healthpermission(the
serviceandmaintainerroles). Viewers and unknown roles are refusedbefore any request is made.
npm run synthetic:monitor -- --network testnet|mainnet [--json]..github/workflows/synthetic-monitor.ymlruns it every 30 minutes for bothnetworks.
Why it matters. Upstream regressions in the most important user journey
are detected on a schedule, with a correlation id for diagnosis, before users
notice them.
#268: Fuzz coverage for malformed payloads and boundary numeric values
How it works now.
core/__tests__/fuzz.test.tsis a seeded fuzz suite. Ituses a mulberry32 PRNG, runs 1,000 inputs per property, can be reproduced with
FUZZ_SEED, and adds no new dependency. It covers:922337203685.4775807/-922337203685.4775808), including the one-stroop overflows, and exactround-trips of random int64 values.
prototype keys.
The suite found three real defects, each fixed with a minimal change:
hasPermissionthrew aTypeErrorfor any unknown role. A mistypedUTIX_USER_ROLEcrashed every permission check, andtoString/__proto__resolved to functions.Object.hasOwnguard; unknown roles are deniedcanonicalizethrew on BigInt and overflowed the stack on cyclic payloads, soIdempotencyStore.beginthrew instead of returning aResult.10nand a cycle as#cycle; neither is valid JSON, so neither collides with a real valueparseJsonRpcResponseacceptedNaNand fractional error codes.Why it matters. Authorization and replay protection no longer crash on
unexpected input. Every input that previously worked keeps its exact behaviour,
so persisted idempotency digests remain valid.
#271: Concurrency tests for duplicate submissions and stale versions
Problem.
NotificationPreferences.versionwas incremented but never checked. A writeracting on a stale read silently overwrote a newer write; for example, a toggle
computed from a stale
mutedvalue re-muted a user who had just unmuted.How it works now.
idempotency.concurrency.test.tsholds work openon gates and asserts the exact interleaving, with no sleeps. It shows that:
abandon;updatePreferences(recipient, patch, { expectedVersion })is now an optional compare-and-set. On a version mismatch it throws
StalePreferencesVersionError(withrecipient,expectedVersionandactualVersion) and leaves storage untouched. The caller re-reads andretries.
last-write-wins behaviour, so no existing caller changes.
Why it matters. Double clicks, retries and multi-tab use cannot repeat side
effects or silently lose a newer preference change.
Verification of Stellar specifics
Every Stellar detail was checked against the official Stellar docs and Horizon's
source (
stellar/stellar-horizon):GET /field names;paging_tokenis the decimal operation ID;source_accountis always theG…account.The probe ran live and reported HEALTHY on both testnet and mainnet.
Changed
core/cache/bounded-cache.ts, exported fromcore/cache/index.ts: anO(1) LRU with TTL, a ledger-regression guard, single-flight loads, explicit
invalidation, and a flush that requires
admin:recovery.core/cache/__tests__/bounded-cache.test.ts(18 tests).core/health/synthetic.ts, a live probe of the critical journey withretry/backoff,
Retry-Aftersupport and anadmin:healthgate.scripts/synthetic-monitor.mjsand thesynthetic:monitornpm script..github/workflows/synthetic-monitor.yml(every 30 minutes, testnetand mainnet).
core/health/__tests__/synthetic.test.ts(23 tests).core/__tests__/fuzz.test.ts, a seeded fuzz suite.hasPermissionincore/auth/types.ts(unknown roles are denied).canonicalizeincore/idempotency/idempotency.ts(BigInt and cycles).parseJsonRpcResponseincore/rpc/client.ts(integer error codes).expectedVersiontoupdatePreferences, andStalePreferencesVersionError, incore/notifications/store.tsandcore/notifications/index.ts.core/idempotency/__tests__/idempotency.concurrency.test.ts(9 tests).core/notifications/__tests__/preferences.concurrency.test.ts(8 tests).docs/STALE_CACHE_REPAIR.md;docs/OPERATIONAL_HEALTH.md;docs/IDEMPOTENCY.md;docs/FUZZING.md.Testing
Run on Node 24.11.0:
The fuzz suite was confirmed to detect the #268 defects: with the three fixes
reverted, 4 fuzz tests fail.
Scope Notes
read-only against public Horizon.
package-lock.jsonis unchanged.expectedVersionis optional;JSON-RPC spec forbids;
mainwere left as they are, because they areunrelated to these issues:
verify:features;tscerrors in other files;npm cilockfile drift.