mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-06 09:47:44 +02:00
[eric] workflows: a cloud with no workflows API reads as unavailable with the honest reason, not as the cloud declining the request
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C9zwUaHucUgrdxvK8FvjYT
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
4d2cd934b5
commit
425df32fc4
@@ -49,6 +49,13 @@ class CloudStatusUnknown(CloudStatusBase):
|
||||
detail: str
|
||||
|
||||
|
||||
# The cloud we are signed into has no workflows API at all (the preflight route 404s): this app is ahead
|
||||
# of that control plane. Nothing was sent, and the toggle should say so instead of "declined".
|
||||
class CloudStatusUnavailable(CloudStatusBase):
|
||||
state: Literal["unavailable"] = "unavailable"
|
||||
reason: str
|
||||
|
||||
|
||||
class CloudStatusReady(CloudStatusBase):
|
||||
state: Literal["ready"] = "ready"
|
||||
plan: Optional[str] = None
|
||||
@@ -62,7 +69,7 @@ class CloudStatusReady(CloudStatusBase):
|
||||
credential: CredentialReadiness
|
||||
|
||||
|
||||
CloudStatus = Union[CloudStatusReady, CloudStatusSignedOut, CloudStatusUnknown]
|
||||
CloudStatus = Union[CloudStatusReady, CloudStatusSignedOut, CloudStatusUnknown, CloudStatusUnavailable]
|
||||
|
||||
|
||||
@typechecked
|
||||
@@ -124,6 +131,8 @@ async def compute_status(wf: Workflow) -> CloudStatus:
|
||||
except cloud.CloudUnreachable as exc:
|
||||
return CloudStatusUnknown(detail=exc.detail, **shared)
|
||||
except cloud.CloudRefused as exc:
|
||||
if exc.status == 404:
|
||||
return CloudStatusUnavailable(reason="Cloud runs are not available on your OpenSwarm Cloud yet; this version of the app is ahead of it. Nothing was sent.", **shared)
|
||||
return CloudStatusUnknown(detail=exc.message, **shared)
|
||||
|
||||
p_mirror_cloud_state(wf, pre.hosted)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""A 404 on the cloud's preflight route means the signed-in cloud has no workflows API: this app is
|
||||
ahead of it. That is a blocked state with the honest words, not "The cloud declined this request"."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from backend.apps.workflows.cloud import client as cloud
|
||||
from backend.apps.workflows.cloud import status as status_mod
|
||||
from backend.apps.workflows.models import Workflow
|
||||
|
||||
|
||||
def p_wf() -> Workflow:
|
||||
return Workflow(name="probe", steps=[])
|
||||
|
||||
|
||||
def test_a_404_preflight_is_unavailable_and_says_nothing_was_sent(monkeypatch):
|
||||
async def refuse(definition, hosted_id=None):
|
||||
raise cloud.CloudRefused("The cloud declined this request.", 404)
|
||||
monkeypatch.setattr(cloud, "preflight", refuse)
|
||||
st = asyncio.run(status_mod.compute_status(p_wf()))
|
||||
assert st.state == "unavailable" and "not available on your OpenSwarm Cloud yet" in st.reason and "Nothing was sent" in st.reason
|
||||
|
||||
|
||||
def test_any_other_refusal_stays_unknown_with_the_clouds_words(monkeypatch):
|
||||
async def refuse(definition, hosted_id=None):
|
||||
raise cloud.CloudRefused("Your plan does not include cloud runs.", 403)
|
||||
monkeypatch.setattr(cloud, "preflight", refuse)
|
||||
st = asyncio.run(status_mod.compute_status(p_wf()))
|
||||
assert st.state == "unknown" and st.detail == "Your plan does not include cloud runs."
|
||||
@@ -62,7 +62,13 @@ export interface CloudStatusUnknown extends CloudStatusShared {
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export type CloudStatus = CloudStatusReady | CloudStatusSignedOut | CloudStatusUnknown;
|
||||
// The signed-in cloud has no workflows API (its preflight route 404s): this app is ahead of it.
|
||||
export interface CloudStatusUnavailable extends CloudStatusShared {
|
||||
state: 'unavailable';
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export type CloudStatus = CloudStatusReady | CloudStatusSignedOut | CloudStatusUnknown | CloudStatusUnavailable;
|
||||
|
||||
export interface CloudRun {
|
||||
id: string;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { cloudAvailability } from './cloudAvailability';
|
||||
import type { CloudProbe } from './cloudApi';
|
||||
|
||||
// A cloud with no workflows API used to read as "The cloud declined this request" under an unknown
|
||||
// state (Haik, 2026-09-03: "requests routed to Cloud don't go through"). It is a blocked state with a
|
||||
// reason that says the app is ahead of the cloud and nothing was sent.
|
||||
|
||||
test('a cloud without the workflows API blocks the toggle with the honest reason', () => {
|
||||
const probe = { phase: 'answered', status: { state: 'unavailable', reason: 'Cloud runs are not available on your OpenSwarm Cloud yet; this version of the app is ahead of it. Nothing was sent.', target: 'device', schedule_supported: true, schedule_reason: null } } as unknown as CloudProbe;
|
||||
const a = cloudAvailability(probe);
|
||||
assert.equal(a.kind, 'blocked');
|
||||
assert.match((a as { reason: string }).reason, /not available on your OpenSwarm Cloud yet/);
|
||||
assert.equal((a as { action: string | null }).action, null);
|
||||
});
|
||||
@@ -43,6 +43,7 @@ export function cloudAvailability(probe: CloudProbe): CloudAvailability {
|
||||
return { kind: 'blocked', reason: status.schedule_reason, action: null };
|
||||
}
|
||||
if (status.state === 'unknown') return { kind: 'unknown', detail: status.detail };
|
||||
if (status.state === 'unavailable') return { kind: 'blocked', reason: status.reason, action: null };
|
||||
if (status.state === 'signed_out') {
|
||||
return {
|
||||
kind: 'blocked',
|
||||
|
||||
Reference in New Issue
Block a user