Conversation
Extend #[resolve] macro to support URL templates with field placeholders (e.g., url = "https://api.example.com/{field.path}/data") alongside the existing dotted-path syntax. Add AST types (UrlTemplatePart, UrlSource, ResolverCondition, ScheduledCallback), condition/schedule_at parameter parsing, compiler opcode extensions, and VM runtime template construction with field interpolation. Made-with: Cursor
Generate actual ResolverCondition and schedule_at values in the proto_struct code path instead of hardcoded None, completing the condition parameter support across both AST-writer and codegen paths. Made-with: Cursor
New scheduler.rs with BTreeMap-based SlotScheduler for registering, deduplicating, and dispatching slot-triggered resolver callbacks with retry support. Adds get_entity_state() to VmContext for read-only state access needed by the scheduler background task. Made-with: Cursor
Wire SlotScheduler into both single-entity and multi-entity VmHandler codegen. After each handler execution, scheduled callbacks are collected and registered. A background task polls every 400ms, evaluates conditions, retries up to 100 slots, builds URLs from templates, and fires resolver requests through the standard resolve_url_batch pipeline. Made-with: Cursor
Use the new #[resolve] syntax to pre-fetch entropy seed from the Ore API when a round expires, conditioned on entropy_value not yet being revealed on-chain. Made-with: Cursor
…or missing entity states
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
… resolution failures
adiman9
left a comment
There was a problem hiding this comment.
Yh this is banging. Few notes but otherwise looks good to merge
| } | ||
| } | ||
|
|
||
| pub fn register(&mut self, target_slot: u64, callback: ScheduledCallback) { |
There was a problem hiding this comment.
If the same entity gets a new expires_at value (e.g. a new round starts), register silently drops the callback because the dedup key is already in self.registered from the first call. The callback stays pinned to the original target slot.
if self.registered.contains(&dedup_key) {
// Remove old callback targeting the stale slot
for cbs in self.callbacks.values_mut() {
cbs.retain(|cb| Self::dedup_key(cb) != dedup_key);
}
}
interpreter/src/scheduler.rs
Outdated
| use serde_json::Value; | ||
| use std::collections::{BTreeMap, HashSet}; | ||
|
|
||
| const MAX_RETRIES: u32 = 100; |
There was a problem hiding this comment.
This constant is exported as MAX_SCHEDULER_RETRIES on line 123, but the generated code in vixen_runtime.rs (line 946) re-declares its own const MAX_RETRIES: u32 = 100 instead of referencing hyperstack::runtime::hyperstack_interpreter::scheduler::MAX_SCHEDULER_RETRIES.
| } | ||
| continue; | ||
| } | ||
| }; |
There was a problem hiding this comment.
We probably want to re-evaluate the condition here. If resolved_seed is already populated (from a previous successful resolve), it'll fire again every slot until expires_at + MAX_RETRIES slots pass. Also if for some reason something else in the condition has changed we may want to bail on making the http request.
Also worth checking SetOnce incase its already been set and we should bail in that case as well.
// Re-evaluate condition against current state
if let Some(ref cond) = callback.condition {
if !hyperstack::runtime::hyperstack_interpreter::scheduler::evaluate_condition(cond, &state) {
continue; // condition no longer holds
}
}
// For SetOnce, check if target fields are already populated
if callback.strategy == ResolveStrategy::SetOnce {
let already_resolved = callback.extracts.iter().all(|ext| {
hyperstack::runtime::hyperstack_interpreter::scheduler::get_value_at_path(&state, &ext.target_path)
.map(|v| !v.is_null())
.unwrap_or(false)
});
if already_resolved { continue; }
}
| } | ||
| } | ||
| } else { | ||
| continue; |
There was a problem hiding this comment.
This silently drops requests that don't contain a url_template but they might have a valid url in them already and just want to trigger of a slot
interpreter/src/vm.rs
Outdated
| pub queued_at: i64, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] |
There was a problem hiding this comment.
Maybe derive PartialEq as well?
|
Noticed the CI is failing. Some clippy errors to clean up and you will need to regenerate the ore sdk via Then worth just running |
… scheduler's MAX_RETRIES constant
…_runtime callbacks
…d callback handling in vixen_runtime
Scheduled URL Resolver for
#[resolve]MacroAdds slot-scheduled, condition-gated URL resolution to the
#[resolve]attribute macro. This enables entities to automatically fetch external data at a specific Solana slot, with conditional triggering and URL templating — all declaratively from the entity definition.Motivation
The Ore mining protocol requires fetching a pre-computed
seedfrom the Entropy API when a mining round expires. Previously, this would require manual polling or custom handler logic. With this feature, a single#[resolve]annotation handles:Usage
New
#[resolve]Parametersurl = "..."{field.path}template interpolation from entity stateschedule_at = field.pathcondition = "field == null"strategy = SetOnceArchitecture
URL Templates (
UrlTemplatePart,UrlSource){entropy.entropy_var_address}syntax is parsed intoUrlTemplatePart::FieldRefsegmentsbuild_url_from_template()Conditions (
ResolverCondition)"entropy.entropy_value == null"==and!=operators withnullcomparisonsSlot Scheduler (
SlotScheduler)BTreeMap<u64, Vec<ScheduledCallback>>register(target_slot, callback)— enqueues a callback with deduplicationtake_due(current_slot)— returns all callbacks whose target slot has passedre_register(callback, next_slot)— re-enqueues a failed callback for retrytokio::spawntask polls every 400ms, checking current slot against pending callbacksRetry Logic
current_slot + 1with an incrementedretry_countMAX_RETRIES(100 attempts, ~40s at 400ms polling), the callback is discarded with a warning logCompiler & VM
QueueResolveropcode extended withurl_template,condition, andschedule_atfieldsScheduledCallbackSlotSchedulerUrlResolverClient, applies mutations, and publishes to the projectorFiles Changed
AST & Types
hyperstack-macros/src/ast/types.rs—UrlTemplatePart,UrlSource,UrlResolverConfig,ResolverCondition,ResolverSpec.schedule_athyperstack-interpreter/src/ast.rs— Mirror types for interpreterParsing
hyperstack-macros/src/parse/attributes.rs— Parseurl,condition,schedule_atfrom#[resolve]hyperstack-macros/src/stream_spec/entity.rs—parse_url_template()for{field}syntaxhyperstack-macros/src/stream_spec/sections.rs— Wire parsed attributes into resolver specshyperstack-macros/src/stream_spec/ast_writer.rs— Condition string parsing, resolver spec groupinghyperstack-macros/src/stream_spec/proto_struct.rs— Code generation for URL template typesInterpreter
hyperstack-interpreter/src/compiler.rs— Compile resolver specs with new fields intoQueueResolverhyperstack-interpreter/src/vm.rs—ScheduledCallbackstruct, condition evaluation, scheduled callback extractionhyperstack-interpreter/src/scheduler.rs—SlotScheduler,build_url_from_template(),evaluate_condition(),get_value_at_path()Runtime Codegen
hyperstack-macros/src/codegen/vixen_runtime.rs—SlotSchedulerinstantiation, background polling task, URL resolution and mutation publishing