Repository navigation
One SDK write operation for every write path - #43
Conversation
Every mutation now goes through AsyncBCClient.plan() (resolve once) and AsyncBCClient.write() (send, audit, return status + correlation id): CLI post/patch/delete/action, batch steps and rollback, SafeContext, MCP record writes, and plain SDK client.post(). - Transport returns BCResponse(body, status, correlation_id); the BC correlation id is read from `request-id` (BC never sent the x-ms-correlation-request-id header we looked for, so it was always empty). - Public client.resolve() replaces private _resolve_url everywhere; `get --company all` no longer lets the registry override --publisher and pages with --all. - client.pages(entity, query, company_id=) keeps top/skip/count for `get --all` and saved queries. - Batch writes and rollback deletes are audited; ledger rows carry the real correlation id; steps accept publisher/group/version. - MCP bcli_post/patch/delete run in-process via the SDK; remaining subprocess tools run off the event loop. - The dlt source runs on bcli's transport and honours disable_standard_api. - Tests drive the real client against a shared FakeTransport instead of AsyncMock clients. Verified against the SBEnvQD sandbox (reads, dry-runs, CLI/batch/MCP writes, rollback, audit and envelopes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ff5788d. Configure here.
| return None | ||
| try: | ||
| base = client._resolve_url(entity) # noqa: SLF001 | ||
| return client.resolve(entity, record_id=str(record_id)) |
There was a problem hiding this comment.
Batch rollback misses custom route
Medium Severity
A batch POST that uses publisher/group/version writes to that custom route, but _compose_rollback_url calls resolve without those fields, so the ledger stores a standard or registry URL. batch rollback then DELETEs a different API than the create, which 404s or can remove the wrong record. The write already has the planned URL; rollback composition never receives the step route.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ff5788d. Configure here.
| return client.plan( | ||
| action.upper(), step.get("endpoint", ""), | ||
| body=None if action == "delete" else (step.get("data") or {}), | ||
| record_id=record_id, |
There was a problem hiding this comment.
Batch POST treats id as key
Medium Severity
_plan_step always forwards a step's id into plan(..., record_id=...), including for action: post. The previous batch runner posted to the collection and ignored id on creates. A POST step that also has id now hits entity(id) instead of entity, so the create goes to the wrong URL.
Reviewed by Cursor Bugbot for commit ff5788d. Configure here.


Summary
Every mutation now goes through the SDK:
client.plan()resolves the URL once, andclient.write()sends the request, writes the audit entry, and returns the status and BC correlation id. CLI post/patch/delete/action, batch steps and rollback,SafeContext, MCP record writes and plainclient.post()all share this one path.x-ms-correlation-request-id, a header BC never sends. It now readsrequest-id, the value BC quotes asCorrelationIdin its own error messages. Audit entries, result envelopes and batch ledger rows now carry the real status and correlation ID.client.resolve()replaces the private_resolve_url.get --company allnow lets an explicit--publisherwin over the registry route and follows pages with--all.client.pages().get --alland saved queries keep--top,--skipand--count.publisher/group/version, and dry-run shows each step's URL.bcli_post,bcli_patchandbcli_deleterun in-process. The other tools still start abclisubprocess, but no longer block the event loop.disable_standard_api.Behaviour changes are listed in
CHANGELOG.mdunder Unreleased.Test plan
uv run pytest: 1257 passed. Tests now run the real client against a sharedFakeTransportinstead ofAsyncMockclients.uv run ruff check src tests--all/--skip,-c all, saved query, dry-runs, CLI post→patch→delete, batch run plus rollback, MCP post/delete. All test records were cleaned up.🤖 Generated with Claude Code
Note
Medium Risk
Centralizes every mutation and audit emission in the SDK; regressions could affect URL routing, dry-run parity, batch ledger metadata, or MCP write behavior across many entry points.
Overview
All BC mutations now share one SDK path:
client.plan()resolves the URL once (so dry-run and real runs match), andclient.write()executes the HTTP call, emits audit entries when enabled, and returnsWriteResultwith status and correlation id. CLIpost/patch/delete/action, batch steps and rollback,SafeContext, MCP record writes, and directclient.post/patch/deleteall route through this.Audit and debugging: Write auditing moved from CLI-only wrappers into
AsyncBCClient.writefor profile-based clients, withcallerset tocli,mcp, orsdk. The BC correlation id is read from therequest-idheader (not the unusedx-ms-correlation-request-id), so audit logs, result envelopes, and batch ledger rows now record real HTTP status and correlation ids.API and read fixes:
_resolve_urlis replaced by publicclient.resolve()and newclient.pages(); syncBCClientexposes the same.get --company allrespects explicit--publisher/group/versionand can paginate with--all;get --alland saved queries withall_pageskeep$top,$skip, and$count.MCP and ETL:
bcli_post/patch/deleterun in-process via the SDK (same audit/correlation behavior); other subprocess tools useasyncio.to_thread. The dlt bridge usesBCTransportand the profile registry’sdisable_standard_api/ allow-lists.Reviewed by Cursor Bugbot for commit ff5788d. Bugbot is set up for automated code reviews on this repo. Configure here.