Topolo Docs Topolo Platform
Open workspace

Get campaign autonomy policy

Inspect the campaign’s current autonomy policy, immutable history and workspace emergency stop.

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

FieldRequiredMeaning and constraints
idYesRequired 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.

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.