Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ and need no setup.
The complete, compiled example is [HostQuickStart.java](samples/src/main/java/dev/openan/workflow/engine/examples/demo/HostQuickStart.java).
It converts registry JSON maps to typed AgentCards, runs a remote task and local aggregation, cancels timed-out work,
and closes caller-owned transport resources. Run its main method in IDEA with three arguments: registry URL,
target AgentCard name, and credentials JSON path. The registry and target agent must be reachable; TLS verification is enabled.
target AgentCard name, and credentials JSON path. First set `REGISTRY_API_TOKEN` from the registry's configured identity provider; the example fails locally when it is missing. Registry credentials are independent from agent credentials and `ORCH_USERNAME` / `ORCH_PASSWORD`. The registry and target agent must be reachable; TLS verification is enabled.

```java
// In the samples module; HostQuickStart is example source, not a class shipped in the SDK jar.
Expand Down
44 changes: 43 additions & 1 deletion docs/en/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,10 @@ static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify)
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)
static Workflow load(String baseUrl, String psopId,
HttpAuthProvider auth, TlsPolicy tls)
static Workflow load(String baseUrl, String psopId,
HttpAuthProvider auth, TlsPolicy tls, LoadPsop.Timeouts timeouts)

static Workflow load(String baseUrl, String psopId)
```
Expand All @@ -403,6 +407,11 @@ static List<WorkflowSearchResult> search(
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN, HttpAuthProvider auth, TlsPolicy tls)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN, HttpAuthProvider auth, TlsPolicy tls,
LoadPsop.Timeouts timeouts)

static List<WorkflowSearchResult> search(
String baseUrl, String intent)
Expand All @@ -418,8 +427,39 @@ This is distinct from the engine's southbound HTTP/JSON-RPC verification policy.
default to 30 seconds. Supply
`new LoadPsop.Timeouts(connectTimeout, readTimeout)` to set positive per-call values.

### OrchestrationAccountAuth

```java
new OrchestrationAccountAuth(baseUrl, username, password, tls)
new OrchestrationAccountAuth(baseUrl, username, password, tls, loginTimeouts)
```

Implements HttpAuthProvider for PSOP loading/search. Uses the same account as the browser
login through POST /api/v1/auth/token (JSON username/password). A shared instance lazily
acquires and caches the opaque Bearer with a monotonic expiry budget; expiry renews by
login, not an OAuth2 refresh grant. Login responses must have a non-empty access_token,
Bearer token_type and positive integral expires_in. Login responses are size-bounded;
rejected/malformed responses never expose their body or parser cause.

LoadPsop read/search calls can invoke HttpAuthProvider.onUnauthorized(uri, rejectedHeaders)
once after 401. The account implementation invalidates only the rejected cached session;
the default hook returns false for external Bearer/custom providers. No 403/service-error
retry, business-write replay or anonymous/local-workflow fallback. Scope is restricted to
this center's PSOP API; redirects are not followed. Pass the same TLS policy to account
login and LoadPsop; HTTP exposes the password, so prefer verified HTTPS.

### RegistryClient

Authentication is independent of TLS. HttpAuthProvider.bearer(tokenSupplier) reads credentials for
every request; OAuth2 token acquisition/refresh belongs to the application. LoadPsop accessToken
is transmitted in Authorization: Bearer, never in query parameters.
HttpServiceException exposes getStatusCode() and isAuthenticationFailure(); errors are sanitized.
HttpResponseException exposes getStatusCode() for JSON/model decoding failures after a successful
HTTP response, without retaining the raw body or parser cause that may echo credentials.
TlsPolicy.strict()/trustedCa() verify trust and hostname, insecure() skips both checks per client,
and mutualTls() configures client identity. Old anonymous boolean constructors retain their
chain-only false semantics; prefer the explicit authentication/TLS constructors.

The two-argument constructor uses a 30-second complete-response deadline. Override it with
`new RegistryClient(url, sslVerify, Duration.ofSeconds(15))`; the duration must be positive.
The deadline includes response-body consumption, and interruption cancels the pending request.
Expand All @@ -429,7 +469,9 @@ Registry methods return JSON maps; convert them to AgentCard with AgentCardJacks
Fetch and register AgentCards from the Registry Center.

```java
new RegistryClient("https://127.0.0.1:5000",false)
new RegistryClient(url, HttpAuthProvider.bearer(tokenSupplier))
new RegistryClient(url, auth, TlsPolicy.strict())
new RegistryClient(url, auth, TlsPolicy.insecure(), Duration.ofSeconds(15))

List<Map<String, Object>> fetchAgentCards()

Expand Down
55 changes: 35 additions & 20 deletions docs/en/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,32 +256,47 @@ provider, not template overrides or production fallbacks.

### SSE Server (Spring WebFlux)

Create one shared account provider per host/base URL, not per request. Login and retrieval use the same TLS policy; credentials belong in the secret environment. Complete initial password change in the web UI first. Fetch cards independently with `RegistryClient(registryUrl, registryAuth, registryTls)`; do not reuse the orchestration token for registry or Agents.

```java
// Host-owned fields, constructed once; imports are in dev.openan.workflow.engine.registry.
private final TlsPolicy orchTls = TlsPolicy.strict();
private final HttpAuthProvider orchAuth = new OrchestrationAccountAuth(
baseUrl, System.getenv("ORCH_USERNAME"), System.getenv("ORCH_PASSWORD"), orchTls);
```



@GetMapping("/execute/{psopId}")
```java

@GetMapping(value = "/execute/{psopId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> execute(@PathVariable String psopId) {
Workflow workflow = LoadPsop.load(baseUrl, psopId, token, false);

return Flux.create(sink -> {
ExecutePsop.builder()
.psop(workflow)
.agentCards(cards)
.controlPoint(cp)
.eventCallback(new EventCallback() {
@Override
public void onEvent(String type, Map<String, Object> data) {
sink.next("data: " + toJson(type, data) + "\n\n");
}
})
.onFinish((r, e) -> {
sink.complete();
return CompletableFuture.completedFuture(null);
})
.execute();
});
// LoadPsop is blocking; keep login/retrieval off the reactive event loop.
return Mono.fromCallable(() -> LoadPsop.load(baseUrl, psopId, orchAuth, orchTls))
.subscribeOn(Schedulers.boundedElastic())
.flatMapMany(workflow -> Flux.create(sink -> {
CompletableFuture<ExecutionResult> execution = ExecutePsop.builder()
.psop(workflow)
.agentCards(cards)
.controlPoint(cp)
.eventCallback(new EventCallback() {
@Override
public void onEvent(String type, Map<String, Object> data) {
sink.next(toJson(type, data));
}
})
.execute();
execution.whenComplete((result, error) -> {
if (error != null) sink.error(error);
else sink.complete();
});
sink.onCancel(() -> execution.cancel(true));
}));
}
```

Imports for the reactive adapter: `reactor.core.publisher.Mono/Flux` and `reactor.core.scheduler.Schedulers` and `org.springframework.http.MediaType`; expose it with the host's SSE response media type/serialization. The framework owns SSE framing. For explicit machine Bearer mode, `HttpAuthProvider.bearer(tokenSupplier)` remains supported, with host-owned token lifecycle. Account PSOP reads have at most one 401 renewal/retry; writes are not replayed. See [account integration](INTEGRATION_GUIDE.md).

### Cancellation

ExecutePsop.builder().execute() returns a CompletableFuture. Cancellation prevents late callback results from being
Expand Down
62 changes: 54 additions & 8 deletions docs/en/INTEGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ AgentCard card = mapper.readValue(
new File("agentcard/my_agent.json"), AgentCard.class);

// Option B: From Registry Center
RegistryClient registry = new RegistryClient("https://127.0.0.1:5000", false);
HttpAuthProvider registryAuth = HttpAuthProvider.bearer(() -> System.getenv("REGISTRY_API_TOKEN"));
RegistryClient registry = new RegistryClient("https://127.0.0.1:5000", registryAuth, TlsPolicy.strict());
ObjectMapper cardMapper = new ObjectMapper().registerModule(new AgentCardJacksonModule());
List<AgentCard> cards = registry.fetchAgentCards().stream()
.map(raw -> cardMapper.convertValue(raw, AgentCard.class)).toList();
Expand All @@ -99,8 +100,9 @@ List<AgentCard> cards = registry.fetchAgentCards().stream()


RegistryClient defaults to a 30-second deadline including response-body consumption. For another budget, use
`new RegistryClient(url, true, Duration.ofSeconds(15))`; only positive durations are accepted. Query access tokens
are logged as `<anonymous>`; do not log raw discovery URLs from host code.
`new RegistryClient(url, registryAuth, TlsPolicy.strict(), Duration.ofSeconds(15))`; only positive
durations are accepted. All discovery and registration requests apply credentials immediately before sending.
Credentials are sent in headers, never in URL query parameters. Missing/invalid Bearer tokens fail locally.

### 4.3 Implement ControlPoint

Expand Down Expand Up @@ -547,13 +549,18 @@ Common event types: `START`, `STEP_START`, `TASK_REQUEST`,
## 11. Load Workflows from Orchestration Center

```java
// Center credentials are independent of A2A AuthProvider and agent credentials.
TlsPolicy orchTls = TlsPolicy.strict(); // Or trustedCa("/external/certs/ca.pem").
HttpAuthProvider orchAuth = new OrchestrationAccountAuth(
"https://orchestration.example.com", System.getenv("ORCH_USERNAME"),
System.getenv("ORCH_PASSWORD"), orchTls);
// Search by intent
List<WorkflowSearchResult> results = LoadPsop.search(
"https://orchestration.example.com", "analyze service request", 5, null, true);
"https://orchestration.example.com", "analyze service request", 5, orchAuth, orchTls);

// Load full workflow by ID
Workflow workflow = LoadPsop.load(
"https://orchestration.example.com", results.get(0).getWorkflowId(), null, true);
"https://orchestration.example.com", results.get(0).getWorkflowId(), orchAuth, orchTls);
```

## 12. Custom Extensions
Expand All @@ -566,17 +573,56 @@ metadata and activation URIs. AgentCard declarations never cause implicit genera

### 13.1 Orchestration-center HTTPS development

`LoadPsop.search/load` receives an explicit `sslVerify` argument. Keep it `true` in production. In a controlled
development environment, `false` disables certificate-chain and hostname checks for that connection only and emits a
`[Registry] INSECURE_TLS` warning. It does not modify JVM-wide TLS defaults.
`TlsPolicy.strict()` verifies certificate trust and hostname. Use `TlsPolicy.trustedCa(caPemPath)`
for a private/self-signed CA. `TlsPolicy.insecure()` explicitly skips both checks for the current
management client only (Python `verify=False` equivalent) and emits an `INSECURE_TLS` warning.
It does not modify JVM-wide TLS defaults or bypass Bearer authentication.

The HTTPS server must still present a certificate, and client-certificate requirements (mTLS) still apply.
This mode cannot authenticate the server and is vulnerable to interception; do not use it in production.
Keep verification enabled in production, configure matching server SANs and trust its CA in the running JVM.
For mTLS, use `TlsPolicy.mutualTls(ca, clientCert, pkcs8Key, password)`; the insecure overload
`TlsPolicy.insecure(clientCert, pkcs8Key, password)` retains client identity.
The dispatched-agent `caCertsPath` setting is not forwarded to LoadPsop.
LoadPsop never changes JVM-wide SSLContext, SocketFactory or HostnameVerifier defaults.
Dispatched-agent HTTP/JSON-RPC retains hostname checks when chain verification is disabled.

Existing LoadPsop methods retain their arguments, but now send accessToken in the Bearer header,
not the URL. Existing RegistryClient boolean constructors remain anonymous; their false setting
still disables only certificate-chain checks. Prefer the explicit authentication/TLS constructors.

`HttpAuthProvider.bearer(tokenSupplier)` reads the current token once per request. The application/IAM
client owns OAuth2 acquisition, caching and refresh; the SDK does not retry business writes on 401.
Custom authentication implements `headers(URI)` and returns business-defined header names/values.
Duplicate case-insensitive or reserved transport headers are rejected. Use `HttpAuthProvider.none()`
only when the service deliberately allows anonymous access or uses mTLS identity instead.

SpringSpnDemo uses `ORCH_USERNAME` and `ORCH_PASSWORD` (the login-page account),
`ORCH_VERIFY_SERVER` (default true), and optional `ORCH_CA_CERTS_PATH`, independently
of `A2A_SSL_VERIFY` and agent/Order credentials. Supply the password through an external
secret, never in committed YAML or command-line arguments. The shared account provider
logs in lazily at `POST /api/v1/auth/token`, sends `{username, password}` as JSON, and
caches `access_token` according to `expires_in`. Expiry is handled by logging in again;
this first-party endpoint is not an OAuth2 password grant or refresh-token server.

Only PSOP load/search may retry once after 401 with refreshed account credentials.
403, 429, service errors, TLS/network failures and rejected logins are not retried.
Credentials are scoped to the configured center's PSOP API, never query strings/cookies
or another origin. Login and PSOP calls must use the same center TLS policy; skipping
server verification does not skip account authentication. HTTP is supported for isolated
development, but exposes the login password; prefer verified HTTPS in deployments.
Share one `OrchestrationAccountAuth` instance across executions to reuse its thread-safe
session cache. No center credentials affect Task-T or Notification-T streams.

Alternatively set `ORCH_API_TOKEN` alone for an externally managed Bearer; do not combine
it with account credentials. Static/supplier Bearer providers do not opt into retries.
For dynamic/custom credentials, provide a bean named `orchestrationHttpAuthProvider`.
For mTLS or an application-owned TLS policy, provide a `TlsPolicy` bean named `orchestrationTlsPolicy`.
HostQuickStart requires `REGISTRY_API_TOKEN` for registry discovery.
Center failures (including 401/403, TLS/network errors and empty search results) never select a
different workflow. `--a2a.local-workflow-enabled=true` explicitly runs the offline sample workflow
without contacting the center; it is disabled by default and is not a production fallback.

### 13.2 Dispatched-agent endpoints

The engine consumes AgentCards and never starts dispatched-agent servers. The host agent owns AgentCard discovery,
Expand Down
42 changes: 41 additions & 1 deletion docs/zh/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify)
static Workflow load(String baseUrl, String psopId,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)
static Workflow load(String baseUrl, String psopId,
HttpAuthProvider auth, TlsPolicy tls)
static Workflow load(String baseUrl, String psopId,
HttpAuthProvider auth, TlsPolicy tls, LoadPsop.Timeouts timeouts)

static Workflow load(String baseUrl, String psopId)
```
Expand All @@ -378,6 +382,11 @@ static List<WorkflowSearchResult> search(
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN,
String accessToken, boolean sslVerify, LoadPsop.Timeouts timeouts)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN, HttpAuthProvider auth, TlsPolicy tls)
static List<WorkflowSearchResult> search(
String baseUrl, String intent, int topN, HttpAuthProvider auth, TlsPolicy tls,
LoadPsop.Timeouts timeouts)

static List<WorkflowSearchResult> search(
String baseUrl, String intent)
Expand All @@ -391,8 +400,37 @@ LoadPsop 的简便重载默认 `sslVerify=true`,使用 JVM 信任库并校验
生产环境必须保持验证并配置正确的服务端 SAN 与信任库;本设置不是引擎南向 HTTP/JSON-RPC 的 TLS 策略变更。 连接与读取超时默认均为
30 秒;可通过 `new LoadPsop.Timeouts(connectTimeout, readTimeout)` 为单次调用设置正值。

### OrchestrationAccountAuth

```java
new OrchestrationAccountAuth(baseUrl, username, password, tls)
new OrchestrationAccountAuth(baseUrl, username, password, tls, loginTimeouts)
```

实现 PSOP 加载/搜索的 HttpAuthProvider,使用与网页相同的账号向
POST /api/v1/auth/token 发送 username/password JSON。共享实例懒登录并以
单调时钟缓存 opaque Bearer;到期重新登录,不是 OAuth2 refresh grant。
响应要求非空 access_token、Bearer token_type 和正整数 expires_in。
登录响应有大小上限;错误及畸形响应不保留原始正文或解析 cause。

LoadPsop 仅在读/搜索返回 401 后调用一次
HttpAuthProvider.onUnauthorized(uri, rejectedHeaders)。账号实现只使被拒绝
的缓存会话失效;外部 Bearer/自定义提供器默认返回 false,不参与自动重试。
403/服务错误不重试,不重放业务写入,不回退匿名或本地工作流。
凭据限定在本中心 PSOP API,不跟随重定向。登录与 LoadPsop 传相同 TLS
策略;HTTP 明文传输密码,部署应优先使用验证对端的 HTTPS。

### RegistryClient

认证与 TLS 独立。HttpAuthProvider.bearer(tokenSupplier) 每次请求读取当前凭据;
OAuth2 获取/刷新由应用负责。LoadPsop 的 accessToken 改用 Authorization: Bearer,不进入查询参数。
HttpServiceException 提供 getStatusCode()、isAuthenticationFailure(),异常内容经过脱敏。
成功 HTTP 状态的 JSON/模型转换失败抛 HttpResponseException,提供 getStatusCode();
不携带原始正文或解析 cause,避免异常链泄露凭据。
TlsPolicy.strict()/trustedCa() 校验证书链与主机名,insecure() 对当前客户端关闭两项校验,
mutualTls() 配置客户端身份。旧匿名 boolean 构造的 false 仍只关闭证书链校验;
新集成优先使用显式认证/TLS 构造。

双参数构造默认使用 30 秒完整响应截止时间。可通过
`new RegistryClient(url, sslVerify, Duration.ofSeconds(15))` 设置正值预算,包含响应正文读取;线程中断会取消待处理请求。
注册中心方法返回 JSON Map,使用 AgentCardJacksonModule 转换为 AgentCard,详见集成指南。
Expand All @@ -401,7 +439,9 @@ LoadPsop 的简便重载默认 `sslVerify=true`,使用 JVM 信任库并校验
从注册中心获取和注册 AgentCard。

```java
new RegistryClient("https://127.0.0.1:5000",false)
new RegistryClient(url, HttpAuthProvider.bearer(tokenSupplier))
new RegistryClient(url, auth, TlsPolicy.strict())
new RegistryClient(url, auth, TlsPolicy.insecure(), Duration.ofSeconds(15))

List<Map<String, Object>> fetchAgentCards()

Expand Down
Loading
Loading