Outcome
Choose whether an approved app connection uses all workspace pairs or linked workspace pairs, with explicit placement-impact and recovery limits.
POST /api/developer-console/connections/{consumerAppId}/{providerAppId}/workspace-scope
Action: app_topolo_developers.connections.workspace_scope.update.
Before you start
Use the original authenticated active organization with Developers connections:write and the selected service’s organization-admin authority (including its explicit platform-access branch). Review and approve the intended workspace-pair policy and possible placement access changes before confirmation. A same-value request still requires administrator authority.
No organization id is accepted in action input. This handler derive it from the authenticated user. No workspace resource selector is declared in these exact action inputs or passed by the selected handler; do not invent one or mistake consumer/provider workspaceScope for a credential binding. Shared console authentication/resource/profile initialization is outside these selected handlers; its existing owner-qualified context must remain in force. This source packet does not certify the whole request as zero-write, and no automatic deletion of any profile/mirror is a rollback.
Discover the original consumer/provider pair and read its saved connection before planning. Input is exactly consumerAppId,providerAppId and workspaceScope:all|linked; both ids match ^app_[a-z0-9]+(?:_[a-z0-9]+)+$. No organization id, individual workspace ids, expected revision, link-creation fields or retry key are accepted.
All five complete examples are synthetic source-derived UNRUN carriers. No scope, link, placement, grant, subscription or provider operation was performed; no app import/test was run. Immutable source matches the current published declaration revision, not served-runtime certification.
Request
These ids are fictional; discover the actual original pair and review the current plan before confirming a policy change.
{
"consumerAppId": "app_example_consumer",
"providerAppId": "app_example_provider",
"workspaceScope": "linked"
}
Policy and placement effects
POST /api/developer-console/connections/{consumerAppId}/{providerAppId}/workspace-scope changes the saved connection policy between all and linked. The policy concerns which consumer/provider workspace pairs may be used; it does not grant a person access to either workspace, create or approve workspace links, activate the connection or perform a provider action.
The selected helper first reads the exact original organization/consumer/provider record and treats truthy removedAt as no live row. Missing or soft-removed returns404 connection_not_found before its administrator check. A live row can have any stored status; this function does not require active status or recheck current app declarations/entitlements. Changing the policy does not approve a pending connection, clear errors or turn a revoked/degraded/paused record healthy.
It then requires actor.isAdmin; lack of admin returns403 even when the requested scope equals the saved scope. With admin and equal scope it returns200 data:{outcome:"unchanged",connection:<full stored row>,accessRemoved:0} without changing scope or reconciling placements. unchanged is not a placement-consistency or current-access certificate.
For a changed scope, it writes workspace_scope and updated_at on the still-unremoved connection. There is no expectedRevision or compare-and-set over the full operation. If the new value is linked, a separate query marks matching placements access_removed/statusReason workspace_link_required and updates updatedAt. A later failure can leave the scope saved before placement updates or before final response; do not assume an error means no change.
The placement query matches the original organization, consumer app and provider reference app; excludes soft-deleted rows and rows already access_removed; and only affects rows whose provider workspace differs from the organization id and lacks an active, nonremoved stored link for the exact consumer/provider workspace pair. It does not consult a live provider or fresh person grants. A stored status removed is not soft deletion and is not excluded by this query; other matching statuses can become access_removed.
When switching to all, this helper does not restore access_removed placements or refresh their provider snapshots. It returns accessRemoved:0 because it skips placement marking, not because it verified or repaired access. A separate authorized placement refresh may recheck a legitimate placement; it is not an automatic inverse of changing scope.
The complete normal200 envelope is {success:true,message:"OK",data:{outcome:"updated"|"unchanged",connection,accessRemoved}}. accessRemoved is the placement UPDATE rowsWritten count for this call, not a total affected-resource inventory. Connection is the full stored record, including both subscription collections and original declaration/approval/removal history. Its status/lastError are preserved. The final read is separate and uses a TypeScript nonnull assertion without a runtime null guard; a concurrently missing row may serialize connection:null.
The route validates the strict body combined with path ids. Invalid JSON/nonobject/array bodies, conflicting body/path ids, unsupported scope or other schema issues return422 invalid_input with nested details.issues. This error’s message is Invalid placement request even though the selected action changes a connection. Use the supported current CLI/MCP schema; do not send a different organization/workspace or bypass validation.
The selected helper performs connection/placement storage work only: it does not enable/disable the pair, provision/remove Notify subscriptions, mint keys, invoke providers or send a message. Shared console authentication/dependency initialization remains outside the helper’s zero-provider statement; preserve the original credential and context after refusals.
Worked responses
Narrow an administrator-reviewed pair to linked workspaces
Complete synthetic UNRUN HTTP200. The prior fictional row has workspaceScope all; two matching non-soft-deleted placements lack active stored workspace links. The scope write and placement marking both complete; full stored degraded history and subscription collections remain.
{
"success": true,
"message": "OK",
"data": {
"outcome": "updated",
"connection": {
"id": "conn_docs_fixture",
"organizationId": "org_docs_fixture",
"consumerAppId": "app_example_consumer",
"providerAppId": "app_example_provider",
"status": "degraded",
"workspaceScope": "linked",
"declaration": {
"actions": [
{
"action": "app_example_provider.items.list",
"scopes": [
"items:read"
],
"resource": "workspace",
"optional": false,
"purpose": "Read example items"
}
],
"events": []
},
"declarationDigest": null,
"declarationSource": "topolo.app.json",
"lastError": "Synthetic subscription provisioning failure",
"requestedBy": "user_docs_requester",
"requestedAt": "2026-10-21T08:00:00.000Z",
"approvedBy": "user_docs_approver",
"approvedAt": "2026-10-21T08:00:00.000Z",
"removedBy": null,
"removedAt": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-22T18:00:00.000Z",
"subscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.updated",
"eventVersion": 1,
"subscriptionId": null,
"status": "failed",
"lastError": "Synthetic provisioning failure",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
],
"referenceSubscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.reference.changed",
"eventVersion": 1,
"purpose": "changed",
"referenceTypes": [
"example.item"
],
"subscriptionId": "whs_docs_fixture",
"status": "provisioned",
"lastError": null,
"updatedAt": "2026-10-21T08:00:00.000Z"
}
]
},
"accessRemoved": 2
}
}
The policy is linked and two storage rows changed. Degraded status and lastError are preserved; no provider health or person access was established.
Widen the policy without restoring placement snapshots
Complete synthetic UNRUN HTTP200 starting from a live linked row with the same shown historical fields. The helper saves all and skips placement marking.
{
"success": true,
"message": "OK",
"data": {
"outcome": "updated",
"connection": {
"id": "conn_docs_fixture",
"organizationId": "org_docs_fixture",
"consumerAppId": "app_example_consumer",
"providerAppId": "app_example_provider",
"status": "degraded",
"workspaceScope": "all",
"declaration": {
"actions": [
{
"action": "app_example_provider.items.list",
"scopes": [
"items:read"
],
"resource": "workspace",
"optional": false,
"purpose": "Read example items"
}
],
"events": []
},
"declarationDigest": null,
"declarationSource": "topolo.app.json",
"lastError": "Synthetic subscription provisioning failure",
"requestedBy": "user_docs_requester",
"requestedAt": "2026-10-21T08:00:00.000Z",
"approvedBy": "user_docs_approver",
"approvedAt": "2026-10-21T08:00:00.000Z",
"removedBy": null,
"removedAt": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-22T18:00:00.000Z",
"subscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.updated",
"eventVersion": 1,
"subscriptionId": null,
"status": "failed",
"lastError": "Synthetic provisioning failure",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
],
"referenceSubscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.reference.changed",
"eventVersion": 1,
"purpose": "changed",
"referenceTypes": [
"example.item"
],
"subscriptionId": "whs_docs_fixture",
"status": "provisioned",
"lastError": null,
"updatedAt": "2026-10-21T08:00:00.000Z"
}
]
},
"accessRemoved": 0
}
}
Zero is not a repair count or proof of current access. Prior access_removed placements remain until a separate authorized workflow rechecks them.
A same-value administrator request does no reconciliation
Complete synthetic UNRUN HTTP200 for the exact prior live linked row. Original updatedAt and both subscription collections are unchanged; no placement query runs.
{
"success": true,
"message": "OK",
"data": {
"outcome": "unchanged",
"connection": {
"id": "conn_docs_fixture",
"organizationId": "org_docs_fixture",
"consumerAppId": "app_example_consumer",
"providerAppId": "app_example_provider",
"status": "degraded",
"workspaceScope": "linked",
"declaration": {
"actions": [
{
"action": "app_example_provider.items.list",
"scopes": [
"items:read"
],
"resource": "workspace",
"optional": false,
"purpose": "Read example items"
}
],
"events": []
},
"declarationDigest": null,
"declarationSource": "topolo.app.json",
"lastError": "Synthetic subscription provisioning failure",
"requestedBy": "user_docs_requester",
"requestedAt": "2026-10-21T08:00:00.000Z",
"approvedBy": "user_docs_approver",
"approvedAt": "2026-10-21T08:00:00.000Z",
"removedBy": null,
"removedAt": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z",
"subscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.updated",
"eventVersion": 1,
"subscriptionId": null,
"status": "failed",
"lastError": "Synthetic provisioning failure",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
],
"referenceSubscriptions": [
{
"sourceAppId": "app_example_provider",
"eventType": "example.reference.changed",
"eventVersion": 1,
"purpose": "changed",
"referenceTypes": [
"example.item"
],
"subscriptionId": "whs_docs_fixture",
"status": "provisioned",
"lastError": null,
"updatedAt": "2026-10-21T08:00:00.000Z"
}
]
},
"accessRemoved": 0
}
}
A replay after an earlier partial failure may take this branch without completing missed placement updates. Inspect actual state and use owner repair rather than assuming idempotent completion.
Action permission without administrator authority is insufficient
Complete synthetic UNRUN HTTP403 after a live row is found, including when the saved value already equals linked.
{
"success": false,
"error": {
"message": "Only an organization admin can change a connection workspace scope",
"code": "organization_admin_required"
}
}
Do not expand grants or switch identity automatically; have the legitimate organization administrator review the actual policy change.
There is no live connection in the original organization
Complete synthetic UNRUN HTTP404 for absent or already soft-removed pair. This occurs before the helper admin check.
{
"success": false,
"error": {
"message": "app_example_consumer has no connection to app_example_provider in this organization",
"code": "connection_not_found"
}
}
Reconcile the discovered pair and retained removal outcome; do not create or enable a replacement as recovery.
Verify and recover
-
Read connections.status in the original organization and pair after any outcome. Compare its live stored connection.workspaceScope and id to the approved intent; interpret status, reasons and nullable entitlements separately from the policy value. Preserve full actual response/error and request id.
-
When narrowing, read separately authorized affected consumer placements and matching workspace links. Check actual workspace_link_required/access_removed results; the returned rowsWritten count is not proof that every relevant external resource or grant was inspected.
-
When widening, do not claim access_removed snapshots were restored. Propose only a separately authorized placement refresh after its current access and provider prerequisites are satisfied. Never enable a connection, approve links or widen grants merely to obtain a green result.
-
After timeout or failure, read current scope and actual placement state before a new approved action. A same-value replay returns unchanged and does not repair a missed placement update; route that state to the owner instead of flipping scope back and forth.
A separately approved change back to the prior scope is a new policy mutation, not an exact rollback of placement snapshots or grants. Read current policy and placement effects first. Same-value retries do not repair skipped placement work; do not flip policy to force a retry or auto-refresh/enable/approve links.
Related actions
- Inspect connection status before and after the approved change.
- List visible placements in the separately authorized consumer workspace.
Change log
- 2026-10-22: Added exact administrator/policy rules, unchanged versus updated responses, linked-workspace placement filtering, partial-write and non-restoration limits, and five complete synthetic UNRUN examples. No live policy/provider or business outcome is certified.