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
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-scheduleandpackages/types), then the owning lane's seat. Filed by therepo:cloudseat (repo:cloud#1, sessionsession_01Wxo1xhh2bU66T73q23jzE4); ⛔ not a claim.What happens
Since #19834 (PR #19858,
863a7758), a host can give each kernel its ownScheduledWorkPolicy. cloud now does (PR objectstack-ai/cloud#2535 for cloud#2337, ruling ①′): afree-plan kernel getsenabled: false, while the hosted runtime sets the deployment variableOS_AUTOMATION_SCHEDULED_WORK_ENABLEDto'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.tsat cloud headad77c456, with the framework at cloud's pin4b4ee88f):GET /automation/_status: the flow'sFlowRuntimeState.reasonisSCHEDULED_WORK_DISABLED_REASON, which says the variable "is unset or not truthy" and to setOS_AUTOMATION_SCHEDULED_WORK_ENABLED=true.getTriggerBindingAudit()carries the same sentence, and cloud'sGET /api/v1/health/trigger-bindingscopies it verbatim.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.reasonis "WHY this flow is not armed, in one sentence" (packages/spec/src/contracts/automation-service.ts), andenv.tssays 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.reasonon 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⚠️ 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.
defineJobloop inpackages/runtime/src/app-plugin.ts(about :1191 at the pin) still reads the zero-argumentresolveScheduledWorkEnabled(), so a host's per-kernel OFF does not reach it.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