Skip to content

Fix Trigger timezone and occurrence scheduling semantics - #833

Draft
Y1fe1Zh0u wants to merge 6 commits into
mainfrom
fix/bugfix-20260804-pr
Draft

Fix Trigger timezone and occurrence scheduling semantics#833
Y1fe1Zh0u wants to merge 6 commits into
mainfrom
fix/bugfix-20260804-pr

Conversation

@Y1fe1Zh0u

Copy link
Copy Markdown
Collaborator

What changed

  • make the effective schedule timezone follow Agent.timezone -> Tenant.timezone -> Asia/Shanghai
  • validate Tenant and Agent IANA timezone writes and make the Tenant database default required
  • make the evaluator produce the scheduled occurrence once and pass that exact instant through daemon, dispatch, idempotency, persistence, and Runtime intake
  • keep Cron progression independent from last_fired_at and apply the agreed 30-second grace without historical catch-up
  • roll back failed scheduled Runtime intake so the same occurrence remains eligible on the next daemon scan
  • reject invalid Cron expressions at both existing update boundaries before database commit
  • keep Agent detail timezone output aligned with the timezone actually used by scheduling

Why

The evaluator and dispatch previously interpreted Cron independently. That gave one business fact, the planned occurrence time, two competing sources. Registration time was then persisted as though it were the planned time, and last_fired_at could move the calendar schedule forward from actual dispatch time.

The repair makes the evaluator the owner of scheduled_at. Downstream components consume that occurrence without recalculating it. Runtime completion remains outside Trigger scheduling semantics: a successful atomic Runtime registration counts as one wake-up, while later Runtime success or failure belongs to Runtime itself.

Compatibility and scope

  • no Task, AgentSchedule, or AgentTrigger model unification
  • no Trigger-specific timezone UI or semantics
  • no new scheduling state fields
  • existing cooldown_seconds behavior is preserved
  • Runtime internal retry, DST policy, second-level Cron, and historical backfill remain out of scope
  • the branch is replayed on current upstream/main and retains its query_dao session boundary and single-head migration chain

Validation

  • focused timezone, Trigger update, scheduling, and queue regression: 33 passed
  • current-main full backend regression: 2173 passed, 9 warnings
  • scoped Ruff checks: passed
  • Alembic: one head, f061_default_tenant_timezone

Tenant and Agent timezone values now enter the scheduler through one IANA validation boundary, while new tenants receive the confirmed Beijing default.

Constraint: Agent timezone remains nullable to preserve tenant inheritance.

Rejected: Validate against COMMON_TIMEZONES | it is a UI shortlist, not the complete IANA set.

Confidence: high

Scope-risk: narrow

Directive: Do not reintroduce silent UTC fallback for configured Trigger timezones.

Tested: backend/tests/test_timezone_validation.py (14 passed); Alembic single-head check.

Not-tested: Migration execution against a production-sized tenants table.
The evaluator now owns Cron occurrence calculation in the Agent effective timezone. The daemon, dispatch key, queue record, and Runtime source all consume that same planned instant, with a bounded 30-second grace and creation-time lower bound.

Constraint: Keep cooldown_seconds and non-Cron Trigger behavior compatible.

Rejected: Persist next_run_at or schedule_effective_from | the accepted design uses current rules plus a bounded grace without new schedule state.

Confidence: high

Scope-risk: moderate

Directive: Do not derive Cron occurrences from last_fired_at or recompute them below evaluator.

Tested: 22 scheduling, queue, intake, completion, and A2A tests; scoped Ruff.

Not-tested: DST edge behavior and second-level Cron expressions are out of scope.
Scheduled Trigger intake failures now roll back the occurrence instead of persisting a failed receipt that blocks the next daemon scan. Structured logs retain the failure evidence, while webhook receipts preserve their existing synchronous failure contract.

Constraint: Retry is only the ordinary 15-second scan while the occurrence remains inside the 30-second grace.

Rejected: Add retry counters, backoff jobs, or new schedule fields | outside the confirmed minimal repair.

Confidence: high

Scope-risk: narrow

Directive: A failed scheduled intake must not consume its occurrence identity.

Tested: 40 timezone, scheduling, queue, Runtime, A2A, and webhook tests; scoped Ruff.

Not-tested: Process termination during the database commit syscall.
Cron creation already rejected malformed expressions, but both existing update boundaries could persist them. Validate before mutation and commit while preserving REST replacement and Agent-tool patch semantics.

Constraint: Preserve current Trigger APIs and config compatibility
Rejected: Introduce a shared Trigger config abstraction | interface unification remains deferred
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Trigger config timezone may remain stored for compatibility but must not define occurrence timezone
Tested: Full backend pytest 2162 passed; focused Trigger tests 18 passed; scoped Ruff and git diff checks passed
Not-tested: DST-specific and second-level Cron behavior remain out of scope
The scheduler already falls back through Agent and Tenant to Asia/Shanghai, but the Agent detail response still advertised UTC when both stored values were absent. Reuse the platform default so the visible configuration describes the runtime behavior.

Constraint: Preserve the existing Agent detail response shape
Rejected: Add a new frontend timezone resolution path | the backend already owns effective timezone resolution
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any displayed effective timezone must follow the same Agent to Tenant to platform-default order as scheduling
Tested: Full backend pytest 2163 passed; timezone tests 15 passed; scoped Ruff and git diff checks passed
Not-tested: Frontend visual regression was unnecessary because the response field shape is unchanged
Current main routes Trigger persistence through query_dao and has advanced the Alembic chain. Preserve those boundaries while replaying the scheduling repair, move Cron imports to the module boundary, and adapt tests to the DAO session contract.

Constraint: Publish without rebasing the user's dirty working tree
Constraint: New migrations must follow the current single-head and DDL-only rules
Rejected: Rebase the checked-out fix branch | unrelated concurrent work makes worktree mutation unsafe
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the PR branch based on current upstream main and retain query_dao session ownership
Tested: Focused timezone and Trigger regression 33 passed; scoped Ruff passed; Alembic reports one f061 head
Not-tested: Full backend suite was interrupted before completion
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