Skip to content

Add Azure AI Emulation (CognitiveServices + Machine Learning, ARM + Data Plane)#232

Merged
thzgajendra merged 12 commits into
stackshy:developmentfrom
thzgajendra:feat/azure-ai-sdk-compat
Jul 11, 2026
Merged

Add Azure AI Emulation (CognitiveServices + Machine Learning, ARM + Data Plane)#232
thzgajendra merged 12 commits into
stackshy:developmentfrom
thzgajendra:feat/azure-ai-sdk-compat

Conversation

@thzgajendra

@thzgajendra thzgajendra commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds full Azure AI emulation across all three layers, spanning both ARM providers that make up "Azure AI" — Microsoft.CognitiveServices (Azure AI Foundry / AI Studio, the AI Services resource, Azure OpenAI) and Microsoft.MachineLearningServices (Azure Machine Learning) — plus the data planes. Third AI platform after AWS SageMaker (#222) and GCP Vertex AI (#231).

  • Driver + in-memory provider (azureai/driver, providers/azure/azureai) — 92 operations: 31 CognitiveServices + 46 MachineLearningServices + 15 data plane.
  • SDK-compat ARM + data-plane servers (server/azure/azureai) — real armcognitiveservices / armmachinelearning clients (or any REST caller) on a custom endpoint hit these the same way they hit management.azure.com. ARM PUT returns the resource inline with a terminal provisioningState.
  • Portable Layer-1 wrapper (azureai/azureai.go) — recorder / metrics / rate-limit / error-injection / latency do() pipeline, mirroring sagemaker/vertexai.
  • Cross-cutting paritychaos.WrapAzureAI, default cost rates, auto-metrics to Azure Monitor, lifecycle state machines (compute start/stop, terminal provisioning states).

Coverage

CognitiveServices: accounts (CRUD, list by RG/sub, listKeys, regenerateKey, listModels, listSkus, listUsages), deployments, projects (AI Foundry), raiPolicies, commitmentPlans, privateEndpointConnections.
Data plane: Azure OpenAI chat/completions · completions · embeddings; AI Foundry Agents/Assistants (assistants, threads, messages, runs); AML /score.
MachineLearningServices: workspaces (Default/Hub/Project/FeatureStore), computes (+start/stop/restart), online/batch endpoints + deployments, jobs (+cancel), versioned assets (models/data/environments/components/featuresets), datastores, connections, schedules, registries.

Notes (honesty flags)

  • The shared azurearm.ParsePath only reaches {name}/{sub}/{subName}; AML nests deeper (onlineEndpoints/{e}/deployments/{d}, models/{n}/versions/{v}, computes/{c}/start), so the AML handler parses trailing segments itself.
  • AML's long tail is deliberately scoped to the ~14 canonical families above (the versioned-asset types share one uniform implementation). Niche types (labelingJobs, outboundRules, managed-network, etc.) are not included in this PR.
  • Assistants covers the core surface (assistants/threads/messages/runs), not every beta sub-route.

Test plan

  • go build ./...
  • go test ./... — all green
  • golangci-lint run clean on every new package (driver, provider, servers, Layer-1, chaos, cost)
  • ARM roundtrip tests over a real httptest server: account lifecycle, keys/catalogs, deployments/projects/raiPolicies/commitmentPlans/PEC; AML workspaces, compute lifecycle, online endpoints + deployments, jobs + assets, datastores/connections/schedules/registries
  • Data-plane roundtrip tests: chat/completions, embeddings, completions, assistants→threads→messages→runs, /score
  • Layer-1 wrapper tests (recording, injection, latency, result forwarding) and chaos tests (injected-failure + pass-through)

Update: Azure AI Search added (Microsoft.Search)

This PR now also adds Azure AI Search (the RAG/retrieval backbone) as its own azuresearch package — a distinct ARM provider — with full parity (53 operations: 19 control + 34 data plane).

  • Control plane (Microsoft.Search/searchServices): service CRUD, list by RG/sub, update; admin keys (list/regenerate), query keys (list/create/delete); shared private links + private-endpoint connections.
  • Data plane ({service}.search.windows.net): indexes; documents (upload/merge/delete batch, search +count, suggest, autocomplete, count, get-by-key); indexers (+run/reset/status); data sources; skillsets; synonym maps; aliases; service statistics.
  • Full parity: portable Layer-1 wrapper (azuresearch/azuresearch.go), chaos.WrapAzureSearch, cost rates, Azure Monitor auto-metrics.
  • Roundtrip tests over a real httptest server cover the control plane (lifecycle, keys, private links) and the entire data plane.

Notes: document search is a deterministic substring match (bounded by maxSearchTop) suitable for emulation, not a real scoring engine. Data-plane service scope is derived from the request Host subdomain.

Add the azureai driver interfaces and in-memory provider Mock spanning both
ARM providers that make up Azure AI — Microsoft.CognitiveServices (AI Foundry /
AI Studio / the AI Services resource / Azure OpenAI) and
Microsoft.MachineLearningServices (Azure ML) — plus the data planes: Azure
OpenAI inference (chat/completions, embeddings, completions), the AI Foundry
Agents/Assistants API, and AML online-endpoint scoring.

CognitiveServices covers accounts (CRUD, keys, models, skus, usages),
deployments, projects, raiPolicies, commitmentPlans, and private-endpoint
connections. MachineLearningServices covers workspaces, computes (with a
start/stop/restart state machine), online/batch endpoints + deployments, jobs,
versioned assets (models/data/environments/components/featuresets), datastores,
connections, schedules, and registries. Slices/maps are deep-copied on store
and return and mutators use copy-then-Set. Auto-metrics push to Azure Monitor
via SetMonitoring; wired into the Azure provider.
Serve Microsoft.CognitiveServices and Microsoft.MachineLearningServices over
the ARM JSON wire protocol, plus the Azure OpenAI inference / Assistants and
AML scoring data planes. Real armcognitiveservices / armmachinelearning clients
(and any REST caller) configured with a custom endpoint hit these handlers the
same way they hit management.azure.com.

CognitiveServices uses the shared azurearm.ParsePath; Azure ML nests deeper
(onlineEndpoints/{e}/deployments/{d}, models/{n}/versions/{v}, computes/{c}/start)
so its handler parses the trailing segments itself. The data plane is
host/path-routed on /openai/ and /score, with the account scope derived from the
request Host subdomain. Roundtrip tests start a real httptest server and cover
every family over HTTP. Wired into the Azure server Drivers bundle.
Add the portable azureai.AzureAI Layer-1 wrapper implementing the full
driver.AzureAI surface through a single do() pipeline (recorder, metrics, rate
limiting, error injection, latency), mirroring sagemaker/vertexai. Add a chaos
wrapper (chaos.WrapAzureAI) that injects failures on account/workspace and
deployment/job creation and the inference/scoring runtimes, plus default Azure
AI cost rates. Brings Azure AI to full parity with how the other AI services
participate in the cross-cutting layers.
…i-sdk-compat

# Conflicts:
#	cost/cost.go
#	docs/services.md
Add the azuresearch driver interfaces and in-memory provider Mock for Azure
AI Search (Microsoft.Search) — the ARM control plane (searchServices lifecycle,
admin/query keys, shared private links, private-endpoint connections) and the
{service}.search.windows.net data plane (indexes, documents with
upload/merge/delete + search/suggest/autocomplete/count/get, indexers with
run/reset/status, data sources, skillsets, synonym maps, aliases, service
statistics). Slices/maps are deep-copied and mutators copy-then-Set; document
search is a deterministic substring match bounded by maxSearchTop. Auto-metrics
push to Azure Monitor; wired into the Azure provider.
Serve Microsoft.Search/searchServices over ARM and the search data plane over
its host/path-routed surface (/indexes, /indexes/{i}/docs/*, /indexers,
/datasources, /skillsets, /synonymmaps, /aliases, /servicestats). The data-plane
service scope is derived from the {service}.search.windows.net Host subdomain.
Roundtrip tests start a real httptest server and cover service lifecycle + keys
+ private links and the full index/document/indexer/datasource/skillset/synonym/
alias/stats surface. Wired into the Azure server Drivers bundle.
Add the portable azuresearch.AzureSearch Layer-1 wrapper implementing the full
driver.AzureSearch surface through a do() pipeline (recorder, metrics, rate
limiting, error injection, latency), a chaos wrapper (chaos.WrapAzureSearch)
that injects failures on service/index creation, document indexing and the
query runtime, and default Azure AI Search cost rates.

@NitinKumar004 NitinKumar004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Azure AI + Azure AI Search SDK-compat

Great scope — two whole services (Cognitive Services + Azure OpenAI + Machine Learning, and AI Search) with control-plane + data-plane. It builds cleanly and the full go test ./... tree is green. I ran a multi-angle review (both server handlers, both provider mocks, and the wiring/chaos/cost surface) and verified findings against source. Dispatch/registration is correct — the ARM handlers are provider-scoped and the data-plane paths are disjoint from Databricks/Cosmos/Functions (one caveat below).

The headline: these aren't real-SDK roundtrip tests

Despite the *_roundtrip_test.go / sdk_roundtrip_test.go names, none of these tests import the real Azure SDKs (azsearch, armcognitiveservices, armmachinelearning, azopenai). They issue raw http.NewRequest calls with hand-built URLs and decode into map[string]any. Every other SDK-compat slice in this repo (Secrets, DNS, RDS, …) drives the real client, which is what actually proves wire compatibility. Because these don't, the contract bugs below ship green. Strong recommendation: add at least one real-SDK roundtrip per surface — it would have caught most of the inline findings.

Inline findings

Marked inline. The most impactful: mergeOrUpload drops fields, key rotation is a no-op in both services, $filter is ignored, AML /score collapses all endpoints to default, and the data-plane error code is numeric where the SDK expects a string.

Additional items (not inline)

  • Real-SDK OData forms untested: GET /indexes/{i}/docs('{key}') and /indexes('{name}') (paren form the SDK emits) won't route — dataplane.go matches parts[2] == "docs" / exact first-segment roots, so the paren form falls through. Untested because the tests use the slash form.
  • Blob shadowing (narrow): the Search data-plane Matches claims bare first segments (indexes, aliases, datasources, …); when both Search and Blob drivers are enabled, a blob container named aliases/indexes is captured by the Search handler.
  • embeddings drops non-string inputs: token-array inputs ([[1,2,3]], valid per the API) silently yield zero rows.
  • Loose action routing: /openai/deployments/{d}/chat/anything serves a chat completion (no validation of the trailing completions segment); unknown deployments aren't 404'd.
  • Chaos wrappers are selective: WrapAzureSearch/WrapAzureAI only intercept a few ops, so ServiceOutage("azuresearch") leaves reads/indexer/runtime ops live — confirm that's intentional (differs from wrappers_dns.go, which wraps every method).
  • Reuse: the five near-identical serve{Indexers,DataSources,Skillsets,SynonymMaps,Aliases} handlers could share a generic serveCollection[T] helper.

Overall: solid foundation, but I'd address the correctness findings and add real-SDK tests before merge. None of these are blockers to the architecture — they're fidelity/semantics fixes.

}

m.documents.Set(storeKey, merged)
default: // upload | mergeOrUpload

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mergeOrUpload silently overwrites instead of merging. This branch handles upload and mergeOrUpload as a full replace via documents.Set(...). For mergeOrUpload/merge on an existing doc, Azure preserves fields not present in the payload; here {id:1,name:"x",price:9} + mergeOrUpload {id:1,price:12} becomes {id:1,price:12}name is dropped. Merge into the existing stored doc for the merge actions.

matched := make([]driver.SearchResult, 0, len(docs))

for _, d := range docs {
if term == "" || term == "*" || docContains(d, term) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SearchDocuments ignores Filter/OrderBy/Top/Skip. The match loop only tests free-text term; req.Filter is never consulted (grep finds no reference). A query with filter: "price gt 100" returns every doc and a count over the full set. The GET search path (dataplane_docs.go) also drops $top/$skip/$orderby/$select.

Secondary: hashHex(resourceGroup, name, "admin-secondary"),
}

salt := "regen-" + m.now()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Admin-key regeneration is a no-op. The salted key is returned once but never persisted; ListAdminKeys always recomputes the unsalted deterministic value, so RegenerateAdminKeyListAdminKeys returns the original key. Key rotation can't be observed. (Same pattern for query keys, and for Cognitive Services in providers/azure/azureai/azureai.go:301.)

results := make([]driver.IndexResult, 0, len(actions))

for _, act := range actions {
docKey := fmt.Sprintf("%v", act.Document[kf])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keyless documents collide + delete-missing reports success. docKey := fmt.Sprintf("%v", act.Document[kf]) yields "<nil>" when the key field is absent, so every keyless doc maps to one store key (second overwrites first, CountDocuments→1). And a delete for a never-indexed key still returns Status:true/200 (memstore miss ignored), unlike the merge branch which correctly 404s.


salt := "regen-" + m.now()
if strings.EqualFold(keyName, "Key2") {
keys.Key2 = deterministicKey(resourceGroup, name, salt)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RegenerateAccountKey is a no-op. Keys are recomputed deterministically from (rg,name) with no key store, so the regenerated value is discarded on return; a subsequent ListAccountKeys returns the original key. Rotation round-trip is broken (mirrors the Search admin-key issue).


for k, a := range m.assets.All() {
if strings.HasPrefix(k, prefix) {
if cur, ok := latest[a.Name]; !ok || a.Version > cur.Version {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Latest" asset version chosen by lexical string compare. a.Version > cur.Version compares raw strings, so "10" > "9" is false — registering versions 1..10 reports 9 as latest. Compare numerically when the versions are integers.

Comment thread server/azure/azureai/dataplane.go Outdated
return
}

out, cerr := h.dp.ScoreOnlineEndpoint(r.Context(), accountFromHost(r.Host), body)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AML /score collapses every online endpoint to "default". The endpoint name comes from accountFromHost, which only recognizes .openai./.services.ai./.cognitiveservices. infixes — not the real AML host {endpoint}.{region}.inference.ml.azure.com. So ep1, ep2, … all map to "default" and endpoint identity/metrics are lost.

Comment thread server/azure/azureai/dataplane.go Outdated
func dpErr(w http.ResponseWriter, status int, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]any{"message": msg, "code": status}})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data-plane error envelope emits a numeric code. "code": status writes the HTTP status int, but Azure OpenAI / azcore.ResponseError treat error.code as a string identifier (e.g. "DeploymentNotFound"). A client matching on the string code mis-parses the error. (Same convention in azuresearch/dataplane.go.)


switch rp.SubResource {
case subAdminKeys:
keys, err := h.svc.ListAdminKeys(r.Context(), rg, name)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key actions respond to any HTTP method. serveServiceAction never checks r.Method, so GET .../searchServices/{name}/listAdminKeys returns primaryKey/secondaryKey (real Azure requires POST), and a stray GET .../deleteQueryKey/{k} deletes a key. The control serveService path does switch on method — mirror that here. (Azure AI serveAccountAction has the same gap.)

})
}

dpJSON(w, map[string]any{"value": out})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial-failure document batches always return HTTP 200. The provider correctly emits per-doc Status:false/404, but Azure returns 207 Multi-Status on partial success and the azsearch SDK keys its IndexBatchException off the 207. Because this always writes 200, callers never see partial-batch failures.

Resolve PR stackshy#232 review feedback across the CognitiveServices,
MachineLearningServices, and Azure AI Search provider and server layers:

- Search: mergeOrUpload now merges onto the existing document instead of
  replacing it; reject documents missing the key field; honor $filter
  (single OData comparison), $orderby, $top, $skip and $select; return
  207 Multi-Status on partial-failure index batches.
- Persist regenerated admin/query and Cognitive Services account keys so
  rotation is observable via a subsequent list.
- Sort ListMessages/ListAssistants by creation sequence for deterministic
  ordering; pick the latest asset version numerically, not lexically.
- Derive the AML online-endpoint name from the inference host so /score
  no longer collapses every endpoint to "default".
- Emit a string error.code (not the numeric status) in data-plane error
  envelopes, and enforce the correct HTTP method on key/account actions.

Add regression tests to the existing roundtrip test files.

@NitinKumar004 NitinKumar004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review after 891af743a — all 11 inline findings resolved ✅

Thanks for the thorough pass. I re-verified each finding against the current head and the whole tree still builds green with both packages' tests passing. Every concrete correctness bug I flagged is fixed:

Finding Fix
mergeOrUpload dropped fields now merges via mergeInto, preserving unspecified fields
$filter/$orderby/$top/$skip/$select ignored real OData filter parser + orderResults + paginate; plumbed through both the GET and POST search paths
admin/query & account key regen were no-ops persisted to a key store, salted with a monotonic seq — rotation is now observable via a subsequent list
keyless docs collided keyless/empty key now returns 400/Status:false instead of colliding on "<nil>"
ListMessages random order sorted by the monotonic sequence encoded in the id
"latest" asset version by lexical compare versionNewer compares numerically
AML /score collapsed to default endpointFromHost parses *.inference.ml.azure.com
numeric data-plane error code codeForStatus() returns string identifiers
key actions accepted any method method-map + 405 on both serveServiceAction and serveAccountAction
partial-batch always 200 emits 207 Multi-Status when any doc fails

One thing I got wrong in the original review: delete is idempotent in Azure AI Search — returning 200 for a missing key is correct, so keeping that behavior (with the added comment) is the right call. 👍

Still open

The main recommendation — real-SDK roundtrip tests — is still unaddressed. The *_roundtrip_test.go files continue to use hand-built http.NewRequest + map[string]any decoding; none import armcognitiveservices / armmachinelearning / azopenai / azsearch. The new cases are good regression coverage for the fixes above, but without at least one real-client roundtrip per surface we still can't prove wire compatibility — and it's what would settle the remaining items below. Every other SDK-compat slice in the repo drives the real client, so this is also a consistency gap.

Smaller items still open (all non-blocking):

  • Embeddings drop non-string input: parseEmbeddingInput only handles string / []string; a token-array input ([[1,2,3]], valid per the API) still yields zero rows.
  • OData paren-form routing: docKey() now strips a standalone ('key') segment, but the fused forms the SDK emits — /indexes('{name}') and /docs('{key}') — still don't route (Matches keys on the bare first segment; serveDocs requires parts[2] == "docs" exactly). This is exactly what a real-SDK test would surface.
  • Blob shadowing (narrow): the Search data-plane Matches still claims bare first segments (indexes, aliases, datasources, …), so a blob container with one of those names is captured by the Search handler when both drivers are enabled.
  • Chaos wrappers selective: WrapAzureSearch intercepts 4 ops and WrapAzureAI 8, so ServiceOutage(...) leaves reads/indexer/runtime ops live — fine if intentional, just flagging it differs from e.g. wrappers_dns.go which wraps every method.

Net: the correctness fixes are solid and verified. I'd still like a real-SDK roundtrip per surface before merge — it closes the loop on the paren-form/embeddings/host-parsing fidelity that hand-rolled HTTP tests can't.

Address the re-review follow-ups on PR stackshy#232:

- Embeddings: parseEmbeddingInput now accepts token-array inputs ([]int and
  [][]int) in addition to string / []string, rendering each to a synthetic
  string so every input yields one embedding row.
- Search data plane: normalize the fused OData key form the SDKs emit, so
  /indexes('name') and /docs('key') route identically to the slash forms.

Add real-client roundtrip tests driving the official Azure SDKs against the
in-memory server, proving wire compatibility per surface:

- armcognitiveservices: account lifecycle + key rotation
- armmachinelearning: workspace + versioned assets (numeric latest version)
- azopenai: chat completions + embeddings
- armsearch: service lifecycle + admin/query key rotation

Azure ships no Go data-plane search SDK, so the search data plane's wire
fidelity (paren-form routing) is covered by explicit fused-URL tests.
@thzgajendra

Copy link
Copy Markdown
Collaborator Author

Pushed 3a3db95 addressing the re-review.

Concrete bugs

  • Embeddings non-string inputparseEmbeddingInput now accepts string, []string, []int, and [][]int; token arrays are rendered to a synthetic string so each input yields exactly one embedding row.
  • OData paren-form routingsplitPath now expands the fused key form the SDKs emit, so /indexes('name') and /docs('key') route identically to the slash-separated forms (fixes both Matches and serveDocs). Covered by a new fused-URL test.

Real-SDK roundtrip tests (the main recommendation)

Added real-client roundtrips driving the official Azure SDKs against the in-memory server, one per available surface:

  • armcognitiveservices — account lifecycle + key rotation
  • armmachinelearning — workspace + versioned model assets (asserts numeric latest-version through the real client)
  • azopenai — chat completions + embeddings
  • armsearch — service lifecycle + admin/query key rotation (exercises key persistence + the method-enforcement fixes)

One honest caveat: Azure ships no Go data-plane search SDKazsearch/azsearchindex are not published Go modules (only control-plane armsearch exists). So the {service}.search.windows.net data plane can't be driven by a real Go client. I've proven control-plane wire compat with the real armsearch client, and covered the data-plane paren-form fidelity you flagged with explicit fused-URL tests. If you know of a data-plane search module I've missed, point me at it and I'll wire it in.

Intentionally left as-is (non-blocking, per your flags)

  • Blob shadowing — the Search data-plane Matches claiming bare roots is a real but narrow overlap only when both drivers share a server and a container is named indexes/aliases/etc. Leaving as-is for now; happy to host-gate Matches on .search. in a follow-up if you'd prefer.
  • Chaos wrappers selectivityWrapAzureSearch/WrapAzureAI wrapping a subset of ops is intentional (mutation/inference paths), but I'll expand to every method if you want parity with wrappers_dns.go.

go build, full go test ./..., and golangci-lint on the changed packages are green.

…i-sdk-compat

# Conflicts:
#	go.mod
#	server/azure/azure.go

@NitinKumar004 NitinKumar004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving ✅

Re-reviewed at the current head (merge-current with development). All prior findings are resolved and verified:

  • All 11 original correctness findings — fixed and confirmed against source.
  • Real-SDK roundtrip tests — the headline ask is done: azopenai, armcognitiveservices, armmachinelearning/v4, and armsearch now drive the in-memory server end-to-end, using BeginCreate/PollUntilDone (proving the ARM LRO/provisioning-state responses satisfy the real pollers) and asserting key rotation with NotEqual + a persistence re-read.
  • Embeddings now accept token-array inputs ([]int / [][]int); OData paren-form routing (indexes('x'), docs('k')) is normalized and covered by a dedicated test.
  • Merge integrated cleanly: go build ./... and the full go test ./... tree are green (185 packages), and the Azure AI/Search suites pass on the merged head.

Great work turning this around.

Non-blocking nit (fix on the way in, or as a follow-up)

golangci-lint (repo config enables prealloc) flags 3 spots in providers/azure/azuresearch/dataplane_more.go (lines 57, 133, 177) — out := make([]T, 0) should be make([]T, 0, len(childKeysUnder(...))). Trivial, but our merge gate is 0 lint issues.

Leaving as-is (acknowledged, minor)

  • Search data-plane Matches claims bare roots (indexes, aliases, …) — narrow blob-shadow overlap only when both drivers are enabled.
  • Chaos wrappers are selective (Search 4 ops, AI 8) — fine if intentional.

@thzgajendra thzgajendra merged commit e5ec174 into stackshy:development Jul 11, 2026
@thzgajendra thzgajendra deleted the feat/azure-ai-sdk-compat branch July 11, 2026 19:09
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.

2 participants