From 2ae57c7dcb4e99d1417fb0efbe05816c5a154aa0 Mon Sep 17 00:00:00 2001 From: mattshax Date: Fri, 2 Oct 2026 16:11:23 +0000 Subject: [PATCH] Settings lists workflows with the viewer's key when the deployment credential fails --- server/src/workflowsTab.ts | 13 +++++++++++-- server/test/workflowsTab.test.mjs | 21 +++++++++++++++++++++ web/src/components/WorkflowsSection.tsx | 5 +++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/server/src/workflowsTab.ts b/server/src/workflowsTab.ts index e5a1012..d182938 100644 --- a/server/src/workflowsTab.ts +++ b/server/src/workflowsTab.ts @@ -139,11 +139,20 @@ export async function workflowsTabRoutes(app: FastifyInstance): Promise { // What an administrator can pick from: every workflow on the account the // Studio is deployed under, which on a site deployment is the site's list. + // When the deployment's credential is missing, expired, or rejected, the + // viewer's own key lists their account instead, and the answer says so. app.get('/api/workflows/catalog', async req => { - const key = gatewayKey() ?? viewer(req).key - const rows = await listFor(key) + const dep = gatewayKey() + const mine = platformKeyFor(req.user?.id) + let rows: WorkflowRow[] | null = null + let source: 'deployment' | 'viewer' = 'deployment' + let depError: unknown = null + if (dep) rows = await listFor(dep).catch(e => { depError = e; return null }) + if (!rows && mine.own && mine.key) { rows = await listFor(mine.key); source = 'viewer' } + if (!rows) throw depError ?? new KbError(409, 'No platform credential: add your ACTIVATE API key under Settings, Model access.') const curated = new Set(curatedNames()) return { + source, workflows: rows.map(w => ({ name: w.name, displayName: w.displayName || w.name, diff --git a/server/test/workflowsTab.test.mjs b/server/test/workflowsTab.test.mjs index 7aaee3f..81a6d89 100644 --- a/server/test/workflowsTab.test.mjs +++ b/server/test/workflowsTab.test.mjs @@ -121,3 +121,24 @@ test('an expired platform credential is reported as such, not as an internal err assert.equal(other.status ?? other.statusCode, 502) assert.equal(other.message, 'The platform did not answer: connection reset by peer') }) + +test('with the deployment credential expired, the catalog lists the viewer\'s own account', async () => { + const { setUserKey } = await import('../dist/credentials.js') + setUserKey('viewer-1', 'viewer-key', false) + const app2 = Fastify() + app2.setErrorHandler(sanitizedErrorHandler(app2)) + app2.addHook('onRequest', async req => { req.user = { id: 'viewer-1', username: 'viewer' } }) + await app2.register(workflowsTabRoutes) + await app2.ready() + resetWorkflowsTabForTests() + setWorkflowsCli(async (args, key) => { + if (key === 'deployment-key') throw new Error("2026-10-02T14:33:54Z [ERROR] Authentication has expired. Please authenticate again using 'pw auth'.") + if (args[1] === 'ls') return JSON.stringify([{ name: 'mine-only', displayName: 'Mine only' }]) + throw new Error(`unexpected ${args.join(' ')}`) + }) + const res = await app2.inject({ method: 'GET', url: '/api/workflows/catalog' }) + assert.equal(res.statusCode, 200) + const body = res.json() + assert.equal(body.source, 'viewer') + assert.deepEqual(body.workflows.map(w => w.name), ['mine-only']) +}) diff --git a/web/src/components/WorkflowsSection.tsx b/web/src/components/WorkflowsSection.tsx index 43ad0de..24a9b69 100644 --- a/web/src/components/WorkflowsSection.tsx +++ b/web/src/components/WorkflowsSection.tsx @@ -11,6 +11,7 @@ interface CatalogRow { name: string; displayName: string; description: string; t export function WorkflowsSection() { const [catalog, setCatalog] = useState(null) + const [source, setSource] = useState<'deployment' | 'viewer'>('deployment') const [picked, setPicked] = useState([]) const [filter, setFilter] = useState('') const [note, setNote] = useState('') @@ -23,6 +24,7 @@ export function WorkflowsSection() { const d = await r.json() if (!r.ok) throw new Error(d.error ?? `${r.status}`) setCatalog(d.workflows) + setSource(d.source === 'viewer' ? 'viewer' : 'deployment') }).catch(e => setNote(`Cannot list the platform's workflows: ${(e as Error).message}`)) }, []) @@ -81,6 +83,9 @@ export function WorkflowsSection() {
On the platform{catalog ? ` (${catalog.length})` : ''}
+ {catalog && source === 'viewer' && ( +

Listed from your own account, because the deployment's platform credential is not usable. Until it is renewed, other viewers can run a workflow picked here only if it is already in their own account.

+ )} setFilter(e.target.value)} /> {!catalog && !note &&

Loading…

} {catalog && (