The intermediate migration added lease_generation only for the next
migration to remove it. Fold the drops into 20260608T090000Z with IF
EXISTS so fresh installs and databases that already ran 20260607 both
migrate cleanly.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Replace the single-case switch in the agent-run handler with an if,
and add the blank lines wsl_v5 requires around statements in the
approval checkpoint, worker tests, and policy-set registration.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Drop the isType helper that merely discarded the value already
returned by errors.AsType and inline the suspend and interrupt
checks directly into the result-handling branch.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Drop the lease, heartbeat, and stale-recovery test coverage that no
longer applies, and add coverage for the new behaviour: graceful
suspend returning a run to PENDING, an approval interruption parking it
in AWAITING_APPROVAL, and SubmitApproval merging decisions and
requeuing the run. Exercise the submitAgentRunApproval mutation end to
end, including the authorization paths for the relocated policies.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The worker leaned on a lease plus a heartbeat goroutine and a stale
recovery sweep to reclaim runs from crashed workers. That machinery
raced with long LLM and tool calls and conflated graceful stops with
failures. Remove the lease columns, heartbeat, and stale recovery, and
rely on FOR UPDATE SKIP LOCKED for single-claim plus explicit state
transitions: a graceful suspend returns the run to PENDING and a crash
now leaves it RUNNING for manual recovery.
Treat an approval interruption as a known stop that parks the run in
AWAITING_APPROVAL, and add SubmitApproval to merge human decisions into
the checkpoint and requeue the run to PENDING. The decisions must cover
exactly the pending approvals, since a missing one would resume as an
implicit denial. Expose this through the submitAgentRunApproval
mutation.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The agent-run actions and policies lived in the core probo policy set,
which forced every authorization change for the agent-run domain to
touch unrelated core files. Move the actions and the OWNER/ADMIN and
VIEWER/AUDITOR policies into the agentrun package and have it expose a
PolicySet that probod registers into the authorizer at composition
time, so the rules live alongside the domain logic they govern.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The nested-restore test only checked that an unresolvable inner agent
left its tool call in the checkpoint for a later retry. It did not
verify what happens to a sibling inner agent that does resolve and
complete during the same restore.
Add a resolvable done-agent alongside the missing inner-agent and
assert that its progress is persisted: its inner checkpoint is dropped
and its result recorded as a completed call, so a later retry replays
only the still-unresolved branch instead of re-running finished work.
Signed-off-by: Bryan Frimin <bryan@probo.com>
The single- and multi-level suspend/restore tests gated their slow
leaf tool on a manual release channel closed 50ms after cancel().
That sleep was a guess at how long the suspend signal takes to reach
the running sub-agent, so the post-tool turn boundary could observe
the release before cancellation and complete the run instead of
checkpointing, making the assertions timing-dependent.
Expose the per-run suspend signal through SuspendSignalFrom in an
export_test shim and have the leaf tools block on it directly. The
tool now returns only once the graceful-suspend signal has actually
propagated to its agent, so suspension is observed deterministically
without sleeps or release channels.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Each package that exercises the database against a real Postgres
carried its own copy of the connection bootstrap and schema setup.
Those copies had already drifted: some keyed off PROBO_TEST_PG_ADDR
with hardcoded defaults, others off PROBO_TEST_PG_URL, and the
agentrun/coredata suites hand-applied individual agent_runs
migrations to ensure the table existed.
Introduce a single test.PGClient helper that parses PROBO_TEST_PG_URL
(falling back to the local compose database), runs the full coredata
migration set once per process, and skips when no database is
reachable so make test stays a pure unit-test run. Migrate the
agentrun, coredata, cookiebanner, iam, and thirdparty suites onto it
and delete the duplicated helpers so the bootstrap can no longer
diverge.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Propagate graceful-suspend signals through detached run contexts and let
only opt-in suspendable tools re-attach cancellation, so AsTool sub-agents
can checkpoint and restore across nested trees while leaf tools keep
running detached.
Add focused agent and worker tests for single and multi-level suspend/
restore flows, plus heartbeat lease-loss and nested-restore error paths to
harden functional behavior under failure conditions.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Move agent-run orchestration from the legacy supervisor path into the new
agentrun worker/service package and wire it through coredata, server,
policies, and GraphQL resolvers.
This consolidates run lifecycle handling around lease-aware workers and
aligns API surface with the new agent-run domain model so reviewers can
follow one coherent execution path.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Auditors could not see the Context page in the console because
AuditorPolicy was missing core:organization-context:get. Grant the
read action (mirroring ViewerPolicy) so the sidebar item appears and
the context resolver succeeds for auditors.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Replace the hand-rolled credential chain (static, ECS endpoint, EC2
role) with config.LoadDefaultConfig, which handles the full standard
AWS credential resolution chain automatically.
Co-authored-by: Neil McGibbon <code@neilmcgibbon.com>
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Remove schema objects that are no longer referenced by any query code,
GraphQL resolver, or frontend and only survive in old migration files:
- users_organizations: superseded by iam_memberships (data was migrated in
20251006T220024Z).
- organizations.logo_object_key: pre-files-table logo storage; replaced by
logo_file_id / horizontal_logo_file_id (data migrated in 20251009T140000Z).
- trust_center_accesses.has_accepted_non_disclosure_agreement,
has_accepted_non_disclosure_agreement_metadata, nda_file_id and
last_token_expires_at: the NDA-acceptance flow now uses
electronic_signature_id together with the state column.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
@n8n/node-cli pulls LangChain and n8n-workflow packages that require
incompatible uuid versions (^10, ^11, ^13). Force uuid 14.0.0 via npm
overrides and refresh the lockfile so audit can apply the security fix.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Instead of a toast, expired magic link tokens now return a TOKEN_EXPIRED
GraphQL error code and redirect users to a dedicated /magic-link-expired
page with a clear CTA to request a new link.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@probo.com>
Users were missing the expiry notice and hitting link errors. Replace the
small gray footer text with a prominent yellow callout box in the HTML
email, and prefix the plain-text version with IMPORTANT.
Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Signed-off-by: Bryan Frimin <bryan@probo.com>
Using localhost as the EHLO identity is the Go net/smtp default but is
rejected by strict relays such as Google Workspace. os.Hostname() is
the conventional SMTP client fallback and returns the actual machine or
pod name in production environments.
Signed-off-by: Bryan Frimin <bryan@probo.com>
Google Workspace SMTP relay (smtp-relay.gmail.com) rejects generic
EHLO identifiers such as "localhost", which is Go net/smtp's default.
Operators can now set SMTP_HELLO_NAME to a valid hostname so that
c.Hello() is called before StartTLS, satisfying strict relay policies.
Fixes https://github.com/getprobo/probo/issues/1284
Signed-off-by: Bryan Frimin <bryan@probo.com>
Rejecting one audit report via Slack could look like a blanket denial
when HIPAA and SOC 2 reports shared a filename. Use framework and
audit name in rejection emails.
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
Field exposes onValueChange(value) and wires the input's onChange itself,
so the explicit event-typed onChange handler was redundant.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
BuildTokenURLForDomain and BuildTokenURLForSite cannot be merged: a
provider's signed-state Site (e.g. Datadog's region key) is not always the
string its token host needs (the API domain). Spell that out at the switch
so the split is not mistaken for accidental duplication.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Drop the zendeskRole helper and use the raw API role, matching the other
drivers. Factor the duplicated RFC3339 timestamp parsing (parseZendeskTime
and parseDatadogTime were identical) into a shared parseRFC3339Ptr in
driver.go.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Pasting a full Zendesk URL with a path or query (e.g.
acme.zendesk.com/agent?x=1) left the trailing segments in place because
the .zendesk.com suffix no longer matched at the end, producing an
invalid site value rejected by the backend. Drop everything from the
first path/query/fragment separator before stripping the suffix.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
A Registration that sets both BuildTokenURLForDomain and
BuildTokenURLForSite would pass startup but silently use only the first
at the OAuth callback (CompleteWithState checks them in order). Reject
the combination in Register so the misconfiguration fails loud at
process start, matching the existing APIKeyBasicAuth/APIKeyHeader guard.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add the Zendesk brand mark under ThirdParties and wire it into the
logo map. Because the customer subdomain is free-form (not a fixed
region list), the connect flow opens a dialog with a subdomain text
input that is sanitized and passed as ?site= to the OAuth initiate
endpoint, mirroring the Datadog multi-site dialog.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Zendesk is a multi-tenant OAuth connector keyed by the customer
subdomain. The customer enters it at connect time; it rides the signed
state to the callback, is re-validated, and is stored on the connector
settings to build the API host.
List staff (agents and admins) via GET /api/v2/users.json with cursor
pagination, mapping role, active/suspended, and 2FA status; end-users
are excluded. The subdomain is validated as a single DNS label at every
trust boundary to close the SSRF vector, and the data client keeps the
SSRF-protected transport.
Zendesk OAuth across customer subdomains requires a Zendesk-approved
global OAuth client; the connector goes live once those credentials are
supplied via bootstrap.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Subdomain-based OAuth providers (e.g. Zendesk) authenticate against a
per-customer host, <subdomain>.zendesk.com, for both the authorize and
the token endpoint. The subdomain is known at initiate but, unlike
Datadog's region, the provider does not echo it back on the callback,
so it must survive the round-trip another way.
Carry the chosen site on the HMAC-signed OAuth state (OAuth2State.Site)
and add a BuildTokenURLForSite closure, symmetric with the existing
BuildAuthURLForSite, that builds the token URL from it at callback time.
The state is signed, so a tampered site is rejected before use; the
closure still re-validates the format. Datadog's domain-echo path is
unchanged and a provider sets at most one of the two closures.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
OktaProvider was dead: the provider-string const is only matched by
OAuth providers (SlackProvider in oauth2.go / connector.go), and an
API-key connector never matches on it.
Wrap the url.Parse error in nextPageURL with %w, like every sibling
driver, instead of dropping it.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The rel="next" link is taken from the provider response, so the host
check alone let a response downgrade the next request to http or move
it to another port on the same host. Reject a non-https scheme and any
explicit port in addition to a host mismatch, so the crawl stays on
the same https origin.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
The driver test cassette was regenerated by running the driver in
record mode against a live Okta org, which validated the field
mapping (id, status, created/activated/lastLogin, profile.*) and the
rel="self" Link header format against the real GET /api/v1/users
response.
The recording is then fully anonymized: the real domain, member
names, emails, user IDs, session cookie, and request-id are replaced
with synthetic values, and a second page with a rel="next" Link
header is added back to keep pagination coverage (the source org had
a single user). TestCassettesUseSyntheticEmails guards the result.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Add the Okta brand mark to the ThirdParties atoms (export, logo map,
and ThirdPartyLogo wiring) and map the generic "domain" extra setting
to the oktaDomain field in the add-access-source dialog so the
API-key connect flow submits the org domain.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
Register the Okta provider (SupportsAPIKey, APIKeyAuthScheme SSWS, a
required "domain" extra setting, and the driver/name-resolver
factories) and add it to the builtin registry.
The create-API-key resolver normalizes and validates oktaDomain into
OktaConnectorSettings, returning a static INVALID error that never
echoes operator input, and stamps the SSWS scheme onto the
connection. No picker, OAuth metadata, or probe URL: the token plus
domain identify exactly one org and the host is per-connection.
Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>