fix(errors): 403 auth rejections no longer read as policy blocks - #197
Conversation
8.5.1 went stale mid-flight: Maven Central now serves 9.0.0
(released 2026-07-18, com.getaxonflow:axonflow-sdk latest/release).
The 9.0.0 breaking change is confined to the LangGraph MCP adapter
(connector_type/tool split), which none of the four examples touch;
all four build clean against the published 9.0.0 artifact from a
fresh local repository.
The Basic.java comment is updated to 'SDK <= 9.0.0': the 403
body.contains("blocked") misclassification is still present in the
published 9.0.0 (verified in the v9.0.0 tag and reproduced against
the published jar), so the tenant-mismatch fail-loud workaround
remains required until the library fix (#197) ships.
Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…tch [skip-runtime-e2e] (#196) * fix(examples): pin poms to SDK 8.5.1; fail loud on tenant-mismatch 403 Release-gate smoke against a live enterprise stack (v9.6.1, epic #2861) surfaced two example-level issues: - Example poms pinned stale SDK versions (basic at 6.1.0, the rest at 8.5.0), so smoke runs resolved remote Maven artifacts instead of the locally built 8.5.1 under test. Pin all four to 8.5.1. - basic swallowed a 403 tenant-mismatch rejection as a policy block and exited 0: the agent's error body carries a literal "blocked":false key, which trips the SDK's handleErrorResponse body.contains("blocked") heuristic and misclassifies the 403 as PolicyViolationException. Until the library fix ships, the example treats a Tenant mismatch message as the auth failure it is and exits 1 with a pointer to the AXONFLOW_CLIENT_ID/user-token tenant pairing requirement. Verified against the live stack: all four examples exit 0 with real LLM round-trips, and basic exits 1 on a deliberately mismatched client-id/token pairing. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com> * chore(ci): retrigger DoD gate after skip-runtime-e2e escape hatch No-diff commit: the DoD workflow only runs on opened/synchronize/reopened, so the [skip-runtime-e2e] title + justification body edit needs a fresh synchronize event to be evaluated against the updated payload. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com> * fix(examples): refresh example pom pins to published 9.0.0 8.5.1 went stale mid-flight: Maven Central now serves 9.0.0 (released 2026-07-18, com.getaxonflow:axonflow-sdk latest/release). The 9.0.0 breaking change is confined to the LangGraph MCP adapter (connector_type/tool split), which none of the four examples touch; all four build clean against the published 9.0.0 artifact from a fresh local repository. The Basic.java comment is updated to 'SDK <= 9.0.0': the 403 body.contains("blocked") misclassification is still present in the published 9.0.0 (verified in the v9.0.0 tag and reproduced against the published jar), so the tenant-mismatch fail-loud workaround remains required until the library fix (#197) ships. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com> --------- Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
Release-gate smoke for getaxonflow/axonflow-enterprise#2861 found that every agent error envelope carries a literal "blocked" JSON key (a tenant-mismatch rejection is {"success":false,"error":"Tenant mismatch","blocked":false}), so handleErrorResponse's body.contains("policy") || body.contains("blocked") substring heuristic classified EVERY 403 authorization rejection as PolicyViolationException. Callers following the documented pattern of treating policy blocks as an expected, non-fatal outcome silently swallowed real auth failures (wrong client-id/user-token tenant pairing) with exit 0. The 403 branch now parses the JSON body and treats a present "blocked" boolean as authoritative: true -> PolicyViolationException, false -> AuthenticationException. Only unparseable or blocked-less bodies fall back to the policy-phrase heuristic (policy / block_reason; the bare "blocked" substring no longer counts). This is the only substring classification site in the method — 401 always maps to AuthenticationException and the remaining branches key on status code alone. Tests: exact live tenant-mismatch envelope -> AuthenticationException; blocked:true envelope -> PolicyViolationException; unparseable-body fallback both ways (1316 unit tests green). New live-stack leg runtime-e2e/error_classification_403/ asserts both directions through proxyLLMCall against a real enterprise agent; it fails against the 8.5.1 jar and passes against this build. Release prep for 8.5.2: pom.xml bumped, CHANGELOG [Unreleased] renamed to [8.5.2] - 2026-07-10 with the fix noted, example poms pinned to 8.5.2 per the 8.5.1 release convention (CI re-syncs them to the parent version at run time). Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
50c25ca to
0ef9a25
Compare
|
Backlog-clearance verification (rebased head 0ef9a25): Rebase onto current main (post 9.0.0 release, #203 jackson 2.22.1, #196 example pins to published 9.0.0). Conflict resolution dropped the stale 8.5.2 release prep: The fix still binds - both discriminating directions run locally on the rebased head (JDK 17):
Mutation proof: with the fix hunk reverted to the old substring heuristic ( CI: all 17 checks green on 0ef9a25, including Contract Integration (WireMock), real-stack ubuntu/macos/windows, Validate Wire Shape, Validate Version Alignment, CodeQL and DCO: https://github.com/getaxonflow/axonflow-sdk-java/actions/runs/30837069806 https://github.com/getaxonflow/axonflow-sdk-java/actions/runs/30837069865 Note for the next release cut: published 9.0.0 does NOT contain this fix, and the fix changes the exception type thrown for 403 auth rejections ( Merging per backlog-clearance brief. |
Release-blocking library fix from the getaxonflow/axonflow-enterprise#2861 release-readiness gate.
Bug
Every agent error envelope carries a literal
"blocked"JSON key - a tenant-mismatch rejection is:{"success":false,"error":"Tenant mismatch","blocked":false} // HTTP 403so
handleErrorResponse's substring heuristicclassified every 403 authorization rejection as
PolicyViolationException. Callers following the documented pattern (policy block = expected, non-fatal outcome) silently swallowed real auth failures with exit 0 - exactly howexamples/basicpassed the smoke with a wrong client-id/user-token tenant pairing.Fix
The 403 branch parses the JSON body (Jackson, already in use) and treats a present
blockedboolean as authoritative:"blocked":true->PolicyViolationException"blocked":false->AuthenticationException(HTTP 403)blockedboolean -> legacy policy-phrase fallback (policy/block_reason; the bareblockedsubstring no longer counts)Audited the rest of the method: this was the only substring-classification site - 401 always maps to
AuthenticationException; 402/409/429/408/504 key on status code alone.Tests
AuthenticationExceptionwith message intact;blocked:trueenvelope ->PolicyViolationException; unparseable-body fallback in both directions.runtime-e2e/error_classification_403/(no skip hatch) asserting both directions throughproxyLLMCall.Live runtime-e2e evidence (enterprise stack, platform v9.6.1, agent :8080, original pre-rebase round)
Against this fix:
Same leg against the released 8.5.1 jar (proves the bug + the leg's sensitivity):
Rebase note (2026-08-03, backlog clearance)
Rebased onto main after the 9.0.0 release, #203 (jackson 2.22.1) and #196 (example pins to published 9.0.0). The original 8.5.2 release prep no longer applies and was dropped in the rebase:
pom.xmlstays at main's 9.0.0 - versioning belongs to the next release cut (published 9.0.0 does NOT contain this fix; per the semver policy the operator decides the next version, noting this changes the exception type thrown for 403 auth rejections).mvn packagefor users).[Unreleased]### Fixedinstead of a[8.5.2]heading.The library fix, unit tests and runtime-e2e leg are unchanged from the reviewed diff.
Refs getaxonflow/axonflow-enterprise#2861.