Skip to content

automation: a host's per-kernel scheduled-work refusal is reported with the deployment sentence — SCHEDULED_WORK_DISABLED_REASON tells a free-plan tenant to set OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true (the #19834 seam has no reason slot) #21110

Description

@objectstack-fleet

Category ① — a product defect, reach measured on a public endpoint.
Reader: objectstack triage for routing and grade (the fix surface reads as packages/services/service-automation, packages/triggers/trigger-schedule and packages/types), then the owning lane's seat. Filed by the repo:cloud seat (repo:cloud#1, session session_01Wxo1xhh2bU66T73q23jzE4); ⛔ not a claim.

What happens

Since #19834 (PR #19858, 863a7758), a host can give each kernel its own ScheduledWorkPolicy. cloud now does (PR objectstack-ai/cloud#2535 for cloud#2337, ruling ①′): a free-plan kernel gets enabled: false, while the hosted runtime sets the deployment variable OS_AUTOMATION_SCHEDULED_WORK_ENABLED to 'true'.

The refusal is then reported with the deployment sentence. Measured in that PR's e2e (packages/objectos-runtime/src/time-relative-trigger.e2e.test.ts at cloud head ad77c456, with the framework at cloud's pin 4b4ee88f):

  • GET /automation/_status: the flow's FlowRuntimeState.reason is SCHEDULED_WORK_DISABLED_REASON, which says the variable "is unset or not truthy" and to set OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true.
  • getTriggerBindingAudit() carries the same sentence, and cloud's GET /api/v1/health/trigger-bindings copies it verbatim.
  • The bind log reads "Flow tr_contract_expiry_reminder is not armed on trigger time_relative -- disabled by deployment policy ... is unset or not truthy", with the variable set.

On that kernel the variable IS set, and the cause is the host's plan. The reason names the wrong cause and the wrong remedy.

Where (at 4b4ee88f; anchored by symbol, line numbers approximate)

  • packages/types/src/env.ts, ScheduledWorkPolicy (about :330): no field for a reason.
  • packages/services/service-automation/src/engine.ts: describeUnboundReason (about :4541) and the bind log (about :3685).
  • packages/triggers/trigger-schedule/src/schedule-trigger.ts: the trigger's own refusal (about :365).

The contract it falls short of: FlowRuntimeState.reason is "WHY this flow is not armed, in one sentence" (packages/spec/src/contracts/automation-service.ts), and env.ts says the reason "names the switch and its remedy".

It was asked once and never answered

The #19834 report (5794427869) asked exactly this as its open question 2: "Should a host-injected OFF get its own sentence?" It recommended "A for now … Revisit B only if an operator-facing surface shows the env remedy to a tenant." The thread carries no answer, and the card closed with the PR. That surface now exists: FlowRuntimeState.reason on the status endpoint, which objectstack-ai/objectui#9217 (open) renders in Studio.

What the fix enables

cloud's ruling C on cloud#2337 (5731311433) requires that "the flow list (and the install result) states the reason for a scheduled flow that is not armed on a free plan". cloud#2337 delivers the install-result half. The flow-list half needs this producer to carry a host-supplied reason, or a host-policy sentence, so that a free-plan kernel can say why instead of naming a variable the tenant cannot set. The cloud consumer step is a cloud card filed with Blocked-by: this one.

Also unanswered on #19834, noted, not this card's scope

Open question 1: the declarative defineJob loop in packages/runtime/src/app-plugin.ts (about :1191 at the pin) still reads the zero-argument resolveScheduledWorkEnabled(), so a host's per-kernel OFF does not reach it. ⚠️ Reach in cloud is not established: cloud has no producer of declarative jobs, and installed-package handlers do not bind there (cloud#2324). It is recorded for the seat that owns #19834's question; ⛔ it is not filed.

Dedup

REST list of objectstack issues and PRs updated since 2026-09-15, open and closed (1,659 read), title and body matched on ScheduledWorkPolicy|scheduledWorkPolicy|SCHEDULED_WORK_DISABLED_REASON|describeUnboundReason. Hits: #19834 and #19858 (the seam, closed); #20702 (closed, the activation ledger, a different defect); and the seat post #6026. None carries this gap. Control: #19834 hit.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions