Skip to content

v0.9.54: non-OAuth auth for twelve more integrations, agent API additions (workflow outline, schedules list, paused runs, scoped CLI pairing keys), concurrent-edit guard, dependency updates - #9912

Merged
waleedlatif1 merged 39 commits into
mainfrom
staging
Nov 12, 2026

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 30 commits November 11, 2026 09:46
… keys are live (#9879)

Every production pod runs v0.9.53, which enforces key access, and the flag is on in prod AppConfig. Read-only key creation is now always available: the create modal always offers Access (except where a key must be full access), and the create use case no longer consults the flag.
* docs(library): update best-ai-agents-sales-crm-automation

* Pi Babysit: address PR #9882 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…e Questions (#9883)

* feat(library): Best AI Agent Platforms for HR Teams Answering Employee Questions

* Pi Babysit: address PR #9883 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
… and Code Review (#9884)

* feat(library): Best AI Agents for GitHub Pull Requests, Issue Triage, and Code Review

* Pi Babysit: address PR #9884 feedback

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
… tools, Instagram revocation, selector failure logs (#9880)

* fix(errors): actionable table read timeouts, 409 for duplicate custom tools, Instagram revocation, selector failure logs

* fix(errors): address review on table timeout, selector logging, and route test context

* fix(table): limit timeout guidance to the structured row query
… to the row limit (#9881)

* fix(tables): show rejected filter errors in the grid and fill imports to the row limit

* fix(tables): use caption size for the grid error and drop the redundant mock reset
* docs(library): update postgres-mcp-server-ai-agents

* fix(library): restore dropped Postgres MCP FAQs and make the tenant policy fail closed on malformed context

* fix(library): show security_invoker views so tenant row security applies through curated views

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…oogle Sheets (#9886)

* feat(library): Best AI Agents for Spreadsheet Analysis in Excel and Google Sheets

* fix(library): lead the spreadsheet guide's Sim section with the AI workspace definition

* fix(library): use teams instead of users for audience wording

---------

Co-authored-by: Sim Pi Agent <pi@sim.ai>
…Rows (#9887)

* feat(table): add Columns to Return to the legacy Table block's Query Rows

* fix(table): treat a column selection with no resolvable columns as all columns

* Revert "fix(table): treat a column selection with no resolvable columns as all columns"

This reverts commit 1b59b12.
…launched (#9888)

* fix(chat): bring a restored workflow's chat back online when it is relaunched

* fix(chat): refuse a chat update on a superseded or version-less deployment, matching the shared deploy path

* fix(chat): never re-enable a chat archived while its relaunch was deploying
Use cronstrue's trimHoursLeadingZero option instead of a regex over its
output, and update the schedule description tests to the new English
wording.
0.56 reorganized the package exports so subpaths map under esm/vs; the
old monaco-editor/esm/vs/... worker URLs no longer resolve. Point the
file editor's workers at the new supported entry points.
Streamdown 2.6+ hands streaming placeholders (streamdown:incomplete-link,
streamdown:incomplete-image) to custom components instead of stripping
them, so the mothership chat link and image renderers now withhold those
until the URL completes.
knip 6.44.1 tracks member use inside import().then() callbacks and follows
Tailwind @reference and bun spawn arguments, so check:unused-exports now sees:

- six exports only used in their own file (previously kept alive by an
  import('...').then(...) of their module): drop the export keyword and the
  matching keys in the storage-service test mock and the shared telemetry mock
- @sim/auth-ui's stylesheet uses @reference/@apply from tailwindcss without
  listing it: declare it as a peer dependency (consumers compile it)
- packages/db integration tests spawn bun scripts with an explicit cwd that
  knip cannot see: ignore those three specifiers with a reason
… item (#9898)

Bump every @tiptap/* package to 3.32.2 and raise the root prosemirror-transform
override to 1.12.4 (@tiptap/pm 3.32 requires ^1.12.2).

@tiptap/extension-list 3.32 demotes a heading that opens a list item to the
item's paragraph, so `- # Title` loaded and saved as `- Title`. CommonMark
reads it as a heading inside the item, which the Sim schema already supports;
MarkdownListItem now parses that case as blocks itself.

@tiptap/y-tiptap stays on 3.0.7: 3.0.8+ replaces a list's Yjs element when a
sibling is merged into it, discarding concurrent peer edits inside the list.
* chore(deps): upgrade docx to 9.15.2

docx 9.15 packs with fflate and encodes base64 with the global btoa, which
the isolated-vm sandbox does not provide, so the docx-generate finalize
(Packer.toBase64String + hand-rolled decode) threw ReferenceError. Pack with
Packer.toArrayBuffer instead, like the pptx/pdf tasks, and cover the real
bundle + bootstrap + finalize in a bare isolate-shaped context.

docx now drops XML-1.0-illegal control characters itself, so the Word
block's pre-strip before Markdown parsing is removed. Regenerated the
docx sandbox bundle.

* fix(word): keep stripping control characters before Markdown parsing

* fix(sandbox): provide btoa and atob to sandbox tasks

docx 9.15 encodes base64 through the global btoa/atob, so in the task
isolate Packer.toBase64String and patchDocument's base64 output (which
worked on 9.7.1) and password protection threw ReferenceError. The worker
now bridges btoa/atob to Node's native WHATWG implementations, like the
text codecs, and the bare verification context mirrors them.
…0, and @daytona/sdk to 0.227.3 (#9900)

Moves the three together so @daytona/sdk 0.227.3 resolves its AWS and OTel
dependencies to Sim's own copies instead of nesting duplicates. Aligns the
@smithy/node-http-handler pin with what the 3.1162 clients require.
* chore(deps): bump turbo to 2.11.10

* docs(ci): describe the Turborepo 2.11 task-level planner flag
…ent change (#9902)

* fix(workflows): serialize operation edits under the workflow row lock

* fix(workflows): fingerprint only stored rows so container child order cannot raise a false conflict
… gate (#9894)

* chore(deps): update minor and patch dependencies past the release-age gate

* chore(deps): keep turbo at 2.9.14
* docs(api): add a guide for building and running workflows with the API

* docs(api): point the workflow guide at the Sim skills

* docs(api): pass only the operations array to the CLI in the workflow guide

* docs(api): keep polling while a run is redacting
… workspace rename (#9904)

* feat(api): agent identity on v2 meta, scoped CLI pairing keys, and v2 workspace rename

- GET /api/v2/meta adds keyId, userId, workspaceId and links {openapi, docs, mcp};
  existing fields unchanged. The API catalog shares the same link source.
- CLI pairing login accepts --read-only and --expires-in <days>. The request rides
  the approval URL, is shown on the approval card, is recorded with the approval
  and applied at mint; the unauthenticated poll cannot set either. The poll echoes
  access and expiresAt, and the CLI refuses to store a key that does not match
  what was asked (an older server mints full access). The signed-out login bounce
  now keeps scope, workspace, access and expiry instead of dropping them.
  sim whoami shows the key's expiry and bound workspace.
- POST /api/v2/credentials/connections returns draftId. Opening a connection link
  that is unknown, expired or another account's now explains to sign in as the
  creating account, without saying which case applied.
- PATCH /api/v2/workspaces/{workspaceId} renames a workspace or changes its logo
  through the existing workspaces.identity.update operation, widened to personal
  API keys and OAuth tokens (api:write); Admin still required, workspace keys
  still denied. Workspace update operations move into the operations registry.
- Regenerated OpenAPI, MCP catalog, CLI client and CLI docs.

* fix(cli-auth): pass route context in the new approve and poll tests

* test(mcp): expect the identity fields getMeta now returns

* fix(workspaces): count members before the rename so a failed count never hides a committed update

* improvement(cli-auth): reuse the api-key access schema and tidy handoff validation

* fix(api): publish the workspace PATCH body rules and check CLI-minted keys exactly

- PATCH /v2/workspaces/{id}: the body schema publishes minProperties 1, and
  logoUrl is a pattern (root-relative path or https URL, protocol-relative
  refused) instead of an unpublished refine.
- sim login refuses a minted key whose access or expiry differs from the
  request in either direction, with the expiry checked against the requested
  days within a clock tolerance.
- sim workspaces update --logo-url documents that clearing the logo goes
  through the API or MCP.
…ors, and catalog value types (#9905)

* feat(api): add traceId and field paths to v2 errors, name the read-only key remedy

* feat(api): publish declared value types on block catalog fields

* feat(api): add workflow outline and workspace schedules reads to v2

* fix(api): type declared catalog inputs as block params and regenerate the docs manifest

* fix(api): compare member responses without their per-request traceId

* improvement(api): tidy catalog field projection default and integration test comment

* docs(api): say the outline's derived fields come from values state already returns
…; correct table timeout guidance (#9908)

* fix(selectors): redact email path segments from provider failure logs; correct table timeout guidance

* fix(table): keep timeout guidance to sorts and range filters
…flows (#9909)

#9889 fixed JSend-style 200s ({status, data}) being reported as failures by
deleting the old /api/proxy envelope branch outright. That also changed two
behaviors existing workflows rely on: 200 bodies with success/status/data were
unwrapped (<api.data.x> read the inner data) and success: false failed the
block. Restore both through one shared toHttpToolResponse helper, gated on the
body carrying a success field — the only shape the old route ever produced —
so only the misclassified case changes. Non-string envelope errors are now
serialized instead of surfacing as [object Object].
* fix(api): name the failing field once in v2 validation errors

A message that already leads with its field label no longer repeats it after
the location prefix, and the CLI restates the prefix as the flag the caller
typed instead of rewriting it to query.--flag.

* fix(api): call the v2 error id requestId

The id in error bodies is the request id that x-request-id carries and logs
store as requestId; traceId already means the OTel trace id in logs, so a
search by the published value found nothing.

* fix(api): describe what the workflow outline actually omits

Only null values are dropped, every sub-block hidden from agents is dropped
(some are real configuration), and blocks whose parent container is gone are
left out, so point readers at Get workflow state for the full configuration.

* fix(api): prove the workspace schedules list against Postgres

Adds an integration suite for listWorkspaceLiveSchedulesPage: it returns the
rows the per-workflow live list reports, leaves out archived, undeployed and
other-workspace workflows, and pages through createdAt ties without repeats or
gaps. sortBy=createdAt now says that each deploy creates new schedule rows.

* chore(skills): include the optional requestId in the v2 error shape

* fix(cli): pick the handoff for --expires-in and name a refused key

sim login --expires-in without --method failed on servers that offer OAuth,
because OAuth was chosen and then refused the flag; a lifetime now selects the
API-key handoff. A refused handoff key no longer says nothing was stored: it
already exists on the account, so the CLI points at how to revoke it.

* fix(api): publish the workspace logoUrl pattern without a lookahead

RE2-based validators (Go, Rust, many gateways) reject lookaheads, so the
OpenAPI pattern for logoUrl could not be compiled there. The rewrite accepts
and refuses exactly the same values.

* chore(cli-auth): drop the poll-body half of a test that could not fail

The poll contract strips unknown keys and the route never reads them, so
sending access and expiresInDays in the body proved nothing. The test keeps
checking that the approval's access and lifetime reach the mint.

* fix(workflows): refuse withheld blocks before the operations lock

applyWorkflowOperations now hands the persistence primitive a reader, which
skipped its pre-lock withheld-block check, so a refused batch took the row lock
and re-read the graph first. The use case checks the graph it composed before
writing. A failed re-read under the lock is now an internal error instead of a
400 saying the workflow has no state.

* chore(api): document the CONFLICT 409 from Apply workflow operations

* improvement(workflows): prove the operations conflict guard against Postgres

Holds the workflow row lock to force each interleaving: an unchanged workflow
with containers and a migrated legacy block commits, a write that lands after
the base read is refused as a conflict, and of two concurrent applies one
commits and the other is refused with nothing lost.

* chore(api): tighten comments and the field-label check from review

* fix(cli): name a refused handoff key exactly

The poll response now carries the minted key's name (an additive, optional
field), so the CLI tells the caller exactly which key in Settings → API keys
to revoke, falling back to the key id for an older server. Restores the route
context argument the trimmed poll test dropped.

* fix(workflows): run the conflict-guard races on their own connections

The applies and the lock-wait poll started inside the lock holder's
transaction callback, so the transaction tripwire refused them. They now leave
that context, the applies run in a permission-group scope as routes do, and an
apply that settles early is reported without awaiting the ones queued on the
held lock.

* chore(cli-auth): declare the poll key name the server always sends

* fix(workflows): count lock waiters queued behind another waiter

A second FOR UPDATE waiter queues on the first waiter's tuple lock, so
pg_blocking_pids names that waiter rather than the holder; the barrier now
walks the chain.

* fix(cli): probe the endpoint before an --expires-in handoff

--expires-in without --method chose the handoff before the reachability
probe, so a mistyped endpoint printed a pairing code and polled instead of
failing fast. The probe now runs first. A refused key from a server that does
not report its name is described by the name pattern and time it was made.
…d-runs list, and applied limits (#9906)

* feat(api): attribute polled run failures, deployed input schema, paused-run list, and applied run limits

- GET /v2/workflows/{id}/runs/{runId} reads the failing block back from the run's
  recorded trace, withheld when the viewer's permission group hides execution data
- GET /v2/workflows/{id} adds inputSchema for the active deployment's API run input
- GET /v2/workflows/runs/paused lists a workspace's runs waiting on human input,
  projecting pause points exactly as the run detail does
- Execute responses report executionTimeoutSeconds; GET /v2/billing/status adds plan limits

* fix(api): respect the paused-run page cap in the CLI and keep waiting pause points under the cap

- CLI generator carries a numeric field's contract maximum; the pager never requests
  a page above it while still paging to the caller's total --limit
- Pause point cap keeps points not yet resumed before resumed history, so a run that
  paused again is never listed without its waiting point (run detail and paused list)

* improvement(api): reuse FailedTraceBlock and sort pause points once when capping

* fix(api): custom-block trace boundary, legacy pause point arrays, and list point selection

- A custom block is an opaque trace boundary for failing-block attribution
- The paused-run list reads object and legacy array pause_points
- The list selects waiting human points before the per-run cap, reads points in
  waves that stop once the page budget is spent, and shares one waiting predicate
- Resume response schemas omit executionTimeoutSeconds
- Drop a non-null assertion from the CLI page cap test

* fix(api): attribute concurrent failures to the stored error's block and never fail a workflow read on its deployment

- Failing-block attribution prefers the span the stored error names, else the
  failure that ended first, else none; the block error prefix has one owner
- The workflow read degrades inputSchema to null when the deployment cannot be
  read or its schema has an unexpected shape
- Integration cases for time-only, archived, and cross-workspace exclusion

* fix(api): consistent trace walk, block-named error attribution, required applied timeout, and ungated input schema

- Failing-block attribution walks spans as hasUnhandledError does, through
  successful blocks, with one shared successful-boundary rule
- An error message headed by the failing block's name is attributed to it even
  when the name reads as a run-level failure
- executionTimeoutSeconds is required (nullable) on Execute workflow responses
- The workflow read takes inputSchema from the active version row, not isDeployed
- Pause cap wording: unresolved points are kept in preference, listed earliest first
- Regenerate the CLI API the previous contract wording left stale

* fix(api): parse resume responses with the resume schema in e2e scripts and regenerate the CLI API on staging

- Stop-after and pinned-execute e2e scripts parse resume responses with
  v2ResumeWorkflowDataSchema, which omits executionTimeoutSeconds
- Regenerate the CLI API against the merged staging contracts

* fix(api): parse the stop-after e2e resume helper's response with the resume schema
…tadog, Gong, PlanetScale, SailPoint, ServiceNow, Zendesk, ZoomInfo, Shopify, Atlassian, Linear and Twilio (#9911)

* fix(credentials): accept permission-scoped AgentMail keys at connect

A valid AgentMail key that lacks inbox_read or is scoped to a pod or inbox
gets a 403 coded missing_permission or forbidden on the inbox listing, and
was refused as invalid. Accept a coded 403 as proof of the key, and keep
rejecting the gateway's bare Forbidden and the key-naming 401 codes.

* fix(credentials): offer Atlassian API tokens for the Bitbucket saved credential

Bitbucket takes an Atlassian API token created for Bitbucket as a Bearer
token, so the existing repository read already verifies one. Say so in the
connect copy and setup guide, and note that app passwords no longer work.

* fix(credentials): ask for a reconnect when a Microsoft app certificate can no longer sign

An expired or unreadable stored certificate failed before any request reached
Entra, carried no status, and was reported at run time as the provider being
temporarily unavailable (502). Local configuration failures now classify as
invalid_credentials, and only an unreachable Entra keeps provider_unavailable.
Saving also refuses a certificate that is not valid yet.

* fix(credentials): warn that saved Infisical access tokens expire

Sim stores the machine identity access token as pasted and never renews
it, so it stops at its Access Token TTL (30 days by default). Tell users
to set a long TTL and Max TTL, and not an Access Token Period, which needs
renewal each period.

* fix(credentials): retry keyless Microsoft token requests while a federated credential propagates

Entra can refuse a new federated identity credential with AADSTS70021 for
several minutes after it is added, even after a token was issued, and
Microsoft asks callers to retry. A run now retries that refusal (and
AADSTS700213) twice with jittered backoff before reporting it; the save-time
subject-pin probe, which expects the refusal, is unchanged.

* fix(infisical): keep the instance URL out of the model's reach and normalize it like a saved credential

The legacy Infisical tools let the model set baseUrl, so a prompt-injected
agent call could send the inline Infisical token to any public host. baseUrl
is now user-only (matching Grafana and Upstash), and inline instance URLs go
through the same normalizer the saved-credential validator stores, which moves
from the validator module into input-validation so tools can share it.

* feat(credentials): accept HubSpot service keys for the saved HubSpot credential

HubSpot stopped new legacy private apps on October 26, 2026 and points
direct integrations at service keys, which are Bearer tokens for the same
REST APIs. The connect copy now asks for a service key or an existing
private app token. The token-info route covers only private apps, so a
non-transient refusal there defers to the Account Information API, and that
fallback accepts a 403 only with a JSON body, so a firewall page never
passes as a live token. Both checks refuse redirects.

* feat(credentials): accept Microsoft app credentials for Intune

Every Intune operation the block offers (managed devices, detected apps,
compliance policies and their device statuses, device configurations, and the
sync, restart, remote lock, and retire actions) documents application
permissions in Microsoft Graph v1.0, so the Intune picker now lists Microsoft
app credentials and the resolver mints a Graph token for them. OAuth Intune
connections are unchanged. The setup docs name the application permissions
each operation needs.

* fix(credentials): refuse rotating Slack tokens for custom bot credentials

A Slack app with token rotation on issues xoxe.-prefixed tokens that expire
in 12 hours and can only be refreshed with the app's client ID and secret,
which Sim never holds. Such a token passed auth.test at connect and failed
every run after. Refuse xoxe tokens up front with a message that asks for
the xoxb- token of an app with rotation off.

* docs(credentials): correct the Devin key check comment to match Devin's docs

Devin documents 401 for an invalid key and one 403 for both a key outside
the organization and a key without ViewOrgSessions. Behavior is unchanged:
nothing documented separates the two 403s, so a 403 still rejects.

* fix(credentials): refuse redirects in every per-vendor saved-key check

The shared token-check engine already refuses redirects, but the per-vendor
validators followed them. Fetch keeps custom headers such as x-api-key and
X-Shopify-Access-Token on a cross-origin redirect, so a redirected check
would hand the key to another host. Every per-vendor check now refuses a
redirect, which surfaces as the provider being unavailable.

* fix(credentials): treat a Webflow token that reaches no site as the user's error

GET /v2/sites lists every site the token can access, so an empty list is a
token the user must replace, not a retryable outage. A token that reaches
several sites is no longer pinned to whichever site the list returns first.

* feat(credentials): pick and connect Google service accounts for Vertex AI

The Vertex AI runtime already resolves Google service account credentials,
keyless or JSON key, but the Google Cloud Account picker on model blocks only
listed OAuth accounts, so a saved service account could be used only by
pasting its ID in advanced mode. Vertex AI now names google-service-account
as its service-account provider, and its picker lists both kinds with a
connect action for each, so the keyless option is offered for Vertex.
Existing OAuth selections and the advanced credential ID field are unchanged.

* fix(copilot): let Copilot pass a saved credential to hybrid tools

Copilot's integration schema only offered credentialId for tools that require
OAuth, so a tool that takes an inline key or an optional saved credential always
demanded the inline secret and the gateway never forwarded a saved credential.
On the copilot surface those tools now declare an optional credentialId and stop
requiring the params a saved credential supplies; the workflow surface and
OAuth-required tools are unchanged. The executor now rejects a Copilot call to
such a tool that sends neither, instead of calling the provider without a key.

* fix(credentials): report a NerdGraph error answer as New Relic being unavailable

New Relic refuses an unknown or wrong-region User key with a 401, so a 200
whose errors array left the fixed actor query unanswered is NerdGraph
failing, not a verdict on the key. Report it as provider_unavailable
instead of telling the user their key is wrong. A 200 with no user and no
errors still rejects.

* fix(aws): refuse S3 presigned URLs that outlive the saved role session signing them

A presigned URL signed with STS credentials stops working when the role session
ends, whatever expiry it asks for, but the S3 tool reported the requested expiry.
Resolved sessions now carry the STS expiration, and presigning with a saved
assume_role or oidc_role credential refuses an expiry past the session's end
with a 400 that names the remaining time and the session duration. Long-lived
access keys and inline keys are unchanged.

* fix(guardrails): resolve the hallucination check's Vertex credential like every other model call

The hallucination check looked the Vertex credential up as a raw account ID,
so a saved credential (whose ID is a credential row, OAuth or Google service
account) resolved no token and the scoring call ran without one. It now uses
the shared Vertex resolver, which handles credential rows, Google service
accounts, and the legacy account IDs the old lookup accepted, and fails the
check instead of calling the model when the credential cannot be resolved.

* fix(aws): report transient STS failures on a saved credential as 503, not a denial

Every failure to resolve a saved AWS credential came back as a 403, so an STS
outage, throttling, or STS failing to reach Sim's OIDC issuer (IDPCommunicationError)
read as a permission problem and was never retried. Failures STS models as
retryable, throttling, STS 5xx, and network errors now return 503; real denials
(AccessDenied, invalid or expired identity tokens, rejected claims) stay 403.
No extra retry loop is added: the pinned STS client already retries
IDPCommunicationError and throttling up to three times with backoff.

* fix(databricks): report an unreachable workspace at token mint as provider_unavailable

A connection failure, timeout, or refused redirect while minting a run's
Databricks token escaped as a raw fetch error, which the executor reported as a
generic 401 and whose cause can carry connection details. It is now a 502
DatabricksCredentialError, which resolves to provider_unavailable; a cancelled
run still rethrows its abort, and personal access tokens are unaffected.

* feat(credentials): connect Shopify Dev Dashboard apps with client credentials

Shopify Dev Dashboard apps never reveal an Admin API token, so the Shopify
saved credential gains a client-credentials mode beside the pasted shpat_
token. A credential saved with authMethod client_credentials stores the
client ID, secret, and store domain; execution mints the 24-hour token at
the store's /admin/oauth/access_token endpoint.

Shopify does not document whether a new token retires earlier ones, so
every saved credential for one app and store shares a single token kept
under an advisory lock in client_credential_token, instead of minting per
worker. shop_not_permitted surfaces as grant_not_permitted with copy that
names the same-organization requirement.

Pasted-token credentials, their blob, and their resolution are unchanged;
the minted-token cache in the credential service is factored out so the
client-credential providers and this mode share it.

* fix(credentials): keep keyless off when a pinned workload identity issuer has an unserved path

Sim serves workload identity discovery and keys only under /oidc/workload, and
clouds fetch <issuer>/.well-known/openid-configuration, so a WORKLOAD_IDENTITY_ISSUER
with any other path advertised keyless credentials that no cloud could verify. A
pinned issuer must now have the /oidc/workload path (its host may differ), or
keyless stays off with a log line naming the requirement. The default issuer
derived from the app URL is unchanged, and the self-hosting and AWS credential
docs state the requirement.

* feat(credentials): saved CrowdStrike API client credential and credential-only block

A CrowdStrike credential holds a Falcon API client id and secret, the cloud
(from the existing Falcon cloud allowlist), and an optional MSSP member CID.
The minter posts them to the cloud's /oauth2/token with redirects refused and
returns the token with its expires_in lifetime (30 minutes when unusable)
and the cloud, which the credential cache keys by credential id and secret
fingerprint, so changing the cloud or secret re-mints.

- clientCredentialOAuth: a policy for tools that mint their own token from
  inline client credentials. The credential fills the hidden accessToken and
  the region param; tokenReplacesParams names the inline secrets the token
  replaces, and the saved-credential contract check accepts exactly those.
- crowdstrike_v2 is the credential-only successor; the legacy block keeps
  its inline client id, secret, and cloud and is hidden from the toolbar.
- The operation uses a credential's token as is. Inline credentials now
  reuse a cached token (keyed by cloud, client id, and secret hash) until a
  minute before it expires, and a 401 drops it so the next run re-mints.
- Client-credential mints can bind a domain (the cloud), carried through
  the token cache to tools.

* fix(credentials): accept only documented scoped-key answers from AgentMail and New Relic

AgentMail now proves a key only on the two 403 codes its docs give for a
valid key with the wrong scope (missing_permission, forbidden); a suspended
account, a gateway deny, and any other refusal reject the key.

New Relic reports an outage only for NerdGraph errors in the server classes
its own client retries; BAD_API_KEY and unclassified errors reject the key.

* fix(credentials): settle HubSpot service keys on the account-info route

A token-info 200 without a portal now falls back to account-info instead of
failing as an outage, and a token-info 401 falls back only for pat- keys, so
other tokens keep being rejected there as before. The vendor-JSON 403 check
moves to one shared helper.

* docs(credentials): align HubSpot, Infisical, and Bitbucket saved-key copy with vendor docs

* feat(credentials): saved Zendesk credentials with client credentials or an API token

Zendesk gets a zendesk-service-account credential with two sign-ins: a
confidential OAuth client exchanged through the client-credentials grant
(POST /oauth/tokens, JSON, an explicit read or read write scope, a one-hour
expires_in), or an agent email and API token sent as the documented
email/token Basic credential. The subdomain is normalized and must match
^[a-z0-9][a-z0-9-]*$ before any request, and connecting requires
users/me to answer with a real (non-anonymous) agent or admin.

The credential resolves to its token, its subdomain as domain, and
authStyle basic for the API-token sign-in. zendesk_v2 is the
credential-only successor; every Zendesk tool reads the token through one
header helper and takes its subdomain from the credential. The legacy
zendesk block and its inline email, API token, and subdomain keep signing
in exactly as before.

* feat(credentials): PlanetScale saved service token credential

A PlanetScale credential holds a service token and its ID, verified by
listing the organizations the token reaches with the same ID:token
Authorization the tools send; a JSON 403 is a valid token without that
read. Every PlanetScale tool accepts it, the selectors gain saved-credential
twins that read the pair from the credential, and planetscale_v2 is the
credential-only successor. The legacy block keeps its inline inputs.

* feat(credentials): Gong saved access key credential

A Gong credential holds an access key and its secret, verified by listing
workspaces on api.gong.io with the Basic key:secret the tools send. Gong
documents its 403 for this read without telling a bad key from a missing
permission, so any refusal rejects. Every Gong tool accepts the credential
and gong_v2 is the credential-only successor; the legacy block keeps its
inline inputs.

* feat(credentials): ServiceNow saved username and password credential

A ServiceNow credential holds an instance, a username, and its password,
verified by reading one user id from the Table API with the Basic auth the
tools send. The instance is reduced to the https origin of a
ServiceNow-owned host before anything is sent, and the check goes through
the guarded egress; 401 rejects, a JSON 403 is a login its ACLs limit, and
only a Table API result list proves the login. Every ServiceNow tool
accepts the credential, servicenow_v3 is the credential-only successor, and
servicenow and servicenow_v2 keep their inline inputs as legacy blocks.

* feat(credentials): Twilio API key mode for the saved Twilio credential

The Twilio credential and every Twilio tool now also take an API key, as
the key SID and secret joined by a colon (SKxxx:secret), the exact Basic
username and password Twilio documents for a key, while the URL keeps the
Account SID. A key is verified by listing one of the account's phone
numbers, a read a Standard key may make, since only Main keys and auth
tokens may read the Accounts resource. Stored auth-token credentials and
inline auth tokens keep their shape and their account-resource check; a
value with a colon that is not a key pair is refused before any request.

* fix(credentials): retry only the documented Entra propagation refusal, within the shared mint deadline

AADSTS700213 is Microsoft's documented case-sensitive federated credential
mismatch, not a propagation refusal, so a mismatched keyless credential no
longer makes three token requests per run. The AADSTS70021 retries now stop
when a slow answer would push the run past the 30 s deadline that concurrent
runs sharing the mint wait for. The Microsoft app service list is declared
once for both its type and the resolver's check, and the Intune and Vertex
copy names the app credential and the Google service account.

* feat(credentials): Atlassian service-account OAuth 2.0 credentials

An Atlassian service account can now connect with its OAuth 2.0
credential (client ID and secret) as well as a scoped API token. Saved
with authMethod client_credentials, the credential stores the client
credentials, the site, its cloudId from tenant_info, and the product;
execution mints a 60-minute token at auth.atlassian.com with the client
credentials grant and calls api.atlassian.com/ex/{product}/{cloudId}
exactly as a scoped token does. Connecting proves the minted token
against the product's current-user endpoint.

Atlassian documents these credentials for Jira and Confluence only, so
other products are refused. It does not say whether a new token retires
earlier ones, so every saved credential for one OAuth client shares one
coordinated token. Scoped-token credentials, their blob, and their
resolution are unchanged; the identity request now refuses redirects.

* feat(credentials): saved Datadog API and application key credential and credential-only block

A Datadog credential holds the site (from the DATADOG_SITES allowlist), the
API key, and the application key. Datadog authenticates every request with
the two static headers, so the "mint" resolves the API key as the token, the
application key beside it, and the site as the credential's domain.

- Save verifies the API key with GET https://api.{site}/api/v1/validate,
  sending DD-API-KEY alone, redirects refused: 403 is invalid_credentials,
  and a 200 must report valid: true. The application key is not probed:
  Datadog publishes no reference for an application-key check, and any other
  read would reject a correctly scoped key with a permission 403. Keys with
  control characters or whitespace are refused before any request.
- Credential resolution carries a new applicationKey field (credential
  cache, token payload contract, executor credentialParams source), and the
  Assistant registers it as a resolved secret beside the token.
- Every Datadog tool takes the saved credential: the credential fills apiKey,
  applicationKey (where the tool sends it), and site, overriding caller values.
- datadog_v2 is the credential-only successor; the legacy block keeps its
  inline keys and site and is hidden from the toolbar. withSavedCredential
  accepts condition: null for a picker shown in every operation when the
  replaced fields show differently.

* fix(aws): bound S3 presigned URLs by the role session's duration, not its time left

The presign guard compared the requested expiry with the seconds left before the
STS session ended, floored. A session is minted for each call, so by the time the
guard runs the STS round trip has used part of it and a default 3600s session
reports 3599s left, which refused the default 3600s expiry for every saved
assume_role or oidc_role credential on default settings.

Resolved sessions now carry the duration Sim requested from STS, and the guard
refuses only an expiry longer than that duration. The reported expiresAt is
clamped to the session's end, so it is never later than the time the URL stops
working. The session lookup that the signer and the guard share is one helper.

* fix(aws): classify wrapped STS socket failures as transient with the shared network-error helper

isTransientAwsFailure kept its own list of socket codes and checked only the top-level code, so a socket failure the SDK request handler wrapped in a cause, or an undici code, was reported as a 403 denial instead of a 503. It now uses isRetryableNetworkError, which walks the cause chain and errno.

* feat(credentials): connect Linear OAuth apps with client credentials

The Linear saved credential gains a client-credentials mode beside the
personal API key. Saved with authMethod client_credentials, it stores the
app's client ID and secret and mints an app-actor token at
api.linear.app/oauth/token with the fixed scope read,write, so two
credentials for one app never request different scopes (which Linear
treats as replacing the app's tokens). Linear documents that tokens with
the same scopes coexist, so each worker caches its own, and a token is
reused for at most an hour so one revoked early is replaced quickly.

A client-credentials token acts as the app, so connecting falls back to
the workspace (organization.id) as the principal when Linear returns no
viewer. The connect copy warns that requesting a token replaces an
existing authorization-code installation of the app and that rotating
the secret invalidates its tokens. Personal API keys and Linear OAuth
credentials are unchanged.

* fix(credentials): warn instead of turning keyless off for a pinned issuer on another path

Refusing a pinned WORKLOAD_IDENTITY_ISSUER whose path was not exactly
/oidc/workload turned keyless off on upgrade for a deployment that routes a
different issuer path to Sim through a proxy, and it also refused pinning the
default issuer of a path-prefixed app URL, which the default itself accepts.
Trust policies name the issuer, so those operators could only recover by
recreating the identity provider and every trust policy.

A pinned issuer whose path ends in /oidc/workload is now accepted the same way
the default is. Any other path logs a warning that clouds can verify its tokens
only if a proxy routes it to /oidc/workload; saving a keyless credential still
proves the route end to end. The https, port, query, and fragment refusals are
unchanged, and the docs describe the rule.

* fix(copilot): exempt a hybrid tool's key only when the hosted key applies to the call

The Copilot credential check waived a hybrid tool's hosted key param whenever the deployment was hosted, but injectHostedKeyIfNeeded skips the hosted key when the tool's hosting.enabled predicate rejects the params, so such a call could reach the provider with neither a key nor a saved credential. Both now use one hostedKeyMayFill predicate. No hybrid tool declares hosting today, so this is latent.

* fix(databricks): stop repeating 'Try again' when the save-time probe cannot reach the workspace

The unreachable-workspace error ended in 'Try again.', and the federation-policy check that wraps it adds its own, so a save showed 'Try again. Try again.'. The low-level message now only names the failure.

* fix(credentials): refuse redirects by default in fetchProvider

Every fetchProvider caller sends a secret, and fetch replays custom headers
and 307/308 bodies to whatever host a redirect names. Default the helper to
redirect: 'error' so validators and client-credential mints inherit it, and
drop the per-call copies this branch had added.

* fix(credentials): settle a HubSpot key account-info refuses with a contacts read

HubSpot documents service keys only against its CRM REST routes, not the
token-info or account-info routes. When account-info answers 401, confirm
on a one-contact read, where a 200 or JSON 403 proves the key is live and a
401 rejects it.

* test(security): move the typed base URL normalizer test next to input-validation

normalizeHttpBaseUrl and refuseUnsafeTypedUrl moved from the token validator module into input-validation; their test now lives with them.

* feat(credentials): saved SailPoint API client credential and credential-only block

A SailPoint credential holds a tenant plus an API client or personal access
token client id and secret. The minter shares the legacy block's token
exchange (now requestSailPointToken in lib/internal/sailpoint/client: the
client_credentials grant at the tenant's /oauth/token, redirects refused,
bounded 429 retries, and a SailPointAuthError carrying the status), so 4xx
maps to invalid_credentials and 5xx, throttling, or network failures to
provider_unavailable. The token lifetime comes from expires_in.

- The tenant is normalized to {tenant}.api.identitynow.com, the host
  SailPoint documents for API and token calls, from a tenant name, the API
  host, or the tenant's UI URL. Anything else (other domains, credentials,
  ports, more than one tenant label) is refused before the secret is sent.
  The legacy block's inline tenants, including identitynowgov.com hosts,
  are unchanged.
- The credential fills the hidden accessToken and the tenant (its API host);
  the operation sends that token as is and never re-mints it on a 401.
  Inline credentials keep their cached token and single 401 refresh.
- sailpoint_v2 is the credential-only successor; the legacy block keeps its
  inline tenant, client id, and secret and is hidden from the toolbar.

* feat(credentials): saved ZoomInfo client credentials and credential-only block

A ZoomInfo credential holds a client credentials app's client id and secret.
The tools already authenticate with that grant (Basic auth at
api.zoominfo.com/gtm/oauth/v1/token), so the minter shares the legacy
block's exchange, now requestZoomInfoToken: a refusal throws
ZoomInfoTokenError, which the operation answers exactly as before (HTTP 500
naming the provider status) and the minter maps to invalid_credentials for a
4xx or provider_unavailable for throttling, 5xx, or network failures.

- Save verifies by minting a token only; the token endpoint uses no credits
  and no data endpoint is called. Granted scopes are recorded as metadata.
- A client id with a colon or control characters is refused before any
  request, since it would corrupt the Basic credential.
- The token exchange and API calls refuse redirects (maxRedirects: 0); both
  go only to the fixed api.zoominfo.com host.
- The credential fills the tools' hidden accessToken, which replaces the
  inline client id and secret; zoominfo_v2 is the credential-only successor,
  and the legacy block keeps its inline credentials and is hidden from the
  toolbar. Search filters named like hosts are recorded as request data in
  the saved-credential contract check.

* refactor(gong): share one Basic auth header builder between the tools and the credential check

* fix(credentials): stop accepting an undocumented PlanetScale 403 as proof of a service token

PlanetScale documents 401 for a bad token and 404 for missing access, but gives no meaning to a 403 from listing organizations, so a JSON 403 now rejects like any other refusal. The token ID is checked in the account-id normalizer with the same printable, colon-free rule the tools' header builder applies.

* test(credentials): prove refused saved-credential hosts never reach the guarded egress fetch

The steer cases asserted only on the global fetch stub, but ServiceNow sends through the guarded egress transport, so its 'before sending the key' half never ran. Both transports now answer, so only the refusal can keep the key unsent.

* fix(credentials): tell ServiceNow and Gong users which hosts the saved credential supports

ServiceNow custom URLs are aliases of the instance's original service-now.com address, so the help text points custom-URL tenants there; on-premises instances are named as unsupported. Gong's help text names the fixed api.gong.io host the tools call.

* docs(credentials): document the in-band Twilio API key form and hint a half-entered key pair

The saved Twilio credential keeps an API key in the auth-token field as SKxxx:secret, Twilio's documented Basic username and password for a key. The provider id now records that choice and warns that a consumer needing the account auth token itself, such as webhook signature validation, must refuse a key pair. The connect form shows a hint while an SK value is not a complete SID:secret pair.

* fix(credentials): share the CrowdStrike token exchange and report a wrong Falcon cloud

- Legacy inline tokens are also dropped on a 403, so a scope granted after a
  failed run applies on the next one, as it did before tokens were cached.
- The saved-credential minter and the legacy path call one exchange,
  requestCrowdStrikeToken; the legacy path keeps following redirects.
- The minter never follows a redirect and reports one as invalid_credentials
  naming the API client's cloud from X-Cs-Region, instead of a provider outage.
- Drop the unused instanceUrl param and cap a saved-credential accessToken.

* fix(credentials): expire shared client-credentials tokens the provider rejects

A token served from the shared client_credential_token row was never expired
early, so a Shopify or Atlassian token revoked before its stated expiry kept
being served, and reconnecting with the same secret got the same dead token.

- invalidateCoordinatedClientToken expires exactly the rejected token
- saving or reconnecting re-mints once when the provider rejects the served token
- a 401 from a tool call on a service-account credential expires the token
- expired failure memos are swept by the shared coordinator, which Vanta now reuses
- drop the Atlassian product check that could never refuse anything

* fix(credentials): report a mistyped SailPoint tenant as not found and share site lists

- A SailPoint tenant host that does not resolve (ENOTFOUND) is site_not_found,
  not a provider outage; a transient resolver failure stays provider_unavailable.
- The tenant normalizer refuses `api`, SailPoint's own API domain, in every
  input form instead of minting at api.api.identitynow.com.
- Datadog site labels and the isDatadogSite guard live once in tools/datadog and
  serve the block, the connect modal, and the minter; CrowdStrike cloud labels
  derive from the cloud ids.

* fix(credentials): drop the unverified ZoomInfo token-credits claim and cap saved tokens

ZoomInfo's client-credentials docs do not say whether a token request uses
credits, so the docs and comments describe only what Sim does: connecting
requests a token and calls no data endpoint. A saved-credential accessToken is
capped at 16 KiB like SailPoint's.

* fix(credentials): share one Linear client-credentials token per app

Linear asks clients to reuse a token until shortly before it expires and to
request a new one after a 401, and caps an app at 1,000 tokens per workspace
through cleanup. Each worker minted a new 30-day token every hour instead.
Linear now goes through the coordinated token for its full lifetime, and the
401 path replaces it early; every token grant now coordinates, so the grant
registry maps providers straight to minters.

* fix(credentials): keep a credential's client-credentials mode on reconnect

A reconnect that sent only a new client secret for a Shopify, Linear, or
Atlassian client-credentials credential was routed to the pasted-token
builder and failed. The stored mode now carries forward unless the reconnect
sends a token. The connect modals no longer preselect a mode on reconnect, so
the admin restates it instead of silently switching modes.

* docs(credentials): align Zendesk, Linear, and Shopify copy with vendor docs

- mark Zendesk API tokens as deprecated by Zendesk in the picker, hint, and docs
- warn that a Linear client-credentials request with other scopes revokes tokens
- say where Shopify's 24-hour token is kept instead of claiming it is never stored
- reword comments that stated undocumented Linear and Zendesk behavior as fact

* fix(credentials): drop exports that only their own module uses

* fix(credentials): keep shared tokens on scope 401s and rate-limit tool-call expiry

A 401 whose body reports a scope mismatch (Atlassian's 'Unauthorized; scope
does not match', RFC 6750 insufficient_scope) comes with a valid token, so it
no longer expires the application's shared token. A tool-call rejection also
leaves a token minted within the last minute in service, so a call that keeps
failing re-mints at most once a minute. Connect-time identity proofs still
expire a rejected token unconditionally.

* fix(credentials): replace the shared Linear client-credentials token daily

Linear tokens live 30 days, but a revocation that does not surface as a 401
would otherwise keep a dead token in service until then. Same-scope tokens
coexist, so a daily replacement leaves in-flight calls valid.

* chore(credentials): regenerate block successors, tool metadata, deployment config, integration docs, OpenAPI and CLI API for credential auth wave 2

* test(credentials): drive the slow Entra answer from the propagation suite's fake clock

* fix(credentials): declare the Zendesk email and API token as replaced by the saved credential's token

* test(credentials): wait for the slow Entra answer with the shared sleep helper

* fix(crowdstrike): mint a new token for each legacy inline request again

The inline token cache held a Falcon token for up to 29 minutes, so a token
Falcon had since revoked or rescoped failed the next run. Legacy blocks mint
per request as they did before the saved credential.

* fix(credentials): drop a saved client-credential token a provider refuses

Internal operations such as CrowdStrike and SailPoint answer 401 or 403
when the provider refuses a saved credential's token. The executor now
drops that token from the per-instance cache so the next call mints a
replacement instead of reusing it until it nears expiry.

* test(credentials): serve the stored coordinated token until it is invalidated

The coordinated-token mock minted on every call, so the revoked Atlassian
token test passed with the invalidation removed. The mock now keeps one
token per application until invalidated.

* refactor(credentials): share one client-credentials token exchange

Atlassian, Linear, and Shopify each carried the same form POST, refusal
mapping, payload check, and lifetime cap. They now call one
exchangeClientCredentialsGrant, tested once, and keep only their URL,
extra fields, lifetime cap, and Shopify's refusal classifier.

* fix(credentials): read and decrypt a client-credentials secret once per resolution

Token providers and Atlassian read and decrypted the stored secret to pick
its mode, then read and decrypted it again to mint. The mode read now
carries into the mint, and both blob parsers share one mode check.

* refactor(credentials): share the credential-text check across minters

Fivetran, ZoomInfo, and Datadog each carried the same empty, control
character, and size check. They now call one assertCredentialText, with
Datadog also refusing spaces.

* refactor(zendesk): encode the API-token Basic credential in one place

The saved-credential minter and the tools each built the
{email}/token:{apiToken} Basic value. Both now call
zendeskApiTokenCredential, so the saved and inline sign-ins cannot differ.

* refactor(tools): declare the saved-credential token param once

CrowdStrike, ZoomInfo, and SailPoint tools each repeated the hidden
accessToken param a saved credential fills. They now use
savedCredentialTokenParam, with the same descriptions.

* fix(tools): name a missing credential field without naming Datadog

The generic credential-field labels named Datadog for applicationKey, so
any other tool mapping it would blame a Datadog key. Labels now carry
their article and stay provider-neutral.

* fix(security): redact Datadog application keys in logged inputs

No key-name pattern matched applicationKey, so a key typed into the
legacy Datadog block showed in plaintext in execution input logs.

* fix(credentials): match the Atlassian OAuth 2.0 hints to what Atlassian supports

In client-credentials mode the Jira client ID hint said one credential
covers Jira Service Management, which Atlassian does not list for OAuth
2.0 credentials. It now points Service Management at a scoped API token.
Also corrects the reconnect note on clientCredentials.preferred.

* refactor(credentials): share the client ID and secret fields across connect forms

The Atlassian and token-provider forms each built the client-credentials
fields, and only one labelled its secret input for assistive tech. Both
now render ClientCredentialsFields.

* docs(credentials): describe the saved credentials on their setup-guide pages

The PlanetScale, ServiceNow, Gong, and Twilio pages are the connect
modal's setup guides but still described only the legacy inline inputs.
Each now says how to add the credential, the hosts it accepts, and that
earlier blocks keep their inline values. SailPoint v2 names both the PAT
and the client-credentials API client.

* test(credentials): mock credential-service through the central @sim/testing mock

* fix(sailpoint): accept government tenant hosts on the saved credential, as the earlier block does

* fix(servicenow): accept custom and self-hosted https instance URLs on the saved credential, as the earlier block does

* fix(credentials): type the new test fixtures, unexport file-local types, and regenerate CLI docs

* fix(credentials): drop refused local tokens, keep exact ServiceNow passwords, and refuse non-http URL forms

- Forget a client-credential token cached on this instance when an external
  request answers 401, alongside expiring the coordinated token.
- Add preserveWhitespace to token descriptor fields; ServiceNow passwords keep
  their surrounding spaces through the connect form, contracts, and builder.
  Contracts no longer trim apiToken; Atlassian and GitLab builders trim it.
- Refuse any scheme other than http(s), and protocol-relative or backslash
  authorities, in the shared typed-URL helpers while bare host:port still works.
- Refuse client-credentials extra fields that would replace grant_type,
  client_id, or client_secret.
- Report a Datadog validate answer without a boolean valid field as a provider
  fault rather than invalid credentials.

* fix(credentials): report an aborted grant refusal as the abort, and keep the CrowdStrike cloud required

* fix(credentials): name a dropped connection separately from a malformed token response
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner November 12, 2026 00:21
@vercel

vercel Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs Skipped Skipped Nov 12, 2026 2:18am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The reviewed changes appear safe to merge; no actionable new issue was established.

Summary

This release adds saved credentials for more integrations, expands the API and CLI, guards workflow edits against concurrent changes, and updates dependencies.

  • Workflow tools can use saved credentials for twelve more integrations.
  • Workflow callers can read a compact outline and the live input schema.
  • API callers can list a workspace's schedules and human-paused runs.
  • Run details report the applied timeout and can name the failed block.
  • Workflow edits return a conflict when the graph changed during editing.
  • CLI pairing can request a read-only key or a key with an expiry.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Stored run error] --> C[Match failed trace blocks]
  B[Recorded block errors] --> C
  C --> D{Matching block found?}
  D -->|Yes| E[Return error with block details]
  D -->|No| F[Return error without block details]
Loading

Reviews (2) · Last reviewed commit: "improvement(table): document that a colu..." · Reviewed by Greptile

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 583 files

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Turn on auto-fix | Re-trigger cubic

Comment thread apps/sim/executor/utils/errors.ts Resolved
Comment thread .github/CI.md Resolved
…formed ZoomInfo access token (#9914)

- isVendorJsonForbidden lowercases Content-Type before matching, since media
  types are case-insensitive; a vendor 403 sent as Application/JSON no longer
  rejects a live key that only lacks a scope.
- requestZoomInfoToken requires a non-blank string access_token, so the
  saved-credential minter and the legacy inline path never cache or send a
  malformed token.
…sed it (#9916)

* fix(api): attribute a polled run's failure only to the block that caused it

A run's stored error was paired with any failed block in its trace when the
message classified as a generic failure, so a run-level fault or a branch that
failed after the settling error reported an unrelated block. The trace read now
names a block only when its name heads the run error or its recorded error is
exactly it, which keeps unprefixed loop and parallel failures attributed.

* fix(cli-auth): say when a requested key never expires
…er license and add the EKS install profile (#9917)

* feat(aws-marketplace): gate Enterprise on the AWS Marketplace container license and add the EKS install profile

* fix(aws-marketplace): read the license instead of checking a unit out, scope the IAM role to it, and bump the chart

* docs(aws-marketplace): say where the profile's images resolve, keep the RDS username tied to the template, and note why the license is read in the pod's Region
…dential paths (#9918)

* fix(credentials): age-gate 403 token eviction and stop memoizing transient mint failures

An internal operation's 403 now drops this instance's cached client-credential
token only once it is older than a minute, matching the external-request path,
so a 403 from a file-access refusal or per-record permission no longer re-mints
on every call. The coordinated token's failure memo keeps only refusals of the
credentials themselves; provider_unavailable propagates with no memo row.

* chore(credentials): drop redundant redirect options and correct SailPoint and HubSpot docs

fetchProvider already refuses redirects by default. Zendesk's hidden token param
uses savedCredentialTokenParam, the SailPoint minter takes its token URL from
resolveSailPointHosts, and the SailPoint host and HubSpot fallback TSDoc now
match the code.

* fix(credentials): keep the coordinated token's 30-second backoff for every failed exchange

Remembering only credential refusals in the shared row left the per-instance
mint cache and the Vanta chain still backing off on outages, so the policy
differed by layer. Keep the long-standing 30-second backoff everywhere and
document it.
…lient-token resolver (#9919)

* refactor(credentials): resolve Vanta tokens through the coordinated client-token resolver

vanta-token.ts carried a second copy of the lock-and-memo protocol in
coordinated-client-token.ts. Vanta now calls resolveCoordinatedClientToken and
invalidateCoordinatedClientToken through four optional inputs whose defaults
leave Shopify, Atlassian, and Linear rows unchanged:

- minRemainingMs: Vanta keeps its 30-second reuse margin
- failureKeyQualifier: Vanta's failure memo stays keyed by secret and scope
- accessTokenDigestTag: Vanta's token digest stays keyed by API domain
- pinnedState: Vanta's scope is stored with the token and checked against
  every stored record, expired ones included, under the lock

Row ids, the advisory lock name and key, token digests, and encrypted payloads
are unchanged, so old and new workers read each other's rows and contend on
the same lock during a rollout. Unit tests pin those formats for Vanta and for
the shared defaults; they pass against the previous code too.

* chore(credentials): keep the expired-failure sweep private to the coordinated resolver

* test(credentials): prove rolling-deploy token compatibility against real Postgres

Replace the mock-based unit tests for the coordinated resolver and the Vanta token chain
with integration tests that seed rows exactly as the previous build wrote them (inline
sha256 ids and digests, real encryptSecret) and read back what the new resolver writes.
They cover a fresh stored token, a remembered refusal, the expired-row scope pin,
mint and refusal payloads at the old ids, invalidation by the old digest, the old
build's advisory lock blocking a new resolution, and the default Shopify-style formats.
@waleedlatif1
waleedlatif1 merged commit 2124b78 into main Nov 12, 2026
119 of 120 checks passed

This branch was previously deployed

1 inactive deployment
Preview — 56f7ed75 Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants