feat(server): let a host read what the last renewal pass achieved - #447
Open
TarikGul wants to merge 4 commits into
Open
feat(server): let a host read what the last renewal pass achieved#447TarikGul wants to merge 4 commits into
TarikGul wants to merge 4 commits into
Conversation
The host READMEs already cover the cadence, the grace window and the cold-start session problem, but not what to tell the operating system once a pass returns. That decision lived only in the tracking issue, so each host would have arrived at its own mapping from the same report. Every target registered or already allocated is success. A failure is worth another attempt, and the grace window means it can wait for the next opportunistic wake rather than a tight loop. Exhaustion is success as far as the scheduler is concerned, because retrying cannot free a slot, only time or a replacement can, and a retry there only spends background budget. Exhaustion still reaches nobody who can act on it, which is called out where a host would otherwise assume silence means success. Also names the two layers either side of the scheduled wake, a pass on session activation and on-demand allocation, so a missed wake reads as ordinary rather than as the only line of defence.
The in-process renewal loop computed a report on every tick and dropped it, so a host that drives the loop rather than calling a pass directly had no way to learn that a period filled up and an allowance went unrenewed. Exhaustion was a log line and nothing else. `last_statement_renewal_report` returns the most recent pass from either path, exported on both native types and forwarded by both host shells. `None` means no pass has run, which is not the same as a healthy one. A tick that could not run leaves the previous report in place, since the last thing known beats nothing. Recording and logging move into `absorb_tick`, separated from the loop body so the wiring is reachable from a test. A loop that logs but forgets to record is the regression worth catching, and testing the container alone did not catch it. Also documents how a host answers its scheduler once a pass returns: every target registered or already allocated is success, a failure is worth another attempt on the next opportunistic wake, and exhaustion is success as far as the scheduler is concerned because retrying cannot free a slot. It still means an allowance went unrenewed, so it belongs in front of a person rather than only in a log. Names the two layers either side of the scheduled wake as well, so a missed wake reads as ordinary rather than as the only line of defence.
The committed copy was generated while the two renewal doc comments were attached to the wrong methods, which uniffi carries into the generated interface and into its method checksums. Regenerating from the corrected source moves the docs onto the methods they describe and settles the checksums at values both stable and nightly reproduce.
filvecchiato
approved these changes
Aug 20, 2026
| ) -> Result<crate::runtime::statement_allowance::renewal::StatementRenewalReport, String> { | ||
| allowance_renewal::renew_now(&self.services, self).await | ||
| let report = allowance_renewal::renew_now(&self.services, self).await?; | ||
| self.renewal.record_report(&report); |
Collaborator
There was a problem hiding this comment.
No test reaches this line: deleting it leaves all 643 green, while deleting the same call in absorb_tick fails three. Same getter, only the loop path guarded
The direct entry point recorded its report alongside returning it, and no test reached that line: deleting it left the suite green, while deleting the same call in `absorb_tick` fails three. Driving it would need a `SigningHost` with real entropy, storage and chain services, which is why it was never covered. It is also redundant. A caller of `renew_statement_allowances` already holds the report; the loop is what has nowhere to return one to. So the recording belongs to the loop alone, and the getter now says that rather than claiming either path. Corrects the same claim on both host shells, both host guides and the runtime export, and regenerates the Swift bindings the merge left conflicting.
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.
The in-process renewal loop computes a report on every tick and has no caller to hand it to, so a host that drives the loop rather than calling a pass directly cannot learn that a period filled up and an allowance went unrenewed. Exhaustion
reaches a log line and stops there.
last_statement_renewal_reportreturns the most recent pass from either path. It is exported on both native types and forwarded by both host shells, including the Swift protocol.Nonemeans no pass has run, which is not the same as a healthy one. A tick that could not run at all leaves the previous report in place, since the last thing known beats nothing.Recording and logging live in
absorb_tick, separate from the loop body so the wiring is reachable from a test. A loop that logs but forgets to record is the regression worth catching, and a test that only exercises the state container does not catch it.How a host answers its scheduler
The mapping from a report to a scheduler result is documented in both host READMEs and in the renewal module docs, because it is a decision every host would otherwise make separately from the same data:
RegisteredorAlreadyAllocated: success.Failed: worth another attempt, on the next opportunistic wake rather than a tight loop.slots_exhausted: success as far as the scheduler is concerned, because retrying cannot free a slot, only time or a replacement can. It does mean an allowance went unrenewed, so it belongs in front of a person rather than only in a log, which is what the new getter makes possible for a loop-driven host.The docs also name the two layers either side of the scheduled wake, a pass on session activation and on-demand allocation, so a missed wake reads as ordinary.
Resources.StmtStoreGraceWindowis 48 hours on top of a 24 hour period, so the deadline is soft by days.Scope
Products still receive
NotAvailablefor exhaustion, the same as for a chain that does not offer the resource. Distinguishing them needs a newAllocationOutcomevariant, which lands in the only payload version that exists and breaks decode for clients that do not know the index. That belongs in the deliberate breaking-change window #259 is waiting for.