Skip to content

Routing rules can match the local time of day - #314

Merged
fylorn merged 5 commits into
mainfrom
feat/rule-time-condition
Oct 10, 2026
Merged

fylorn merged 5 commits into
mainfrom
feat/rule-time-condition

Conversation

@fylorn

@fylorn fylorn commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

What

Routing rules gain a time condition: the request matches when the local wall-clock time of the machine core runs on falls inside any of the listed windows. Protocol version 45.

Value grammar (one string per window): [<days> ]<HH:MM>-<HH:MM>

  • <days>: comma-separated mon,tue,wed,thu,fri,sat,sun and/or ranges mon-fri, sat-sun, fri-mon (wraps past Sunday). Omitted = every day. Case-insensitive on input; the control plane stores values lower-case.
  • <HH:MM>-<HH:MM>: 24-hour, two digits each; start inclusive, end exclusive; the end may be 24:00. An end earlier than the start is an overnight window (22:00-06:00 = 22:00 through 05:59 the next day); the day part names the day the window starts on. A window whose start equals its end is refused.
  • Several values on one condition are OR, like intent.

Where

  • tw-engine: new time module (Window, LocalTime, parse; pure, unit-tested: day lists, ranges incl. wrapping, omitted days, overnight windows, 24:00, malformed input). When gains time: Option<OneOrMany> (counts as a condition, validated at load, matched in When::matches). RequestFacts gains time: Option<LocalTime>: filled by the gateway at routing time, None never matches a time rule. RouteError::TimeSyntax { rule, value } carries the rule name; Engine::check_rules maps the engine-level MatchError::BadTime onto it.
  • tw-gateway: AppState holds a LocalClock (default: system clock, local zone; set_local_time for tests). The fact is filled in the HTTP pipeline, on WebSocket upgrade and per WebSocket frame.
  • tw-control: ConditionField::Time round-trips through describe_when / when_from (values validated and lower-cased on save); dry-run evaluates against the current time and reports the mismatch as fri 17:30.
  • tw-api: ConditionField gains time; CONTROL_API_VERSION 44 → 45 with notes.
  • Docs: docs/config.md / docs/config.zh-CN.md table row and grammar paragraph (regenerated by the manual test).

New messages

  • engine.rule_time_syntax (args rule, value): rule {rule}: time condition {value} is not written as [days ]HH:MM-HH:MM: days are mon, tue, wed, thu, fri, sat, sun or a range like mon-fri, the hours run from 00:00 to 24:00, as in "mon-fri 09:00-18:00"
  • engine.time_syntax (arg value): time condition {value} is not written as [days ]HH:MM-HH:MM: days are mon, tue, wed, thu, fri, sat, sun or a range like mon-fri, the hours run from 00:00 to 24:00, as in "mon-fri 09:00-18:00" (the engine-level sentence without a rule name; config load and the control plane always surface the rule-named one)

Verified

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings, cargo clippy -p tw-api --all-targets --features ts -- -D warnings
  • env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy cargo test -p tw-engine -p tw-config -p tw-api -p tw-control, cargo test -p tw-api --features ts
  • cargo test -p tw-gateway --test time_window (the same request routes to a different upstream at Wed 10:00, Sat 10:00 and Wed 18:00 with a fixed clock)

🤖 Generated with Claude Code

fylorn and others added 5 commits October 10, 2026 18:29
A rule condition `time` matches when the wall-clock time of the machine
core runs on falls inside any of its windows, written as
`[days ]HH:MM-HH:MM` (`mon-fri 09:00-18:00`, `sat,sun 00:00-24:00`,
`22:00-06:00`). The start is inclusive and the end exclusive, `24:00` is
allowed as the end, and an end earlier than the start is an overnight
window belonging to the day it starts on. Several windows are OR.

The parser is a pure function in tw-engine with its own tests. A
malformed value is refused when the configuration is read and when a
rule is saved, with a message that names the rule and the value
(`engine.rule_time_syntax`): a rule that silently never matches is the
kind of problem users spend an afternoon on.

The time itself is a routing fact (`RequestFacts::time`) the gateway
fills at routing time from a clock on `AppState`, so tests can pin it
and the HTTP pipeline, the WebSocket upgrade and every WebSocket frame
see the same source. Dry-run uses the current time and reports a miss as
`fri 17:30`. Protocol version 45.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@fylorn
fylorn merged commit 2c4386b into main Oct 10, 2026
6 checks passed
@fylorn
fylorn deleted the feat/rule-time-condition branch October 10, 2026 11:02
@fylorn fylorn mentioned this pull request Oct 10, 2026
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.

1 participant