Outcome
Inspect the campaign’s current autonomy policy, immutable history and workspace emergency stop.
Before you start
- Verify the environment, credential, authorized organization and the published action permission workspace:read.
- Discover an accessible Engage workspace and bind resourceType=workspace and its resourceId separately from action input. Fixture identifiers grant no access.
Discover the target
- Discover campaign id through campaigns.list in the intended authorized workspace, then read campaigns.get to verify the target.
topolo whoami --json
topolo actions get app_topolo_engage.campaigns.policy.get --json
topolo workspaces list topolo-engage --json
Input fields
| Field | Required | Meaning and constraints |
|---|---|---|
id | Yes | Required campaign identifier discovered in this workspace. |
Validate, plan and execute
Set ENGAGE_WORKSPACE_ID to the identifier of your selected workspace. Prepare payload.json from the current input schema and discovered identifiers. Use the same payload for validation, planning and execution.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions validate app_topolo_engage.campaigns.policy.get --data @payload.json --json
topolo actions plan app_topolo_engage.campaigns.policy.get --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json
Execute the unchanged validated read in the selected workspace.
: "${ENGAGE_WORKSPACE_ID:?Set ENGAGE_WORKSPACE_ID to your selected workspace identifier}"
topolo actions call app_topolo_engage.campaigns.policy.get --resource-type workspace --resource-id "$ENGAGE_WORKSPACE_ID" --data @payload.json --json
For MCP, use topolo_get_action, topolo_validate_action, topolo_plan_action and topolo_call_action. Pass the selected resource context separately; confirm:true is required for mutations.
Worked requests and responses
Captured campaigns.policy.get · HTTP 200
Action input
{
"id": "eng_cmp_1"
}
Actual local response
{
"policy": {
"campaignId": "eng_cmp_1",
"version": 1,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
"versions": [
{
"campaignId": "eng_cmp_1",
"version": 1,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
}
],
"workspace": {
"emergencyStop": false,
"reason": null,
"updatedBy": null,
"updatedAt": null
}
}
Source test: campaign autonomy policy > starts a new campaign on version 1: per-message approval, email, no caps, every seat, not stopped. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured campaigns.policy.get · HTTP 200
Action input
{
"id": "eng_cmp_1"
}
Actual local response
{
"policy": {
"campaignId": "eng_cmp_1",
"version": 3,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": 3,
"seatAllowlist": [
"eng_seat_9"
],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
"versions": [
{
"campaignId": "eng_cmp_1",
"version": 3,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": 3,
"seatAllowlist": [
"eng_seat_9"
],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
{
"campaignId": "eng_cmp_1",
"version": 2,
"approvalMode": "per_batch",
"allowedChannels": [
"email"
],
"dailyVolumeCap": 20,
"maxTouches": 3,
"seatAllowlist": [
"eng_seat_9"
],
"emergencyStop": false,
"changeReason": "pilot",
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
{
"campaignId": "eng_cmp_1",
"version": 1,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
}
],
"workspace": {
"emergencyStop": false,
"reason": null,
"updatedBy": null,
"updatedAt": null
}
}
Source test: campaign autonomy policy > versions every change, keeps history and audits it. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured campaigns.policy.get · HTTP 200
Action input
{
"id": "eng_cmp_legacy"
}
Actual local response
{
"policy": {
"campaignId": "eng_cmp_legacy",
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"version": 0,
"createdBy": "system",
"createdAt": "2026-01-01T00:00:00.000Z"
},
"versions": [
{
"campaignId": "eng_cmp_legacy",
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"version": 0,
"createdBy": "system",
"createdAt": "2026-01-01T00:00:00.000Z"
}
],
"workspace": {
"emergencyStop": false,
"reason": null,
"updatedBy": null,
"updatedAt": null
}
}
Source test: campaign autonomy policy > gives a campaign created before policies the built-in version 0 until its first change. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Captured campaigns.policy.get · HTTP 200
Action input
{
"id": "eng_cmp_1"
}
Actual local response
{
"policy": {
"campaignId": "eng_cmp_1",
"version": 2,
"approvalMode": "none",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
"versions": [
{
"campaignId": "eng_cmp_1",
"version": 2,
"approvalMode": "none",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
},
{
"campaignId": "eng_cmp_1",
"version": 1,
"approvalMode": "per_message",
"allowedChannels": [
"email"
],
"dailyVolumeCap": null,
"maxTouches": null,
"seatAllowlist": [],
"emergencyStop": false,
"changeReason": null,
"createdBy": "user_1",
"createdAt": "2026-10-06T08:30:00.000Z"
}
],
"workspace": {
"emergencyStop": true,
"reason": "Incident",
"updatedBy": "user_1",
"updatedAt": "2026-10-06T08:30:00.000Z"
}
}
Source test: emergency stop and seat allowlist > the workspace stop holds every campaign until resumed. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Effects and response meaning
- Reads policy, versions[] newest first and workspace emergency-stop state. New campaigns start at policy version 1 with per_message approval; legacy campaigns with no stored policy expose built-in version 0 attributed to system.
- Policy fields include approvalMode, allowedChannels, caps, seatAllowlist and the campaign stop. Empty seatAllowlist allows every seat; null caps mean no campaign policy limit. A workspace stop and campaign stop are separate controls.
Verify the result
- Compare policy.campaignId to the discovered id, retain policy.version and every editable field before planning a change. Inspect both policy.emergencyStop and workspace.emergencyStop.
- For a pending-message decision, inspect the actual queue and its retained policyVersion separately. This read does not establish recipient eligibility or delivery.
Recover
Retry this read in the same verified workspace after a transient failure. On not_found, rediscover the campaign rather than guessing another organization or workspace.
Refused responses
Captured campaigns.policy.get · HTTP 404
Action input
{
"id": "eng_cmp_nope"
}
Actual local response
{
"error": {
"code": "not_found",
"message": "Campaign was not found in this workspace."
}
}
Source test: campaign autonomy policy > 404s an unknown campaign, refuses readers and invalid input, and 409s a concurrent change. Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
Error recovery
- HTTP 403
permission_denied: Stop and inspect the permission required by this action in the current credential. Do not broaden permissions or switch organization automatically. - HTTP 422
invalid_action_input: Correct the request against the current input schema and validate it again before planning. - HTTP 404
not_found: Rediscover the campaign using campaigns.list in the same authorized workspace, including status=archived when appropriate. Do not guess another organization or workspace.
Related actions
Evidence and limits
-
contracts/autonomy.mjs -
apps/api/src/autonomy/policy-domain.ts -
apps/api/src/autonomy/policy-handlers.ts -
apps/api/src/autonomy/policy-service.ts -
apps/api/src/autonomy/policy-repository.ts -
apps/api/src/autonomy/approvals-repository.ts -
apps/api/test/autonomy.test.ts -
Actual route handlers with SQLite repositories; synthetic identity/workspace grants and provider ports.
-
Widget/bootstrap use actual app handler but mocked worker runtime and app identity.
-
No gateway execution, live permissions, external delivery or deployed proof.
The examples are local route captures with synthetic context and simulated providers. They do not prove live authorization, CLI/MCP gateway execution, production state or delivery.