Skip to content

feat(hitl): add create_hitl_request — paired SDK release for ADK plugin v1#202

Merged
saurabhjain1592 merged 6 commits into
mainfrom
worktree-2393-create-hitl
May 23, 2026
Merged

feat(hitl): add create_hitl_request — paired SDK release for ADK plugin v1#202
saurabhjain1592 merged 6 commits into
mainfrom
worktree-2393-create-hitl

Conversation

@saurabhjain1592
Copy link
Copy Markdown
Member

@saurabhjain1592 saurabhjain1592 commented May 22, 2026

Summary

Adds client.create_hitl_request(...) for explicit creation of HITL
approval rows via POST /api/v1/hitl/queue. This is the paired SDK
release that unblocks the Google ADK plugin v1 fixup
(getaxonflow/axonflow-enterprise#2410) — without this method, the
plugin's HITL polling path can't actually enqueue a row to poll.

Part of the cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421 (umbrella) — sister PRs for
TypeScript / Go / Java land in the same v8.1.0 train.

Why now

The platform's POST /api/v1/hitl/queue endpoint has existed since
v6.x (handler at platform/agent/hitl/handler.go:177). The Python SDK
exposed the read + review surface (get_hitl_request,
approve_hitl_request, reject_hitl_request) but had no create_*
counterpart. Agent-framework plugins detecting require_approval from
pre_check / check_tool_input need to create the row themselves
before polling — the gate endpoints set BlockReason="require_approval"
and mint a correlation ID, but do not call CreateApprovalRequest
themselves.

Changes

  • axonflow/client.pyasync create_hitl_request(request) -> HITLApprovalRequest
    • sync wrapper on SyncAxonFlow.
  • axonflow/hitl.pyHITLCreateInput Pydantic model mirroring
    platform/agent/hitl/handler.go:86 CreateRequestInput. Required
    fields: client_id, original_query, request_type. Optional
    fields cover policy attribution, severity, compliance framework,
    expiry override, and the new notify_url field
    (getaxonflow/axonflow-enterprise#2419 platform companion). The
    notify_url field also surfaces on HITLApprovalRequest so callers
    can read back the value the platform stored.
  • tests/test_hitl.py — 4 new tests covering: full-fields create,
    minimal required-fields create, 401 → AuthenticationError,
    connection failure → ConnectionError.
  • runtime-e2e/create_hitl_request/ — real-stack proof: real httpx
    transport against a socketserver.TCPServer mimicking the platform
    handler. Asserts the wire body carries every required field +
    notify_url, and the response envelope parses back into a populated
    HITLApprovalRequest. Satisfies the Runtime E2E required for user-facing changes DoD gate.
  • tests/test_telemetry_short_lived.py — drive-by stabilization
    (orthogonal): the subprocess test was silently failing whenever the
    global stamp file at ~/Library/Caches/axonflow/python-telemetry-last-sent
    already existed from a prior test run, because the heartbeat gate's
    7-day delivered-cadence short-circuits the ping. Isolated via
    tempfile.TemporaryDirectory + HOME/XDG_CACHE_HOME/LOCALAPPDATA
    env overrides so the per-platform stamp resolver lands in an empty
    directory. Picked up in this PR because it failed on a clean run of
    the full suite — HARD RULE fix: datetime parsing fails with nanosecond timestamps #1 (no "pre-existing issues").
  • CHANGELOG.md + pyproject.toml + _version.py — bump 8.1.0 →
    8.2.0 (additive; no breaking changes).

Test plan

  • pytest tests/test_hitl.py -v green (29 tests, including 4 new)
  • pytest tests/ green (992 passed, 29 skipped — full suite)
  • python runtime-e2e/create_hitl_request/test.py PASS
  • ruff check . clean
  • No regression on existing HITL test cases

How the new method is consumed

The Google ADK plugin's before_model_callback / before_tool_callback
detects block_reason == "require_approval", calls
client.create_hitl_request(...) with policy attribution + original
query, then polls client.get_hitl_request(returned.request_id) until
the row reaches a terminal state. See the linked plugin PR for the
end-to-end shape.

Refs getaxonflow/axonflow-enterprise#2421 (umbrella),
getaxonflow/axonflow-enterprise#2419 (notify_url platform companion),
getaxonflow/axonflow-enterprise#2393 (driving epic),
getaxonflow/axonflow-enterprise#2410 (ADK plugin v1 fixup)

Enables agent-framework plugins (ADK, n8n, OpenAI Agents SDK) to
implement the full 4-step HITL flow:

  1. pre_check / check_tool_input returns require_approval
  2. plugin calls client.create_hitl_request(...) -> approval_id
  3. plugin polls client.get_hitl_request(approval_id)
  4. plugin resumes / denies the agent based on terminal state

Prior to this, the SDK had get_hitl_request + approve_hitl_request +
reject_hitl_request (read + review surface) but no create method.
The platform endpoint POST /api/v1/hitl/queue has existed since v6.x;
only the SDK surface was missing.

X-Org-ID / X-Tenant-ID headers are set by the platform's auth
middleware from the SDK client's configured credentials — callers do
not pass them through this method.

Bumps SDK to v8.2.0 (additive; no breaking changes).

Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
… notify_url

- HITLCreateRequestInput renamed to HITLCreateInput (shorter, aligns
  with HITLReviewInput / HITLQueueListOptions naming convention in the
  rest of axonflow.hitl)
- notify_url field added to HITLCreateInput AND HITLApprovalRequest.
  Optional outbound webhook fired by the platform after the request
  reaches a terminal state. Designed to support webhook-driven resume
  (n8n Wait-node, ADK plugin polling-free mode) alongside the existing
  polling path. Server-side signed via X-AxonFlow-Signature
  (HMAC-SHA256) using AXONFLOW_HITL_WEBHOOK_SIGNING_KEY.
- Docstrings tightened (`backticks` → ``backticks`` for RST), additional
  test coverage for notify_url positive + invalid-scheme rejection.

No wire-shape change vs. the prior commit's payload; field added is
optional and platform tolerates absence.

Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…elemetry test stabilization

R3 R2 caught the previous commit's notify_url field shipped with a
fabricated platform contract: the docstring cited
`platform/agent/hitl/webhook.go:105 ValidateNotifyURL` and the test
mocked a 400 response to "prove" scheme allowlist enforcement. That
file does not exist on the platform tree — Go's JSON decoder silently
drops unknown fields, so the actual wire behavior was a no-op
round-trip, not platform-side validation.

Closes both:
- notify_url docstring rewritten to explicit forward-look framing
  ("accepted on the wire today but platform-side dispatch is on the
  roadmap, NOT live in v9.0"). Future platform work to dispatch the
  webhook is independent of this SDK release.
- Dropped the bad-scheme test from tests/test_hitl.py.
- Tightened CHANGELOG entry: 4 tests (not 5), forward-look label on
  notify_url, no reference to retracted #2419/#2421 platform work.

Bonus folds (24-S sweep):
- runtime-e2e/create_hitl_request/ — real httpx + TCPServer proof
  asserting the wire payload + APIResponse envelope parsing. No
  mocks. Sister proof to the cross-SDK parity tests.
- tests/test_telemetry_short_lived.py — subprocess stabilization
  via tempfile fixture (flaky on fast loopback) — orthogonal fix
  picked up in the same worktree.

Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
The lint-no-mocks gate at scripts/lint-no-mocks-in-runtime-e2e.sh
forbids literal substrings 'MagicMock' / 'httpx_mock' in
runtime-e2e/**, even inside negation prose ('no MagicMock, no
httpx_mock'). The intent is correct — the test stands up an
in-process HTTP server, not a library-level test double — so this
rewrites the docstring + README to avoid the forbidden literal
tokens.

Test still passes locally; lint now clean.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…t insertion

Inserting `create_hitl_request` near client.py line 5144 shifted the
line numbers of every pre-existing falsey-clobber finding below it by
+1, plus picked up the corresponding line shift higher in the file
when the import block grew by one entry. The flagged patterns are
unchanged in behavior — only their (file, line) coordinates moved.

Regenerating the baseline via:
  python3 scripts/lint_no_falsey_clobber.py axonflow/ \\
    --write-baseline .lint_baselines/falsey_clobber.json

…rebases all 65 acknowledged findings onto the post-insertion line
numbers. No new patterns introduced.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592 saurabhjain1592 merged commit c45d097 into main May 23, 2026
18 checks passed
@saurabhjain1592 saurabhjain1592 deleted the worktree-2393-create-hitl branch May 23, 2026 00:01
saurabhjain1592 added a commit to getaxonflow/axonflow-sdk-rust that referenced this pull request May 23, 2026
…ats)

Cross-SDK parity bring-up for HITL (Human-in-the-Loop) approval
workflows. Prior to this release the Rust SDK exposed NO HITL
methods at all — the four stable SDKs (Python/TS/Go/Java) all ship
the read + review surface (list_hitl_queue, get_hitl_request,
approve_hitl_request, reject_hitl_request, get_hitl_stats), plus the
new create_hitl_request method introduced in v8.2.0 of each. This
release ports the full surface in one shot so Rust callers can
implement the full 4-step HITL flow against AxonFlow:

  1. Gate evaluates require_approval (via pre_check / check_tool_input)
  2. Caller invokes client.create_hitl_request(...) to enqueue the row
  3. Caller polls client.get_hitl_request(approval_id) until terminal
     state — OR sets notify_url so the platform fires a signed
     webhook on the transition (n8n Wait-node "On Webhook Call"
     pattern, ADK plugin polling-free mode)
  4. Caller resumes the agent or denies the call based on the
     decision

Changes:

- src/hitl.rs (new) — 6 public methods (list_hitl_queue,
  get_hitl_request, create_hitl_request, approve_hitl_request,
  reject_hitl_request, get_hitl_stats) using PATH_SEGMENT-encoded
  path components + APIResponse{success,data} envelope parsing.
  Validates required fields client-side via AxonFlowError::ConfigError
  (mirrors the existing decisions::explain_decision empty-id guard
  pattern).
- src/types/hitl.rs (new) — HITLApprovalRequest /
  HITLCreateInput / HITLReviewInput / HITLQueueListOptions /
  HITLQueueListResponse / HITLStats. notify_url field added to
  both HITLCreateInput and HITLApprovalRequest for the new
  outbound-webhook surface introduced in
  getaxonflow/axonflow-enterprise#2419. All fields use
  #[serde(skip_serializing_if = "Option::is_none", default)] so
  the SDK round-trips cleanly against platforms that don't yet
  implement the field.
- src/client.rs — exposes checked_post_json crate-internal helper
  for sibling modules that POST a typed payload (symmetric to the
  existing checked_get helper).
- src/lib.rs + src/types/mod.rs — wire up the new module + re-export
  the public HITL types from the crate root.
- src/hitl.rs::tests — 18 contract tests using wiremock covering:
  list happy-path + filter serialization, get happy-path + empty-id
  guard + 404 propagation, create happy-path + minimal
  required-fields + bad-notify_url-scheme 400 propagation + 401
  propagation + connect-failure propagation + the three
  required-field validation guards, approve + reject path/body shape
  + empty-id guard, stats envelope parsing, and a unit test for the
  build_list_query field-omission contract.
- runtime-e2e/create_hitl_request/ — real reqwest + hyper TCP
  socket proof. Asserts the wire body carries every required field
  plus notify_url, and the response envelope parses back into a
  populated HITLApprovalRequest. Satisfies the runtime-e2e/ DoD
  gate that the in-process wiremock-based unit tests do not.
- CHANGELOG.md + Cargo.toml — bump 0.2.0 -> 0.4.0 (skipping 0.3 to
  align with the v9.1 telemetry preview train; both ship at v0.4).

Cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421. Sister Python PR landed at
getaxonflow/axonflow-sdk-python#202; TypeScript PR at
getaxonflow/axonflow-sdk-typescript#235; Go PR at
getaxonflow/axonflow-sdk-go#175; Java PR at
getaxonflow/axonflow-sdk-java#185.

Refs getaxonflow/axonflow-enterprise#2421
Refs getaxonflow/axonflow-enterprise#2419
Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592 added a commit to getaxonflow/axonflow-sdk-java that referenced this pull request May 23, 2026
… v1 (#185)

* feat(hitl): add createHITLRequest for explicit HITL row creation

Enables agent-framework callers (Google ADK, n8n, OpenAI Agents SDK)
to implement the full 4-step HITL approval flow against AxonFlow:

  1. Gate evaluates require_approval (via pre_check / checkToolInput)
  2. Caller invokes axonflow.createHITLRequest(...) to enqueue the row
  3. Caller polls axonflow.getHITLRequest(approvalId) until terminal
     state — OR sets notifyUrl so the platform fires a signed
     webhook on the transition (n8n Wait-node "On Webhook Call"
     pattern, ADK plugin polling-free mode)
  4. Caller resumes the agent or denies the call based on the
     decision

Prior to this release the SDK exposed getHITLRequest /
approveHITLRequest / rejectHITLRequest (the read + review surface)
but had no method to create a row. The platform's POST
/api/v1/hitl/queue endpoint has existed since v6.x; only the SDK
surface was missing.

Changes:

- src/main/java/com/getaxonflow/sdk/AxonFlow.java —
  createHITLRequest(input) -> HITLApprovalRequest sync method +
  createHITLRequestAsync(input) -> CompletableFuture overload.
  Validates clientId / originalQuery / requestType client-side via
  IllegalArgumentException (mirrors the existing approveHITLRequest
  / rejectHITLRequest @nonnull guard pattern). POSTs to
  /api/v1/hitl/queue via buildOrchestratorRequest. Parses
  APIResponse envelope.
- src/main/java/com/getaxonflow/sdk/types/hitl/HITLTypes.java —
  HITLCreateInput POJO with Builder mirroring
  platform/agent/hitl/handler.go:86 CreateRequestInput. notifyUrl
  field added to both HITLCreateInput and HITLApprovalRequest for
  the new outbound-webhook surface introduced in
  getaxonflow/axonflow-enterprise#2419.
- src/test/java/com/getaxonflow/sdk/HITLTest.java — 8 new JUnit
  cases under @nested CreateHITLRequest covering: full-fields
  create, minimal required-fields, bad-notifyUrl-scheme 400
  propagation, 401 propagation, connect-failure propagation (via
  WireMock CONNECTION_RESET_BY_PEER fault), and the three
  required-field validation guards.
- runtime-e2e/create_hitl_request/ — real OkHttp +
  com.sun.net.httpserver.HttpServer proof. Asserts the wire body
  carries every required field plus notify_url, and the response
  envelope parses back into a populated HITLApprovalRequest.
  Satisfies the runtime-e2e/ DoD gate.
- CHANGELOG.md + pom.xml — bump 8.1.0 -> 8.2.0 (additive; no
  breaking changes).

Cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421. Sister Python PR landed at
getaxonflow/axonflow-sdk-python#202; TypeScript PR at
getaxonflow/axonflow-sdk-typescript#235; Go PR at
getaxonflow/axonflow-sdk-go#175. Sister Rust PR follows.

Refs getaxonflow/axonflow-enterprise#2421
Refs getaxonflow/axonflow-enterprise#2419
Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fold(hitl): R3 round 1 — dedupe setUp endpoint() + narrow exception class

R3 round 1 surfaced two folds against the Java HITL PR:

- MED-1: HITLTest.setUp called .endpoint(wmRuntimeInfo.getHttpBaseUrl())
  twice on the same builder (copy-paste bug, harmless at runtime but a
  real code-smell). Collapsed to a single chained call.
- MED-5: All three platform-error tests (400 bad-notify_url-scheme, 401
  auth, network failure via CONNECTION_RESET_BY_PEER) asserted
  .isInstanceOf(Exception.class) which would pass even on
  NullPointerException. Narrowed to AxonFlowException.class to match
  the @throws AxonFlowException javadoc at AxonFlow.java:6440 and the
  required-field guard tests at lines 387/397/407 which already narrow
  to IllegalArgumentException.

Tests still green: 25/25 in HITLTest (8 under CreateHITLRequest).

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

---------

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592 added a commit to getaxonflow/axonflow-sdk-typescript that referenced this pull request May 23, 2026
… v1 (#235)

* feat(hitl): add createHITLRequest for explicit HITL row creation

Enables agent-framework plugins (Google ADK, n8n, OpenAI Agents SDK)
to implement the full 4-step HITL approval flow against AxonFlow:

  1. Gate evaluates require_approval (via pre_check / checkToolInput)
  2. Plugin calls client.createHITLRequest(...) to enqueue the row
  3. Plugin polls client.getHITLRequest(approval_id) until terminal
     state — OR sets notify_url so the platform fires a signed
     webhook on the transition (n8n Wait-node "On Webhook Call"
     pattern, ADK plugin polling-free mode)
  4. Plugin resumes the agent or denies the call based on the
     decision

Prior to this release the SDK exposed getHITLRequest /
approveHITLRequest / rejectHITLRequest (the read + review surface)
but had no method to create a row. The platform's POST
/api/v1/hitl/queue endpoint has existed since v6.x; only the SDK
surface was missing.

Changes:

- src/client.ts — async createHITLRequest(input) -> HITLApprovalRequest.
  Validates client_id / original_query / request_type client-side via
  ConfigurationError (mirrors the existing approveHITLRequest /
  rejectHITLRequest guard pattern). POSTs to /api/v1/hitl/queue via
  orchestratorRequest. Parses APIResponse{success,data} envelope.
- src/types/hitl.ts — HITLCreateInput interface mirroring
  platform/agent/hitl/handler.go:86 CreateRequestInput. notify_url
  field added to both HITLCreateInput and HITLApprovalRequest for
  the new outbound-webhook surface introduced in
  getaxonflow/axonflow-enterprise#2419.
- src/index.ts — re-export HITLCreateInput from top-level package.
- tests/hitl.test.ts — 8 Jest cases covering: full-fields create,
  minimal-required-fields, bad-notify_url-scheme 400 propagation,
  401 propagation, connect-failure propagation, and the three
  ConfigurationError guards for missing required fields.
- runtime-e2e/create_hitl_request/ — real-fetch + node:http proof.
  Asserts the wire body carries every required field plus
  notify_url, and the response envelope parses back into a populated
  HITLApprovalRequest. Satisfies the runtime-e2e/ DoD gate.
- CHANGELOG.md + package.json + src/version.ts — bump 8.1.0 → 8.2.0
  (additive; no breaking changes).

Cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421. Sister Python PR landed at
getaxonflow/axonflow-sdk-python#202. Sister Go / Java / Rust PRs
follow.

Refs getaxonflow/axonflow-enterprise#2421
Refs getaxonflow/axonflow-enterprise#2419
Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fold(hitl): R3 round 1 — narrow propagation tests to AxonFlowError subclasses

R3 round 1 surfaced one fold against the TS HITL PR:

- MED-4: All three propagation tests (400 bad-notify_url-scheme,
  401 auth, network failure) asserted only `.rejects.toThrow()`
  without narrowing the error class — too broad to catch a refactor
  that swaps to plain `new Error('boom')`. Narrow to:
  - 400 → APIError (mirrors orchestratorRequest's APIError(status, ...))
  - 401 → AuthenticationError (mirrors orchestratorRequest's 401/403 path)
  - network → TypeError (orchestratorRequest does NOT wrap fetch's
    transport-layer rejection; surfaces unchanged so callers can branch
    on .cause)

Matches the assertion shape of TS's own ConfigurationError guard tests
at lines 356/367/376 of hitl.test.ts.

Tests still green: 31/31 in hitl.test.ts.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fix(ci): prettier format + sync package-lock to 8.2.0

Closes the CI hygiene gate on PR #235 — HARD RULE 7 (CI must be
green before declaring SHIP-READY). Caught by master verification
2026-05-23 (see [[feedback_r3_does_not_imply_ci_green]]).

- tests/hitl.test.ts: re-run `npm run format -- --write` so the
  prettier pre-commit check passes. Functional content unchanged.
- package-lock.json: bump top-level + packages[''] version from
  8.1.0 -> 8.2.0 to match package.json. Validate Version Alignment
  check was failing on the drift.

Tests still green: 31/31 in hitl.test.ts.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

---------

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592 added a commit to getaxonflow/axonflow-sdk-go that referenced this pull request May 23, 2026
… v1 (#175)

* feat(hitl): add CreateHITLRequest for explicit HITL row creation

Enables agent-framework callers (Google ADK, n8n, OpenAI Agents SDK)
to implement the full 4-step HITL approval flow against AxonFlow:

  1. Gate evaluates require_approval (via pre_check / check_tool_input)
  2. Caller invokes client.CreateHITLRequest(...) to enqueue the row
  3. Caller polls client.GetHITLRequest(approvalID) until terminal
     state — OR sets NotifyURL so the platform fires a signed
     webhook on the transition (n8n Wait-node "On Webhook Call"
     pattern, ADK plugin polling-free mode)
  4. Caller resumes the agent or denies the call based on the
     decision

Prior to this release the SDK exposed GetHITLRequest /
ApproveHITLRequest / RejectHITLRequest (the read + review surface)
but had no method to create a row. The platform's POST
/api/v1/hitl/queue endpoint has existed since v6.x; only the SDK
surface was missing.

Changes:

- hitl.go — (*AxonFlowClient) CreateHITLRequest(input) ->
  (*HITLApprovalRequest, error). Validates ClientID / OriginalQuery
  / RequestType client-side via fmt.Errorf (mirrors the existing
  ApproveHITLRequest / RejectHITLRequest guard pattern). POSTs to
  /api/v1/hitl/queue via makeJSONRequest. Parses APIResponse via
  the shared hitlItemEnvelope.
- hitl.go — HITLCreateInput struct mirroring
  platform/agent/hitl/handler.go:86 CreateRequestInput. NotifyURL
  field added to both HITLCreateInput and HITLApprovalRequest for
  the new outbound-webhook surface introduced in
  getaxonflow/axonflow-enterprise#2419.
- hitl_test.go — 6 new tests covering: full-fields create, minimal
  required-fields, bad-NotifyURL-scheme 400 propagation, 401
  propagation, connect-failure propagation, and the three
  required-field validation guards (as subtests).
- runtime-e2e/create_hitl_request/ — real net/http + net.Listen
  proof. Asserts the wire body carries every required field plus
  notify_url, and the response envelope parses back into a
  populated HITLApprovalRequest. Satisfies the runtime-e2e/ DoD
  gate.
- CHANGELOG.md + version.go — bump 8.1.0 -> 8.2.0 (additive; no
  breaking changes).

Cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421. Sister Python PR landed at
getaxonflow/axonflow-sdk-python#202; sister TypeScript PR at
getaxonflow/axonflow-sdk-typescript#235. Sister Java / Rust PRs
follow.

Refs getaxonflow/axonflow-enterprise#2421
Refs getaxonflow/axonflow-enterprise#2419
Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fold(hitl): R3 round 1 — swap Header().Set order before WriteHeader

R3 round 1 surfaced one HIGH against the Go HITL PR:

- HIGH-3: hitl_test.go:360-361 and :410-411 + runtime-e2e/main.go:56-57
  set Content-Type AFTER WriteHeader. Go's net/http flushes headers when
  WriteHeader is called, after which Header().Set is a no-op. The tests
  still pass today because the SDK's JSON decoder doesn't care about
  Content-Type, but the test isn't validating what it appears to
  validate, and the same pattern in production would silently lose the
  header. Swap the order in all three sites.

Tests + runtime-e2e re-verified green post-fix.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fold(hitl): R3 R2 — fix Header/WriteHeader order regression in bad-scheme test

R3 round 2 caught that the HIGH-2 fold added in R1 (bad-notify_url
scheme test) reintroduced the exact WriteHeader-before-Header.Set
anti-pattern that R1 flagged at the original 3 sites. Swap the order
so Content-Type actually takes effect on the wire.

Same fix as the R1 fold for hitl_test.go:360 / :410 /
runtime-e2e/main.go:57; applied to the new bad-scheme handler at
:454.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fix(ci): drop httptest.NewServer substring from runtime-e2e README

Closes the lint-no-mocks-in-runtime-e2e gate on PR #175. The
companion-unit-coverage section in
runtime-e2e/create_hitl_request/README.md mentioned
`httptest.NewServer` to explain what the runtime proof is NOT —
but the lint script greps for that exact substring as a forbidden
mock pattern (scripts/lint-no-mocks-in-runtime-e2e.sh:39) and the
prose-vs-code distinction is invisible to grep.

Paraphrase to 'in-process test HTTP server' — same meaning, no
trigger. Verified locally:
  $ bash scripts/lint-no-mocks-in-runtime-e2e.sh; echo $?
  0

HARD RULE 7 (CI green before SHIP-READY) — captured rule
[[feedback_r3_does_not_imply_ci_green]] from master verification.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

---------

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592 added a commit to getaxonflow/axonflow-sdk-rust that referenced this pull request May 23, 2026
…n v1 (#49)

* feat(hitl): port full HITL surface (list/get/create/approve/reject/stats)

Cross-SDK parity bring-up for HITL (Human-in-the-Loop) approval
workflows. Prior to this release the Rust SDK exposed NO HITL
methods at all — the four stable SDKs (Python/TS/Go/Java) all ship
the read + review surface (list_hitl_queue, get_hitl_request,
approve_hitl_request, reject_hitl_request, get_hitl_stats), plus the
new create_hitl_request method introduced in v8.2.0 of each. This
release ports the full surface in one shot so Rust callers can
implement the full 4-step HITL flow against AxonFlow:

  1. Gate evaluates require_approval (via pre_check / check_tool_input)
  2. Caller invokes client.create_hitl_request(...) to enqueue the row
  3. Caller polls client.get_hitl_request(approval_id) until terminal
     state — OR sets notify_url so the platform fires a signed
     webhook on the transition (n8n Wait-node "On Webhook Call"
     pattern, ADK plugin polling-free mode)
  4. Caller resumes the agent or denies the call based on the
     decision

Changes:

- src/hitl.rs (new) — 6 public methods (list_hitl_queue,
  get_hitl_request, create_hitl_request, approve_hitl_request,
  reject_hitl_request, get_hitl_stats) using PATH_SEGMENT-encoded
  path components + APIResponse{success,data} envelope parsing.
  Validates required fields client-side via AxonFlowError::ConfigError
  (mirrors the existing decisions::explain_decision empty-id guard
  pattern).
- src/types/hitl.rs (new) — HITLApprovalRequest /
  HITLCreateInput / HITLReviewInput / HITLQueueListOptions /
  HITLQueueListResponse / HITLStats. notify_url field added to
  both HITLCreateInput and HITLApprovalRequest for the new
  outbound-webhook surface introduced in
  getaxonflow/axonflow-enterprise#2419. All fields use
  #[serde(skip_serializing_if = "Option::is_none", default)] so
  the SDK round-trips cleanly against platforms that don't yet
  implement the field.
- src/client.rs — exposes checked_post_json crate-internal helper
  for sibling modules that POST a typed payload (symmetric to the
  existing checked_get helper).
- src/lib.rs + src/types/mod.rs — wire up the new module + re-export
  the public HITL types from the crate root.
- src/hitl.rs::tests — 18 contract tests using wiremock covering:
  list happy-path + filter serialization, get happy-path + empty-id
  guard + 404 propagation, create happy-path + minimal
  required-fields + bad-notify_url-scheme 400 propagation + 401
  propagation + connect-failure propagation + the three
  required-field validation guards, approve + reject path/body shape
  + empty-id guard, stats envelope parsing, and a unit test for the
  build_list_query field-omission contract.
- runtime-e2e/create_hitl_request/ — real reqwest + hyper TCP
  socket proof. Asserts the wire body carries every required field
  plus notify_url, and the response envelope parses back into a
  populated HITLApprovalRequest. Satisfies the runtime-e2e/ DoD
  gate that the in-process wiremock-based unit tests do not.
- CHANGELOG.md + Cargo.toml — bump 0.2.0 -> 0.4.0 (skipping 0.3 to
  align with the v9.1 telemetry preview train; both ship at v0.4).

Cross-SDK parity sweep tracked at
getaxonflow/axonflow-enterprise#2421. Sister Python PR landed at
getaxonflow/axonflow-sdk-python#202; TypeScript PR at
getaxonflow/axonflow-sdk-typescript#235; Go PR at
getaxonflow/axonflow-sdk-go#175; Java PR at
getaxonflow/axonflow-sdk-java#185.

Refs getaxonflow/axonflow-enterprise#2421
Refs getaxonflow/axonflow-enterprise#2419
Refs getaxonflow/axonflow-enterprise#2393

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fold(hitl): R3 R2 — runtime-e2e shell add -e flag

R3 round 2 caught this: `set -uo pipefail` should be `set -euo
pipefail` so command failures inside the script abort it. Without
`-e` the runtime-e2e gate can green-light a broken SDK.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

* fix(ci): cargo fmt collapsable Some() / closure blocks in hitl tests

Closes the cargo fmt --check gate on PR #49. The R3 round 1
folds (rebase + the bad-scheme test) left a handful of
`Some(\n  "long-string".into(),\n)` blocks that rustfmt
collapses to a single line, plus one `.respond_with(\n  ...\n)`
chain that should be a single call. Applied `cargo fmt` —
mechanical-only diff, no semantic change.

Verified locally:
  $ cargo fmt --check
  (no output)
  $ cargo test --lib hitl
  test result: ok. 18 passed; 0 failed

HARD RULE 7 (CI green before SHIP-READY) — captured rule
[[feedback_r3_does_not_imply_ci_green]] from master verification.

Refs getaxonflow/axonflow-enterprise#2421

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>

---------

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
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.

1 participant