publ betaReference (MCP guide)
Open publ console

design_access_apply

[Migration map](../reference/tool-name-migration.md).

# design_access_apply

Save the permission dialog's guest and member conditions together for one installed feature. Start with [get](publ_design_access_get.md), use its editable target and revision, and change only the conditions authorized by the user.

| Input | Contract |
| --- | --- |
| `requestId` | Required, 8–80 ASCII letters/digits/underscores/hyphens; retain it for the original task |
| `channelCode` | Required nonempty selected-channel code |
| `installedPAppId` | Required positive integer from an editable target |
| `expectedRevision` | Required 64 lowercase hexadecimal characters from that target's get |
| `guest` | Optional `NONE`, `INFO`, `CONTENT` or `PURCHASE`; choose only from returned `capabilities.guestModes` |
| `members` | Optional strict object: `{"mode":"ALL"}`, `{"mode":"SELECTED","membershipIds":[...]}` or `{"mode":"EVERY_MEMBERSHIP"}` |
| `dryRun` | Optional boolean, default false |

At least one of `guest` or `members` is required. SELECTED accepts at most 1,000 unique nonempty string IDs, including an empty array. ALL accepts no `membershipIds`. Unknown fields, including a direct `allowPAppUnlockAccess` override, are rejected. Candidate `mid` values are membership IDs, not member profile IDs or account IDs. Existing design-screen role and hosted scope requirements match get.

Plan a guest-only change, preserving member conditions:

```json
{"requestId":"access-guest-example-001","channelCode":"selected-channel-code","installedPAppId":123,"expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","guest":"NONE","dryRun":true}
```

Plan a member-only change, preserving guest and existing paid-entitlement access:

```json
{"requestId":"access-members-example-001","channelCode":"selected-channel-code","installedPAppId":123,"expectedRevision":"0000000000000000000000000000000000000000000000000000000000000000","members":{"mode":"SELECTED","membershipIds":["membership-mid-from-get"]},"dryRun":true}
```

All target IDs and zero-filled revisions are placeholders. Replace them from one current selected-target get. A fresh dry run compares that revision, derives the combined ACL and verifies all resulting membership IDs. It returns `PLAN_READY` with `before` and proposed `permissions`, without creating a job or sending a save. Reusing a matching existing task with `dryRun:true` instead returns its saved permissions, task identity and `existingPhase`; it does not refresh the ACL or validate that old plan against current state.

Omitted conditions preserve current values. Guest choices expand to their required access/consume/purchase flags. ALL enables general member access **and** paid-entitlement access and clears individual membership links. SELECTED disables general member access, replaces links with the supplied IDs and preserves the existing paid-entitlement flag. Therefore SELECTED with an empty list does not mean that every member or paying visitor is denied. EVERY_MEMBERSHIP disables general member access and links every general membership of the channel, as switching off "all members" does on the source screen; the plan lists the exact IDs before any save. The complete [permission rules](../guides/access-control.md#permission-rules) cover feature-specific guest options.

A real apply persists a `prepared` job, rechecks target/system-monitoring identity and the original revision, then dispatches the necessary combined ACL save. If the current ACL already equals the requested body, it skips the POST and still compares hierarchy permissions. A non-no-op also verifies selected membership details immediately before sending. A no-op may complete as `ACCESS_APPLIED`; this code alone does not prove a write occurred.

Completion requires matching ACL summary and hierarchy permissions, unchanged target identity and unchanged monitoring digest. The result contains task ID, `taskType:"access-control"`, environment, channel, installed ID, resulting permissions and revision, with `verification.confirmed:true`, `method:"acl_summary_and_hierarchy_read"` and `effectiveVisitorAccess:false`. It confirms stored ACL state, not visitor entry or preservation of every unrelated channel field. The workflow does not perform an automatic rollback or credential refresh.

Recover through [status](publ_task_status.md) and [resume](publ_task_resume.md) using the same task ID/type. A `prepared` task can still save; an uncertain `sending` task only reconciles by reads and never repeats that save. A definite rejected/not-sent save can return to prepared. Completed replays return historical results. See [recovery](../guides/access-control.md#recover-the-original-task), including revision conflicts, mismatched hierarchy and changed monitoring grants. Do not use a new request ID to bypass an uncertain outcome.

## Saved plan replay

For an existing matching task, `dryRun` returns historical saved plan/progress,
with `historical:true`, `replayed:true` and false current verification. It performs
no fresh business-state or eligibility check, no business effect and no journal
change. Current console/action links are omitted. Follow `nextStep` for the same
target's current state; keep the original task ID for uncertain-outcome recovery.
Context and saved-input checks still apply. A new task follows normal fresh-plan
validation; another request ID must not bypass an uncertain effect.

`nextStep` uses design_access_get with the original channelCode and installedPAppId; saved permissions and existingPhase are retained.

## Saved record identity

For this workflow, existing apply/dry-run calls and task status/resume require
both the saved task ID and saved input requestId to match the requested ID.
A mismatching or malformed saved identity returns `TASK_STATE_UNVERIFIED` before
workflow context or business-state reads. Preserve the original record for
inspection; do not rename/edit it, try its embedded ID or create a replacement
request to bypass the error. No stored record is repaired or resent. Valid
matching records retain the recovery behavior described above.

## Installed-feature policy

Installed pApps are enabled by installation. There is no separate activation or
deactivation step. Use this ACL workflow for supported visitor-access changes;
uninstall is not an access-control substitute. A failed read leaves permissions
unverified and unchanged, not denied. SELECTED with no memberships still preserves
existing paid-entitlement access and must not be reported as blocking everyone.

## Inputs

| Field | Required here | Contract |
| --- | --- | --- |
| `guest` | No | `NONE`, `INFO`, `CONTENT`, `PURCHASE` |
| `members` | No | one of: `ALL`, `SELECTED`, `EVERY_MEMBERSHIP` (by `mode`) |
| `members.membershipIds` | Yes | When `members.mode` is `SELECTED`: array; items: string; min length 1; max items 1000 |
| `app` | Yes | string; The feature screen: its code (for example A00002) or its exact title from the targets list. |
| `confirmToken` | No | string; pattern `^ct[a-f\d]{32}$`; The confirmToken from this tool's preview result. Omit it to preview; repeat the same input with it to execute. |