[#3254] audit model: additive real-wire fields + deprecations - #223
Merged
Conversation
saurabhjain1592
added a commit
that referenced
this pull request
Aug 3, 2026
Refreshes tests/fixtures/wire_shape_baseline.json via scripts/refresh_wire_shape_baseline.py against a checkout of getaxonflow/axonflow tag v9.13.0 (df027c788) docs/api. Stacked on the #3254 additive interim (PR #223): the audit fiction fields and the request_type search filter stay as consciously noted deprecated-pending-removal drift entries naming #3254 and the coordinated next-major burn-down. Regen result: 12 per-model drift entries, MCPCheckInputRequest drops out (spec now declares content_type + tool), 14 newly registered models, spec-bug-pending count 0. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…onse_time_ms, deprecate the seven fiction fields (#3254) Additive interim per the operator decision on getaxonflow/axonflow-enterprise#3254: the orchestrator has never served query_summary/success/blocked/risk_score/latency_ms/policy_violations/ metadata on the 9.x line; the real wire carries policy_decision, policy_details and response_time_ms. AuditSearchRequest gains action (the filter the 9.x server actually reads); request_type is deprecated on the search request (silently ignored server-side). - AuditLogEntry: three new defaulted fields; docstring deprecation notes on the seven fiction fields (kept, still parse, removal rides the next major); policy_details/metadata/policy_violations are null-tolerant (the orchestrator marshals a nil Go map/slice as JSON null - observed live, caught by the runtime-e2e suite) - AuditSearchRequest.action wired into _build_audit_search_body; request_type still sent when set (harmless, ignored) - tests: real v9.13.0 capture fixture (session 3254) + old-server tolerance + both-present + null-tolerance, action filter on the wire - runtime-e2e/audit_real_wire_fields: real-stack proof (typed parse of fresh rows, action filter read server-side, request_type no-op) - wire_shape_baseline: curated sdk_only entries for the new fields against the pre-v9 spec pin, notes name #3254 and the PR #214 pin bump that clears them - falsey_clobber baseline: line-number refresh only (comment insertion in client.py shifted 15 entries; finding set unchanged at 51) Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592
force-pushed
the
feat/3254-audit-real-wire-fields
branch
from
August 3, 2026 22:25
97be544 to
5d99c68
Compare
saurabhjain1592
added a commit
that referenced
this pull request
Aug 3, 2026
Refreshes tests/fixtures/wire_shape_baseline.json via scripts/refresh_wire_shape_baseline.py against a checkout of getaxonflow/axonflow tag v9.13.0 (df027c788) docs/api. Stacked on the #3254 additive interim (PR #223): the audit fiction fields and the request_type search filter stay as consciously noted deprecated-pending-removal drift entries naming #3254 and the coordinated next-major burn-down. Regen result: 12 per-model drift entries, MCPCheckInputRequest drops out (spec now declares content_type + tool), 14 newly registered models, spec-bug-pending count 0. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…oudly on a set-but-missing specs dir R3 round 1 follow-ups on #223: - CHANGELOG.md: a global editor hook had rewritten 129 em/en dashes across ~250 lines of already-published release entries (9.0.0, 8.5.1, ...) - retroactive mutation of dated release records. Restored from main byte-for-byte; the diff vs main is now exactly the 13-line Unreleased hunk (13 insertions, 0 deletions). - tests/test_wire_shape.py _specs_dir(): when AXONFLOW_OPENAPI_SPECS_DIR was SET but pointed at a missing directory, all 7 gate tests skipped silently with exit 0 - a broken CI specs checkout read as a green wire-shape gate. That case now pytest.fail()s with a clear message (7 errors, exit 1); an UNSET variable keeps the designed local-dev skip. Three guard tests pin all directions (unset skips, set+missing fails naming the variable, set+valid resolves); they run in the regular suite too. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592
added a commit
that referenced
this pull request
Aug 3, 2026
Refreshes tests/fixtures/wire_shape_baseline.json via scripts/refresh_wire_shape_baseline.py against a checkout of getaxonflow/axonflow tag v9.13.0 (df027c788) docs/api. Stacked on the #3254 additive interim (PR #223): the audit fiction fields and the request_type search filter stay as consciously noted deprecated-pending-removal drift entries naming #3254 and the coordinated next-major burn-down. Regen result: 12 per-model drift entries, MCPCheckInputRequest drops out (spec now declares content_type + tool), 14 newly registered models, spec-bug-pending count 0. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
…in the set-to-a-file direction R3 round 2 on #223: an EMPTY (or whitespace-only) value read as unset and produced the same 7 silent skips + exit 0 the round-1 fix targeted - a CI consumer wiring the variable from an expression that evaluates empty would get a green gate. Choice: FAIL on set-but-empty (rather than pinning empty-as-unset), stated in the test comment; unset keeps the designed local-dev skip. Also pins the set-to-a-FILE direction (behaviorally covered by is_dir(), previously unpinned) so a refactor to exists() cannot reopen the class. Proven end-to-end: empty env = 7 errors exit 1; unset = 7 skips; valid dir = 12 passed. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
saurabhjain1592
added a commit
that referenced
this pull request
Aug 3, 2026
Refreshes tests/fixtures/wire_shape_baseline.json via scripts/refresh_wire_shape_baseline.py against a checkout of getaxonflow/axonflow tag v9.13.0 (df027c788) docs/api. Stacked on the #3254 additive interim (PR #223): the audit fiction fields and the request_type search filter stay as consciously noted deprecated-pending-removal drift entries naming #3254 and the coordinated next-major burn-down. Regen result: 12 per-model drift entries, MCPCheckInputRequest drops out (spec now declares content_type + tool), 14 newly registered models, spec-bug-pending count 0. Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Additive interim for getaxonflow/axonflow-enterprise#3254: the Python SDK's audit read model was built to spec fiction -
platform/orchestrator/audit_logger.goAuditEntryhas never servedquery_summary/success/blocked/risk_score/latency_ms/policy_violations/metadataon the 9.x line, so every consumer reading audit entries gets silent zero-values today. This PR adds the real wire fields alongside the fiction fields (no removal, no rename - the coordinated removal rides the next major per the operator decision on #3254).Canonical field list (per the #3254 cross-SDK spec)
Read model
AuditLogEntry, three fields ADDED (all defaulted, absence-tolerant):policy_decisionallowed/blocked/redactedobserved in code,errorobserved live - documented as string, NOT an enumpolicy_decision: str = ""policy_detailspolicy_details: dict[str, Any] = {}(default_factory)response_time_msresponse_time_ms: int = 0(ge=0)Read model, seven fiction fields DEPRECATED (kept, keep parsing, keep JSON tags):
query_summary,success,blocked,risk_score,latency_ms,policy_violations,metadata.Search request
AuditSearchRequest: ADDEDaction(optional, the filter the 9.x server actually reads,audit_read_handlers.go; doc: "Filters by action/request type with verdict normalization on the server side"). DEPRECATEDrequest_typeon the search request only (the 9.x server does not read it; a search filtered only by it returns unfiltered results). It is still sent when set (harmless, ignored) -_build_audit_search_bodykeeps emitting it until the next major removes the field.Fields NOT touched (real on the wire):
id,request_id,timestamp,user_email,client_id,tenant_id,request_type(read model - it IS served),provider,model,tokens_used,data_residency,transfer_basis.Deprecation idiom chosen: docstring "Deprecated:" notes on each attribute line (full canonical wording) plus "Deprecated:" prefixes on the
Field(description=...)strings - the SDK's established idiom (seeToolContext.tool_type,axonflow/types.py, "Deprecated: use caller_name instead"). No runtimeDeprecationWarningon field access: pydantic v2'sField(deprecated=...)warns on every attribute read, which would fire inside the SDK's own serialization paths and on every consumer render loop; a documented deprecation is the interim contract, removal warnings ride the next major.Live capture (provenance: captured 2026-08-03 from an isolated community v9.13.0 stack, session 3254)
Raw
POST /api/v1/audit/searchresponse through the agent proxy (excerpt, entry 1 of 2):{ "id": "audit_1785794706_23m371y7", "timestamp": "2026-08-03T22:05:06.947296Z", "client_id": "community", "tenant_id": "community", "request_type": "tool_call_audit", "query": "Tool: s3254_blocked_probe", "policy_decision": "error", "policy_details": { "caller_name": "unknown", "error_message": "blocked by policy sys_sqli_or_true", "success": false, "tool_name": "s3254_blocked_probe" }, "response_time_ms": 0, "error_message": "blocked by policy sys_sqli_or_true" }ABSENT from the wire:
blocked,success,risk_score,latency_ms,query_summary,policy_violations,metadata- the seven fiction fields. Verdict spellings observed:allowed,error(andblockedin the live probe below) - the set is open, so the SDK documents the field as a plain string.The unmodified capture is committed as
tests/fixtures/audit_search_live_v9130.jsonwith the provenance comment at the fixture load site intests/test_audit.py.Additive-only public API proof
Diff of the exported audit-model surface (sorted
model_fieldswith requiredness), main vs this branch - additions only, no removal, no optionality or type change on any existing field:Wire-shape gate: red run before the curated baseline entries
Main's spec pin (
0bd92562, pre-v9) predates the real fields, so the new SDK fields read as sdk-only drift. Gate run against the pinned specs BEFORE adding the curated baseline entries (evidence of what the gate catches):Curated
per_model_driftentries added for exactly those four fields, notes naming #3254 and the PR #214 pin bump that clears them (note mechanism perscripts/refresh_wire_shape_baseline.py: notes are carried forward verbatim on regen). Gate re-run with the entries: 7/7 passed.openapi_specs_shais UNCHANGED in this PR - no spec-pin-bump label needed; the v9.13.0 pin refresh is stacked separately as #214.Test evidence
tests/test_audit.py::TestAuditRealWireShape(new):error) parses, no throwpolicy_details/metadata/policy_violationsset to JSON null parses (see the runtime finding below)actionsent on the wire,request_typestill sent when setsearch_audit_logs: 2/2 entries withpolicy_decisionpopulated (blocked,allowed) whileblocked=False/success=True/risk_score=0.0stay at defaults on every entry, andAuditSearchRequest(action="blocked")returns exactly the blocked-verdict entry (server reads the filter; arequest_type-only filter is a server-side no-op per the session-3254 captureaudit-search-reqtype-noop.json)Runtime E2E (real stack, new suite in this PR)
runtime-e2e/audit_real_wire_fields/test.pydrives the real SDK against a real running community v9.13.0 agent (session-3254 stack): writes a success-shaped and a failure-shaped row viaclient.audit_tool_call, polls the realPOST /api/v1/audit/search, and asserts on the TYPED model. Run output:Runtime finding the mock-based tests could not catch: the first live run FAILED with
pydantic ValidationError: policy_details Input should be a valid dictionary [input_value=None]- the orchestrator marshals a nil Go map as JSONnullon some real rows, so the new field must be NULL-tolerant, not merely absence-tolerant. Fixed with amode="before"validator coercingnullto{}onpolicy_details/metadata(andnullto[]onpolicy_violations, same class), pinned by the null-tolerance unit test and re-proven by the passing runtime run above.CHANGELOG
Unreleased section only, no version bump: Added
policy_decision/policy_details/response_time_ms(read model) andaction(search); Deprecated the seven fiction fields (read model) andrequest_type(search request) - removal rides the next major.R3 round 2 fix (third commit)
_specs_dir(): set-but-EMPTYAXONFLOW_OPENAPI_SPECS_DIRnow FAILS instead of reading as unset. An empty (or whitespace-only) value fell through the round-1 fix and still produced 7 silent skips + exit 0 - a CI consumer wiring the variable from an expression that evaluates empty would get a green gate, the exact class the fix targets. Choice: fail on set-but-empty (stated in the test comment); unset keeps the designed local-dev skip. Also pins the set-to-a-FILE direction (behaviorally covered byis_dir(), previously unpinned). Proven end-to-end: empty env = 7 errors, exit 1; unset = 7 skipped; valid dir = 12 passed. Suite now 1071 passed, 29 skipped.R3 round 1 fixes (second commit)
git diff main --numstat).tests/test_wire_shape.py_specs_dir(): a set-but-missing specs dir now FAILS instead of silently skipping. PreviouslyAXONFLOW_OPENAPI_SPECS_DIRset to a nonexistent path produced 7 silent skips and exit 0 - a broken CI specs checkout read as a green gate. Now: env unset keeps the designed local-dev skip (proven: 7 skipped); env set + missing dir fails loudly naming the variable (proven: 7 errors, exit 1); env set + valid dir runs the gate (proven: 10 passed). Three guard tests pin all directions and run in the regular suite as well.Items not modified in this PR (with justification)
DeprecationWarningon field access (see idiom note above): documented deprecation chosen per the #3254 interim contract.query/query_hash(served on the wire) are not modeled: out of the #3254 canonical interim scope; noted in thequery_summarydeprecation text.openapi_specs_sha) untouched: the v9.13.0 pin refresh re-lands as chore(contract): bump OpenAPI spec pin to community v9.13.0 #214, stacked on top of this PR.Refs getaxonflow/axonflow-enterprise#3254.