# An access receipt can succeed with an extra permission

Source: https://rasa.community/library/casebook/internal-it-helpdesk/
Author: Rod Rivera
Published: 2026-10-08T09:00:00Z

The status check passes. The permission-delta assertion fails:

```text
{"added_roles": ["FIN-REPORTS-VIEW", "PROD-DB-ADMIN"], "delta_check": "FAIL: expected viewer only; got ['FIN-REPORTS-VIEW', 'PROD-DB-ADMIN']", "status_check": "PASS", "variant": "extra_permission"}
```

We injected database administrator access into a temporary copy of the Orchard Works grant function. The receipt still confirms the approved finance viewer role. The independent assertion sees both roles. This is a deliberate mutation over invented employees, not a defect observed in a real directory.

## What the acknowledgement checks

The original function adds only the requested role. The experiment changes that one append to add the administrator role too; approval and receipt code stay untouched. The [pinned read-back method](https://github.com/RasaHQ/rasa-community-resources/blob/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-internal-it-helpdesk-claude/lib/helpdesk.py#L232-L240) checks the fixture's acknowledgement mode:

```python
def read_back(self, change_ref: str, role_ref: str) -> bool:
    self._reads[change_ref] = self._reads.get(change_ref, 0) + 1
    mode = self.roles[role_ref]["directory"]
    if mode == "ok":
        return True
    if mode == "ack_lost":
        return self._reads[change_ref] >= 2
    return False
```

That true result does not compare the entire permission difference with approval. The receipt computes one out-of-scope change under the mutation but still says succeeded and reconciled. Reading only its status and declared viewer scope therefore misses the violation.

## Execute the assertion that catches the extra role

Use the [pinned helpdesk example](https://github.com/RasaHQ/rasa-community-resources/tree/4aa0c4419dc193fef7a969c12d59edcf720f2606/examples/mantle-text-internal-it-helpdesk-claude). Its `test_grant_adds_exactly_the_approved_role` checks the added-role set; `test_case_metric_zero_out_of_scope_for_every_approved_role` checks all approved roles. Our selected `GrantTests` and `StatusAndRoutingTests` receipt contains 11 passing checks without skips.

The following local experiment executes the viewer-delta assertion against both the original function and its temporary mutant. It catches the expected assertion error only to print both outcomes. It does not edit the companion source file or contact a directory.

Save this as `/tmp/internal-it-helpdesk-check.py`. From the example's `tests` directory, run `PYTHONPATH=. python3 -B /tmp/internal-it-helpdesk-check.py`:

```python
import inspect
import json
import test_guard as t
h = t.hd
source = inspect.getsource(h.grant_access)
old = 'service.employees[subject]["access"].append(role_ref)'
assert source.count(old) == 1
namespace = dict(h.__dict__)
exec(compile(source.replace(old, 'service.employees[subject]["access"].extend([role_ref, "PROD-DB-ADMIN"])'), "<extra-permission experiment>", "exec"), namespace)
for name, grant in (("original", h.grant_access), ("extra_permission", namespace["grant_access"])):
    svc = t.fresh()
    opened = t.ready(svc, "viewer on finance reporting")
    before = set(svc.access(t.ME))
    result = grant(svc, t.ME, opened["ticket_ref"], opened["ticket_ref"], conversation_id="catchup")
    added = sorted(set(svc.access(t.ME)) - before)
    try:
        assert added == ["FIN-REPORTS-VIEW"], f"expected viewer only; got {added}"
        delta_check = "PASS"
    except AssertionError as error:
        delta_check = f"FAIL: {error}"
    print(json.dumps({"variant": name, "status_check": "PASS" if result["status"] == "succeeded" else "FAIL", "delta_check": delta_check, "added_roles": added}, sort_keys=True))
```

Actual offline output:

```text
{"added_roles": ["FIN-REPORTS-VIEW"], "delta_check": "PASS", "status_check": "PASS", "variant": "original"}
{"added_roles": ["FIN-REPORTS-VIEW", "PROD-DB-ADMIN"], "delta_check": "FAIL: expected viewer only; got ['FIN-REPORTS-VIEW', 'PROD-DB-ADMIN']", "status_check": "PASS", "variant": "extra_permission"}
```

Status passes in both variants. The added-role assertion passes only in the baseline. Keep both checks: this is not a reason to remove acknowledgement checks, but a reason to stop treating them as a scope check.

## A failed check still leaves access to recover

:::diagram{title="Checking after a write needs an authorised recovery path"}

```dot
rankdir=TB;
a [label="Approved viewer request"];
b [label="Write adds viewer and admin"];
c [label="Receipt acknowledgement passes"];
d [label="Independent delta assertion fails"];
e [label="Access owner investigates
uses approved revocation process"];
a -> b;
b -> c;
b -> d;
d -> e [label="extra access already exists"];
```

:::

For a production adapter, compare effective permissions read independently from the directory, including inherited scope. Assert the added set equals approval and unexpected removals are empty. Assign independent directory reads and reconciliation to the adapter owner. This is a recommended contract; no production adapter or overhead comparison was tested here.

Here the check happens after a side effect. Refusing the conversational result cannot undo the grant. Alert the access owner and use the authorised recovery process; do not improvise revocation or claim nothing changed. A production rollback path needs its own permissions and evidence.

:::checkpoint{id="access-receipt-delta" question="The receipt says succeeded and viewer, but records one out-of-scope change. Is status enough to pass acceptance?" options="Yes, because the directory acknowledged the write|No, the actual permission delta exceeds approval|Yes, if the caller asked urgently" answer="1"}
:::solution{title="Test the entire permission difference"}
Fail the independent delta assertion and handle the extra access through the authorised recovery process. The declared scope cannot erase the observed change.
:::
:::

:::cta{href="/library/tutorials/guarding-irreversible-actions/" label="Check execution boundaries before a side effect"}
The execution-guard lesson separates permission to act from a receipt reporting an action.
:::

The experiment does not exercise a model, identity provider, inherited-role graph, or revocation API. Those need separate adapter tests.