Topolo Docs Topolo Platform
Open workspace

Replace organization notification action policy

Replace the complete organization-level policy sequentially with new rule ids; failures can leave partial rows and user scopes are preserved.

Outcome

Replace the complete organization-level policy sequentially with new rule ids; failures can leave partial rows and user scopes are preserved.

These examples prove local handler behavior with real isolated notification SQL and explicitly simulated identity dependencies.

Before you start

  • Verify pinned identity, the exact action and intended organization/user. Organization policy requires its manager/platform authority; user policy permits self/platform or organization management. Ordinary preference resolution for another user requires platform authority, not organization admin alone.

  • Before a whole-scope replacement capture complete prior semantic settings, including disabled rules and JSON objects. Review intended wildcard dimensions and effects. Empty rows clears that explicit scope; replacements are sequential, can fail partially and always create new rule ids.

  • Ordinary user resolution must omit Notify-only default_channel/transactional. Separate local service-context captures prove real ephemeral Notify signature checks, not a public gateway transport or live service credential. Do not copy service-only inputs into user OAuth calls.

  • Stored policy, candidate channels, resolved delivery mode, actual Notify dispatch/receipt, timers and application action approval are separate outcomes. This family sends no messages and performs no application action.

Discover the target

topolo whoami --json
topolo apps --json
topolo actions get app_topolo_auth.organization_notification_action_policy.replace --detail --json

Discover intended exact organization/user. Read organization_notification_action_policy.list and user_notification_action_policy.list for their separate scopes, plus ordinary global/organization preferences and stored channel flags. Resolve a specific actual producer/event/surface/category/priority context without Notify-only defaults on user OAuth.

Input fields

FieldRequiredMeaning
orgIdYesExact stored organization scope from authorized discovery. Organization rules require its management authority. User action-policy rules permit self/platform or that organization manager; the storage handler does not independently verify organization/user membership or existence.
rowsYesComplete explicit organization or user action-policy scope, up to 500 rows with unique six matching dimensions. Omitted dimensions become *. Empty rows clears that scope; replacement uses sequential writes and new rule ids, and can fail partially. Capture previous semantic settings before approval.
rows[].source_app_idNoSource application matching dimension: * wildcard or a 1–128-character discovered canonical id/slug using letters, digits, dot, underscore, colon or hyphen. Row omission defaults to *. Resolver omission matches its * context; keep the actual event producer identifier consistent.
rows[].event_typeNoSpecific two-to-four-segment lowercase event type, or * in a stored policy row. Resolver requires a specific event and refuses *. Stored row omission becomes *.
rows[].surfaceNoStored notification/action surface or * wildcard; omitted row value becomes *. Resolver uses notification by default and accepts notification/action only. This describes delivery policy, not permission to execute an application action.
rows[].categoryNoMatching event/action category string (same bounded dimension syntax) or *. Stored omission and resolver omission use *. Use the producer contract category rather than invent a label.
rows[].priorityNoMatching event/action priority string (same bounded dimension syntax) or *. Stored and resolver omission use *. Priority matching does not independently bypass delivery/approval rules.
rows[].channelNoStored matching dimension: email, push, sms, in_app, whatsapp or *. Omission becomes *. A specific rule channel must be in the resolver computed candidate channels; it does not create an address.
rows[].delivery_modeYesStored policy hint: immediate, digest, in_app_only or muted. Auth resolution converts muted plus required_action true to in_app_only while preserving the original stored row. Returning digest does not assemble or send a digest.
rows[].required_actionYesRequired boolean; when true it prevents matched muted policy from fully suppressing a required action by resolving to in_app_only. This is presentation/delivery policy, not action execution or user approval.
rows[].quiet_hoursNoOptional nullable JSON object, limited to 8192 serialized characters, stored and returned with its TEXT encoding. Auth does not enforce its schedule. Current Notify source understands start/end clock strings with timezone or explicit defer_until/next_at/ends_at; actual scheduling requires separate Notify/runtime proof.
rows[].digestNoOptional nullable JSON object, limited to 8192 serialized characters, retained as metadata. Auth does not assemble a digest or prove later delivery; verify the intended owning Notify workflow separately.
rows[].escalationNoOptional nullable JSON object, limited to 8192 serialized characters, stored with its TEXT encoding. Current Notify source reads explicit at/escalates_at or positive after_minutes; Auth resolution alone creates no timer or escalation delivery.
rows[].enabledYesRequired boolean. Disabled rules remain in storage lists but are excluded from effective action-policy lookup. Whole-scope replacement still retains or removes them according to the supplied complete set.

Effects

  • Sequentially replaces the complete explicit organization-level scope while keeping user rules.

  • Creates new rule identities even for unchanged settings; clearing rules changes effective policy without dispatching messages.

Interpret the response

Replaces every explicit organization-level row, preserving separate user scopes. Omitted dimensions become * and duplicate six-dimension keys are rejected before writes; rows [] clears only the selected explicit scope.

DELETE then individual INSERTs are sequential, not atomic, and every replacement generates new rule ids even for identical settings. A later failure can leave a partial set. Compare semantic settings and read current rows before reviewed recovery; do not reuse old rule ids.

Worked examples

These are complete raw target-handler responses, not CLI/gateway envelopes or live authorization proof. Synthetic users/organizations exist only in isolated fixtures. Ordinary examples use simulated user identity while real authority, SQL and resolution are exercised. Discover actual intended targets, save only reviewed input as payload.json, validate and inspect the unchanged plan before an authorized call. --confirm assumes intended approval. Stored rows and computed modes do not prove dispatch, receipt, timers or application action execution.

Omitted matching dimensions become wildcard and generate a new rule id

Input · payload.json:

{
  "orgId": "org_example",
  "rows": [
    {
      "delivery_mode": "digest",
      "required_action": false,
      "enabled": true
    }
  ]
}

CLI:

topolo actions validate app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --json
topolo actions plan app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json
topolo actions call app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json

MCP call after validation and plan review:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.organization_notification_action_policy.replace",
    "input": {
      "orgId": "org_example",
      "rows": [
        {
          "delivery_mode": "digest",
          "required_action": false,
          "enabled": true
        }
      ]
    },
    "confirm": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Organization action policy replaced",
  "data": {
    "orgId": "org_example",
    "rows": [
      {
        "rule_id": "nap_H9DRTJBOeOPn9ydR",
        "organization_id": "org_example",
        "user_id": null,
        "source_app_id": "*",
        "event_type": "*",
        "surface": "*",
        "category": "*",
        "priority": "*",
        "channel": "*",
        "delivery_mode": "digest",
        "required_action": false,
        "quiet_hours_json": null,
        "digest_json": null,
        "escalation_json": null,
        "quiet_hours": null,
        "digest": null,
        "escalation": null,
        "enabled": true,
        "updated_at": 1790972360
      }
    ]
  },
  "timestamp": "2026-10-02T00:00:00.000Z"
}

Repeating a full replacement creates a different rule id with the same semantic settings

Input · payload.json:

{
  "orgId": "org_example",
  "rows": [
    {
      "delivery_mode": "digest",
      "required_action": false,
      "enabled": true
    }
  ]
}

CLI:

topolo actions validate app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --json
topolo actions plan app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json
topolo actions call app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json

MCP call after validation and plan review:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.organization_notification_action_policy.replace",
    "input": {
      "orgId": "org_example",
      "rows": [
        {
          "delivery_mode": "digest",
          "required_action": false,
          "enabled": true
        }
      ]
    },
    "confirm": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Organization action policy replaced",
  "data": {
    "orgId": "org_example",
    "rows": [
      {
        "rule_id": "nap_SfATfU4PBhTYRflm",
        "organization_id": "org_example",
        "user_id": null,
        "source_app_id": "*",
        "event_type": "*",
        "surface": "*",
        "category": "*",
        "priority": "*",
        "channel": "*",
        "delivery_mode": "digest",
        "required_action": false,
        "quiet_hours_json": null,
        "digest_json": null,
        "escalation_json": null,
        "quiet_hours": null,
        "digest": null,
        "escalation": null,
        "enabled": true,
        "updated_at": 1790972360
      }
    ]
  },
  "timestamp": "2026-10-02T00:00:00.000Z"
}

Store a more-specific organization rule; a matching enabled user rule still outranks it

Input · payload.json:

{
  "orgId": "org_example",
  "rows": [
    {
      "delivery_mode": "digest",
      "required_action": false,
      "enabled": true
    },
    {
      "delivery_mode": "digest",
      "required_action": false,
      "enabled": true,
      "source_app_id": "app_topolo_campaigns",
      "event_type": "campaigns.send_completed"
    }
  ]
}

CLI:

topolo actions validate app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --json
topolo actions plan app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json
topolo actions call app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json

MCP call after validation and plan review:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.organization_notification_action_policy.replace",
    "input": {
      "orgId": "org_example",
      "rows": [
        {
          "delivery_mode": "digest",
          "required_action": false,
          "enabled": true
        },
        {
          "delivery_mode": "digest",
          "required_action": false,
          "enabled": true,
          "source_app_id": "app_topolo_campaigns",
          "event_type": "campaigns.send_completed"
        }
      ]
    },
    "confirm": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Organization action policy replaced",
  "data": {
    "orgId": "org_example",
    "rows": [
      {
        "rule_id": "nap_TCM2RthHRKAMGfbP",
        "organization_id": "org_example",
        "user_id": null,
        "source_app_id": "*",
        "event_type": "*",
        "surface": "*",
        "category": "*",
        "priority": "*",
        "channel": "*",
        "delivery_mode": "digest",
        "required_action": false,
        "quiet_hours_json": null,
        "digest_json": null,
        "escalation_json": null,
        "quiet_hours": null,
        "digest": null,
        "escalation": null,
        "enabled": true,
        "updated_at": 1790972360
      },
      {
        "rule_id": "nap_IExVJt9-5uAJux-v",
        "organization_id": "org_example",
        "user_id": null,
        "source_app_id": "app_topolo_campaigns",
        "event_type": "campaigns.send_completed",
        "surface": "*",
        "category": "*",
        "priority": "*",
        "channel": "*",
        "delivery_mode": "digest",
        "required_action": false,
        "quiet_hours_json": null,
        "digest_json": null,
        "escalation_json": null,
        "quiet_hours": null,
        "digest": null,
        "escalation": null,
        "enabled": true,
        "updated_at": 1790972360
      }
    ]
  },
  "timestamp": "2026-10-02T00:00:00.000Z"
}

Empty organization rows leaves the separate user scopes intact

Input · payload.json:

{
  "orgId": "org_example",
  "rows": []
}

CLI:

topolo actions validate app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --json
topolo actions plan app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json
topolo actions call app_topolo_auth.organization_notification_action_policy.replace --data @payload.json --confirm --json

MCP call after validation and plan review:

{
  "tool": "topolo_call_action",
  "arguments": {
    "actionId": "app_topolo_auth.organization_notification_action_policy.replace",
    "input": {
      "orgId": "org_example",
      "rows": []
    },
    "confirm": true
  }
}

Captured local handler response · HTTP 200:

{
  "success": true,
  "message": "Organization action policy replaced",
  "data": {
    "orgId": "org_example",
    "rows": []
  },
  "timestamp": "2026-10-02T00:00:00.000Z"
}

Verify the result

  • Read organization_notification_action_policy.list for the exact orgId and compare complete semantic settings, including disabled rows and JSON objects; new rule ids are expected.

  • Confirm separate user scopes remain and resolve intended event contexts. After any failure read actual rows before reviewing a retry.

Errors and recovery

Ordinary member cannot replace organization action policy

Captured local refusal · HTTP 401:

{
  "credentialPrerequisite": "simulated-user-token",
  "input": {
    "orgId": "org_example",
    "rows": [
      {
        "delivery_mode": "digest",
        "required_action": false,
        "enabled": true
      }
    ]
  },
  "response": {
    "success": false,
    "error": {
      "message": "Admin access required",
      "code": "AUTH_ERROR",
      "details": null,
      "timestamp": "2026-10-02T00:00:00.000Z"
    }
  }
}

Duplicate complete matching dimensions are refused before deleting old rules

Captured local refusal · HTTP 400:

{
  "credentialPrerequisite": "simulated-user-token",
  "input": {
    "orgId": "org_example",
    "rows": [
      {
        "delivery_mode": "digest",
        "required_action": false,
        "enabled": true
      },
      {
        "delivery_mode": "digest",
        "required_action": false,
        "enabled": true
      }
    ]
  },
  "response": {
    "success": false,
    "error": {
      "message": "Invalid action policy payload",
      "code": "VALIDATION_ERROR",
      "details": [
        "rows[1] duplicates action policy dimensions"
      ],
      "timestamp": "2026-10-02T00:00:00.000Z"
    }
  }
}

Sequential organization action-policy replacement can leave partial rules

Captured local refusal · HTTP 500:

{
  "credentialPrerequisite": "simulated-user-token",
  "input": {
    "orgId": "org_example",
    "rows": [
      {
        "delivery_mode": "digest",
        "required_action": false,
        "enabled": true
      },
      {
        "delivery_mode": "digest",
        "required_action": false,
        "enabled": true,
        "source_app_id": "app_topolo_campaigns"
      }
    ]
  },
  "response": {
    "success": false,
    "error": {
      "message": "Failed to replace organization notification action policy: Isolated second organization action-policy insert failure",
      "code": "SERVER_ERROR",
      "details": null,
      "timestamp": "2026-10-02T00:00:00.000Z"
    }
  }
}

Published recovery guidance

  • state_conflict (HTTP 409): Read the current resource state, reconcile the intended change, and retry only if still valid.

  • AUTH_ERROR (HTTP 401): Verify pinned identity and respect the exact user/organization boundary. A valid other-service signature is not Notify trust; do not switch credentials or infer OAuth expiry from an authenticated target denial.

  • VALIDATION_ERROR (HTTP 400): Correct reported fields and unique complete dimension keys. Ordinary user resolution must omit defaults/transactional; only an owning trusted Notify invocation may supply them and must provide explicit transactional true/false. Revalidate and review any replacement.

  • SERVER_ERROR (HTTP 500): Preserve request/result and read the exact stored scope. A replacement may have removed prior rows and inserted only part of the intended set; separately review finishing or restoring complete captured semantic settings. Do not rely on old rule ids.

Recovery boundaries

Capture full prior semantic rows before approval. Read actual stored state after any unknown result, then separately review restoring or finishing the complete intended set. Replacement creates new rule ids and may fail partially; it does not restore delivered/queued events, timers or action approvals.

Discovery and evidence

This record belongs to app_topolo_auth. Fixtures prove actual authority, action-policy validation, canonical notification/core SQL, scoring/required-action guard, address/timezone resolution and partial replacement behavior. User tokens/targets/unused addresses are simulated. Trusted Notify captures use real ephemeral local RSA generation, Auth minting and local JWKS verification; no live credential/key is read or serialized. Current Notify source informs optional metadata field meanings, while its scheduling/dispatch is not exercised. No live storage, ownership, gateway credential parity, notification, timer, deployment or production publication is proved.