Skip to content

feat(server,cli): auto-renew statement-store allowances#308

Draft
pgherveou wants to merge 174 commits into
headless-hostfrom
pg/statement-allowance-renewal
Draft

feat(server,cli): auto-renew statement-store allowances#308
pgherveou wants to merge 174 commits into
headless-hostfrom
pg/statement-allowance-renewal

Conversation

@pgherveou

Copy link
Copy Markdown
Collaborator

Why

Statement-store allowances are registered per UTC-day period (period = unix_seconds / 86400) and die at the boundary, ~12h average lifetime plus a ~1h grace, after which product statement submissions fail with "no allowance set for account". Per RFC-0010, keeping an allowance alive is the Account Holder's job; in the headless world the signing host plays that role, but it only registered allowances on demand for the current period and never renewed. This ports the mobile app's auto-renewal model into the shared core.

Design

Rule: core owns what to renew and how; the host owns only when.

 tick  (min(1h, next UTC-day boundary + 2min))
  ├─ no active session? ──────► skip
  ├─ read ledger, resolve targets against current root entropy
  ├─ fetch metadata + chain state + ring   (once per tick)
  └─ per target, under the registration lock:
       scan slots ── AlreadyAllocated ► fresh no-op
                 └── Free(seq) ► ring-VRF proof ► submit ► inBlock
                 └── exhausted ► stop pass, report slots_exhausted
  • statement_allowance/renewal.rs — chain-pure pass reusing the existing register_statement_account orchestrator. No persisted (period, seq): chain state is the truth via the AlreadyAllocated fast path.
  • signing_host/allowance_renewal.rs — target ledger persisted through CoreStorage (new StatementRenewalTargets slot). Entries are derivation recipes (WalletSso, ProductStatementAllowance{product_id}) so they survive the CLI's root-entropy rotation on slot exhaustion; only pairing peer device keys are raw account ids. Driver (renew_now) + idempotent loop spawned via Spawner (weak refs only, dies with the runtime; futures/futures-timer only, no tokio in core).
  • A registration lock serializes the pass (held per target, so on-demand allocation waits at most one registration) with allocate_statement_store_allowance, which now also records its product target in the ledger.
  • Public API on SigningHostRuntime (native-only): renew_statement_allowances() (primary one-pass entry, what a mobile WorkManager/BGTaskScheduler shim would call over FFI), start_statement_allowance_renewal(), track_statement_renewal_targets(), next_statement_renewal_delay().

CLI

  • build_signing_runtime starts the renewal loop (covers startup and /session switches, all modes).
  • Pairing registration returns the device key and tracks [WalletSso, device] in the ledger.
  • New /renew command (interactive + signing-host exec '/renew') as the manual escape hatch: prints per-target outcomes and, on slot exhaustion, marks auto-managed accounts exhausted so the next pairing rotates.

Tests

  • Tick-delay boundary math, outcome folding (mid-list failure continues; exhaustion skips the rest).
  • Ledger round-trip / dedupe / order, strict decode, recipe resolution matches the sso_responder derivations.
  • /renew parse accept/reject; codegen golden updated for the new CoreStorageKey variant.
  • Gates: cargo +nightly fmt --check, clippy --workspace --all-targets --all-features -D warnings, cargo test --workspace, cargo check --target wasm32-unknown-unknown -p truapi-server (renewal is native-only; on web the paired mobile app is the Account Holder).

Follow-ups (out of scope)

  • Bulletin long-term-storage renewal (additive ProductBulletinAllowance ledger variant).
  • Core→host exhaustion callback so the background loop (not just /renew) can trigger account rotation.
  • Move register_pairing_allowances into core to close the residual CLI-side registration race (benign: duplicate submits rescan).

pgherveou added 30 commits June 30, 2026 17:30
Adds the canonical testing module (api/testing.rs) and its v01/v02/versioned
wiring used by the Rust host runtime and generated clients.
New crate defining the host syscall traits (storage, navigation, consent,
permissions, ...) that host runtimes implement. Types are re-exported from
truapi::versioned/v01 rather than redefined.
WASM host runtime that hosts implement: dispatcher, SCALE frames, subscription
streams, chain runtime, host logic (sessions, SSO pairing, permissions,
statement store, dotns) and the wasm bindings. Includes the committed generated
dispatcher/wire-table under src/generated/.
…backs

Extends the rustdoc-JSON code generator to emit the Rust dispatcher and wire
table consumed by truapi-server, plus the TS host-callbacks adapter. Golden
tests pin the emitted shapes.
New WASM-backed host runtime package embedding the Rust core, with web iframe
and Web Worker entry points. Updates the @parity/truapi client (SCALE, sandbox,
transport) and drops the obsolete explorer 0.3.2 codegen snapshot.
Updates CLAUDE.md/README, CI workflows, Makefile, deny.toml, changesets, and
linguist attributes for generated code, and bumps the dotli submodule to the
host integration that consumes the WASM runtime.
Adds the canonical testing module (api/testing.rs) and its v01/v02/versioned
wiring used by the Rust host runtime and generated clients.
New crate defining the host syscall traits (storage, navigation, consent,
permissions, ...) that host runtimes implement. Types are re-exported from
truapi::versioned/v01 rather than redefined.
…backs

Extends the rustdoc-JSON code generator to emit the Rust dispatcher and wire
table consumed by truapi-server, plus the TS host-callbacks adapter. Golden
tests pin the emitted shapes.
pgherveou added 28 commits July 12, 2026 09:41
Decode ring revisions correctly for Bulletin allowance claims.

Add CLI-managed signer accounts, network presets, and script-capable host modes.

Regenerate headless signing and pairing diagnosis reports with zero failures.
Preserve headless SSO resource allocation while aligning Ring-VRF wire variants and confirmation policy with the iOS host.

Refs #264, #288
Pin responder wire fixtures to @novasamatech/host-papp 0.8.11 and expose a global log-level flag for paired-host diagnostics.
BREAKING CHANGE: use bare /script for the editable getUserId template, or run whoami.ts explicitly by path.
Statement-store allowances are registered per UTC-day period and die at
the boundary (~12h average lifetime plus a 1h grace), after which product
statement submissions fail with "no allowance set for account". RFC-0010
assigns keeping them alive to the Account Holder; for the headless host
that is the signing host, which until now only registered allowances on
demand for the current period.

Core (truapi-server):
- statement_allowance/renewal: chain-pure pass that re-registers every
  resolved target for the current period (AlreadyAllocated = fresh no-op,
  chain state is the truth), continues past per-target failures, stops on
  slot exhaustion, and computes the next tick delay
  (min(1h, period boundary + 2min)).
- signing_host/allowance_renewal: durable target ledger persisted via
  CoreStorage (new StatementRenewalTargets slot) storing derivation
  recipes (//wallet//sso, //allowance//statement-store//{product}) so
  entries survive root-entropy rotation, plus raw device keys; renew_now
  driver and an idempotent Spawner-based loop holding only weak refs.
- A registration lock serializes the renewal pass (per target) with
  on-demand allocation; allocate_statement_store_allowance now records
  its product target in the ledger.
- SigningHostRuntime gains track_statement_renewal_targets,
  renew_statement_allowances (primary one-pass API, FFI-friendly for
  mobile OS schedulers), start_statement_allowance_renewal, and
  next_statement_renewal_delay.

CLI (truapi-host):
- The signing host starts the renewal loop at runtime construction and
  tracks the wallet-sso + peer device targets after pairing registration.
- New /renew command (interactive and `signing-host exec '/renew'`)
  runs one pass, prints per-target outcomes, and marks auto-managed
  accounts exhausted on slot exhaustion so the next pairing rotates.
@valentunn

valentunn commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Note that mobile does that from a worker: doing so when app is running is not 100% reliable since the slot might expire when the app is not running
We should account that in the rust core: we want to be able to iniate & run stuff from the worker. One idea: rust core to send a command to native that it wants to schedule a specific / general purpose worker worker? We can (potentially, not sure) make "initiate worker" generic if rust core would pass opaque command data that worker will pass back to the core in onWorkerStarted or something like that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants