feat(AF-614): filter connector operations at schema import#618
Merged
babltiga merged 2 commits intoJul 20, 2026
Conversation
Real-world OpenAPI documents carry operations nobody should reach through
AccessFlow (/internal/**, /actuator/**, deprecated surfaces). Admins can now
declare an operation filter per schema so the connector's governed catalog only
contains operations they intend to expose.
The filter is persisted on api_schemas.operation_filter (new nullable JSONB) and
applied on the read path rather than at parse time: parsed_operations keeps the
complete catalog, so the filter is re-editable via PUT .../schemas/{id}/filter
without re-fetching a remote sourceUrl. listOperations is the single choke point,
so filtered-out operations are unreachable from /api-editor, text-to-API, and the
allowed_operations grant picker alike. operation_count is the post-filter count.
Dimensions: path glob, HTTP verb, operation-id glob, tag, and excludeDeprecated.
Include and exclude compose as "keep when every non-empty include matches and no
exclude matches" - exclude wins. ApiOperation gains tags + deprecated (boxed, so
pre-AF-614 rows deserialize as null instead of tripping FAIL_ON_NULL_FOR_PRIMITIVES);
both are OpenAPI-only. POST .../schemas/preview dry-runs a filter without
persisting. API_SCHEMA_UPLOADED audit metadata records the filter, total count,
and excluded count for uploads and filter edits.
An absent or empty filter is exactly the previous behaviour.
Closes #614
Contributor
Frontend Test Results1 258 tests 1 258 ✅ 4m 18s ⏱️ Results for commit 6cd2058. ♻️ This comment has been updated with latest results. |
Contributor
Contributor
Backend Code Coverage
|
The "edits an existing schema filter" test timed out at 15s on CI. It leaned on
whole-page findByRole(..., { name }) queries, which compute accessible names
across every mounted element — and AntD keeps all six tab panels mounted, so the
scan covered the entire settings page. getByRole('button', { name: 'Save' }) was
also ambiguous: the Config tab has its own Save button.
Query the modal through within(dialog) and use text queries instead. The file
drops from ~11.7s to ~1.5s, so the global 15s timeout is ample headroom again.
Contributor
Coverage Report for Frontend Coverage (frontend)
File Coverage
|
||||||||||||||||||||||||||||||||||||||
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.
Closes #614
What
Lets an admin declare, per schema upload, which operations to keep — so an API connector's governed operation catalog only ever contains operations they intend to expose. Real-world OpenAPI documents carry
/internal/**,/actuator/**, deprecated, and admin-only surfaces that previously could only be kept out downstream, per-grant.Design decision (the one the issue flagged)
Persist the filter + apply on read, not parse-time filtering:
parsed_operationskeeps the complete parsed catalog; the new nullableapi_schemas.operation_filterJSONB stores the filter.DefaultApiSchemaService.listOperationsapplies it — the single choke point that makes filtered-out operations unreachable from/api-editor, text-to-API, and theallowed_operationsgrant picker alike (they all read through it).operation_countis the post-filter (kept) count;totalOperationCountexposes the pre-filter size.PUT /api-connectors/{id}/schemas/{schemaId}/filterre-edits the filter without re-uploading the document or re-fetching a remotesourceUrl. Parse-time filtering wouldn't have kept the excluded surface off disk anyway —raw_contentstores the document regardless.Filter dimensions
Path glob, HTTP verb, operation-id glob, tag, and
excludeDeprecated. Evaluated as keep when every non-empty include dimension matches AND no exclude dimension matches — exclude wins. Globs use the same*-only syntax as the routing-policy table globs; verbs and tags are exact case-insensitive matches.ApiOperationgainstags+deprecated(boxedBooleanon purpose — schemas parsed before this change deserialize asnullrather than tripping Jackson'sFAIL_ON_NULL_FOR_PRIMITIVES). Both are OpenAPI-only; for GraphQL SDL / WSDL / gRPC proto the operation-id glob is the load-bearing dimension sincepathis synthesized.POST .../schemas/previewdry-runs a filter (kept/excluded counts + the dropped operations) without persisting or auditing.API_SCHEMA_UPLOADEDaudit metadata now carries the filter,total_operation_count, andexcluded_count, for uploads and filter edits alike (the edit path is taggedaction=filter_updated, reusing the action to avoidAuditActionenum fan-out).An absent or empty filter is exactly the previous behaviour.
Frontend
ApiConnectorSettingsPage→ Schema tab gains a collapsible Import filter (optional) section (exclude/include path globs, exclude verbs, exclude operation-id globs, exclude tags, exclude-deprecated checkbox), a Preview button reporting "Keeps K of N operations" plus the dropped list, an "N of M operations imported" line after upload, and a per-row Edit filter modal. Client-side caps (100 entries / 200 chars) mirror the backend Bean Validation.Docs & website updated
docs/03-data-model.md—operation_filtercolumn, post-filteroperation_countsemanticsdocs/04-api-spec.md— request shape, the two new endpoints, and an Operation import filter sectiondocs/05-backend.md— apigov "Operation import filter (AF-614)" prosedocs/06-frontend.md—ApiConnectorSettingsPagerowwebsite/index.html+website/docs/index.html— user-facing description of the filterNo new env var, no new dependency, no new
AuditAction→ no CLAUDE.md change.Tests
OperationFilterMatcherTest(14) — each dimension independently, exclude-beats-include, empty-filter passthrough, glob edge cases, tags, deprecatedOperationFilterTest(4) — canonicalization, defensive copying,isEmptyDefaultApiSchemaServiceTest— filter persisted, full catalog retained, applied on read,operation_countpost-filter,updateFilterrecomputes,previewFilterdoesn't persistApiSchemaControllerTest— upload/preview/edit paths and audit metadataOpenApiSchemaParserTest— tags + deprecated capturedApiConnectorSettingsPage.test.tsx— 5 new tests (net-new coverage; the Schema tab was previously untested)Verification: backend
Failures: 0across 5085 tests — the 687 errors are allCould not find a valid Docker environment(Testcontainers*IntegrationTestclasses; Docker isn't running locally, CI will run them).ApplicationModulesTest,ApiPackageDependencyTest, andMessagesParityTestgreen. Frontend: 1258/1258 tests, lint (0 errors), typecheck, and build all green.No new Playwright spec: schema upload isn't covered in
e2e/tests/today, and no existing spec drives the Schema tab or the operations catalog (verified), so nothing drifts. The new flow is covered by component tests instead.