Skip to content

Fix the schedule execution-order gap: run every/at bodies sequentially - #5

Merged
imattau merged 1 commit into
mainfrom
fix/schedule-execution-order
Sep 27, 2026
Merged

imattau merged 1 commit into
mainfrom
fix/schedule-execution-order

Conversation

@imattau

@imattau imattau commented Sep 27, 2026

Copy link
Copy Markdown
Owner

Summary

Real fix (not a workaround) for the schedule-execution-order gap NCC-PLAN.md recorded as a follow-up: a schedule's body used to run through two disconnected static passes — top_level_publications for literal-field publishes, top_level_operation_calls for bare operation calls — so a let computed in a schedule body never reached a later publish or print in the same body. examples/service-operator-bot.ns hit exactly this building its encrypted Locator refresh and had to work around it by moving the sequence into a handler.

  • Interpreter::statement's Every/At arm (previously Err(unsupported(...))) now runs self.block(body) — a schedule's body executes exactly once, inline, through the same sequential evaluator a handler body already gets. This is real evaluation of the one firing every host already guaranteed (startup) — not new periodic-timer support; deploy still only registers the timer for actual refiring.
  • A new eval::run_schedule_bodies drives this at the top level; deferred_spans now also excludes a schedule's span from the two old static passes, so nothing runs twice.
  • nscript-cli's run_program/deploy_program run the old static passes first, completely unchanged, then run_schedule_bodies through a small EvalHost wrapper that traces to the exact same operation N: <value> / publication N: <accepted>/<total> relays accepted output format startup already used — continuing the same counters — plus a log <level>: <message> line for a schedule's own print, which finally runs.
  • Scoped deliberately to schedule bodies, not the whole top-level program. An earlier version of this fix ran all top-level code through the sequential evaluator. Checked against every file in examples/ and conformance/valid/ (not just the test suite), it broke roughly twenty fixtures that rely on the type checker's own leniency toward a bare demo identifier used only as a typed argument, or bind select ... from .../latest ... from ... expressions the plain evaluator has never implemented. No schedule body in this codebase uses either shape, so narrowing the fix to schedule bodies keeps the real bug fixed with zero regressions — verified by diffing every example's nscript run output before and after.
  • examples/service-operator-bot.ns now runs its encrypted Locator refresh directly inside every 24h { ... } alongside the other three publishes — the fix, not the workaround.

Test plan

  • cargo build --workspace — clean
  • cargo test --workspace — all tests pass (250 nscript-runtime + 69 CLI tests, 1 ignored), including a new library-level test for run_schedule_bodies and an updated CLI test for the composite example
  • cargo fmt --check — clean
  • cargo clippy --workspace --tests -- -D warnings — clean
  • Ran every file in examples/ and conformance/valid/ before and after, diffed output: only 4 files changed, all four gaining exactly the print their schedule always should have produced, nothing newly broken (the remaining 4 files that fail nscript check were already failing identically before this change, unrelated to it — pre-existing gaps referencing hypothetical custom modules)

🤖 Generated with Claude Code

https://claude.ai/code/session_01D95PWA2fdNfg7nUvUHBeMi


Generated by Claude Code

A schedule's body used to run through two disconnected static passes
(top_level_publications for literal-field publishes, top_level_operation_calls
for bare operation calls), so a let computed in a schedule body never
reached a later publish or print in the same body -- the exact gap
examples/service-operator-bot.ns hit building its encrypted Locator
refresh and had to work around by moving into a handler.

Interpreter::statement's Every/At arm now runs self.block(body), giving
a schedule's body the same sequential evaluator a handler body already
gets, executed once inline at startup (deploy still only registers the
timer for actual periodic refiring -- this is not new timer support).
A new eval::run_schedule_bodies drives this at the top level;
deferred_spans excludes a schedule's span from the two old static
passes so nothing runs twice. nscript-cli's run_program/deploy_program
call the old static passes first, unchanged, then run_schedule_bodies
through a small EvalHost wrapper that traces to the same operation
N/publication N output format, continuing the same counters, plus a
log line for a print that finally runs.

The fix is scoped to schedule bodies specifically, not the whole
top-level program: an earlier version ran all top-level code through
the sequential evaluator and, checked against every file in examples/
and conformance/valid/ rather than just the test suite, broke roughly
twenty fixtures that rely on the checker's own leniency toward bare
demo identifiers or bind select/latest subscription expressions the
plain evaluator has never implemented. No schedule body in this
codebase uses either shape, so narrowing the fix to schedule bodies
keeps the real bug fixed with zero regressions across the whole
corpus, verified by diffing every example's output before and after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D95PWA2fdNfg7nUvUHBeMi
@imattau
imattau merged commit 365843f into main Sep 27, 2026
2 checks passed
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