You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The PR adds bounded override persistence, overdue-slot processing, and an explicit API for stopping scheduler-generated continuations while preserving authored timeline entries.
Models persistence as either indefinite or a bounded number of successful applications.
Distinguishes authored entries from scheduler-generated continuations and compares concrete resolved account identities.
Updates the Rust RPC surface, endpoint metadata, generated Node types, handwritten SDK API, documentation, and regression coverage.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Filename
Overview
crates/core/src/surfnet/svm.rs
Implements bounded persistence, overdue claims, resolved-target identity, authored/continuation separation, atomic queue planning, and explicit continuation cancellation with extensive regression tests.
crates/core/src/rpc/surfnet_cheatcodes.rs
Exposes the stop-persistence RPC with optional PDA seed values and validates its wire metadata.
crates/types/src/scenarios.rs
Replaces the boolean persistence flag with a backward-compatible boolean-or-bounded-window representation and scheduler-only continuation metadata.
crates/sdk-node/surfpool-sdk/kit/types/api.ts
Adds the complete typed stop-persistence method with the bigint removal count expected by the SDK transport.
Updates the generated Node contract to accept boolean and bounded persistence.
crates/types/src/rpc_endpoints.json
Registers the stop-persistence endpoint and its four-argument contract.
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Authored override becomes due] --> B[Resolve concrete account]
B -->|Unresolved or application fails| C[Retry without consuming bounded slot]
B -->|Application succeeds| D{Persistence remains?}
C --> E[Queue re-armed continuation]
D -->|Yes| E
D -->|No| F[Stop]
E --> G{Authored transition for same target?}
G -->|Yes| H[Preserve authored transition and suppress old continuation]
G -->|No| A
I[stopPersistingOverride] --> J[Remove matching re-armed continuations only]
J --> F
High — “Stopping” an active persisted override does not work.svm.rs:4300 only replaces overrides queued for the newly registered base slot. After an override has run, its persisted copy is queued at base_slot + 1; re-registering with persist: false schedules/materializes a one-shot at the current base slot but leaves that future copy intact. It resumes next slot. Cancellation should also locate/remove or replace future queued copies. The test only cancels before initial materialization, so it misses this case.
Medium — { "slots": 0 } still applies once.scenarios.rs:518 treats every Slots value as enabled. Although zero does not re-arm, the initially scheduled override is still applied, contradicting “applies in N slots total.” Reject zero during deserialization/construction or define/document it as one application.
High — cancellation misses re-armed copies (svm.rs:4300): registration replaces matches only at the newly calculated slot. A persisted copy already queued in another future slot survives and continues re-arming. Cancellation must remove/replace matching queued copies across relevant future slots.
Medium — generated Node contract is stale (OverrideInstance.ts:41): persist remains boolean, so typed clients cannot use { slots: number }. Regenerate and commit the bindings.
Low — zero-slot windows are accepted (scenarios.rs:504): { "slots": 0 } is enabled and still applies once, contradicting the stated total-slot semantics. Reject zero during deserialization or define/document it explicitly.
[P1] Cancellation only replaces the bucket computed during re-registration (svm.rs:4310). If the override already re-armed into the next slot, registering persist: false for the current slot leaves that future copy alive. The cancellation test only cancels before initial materialization, so it misses this case. Remove matching queued copies across future slots or explicitly target the queued slot.
[P1] The Node SDK contract remains persist?: boolean (OverrideInstance.ts:41), so TypeScript consumers cannot pass { slots: N }. Regenerate and commit the bindings.
[P2] { "slots": 0 } is accepted and considered enabled (scenarios.rs:515), causing one application despite requesting zero slots. Reject zero during deserialization/schema validation or define it as disabled.
High — Future cancellation can be overwritten (svm.rs:4528): cancellation removes matching copies only after the cancellation slot. An earlier persistent copy can later re-arm into that slot, replace the persist: false entry, and continue indefinitely. Preserve the cancellation as a tombstone or cap/remove earlier matching persistent copies.
Medium — Clock jumps can apply one override multiple times (svm.rs:2806): overdue buckets are concatenated without deduplicating (id, account, template_id). Matching copies from different slots therefore execute multiple times at the reached slot, contrary to the documented one-application-per-slot behavior. Deduplicate with the latest scheduled entry winning before materialization.
P1 – Future cancellation stops persistence immediately (svm.rs:4542): scheduling persist: false at relative slot N removes the armed copy for the intervening slots. The override therefore disappears until slot N, rather than persisting through N-1. Preserve/re-arm copies before the cancellation slot.
P2 – Failed registration can partially apply (svm.rs:4520): validation occurs while mutating storage. If a later override has slots: 0, earlier overrides remain scheduled although the RPC returns an error. Validate all overrides and absolute-slot calculations before making changes.
P2 – TypeScript bounded slots lose u64 precision (OverrideInstance.ts:33): Rust accepts u64, but the generated contract uses number, unlike scenarioRelativeSlot’s number | bigint. Large windows can be silently rounded. Use a bigint-safe representation or validate against Number.MAX_SAFE_INTEGER.
svm.rs:4558remove_queued_copies_elsewhere still deletes intentional matching timeline entries created by earlier register_scenario calls. scenario_slots protects only the current registration, so registering a later same-identity entry can silently remove an earlier scheduled action.
svm.rs:4558 A future-dated cancellation removes the already-armed next-slot copy immediately. Consequently, persistence stops at registration time rather than continuing until the cancellation’s scenario_relative_slot; the existing test only checks that nothing remains after cancellation and misses the skipped intermediate applications.
svm.rs:2825 Forward-jump deduplication collapses intentional same-identity timeline steps from multiple overdue slots. If those steps modify different fields, dropping all but the latest produces a different final account state than applying the timeline in order.
P1 — Intentional timeline entry is still overwritten (svm.rs:3225): Re-arming matches any same-identity entry in the next slot, including re_armed == false, then replaces it. A persistent step immediately before a deliberate same-ID transition deletes that transition. Only replace an existing entry when queued.re_armed; otherwise append the continuation.
P2 — Internal reArmed flag is client-controlled (scenarios.rs:580): The field is hidden from schemas/serialization but still deserializes from RPC input. A caller can submit "reArmed": true, causing deliberate entries to be treated as scheduler continuations and removed/deduplicated. Add skip_deserializing or reset it to false during registration.
svm.rs:3007Forward jumps bypass future cancellation/transition. All overdue buckets are removed before processing. An earlier persistent entry therefore re-arms into target_slot + 1 while its later intentional one-shot is no longer queued for collision detection. The one-shot then runs but leaves that continuation behind, so the old value returns next slot. Suppress re-arming when a later claimed intentional entry has the same identity, or remove the generated continuation when processing that entry. Add a jump-over-cancellation regression test.
P1 – Registration is not atomic on storage errors (svm.rs): each override removes continuations and writes immediately. If a later get/store fails, earlier mutations remain despite the RPC returning an error. Stage all affected buckets and commit transactionally, or roll back on failure.
No additional actionable issues found in the reviewed range.
The reported stop_persisting_override bug is already fixed at feed639: cancellation filters on re_armed, preserving authored future timeline entries, with regression coverage in test_stop_persisting_removes_only_continuations_without_reapplying.
Medium — PDA cancellation is ambiguous (svm.rs:4775): stop_persisting_override compares unresolved AccountAddress recipes. Property-backed PDA continuations sharing a recipe but resolving to different accounts are all removed together; passing the resolved pubkey removes none. Accept values or a resolved pubkey and compare concrete addresses.
Low — New RPC missing from endpoint metadata (rpc_endpoints.json:837): surfnet_stopPersistingOverride is registered and added to generated method names, but absent from the discoverable RPC endpoint contract. Add its parameters and return type.
svm.rs:4681 — Disabled overrides (enabled: false) still remove matching re-armed continuations during registration. They can therefore unintentionally stop an active persistent override despite being skipped during materialization. Only enabled entries should supersede/cancel continuations; the overdue-batch check at line 3094 has the same issue.
svm.rs:4681 Registration is not atomic after validation. Each override removes continuations and writes storage independently; a later storage failure returns an error after earlier mutations remain. Stage all affected buckets first, then commit atomically or roll back on failure.
The reported SDK return-type issue is already fixed to bigint at this head. Tests could not run because Rustup attempted to write outside the sandbox.
P1 — crates/core/src/surfnet/svm.rs:2877: overdue buckets are removed one-by-one using take(...)?. If a later take fails, previously removed buckets are lost without being processed or restored. Stage all removals atomically, or restore already-claimed buckets before returning the error.
Tests couldn’t run because Rustup attempted to write under the read-only /home/runner/.rustup.
svm.rs:3103: The continuation is queued before applying the current override. If token forging or set_account fails at lines 3198/3206, ? exits without restore_unprocessed, leaving the current entry consumed while its continuation remains scheduled. Other failure paths restore the current entry, potentially alongside that continuation. Apply first and re-arm only after success, or atomically roll back both queue mutations on failure.
[P1] svm.rs:3318: Bounded persistence decrements after failed/skipped writes. Missing accounts, absent IDLs, forge errors, and set_account failures all reach rescheduling, so { slots: 3 } may expire without three successful applications. Track application success and only decrement bounded persistence after a successful write; retries should retain the remaining count.
Tests could not run because rustup attempted to write under the read-only home directory.
The persistence, rollback, PDA identity, overdue-slot, RPC, and SDK paths appear consistent. I couldn’t run tests because rustup’s temp directory is read-only in this environment.
failfmi
changed the title
Feat/scenarios/bounded persist
feat(scenarios): bounded override persistence
Sep 9, 2026
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
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.
Greptile Summary
The PR adds bounded override persistence, overdue-slot processing, and an explicit API for stopping scheduler-generated continuations while preserving authored timeline entries.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Authored override becomes due] --> B[Resolve concrete account] B -->|Unresolved or application fails| C[Retry without consuming bounded slot] B -->|Application succeeds| D{Persistence remains?} C --> E[Queue re-armed continuation] D -->|Yes| E D -->|No| F[Stop] E --> G{Authored transition for same target?} G -->|Yes| H[Preserve authored transition and suppress old continuation] G -->|No| A I[stopPersistingOverride] --> J[Remove matching re-armed continuations only] J --> FReviews (21): Last reviewed commit: "Merge branch 'feat/scenarios/raw-layout'..." | Re-trigger Greptile