Skip to content

Commit ce91c46

Browse files
committed
fix(release): small release-audit cleanups
- docs: MACHINE_ACCESS names MCP TypeScript SDK 2.4.0 (taken in #9846) - run status: readPausedRunPoints reuses the pause_points the status read already loaded instead of re-querying paused_executions on every paused run read - polling: markWebhookFailed logs "auto-disabled" only when this update turned the webhook off, read from a locked prior row in the same single statement - mistral: an explicit image limit of 0 asks Mistral for no images instead of falling back to 100; block placeholder and tool description say so - v2 meta: scopes reports every scope the credential reaches through oauthScopeSatisfies (api:write implies api:read, either implies search:read); enforcement unchanged, OpenAPI and CLI artifacts regenerated
1 parent 03bf275 commit ce91c46

16 files changed

Lines changed: 104 additions & 31 deletions

File tree

‎apps/docs/MACHINE_ACCESS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Continue until `nextCursor` is `null`. Unknown cursors and invalid parameters re
3333

3434
Configure a remote Streamable HTTP server at `https://docs.sim.ai/api/mcp`. This endpoint is separate from Sim's workspace MCP server and `/mcp/...` documentation pages. It requires no API key.
3535

36-
The official stable MCP TypeScript SDK 2.3.1 serves the 2026-07-28 protocol and stateless compatibility for 2025-era clients. Tools:
36+
The official stable MCP TypeScript SDK 2.4.0 serves the 2026-07-28 protocol and stateless compatibility for 2025-era clients. Tools:
3737

3838
| Tool | Input | Result |
3939
| --- | --- | --- |

‎apps/docs/content/docs/integrations/mistral_parse.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Parse PDF documents using Mistral OCR API
3333
| `resultType` | string | No | Legacy parser output format \(markdown, text, or json; default markdown\). Versions 2 and 3 return structured OCR pages regardless of this value. |
3434
| `includeImageBase64` | boolean | No | Extract the images in the document \(figures, logos, photos\) as files on the images output |
3535
| `pages` | array | No | Specific pages to process \(array of page numbers, starting from 0\) |
36-
| `imageLimit` | number | No | Maximum number of images to extract \(at most 100\) |
36+
| `imageLimit` | number | No | Maximum number of images to extract \(at most 100; 0 extracts none\) |
3737
| `imageMinSize` | number | No | Minimum height and width of images to extract from the PDF |
3838
| `tableFormat` | string | No | Return tables separately on each page as markdown or html instead of inline in the page markdown |
3939
| `apiKey` | string | Yes | Mistral API key \(MISTRAL_API_KEY\) |

‎apps/docs/openapi-v2-resources.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18651,7 +18651,7 @@
1865118651
"type": "string",
1865218652
"enum": ["api:read", "api:write", "search:read"]
1865318653
},
18654-
"description": "API scopes the calling credential holds. A read-only API key holds only api:read: it is refused every operation that changes state and every operation that declares no API scope, so api:read alone does not authorize those. A full-access key holds api:read and api:write; an OAuth access token holds what it was granted."
18654+
"description": "API scopes the calling credential can use. api:write implies api:read, and either implies search:read. A read-only API key holds api:read and search:read: it is refused every operation that changes state and every operation that declares no API scope, so api:read alone does not authorize those. A full-access key holds all three; an OAuth access token holds what it was granted and what that implies."
1865518655
}
1865618656
},
1865718657
"required": ["v2Enabled", "keyType", "expiresAt", "scopes"],

‎apps/sim/blocks/blocks/mistral_parse.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ export const MistralParseV3Block: BlockConfig = {
384384
id: 'imageLimit',
385385
title: 'Max Images',
386386
type: 'short-input',
387-
placeholder: 'Up to 100 (default 100)',
387+
placeholder: 'Up to 100 (default 100, 0 for none)',
388388
condition: { field: 'includeImageBase64', value: true },
389389
mode: 'advanced',
390390
},

‎apps/sim/lib/api-key/__integration__/read-only-access.integration.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ describe('read-only and expiring API keys in PostgreSQL', () => {
275275
for (const key of [readPersonalKey, readWorkspaceKey]) {
276276
const meta = await call(v2Meta, '/api/v2/meta', { key })
277277
expect(meta.status).toBe(200)
278-
expect(meta.body).toMatchObject({ data: { scopes: ['api:read'] } })
278+
expect(meta.body).toMatchObject({ data: { scopes: ['api:read', 'search:read'] } })
279279
}
280280
const personalMeta = await call(v2Meta, '/api/v2/meta', { key: readPersonalKey })
281281
expect(personalMeta.body).toMatchObject({ data: { expiresAt } })
@@ -417,7 +417,7 @@ describe('read-only and expiring API keys in PostgreSQL', () => {
417417
it('treats a key created before access levels as full access', async () => {
418418
const meta = await call(v2Meta, '/api/v2/meta', { key: legacyKey })
419419
expect(meta.body).toMatchObject({
420-
data: { scopes: ['api:read', 'api:write'], expiresAt: null },
420+
data: { scopes: ['api:read', 'api:write', 'search:read'], expiresAt: null },
421421
})
422422

423423
const tablesBefore = await tableCount()

‎apps/sim/lib/api/contracts/v2/meta.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const v2MetaSchema = z
2525
scopes: z
2626
.array(z.enum(['api:read', 'api:write', 'search:read']))
2727
.describe(
28-
'API scopes the calling credential holds. A read-only API key holds only api:read: it is refused every operation that changes state and every operation that declares no API scope, so api:read alone does not authorize those. A full-access key holds api:read and api:write; an OAuth access token holds what it was granted.'
28+
'API scopes the calling credential can use. api:write implies api:read, and either implies search:read. A read-only API key holds api:read and search:read: it is refused every operation that changes state and every operation that declares no API scope, so api:read alone does not authorize those. A full-access key holds all three; an OAuth access token holds what it was granted and what that implies.'
2929
),
3030
})
3131
.meta({

‎apps/sim/lib/core/application/oauth-authorization.test.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { OAuthAccessTokenPrincipal, PersonalApiKeyPrincipal } from '@sim/au
22
import { describe, expect, it, vi } from 'vitest'
33
import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application/authorized-workspace-use-case'
44
import {
5+
credentialApiScopes,
56
InsufficientScopeError,
67
OAuthAccessTokenExpiredError,
78
ReadOnlyApiKeyError,
@@ -104,6 +105,30 @@ describe('semantic OAuth policy', () => {
104105
).not.toThrow()
105106
})
106107

108+
it.each([
109+
{ case: 'a write token', credential: { ...principal, scopes: ['api:write'] } },
110+
{ case: 'a read token', credential: principal },
111+
{ case: 'a search-only token', credential: { ...principal, scopes: ['search:read'] } },
112+
{
113+
case: 'a read-only key',
114+
credential: { kind: 'personal_api_key', userId: 'user-1', keyId: 'k-1', access: 'read' },
115+
},
116+
{
117+
case: 'a full-access key',
118+
credential: { kind: 'personal_api_key', userId: 'user-1', keyId: 'k-1', access: 'write' },
119+
},
120+
] as const)('reports exactly the scopes $case can reach', ({ credential }) => {
121+
const reachable = (['api:read', 'api:write', 'search:read'] as const).filter((oauthScope) => {
122+
try {
123+
requireOAuthOperationScope(credential, { ...writeOperation, oauthScope })
124+
return true
125+
} catch {
126+
return false
127+
}
128+
})
129+
expect(credentialApiScopes(credential)).toEqual(reachable)
130+
})
131+
107132
it('rejects expiry before considering scope', () => {
108133
expect(() =>
109134
requireOAuthOperationScope({ ...principal, expiresAt: new Date(0) }, writeOperation)

‎apps/sim/lib/core/application/oauth-authorization.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ const API_SCOPES: readonly OAuthApiScope[] = [
8787
]
8888

8989
/**
90-
* The API scopes a credential holds, as `/api/v2/meta` reports them. A credential
91-
* no grant bounds, such as a write key, holds both `api:read` and `api:write`.
90+
* The API scopes a credential can reach, as `/api/v2/meta` reports them: each scope
91+
* {@link requireOAuthOperationScope} would let it through, so `api:write` reports the
92+
* `api:read` it implies and either reports `search:read`. A credential no grant bounds,
93+
* such as a write key, reaches every scope.
9294
*/
9395
export function credentialApiScopes(principal: Principal): OAuthApiScope[] {
9496
const granted = grantedApiScopes(principal)
95-
if (!granted) return [OAUTH_API_READ_SCOPE, OAUTH_API_WRITE_SCOPE]
96-
return API_SCOPES.filter((scope) => granted.includes(scope))
97+
if (!granted) return [...API_SCOPES]
98+
return API_SCOPES.filter((scope) => oauthScopeSatisfies(granted, scope))
9799
}
98100

99101
function grantedApiScopes(principal: Principal): readonly string[] | undefined {

‎apps/sim/lib/internal/mistral/operations.test.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ describe('Mistral ingestion authorization', () => {
239239
it.each([
240240
{ case: 'a limit above the cap', imageLimit: 5000 },
241241
{ case: 'no limit', imageLimit: undefined },
242-
{ case: 'a zero limit', imageLimit: 0 },
243242
])(
244243
'never asks Mistral for more images than one parse may store, given $case',
245244
async ({ imageLimit }) => {
@@ -255,6 +254,15 @@ describe('Mistral ingestion authorization', () => {
255254
}
256255
)
257256

257+
it('asks Mistral for no images when the image limit is zero', async () => {
258+
await executeMistralToolParse(
259+
'mistral_parser_v3',
260+
{ ...input, includeImageBase64: true, imageLimit: 0 },
261+
context
262+
)
263+
expect(submit.mock.calls[0][1]).not.toHaveProperty('include_image_base64')
264+
})
265+
258266
it('returns extracted images as files, never as base64 in the output', async () => {
259267
const png = Buffer.concat([Buffer.from([0xff, 0xd8, 0xff]), Buffer.from('synthetic jpeg body')])
260268
submit.mockResolvedValue({

‎apps/sim/lib/internal/mistral/operations.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,21 @@ export async function executeMistralParse(
266266
/**
267267
* The workflow tool's parse. Only `mistral_parser_v3` extracts images, and it
268268
* returns them as stored files rather than base64 in the output; older tool
269-
* versions declare no image output, so they never ask Mistral for images.
269+
* versions declare no image output, so they never ask Mistral for images. An
270+
* image limit of 0 asks for no images, the same as leaving extraction off.
270271
*/
271272
export async function executeMistralToolParse(
272273
toolId: string,
273274
input: MistralParseInput,
274275
context: MistralOperationContext
275276
): Promise<{ success: true; output: unknown } | InternalToolFileResult> {
276-
if (toolId !== 'mistral_parser_v3' || !input.includeImageBase64) {
277+
if (toolId !== 'mistral_parser_v3' || !input.includeImageBase64 || input.imageLimit === 0) {
277278
return executeMistralParse({ ...input, includeImageBase64: undefined }, context)
278279
}
279280
const result = await executeMistralParse(
280281
{
281282
...input,
282-
imageLimit: Math.min(input.imageLimit || MAX_OCR_EXTRACTED_IMAGES, MAX_OCR_EXTRACTED_IMAGES),
283+
imageLimit: Math.min(input.imageLimit ?? MAX_OCR_EXTRACTED_IMAGES, MAX_OCR_EXTRACTED_IMAGES),
283284
},
284285
context
285286
)

0 commit comments

Comments
 (0)